import java.awt.Container; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Rectangle; import java.awt.Shape; import java.util.ArrayList; import java.util.List; import java.util.Random; import javax.swing.JPasswordField; import javax.swing.event.DocumentEvent; import javax.swing.text.*; public class VariableWidthPasswordView extends FieldView { private static final int MIN_WIDTH = 2; private static final int MAX_WIDTH = 15; // The widths of each character. There will be one per character of // the Document. private List widths; // Used to generate the widths. If you end up using this class you // should make this static with the appropriate synchronized checks. private Random widthGenerator; public VariableWidthPasswordView(Element element) { super(element); // This should only be used with a JPasswordField, which // will create this for the first Element. So, this check should // always be true. assert (element.getStartOffset() == 0); widthGenerator = new Random(System.currentTimeMillis()); widths = new ArrayList(10); // When the View is created, we may already have content, need // to initialize widths accordingly for (int i = 0; i < element.getEndOffset() - 1; i++) { widths.add(generateCharacterSize()); } } // Converts between coordinates of the model (integer offsets) and // the view. Overriden to consult the widths List of sizes. public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException { Rectangle bounds = adjustAllocation(a).getBounds(); bounds.x += modelToView(pos); bounds.width = 1; return bounds; } // Converts between view coordinates (x,y) and model (integer offset). // Overriden to consult the widths List of sizes. public int viewToModel(float fx, float fy, Shape a, Position.Bias[] bias) { a = adjustAllocation(a); Rectangle bounds = (a instanceof Rectangle) ? (Rectangle)a : a.getBounds(); int x = (int)fx - bounds.x; for (int i = 0; i < widths.size(); i++) { x -= widths.get(i); if (x <= 0) { return i; } } return getEndOffset(); } // Returns the preferred size. Overriden to calculate the size based on // the widths List. For the Y axis we return supers value, which is the // height of a character. public float getPreferredSpan(int axis) { if (axis == View.X_AXIS) { int size = 0; for (Integer width : widths) { size += width; } return size; } return super.getPreferredSpan(axis); } // Invoked when text has been inserted. Updates the width List appropriately public void insertUpdate(DocumentEvent changes, Shape a, ViewFactory f) { for (int i = 0; i < changes.getLength(); i++) { widths.add(i + changes.getOffset(), generateCharacterSize()); } // Invoke super to update the visibility appropriately. super.insertUpdate(changes, a, f); } // Invoked when text has been removed. Updates the width List appropriately public void removeUpdate(DocumentEvent changes, Shape a, ViewFactory f) { int offset = changes.getOffset(); for (int i = 0; i < changes.getLength(); i++) { widths.remove(offset); } super.removeUpdate(changes, a, f); } // Override from PlainView. Just invokes preferenceChanged along the // horizontal axis. protected void updateDamage(DocumentEvent changes, Shape a, ViewFactory f) { preferenceChanged(this, true, false); } // Does no painting, but overriden to return the appropriate offset. protected int drawUnselectedText(Graphics g, int x, int y, int p0, int p1) throws BadLocationException { return x + modelToView(p1); } // Does no painting, but overriden to return the appropriate offset. protected int drawSelectedText(Graphics g, int x, int y, int p0, int p1) throws BadLocationException { return x + modelToView(p1); } // Returns the view x coordinate in the views space for the specified // model coordinate. private final int modelToView(int p1) { int offset = 0; int end = Math.min(widths.size(), p1); for (int i = 0; i < end; i++) { offset += widths.get(i); } return offset; } // Returns the size to use for a new character. private final int generateCharacterSize() { return widthGenerator.nextInt(MAX_WIDTH - MIN_WIDTH) + MIN_WIDTH; } }