/* JentryField.java JentryField serves as an abstract base class for all Fields that use textfields. The subclasses of this class should be used. Created: 12 Jul 1996 Module By: Navin Manohar ----------------------------------------------------------------------- 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.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; import javax.swing.JComponent; import javax.swing.JTextField; import javax.swing.text.AttributeSet; import javax.swing.text.BadLocationException; import javax.swing.text.PlainDocument; /*------------------------------------------------------------------------------ class JentryField ------------------------------------------------------------------------------*/ /** * <p>JentryField serves as an abstract base class for all GUI fields * in the client that use textfields. All text entry fields in the * Ganymede client are done using JentryField. Among other * responsibilities, JentryField is responsible for dispatching a * callback event when the user tabs out of a field he has been * entering text into.</p> * * <p>JentryField includes logic to make sure that the user can't type * invalid characters, nor too many characters, providing immediate * feedback if he tries.</p> * * <p>See this subclasses of this class for actual usable classes.</p> */ abstract public class JentryField extends JTextField implements FocusListener, ActionListener { static final boolean debug = false; // --- public boolean allowCallback = false; /** * <p>True if this JentryField is in the process of programmatically * loading text. If it is, we will not bother validating input * characters against our filters. Normally, of course, the * JentryDocument automatically filters out any character-inserting * keystrokes that the server has requested we filter.</p> * * <p>Any JentryField subclasses that provide programmatic data * setting methods should set this variable to true during the * course of the data setting, preferably in a try.. finally block * so that loadingText is guaranteed to be set false when it is * done.</p> */ protected boolean loadingText = false; /** * <p>If transferFocusOnEntry is true, this JentryField will * transfer focus to the next component in the container when enter * is hit.</p> * * <p>Should be set to false when a JentryField should retain focus * after hitting enter. E.g., when a JentryField is being used as * the text entry field for a StringSelector.</p> */ protected boolean transferFocusOnEntry = true; /** * <p>The custom arlut.csd.ganymede.JDataComponent JsetValueCallback * callback that we use to propagate the value change notice to.</p> */ protected JsetValueCallback my_parent = null; /** * <p>If notifier is not-null, a custom handler will be run when the * user hits enter. Otherwise, enter is treated as equivalent to * hitting tab, and the JsetValueCallback my_parent will be * called.</p> */ protected ActionListener notifier = null; /* -- */ ////////////////// // Constructors // ////////////////// public JentryField(int columns) { super(columns); setEditable(true); addFocusListener(this); addActionListener(this); // try to force a validate so that NT isn't so bitchy setColumns(columns); setDocument(new JentryDocument(this)); } /////////////////// // Class Methods // /////////////////// /** * Sets the parent of this component for callback purposes */ public void setCallback(JsetValueCallback parent) { if (parent == null) { throw new IllegalArgumentException("Invalid Parameter: parent cannot be null"); } my_parent = parent; allowCallback = true; } public void setEnterHandler(ActionListener listener) { this.notifier = listener; } /** * <p>sendCallback is called when focus is lost, or when we are * otherwise triggered.</p> * * @return -1 on change rejected, 0 on no change required, 1 on * change approved */ public abstract int sendCallback(); /** * <p>Returns true if this JentryField is in the process of * programmatically setting the text. While isLoading() returns * true, the JentryDocument that filters input into this field will * let all characters be set.</p> * * <p>The purpose of this is so that this GUI field will not reject * data that was previously loaded into the matching Ganymede * database field, even if it was in violation of the current * constraints.</p> */ public boolean isLoading() { return this.loadingText; } /** * <p>Stub function that is overriden in subclasses of JentryField. * The JentryDocument object for this field will use this method to * allow or disallow the character ch from being added.</p> */ public boolean isAllowed(char ch) { if (debug) { System.err.println("isAllowed in JentryField"); } return true; } /** * <p>Returns the maximum acceptable size of this field</p> * * <p>If no max size has been set, will return -1.</p> */ public int getMaxStringSize() { return -1; } /** * <p>Returns the current size of the contents of this gui field</p> */ public int getLength() { return 0; } public void focusLost(FocusEvent e) { if (debug) { System.err.println("JentryField: focusLost"); } sendCallback(); } public void focusGained(FocusEvent e) { if (debug) { System.err.println("focusGained"); } if (this.getParent() != null) { JComponent parent = (JComponent) this.getParent(); parent.scrollRectToVisible(this.getBounds()); } } /** * <p>Handle someone hitting enter.. try to update the value and notify * the actionListener if we succeeded.</p> */ public void actionPerformed(ActionEvent e) { if (debug) { System.err.println("enter hit"); } if (notifier == null) { if (transferFocusOnEntry) { transferFocus(); } } else if (sendCallback() >= 0) { if (debug) { System.err.println("enter approved"); } notifier.actionPerformed(e); } } } /*------------------------------------------------------------------------------ class JentryDocument ------------------------------------------------------------------------------*/ /** * <p>Helper class for {@link arlut.csd.JDataComponent.JentryField JentryField} * and its subclasses. JentryDocument is responsible for guaranteeing that * the user cannot enter invalid characters into a string field in the client, * nor type too many characters.</p> */ class JentryDocument extends PlainDocument { final static boolean debug = false; // --- private JentryField field; /* - */ public JentryDocument(JentryField field) { this.field = field; } public void insertString(int offset, String str, AttributeSet a) throws BadLocationException { StringBuffer buffer = new StringBuffer(); /* -- */ if (debug) { System.err.println("JentryDocument.insertString(" + str +")"); } if (field.isLoading()) { buffer.append(str); } else { for (int i = 0; i < str.length(); i++) { char c = str.charAt(i); if (!field.isAllowed(c) || (field.getMaxStringSize() != -1 && field.getMaxStringSize() - field.getLength() <= 0)) { if (debug) { System.err.println("Trying to reject character " + c); } Toolkit.getDefaultToolkit().beep(); } else { buffer.append(c); } } } if (buffer.length() != 0) { if (debug) { System.err.println("Inserting string " + buffer.toString()); } super.insertString(offset, buffer.toString(), a); } } }