I need a JPasswordField with a small button (or something similar) with an icon that shows me the password entered so far when the left mouse button is pressed on that icon.
This can easily be implemented creating a panel composed of the password field and a label with an icon on the right side, as suggested in the answer of this question but I prefer a single component that does the job.
After some analysis, I found a solution that is just the normal field with a specific border. This border is a wrapper around the original one and is enlarged on the right side where the icon is shown.
Since I needed a component like this, I thought somebody else might be looking for the same thing I would like to share my solution.
Please find my solution as an answer.
/**********************************************************************************************************************
* Package definition
*********************************************************************************************************************/
package test;
/**********************************************************************************************************************
* Import specifications
*********************************************************************************************************************/
import java.awt.*;
import java.awt.event.*;
import java.net.URL;
import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.event.*;
/**********************************************************************************************************************
* This class manages a password field with a little eye on the right side showing the password when the mouse is
* pressed.
*********************************************************************************************************************/
@SuppressWarnings("serial")
public class PreviewPasswordField extends JPasswordField
{
/********************************************************************************************************************
* The constructor initializes the object.
*******************************************************************************************************************/
public PreviewPasswordField()
{
// ---------------------
// Initialize attributes
// ---------------------
requestedBorder = getBorder();
requestedCursor = getCursor();
imageCursor = new Cursor(Cursor.HAND_CURSOR);
// --------------
// Set new border
// --------------
setBorder(new PasswordBorder());
// ---------------
// Listen to mouse
// ---------------
PasswordMouseListener listener;
listener = new PasswordMouseListener();
addMouseListener(listener);
addMouseMotionListener(listener);
addCaretListener(listener);
// ----
// Done
// ----
objectConstructed = true;
} // constructor
/********************************************************************************************************************
* This method sets the border of this component.
*******************************************************************************************************************/
@Override
public void setBorder(Border border)
{
// ------------------------------------------------------------------------------------------
// We're called also during the construction of the component when setting the default border
// Therefore, do normal stuff if construction not completed yet
// ------------------------------------------------------------------------------------------
if (!objectConstructed)
{
super.setBorder(border);
return;
}
// ----------------------------------------------------------
// Save requested border and make sure user sees what happens
// ----------------------------------------------------------
requestedBorder = border;
invalidate();
} // setBorder
/********************************************************************************************************************
* This method sets the cursor of this component.
*******************************************************************************************************************/
@Override
public void setCursor(Cursor cursor)
{
// ------------------------------------------------------------------------------------------
// We're called also during the construction of the component when setting the default cursor
// Therefore, do normal stuff if construction not completed yet
// ------------------------------------------------------------------------------------------
if (!objectConstructed)
{
super.setCursor(cursor);
return;
}
// ----------------------------------------------------------
// Save requested cursor and make sure user sees what happens
// ----------------------------------------------------------
requestedCursor = cursor;
invalidate();
} // setCursor
/********************************************************************************************************************
* This method sets the cursor to be shown when the mouse is above the preview image.
* <br>It defaults to {@link Cursor#HAND_CURSOR}.
*******************************************************************************************************************/
public void setPreviewCursor(Cursor cursor)
{
// ----------------------------------------------------------
// Save requested cursor and make sure user sees what happens
// ----------------------------------------------------------
imageCursor = cursor;
invalidate();
} // setPreviewCursor
/********************************************************************************************************************
* This method returns the cursor as it is be shown when the mouse is above the preview image.
* <br>It defaults to {@link Cursor#HAND_CURSOR}.
*******************************************************************************************************************/
public Cursor getPreviewCursor()
{
return (imageCursor);
}
/********************************************************************************************************************
* This class implements the border around the password field.
*******************************************************************************************************************/
private class PasswordBorder implements Border
{
/******************************************************************************************************************
* This method returns the insets of the border for specified component.
*****************************************************************************************************************/
@Override
public Insets getBorderInsets(Component c)
{
// ---------------------------------------------------------------------------
// Just the original insets with an added part of the right side for the image
// The latter equals the height of the field
// ---------------------------------------------------------------------------
Insets result;
if (requestedBorder == null)
result = new Insets(0, 0, 0, 0);
else
result = requestedBorder.getBorderInsets(c);
result.right += getHeight();
// ----
// Done
// ----
return (result);
} // getBorderInsets
/******************************************************************************************************************
* This method returns whether or not the border is opaque.
*****************************************************************************************************************/
@Override
public boolean isBorderOpaque()
{
// -------------------------------------------------
// Simply the original border's flag if specified
// Otherwise, we take care of filling the background
// -------------------------------------------------
if (requestedBorder == null)
return (true);
else
return (requestedBorder.isBorderOpaque());
} // isBorderOpaque
/******************************************************************************************************************
* This method paints the border for the specified component with the specified position and size.
*****************************************************************************************************************/
@Override
public void paintBorder(Component c,
Graphics g,
int x,
int y,
int width,
int height)
{
// -----------------------------------------------------------------------
// Determine where the icon starts
// Equals the specified position plus the total width minus the icon width
// The latter equals the component's height
// -----------------------------------------------------------------------
int iconWidth;
int iconOffset;
iconWidth = getHeight();
iconOffset = x + width - iconWidth;
// -----------------------------------------------------------------
// Paint normal border around the field, leaving the image untouched
// -----------------------------------------------------------------
if (requestedBorder != null)
requestedBorder.paintBorder(c, g, x, y, width - iconWidth, height);
// -------------------------------------------
// Set background where the image will be draw
// -------------------------------------------
Graphics iconGraph;
iconGraph = g.create();
iconGraph.setColor(getParent().getBackground());
iconGraph.fillRect(iconOffset, y, iconWidth, height);
// --------------------------------------------------------------------------------------------------
// Make sure icon has correct size
// When for instance a different border is set, the height of the component and therefore the size of
// the image may change
// --------------------------------------------------------------------------------------------------
if ((scaledImage == null) || (scaledImage.getIconHeight() != height))
scaledImage = new ImageIcon(previewImage.getImage().getScaledInstance(height, height, Image.SCALE_SMOOTH));
// --------------
// Now draw image
// --------------
scaledImage.paintIcon(c, iconGraph, iconOffset, y);
} // paintBorder
} // class PasswordBorder
/********************************************************************************************************************
* This class implements the listener for events.
*******************************************************************************************************************/
private class PasswordMouseListener extends MouseAdapter implements CaretListener
{
/******************************************************************************************************************
* This method is called when the caret position is updated.
*****************************************************************************************************************/
@Override
public void caretUpdate(CaretEvent e)
{
// ---------------------------
// Keep track of last position
// ---------------------------
lastCaret = e.getDot();
} // caretUpdate
/******************************************************************************************************************
* This method is called when the mouse moves within the component.
*****************************************************************************************************************/
@Override
public void mouseMoved(MouseEvent e)
{
// --------------------------------------------------------------------------------------
// Set cursor according to location within component
// We intercept the default behavior so make sure we invoke the method of the super class
// --------------------------------------------------------------------------------------
if (isMouseAboveImage(e.getPoint()))
PreviewPasswordField.super.setCursor(imageCursor);
else
PreviewPasswordField.super.setCursor(requestedCursor);
} // mouseMoved
/******************************************************************************************************************
* This method is called when a mouse button is pressed within the component.
*****************************************************************************************************************/
@Override
public void mousePressed(MouseEvent e)
{
// -----------------------------
// Ignore if not the left button
// -----------------------------
if (e.getButton() != MouseEvent.BUTTON1)
return;
// -----------------------
// Save original echo char
// -----------------------
echoChar = getEchoChar();
// ----------------------------------------------------------------
// Show password and restore caret position if the icon was pressed
// ----------------------------------------------------------------
if (isMouseAboveImage(e.getPoint()))
{
if (getCaretPosition() != lastCaret)
setCaretPosition(lastCaret);
setEchoChar('\0');
}
} // mousePressed
/******************************************************************************************************************
* This method is called when a mouse button is released within the component.
*****************************************************************************************************************/
@Override
public void mouseReleased(MouseEvent e)
{
// -----------------------------
// Ignore if not the left button
// -----------------------------
if (e.getButton() != MouseEvent.BUTTON1)
return;
// -------------
// Hide password
// -------------
setEchoChar(echoChar);
} // mouseReleased
/****************************************************************************************************************
* This method returns if the specified position, relative to the component, is above the 'preview' image.
***************************************************************************************************************/
private boolean isMouseAboveImage(Point relativePosition)
{
// -------------------------
// Should be above component
// -------------------------
Dimension compSize;
compSize = getSize();
if ((relativePosition == null) ||
(relativePosition.x < 0) || (relativePosition.x >= compSize.width) ||
(relativePosition.y < 0) || (relativePosition.y >= compSize.height))
return (false);
// ----------------------------------------------------
// Above image if it's within the last part
// The width of the image equals the component's height
// ----------------------------------------------------
return (relativePosition.x >= (compSize.width - compSize.height));
} // mouseAboveImage
/******************************************************************************************************************
* This attribute represents the character shown when the password is hidden.
*****************************************************************************************************************/
private char echoChar;
private int lastCaret = 0;
} // class PasswordMouseListener
/********************************************************************************************************************
* This attribute indicates if the object's construction has completed.
* <br>We need it in order to be able to distinguish between setting the default border/cursor and a custom one.
*******************************************************************************************************************/
private boolean objectConstructed = false;
/********************************************************************************************************************
* This attribute represents the requested border of the component.
*******************************************************************************************************************/
private Border requestedBorder;
/********************************************************************************************************************
* This attribute represents the requested cursor when the mouse is above the component.
*******************************************************************************************************************/
private Cursor requestedCursor;
/********************************************************************************************************************
* This attribute represents the cursor when the mouse is above the image.
*******************************************************************************************************************/
private Cursor imageCursor;
/********************************************************************************************************************
* This attribute represents the icon to be shown next to the password, scaled to the correct size.
*******************************************************************************************************************/
private ImageIcon scaledImage = null;
/********************************************************************************************************************
* This attribute represents the icon to be shown next to the password.
*******************************************************************************************************************/
private static ImageIcon previewImage = null;
/********************************************************************************************************************
* This static initializer loads the image.
*******************************************************************************************************************/
static
{
// -------------------------------
// Determine directory of the icon
// -------------------------------
String directory;
directory = PreviewPasswordField.class.getPackage().getName().replace('.', '/');
// ---------
// Load icon
// ---------
URL name;
name = ClassLoader.getSystemResource(directory + "/passwordPreview.png");
previewImage = new ImageIcon(name);
} // static initializer
} // class PreviewPasswordField