/*
 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.