/*******************************************************************************
* Copyright (c) 2005, 2009 Eric Wuillai.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Eric Wuillai (eric@wdev91.com) - initial API and implementation
* Peter Schulz (eclipse-ps@kurzepost.de) - fix for bug 459484
*******************************************************************************/
package org.eclipse.nebula.widgets.formattedtext;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Text;
/**
* Formatted text viewer. Add formating capabilities to the <code>Text</code>
* widget of SWT. This control works on the same principle than the JFace
* viewers. The embedded text widget is accessible by the getControl() method,
* allowing to apply to it all necessary behaviors (layout, listeners...).<p>
*
* Formatting is delegated to formatter objects implementing the <code>ITextFormatter</code>
* interface. Each formatter class manages a base class of values (date, number...).<br>
* Formatters are associated by 2 different means :
* <ul>
* <li>By the <code>setFormatter()</code> method.</li>
* <li>When <code>setValue()</code> is called and there is currently no formatter,
* a new one is automatically created based on the class of the value.</li>
* </ul>
*
* <h4>Styles:</h4>
* <blockquote>
* CENTER, LEFT, RIGHT, READ_ONLY
* </blockquote>
*/
public class FormattedText {
/** The key used to register the FormattedText in its Text widget data */
public static final String TEXT_DATA_KEY = "formattedText";
/** Encapsulated Text widget */
protected Text text;
/** Formatter */
protected ITextFormatter formatter = null;
/** Save position of cursor when the focus is lost */
protected int caretPos;
/** Layout */
protected GridLayout layout;
/** Filter for modify events */
protected Listener modifyFilter;
/** Flag to activate or not beep sound on input errors */
protected static boolean beepSound = true;
protected static int count = 0;
protected int id = ++count;
/**
* Creates a formatted text on a newly-created text control under the given
* parent. The text control is created using the SWT style bits
* <code>BORDER</code>.
*
* @param parent the parent control
*/
public FormattedText(Composite parent) {
this(parent, SWT.BORDER);
}
/**
* Creates a formatted text on a newly-created text control under the given
* parent. The text control is created using the given SWT style bits.
*
* @param parent the parent control
* @param style the SWT style bits used to create the text
*/
public FormattedText(Composite parent, int style) {
this(new Text(parent, style & (~ (SWT.MULTI | SWT.PASSWORD | SWT.WRAP))));
}
/**
* Creates a formatted text on the given text control.
*
* @param t the text control
*/
public FormattedText(Text t) {
text = t;
text.setData(TEXT_DATA_KEY, this);
text.addFocusListener(new FocusListener() {
public void focusGained(FocusEvent e) {
if ( formatter != null && text.getEditable() ) {
formatter.setIgnore(true);
setText(formatter.getEditString());
text.setSelection(caretPos);
formatter.setIgnore(false);
}
}
public void focusLost(FocusEvent e) {
if ( formatter != null && text.getEditable() ) {
formatter.setIgnore(true);
caretPos = text.getCaretPosition();
String editString = formatter.getEditString();
String displayString = formatter.getDisplayString();
// Detect inconsistency between internal representation,
// for example, a date, and contents of the text control
if (!editString.equals(displayString)) {
// Update the formatter (caches) so it has a consistent state
formatter.setValue(formatter.getValue());
}
setText(displayString);
formatter.setIgnore(false);
}
}
});
modifyFilter = new Listener() {
public void handleEvent(Event event) {
event.type = SWT.None;
}
};
text.addDisposeListener(new DisposeListener() {
public void widgetDisposed(DisposeEvent e) {
text = null;
modifyFilter = null;
formatter = null;
}
});
}
/**
* Returns the primary <code>Text</code> control associated with this viewer.
*
* @return the SWT text control which displays this viewer's content
*/
public Text getControl() {
return text;
}
/**
* Returns the formatter associated to the <code>Text</code> widget.
*
* @return Formatter, or <code>null</code> if no formatter is currently associated
*/
public ITextFormatter getFormatter() {
return formatter;
}
/**
* Returns the current value of the widget.<p>
*
* The returned value is provided by the formatter and is of the type managed
* by the formatter. For example a <code>DateFormatter</code> will return a
* <code>Date</code> value.<br>
* If no formatter is associated, the <code>String</code> contained in the
* <code>Text</code> widget is returned.
*
* @return Current value
*/
public Object getValue() {
return formatter != null ? formatter.getValue() : text.getText();
}
/**
* Returns the type of value the {@link ITextFormatter} associated with this
* FormattedText handles,
* i.e. returns in {@link #getValue()}.
*
* @return The value type.
*/
public Class getValueType() {
return formatter != null ? formatter.getValueType() : String.class;
}
/**
* Returns true if beep sound must be produced on input errors, else false.
*
* @return true / false
*/
public static boolean isBeepSound() {
return beepSound;
}
/**
* Returns <code>true</code> if the current value is empty, else
* <code>false</code>.<br>
* An empty value depends of the formatter applied on the Text widget
* and is not just an empty String in the widget. Formatters can use special
* formatting characters to format the value. These characters are not
* always considered as part of the value. For example, in a DateFormatter
* the pattern uses "/" separator and always displays it in the input field.
*
* @return <code>true</code> if empty.
*/
public boolean isEmpty() {
return formatter != null ? formatter.isEmpty() : text.getText().length() == 0;
}
/**
* Returns <code>true</code> if the current value is valid, else <code>false</code>.
*
* @return <code>true</code> if valid.
*/
public boolean isValid() {
return formatter != null ? formatter.isValid() : true;
}
/**
* Set the beep sound to ON or OFF for all the FormattedText fields. Beep
* sound are produced by formatters on every input error. Formatter
* implementation must check this flag on before to emit a beep sound.
*
* @param beepSound true to emit beep sound on errors, else false
*/
public static void setBeepSound(boolean beepSound) {
FormattedText.beepSound = beepSound;
}
/**
* Associates a formatter to the widget.<br>
* Parameter can not be null. In some situations, the FormattedText component
* must not do formatting (eg. when reusing the same object for editing of
* different types of values). In this case, use a StringFormatter. This
* formatter do no formatting.
*
* @param formatter formatter
*/
public void setFormatter(ITextFormatter formatter) {
if ( formatter == null ) SWT.error(SWT.ERROR_NULL_ARGUMENT);
if ( this.formatter != null ) {
text.removeVerifyListener(this.formatter);
this.formatter.detach();
}
this.formatter = formatter;
this.formatter.setText(text);
text.addVerifyListener(this.formatter);
formatter.setIgnore(true);
text.setText(formatter.getDisplayString());
formatter.setIgnore(false);
}
/**
* Sets the Text widget value, preventing fire of Modify events.
*
* @param value The String value to display in the widget
*/
private void setText(String value) {
Display display = text.getDisplay();
try {
display.addFilter(SWT.Modify, modifyFilter);
text.setText(value);
} finally {
display.removeFilter(SWT.Modify, modifyFilter);
}
}
/**
* Sets a new value.<p>
*
* If no formatter is currently associated to he widget, a new one is created
* by the factory based on the value's class.<br>
* If the value is incompatible with the formatter, an <code>IllegalArgumentException</code>
* is returned.
*
* @param value new value
*/
public void setValue(Object value) {
if ( formatter == null ) {
setFormatter(DefaultFormatterFactory.createFormatter(value));
}
formatter.setValue(value);
formatter.setIgnore(true);
text.setText(text.isFocusControl()
? formatter.getEditString()
: formatter.getDisplayString());
formatter.setIgnore(false);
}
}