/**
* This file is part of HIDB2.
*
* HIDB2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* HIDB2 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 Public License for more details.
*
* You should have received a copy of the GNU Lesser Public License
* along with HIDB2. If not, see <http://www.gnu.org/licenses/>.
*/
package hidb2.gui.util;
import hidb2.kern.AttrType;
import hidb2.kern.HIDBConst;
import java.text.MessageFormat;
import java.util.logging.Logger;
import org.eclipse.core.runtime.Assert;
import org.eclipse.jface.viewers.CellEditor;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.FocusAdapter;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.TraverseEvent;
import org.eclipse.swt.events.TraverseListener;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Text;
/**
* Adaptation/Specialisation of TextCellEditor for Numeric Inputs.
* Type controlled : C_TYPE_INT, C_TYPE_FLOAT, C_TYPE_DOUBLE, C_TYPE_VECTOR3D
* Default : C_TYPE_DOUBLE
*/
public class NumericCellEditor extends CellEditor implements HIDBConst
{
final static Logger log = Logger.getLogger("hidb2.gui.util");
/** Characters allowed for numeric float input */
public final static String S_NUMDBLCHARS = "0123456789.,+-eE";
/** Characters allowed for numeric integer input */
public final static String S_NUMINTCHARS = "0123456789";
/** Character used to separate vector values */
public final static String S_VECTSEP = ";";
/** Characters allowed for numeric vector input */
public final static String S_NUMVECTCHARS = S_NUMDBLCHARS + S_VECTSEP;
/**
* The text control; initially <code>null</code>.
*/
protected Text text;
private ModifyListener modifyListener;
/**
* State information for updating action enablement
*/
private boolean isSelection = false;
private boolean isDeleteable = false;
private boolean isSelectable = false;
/** Default type is Double - Type chosen at creation time */
private AttrType _dataType = AttrType.T_Double;
/**
* Default TextCellEditor style
* specify no borders on text widget as cell outline in table already
* provides the look of a border.
*/
private static final int defaultStyle = SWT.SINGLE;
/**
* Creates a new text string cell editor with no control
* The cell editor value is the string itself, which is initially the empty
* string. Initially, the cell editor has no cell validator.
*/
public NumericCellEditor()
{
setStyle(defaultStyle);
}
/**
* Creates a new text string cell editor parented under the given control.
* The cell editor value is the string itself, which is initially the empty string.
* Initially, the cell editor has no cell validator.
*
* @param parent the parent control
*/
public NumericCellEditor(Composite parent)
{
this(parent, defaultStyle);
}
/**
* Creates a new text string cell editor parented under the given control.
* The cell editor value is the string itself, which is initially the empty string.
* Initially, the cell editor has no cell validator.
*
* @param parent the parent control
* @param style the style bits
*/
public NumericCellEditor(Composite parent, int style)
{
super(parent, style);
}
/**
* Creates a new text string cell editor parented under the given control.
* The cell editor value is the string itself, which is initially the empty string.
* Initially, the cell editor has no cell validator.
*
* @param parent the parent control
* @param style the style bits
* @param dataType Type of numeric to be controlled : C_TYPE_INT, C_TYPE_FLOAT, C_TYPE_DOUBLE, C_TYPE_VECTOR3D
*/
public NumericCellEditor(Composite parent, int style, AttrType dataType)
{
super(parent, style);
_dataType = dataType;
}
/**
* Checks to see if the "deletable" state (can delete/
* nothing to delete) has changed and if so fire an
* enablement changed notification.
*/
private void checkDeleteable()
{
boolean oldIsDeleteable = isDeleteable;
isDeleteable = isDeleteEnabled();
if (oldIsDeleteable != isDeleteable)
{
fireEnablementChanged(DELETE);
}
}
/**
* Checks to see if the "selectable" state (can select)
* has changed and if so fire an enablement changed notification.
*/
private void checkSelectable()
{
boolean oldIsSelectable = isSelectable;
isSelectable = isSelectAllEnabled();
if (oldIsSelectable != isSelectable)
{
fireEnablementChanged(SELECT_ALL);
}
}
/**
* Checks to see if the selection state (selection /
* no selection) has changed and if so fire an
* enablement changed notification.
*/
private void checkSelection()
{
boolean oldIsSelection = isSelection;
isSelection = text.getSelectionCount() > 0;
if (oldIsSelection != isSelection)
{
fireEnablementChanged(COPY);
fireEnablementChanged(CUT);
}
}
/* (non-Javadoc)
* Method declared on CellEditor.
*/
protected Control createControl(Composite parent)
{
text = new Text(parent, getStyle());
text.addSelectionListener(new SelectionAdapter()
{
public void widgetDefaultSelected(SelectionEvent e)
{
handleDefaultSelection(e);
}
});
text.addKeyListener(new KeyAdapter()
{
// hook key pressed - see PR 14201
public void keyPressed(KeyEvent e)
{
keyReleaseOccured(e);
// as a result of processing the above call, clients may have
// disposed this cell editor
if ((getControl() == null) || getControl().isDisposed())
{
return;
}
checkSelection(); // see explanation below
checkDeleteable();
checkSelectable();
}
});
text.addTraverseListener(new TraverseListener()
{
public void keyTraversed(TraverseEvent e)
{
if (e.detail == SWT.TRAVERSE_ESCAPE || e.detail == SWT.TRAVERSE_RETURN)
{
e.doit = false;
}
}
});
// We really want a selection listener but it is not supported so we
// use a key listener and a mouse listener to know when selection changes
// may have occurred
text.addMouseListener(new MouseAdapter()
{
public void mouseUp(MouseEvent e)
{
checkSelection();
checkDeleteable();
checkSelectable();
}
});
text.addFocusListener(new FocusAdapter()
{
public void focusLost(FocusEvent e)
{
NumericCellEditor.this.focusLost();
}
});
text.setFont(parent.getFont());
text.setBackground(parent.getBackground());
text.setText("");//$NON-NLS-1$
text.addModifyListener(getModifyListener());
return text;
}
/**
* The <code>TextCellEditor</code> implementation of
* this <code>CellEditor</code> framework method returns
* the text string.
*
* @return the text string
*/
protected Object doGetValue()
{
String s = text.getText();
Object num = null;
try
{
switch (_dataType)
{
case T_Integer:
num = Integer.parseInt(s);
break;
case T_Double:
num = Double.parseDouble(s);
break;
// case T_Float:
// num = Float.parseFloat(s);
// break;
// case C_TYPE_VECTOR3D:
// String[] ts = s.split(S_VECTSEP);
//
// num = new Vector3d(Double.parseDouble(ts[0]), Double.parseDouble(ts[1]), Double.parseDouble(ts[2]));
// break;
}
}
catch (java.lang.NumberFormatException nfex)
{
log.warning("Format Exception for :" + (s == null ? "null" : s));
}
return num;
}
/* (non-Javadoc)
* Method declared on CellEditor.
*/
protected void doSetFocus()
{
if (text != null)
{
text.selectAll();
text.setFocus();
checkSelection();
checkDeleteable();
checkSelectable();
}
}
/**
* The <code>TextCellEditor</code> implementation of
* this <code>CellEditor</code> framework method accepts
* a text string (type <code>String</code>).
*
* @param value a numeric value (type <code>Double</code> or <code>Float</code> or <code>Integer</code>)
*/
protected void doSetValue(Object value)
{
Assert.isTrue(text != null && (((_dataType == AttrType.T_Double) && (value instanceof Double))
// || ((_dataType == C_TYPE_FLOAT) && (value instanceof Float))
|| ((_dataType == AttrType.T_Integer) && (value instanceof Integer))
// || ((_dataType == C_TYPE_VECTOR3D) && (value instanceof Vector3d))
));
String sval = "";
switch (_dataType)
{
case T_Integer:
sval = ((Integer) value).toString();
break;
case T_Double:
sval = ((Double) value).toString();
break;
// case C_TYPE_FLOAT:
// sval = ((Float) value).toString();
// break;
// case C_TYPE_VECTOR3D:
// Vector3d v = (Vector3d) value;
// sval = Double.toString(v.x) + S_VECTSEP + Double.toString(v.y) + S_VECTSEP + Double.toString(v.z);
// break;
}
text.removeModifyListener(getModifyListener());
text.setText(sval);
text.addModifyListener(getModifyListener());
}
/**
* Processes a modify event that occurred in this text cell editor.
* This framework method performs validation and sets the error message
* accordingly, and then reports a change via <code>fireEditorValueChanged</code>.
* Subclasses should call this method at appropriate times. Subclasses
* may extend or reimplement.
*
* @param e the SWT modify event
*/
protected void editOccured(ModifyEvent e)
{
String value = text.getText();
if (value == null)
{
value = "";//$NON-NLS-1$
}
Object typedValue = value;
boolean oldValidState = isValueValid();
boolean newValidState = isCorrect(typedValue);
if (typedValue == null && newValidState)
{
Assert.isTrue(false, "Validator isn't limiting the cell editor's type range");//$NON-NLS-1$
}
if (!newValidState)
{
// try to insert the current value into the error message.
setErrorMessage(MessageFormat.format(getErrorMessage(), new Object[]
{
value
}));
}
valueChanged(oldValidState, newValidState);
}
/**
* Since a text editor field is scrollable we don't
* set a minimumSize.
*/
public LayoutData getLayoutData()
{
return new LayoutData();
}
/**
* Return the modify listener.
*/
private ModifyListener getModifyListener()
{
if (modifyListener == null)
{
modifyListener = new ModifyListener()
{
public void modifyText(ModifyEvent e)
{
editOccured(e);
}
};
}
return modifyListener;
}
/**
* Handles a default selection event from the text control by applying the editor
* value and deactivating this cell editor.
*
* @param event the selection event
*/
protected void handleDefaultSelection(SelectionEvent event)
{
// same with enter-key handling code in keyReleaseOccured(e);
fireApplyEditorValue();
deactivate();
}
/**
* The <code>TextCellEditor</code> implementation of this
* <code>CellEditor</code> method returns <code>true</code> if
* the current selection is not empty.
*/
public boolean isCopyEnabled()
{
if (text == null || text.isDisposed())
{
return false;
}
return text.getSelectionCount() > 0;
}
/**
* The <code>TextCellEditor</code> implementation of this
* <code>CellEditor</code> method returns <code>true</code> if
* the current selection is not empty.
*/
public boolean isCutEnabled()
{
if (text == null || text.isDisposed())
{
return false;
}
return text.getSelectionCount() > 0;
}
/**
* The <code>TextCellEditor</code> implementation of this
* <code>CellEditor</code> method returns <code>true</code>
* if there is a selection or if the caret is not positioned
* at the end of the text.
*/
public boolean isDeleteEnabled()
{
if (text == null || text.isDisposed())
{
return false;
}
return text.getSelectionCount() > 0 || text.getCaretPosition() < text.getCharCount();
}
/**
* The <code>TextCellEditor</code> implementation of this
* <code>CellEditor</code> method always returns <code>true</code>.
*/
public boolean isPasteEnabled()
{
if (text == null || text.isDisposed())
{
return false;
}
return true;
}
/**
* Check if save all is enabled
* @return true if it is
*/
public boolean isSaveAllEnabled()
{
if (text == null || text.isDisposed())
{
return false;
}
return true;
}
/**
* Returns <code>true</code> if this cell editor is
* able to perform the select all action.
* <p>
* This default implementation always returns
* <code>false</code>.
* </p>
* <p>
* Subclasses may override
* </p>
* @return <code>true</code> if select all is possible,
* <code>false</code> otherwise
*/
public boolean isSelectAllEnabled()
{
if (text == null || text.isDisposed())
{
return false;
}
return text.getCharCount() > 0;
}
/**
* Processes a key release event that occurred in this cell editor.
* <p>
* The <code>TextCellEditor</code> implementation of this framework method
* ignores when the RETURN key is pressed since this is handled in
* <code>handleDefaultSelection</code>.
* An exception is made for Ctrl+Enter for multi-line texts, since
* a default selection event is not sent in this case.
* </p>
*
* @param keyEvent the key event
*/
protected void keyReleaseOccured(KeyEvent keyEvent)
{
if (keyEvent.character == '\r')
{ // Return key
// Enter is handled in handleDefaultSelection.
// Do not apply the editor value in response to an Enter key event
// since this can be received from the IME when the intent is -not-
// to apply the value.
// See bug 39074 [CellEditors] [DBCS] canna input mode fires bogus event from Text Control
//
// An exception is made for Ctrl+Enter for multi-line texts, since
// a default selection event is not sent in this case.
if (text != null && !text.isDisposed() && (text.getStyle() & SWT.MULTI) != 0)
{
if ((keyEvent.stateMask & SWT.CTRL) != 0)
{
super.keyReleaseOccured(keyEvent);
}
}
return;
}
boolean accept = Character.isISOControl(keyEvent.character);
switch (_dataType)
{
case T_Integer:
accept |= S_NUMINTCHARS.indexOf(keyEvent.character) >= 0;
break;
case T_Double:
// case C_TYPE_FLOAT:
accept |= S_NUMDBLCHARS.indexOf(keyEvent.character) >= 0;
break;
// case C_TYPE_VECTOR3D:
// accept |= S_NUMVECTCHARS.indexOf(keyEvent.character) >= 0;
// break;
}
// Try to filter characters
if (accept)
{
super.keyReleaseOccured(keyEvent);
}
else
{
keyEvent.doit = false;
}
}
/**
* The <code>TextCellEditor</code> implementation of this
* <code>CellEditor</code> method copies the
* current selection to the clipboard.
*/
public void performCopy()
{
text.copy();
}
/**
* The <code>TextCellEditor</code> implementation of this
* <code>CellEditor</code> method cuts the
* current selection to the clipboard.
*/
public void performCut()
{
text.cut();
checkSelection();
checkDeleteable();
checkSelectable();
}
/**
* The <code>TextCellEditor</code> implementation of this
* <code>CellEditor</code> method deletes the
* current selection or, if there is no selection,
* the character next character from the current position.
*/
public void performDelete()
{
if (text.getSelectionCount() > 0)
{
// remove the contents of the current selection
text.insert(""); //$NON-NLS-1$
}
else
{
// remove the next character
int pos = text.getCaretPosition();
if (pos < text.getCharCount())
{
text.setSelection(pos, pos + 1);
text.insert(""); //$NON-NLS-1$
}
}
checkSelection();
checkDeleteable();
checkSelectable();
}
/**
* The <code>TextCellEditor</code> implementation of this
* <code>CellEditor</code> method pastes the
* the clipboard contents over the current selection.
*/
public void performPaste()
{
text.paste();
checkSelection();
checkDeleteable();
checkSelectable();
}
/**
* The <code>TextCellEditor</code> implementation of this
* <code>CellEditor</code> method selects all of the
* current text.
*/
public void performSelectAll()
{
text.selectAll();
checkSelection();
checkDeleteable();
}
/**
* This implementation of
* {@link CellEditor#dependsOnExternalFocusListener()} returns false if the
* current instance's class is TextCellEditor, and true otherwise.
* Subclasses that hook their own focus listener should override this method
* and return false. See also bug 58777.
*/
protected boolean dependsOnExternalFocusListener()
{
return getClass() != NumericCellEditor.class;
}
}