package net.developpez.swt;
import java.math.BigInteger;
import java.util.Arrays;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.FocusListener;
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.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.events.VerifyEvent;
import org.eclipse.swt.events.VerifyListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Layout;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Spinner;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.TypedListener;
/**
* Un spinner pour la saisie de nombre héxadécimal.
*
* Les valeurs possibles pour le style :
*
* - SWT.BORDER
* - SWT.LEFT
* - SWT.CENTER
* - SWT.RIGHT
* - SWT.WRAP
* - SWT.READ_ONLY
* - SWT.DOUBLE_BUFFERED
* - SWT.NO_BACKGROUND
*
*
* Les évenements écoutables sont :
*
* - SWT.Selection
* - SWT.Modify
* - SWT.Verify
* - SWT.FocusIn
* - SWT.FocusOut
*
*
* @author jdrigo
*
*/
public class HexSpinner extends Composite {
public final static int LIMIT = Spinner.LIMIT;
public enum CaseType {
UPPER,
LOWER;
public void append(StringBuilder stringBuilder, String string) {
switch(this) {
case UPPER:
stringBuilder.append(string.toUpperCase());
break;
case LOWER:
stringBuilder.append(string.toLowerCase());
break;
}
}
}
public enum PaddingType {
LIMIT,
EVEN,
NONE
}
private Text text; // le champ text
private Spinner spinner; // un champ spinner pour avoir les boutons tels qu'un vrai spinner
private LongValue selection; // la valeur sélectionnée
private int textlimit; // le nombre de caractères maximum qu'on peut saisir dans le champ (au clavier)
private int increment; // le montant de l'incrément
private int pageIncrement; // le montant de l'incrément par page
private LongValue maximum; // la valeur maximum qu'on peut saisir
private LongValue minimum; // la valeur minimum qu'on peut saisir
private CaseType caseType; // le type de casse
private PaddingType paddingType; // le type de padding
private int buttonWidth; // largeur des boutons de spinner (cache)
private boolean rtl; // indique si le composant est Right To Left
private int maximumChar; // le nombre de caractères maxi pour représenter une valeur saisie (pour déterminer la taille du composant)
private Point innerSize; // taille du composant interne de saisie
private Point componentSize; // taille du composant
private boolean updateSelection; // indique qu'on est en cours de mise à jour de la sélection
private boolean textChange; // indique qu'on est en cours de modification du texte (sauf par clavier)
public HexSpinner(Composite parent, int style) {
super(parent, checkComponentStyle(style));
super.setLayout(null);
spinner = new Spinner(this, SWT.NONE);
if ( (style & (SWT.RIGHT_TO_LEFT | SWT.LEFT_TO_RIGHT) )==SWT.RIGHT_TO_LEFT ) {
rtl = true;
text=new Text(this,checkTextStyle(style, SWT.LEFT));
}
else {
text=new Text(this,checkTextStyle(style, SWT.RIGHT));
}
setTabList(new Control[]{text});
text.addVerifyListener(new VerifyListener() {
public void verifyText(VerifyEvent e) {
verify(e, true);
}
});
text.addModifyListener(new ModifyListener() {
private boolean modify;
public void modifyText(ModifyEvent e) {
if ( modify ) return;
try {
modify = true;
String newString = text.getText();
StringBuilder paddText = new StringBuilder(newString);
paddText(paddText);
String paddString = paddText.toString();
/*if ( !textChange && textlimit>0 && paddString.length()>textlimit ) {
paddString = paddString.substring(0, textlimit);
}*/
if ( !paddString.equals(newString) ) {
int diff = paddString.length()-newString.length()+1;
int start = text.getCaretPosition();
int length = text.getSelectionCount();
setText(paddString);
text.setSelection(start+diff, start+length+diff);
}
fireModifyChange();
int start = text.getCaretPosition();
int length = text.getSelectionCount();
updateSelection(false, false);
text.setSelection(start, start+length);
}
finally {
modify = false;
}
}
});
text.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
updateSelection(false, false);
}
});
spinner.addSelectionListener(new SelectionAdapter() {
private boolean select;
@Override
public void widgetSelected(SelectionEvent e) {
if ( select ) return;
boolean down = false;
int amount;
try {
select=true;
int newSelection = spinner.getSelection();
if ( newSelection<0 ) {
down = true;
amount = -newSelection;
}
else {
amount = newSelection;
}
spinner.setSelection(0);
}
finally {
select=false;
}
changeSelection(down,amount);
}
});
addListener(SWT.Resize, new Listener() {
public void handleEvent(Event event) {
resize();
}
});
text.addListener(SWT.Traverse, new Listener() {
public void handleEvent(Event e) {
doTraverse(e);
}
});
addListener(SWT.FocusIn, new Listener() {
public void handleEvent(Event e) {
text.setFocus();
}
});
text.addFocusListener(new FocusListener() {
public void focusLost(FocusEvent e) {
text.clearSelection();
notifyListeners(SWT.FocusOut, new Event()); }
public void focusGained(FocusEvent e) {
text.setSelection(0, text.getText().length());
notifyListeners(SWT.FocusIn, new Event());
}
});
text.addKeyListener(new KeyAdapter() {
public void keyPressed(KeyEvent e) {
handleKey(e.keyCode);
}
});
init();
setText(toText(selection, false));
}
private void updateSelection(boolean defaultSelected, boolean doLimit) {
if ( updateSelection ) return;
try {
updateSelection = true;
String selection = text.getText();
if ( "".equals(selection) ) {
setLimitedSelection(new LongValue(0), doLimit);
}
else {
setLimitedSelection(new LongValue(selection), doLimit);
}
fireSelectionChange(defaultSelected);
}
finally {
updateSelection=false;
}
}
private void fireSelectionChange(boolean defaultSelected) {
Event event = new Event();
event.text=text.getText();
notifyListeners(defaultSelected?SWT.DefaultSelection:SWT.Selection, event);
}
private void fireModifyChange() {
Event event = new Event();
event.text=text.getText();
notifyListeners(SWT.Modify, event);
}
private void init() {
setFont(getFont());
this.textlimit = text.getTextLimit();
this.caseType = CaseType.UPPER;
this.paddingType = PaddingType.NONE;
this.selection = LongValue.VALUE_0;
this.increment = 1;
this.pageIncrement = 16;
this.maximum = new LongValue(Long.MAX_VALUE);
this.minimum = new LongValue(Long.MIN_VALUE);
this.spinner.setIncrement(this.increment);
this.spinner.setPageIncrement(this.pageIncrement);
this.spinner.setSelection(0);
this.spinner.setMaximum(Math.max(this.increment,this.pageIncrement));
this.spinner.setMinimum(Math.min(-this.increment,-this.pageIncrement));
updateMaximumChar();
}
private void updateMaximumChar() {
maximumChar = Math.min( textlimit, Math.max(maximum.toString(16).length(), minimum.toString(16).length()));
}
private static int checkComponentStyle(int style) {
return style & ( SWT.DOUBLE_BUFFERED |
SWT.NO_BACKGROUND |
SWT.BORDER);
}
private int checkTextStyle(int style, int textAlign) {
style &= ( SWT.READ_ONLY |
SWT.WRAP |
SWT.CENTER |
SWT.LEFT |
SWT.RIGHT |
SWT.LEFT_TO_RIGHT |
SWT.RIGHT_TO_LEFT );
if ( ( style & (SWT.CENTER | SWT.LEFT | SWT.RIGHT ))== 0 ) {
style |= textAlign;
}
return style;
}
private void checkHexa(String candidate, boolean negative) {
if ( candidate.length()==0 ) throw new IllegalArgumentException("Not an hexadecimal number");
for(int i=0; i='0' && c<='9') || (c>='a' && c<='f') || (c>='A' && c<='F') ) ) {
throw new IllegalArgumentException("Not an hexadecimal digit: '"+c+"'");
}
}
}
private void doTraverse(Event e) {
switch (e.detail) {
case SWT.TRAVERSE_ARROW_PREVIOUS:
if (e.keyCode == SWT.ARROW_UP) {
e.doit = true;
e.detail = SWT.NULL;
changeSelection(false, increment);
}
break;
case SWT.TRAVERSE_ARROW_NEXT:
if (e.keyCode == SWT.ARROW_DOWN) {
e.doit = true;
e.detail = SWT.NULL;
changeSelection(true, increment);
}
break;
case SWT.TRAVERSE_TAB_PREVIOUS:
e.doit=true;
break;
case SWT.TRAVERSE_TAB_NEXT:
e.doit=true;
break;
case SWT.TRAVERSE_RETURN:
e.doit=true;
break;
}
}
private void handleKey(int keyCode) {
switch (keyCode) {
case SWT.PAGE_UP:
changeSelection(false, pageIncrement);
break;
case SWT.PAGE_DOWN:
changeSelection(true, pageIncrement);
break;
default:
break;
}
}
/**
* Configure le type de casse.
*
* @param caseType
*/
public void setCase(CaseType caseType) {
this.caseType=caseType==null?CaseType.UPPER:caseType;
}
/**
* Lit le type de casse.
*
* @return
*/
public CaseType getCaseType() {
return caseType;
}
/**
* Configure le type de padding.
*
* Un type différent de PaddingType.NONE peut génér la saisie en mode éditable (non SWT.READ_ONLY).
*
* @param paddingType
*/
public void setPadded(PaddingType paddingType) {
PaddingType oldPadding = this.paddingType;
this.paddingType=paddingType==null?PaddingType.NONE:paddingType;
if( oldPadding!=this.paddingType ) {
setSelection(selection, false);
}
}
/**
* Configure le nombre maximum de caractères saisissables
*
* @param limit
*/
public void setTextLimit(int limit) {
checkWidget();
if (limit == 0) SWT.error(SWT.ERROR_CANNOT_BE_ZERO);
if (limit < 0 ) limit=-limit;
innerSize=null;
this.textlimit=limit;
resize();
}
/**
* Lit le nombre maximum de caractères saisissables
* @return
*/
public int getTextLimit() {
checkWidget();
return textlimit;
}
/**
* Modifie la valeur de l'incrément. Si la valeur est inférieure à 1, elle est ignorée
*
* @param value
*/
public void setIncrement (int value) {
checkWidget ();
if (value < 1) return;
this.increment = value;
this.spinner.setIncrement(this.increment);
this.spinner.setMaximum(Math.max(this.increment,this.pageIncrement));
this.spinner.setMinimum(Math.min(-this.increment,-this.pageIncrement));
}
/**
* Lit la valeur de l'incrément.
* @return
*/
public int getIncrement() {
checkWidget();
return increment;
}
/**
* Modifie la valeur de l'incrément en mode page.
*
* @param value
*/
public void setPageIncrement (int value) {
checkWidget ();
if (value < 1) return;
this.pageIncrement = value;
this.spinner.setPageIncrement(this.pageIncrement);
this.spinner.setMaximum(Math.max(this.increment,this.pageIncrement));
this.spinner.setMinimum(Math.min(-this.increment,-this.pageIncrement)); }
/**
* Lit la valeur de l'incrément en mode page.
*
* @return
*/
public int getPageIncrement() {
checkWidget();
return pageIncrement;
}
/**
* Affecte la valeur saisie.
*
* @throws IllegalArgumentException en cas de valeur null ou non héxadécimale
* @param value une chaîne contenant de l'héxadécimal
*/
public void setSelection(String value) {
checkWidget();
if ( value==null ) throw new IllegalArgumentException();
checkHexa(value, true);
setLimitedSelection(new LongValue(value), false);
}
/**
* Récupère la valeur saisie sous forme de String.
*
* @return
*/
public String getSelection() {
checkWidget();
return selection.toString(16);
}
/**
* Affecte la valeur saisie.
*
* @throws IllegalArgumentException en cas de valeur null ou non héxadécimale
* @param value un BigInteger
*/
public void setSelection(BigInteger value) {
checkWidget();
if ( value==null ) throw new IllegalArgumentException();
setLimitedSelection(new LongValue(value), false);
}
public BigInteger getSelectionAsBigInteger() {
checkWidget();
return selection.toBigInteger();
}
public void setSelection(int value) {
checkWidget();
setLimitedSelection(new LongValue(value), false);
}
public int getSelectionAsInt() {
checkWidget();
return selection.toInt();
}
public void setSelection(long value) {
checkWidget();
setLimitedSelection(new LongValue(value), false);
}
public long getSelectionAsLong() {
checkWidget();
return selection.toLong();
}
private void setLimitedSelection(LongValue selection, boolean doLimit) {
selection = LongValue.min (LongValue.max (minimum, selection), maximum);
setSelection(selection, doLimit);
}
private void setSelection(LongValue selection, boolean doLimit) {
innerSize=null;
this.selection = selection;
setText(toText(this.selection,doLimit));
resize();
}
private void setText(String text) {
boolean textChanged=textChange;
textChange = true;
try {
this.text.setText(text);
}
finally {
textChange = textChanged;
}
}
private void changeSelection(boolean down, int amount) {
LongValue work = new LongValue(this.selection);
if ( down ) {
if ( work.compareTo(minimum)==0 ) return;
work.subtract(amount);
int compare = work.compareTo(minimum);
if ( compare<0 ) {
work = minimum;
}
}
else {
if ( work.compareTo(maximum)==0 ) return;
work.add(amount);
int compare = work.compareTo(maximum);
if ( compare>0 ) {
work = maximum;
}
}
if ( work.equals(selection) ) return;
this.selection = work;
setText(toText(this.selection, false));
text.setSelection(0, text.getText().length());
text.setFocus();
fireSelectionChange(false);
}
public void setMinimum(String value) {
checkWidget();
if ( value==null ) throw new IllegalArgumentException();
checkHexa(value, true);
setMinimum(new LongValue(value));
}
public void setMinimum(BigInteger value) {
checkWidget();
if ( value==null ) throw new IllegalArgumentException();
setMinimum(new LongValue(value));
}
public void setMinimum(int value) {
checkWidget();
setMinimum(new LongValue(value));
}
public void setMinimum(long value) {
checkWidget();
setMinimum(new LongValue(value));
}
private void setMinimum(LongValue value) {
if (value.compareTo(maximum) >= 0) return;
minimum = value;
updateMaximumChar();
if (selection.compareTo(minimum)<0 ) setSelection (value, false);
}
public void setMaximum(String value) {
checkWidget();
if ( value==null ) throw new IllegalArgumentException();
checkHexa(value, true);
setMaximum(new LongValue(value));
}
public void setMaximum(BigInteger value) {
checkWidget();
if ( value==null ) throw new IllegalArgumentException();
setMaximum(new LongValue(value));
}
public void setMaximum(int value) {
checkWidget();
setMaximum(new LongValue(value));
}
public void setMaximum(long value) {
checkWidget();
setMaximum(new LongValue(value));
}
private void setMaximum(LongValue value) {
if (value.compareTo(minimum) <= 0) return;
maximum = value;
updateMaximumChar();
if (selection.compareTo(maximum)>0 ) setSelection (value, false);
}
@Override
public void setEnabled(boolean enabled) {
checkWidget();
super.setEnabled(enabled);
text.setEnabled(enabled);
spinner.setEnabled(enabled);
}
@Override
public boolean isEnabled() {
return super.isEnabled();
}
@Override
public void setLayout(Layout layout) {
throw new UnsupportedOperationException();
}
private void verify(VerifyEvent e, boolean raise) {
e.doit=true;
for(int i=0; i='0' && c<='9') || (c>='a' && c<='f') || (c>='A' && c<='F') ) ) {
e.doit=false;
break;
}
}
if ( e.doit ) {
switch (caseType) {
case UPPER:
e.text = e.text.toUpperCase();
e.character = Character.toUpperCase(e.character);
break;
case LOWER:
e.text = e.text.toLowerCase();
e.character = Character.toLowerCase(e.character);
break;
default:
break;
}
if ( !textChange ) {
String currentText = this.text.getText();
StringBuilder newText = replace(new StringBuilder(currentText), e.start, e.end, e.text);
if ( newText.length()>textlimit ) {
e.doit=false;
}
}
if ( e.doit && raise ) {
Event event = new Event();
event.character=e.character;
event.text=e.text;
event.start=e.start;
event.end=e.end;
notifyListeners(SWT.Verify, event);
if ( event.doit ) {
e.text=event.text;
verify(e, false);
}
}
}
}
private StringBuilder replace(StringBuilder currentText, int start, int end, String newText) {
currentText.delete(start, end);
currentText.insert(start, newText);
return currentText;
}
public void addSelectionListener(SelectionListener listener) {
checkWidget ();
if (listener == null) SWT.error (SWT.ERROR_NULL_ARGUMENT);
TypedListener typedListener = new TypedListener (listener);
addListener (SWT.Selection,typedListener);
addListener (SWT.DefaultSelection,typedListener);
}
public void removeSelectionListener(SelectionListener listener) {
removeListener(SWT.Selection, listener);
removeListener(SWT.DefaultSelection, listener);
}
public void addModifyListener (ModifyListener listener) {
checkWidget ();
if (listener == null) SWT.error (SWT.ERROR_NULL_ARGUMENT);
TypedListener typedListener = new TypedListener (listener);
addListener (SWT.Modify, typedListener);
}
public void removeModifyListener(ModifyListener listener) {
removeListener(SWT.Modify, listener);
}
public void addVerifyListener (VerifyListener listener) {
checkWidget ();
if (listener == null) SWT.error (SWT.ERROR_NULL_ARGUMENT);
TypedListener typedListener = new TypedListener (listener);
addListener (SWT.Verify, typedListener);
}
public void removeVerifyListener(ModifyListener listener) {
removeListener(SWT.Verify, listener);
}
public void copy() {
checkWidget();
text.copy();
}
public void cut() {
checkWidget();
text.cut();
}
public String getText() {
checkWidget();
return text.getText();
}
@Override
public void setForeground(Color color) {
checkWidget();
text.setForeground(color);
}
@Override
public void setBackground(Color color) {
checkWidget();
text.setBackground(color);
}
@Override
public Color getBackground() {
checkWidget();
return super.getBackground();
}
@Override
public void setBackgroundImage(Image image) {
checkWidget();
text.setBackgroundImage(image);
}
@Override
public Image getBackgroundImage() {
return text.getBackgroundImage();
}
@Override
public void setFont(Font font) {
checkWidget();
innerSize=null;
text.setFont(font);
spinner.setFont(font);
}
@Override
public Font getFont() {
checkWidget();
return text.getFont();
}
private void resize() {
Rectangle st = getClientArea();
computeSize(st.width, st.height);
doResize();
}
private void doResize() {
int textWidth = innerSize.x - buttonWidth;
if ( rtl ) {
text.setBounds(buttonWidth, 0, textWidth, innerSize.y);
spinner.setBounds(0, 0, buttonWidth, innerSize.y);
}
else {
text.setBounds(0, 0, textWidth, innerSize.y);
spinner.setBounds(textWidth, 0, buttonWidth, innerSize.y);
}
}
private final static String HEXCHARS = "-123456789ABCDEFabcdef";
@Override
public Point computeSize(int wHint, int hHint, boolean changed) {
if ( innerSize==null || changed ) {
Point textExtent;
GC gc = new GC(text);
try {
textExtent = gc.textExtent("0");
for(int i=0; i0 && stringBuilder.charAt(0)=='-' ) {
start = 1;
length--;
}
switch (paddingType) {
case EVEN:
if ( (length%2)==1 ) {
// dimension impair : on devrait avoir un nombre pair de caractère
stringBuilder.insert(start, '0');
}
break;
case LIMIT:
if ( textlimit>0 && (length {
public static final LongValue VALUE_0 = new LongValue(0);
private BigInteger value;
private long longValue;
public LongValue(LongValue value) {
if ( value.value==null ) {
this.longValue=value.longValue;
}
else {
this.value = value.value;
}
}
public int toInt() {
if ( value!=null )
return this.value.intValue();
return BigInteger.valueOf(this.longValue).intValue();
}
public long toLong() {
if ( value!=null )
return this.value.longValue();
return BigInteger.valueOf(this.longValue).longValue();
}
public BigInteger toBigInteger() {
if ( value!=null )
return this.value;
return BigInteger.valueOf(this.longValue);
}
public static LongValue max(LongValue value1, LongValue value2) {
if ( value1.compareTo(value2)>=0 ) {
return value1;
}
return value2;
}
public static LongValue min(LongValue value1, LongValue value2) {
if ( value1.compareTo(value2)<=0 ) {
return value1;
}
return value2;
}
public LongValue(long value) {
this.longValue=value;
}
public LongValue(BigInteger value) {
setValue(value);
}
public LongValue(String hexString) {
setValue(new BigInteger(hexString, 16));
}
public void setValue(BigInteger value) {
if ( value.compareTo(BigInteger.valueOf(Long.MAX_VALUE))>0 ) {
this.value=value;
}
else if ( value.compareTo(BigInteger.valueOf(Long.MIN_VALUE))<0 ) {
this.value=value;
}
else {
this.longValue=value.longValue();
this.value=null;
}
}
@Override
public int hashCode() {
return value!=null?value.hashCode():(int)(longValue ^ (longValue >>> 32));
}
@Override
public boolean equals(Object obj) {
if ( obj==this ) {
return true;
}
else if ( obj==null ) {
return false;
}
else if ( obj instanceof LongValue ) {
LongValue objValue = (LongValue) obj;
if ( objValue.value==null ) {
if ( value==null ) {
return objValue.longValue==longValue;
}
else {
return value.equals(BigInteger.valueOf(objValue.longValue));
}
}
else if ( value==null ) {
return objValue.equals(BigInteger.valueOf(longValue));
}
else {
return value.equals(objValue.value);
}
}
else {
return super.equals(obj);
}
}
public int compareTo(LongValue value) {
if ( value==null ) {
throw new NullPointerException();
}
else if ( this.value==null ) {
if ( value.value==null ) {
return (longValue=0 ) {
this.longValue=value.longValue();
this.value=null;
}
}
public void add(long value) {
if( value!=0 ) {
addAndAdjust(value);
}
}
private void addAndAdjust(long value) {
if ( this.value!=null ) {
this.value = this.value.add(BigInteger.valueOf(value));
if ( value<0 ) {
toPrimitiveIfNeeded();
}
}
else {
if (value > 0 ? longValue > Long.MAX_VALUE - value
: longValue < Long.MIN_VALUE - value) {
this.value = BigInteger.valueOf(longValue).add(BigInteger.valueOf(value));
}
else {
longValue += value;
}
}
}
public void subtract(long value) {
if( value!=0 ) {
addAndAdjust(-value);
}
}
@Override
public String toString() {
return value==null?Long.toString(longValue):value.toString();
}
public String toString(int radix) {
return value==null?Long.toString(longValue, radix):value.toString(radix);
}
public void toString(StringBuilder stringBuilder, CaseType caseType, int radix, int max) {
String string;
if ( value==null ) {
string = Long.toString(longValue, radix);
}
else {
string = value.toString(radix);
}
if ( max>0 && string.length()>max ) {
string = string.substring(0,max);
}
caseType.append(stringBuilder, string);
}
}
}