JTextArea: Limit string width and line count? - java

The user needs to enter text to be printed on a fixed-size label. Given a font, the label has a fixed number of lines and also a fixed width in pixels. How can I adapt a JTextArea (or something else in Swing, if there is another option) to this use case?
Maximum number of lines
Each line of characters not exceeding a certain pixel width
Text wrapping from line to line at the word level for lines attempting to exceed the maximum pixel width
I have a PlainDocument that limits the length of a single line of text according to the width in pixels of the string, measured in capital Ws (the widest character in my font):
public class StandardDocument extends PlainDocument {
/****** VARIABLES **********************************************/
public boolean upperCase = true;
private int textLimit;
private int textWidth;
private int fieldWidth;
private JTextField textField = new JTextField();
/** Get the maximum width of this text field, measured in capital Ws.
* #return textWidth - int
**/
public int getTextWidth() {
return textWidth;
}
/** Get the maximum width of this text field, measured in pixels.
* #return textWidth - int
**/
protected int getFieldWidth() {
return fieldWidth;
}
/** Core method for inserting value provided by user after "cleaning" user's value automatically. **/
public void insertString(int offs, String str, AttributeSet attr) throws BadLocationException {
if (str == null) { return; }
// Set field value when value is within character limit of field.
if (textWidth > 0) {
int attemptedWidth = textField.getFontMetrics(Constants.defaultFontLabels).stringWidth(getText(0, getLength()) + str);
if (attemptedWidth > fieldWidth) {
return;
}
}
// Set value in field.
super.insertString(offs, str, attr);
}
/** Set the maximum text width as a maximum number of capital Ws this field may hold. **/
public void setTextWidth(int wLimit) {
if (wLimit >= 0) {
textWidth = wLimit;
StringBuffer outputBuffer = new StringBuffer(wLimit);
for (int i = 0; i < wLimit; i++){
outputBuffer.append("W");
}
fieldWidth = textField.getFontMetrics(Constants.defaultFontLabels).stringWidth(outputBuffer.toString());
}
}
}

Wrapping is supported by a JTextArea. You turn it on using:
textArea.setLineWrap( true );
textArea.setWrapStyleWord( true );
The line count will be a little more difficult since you don't know if the newly added text will cause the line to wrap until the Document has been updated.
So maybe you need a cross edit that you can invoke when you click the "Print" button. Maybe you can use the getWrappedLines(...) method from theText Utilities class. If the wrapped lines is greater than the maximum you prevent the printing.
Or, maybe you automatically insert the text into the Document. Then you check the number of lines. If greater than the maximum you display a message and then invoke the remove(...) method right away.

Related

How to find out line numbers of selected text?

Assume this very small program:
1. package ex1;
2. public interface Resizable {
3. void resize();
4. }
In my editor, if I select line 2-3 using mouse and say click on a button, I want to highlight these texts and also print, which line numbers were selected exactly for the button.
I can do the highlighting part, but I don't know how to find the line numbers of highlighted texts, As I think, I should use a listener, which will detect any changes in editor.
I think I should use an action listener, which will detect when the button is pressed after selecting text blocks. But how I will know, which lines are selected exactly?
The start and end of the highlight can be taken from the caret position dot and mark respectively. These are offsets in the Document. You must then calculate the number of newlines from the start of the document until the mark/do
textArea.addCaretListener(new CaretListener() {
#Override
public void caretUpdate(CaretEvent e) {
int startLine = getLine(e.getDot());
int endLine = getLine(e.getMark());
...
}
});
private int getLine(int offset) {
String text = textArea.getDocument().getText(0, offset);
int linenr = 0;
int idx = text.indexOf("\n");
while (idx != -1) {
linenr++;
idx = text.indexOf("\n", idx);
}
return linenr;
}

Setting event listener for two JTextArea's

I am developing an image processing software (just for fun) and one of the features it has is image resizing option. Basically window pops up, with two JTextArea components to get desired image width and height for resizing. There is also JCheckBox for keeping Aspect ratio if user desires it. The problem is. When check box is selected and user supposedly inputs either width or height first. I want the other text area to update itself accordingly every time a change is made so it would keep AR. I have developed some code that deals with this, but it does not provide what I really want due to lack of understanding what listener to what component should I really assign.
Code:
String height, width;
if (checkBoxImage.isSelected()){
// aspect ratio = width / height
width = widthArea.getText();
height = heightArea.getText();
double aspectRatio = (double) images.get(tabbedPane.getSelectedIndex()).getWidth() / images.get(tabbedPane.getSelectedIndex()).getHeight();
/**
* to do, update width, height area
* to the closest user input
*/
if(heightArea.getText().length() != 0 && heightArea.getText().length() <= 5
&& heightArea.getText().charAt(0) != '0'){
//parsing string to integer
try{
int heightNum = Integer.parseInt(height);
int widthNum = (int) Math.round(aspectRatio * heightNum);
widthArea.setText(String.valueOf(widthNum) );
widthArea.updateUI();
frameimgSize.repaint();
}
catch(NumberFormatException e1){JOptionPane.showMessageDialog(error,e1.getMessage(),"Error", JOptionPane.ERROR_MESSAGE);}
}
//width has been entered first
else if(widthArea.getText().length() != 0 && widthArea.getText().length() <= 5 &&
widthArea.getText().charAt(0) != '0'){
try{
int widthNum = Integer.parseInt(width);
int heightNum = (int) Math.round(aspectRatio * widthNum);
heightArea.setText(String.valueOf(heightNum) );
heightArea.updateUI();
frameimgSize.repaint();
}
catch(NumberFormatException e1){JOptionPane.showMessageDialog(error,e1.getMessage(),"Error", JOptionPane.ERROR_MESSAGE);}
}
}
Is it ever valid to have non-numeric values in your width and height fields?
If not, then use JSpinners or JFormattedTextFields instead of JTextFields. If so, (say for example you allow a "units" to be entered as well as width and height) you should attach a DocumentListener to your JTextFields to monitor changes to the content of the underlying text documents. Here's an example:
widthField.getDocument().addDocumentListener(new DocumentListener() {
public void changedUpdate(DocumentEvent e) {
update();
}
public void removeUpdate(DocumentEvent e) {
update();
}
public void insertUpdate(DocumentEvent e) {
update();
}
// your method that handles any Document change event
public void update() {
if( aspectCheckBox1.isSelected() ) {
// parse the width and height,
// constrain the height to the aspect ratio and update it here
}
}
});
You'd then add a similar DocumentListener to your heightTextField.
Note that if you use JTextFields you need to parse their contents, read the units (where applicable) and handle NumberFormatExceptions in the case where the user enters invalid numeric values.
To answer your question about where to add your handlers...
The update of the Width should happen when there is a Document change to the Height GUI element. Similarly the update of the Height should happen when there is a document change to the Width GUI element.
You'll need to gracefully handle divide by zero errors (or restrict input to always be greater than 0), perform your calculations using doubles and preferably use Math.round() to get the best integer values for preserving aspect.
ie:
int calculateHeight(int width, double aspect) {
if( aspect <= 0.0 ) {
// handle this error condition
}
return (int)Math.round(width / aspect);
}
For actually tracking the aspect ratio, I would store it in a member variable and add an ActionListener to the JCheckBox... because updating the target aspect ratio on every value change of the width and height fields could result in aspect-ratio "creeping" due to integer round-off.
Here's an example on tracking your aspect every time the aspect ratio check state changes:
private double aspect = 1.0;
aspectCheckBox.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
preserveAspectActionPerformed(evt);
}
});
private void preserveAspectActionPerformed(java.awt.event.ActionEvent evt) {
try {
double w = Double.parseDouble(widthField.getText());
double h = Double.parseDouble(heightField.getText());
aspect = w / h;
}
catch(NumberFormatException ex) {
// ... error occurred due to non-numeric input
// (use a JSpinner or JFormattedTextField to avoid this)
}
}
The most important thing is to avoid using the wrong input type for the job:
JTextAreas are good for multiline text
JTextFields are good for single line text
JFormattedTextFields are good for text constrained to specific format
JSpinners are good for numeric entry.
Hope that helps you.
First, I wouldn't use JTextArea, it's meant for free form text editing (think NotePad). Instead you should, at the very least, use a JTextField but a JSpinner might actually even be better.
Take a look at How to Use Text Fields for more details.
Essentially, for JTextField, you could use a ActionListener and/or a FocusListener to monitor for changes to the field.
This listeners will tend to be notified after the fact, that is, only once the user has finished editing fields. If you want real time feed back, you could use a DocumentListener which will notifiy each time the underlying Document of the field is modified, in real time.
A JSpinner is a little more complicated as it's a component that contains an editor and controls. You can use a ChangeListener, which will notifiy when a change to the fields model is commited. This occurs in place of the ActionListener and FocusListener mentioned previously, so you should only require a single listener, but won't provide real time feedback (at least, not without a lot more work)

Given two label fields inside an HorizontalFieldManager, how should I do to display the full text from second label without wrapping it?

I have an HorizontalFieldManager with two labels inside.
The left label shows a description, and the right one shows
a money amount.
For me, it's more important to show the full text of the
second label. Problem is that if the first label is too long,
the second label will be wrapped. I want to avoid that, so
text from second label always is displayed. I also need to avoid
the wrapping over first label in that case, so text from that label
is trimmed and filled with dots.
This is how the HorizontalFieldManager looks:
And this is what I need to get:
How should I do that?
Thanks in advance!
If you create your LabelField with the LabelField.ELLIPSIS flag, it will truncate the field with . characters. I would recommend that you use a custom Manager subclass (instead of HorizontalFieldManager) to decide what the proper width of your two LabelFields should be. You can do this by asking what the proper width is of the dollar amount, given the current font.
Try this example:
public class LabelAlignScreen extends MainScreen {
private LabelField description;
private LabelField balance;
private static final int MARGIN = 8; // used for x and y
public LabelAlignScreen() {
super(MainScreen.VERTICAL_SCROLL | MainScreen.VERTICAL_SCROLLBAR);
Manager row = new RowManager(Manager.USE_ALL_WIDTH);
description = new LabelField("This is a very looooooooooong description",
LabelField.ELLIPSIS);
row.add(description);
balance = new LabelField("1,500,000,000 USD");
row.add(balance);
add(row);
}
private class RowManager extends Manager {
public RowManager(long flags) {
super(flags);
}
protected void sublayout(int width, int height) {
// first, determine how much space the balance field needs
int balanceWidth = balance.getFont().getAdvance(balance.getText());
// description field gets leftover width,
// minus a margin at left, center and right
int descriptionWidth = width - balanceWidth - 3 * MARGIN;
setPositionChild(description, MARGIN, MARGIN);
layoutChild(description, descriptionWidth, description.getPreferredHeight());
setPositionChild(balance, MARGIN + descriptionWidth + MARGIN, MARGIN);
layoutChild(balance, balanceWidth, balance.getPreferredHeight());
setExtent(width, getPreferredHeight());
}
public int getPreferredHeight() {
return Math.max(balance.getPreferredHeight(), description.getPreferredHeight()) + 2 * MARGIN;
}
public int getPreferredWidth() {
return Display.getWidth();
}
}
}
Note: you didn't specify whether the dollar/balance field should be a fixed width, or always just barely enough to fit the text. I assumed that it should just barely fit the text, as I think that makes for a better layout in most cases. Also, my code above uses a hardcoded MARGIN value for the space around all the fields. You can adjust that if you like.
Results
See that example :
class Test {
public String StringShorter(String field, int maxsize) {
//create a function that process the String you want to put in your field
StringBuilder strb=new StringBuilder();
// lets say you want your field to not more than 10 characters
if(field.length()>=maxsize) {
strb.append(field.substring(0,maxsize));
strb.append("...");
}
return strb.toString();
}
public static void main(String[] args) {
Test sl=new Test();
System.out.println(sl.StringShorter("sdadasfdfsdfsdfsdfdsffdfs", 10));
// define the maximum characters here it is defined to be maximum 10 characters
}
}
the output would be :
sdadasfdfs...

android canvas drawText set font size from width?

I want to draw text on canvas of certain width using .drawtext
For example, the width of the text should always be 400px no matter what the input text is.
If input text is longer it will decrease the font size, if input text is shorter it will increase the font size accordingly.
Here's a much more efficient method:
/**
* Sets the text size for a Paint object so a given string of text will be a
* given width.
*
* #param paint
* the Paint to set the text size for
* #param desiredWidth
* the desired width
* #param text
* the text that should be that width
*/
private static void setTextSizeForWidth(Paint paint, float desiredWidth,
String text) {
// Pick a reasonably large value for the test. Larger values produce
// more accurate results, but may cause problems with hardware
// acceleration. But there are workarounds for that, too; refer to
// http://stackoverflow.com/questions/6253528/font-size-too-large-to-fit-in-cache
final float testTextSize = 48f;
// Get the bounds of the text, using our testTextSize.
paint.setTextSize(testTextSize);
Rect bounds = new Rect();
paint.getTextBounds(text, 0, text.length(), bounds);
// Calculate the desired size as a proportion of our testTextSize.
float desiredTextSize = testTextSize * desiredWidth / bounds.width();
// Set the paint for that size.
paint.setTextSize(desiredTextSize);
}
Then, all you need to do is setTextSizeForWidth(paint, 400, str); (400 being the example width in the question).
For even greater efficiency, you can make the Rect a static class member, saving it from being instantiated each time. However, this may introduce concurrency issues, and would arguably hinder code clarity.
Try this:
/**
* Retrieve the maximum text size to fit in a given width.
* #param str (String): Text to check for size.
* #param maxWidth (float): Maximum allowed width.
* #return (int): The desired text size.
*/
private int determineMaxTextSize(String str, float maxWidth)
{
int size = 0;
Paint paint = new Paint();
do {
paint.setTextSize(++ size);
} while(paint.measureText(str) < maxWidth);
return size;
} //End getMaxTextSize()
Michael Scheper's solution seems nice but it didn't work for me, I needed to get the largest text size that is possible to draw in my view but this approach depends on the first text size you set, Every time you set a different size you'll get different results that can not say it is the right answer in every situation.
So I tried another way:
private float calculateMaxTextSize(String text, Paint paint, int maxWidth, int maxHeight) {
if (text == null || paint == null) return 0;
Rect bound = new Rect();
float size = 1.0f;
float step= 1.0f;
while (true) {
paint.getTextBounds(text, 0, text.length(), bound);
if (bound.width() < maxWidth && bound.height() < maxHeight) {
size += step;
paint.setTextSize(size);
} else {
return size - step;
}
}
}
It's simple, I increase the text size until the text rect bound dimensions are close enough to maxWidth and maxHeight, to decrease the loop repeats just change step to a bigger value (accuracy vs speed), Maybe it's not the best way to achieve this but It works.

design generic API using abstract method, but have to use final variables when pass to anonymous class. Is there a better way to accomplish this?

I am trying to create an generic API that run on-top of iText. One of the function of this API is to allow the user to split the PDF to invidual page, and allow the user to add list of text onto each pdf page after the split. For example, a pdf of 20 pages, and after run this process, I will have 20 of 1-page-pdf, and the first pdf will have a text 000001 on it, and the last pdf will have 000020 on it pdf. So to accomplish this, I use abstract method that allow the developer to write code on how they want the text to format given the current page number.
public abstract class GenericText {
/**
* The X position of the text. (0, 0) is at the bottom left
*/
private float x;
/**
* The Y position of the text. (0, 0) is at the bottom left
*/
private float y;
/**
* The rotation of the text. Rotation 0, 90, 180, 270
*/
private float rotation;
/**
* <code>com.itextpdf.text.pdf.BaseFont</code>. Determine the font for the text
*/
private BaseFont font;
/**
* Determine the font size of the text
*/
private float fontSize;
/**
* This tells whether text can only be placed first page or on every page
*/
private ComponentPlacement placement;
/**
* Since the text that the user want to insert onto the Pdf might vary
* from page to page, or from logical document to logical document, we allow
* the user to write their own implementation of the text. To give the user enough
* flexibility, we give them the reference to the physical page index, the logical page index.
* #param physcialPage The actual page number that the user current looking at
* #param logicalPage A Pdf might contain multiples sub-documents, <code>logicalPage</code>
* tell the user which logical sub-document the system currently looking at
*/
public abstract String generateText(int physicalPage, int logicalPage);
...
}
PdfPrcessor.java: This is where the split happen
/**
* This is the main process that will split the pdf into individual page, and text to each page
*/
public void splitPdf(String inputPdf, boolean isSplit, List<GenericText> textList,
String outputDir, String baseOutputName, String outputPdfName) throws IOException, DocumentException{
...
PdfReader reader = new PdfReader(inputPdf)
PdfContentByte cb = ... ;
for(int physicalPageIndex=1 ; physicalPageIndex<=reader.getNumberOfPages(); physicalPageIndex ++)
...
//Code to split PDF. Write each page to a separate pdf. For each pdf, insert all text inside `textList` onto the pdf
...
//Insert text
if(textList != null){
for(GenericText textComponent : textList){
String text = textComponent.generateText(physicalPageIndex, logicalPageIndex);
addText(text, cb, textComponent.getFont(), textComponent.getFontSize(), textComponent.getX(), textComponent.getY(), textComponent.getRotation());
}
}
}
...
}
So in my main class I would do this,
final String printName = printNameLookup.get(baseOutputName);
final String seq = config.getPrintJobSeq();
GenericText keyline = new GenericText(90, 640, 0, arial, 7, ComponentPlacement.FIRST_PAGE){
#Override
public String generateText(int physicalPage, int logicalPage) {
return printName + seq + " " + Utils.right(String.valueOf(logicalPage), 6, '0');
}
};
textList.add(keyline);
pdfProcess.splitPdf(inputPdfPath, true, textList, outputDir, baseOutputName, outputPdfName);
This work great, and I think it is very flexible however, printName and seq has be be declared as final in order to pass inside generateText(int physicalPage, int logicalPage). How do I design this so that it wont require final field. Will interface help? I use guava API, and I can do this
ImmutableListMultimap<String, File> groups = Multimaps.index(pdfList,
new Function<File, String>(){
public String apply(File input){
String[] ids = getId(input.getName());
PackageLog pl = logProcessor.lookUp(new Long(ids[0]), ids[1]);
String printName = printNameLookup.get(getPackageName(pl, s));
}
});
logProcessor and printNameLookup is not final, I like the way they design, and I am reading their sources now, but it will take some times, anyone with expert on design knowledge can shed me some light?
Copy them into a final variables and use those instead.
String printName = printNameLookup.get(baseOutputName);
String seq = config.getPrintJobSeq();
// use these in the anonymous class.
final String finalPrintName = printName;
final String finalSeq = seq;
or use arrays
final String[] printName = { printNameLookup.get(baseOutputName) };
final String[] seq = { config.getPrintJobSeq() };
// use printName[0] and seq[0] everywhere.
You have discovered the Template Method design pattern. In this case copying the values into final variables will work.

Categories