/*
JInvidChooser.java
A fancy custom JComboBox thing for Scalar Invid fields.
Created: 26 October 1999
Module By: Michael 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.ganymede.client;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.Vector;
import javax.swing.ComboBoxEditor;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.MutableComboBoxModel;
import arlut.csd.JDataComponent.JPanelCombo;
import arlut.csd.JDataComponent.listHandle;
import arlut.csd.ganymede.common.Invid;
import arlut.csd.Util.TranslationService;
/*------------------------------------------------------------------------------
class
JInvidChooser
------------------------------------------------------------------------------*/
/**
* A GUI component for choosing an Invid for a scalar invid_field.
*
* @author Jonathan Abbey
*/
public class JInvidChooser extends JPanelCombo implements ActionListener, ItemListener, FocusListener {
private final static boolean debug = false;
/**
* TranslationService object for handling string localization in the
* Ganymede client.
*/
static final TranslationService ts = TranslationService.getTranslationService("arlut.csd.ganymede.client.JInvidChooser");
// ---
JButton
view;
containerPanel
cp;
private short
type;
private boolean
removedNone = false,
allowNone = true;
private listHandle
noneHandle = new listHandle(ts.l("global.none"), null); // "<none>"
JInvidChooserFieldEditor editor;
/* -- */
/**
* @param parent The general or embedded object panel that contains us
* @param objectType object type number, used to support creating a new
* object by the use of the 'new' button if enabled.
*/
public JInvidChooser(containerPanel parent, short objectType)
{
this(null, parent, objectType);
}
/**
* @param objects A vector of {@link arlut.csd.JDataComponent.listHandle listHandle}
* objects representing labeled Invid choices for the user to choose among.
* @param parent The general or embedded object panel that contains us
* @param objectType object type number, used to support creating a new
* object by the use of the 'new' button if enabled.
*/
public JInvidChooser(Vector objects, containerPanel parent, short objectType)
{
super(objects);
cp = parent;
type = objectType;
JPanel buttonPanel = new JPanel();
buttonPanel.setLayout(new BorderLayout());
// "View"
view = new JButton(ts.l("global.view_button"));
view.addActionListener(this);
view.addFocusListener(this);
editor = new JInvidChooserFieldEditor(this);
getCombo().setEditor(editor);
getCombo().setEditable(true);
getCombo().addItemListener(this);
if (getSelectedInvid() == null)
{
view.setEnabled(false);
}
buttonPanel.add("West", view);
// JPanelCombo already added the combo to the west.
add("East", buttonPanel);
}
public Invid getSelectedInvid()
{
listHandle lh = (listHandle) getSelectedItem();
if (lh == null)
{
return null;
}
if (editor != null && editor.theField != null)
{
if (!lh.toString().equals(editor.theField.getText()))
{
System.err.println("JInvidChooser: " + lh.toString() + " does not equal " +
editor.theField.getText());
return null;
}
}
return (Invid) lh.getObject();
}
/**
* <p>Set the allowNone bit.</p>
*
* @param allow If true, then <none> will remain as a choice
* in the chooser. If false, <none> will only be included in
* the beginning if nothing is set; it will be removed as soon as
* anything is chosen.
*/
public void setAllowNone(boolean allow)
{
if (debug)
{
System.out.println("JInvidChooser: setAllowNone(" + allow +")");
}
// If we used to allow, but now we don't, we need to take out the
// noneHandle if it is not selected.
if (allowNone && (!allow) && (!removedNone))
{
Object item = getCombo().getSelectedItem();
if ((item != null) && (!item.equals(noneHandle)))
{
if (debug)
{
System.out.println("taking out <none>");
}
try
{
getCombo().removeItem(noneHandle);
removedNone = true;
if (debug)
{
System.out.println("+setting removedNone to true");
}
}
catch (IllegalArgumentException ia)
{
// none handle wasn't in there...
removedNone = false;
}
}
else if (debug)
{
System.out.println("<none> is selected, I will wait.");
}
}
// Now if we are allowing none, but we weren't before, and we took
// the none handle out, we have to put it back in
if (removedNone && allow && !allowNone)
{
boolean found = false;
for (int i = 0; i < getCombo().getItemCount(); i++)
{
if (getCombo().getItemAt(i).equals(noneHandle))
{
found = true;
break;
}
}
if (!found)
{
if (debug)
{
System.out.println("Putting none back in.");
}
getCombo().addItem(noneHandle);
}
removedNone = false;
if (debug)
{
System.out.println("+setting removedNone to false");
}
}
allowNone = allow;
}
/**
* <p>Get the allowNone bit.</p>
*
* <p>If allowNone is true, then <none> will remain as a
* choice in the chooser. If it is false, <none> will only be
* included in the beginning if nothing is set; it will be removed
* as soon as anything is chosen.</p>
*/
public boolean isAllowNone()
{
return allowNone;
}
/**
* <p>This method is used to change the dynamic label of an object in
* this JInvidChooser.</p>
*
* @param invid The Invid of the object that we're being told to
* change.
* @param newLabel The new label to be displayed for the provided
* invid.
*/
public void relabelObject(Invid invid, String newLabel)
{
MutableComboBoxModel model = (MutableComboBoxModel) getModel();
synchronized (model)
{
for (int i = 0; i < model.getSize(); i++)
{
listHandle lh = (listHandle) model.getElementAt(i);
if (lh != null && lh.getObject() != null && lh.getObject().equals(invid))
{
model.removeElementAt(i);
lh.setLabel(newLabel);
model.insertElementAt(lh, i);
repaint();
break;
}
}
}
}
public void setSelectedItem(Object o)
{
if (o == null && isAllowNone())
{
getCombo().setSelectedItem(noneHandle);
}
else
{
getCombo().setSelectedItem(o);
}
}
/**
* ItemListener method
*
* @see java.awt.event.ItemListener
*/
public void itemStateChanged(ItemEvent e)
{
// keep non selection events to ourselves
if (e.getStateChange() != ItemEvent.SELECTED)
{
return;
}
if (debug)
{
System.err.println("JInvidChooser.itemStateChanged(" + e.toString() + ")");
}
view.setEnabled(getSelectedInvid() != null);
}
/**
* ActionListener method
*
* @see java.awt.event.ActionListener
*/
public void actionPerformed(ActionEvent e)
{
if (e.getSource() == view)
{
listHandle lh = (listHandle) getSelectedItem();
if (lh != null)
{
Invid invid = (Invid) lh.getObject();
if (invid == null)
{
/* XXX I don't think this can ever occur.. */
// "You don''t have permission to view {0}."
showErrorMessage(ts.l("actionPerformed.permissions_error", lh));
}
else
{
cp.gc.viewObject(invid);
}
}
}
}
public void focusLost(FocusEvent e)
{
if (debug)
{
System.out.println("StringSelector: focusLost");
}
}
public void focusGained(FocusEvent e)
{
if (debug)
{
System.out.println("focusGained");
}
JComponent parent = (JComponent) this.getParent();
if (parent != null)
{
parent.scrollRectToVisible(this.getBounds());
}
}
private final void showErrorMessage(String message)
{
cp.getgclient().showErrorMessage(message);
}
}
/*------------------------------------------------------------------------------
class
JInvidChooserFieldEditor
------------------------------------------------------------------------------*/
/**
* A combobox editor class to provide intelligent keyboard handling for
* the {@link arlut.csd.ganymede.client.JInvidChooser JInvidChooser} scalar
* invid field gui component.
*/
class JInvidChooserFieldEditor extends KeyAdapter implements ComboBoxEditor, ActionListener {
static final boolean debug = false;
// ---
JTextField theField;
Object curItem;
JComboBox box;
JInvidChooser chooser;
Vector actionListeners = new Vector();
String lastGoodString = null;
boolean lastGoodMatched = false;
int lastGoodIndex = -1;
/* -- */
public JInvidChooserFieldEditor(JInvidChooser chooser)
{
this.chooser = chooser;
this.box = chooser.getCombo();
this.box.addFocusListener(chooser);
theField = new JTextField();
theField.addKeyListener(this);
theField.addActionListener(this);
theField.addFocusListener(chooser);
}
public void setItem(Object anObject)
{
curItem = anObject;
if (curItem != null)
{
String str;
str = curItem.toString();
theField.setText(str);
lastGoodString = str;
lastGoodMatched = true;
}
else
{
theField.setText("");
lastGoodString = "";
lastGoodMatched = false;
}
}
public Component getEditorComponent()
{
return theField;
}
public Object getItem()
{
return box.getSelectedItem();
}
public void selectAll()
{
theField.selectAll();
}
public void addActionListener(ActionListener l)
{
actionListeners.addElement(l);
}
public void removeActionListener(ActionListener l)
{
actionListeners.removeElement(l);
}
/**
* <p>Tap into the text field's key release to see if we can
* complete the user's selection. Note that we are doing this
* without synchronizing on the text field's own user interface.. to
* do this properly, we might ought to be doing this with a document
* model on the text field, but this works ok. Since we're keying
* on key release, we can expect to be called after the text field
* has processed the key press.</p>
*
* @param ke The key release event that we're going to react to
*/
public void keyReleased(KeyEvent ke)
{
int curLen;
String curVal;
JButton viewButton = chooser.view;
/* -- */
curVal = theField.getText();
if (curVal != null)
{
curLen = curVal.length();
}
else
{
curLen = 0;
}
// ignore arrow keys, delete, shift
int keyCode = ke.getKeyCode();
switch (keyCode)
{
case KeyEvent.VK_UP:
case KeyEvent.VK_DOWN:
case KeyEvent.VK_LEFT:
case KeyEvent.VK_RIGHT:
case KeyEvent.VK_SHIFT:
return;
case KeyEvent.VK_DELETE:
case KeyEvent.VK_BACK_SPACE:
viewButton.setEnabled(false);
return;
}
if (curLen > 0)
{
String item;
int max = box.getItemCount();
int matching = 0;
String matchingItem = null;
int matchingIndex = -1;
for (int i = 0; i < max; i++)
{
item = box.getItemAt(i).toString();
if (item.equals(curVal))
{
// found it
lastGoodString = curVal;
lastGoodMatched = true;
lastGoodIndex = i;
chooser.cp.gc.setWaitCursor();
try
{
box.setSelectedIndex(lastGoodIndex);
}
finally
{
chooser.cp.gc.setNormalCursor();
}
viewButton.setEnabled(true);
return;
}
else if (item.startsWith(curVal))
{
matching++;
matchingItem = item;
matchingIndex = i;
lastGoodString = curVal;
lastGoodIndex = -1; // nothing definitively selected
}
}
if (matching == 1)
{
lastGoodIndex = matchingIndex;
lastGoodMatched = true;
setItem(matchingItem); // will set lastGoodString
chooser.cp.gc.setWaitCursor();
try
{
box.setSelectedIndex(lastGoodIndex);
}
finally
{
chooser.cp.gc.setNormalCursor();
}
theField.select(curLen, matchingItem.length());
viewButton.setEnabled(true);
return;
}
else if (matching == 0) // no match, don't let them have that char
{
// this is really kind of weak, since we're not actually
// rejecting this with a document model, but it seems to
// work..
Toolkit.getDefaultToolkit().beep();
if (lastGoodMatched)
{
chooser.cp.gc.setWaitCursor();
try
{
box.setSelectedIndex(lastGoodIndex);
}
finally
{
chooser.cp.gc.setNormalCursor();
}
setItem(box.getSelectedItem());
viewButton.setEnabled(true);
}
else
{
theField.setText(lastGoodString);
viewButton.setEnabled(false);
}
}
else
{
// too many matching, we don't yet have a unique prefix
lastGoodMatched = false;
lastGoodIndex = -1;
}
}
}
/**
* <p>Handle the user hitting return in the editable area.. if they
* hit return without a reasonable value, revert the combo.</p>
*
* @param e The ActionEvent propagated from our text editable area
*/
public void actionPerformed(ActionEvent e)
{
String value = theField.getText();
String item;
int max = box.getItemCount();
boolean found = false;
for (int i = 0; !found && i < max; i++)
{
item = box.getItemAt(i).toString();
if (item.equals(value))
{
found = true;
lastGoodIndex = i;
chooser.cp.gc.setWaitCursor();
try
{
box.setSelectedIndex(i); // this will cause the combo box to send an update
}
finally
{
chooser.cp.gc.setNormalCursor();
}
}
}
if (!found)
{
if (lastGoodMatched)
{
chooser.cp.gc.setWaitCursor();
try
{
box.setSelectedIndex(lastGoodIndex);
}
finally
{
chooser.cp.gc.setNormalCursor();
}
setItem(box.getSelectedItem());
chooser.view.setEnabled(true);
}
else
{
theField.setText(lastGoodString);
chooser.view.setEnabled(false);
}
}
}
}