/*
This file is part of the software framework JStatCom,
a library for building applications for data based analysis.
Copyright (C) 2000-2005 Alexander Benkwitz, Markus Kraetzig
This library is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation; either version 2.1 of the
License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
USA
*/
package com.jstatcom.component;
import java.awt.AWTEvent;
import java.awt.Color;
import java.awt.Dialog;
import java.awt.Frame;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.AWTEventListener;
import java.awt.event.FocusEvent;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.text.Keymap;
/**
* Performs input validation before input focus is permanently
* moved away to ensure that no action is carried out, before
* input has been checked. Subclasses need to implement
* the respective validation mechanism and user notification in
* case of an error. The input is only validated, if the text
* has been changed since it was validated the last time.
*
* @author A. Benkwitz, Markus Kraetzig
*/
public abstract class IValTextField extends JTextField {
/**
* The parent frame for the possible error message.
*/
private Frame parentFrame = null;
private Dialog parentDialog = null;
private boolean isEditing = false;
private boolean isHandlingError = false;
/**
* String that holds the last text that was valid. Input validation
* is only invoked, if the text hast changed in the meantime. This is
* to avoid afluent calls to the validation routine if nothing changed
* from the last valid state.
*/
protected String lastValidText = "";
//
private EventHandler eventHandler = null;
private class EventHandler implements AWTEventListener {
public void eventDispatched(AWTEvent event) {
if (event.getSource().equals(IValTextField.this))
return;
// Mouse is clicked over another component, show message
// and consume the event, if input was not valid.
String err = null;
int id = event.getID();
switch (id) {
case (MouseEvent.MOUSE_PRESSED) :
err = validateInputEx();
if (err != null) {
errorHandleImplementation(err);
((InputEvent) event).consume();
}
}
}
}
/**
* IValTextField constructor.
*/
public IValTextField() {
super();
}
/**
* Editing finished. Set text back to its original value and set
* focus to root component.
*/
private void cancelEdit() {
if (isEditing) {
isEditing = false;
Toolkit.getDefaultToolkit().removeAWTEventListener(
getAWTEventListener());
setText(lastValidText);
if (hasFocus()) {
SwingUtilities.getRootPane(this).grabFocus();
}
}
}
/**
* Triggers message.
*
* @param err error String
*/
private void errorHandleImplementation(String err) {
isHandlingError = true;
String error = err;
handleError(error);
isHandlingError = false;
}
/**
* Component gained focus, set editing true and add
* AWTEventListener.
*/
protected void focusGained() {
if (isEnabled() && isEditing == false) {
isEditing = true;
Toolkit.getDefaultToolkit().addAWTEventListener(
getAWTEventListener(),
AWTEvent.MOUSE_EVENT_MASK);
}
}
/**
* Component lost focus, set editing false and remove
* AWTEventListener.
*/
protected void focusLost() {
if (isEditing) {
isEditing = false;
Toolkit.getDefaultToolkit().removeAWTEventListener(
getAWTEventListener());
if (!isHandlingError) {
String err = null;
if ((err = validateInputEx()) != null) {
errorHandleImplementation(err);
return;
}
}
}
}
/**
* Lazily initialize the listener.
*
* @return AWTEventListener
*/
private AWTEventListener getAWTEventListener() {
if (eventHandler == null)
eventHandler = new EventHandler();
return eventHandler;
}
/**
* Called by the validation mechanism if an event possibly moves input focus
* away from this component but input validation fails (validateInput() != null).
* The default implementation displays the error message msg, but this can
* be overwritten by subclasses.
*
* @param msg error message returned from validateInput
*/
public void handleError(String msg) {
setBackground(CompSettings.errorBackground);
if (parentFrame != null)
StdMessages.errorInput(msg, parentFrame);
else if (parentDialog != null)
StdMessages.errorInput(msg, parentDialog);
else
StdMessages.errorInput(msg);
setBackground(Color.WHITE);
}
/**
* Calls supermethod and finds window to set as parent
* for potential error messages.
*/
public void addNotify() {
super.addNotify();
Window window = SwingUtilities.getWindowAncestor(this);
if (window instanceof JFrame)
parentFrame = (JFrame) window;
if (window instanceof JDialog)
parentDialog = (JDialog) window;
}
/**
* Returns whether this textfield is currently edited.
*
* @return true if currently edited
*/
public final boolean isEditing() {
return isEditing;
}
/**
* Make sure that no action is triggered by a keystroke,
* before the input was validated.
*
* @param key event
*/
private void prepareKeyEvent(KeyEvent e) {
String err = null;
if (e.getKeyCode() == KeyEvent.VK_TAB || e.getKeyChar() == '\t') {
if ((err = validateInputEx()) != null) {
errorHandleImplementation(err);
e.consume();
return;
}
} else if (
e.getKeyCode() == KeyEvent.VK_ENTER
&& e.getID() != KeyEvent.KEY_PRESSED) {
if ((err = validateInputEx()) != null) {
errorHandleImplementation(err);
e.consume();
return;
}
} else if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
cancelEdit();
}
// Is there an action with this component?
boolean componentAction = false;
Keymap binding = getKeymap();
if (binding != null) {
KeyStroke k = KeyStroke.getKeyStrokeForEvent(e);
componentAction = (binding.getAction(k) != null);
}
// Maybe some other component wants to trigger an action
boolean maybeTriggerMenu =
(e.getModifiers() == InputEvent.ALT_GRAPH_MASK);
maybeTriggerMenu =
maybeTriggerMenu || (e.getModifiers() == InputEvent.ALT_MASK);
maybeTriggerMenu =
maybeTriggerMenu || (e.getModifiers() == InputEvent.META_MASK);
if (componentAction == false && !e.isConsumed() && maybeTriggerMenu) {
if ((err = validateInputEx()) != null) {
e.consume();
boolean cb = Character.isLetterOrDigit(e.getKeyChar());
if (e.getID() == KeyEvent.KEY_PRESSED && cb) {
errorHandleImplementation(err);
return;
}
}
}
}
/**
* Adds specific behaviour for FOCUS_GAINED and FOCUS_LOST events.
*
* @param e focus event
*/
protected void processFocusEvent(FocusEvent e) {
super.processFocusEvent(e);
switch (e.getID()) {
case FocusEvent.FOCUS_GAINED :
focusGained();
break;
case FocusEvent.FOCUS_LOST :
focusLost();
break;
}
}
/**
* Adds specific behaviour if special keys are pressed that might
* invoke actions on different components.
*
* @param e event
*/
protected final void processKeyEvent(KeyEvent e) {
prepareKeyEvent(e);
super.processKeyEvent(e);
}
/**
* Sets the text but preserves selection and caret position
* if possible. The text should be valid, because it also updates
* the lastValidText field. Input validation is only
* done, when the new text differs from this to avoid afluent
* calls to the validation routine.
*
* @param text the string to set
*/
public void setTextCarefully(String text) {
int selStart = getSelectionStart();
int selEnd = getSelectionEnd();
int pos = getCaretPosition();
setText(text);
int ll = getText().length();
// Set caret again.
if (pos < ll)
setCaretPosition(pos);
// Set selection again.
if (selStart < ll)
setSelectionStart(selStart);
if (selEnd < ll)
setSelectionEnd(selEnd);
else
setSelectionEnd(ll);
lastValidText = getText();
}
/**
* Should contain the algorithm for input validation.
* Subclasses must provide an implementation for this method.
* Any text formatting after the validation should be done within
* this method as well.
*
* @return should return null if ok, error message otherwise
*/
public abstract String validateInput();
/**
* Triggers input validation if the underlying text has changed.
*
* @return should return null if ok, error message otherwise
*/
private String validateInputEx() {
// Validate only if text has changed.
if (lastValidText.equals(getText()))
return null;
String err = null;
try {
err = validateInput();
if (err == null)
lastValidText = getText();
} catch (Throwable ex) {
err = ex.getMessage();
}
return err;
}
}
Ads help maintain this website.