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 : * * * Les évenements écoutables sont : * * * @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); } } }