Android setEllipsize not working on class that extends EditText - java

I have a class that extends EditText, on this class i'm trying to make the text to set setEllipsize when the text is to long. For some reason all my tryies didn't work.
It seems that that i can scroll the text horizontal inside the text view...
Can anybody advice how to make it work.
(So far i've tryed many combination of those functions and
that's my part in the code:
public class LMEditTextAutoSize extends EditText {
private boolean autoSize;
public LMEditTextAutoSize(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mTextSize = getTextSize();
mMaxTextSize = getTextSize();
}
public LMEditTextAutoSize(Context context, AttributeSet attrs) {
super(context, attrs);
mTextSize = getTextSize();
mMaxTextSize = getTextSize();
}
public LMEditTextAutoSize(Context context) {
super(context);
mTextSize = getTextSize();
mMaxTextSize = getTextSize();
}
public void setAutoSize(boolean autoSize) {
this.autoSize = autoSize;
resetTextSize();
}
// Minimum text size for this text view
public static final float MIN_TEXT_SIZE = 50;
// Interface for resize notifications
public interface OnTextResizeListener {
public void onTextResize(TextView textView, float oldSize, float newSize);
}
// Our ellipse string
private static final String mEllipsis = "...";
// Registered resize listener
private OnTextResizeListener mTextResizeListener;
// Flag for text and/or size changes to force a resize
private boolean mNeedsResize = false;
// Text size that is set from code. This acts as a starting point for resizing
private float mTextSize;
// Temporary upper bounds on the starting text size
private float mMaxTextSize = 0;
// Lower bounds for text size
private float mMinTextSize = MIN_TEXT_SIZE;
// Text view line spacing multiplier
private float mSpacingMult = 1.0f;
// Text view additional line spacing
private float mSpacingAdd = 0.0f;
// Add ellipsis to text that overflows at the smallest text size
private boolean mAddEllipsis = true;
private int widthLimit;
private int heightLimit;
/**
* When text changes, set the force resize flag to true and reset the text size.
*/
#Override
protected void onTextChanged(final CharSequence text, final int start, final int before, final int after) {
mNeedsResize = true;
// Since this view may be reused, it is good to reset the text size
resetTextSize();
}
/**
* If the text view size changed, set the force resize flag to true
*/
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
if (w != oldw || h != oldh) {
mNeedsResize = true;
}
}
/**
* Register listener to receive resize notifications
*
* #param listener
*/
public void setOnResizeListener(OnTextResizeListener listener) {
mTextResizeListener = listener;
}
/**
* Override the set text size to update our internal reference values
*/
#Override
public void setTextSize(float size) {
super.setTextSize(size);
mTextSize = getTextSize();
}
/**
* Override the set text size to update our internal reference values
*/
#Override
public void setTextSize(int unit, float size) {
super.setTextSize(unit, size);
mTextSize = getTextSize();
}
/**
* Override the set line spacing to update our internal reference values
*/
#Override
public void setLineSpacing(float add, float mult) {
super.setLineSpacing(add, mult);
mSpacingMult = mult;
mSpacingAdd = add;
}
/**
* Set the upper text size limit and invalidate the view
*
* #param maxTextSize
*/
public void setMaxTextSize(float maxTextSize) {
mMaxTextSize = maxTextSize;
requestLayout();
invalidate();
}
/**
* Return upper text size limit
*
* #return
*/
public float getMaxTextSize() {
return mMaxTextSize;
}
/**
* Set the lower text size limit and invalidate the view
*
* #param minTextSize
*/
public void setMinTextSize(float minTextSize) {
mMinTextSize = minTextSize;
requestLayout();
invalidate();
}
/**
* Return lower text size limit
*
* #return
*/
public float getMinTextSize() {
return mMinTextSize;
}
/**
* Set flag to add ellipsis to text that overflows at the smallest text size
*
* #param addEllipsis
*/
public void setAddEllipsis(boolean addEllipsis) {
mAddEllipsis = addEllipsis;
}
/**
* Return flag to add ellipsis to text that overflows at the smallest text size
*
* #return
*/
public boolean getAddEllipsis() {
return mAddEllipsis;
}
/**
* Reset the text to the original size
*/
public void resetTextSize() {
if (autoSize) {
if (mTextSize > 0) {
super.setTextSize(TypedValue.COMPLEX_UNIT_PX, mMaxTextSize);
mTextSize = mMaxTextSize;
}
}
}
/**
* Resize text after measuring
*/
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
if (changed || mNeedsResize) {
widthLimit = (right - left) - getCompoundPaddingLeft() - getCompoundPaddingRight();
heightLimit = (bottom - top) - getCompoundPaddingBottom() - getCompoundPaddingTop();
resizeText(widthLimit, heightLimit);
}
super.onLayout(changed, left, top, right, bottom);
}
/**
* Resize the text size with specified width and height
*
* #param width
* #param height
*/
public void resizeText(int width, int height) {
if (autoSize) {
CharSequence text = getText();
int oneLineWidth = width;
int lineCount = getLineCount();
int minTextHeight = getTextHeight("oo", getPaint(), width, mMinTextSize);
int maxLineInHeight = height / minTextHeight;
lineCount = lineCount > maxLineInHeight ? maxLineInHeight : lineCount;
if (lineCount > 1) {
width = width * lineCount;
}
// Do not resize if the view does not have dimensions or there is no text
if (text == null || text.length() == 0 || height <= 0 || width <= 0 || mTextSize == 0) {
return;
}
// Get the text view's paint object
TextPaint textPaint = getPaint();
// Store the current text size
float oldTextSize = textPaint.getTextSize();
// If there is a max text size set, use the lesser of that and the default text size
float targetTextSize = mMaxTextSize > 0 ? Math.min(mTextSize, mMaxTextSize) : mTextSize;
// Get the required text height
int textHeight = getTextHeight(text, textPaint, width, targetTextSize);
// Until we either fit within our text view or we had reached our min text size, incrementally try smaller sizes
while (textHeight > height && targetTextSize > mMinTextSize) {
targetTextSize = Math.max(targetTextSize - 2, mMinTextSize);
textHeight = getTextHeight(text, textPaint, width, targetTextSize);
}
TextPaint paintCopy = new TextPaint(textPaint);
paintCopy.setTextSize(targetTextSize);
float textWidte = paintCopy.measureText(text, 0, text.length());
while (textWidte > width && targetTextSize > mMinTextSize) {
targetTextSize = Math.max(targetTextSize - 2, mMinTextSize);
paintCopy.setTextSize(targetTextSize);
textWidte = paintCopy.measureText(text, 0, text.length());
}
if (lineCount > 1) {
int textHeightforLine = getTextHeight(text, textPaint, oneLineWidth, targetTextSize);
while (textHeightforLine > height && targetTextSize > mMinTextSize) {
targetTextSize = Math.max(targetTextSize - 2, mMinTextSize);
textHeightforLine = getTextHeight(text, textPaint, oneLineWidth, targetTextSize);
}
}
// If we had reached our minimum text size and still don't fit, append an ellipsis
if (mAddEllipsis && targetTextSize == mMinTextSize && textHeight > height) {
// Draw using a static layout
// modified: use a copy of TextPaint for measuring
TextPaint paint = new TextPaint(textPaint);
paint.setTextSize(mMinTextSize);
// Draw using a static layout
StaticLayout layout = new StaticLayout(text, paint, width, Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, false);
// Check that we have a least one line of rendered text
if (layout.getLineCount() > 0) {
// Since the line at the specific vertical position would be cut off,
// we must trim up to the previous line
int lastLine = layout.getLineForVertical(height) - 1;
// If the text would not even fit on a single line, clear it
if (lastLine < 0) {
setText("");
}
// Otherwise, trim to the previous line and add an ellipsis
else {
int start = layout.getLineStart(lastLine);
int end = layout.getLineEnd(lastLine);
float lineWidth = layout.getLineWidth(lastLine);
float ellipseWidth = textPaint.measureText(mEllipsis);
// Trim characters off until we have enough room to draw the ellipsis
while (width < lineWidth + ellipseWidth) {
lineWidth = textPaint.measureText(text.subSequence(start, --end + 1).toString());
}
if (end != 0) {
setInputType(InputType.TYPE_CLASS_TEXT);
setSingleLine(true);
setLines(1);
setMaxLines(1);
setEllipsize(TextUtils.TruncateAt.END);
setSelected(true);
setText(text);
}
}
}
}
// Some devices try to auto adjust line spacing, so force default line spacing
// and invalidate the layout as a side effect
setTextSize(TypedValue.COMPLEX_UNIT_PX, targetTextSize);
setLineSpacing(mSpacingAdd, mSpacingMult);
// Notify the listener if registered
if (mTextResizeListener != null) {
mTextResizeListener.onTextResize(this, oldTextSize, targetTextSize);
}
// Reset force resize flag
mNeedsResize = false;
}
}
// Set the text size of the text paint object and use a static layout to render text off screen before measuring
private int getTextHeight(CharSequence source, TextPaint paint, int width, float textSize) {
TextPaint paintCopy = new TextPaint(paint);
paintCopy.setTextSize(textSize);
StaticLayout layout = new StaticLayout(source, paintCopy, width, Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, true);
return layout.getHeight();
}
}

remove setHorizontallyScrolling(true) it is contradicting with setEllipsize(TextUtils.TruncateAt.END); Refer to the android doc for each ones function
https://developer.android.com/reference/android/widget/TextView.html

Related

Unit testing a complex renderer class (Best Practices)

I'm trying to test a class which purpose is taking a specification of what to do with a BufferedImage array and proccess it.
I'm concerned about how to aproach this task, it makes no sense to me just duplicate the function code on the test in order to generate a "expected bufferedImage array" and check if the generated BufferedImages are equals to the returned by the class methods.
I've read some slightly similar questions here, but the answers was "charge expected images from file and check with returned ones by class methods" but that sounds real hard to maintain if the proccess changes, new methods are added to the renderer class, it's hard or imposible pregenerate a resulting image or simply the class is refactorized and functions are splitted.
My actual question might be: Is there a little more elegant, "better to maintain" way to do that. I haven't so much experience with unit testing so i'm not sure about how to do this.
EDIT: sorry for the long code, i simplified and translated it. Excuse me if i did a bad translation. english it's not my main language.
public class Renderer extends SwingWorker<BufferedImage[], Integer>
{
private Device device;
private Main main;
private Controller controller;
public static final int FPS = 25;
public Renderer(Device device, Main main, Controller controller)
{
this.device = device;
this.main = main;
this.controller = controller;
}
#Override
protected BufferedImage[] doInBackground() throws Exception
{
// rendering image array
BufferedImage[] output = renderize(main.getActualEscene());
LOG.log.log(Level.INFO, "Rendering");
// getting how many times should repeat
String stringRepeat =
main.getActualEscene().getProperties().get(TextProperties.REPEAT.toString());
int repeat = (stringRepeat == null) ? 1 : Integer.parseInt(stringRepeat);
// getting text speed
String stringFps =
main.getActualEscene().getProperties().get(TextProperties.TEXT_SPEED.toString());
int fps = (stringFps == null) ? FPS : Integer.parseInt(stringFps);
if (!this.isCancelled()) // if this task is not cancelled
{
// we create a pre-viewer
controller.setPreviewer(
new Previewer(controller, repeat, fps, main.getActualEscene()));
SwingUtilities.invokeLater(new Runnable()
{// sincronizing this code with AWT thread
#Override
public void run()
{ // if it's not cancelled
if (controller.getPreviewer() != null
&& !controller.getPreviewer().isCancelled())
{
controller.getPreviewer().execute(); //execute the task
}
}
});
}
return output;
}
/**
* Renders a scene and transforms it in a image array, then save it to the scene
*
* #param scene -> scene
* #return an array with the scene frames
*/
public BufferedImage[] renderize(Scene scene)
{
BufferedImage[] output = null; // end result
BufferedImage[] base = new BufferedImage[1]; // base image
BufferedImage[] animationImages = null; // animation layer
BufferedImage[] textLayer = null; // text layer
BufferedImage[] overLayer = null; // overlayer
// omitted long process to retrieve image properties
// once the text properties are retrieved if it's not null it will be rendered
if (text != null)
{
String backgroundColorString =
scene.getProperties().get(TextProperties.BACKGROUND_COLOR.toString());
int backGroundcolorInt = Integer.parseInt(backgroundColorString);
if (animationImages != null) // if there is an animation layer we create a same size text layer
{
textLayer = new BufferedImage[animationImages.length];
} else
{
textLayer = new BufferedImage[1];
}
for (int i = 0; i < textLayer.length; i++)
{
textLayer[i] = new BufferedImage(device.getWidth(), device.getHeight(),
BufferedImage.TYPE_INT_ARGB);
}
String font = scene.getProperties().get(TextProperties.FONT.toString());
int lettersHeigth = device.getHeight() / 3;
int colorNumber =
Integer.parseInt(scene.getProperties().get(TextProperties.TEXT_COLOR.toString()));
textLayer = addTextToAImage(text, font, lettersHeigth, new Color(colorNumber),
new Color(backGroundcolorInt), textLayer);
}
// has it overlayer?
if (scene.getProperties().containsKey(AnimationProperties.OVER.toString())) // if it has over
{
String over = scene.getProperties().get(AnimationProperties.OVER.toString());
overLayer =
this.readingImagesFromAnimation(OverLibrary.getInstance().getOverByName(over));
}
// mixing layers
output = base; // adding base base
if (animationImages != null) // if there is animation layer we add it
{
output = mixingTwoImages(output, animationImages);
}
if (textLayer != null) // if there is text layer
{
output = mixingTwoImages(output, textLayer);
}
if (overLayer != null) //if there is overlayer
{
output = mixingTwoImages(output, overLayer);
}
// delimiting image operative zone.
output = delimite(output, device);
main.getActualEscene().setPreview(new Preview(output));
LOG.log.log(Level.INFO, "Rendering scene finished: " + scene.getNombre());
return output;
}
/**
* Apply the device mask to an image. Delimiting the scene operative zone.
*
* #param input -> BufferedImage array which need to be delimited
* #param device -> device which it mask need to be applied
* #return An BufferedImage array with a delimited image for the device.
*/
private BufferedImage[] delimite(BufferedImage[] input, Device device)
{
BufferedImage[] output = new BufferedImage[input.length];
for (int i = 0; i < output.length; i++) // copy all original frames
{
output[i] = new BufferedImage(device.getWidth(), device.getHeight(),
BufferedImage.TYPE_INT_ARGB);
Graphics2D graphics = output[i].createGraphics();
graphics.drawImage(input[i], 0, 0, null);
}
for (int i = 0; i < output.length; i++) // for each frame
{
for (int j = 0; j < output[i].getHeight(); j++) // for each row
{
for (int k = 0; k < output[i].getWidth(); k++) // for each column
{
if (!device.estaEnZonaOperativa(j, k)) // if the coordinate is not in the operative zone
{
int pixel = 0x00000000; // we create a transparent pixel
output[i].setRGB(j, k, pixel); // set the original pixel with this new one
}
}
}
}
return output;
}
/**
* method that reads an animation images and returns it into an array
*
* #param animation -> animaciĆ³n de la que obtendremos sus image
* #return array de BufferedImage con los frames de la animaciĆ³n ordenados por orden de lectura
* ascendente.
*/
private BufferedImage[] readingImagesFromAnimation(Animation animation)
{
BufferedImage[] output = new BufferedImage[animation.getData().length];
for (int i = 0; i < animation.getData().length; i++) // for each frame
{
try
{
File ruta = animation.getData()[i]; // getting its path
output[i] = ImageIO.read(ruta); // reading from the path and save it into output
} catch (IOException e)
{
LOG.log.log(Level.SEVERE, "error reading animation file: " + animation.getData()[i],
e);
}
}
return output;
}
/**
* create a text layer an add it to the image parameter center
*
* #param text -> text to include
* #param font -> font of the text
* #param size -> size
* #param color -> text color
* #param image -> image you want to add text
* #return a text layer that need to be mixed with original one.
*/
private BufferedImage[] addTextToAImage(String text, String font, int size, Color color,
Color backgroundColor, BufferedImage[] image)
{
// we set the size of the output as the same of the original image
BufferedImage[] output = new BufferedImage[image.length];
for (int i = 0; i < image.length; i++) // for each frame
{
output[i] = new BufferedImage(image[i].getWidth(), image[i].getHeight(),
BufferedImage.TYPE_INT_ARGB); // we create a single new image
Graphics2D graphics = output[i].createGraphics(); // get its graphics
// calculate for metrics
Font font = new Font(font, Font.PLAIN, size); // create a font
graphics.setFont(font); // setting it into the graphics
FontMetrics fontMetrics = graphics.getFontMetrics(font); // generating metric object
// calculate the text coordinates
int x = (image[i].getWidth() - fontMetrics.stringWidth(text)) / 2;
int y = ((image[i].getHeight() - fontMetrics.getAscent() - fontMetrics.getDescent()) / 2)
+ fontMetrics.getAscent();
int width = fontMetrics.stringWidth(text); // usefull metric to set a brackground
int height = fontMetrics.getAscent() + fontMetrics.getDescent();
// setting a background
graphics.setColor(backgroundColor);
graphics.fill(new Rectangle(x, y - fontMetrics.getAscent(), width, height));
// drawing text
graphics.setColor(color); // setting a color for text
graphics.drawString(text, x, y);
}
return output;
}
/**
* method to join two bufferedImage arrays and mix them. The shortest array will be played on loop
*
* WARNIN: the images needs to has the same resolution
*
* #param image -> image mixed as background
* #param image2 -> image mixed as foreground
* #return a mixed image array.
*/
private BufferedImage[] mixingTwoImages(BufferedImage[] image, BufferedImage[] image2)
{
// checking no empty images.
if (image.length == 0 || image2.length == 0)
{
RuntimeException exception =
new RuntimeException("empty images");
LOG.log.log(Level.SEVERE, exception.getMessage(), exception);
throw exception;
}
int width = Math.max(image[0].getWidth(), image2[0].getWidth());
int height = Math.max(image[0].getHeight(), image2[0].getHeight());
// creating a sequence of the longest size of them
BufferedImage[] output = new BufferedImage[Math.max(image.length, image2.length)];
for (int i = 0; i < output.length; i++) // for each frame
{
// we create a frame
output[i] = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
Graphics g = output[i].getGraphics();
// mixing the frame
if (image.length > image2.length) // if the first array is longer
{
g.drawImage(image[i], 0, 0, null);
g.drawImage(image2[i % image2.length], 0, 0, null);
} else if (image.length < image2.length) // if the second array is longer
{
g.drawImage(image[i % image.length], 0, 0, null);
g.drawImage(image2[i], 0, 0, null);
} else // bot of the same size
{
g.drawImage(image[i], 0, 0, null);
g.drawImage(image2[i], 0, 0, null);
}
}
return output;
}

Edittext - The limit to number of characters as a function of screen size. Is it possible?

I'd like to have different limits to number of characters in Edittext. That limit must depend upon the screen size. Is it possible to do so? If yes, please share your idea.
Sure. What you can do is add a InputFilter to the edit text. You can then monitor the change in text width of the edit text. If it ever goes beyond some point, you can just chop off the extras. In this case, you'd want to restrict the text width to the width available for the EditText.
Your input filter:
public class TextSizeFilter implements InputFilter {
Paint p;
int maxWidth;
/**
* A filter based on the maxWidth of the text.
* #param p Paint used by the View
* #param maxWidth Max width of the text
*/
public TextSizeFilter(Paint p, int maxWidth) {
this.p = p;
this.maxWidth = maxWidth;
}
#Override
public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
float originalW = p.measureText(dest, 0, dstart);
float spaceLeft = maxWidth - originalW;
if (spaceLeft > 0) {
int w = p.breakText(source, start, end, true, spaceLeft, null);
if (w != source.length())
return source.subSequence(0, start + w);
} else {
return "";
}
return null;
}
}
Then you need to measure the width of the edit text (and subtract the padding since that space cannot be used) and set the input filter of the edit text.
EditText t = ...
int maxWidth = t.getWidth() - t.getPaddingLeft() - t.getPaddingRight();
t.setFilters(new InputFilter[]{new TextSizeFilter(t.getPaint(), maxWidth)});
Based on #idunnololz's answer I made some changes, as that version doesn't seem to work well if the cursor is not at the end of the text, nor does is account for overwriting a text selection.
I also added optionally a maximum amount of characters (so the text will not exceed the view size, nor a limited amount of characters, which ever comes first).
public class TextLengthFilter implements InputFilter {
private final static int NO_MAX_CHARACTERS = -1;
private Paint p;
private int maxWidth;
private int maxCharacters;
/**
* A filter based on the maxWidth of the text.
* #param p Paint used by the View
* #param maxWidth Max width of the text (in pixels)
*/
public TextLengthFilter(Paint p, int maxWidth) {
this(p, maxWidth, NO_MAX_CHARACTERS);
}
/**
* A filter based on the maxWidth of the text.
* #param p Paint used by the View
* #param maxWidth Max width of the text (in pixels)
* #param maxCharacters Max amount of characters for the text
*/
public TextLengthFilter(Paint p, int maxWidth, int maxCharacters) {
this.p = p;
this.maxWidth = maxWidth;
this.maxCharacters = maxCharacters;
}
#Override
public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
float originalW = p.measureText(dest, 0, dest.length());
//Calculate the amount of space that is being reclaimed by characters that are replaced
int toBeReplacedWidth = p.breakText(dest, dstart, dend, true, originalW, null);
float spaceLeft = (maxWidth - originalW) + toBeReplacedWidth;
int selectionLength = (dend - dstart); //The amount of characters that are going to be replaced
int changeLength = (end - start);
//Check if there are more characters after the change than before.
if (maxCharacters != NO_MAX_CHARACTERS && changeLength > selectionLength) {
//If the length of the original text was already too many characters don't allow more to be added.
if (dest.length() > maxCharacters) {
return "";
}
int finalLength = dest.length() + changeLength - selectionLength;
//Check if the final length, after the replacement, doesn't exceed the maximum characters
if (finalLength > maxCharacters) {
//if it does limit the characters to be added to not exceed the maximum
end = end - (finalLength - maxCharacters);
}
}
//Check if the size of the characters does not exceed the maximum view width
if (spaceLeft > 0) {
int w = p.breakText(source, start, end, true, spaceLeft, null) ;
//If not all characters would fit only allow the ones that do fit
if (w != source.length())
return source.subSequence(0, start + w);
}
else {
return "";
}
return null;
}
}

RSyntaxTextArea not showing line numbers

I'm using RSyntaxTextArea for a minimized IDE I'm working on, Everything seems to be really working smoothly except for the line numbering, which I couldn't really make it show:
RSyntaxTextArea textArea = new RSyntaxTextArea(20, 60);
textArea.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_C);
textArea.setCodeFoldingEnabled(true);
textArea.setAntiAliasingEnabled(true);
RTextScrollPane sp = new RTextScrollPane(textArea);
sp.setLineNumbersEnabled(true);
sp.setFoldIndicatorEnabled(true);
if ( sp.getLineNumbersEnabled() )
{
System.out.println("Enabled"); // it prints the line but it's not showing
}
contentPane.add(/*textEditorScrollPane*/ textArea, BorderLayout.CENTER);
I can't figure out why it's not showing the line numbers..
It's not showing the scrollbars either, right? Assuming that contentPane is where you want your components, you need to add the RTextScrollPane instance to the contentPane, not the RSyntaxTextArea instance. The Gutter, which displays line numbers, is a part of the RTextScrollPane - an extended JScrollPane.
If you don't add a scroll pane to your GUI, it will not be shown, nor will you be able to scroll around. :P
So try the following:
contentPane.add(sp, BorderLayout.CENTER);
Alternatively, you can use the following class:
import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.border.CompoundBorder;
import javax.swing.border.EmptyBorder;
import javax.swing.border.MatteBorder;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.*;
import java.awt.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.HashMap;
/**
* This class will display line numbers for a related text component. The text
* component must use the same line height for each line. TextLineNumber
* supports wrapped lines and will highlight the line number of the current
* line in the text component.
* <p>
* This class was designed to be used as a component added to the row header
* of a JScrollPane.
*/
public class TextLineNumber extends JPanel
implements CaretListener, DocumentListener, PropertyChangeListener
{
public final static float LEFT = 0.0f;
public final static float CENTER = 0.5f;
public final static float RIGHT = 1.0f;
private final static Border OUTER = new MatteBorder(0, 0, 0, 2, Color.GRAY);
private final static int HEIGHT = Integer.MAX_VALUE - 1000000;
// Text component this TextTextLineNumber component is in sync with
private JTextComponent component;
// Properties that can be changed
private boolean updateFont;
private int borderGap;
private Color currentLineForeground;
private float digitAlignment;
private int minimumDisplayDigits;
// Keep history information to reduce the number of times the component
// needs to be repainted
private int lastDigits;
private int lastHeight;
private int lastLine;
private HashMap<String, FontMetrics> fonts;
/**
* Create a line number component for a text component. This minimum
* display width will be based on 3 digits.
*
* #param component the related text component
*/
public TextLineNumber(JTextComponent component)
{
this(component, 3);
}
/**
* Create a line number component for a text component.
*
* #param component the related text component
* #param minimumDisplayDigits the number of digits used to calculate
* the minimum width of the component
*/
public TextLineNumber(JTextComponent component, int minimumDisplayDigits)
{
this.component = component;
setFont(component.getFont());
setBorderGap(5);
setCurrentLineForeground(Color.RED);
setDigitAlignment(RIGHT);
setMinimumDisplayDigits(minimumDisplayDigits);
component.getDocument().addDocumentListener(this);
component.addCaretListener(this);
component.addPropertyChangeListener("font", this);
}
/**
* Gets the update font property
*
* #return the update font property
*/
public boolean getUpdateFont()
{
return updateFont;
}
/**
* Set the update font property. Indicates whether this Font should be
* updated automatically when the Font of the related text component
* is changed.
*
* #param updateFont when true update the Font and repaint the line
* numbers, otherwise just repaint the line numbers.
*/
public void setUpdateFont(boolean updateFont)
{
this.updateFont = updateFont;
}
/**
* Gets the border gap
*
* #return the border gap in pixels
*/
public int getBorderGap()
{
return borderGap;
}
/**
* The border gap is used in calculating the left and right insets of the
* border. Default value is 5.
*
* #param borderGap the gap in pixels
*/
public void setBorderGap(int borderGap)
{
this.borderGap = borderGap;
Border inner = new EmptyBorder(0, borderGap, 0, borderGap);
setBorder(new CompoundBorder(OUTER, inner));
lastDigits = 0;
setPreferredWidth();
}
/**
* Gets the current line rendering Color
*
* #return the Color used to render the current line number
*/
public Color getCurrentLineForeground()
{
return currentLineForeground == null ? getForeground() : currentLineForeground;
}
/**
* The Color used to render the current line digits. Default is Coolor.RED.
*
* #param currentLineForeground the Color used to render the current line
*/
public void setCurrentLineForeground(Color currentLineForeground)
{
this.currentLineForeground = currentLineForeground;
}
/**
* Gets the digit alignment
*
* #return the alignment of the painted digits
*/
public float getDigitAlignment()
{
return digitAlignment;
}
/**
* Specify the horizontal alignment of the digits within the component.
* Common values would be:
* <ul>
* <li>TextLineNumber.LEFT
* <li>TextLineNumber.CENTER
* <li>TextLineNumber.RIGHT (default)
* </ul>
*
* #param currentLineForeground the Color used to render the current line
*/
public void setDigitAlignment(float digitAlignment)
{
this.digitAlignment =
digitAlignment > 1.0f ? 1.0f : digitAlignment < 0.0f ? -1.0f : digitAlignment;
}
/**
* Gets the minimum display digits
*
* #return the minimum display digits
*/
public int getMinimumDisplayDigits()
{
return minimumDisplayDigits;
}
/**
* Specify the mimimum number of digits used to calculate the preferred
* width of the component. Default is 3.
*
* #param minimumDisplayDigits the number digits used in the preferred
* width calculation
*/
public void setMinimumDisplayDigits(int minimumDisplayDigits)
{
this.minimumDisplayDigits = minimumDisplayDigits;
setPreferredWidth();
}
/**
* Calculate the width needed to display the maximum line number
*/
private void setPreferredWidth()
{
Element root = component.getDocument().getDefaultRootElement();
int lines = root.getElementCount();
int digits = Math.max(String.valueOf(lines).length(), minimumDisplayDigits);
// Update sizes when number of digits in the line number changes
if (lastDigits != digits)
{
lastDigits = digits;
FontMetrics fontMetrics = getFontMetrics(getFont());
int width = fontMetrics.charWidth('0') * digits;
Insets insets = getInsets();
int preferredWidth = insets.left + insets.right + width;
Dimension d = getPreferredSize();
d.setSize(preferredWidth, HEIGHT);
setPreferredSize(d);
setSize(d);
}
}
/**
* Draw the line numbers
*/
#Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
// Determine the width of the space available to draw the line number
FontMetrics fontMetrics = component.getFontMetrics(component.getFont());
Insets insets = getInsets();
int availableWidth = getSize().width - insets.left - insets.right;
// Determine the rows to draw within the clipped bounds.
Rectangle clip = g.getClipBounds();
int rowStartOffset = component.viewToModel(new Point(0, clip.y));
int endOffset = component.viewToModel(new Point(0, clip.y + clip.height));
while (rowStartOffset <= endOffset)
{
try
{
if (isCurrentLine(rowStartOffset))
{
g.setColor(getCurrentLineForeground());
} else
{
g.setColor(getForeground());
}
// Get the line number as a string and then determine the
// "X" and "Y" offsets for drawing the string.
String lineNumber = getTextLineNumber(rowStartOffset);
int stringWidth = fontMetrics.stringWidth(lineNumber);
int x = getOffsetX(availableWidth, stringWidth) + insets.left;
int y = getOffsetY(rowStartOffset, fontMetrics);
g.drawString(lineNumber, x, y);
// Move to the next row
rowStartOffset = Utilities.getRowEnd(component, rowStartOffset) + 1;
} catch (Exception e)
{
break;
}
}
}
/*
* We need to know if the caret is currently positioned on the line we
* are about to paint so the line number can be highlighted.
*/
private boolean isCurrentLine(int rowStartOffset)
{
int caretPosition = component.getCaretPosition();
Element root = component.getDocument().getDefaultRootElement();
if (root.getElementIndex(rowStartOffset) == root.getElementIndex(caretPosition))
{
return true;
} else
{
return false;
}
}
/*
* Get the line number to be drawn. The empty string will be returned
* when a line of text has wrapped.
*/
protected String getTextLineNumber(int rowStartOffset)
{
Element root = component.getDocument().getDefaultRootElement();
int index = root.getElementIndex(rowStartOffset);
Element line = root.getElement(index);
if (line.getStartOffset() == rowStartOffset)
{
return String.valueOf(index + 1);
} else
{
return "";
}
}
/*
* Determine the X offset to properly align the line number when drawn
*/
private int getOffsetX(int availableWidth, int stringWidth)
{
return (int) ((availableWidth - stringWidth) * digitAlignment);
}
/*
* Determine the Y offset for the current row
*/
private int getOffsetY(int rowStartOffset, FontMetrics fontMetrics)
throws BadLocationException
{
// Get the bounding rectangle of the row
Rectangle r = component.modelToView(rowStartOffset);
int lineHeight = fontMetrics.getHeight();
int y = r.y + r.height;
int descent = 0;
// The text needs to be positioned above the bottom of the bounding
// rectangle based on the descent of the font(s) contained on the row.
if (r.height == lineHeight) // default font is being used
{
descent = fontMetrics.getDescent();
} else // We need to check all the attributes for font changes
{
if (fonts == null)
{
fonts = new HashMap<>();
}
Element root = component.getDocument().getDefaultRootElement();
int index = root.getElementIndex(rowStartOffset);
Element line = root.getElement(index);
for (int i = 0; i < line.getElementCount(); i++)
{
Element child = line.getElement(i);
AttributeSet as = child.getAttributes();
String fontFamily = (String) as.getAttribute(StyleConstants.FontFamily);
Integer fontSize = (Integer) as.getAttribute(StyleConstants.FontSize);
String key = fontFamily + fontSize;
FontMetrics fm = fonts.get(key);
if (fm == null)
{
Font font = new Font(fontFamily, Font.PLAIN, fontSize);
fm = component.getFontMetrics(font);
fonts.put(key, fm);
}
descent = Math.max(descent, fm.getDescent());
}
}
return y - descent;
}
//
// Implement CaretListener interface
//
#Override
public void caretUpdate(CaretEvent e)
{
// Get the line the caret is positioned on
int caretPosition = component.getCaretPosition();
Element root = component.getDocument().getDefaultRootElement();
int currentLine = root.getElementIndex(caretPosition);
// Need to repaint so the correct line number can be highlighted
if (lastLine != currentLine)
{
repaint();
lastLine = currentLine;
}
}
//
// Implement DocumentListener interface
//
#Override
public void changedUpdate(DocumentEvent e)
{
documentChanged();
}
#Override
public void insertUpdate(DocumentEvent e)
{
documentChanged();
}
#Override
public void removeUpdate(DocumentEvent e)
{
documentChanged();
}
/*
* A document change may affect the number of displayed lines of text.
* Therefore the lines numbers will also change.
*/
private void documentChanged()
{
// View of the component has not been updated at the time
// the DocumentEvent is fired
SwingUtilities.invokeLater(() ->
{
try
{
int endPos = component.getDocument().getLength();
Rectangle rect = component.modelToView(endPos);
if (rect != null && rect.y != lastHeight)
{
setPreferredWidth();
repaint();
lastHeight = rect.y;
}
} catch (BadLocationException ex)
{ /* nothing to do */ }
});
}
//
// Implement PropertyChangeListener interface
//
#Override
public void propertyChange(PropertyChangeEvent evt)
{
if (evt.getNewValue() instanceof Font)
{
if (updateFont)
{
Font newFont = (Font) evt.getNewValue();
setFont(newFont);
lastDigits = 0;
setPreferredWidth();
} else
{
repaint();
}
}
}
}
Then line numbers can be added like this:
TextLineNumber textLineNumber = new TextLineNumber(sourceCodeArea);
sourceCodeAreaScrollPane.setRowHeaderView(textLineNumber);

how to overlay image with multiline text(text will be in center of the canvas )

I am developing photography apps in that I overlay an image with text.
Here is my code:
Bitmap mBitmap = BitmapFactory.decodeResource(getResources(),
R.drawable.themes11);
// create a mutable bitmap with the same size as the background image's size
bmOverlay = Bitmap.createBitmap(mBitmap.getWidth(),
mBitmap.getHeight(), Bitmap.Config.ARGB_4444);
// create a canvas on which to draw
Canvas canvas = new Canvas(bmOverlay);
TextPaint paint = new TextPaint();
paint.setColor(Color.RED);
paint.setTextSize(40);
paint.setFlags(Paint.ANTI_ALIAS_FLAG);
// if the background image is defined in main.xml, omit this line
canvas.drawBitmap(mBitmap, 0, 0, null);
// draw the text and the point
canvas.drawPoint(50, 100, paint);
// canvas.drawText(InstaTextActivity.CurrentWord, 300, 200, paint);
StaticLayout layout = new StaticLayout(InstaTextActivity.CurrentWord,
paint, display.getHeight(),
android.text.Layout.Alignment.ALIGN_NORMAL, (float) 1.0,
(float) 0.0, true);
canvas.translate(width / 5, height / 5);
layout.draw(canvas);
imageview_img.setImageBitmap(bmOverlay);
In this code I overlay the text on screen width/2 and height/2 it will display on top the image but I want the text to be center-aligned. Also when I write a large text it will align form center to right.
Have a look at the images to see how I want it:
The Image Background:
And the result I want:
Use the below methods to measure the height and width of the text.
Then when drawing the text on canvas
left = width/2 - textWidth/2
top = height/2 - textHeight/2
But if you need a multiple line text for long texts, it will be a bit tricky.
/**
* Method to get the height of the paint
*
* #param brush The TextPaint used to paint the text
* #param text The text which needs to be measured
* #return height of the text
*/
public static int measureTextHeight(Paint brush, String text) {
Rect result = new Rect();
// Measure the text rectangle to get the height
brush.getTextBounds(text, 0, text.length(), result);
return result.height();
}
/**
* Method to get the width of the paint
*
* #param brush The TextPaint used to paint the text
* #param text The text which needs to be measured
* #return width of the text
*/
public static int measureTextWidth(Paint brush, String text) {
Rect result = new Rect();
// Measure the text rectangle to get the height
brush.getTextBounds(text, 0, text.length(), result);
return result.width();
}
i using this. working for me.
public Bitmap drawMultilineTextToBitmap(Context gContext,
int gResId,
String gText) {
// prepare canvas
Resources resources = gContext.getResources();
float scale = resources.getDisplayMetrics().density;
Bitmap bitmap = BitmapFactory.decodeResource(resources, gResId);
android.graphics.Bitmap.Config bitmapConfig = bitmap.getConfig();
// set default bitmap config if none
if(bitmapConfig == null) {
bitmapConfig = android.graphics.Bitmap.Config.ARGB_8888;
}
// resource bitmaps are imutable,
// so we need to convert it to mutable one
bitmap = bitmap.copy(bitmapConfig, true);
Canvas canvas = new Canvas(bitmap);
// new antialiased Paint
TextPaint paint=new TextPaint(Paint.ANTI_ALIAS_FLAG);
// text color - #3D3D3D
paint.setColor(Color.rgb(61, 61, 61));
// text size in pixels
paint.setTextSize((int) (14 * scale));
// text shadow
paint.setShadowLayer(1f, 0f, 1f, Color.WHITE);
// set text width to canvas width minus 16dp padding
int textWidth = canvas.getWidth() - (int) (16 * scale);
// init StaticLayout for text
StaticLayout textLayout = new StaticLayout(
gText, paint, textWidth, Layout.Alignment.ALIGN_CENTER, 1.0f, 0.0f, false);
// get height of multiline text
int textHeight = textLayout.getHeight();
// get position of text's top left corner
float x = (bitmap.getWidth() - textWidth)/2;
float y = (bitmap.getHeight() - textHeight)/2;
// draw text to the Canvas center
canvas.save();
canvas.translate(x, y);
textLayout.draw(canvas);
canvas.restore();
return bitmap;
}
source : http://www.skoumal.net/en/android-drawing-multiline-text-on-bitmap/
This is not a very refined approach(Not optimized for memory usage) but does the job. You will need to modify the code in order to split at the string keeping words intact. Hope this helps.
package org.edu.abhi;
import java.util.ArrayList;
import java.util.List;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.AsyncTask;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.View;
import android.view.WindowManager;
/**
* #author Abhishek_Nandi
*
*/
public class DummyUserSpace extends View {
/***************************************************************************
* Calls superclass constructor. This will call {#link #init(Context)}
*
* #param context
* The Context the view is running in, through which it can
* access the current theme, resources, etc.
**************************************************************************/
public DummyUserSpace(Context context) {
super(context);
init(context);
}
/***************************************************************************
* Calls superclass constructor. This will call {#link #init(Context)}
*
* #param context
* The Context the view is running in, through which it can
* access the current theme, resources, etc.
* #param attrs
* The attributes of the XML tag that is inflating the view.
* #param defStyle
* The default style to apply to this view. If 0, no style will
* be applied (beyond what is included in the theme). This may
* either be an attribute resource, whose value will be retrieved
* from the current theme, or an explicit style resource.
**************************************************************************/
public DummyUserSpace(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
/***************************************************************************
* Calls superclass constructor. This will call {#link #init(Context)}
*
* #param context
* The Context the view is running in, through which it can
* access the current theme, resources, etc.
* #param attrs
* The attributes of the XML tag that is inflating the view.
**************************************************************************/
public DummyUserSpace(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
/**
* Width of the View
*/
int viewWidth = 0;
/**
* Height of the View
*/
int viewHeight = 0;
/**
* {#link Paint} which is used to draw {#link #text}
*/
Paint textPaint;
/**
* The text which needs to be drawn
*/
String text = "can be spared the burden of having to perform manual memory management. In some languages, memory for the creation of objects is implicitly allocated on the stack, or explicitly allocated and deallocated from the heap. In the latter case the responsibility of managing memory resides with the programmer. If the program does not deallocate an object, a memory leak occurs. If the program attempts to access or deallocate memory that has already been deallocated, the result is undefined and difficult to predict, and the program is likely to become unstable and/or crash. This can be partially remedied by the use of smart pointers, but these add overhead and complexity. Note that garbage collection does not prevent \"logical\" memory leaks, i.e. those where the memory is still referenced but never used.. o have as few implementation dependencies as possible. It is intended to let application developers \"write once, run anywhere\" (WORA), meaning that code that runs on one platform does not need to be recompiled to run on another. Java is as of 2012 one of the most popular programming languages in use, particularly for client-server web applications, with a reported 10 million users";
/**
* Common constructor routine
*
* #param context
* The Context the view is running in. It is used to get the
* density of the device
*/
private void init(Context context) {
textPaint = new Paint();
textPaint.setColor(Color.WHITE);
textPaint.setTextSize(40);
textPaint.setStrokeWidth(6.0f);
WindowManager manager = (WindowManager) context
.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics outMetrics = new DisplayMetrics();
manager.getDefaultDisplay().getMetrics(outMetrics);
padding *= outMetrics.density;
lineSpacing *= outMetrics.density;
}
/**
* Split the string and compute the {#link #startY}, {#link #textHeight}
* which is required for drawing the view
*/
private void prepareForDrawing() {
viewWidth = getWidth();
viewHeight = getHeight();
if (viewHeight == 0) {
return;
}
textHeight = Utility.measureTextHeight(textPaint, text);
if (0 == textHeight) {
return;
}
maxLines = (viewHeight - 2 * padding) / (textHeight + lineSpacing);
prepareMultiLineText(textPaint, text);
int noOfLines = splittedText.size();
int section = noOfLines / 2;
int center = viewHeight / 2;
if (noOfLines % 2 == 0) {
center -= lineSpacing / 2;
} else {
center -= textHeight / 2;
}
startY = center - (section - 1) * (textHeight + lineSpacing)
+ lineSpacing;
}
/**
* The starting position from where the view will start drawing
*/
private int startY = 0;
/**
* Height of the text
*/
private int textHeight = 0;
/**
* Maximum lines this view can hold
*/
private int maxLines = 1;
/**
* Padding from the view border
*/
private int padding = 20;
/**
* Spacing between each line
*/
private int lineSpacing = 10;
private final String loadingText = "Loading..";
/*
* (non-Javadoc)
*
* #see android.view.View#onDraw(android.graphics.Canvas)
*/
#Override
protected void onDraw(Canvas canvas) {
if (!computationComplete) {
new DrawComputations().execute();
canvas.drawText(
loadingText,
getWidth() / 2
- Utility.measureTextWidth(textPaint, loadingText)
/ 2,
getHeight() / 2
- Utility.measureTextHeight(textPaint, loadingText)
/ 2, textPaint);
} else {
for(int count = 0; count<splittedText.size() ; count++){
String trimmed = splittedText.get(count);
//System.out.println(trimmed);
canvas.drawText(
trimmed,
viewWidth / 2
- Utility.measureTextWidth(textPaint, trimmed)
/ 2, startY + (count - 1)
* (textHeight + lineSpacing), textPaint);
}
}
}
/**
* Denotes the computation has completed and the view is ready to be drawn
*/
boolean computationComplete = false;
/**
* This {#link AsyncTask} performs the computation in background and updates
* the view when computation is finished and the view is ready to be drawn
*
* #author Abhishek_nandi
* #version 1.0
*/
class DrawComputations extends AsyncTask<Void, Void, Void> {
/*
* (non-Javadoc)
*
* #see android.os.AsyncTask#doInBackground(Params[])
*/
#Override
protected Void doInBackground(Void... params) {
prepareForDrawing();
computationComplete = true;
return null;
}
/*
* (non-Javadoc)
*
* #see android.os.AsyncTask#onPostExecute(java.lang.Object)
*/
#Override
protected void onPostExecute(Void result) {
invalidate();
}
}
/**
* Collection which holds the multi-line text
*/
private List<String> splittedText = new ArrayList<String>();
/**
* This method is responsible for stripping {#link #text} into individual
* lines in order to form the multi line text. This method can be used as a
* utility.
*
* #param paint
* The {#link Paint} which is used to draw the text
* #param str
* The string which needs to be stripped
*/
private void prepareMultiLineText(Paint paint, String str) {
if (str == null || str.trim().length() == 0)
return;
str = str.trim();
String result = str;
int boxWidth = viewWidth;
try {
float textWidth = paint.measureText(str) + 2 * padding;
result = str;
while (textWidth > boxWidth) {
if (result.length() == 0)
break;//keeping the entire word intact//if(result.lastIndexOf(" ")!=-1){result = result.substring(0, result.lastIndexOf(" "))}else
result = result.substring(0, result.length() - 1);
textWidth = paint.measureText(result) + 2 * padding;
}
result = result.trim();
boolean exceeded = false;
if (splittedText.size() == maxLines) {
exceeded = true;
result = result.substring(0, result.length()-2).concat("..");
}
splittedText.add(result);
if (!exceeded && result.length() != str.length()) {
prepareMultiLineText(textPaint, str.substring(result.length()));
}
} catch (Exception e) {
Log.e("CustomView", "prepareMultiLineText", e);
}
}
} // END of class
// END of file
You should use StaticLayout for this. It measures and draws multiline text, handling line wrapping, line spacing, alignment etc.
StaticLayout layout = new StaticLayout("your long text", textPaint,
pixelsToFitWidthTo, Layout.Alignment.ALIGN_NORMAL, 1f, 0f, true);
int boxHeight = layout.getHeight(); // measure
The text can even be HTML formatted, just wrap your text in Html.fromHtml():
Html.fromHtml("your long <b>and bold</b> text");

Icons between text in a jlabel using graphics

How to paint multiple icons (ie., say icons between text) in JLabel using graphics? Please do assist me in this effort
One option I would use is to have a sequence of JLabels. Ones with icon and ones with text only.
The other option would be to leverage the mini HTML support of the JLabel: Have
<html><img src='theimage.png'>The text<img src='theimage2.png'>
as the text of the JLabel. This approach works for text formatting but I'm not sure if the image tags work there too.
Or you did override the JLabel.paint() to do custom rendering btw?
Then I would use the following approach:
List<Object> textAndImage = new ArrayList<Object>() {{
add("This ");
add(new ImageIcon("image1.png"));
add(" is ");
add(new ImageIcon("image2.png"));
add(" an ");
add(" imaged text sample ");
}};
FontMetrics fm = g.getFontMetrics();
int x = 0;
for (Object o : textAndImage) {
if (o instanceof String) {
g.drawString((String)o, x, fm.getHeight());
x += fm.stringWidth((String)o);
} else
if (o instanceof ImageIcon) {
((ImageIcon)o).paintIcon(null, g, x, 0);
x += ((ImageIcon)o).getIconWidth();
}
}
Of course this is not a fully fledged solution but might give you some hints how to proceed.
One way is to use a custom border that paints an icon (and text if you want), then you can nest indefinitely.
Here's one such border:
/**
* Show a leading or trailing icon in border.
*/
public static class IconBorder implements Border
{
/**
* #param icon - icon to paint for this border
* #param top outer insets for this border
* #param left
* #param bottom
* #param right
*/
public IconBorder(Icon icon, int top, int left, int bottom, int right)
{
setIcon(icon);
top_ = top;
left_ = left;
bottom_ = bottom;
right_ = right;
rect_ = new Rectangle(0, 0, icon_.getIconWidth(), icon_.getIconHeight());
}
public Insets getBorderInsets(Component c)
{
if( icon_ == null )
return new Insets(0, 0, 0, 0);
return isTrailing_ ? new Insets(top_, left_, bottom_, icon_.getIconWidth() + right_) :
new Insets(top_, icon_.getIconWidth() + left_, bottom_, right_);
}
public boolean isBorderOpaque()
{
return false;
}
public void paintBorder(Component c, Graphics g, int x, int y, int width, int height)
{
if( icon_ != null )
{
if( isTrailing_ )
x = width - icon_.getIconWidth() + 4;
else
x += left_ - 1;
icon_.paintIcon(c, g, x, y + top_);
rect_.x = x;
rect_.y = y;
}
}
public void setIcon(Icon icon)
{
if( icon_ != icon )
icon_ = icon;
}
public Icon getIcon()
{
return icon_;
}
public void setPosition(boolean isTrailing)
{
isTrailing_ = isTrailing;
}
public Rectangle getIconRect()
{
return rect_;
}
private final int top_;
private final int left_;
private final int bottom_;
private final int right_;
private final Rectangle rect_;
private Icon icon_;
private boolean isTrailing_ = true;
}
I use this to add a search icon to a JTextField (a la browser search box). getIconRect can be used to check for mouse hover. For example I change cursor to HAND_CURSOR when mouse is over the search icon.

Categories