I experimented with MatteBorder in order to display an icon at the start of a JTextField (similiar to search icons being displayed in a textfield).
This is my current implementation:
JTextField textField = new JTextField("Filter", 8);
textField.setPreferredSize(new Dimension(getPreferredSize().width, 24));
Border outer = textField.getBorder();
// ugly workaround
Border padding = BorderFactory.createEmptyBorder(2, 2, 2, 2);
Border search = new MatteBorder(0, 16, 0, 0, icon);
textField.setBorder(new CompoundBorder(new CompoundBorder(outer, padding), search));
The icon is a 16x16px icon with no whitespace around it. The textfield is 24px high. I introduced a padding Border to have some whitespace around the icon (otherwise it would display a full icon and the first 4px of the icon under it). My problem is that there is no whitespace to the right of the icon (where the user enters text).
Question: Is there a way to have a defined amount of whitespace around the image, so it does not tile? Can I somehow add "padding" around the icon before I add it to the MatteBorder?
P.S. I know that I could add whitespace around the image file, but it is used in other instances where there should not be any whitespace around it.
Since a MatteBorder always tiles the icon, I would not use it. I would just write a custom border:
static void updateBorder(JTextField textField,
Icon icon) {
Border iconBorder = new AbstractBorder() {
private static final long serialVersionUID = 1;
#Override
public Insets getBorderInsets(Component c,
Insets insets)
{
insets.left = icon.getIconWidth() + 4;
insets.right = insets.top = insets.bottom = 0;
return insets;
}
#Override
public void paintBorder(Component c,
Graphics g,
int x,
int y,
int width,
int height)
{
icon.paintIcon(c, g,
x, y + (height - icon.getIconHeight()) / 2);
}
};
Border oldBorder = textField.getBorder();
// Inside text field's original border, place icon border
// with a little empty space around it.
textField.setBorder(BorderFactory.createCompoundBorder(
oldBorder,
BorderFactory.createCompoundBorder(
BorderFactory.createEmptyBorder(2, 2, 2, 2),
iconBorder)));
}
My problem is that there is no whitespace to the right of the icon (where the user enters text).
How about using a second CompoundBorder to add extra space on the inside of the Border?
Another possiblility is to use the Component Border which allows you to add a component to a Border. So you could add a JLabel with an Icon.
Related
I want to apply round corner border to a table. This table is dynamic. That means it can grow up to multiple pages or can accommodate in single page.
If table comes in single page, then outermost corner of all four corner cells should be drawn as rounded.
If table grows up to multiple pages (say 3 pages), then outermost corner of all four corner cells should be drawn as rounded for all 3 pages.
Here is the approach which I am using to implement the above scenario.
public void createPdf(String dest) throws FileNotFoundException {
PdfWriter writer = new PdfWriter(DEST);
PdfDocument pdfDoc = new PdfDocument(writer);
Document document = new Document(pdfDoc, PageSize.A4, false);
Table table = new Table(3);
for (int i=0; i < 100; i++) {
for (int j=0; j < 3; j++) {
table.addCell(new Cell().add(new Paragraph("Cell content")));
}
}
table.setNextRenderer(new TableBorderRenderer(table));
document.add(table);
document.close();
}
TableBorderRenderer.java
public class TableBorderRenderer extends TableRenderer {
public TableBorderRenderer(Table modelElement) {
super(modelElement);
}
#Override
public IRenderer getNextRenderer() {
return new TableBorderRenderer((Table) modelElement);
}
#Override
protected void drawBorders(DrawContext drawContext) {
Rectangle rect = getOccupiedAreaBBox();
PdfPage currentPage = drawContext.getDocument().getPage(getOccupiedArea().getPageNumber());
PdfCanvas aboveCanvas = new PdfCanvas(currentPage.newContentStreamAfter(), currentPage.getResources(), drawContext.getDocument());
// drawing white rectangle over table border in order to hide it.
aboveCanvas.saveState().setStrokeColor(new DeviceRgb(255,255,255)).rectangle(rect).stroke().restoreState();
// drawing red round rectangle which will be shown as boundary.
aboveCanvas.saveState().setLineWidth(0.5f).setStrokeColor(new DeviceRgb(255,0,0)).roundRectangle(rect.getLeft(), rect.getBottom(), rect.getWidth(), rect.getHeight(), 5).stroke().restoreState();
super.drawBorders(drawContext);
}
}
Now the code is working fine as it is supposed to be, but there is an issue with the rendering. when i draw the white border on top of table border it does not overlap it completely. Also the outer red border is drawn slightly outside the expected area. In simple words, white rectangle and red rectangle are not coinciding with each other. So there is some gap coming between the outer border and cell borders.
I am attaching the generated output from above code. To notice the issue you might need to zoom the PDF a little bit.
I have some doubts regarding the same:
I am using a top canvas to get the expected solution. But is there any approach in which I can modify the table border directly while rendering? I tried
drawContext.getCanvas()
.saveState()
.roundRectangle(rect.getLeft(), rect.getBottom(), rect.getWidth(), rect.getHeight(), 5).stroke().restoreState();
But in this approach border is overlapped by the cell borders (cell borders are needed as well). If I am missing something to prevent this issue, guide me.
Thanks.
The getOccupiedAreaBBox method gives you the outer bounding box of the border area. Borders, however, have thickness on their own, and when you draw lines in PDF by default you should pass the center of the line to the drawing operation, while you are passing the outer bbox coordinates, hence the small margin and imprecise overlap.
To fix the issue, you need to add half of the border line width to all of the edges of your rectangle:
float lineWidth = 0.5f;
rect.applyMargins(lineWidth / 2, lineWidth / 2, lineWidth / 2, lineWidth / 2, false);
All in all, the following customized code:
#Override
protected void drawBorders(DrawContext drawContext) {
Rectangle rect = getOccupiedAreaBBox();
PdfPage currentPage = drawContext.getDocument().getPage(getOccupiedArea().getPageNumber());
PdfCanvas aboveCanvas = new PdfCanvas(currentPage.newContentStreamAfter(), currentPage.getResources(), drawContext.getDocument());
float lineWidth = 0.5f;
rect.applyMargins(lineWidth / 2, lineWidth / 2, lineWidth / 2, lineWidth / 2, false);
// drawing white rectangle over table border in order to hide it.
aboveCanvas.saveState().setLineWidth(lineWidth).setStrokeColor(new DeviceRgb(255,255,255)).rectangle(rect).stroke().restoreState();
// drawing red round rectangle which will be shown as boundary.
aboveCanvas.saveState().setLineWidth(lineWidth).setStrokeColor(new DeviceRgb(255,0,0))
.roundRectangle(rect.getLeft(), rect.getBottom(), rect.getWidth(), rect.getHeight(), 5).stroke().restoreState();
super.drawBorders(drawContext);
}
Yields the following visual result:
When I resize the window, my custom JTextField changes size. It only does this once, the first time I resize the window. It's related to this line inside paintComponent:
setMargin(new Insets(2, 25, 2, 2));
Running that command does not resize the text field until I resize the window. After resizing the window, running that command causes JTextField to become larger. Actual size of the window does not matter. The first time window size is changed, JTextField becomes larger and then it stays large until the end of time. I would prefer if the field was large as soon as I start the program, and obviously, I don't want it to randomly change size.
How can I fix the size of this JTextField so that it does not randomly change?
Here is the entire class:
/**
* From https://gmigdos.wordpress.com/2010/03/30/java-a-custom-jtextfield-for-searching/
* #author Georgios Migdos <cyberpython#gmail.com> */
public class JIconTextField extends JTextField {
private Icon icon;
private Insets dummyInsets;
public JIconTextField(int columns) throws IOException {
super(columns);
Border border = UIManager.getBorder("TextField.border");
JTextField dummy = new JTextField();
this.dummyInsets = border.getBorderInsets(dummy);
String path = "find-16x16.png";
InputStream is = Main.class.getClassLoader().getResourceAsStream(path);
setIcon(new ImageIcon(ImageIO.read(is)));
repaint();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
int textX = 2;
if(this.icon!=null){
int iconWidth = icon.getIconWidth();
int iconHeight = icon.getIconHeight();
int x = dummyInsets.left + 5;//this is our icon's x
textX = x+iconWidth+2; //this is the x where text should start
int y = (this.getHeight() - iconHeight)/2;
icon.paintIcon(this, g, x, y);
}
setMargin(new Insets(2, textX, 2, 2));
}
}
Further to the comments, if you cut the code out from the paintComponent method and paste it into the constructor, you will get your desired result.
The paintComponent method for actual painting, i.e. blanking the background of the field and drawing the text. Generally, unless you specifically want to change the way the component is drawn, you should not need to override this method.
I have some problem with iText in Java. I need to produce a cell or rectangle with shadow just like in this example:
4 and 60 are in some kind of cell or rectangle with shadow. I don't know how to do it. Any help ?
The easiest way probably is to use a Chunk with a generic tag and a PdfPageEvent. This way you'll get an event callback when the Chunk is positioned on the page. The callback will give you the coordinates (rectangle) of the Chunk, allowing you to paint a border and a shadow at the correct location.
An example of such an event handler to paint a black border and a gray shadow:
class ShadowEvent extends PdfPageEventHelper {
#Override
public void onGenericTag(PdfWriter writer, Document document,
Rectangle rect, String text) {
PdfContentByte canvas = writer.getDirectContent();
// Paddings for the border
int paddingHorizontal = 20;
int paddingVertical = 5;
// Width of the shadow
int shadowwidth = 5;
// Calculate border location and size
float left = rect.getLeft() - paddingHorizontal;
float bottom = rect.getBottom() - paddingVertical;
float width = rect.getWidth() + 2*paddingHorizontal;
float height = rect.getHeight() + 2*paddingVertical;
canvas.saveState();
canvas.setColorFill(BaseColor.GRAY);
// Draw the shadow at the bottom
canvas.rectangle(left + shadowwidth, bottom - shadowwidth, width, shadowwidth);
canvas.fill();
// Draw the shadow at the right
canvas.rectangle(left + width, bottom - shadowwidth, shadowwidth, height);
canvas.fill();
canvas.setColorStroke(BaseColor.BLACK);
// Draw the border
canvas.rectangle(left, bottom, width, height);
canvas.stroke();
canvas.restoreState();
}
}
This shows how to use the generic tag:
Document doc = new Document();
PdfWriter pdfWriter = PdfWriter.getInstance(doc, outfile);
pdfWriter.setPageEvent(new ShadowEvent());
doc.open();
Chunk c = new Chunk("60");
c.setGenericTag("shadow");
doc.add(c);
doc.close();
(Note that the text parameter of the onGenericTag method will contain the String that was set to the Chunk with setGenericTag. This is "shadow" in the example above. It allows to differentiate between different tags. Since we're just using 1 tag here, I'm not using the text parameter.)
The result of the example looks like this:
How would you effectively do a word wrap on a short label String below with TextLayout in java?
My label are only two or three words long
Some examples:
1. Inflatable Greenhouse D10 ;
2. Command and Control Center A5;
3. Jason Boris;
I'd like to wrap the words in such a way to shape as like a square as possible, rather than one long rectangle.
So my question is: What does it take to wrap the building names to the 2nd line, instead of one long line? See pic below:
Is there a way to set the maximum number of characters to be contained in a line of text and wrap the remaining characters to the second line and so on (it would need to account for whitespace)?
For example, I'd like to wrap the name "Residential Quarter D12" into three lines.
Residential
Quarter
D12
and wrap "Command and Control D16" into four lines.
Command
and
Control
D16
Wouldn't it be nice if TextLayout can understand html codes like a regular JLabel!? Then it'll make things easy:
String label = "<html>" + "Inflatable" + "<br>" + "Greenhouse" + "<br>" + "D10" + "</html>";
Note: it doesn't have to be one word per line. But I'd like to have them "centered" on each line
What I have was the following method for generating a BufferedImage of the building name labels o or just the first and last name of a person.
private BufferedImage createLabelImage(
String label, Font font, FontRenderContext fontRenderContext, Color labelColor,
Color labelOutlineColor) {
// Determine bounds.
TextLayout textLayout1 = new TextLayout(label, font, fontRenderContext);
Rectangle2D bounds1 = textLayout1.getBounds();
// Get label shape.
Shape labelShape = textLayout1.getOutline(null);
// Create buffered image for label.
int width = (int) (bounds1.getWidth() + bounds1.getX()) + 4;
int height = (int) (bounds1.getHeight()) + 4;
BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
// Get graphics context from buffered image.
Graphics2D g2d = (Graphics2D) bufferedImage.getGraphics();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.translate(2D - bounds1.getX(), 2D - bounds1.getY());
// Draw label outline.
Stroke saveStroke = g2d.getStroke();
g2d.setColor(labelOutlineColor);
g2d.setStroke(new BasicStroke(2, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
g2d.draw(labelShape);
g2d.setStroke(saveStroke);
// Fill label
g2d.setColor(labelColor);
g2d.fill(labelShape);
// Dispose of image graphics context.
g2d.dispose();
return bufferedImage;
}
As you can see, this method can only create a BufferedImage form of label with one line of text only.
As I overlay these BufferedImage labels on a map, they look too long and they overlap one another.
That's why I need to make each label to shape like a square as possible.
Let me try to suggest an algoritm.
Split the label by space to get list of words and measure each word to get array
int[] wordWidths;
int minWidth=max(wordWidths);
int height=the row height const;
int minHeight=height;
int maxHeight=wordWidths.length*height;
int currentWidth=minWidht;
int currentHeight=maxHeight;
while(currentWidth<currentHeight || wordWidths.length>1) {
int mergedWidth=find minimal sum of neighbour words' widths
replace the 2 widths with the mergedWidth reducing the wordWidthssize
currentHeight=wordWidths.length*height;
}
Or you can try to rely on components. I would define a JTextArea instance assigning the label there and trying to play with the wrap reducing width 1 by 1 and measuring preferred height for the width.
When optimal size is achived you can call theWrappedJtextArea.printAll(g) to paint it on your BufferedImage's Graphics.
The code below plots some simple x-y data, but it has two problems that I do not know how to fix.
First, it plots negative values for some of the data points, which means lines extending southward below the x-axis. Since the data points are selected at random, you may have to resize the frame a bit in order to view new random numbers to be plotted in a way that shows this bug. All data values will be positive, so I want all deflections to project northward above the blue bottom marker line, and I need to make sure that no deflections extend southward below the blue bottom marker line.
Second, the y-axis label takes up too much real estate on the screen. It needs to be rotated -90 degrees. However, all the examples I have seen for this involve rotating the entire panel using a graphics2d object. I do not want to rotate the entire panel. Instead, I just want to rotate the text of the y-axis label.
Can anyone show me how to change the code below to fix these two specific problems?
The code is in the following two files:
GUI.java
import java.awt.*;
import javax.swing.*;
class GUI{
GUI() {
// Create a new JFrame container.
JFrame jfrm = new JFrame("X-Y Plot");
// Specify FlowLayout for the layout manager.
jfrm.getContentPane().setLayout(new FlowLayout());
int frameHeight = 400;
int frameWidth = 300;
// Give the frame an initial size.
jfrm.setSize(frameWidth, frameHeight);
// Terminate the program when the user closes the application.
jfrm.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// Create a text-based label.
JVertLabel myVLabel = new JVertLabel("y-axis label");
int width = myVLabel.WIDTH;
PaintPanel myPP = new PaintPanel(frameWidth-width-50-20,frameHeight-70);
jfrm.add(myPP);
jfrm.add(myVLabel);// Add the label to the frame.
// Display the frame.
jfrm.setVisible(true);
}
public static void main(String args[]) {
// Create the frame on the event dispatching thread.
SwingUtilities.invokeLater(new Runnable() {public void run(){new GUI();}});
}
public class JVertLabel extends JComponent {
private String text;
public JVertLabel(String s) {
text = s;
}//constructor
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.rotate(Math.toRadians(-90));
g2d.drawString(text, 0, 0);
}
}
}
PaintPanel.java
import java.awt.*;
import javax.swing.*;
import java.util.*;
class PaintPanel extends JPanel {
Insets ins; // holds the panel's insets
Random rand; // used to generate random numbers
PaintPanel(int w, int h) {
setOpaque(true);// Ensure that panel is opaque.
setPreferredSize(new Dimension(w, h));// Set preferred dimension as specfied.
rand = new Random();
}
protected void paintComponent(Graphics g) {// Override paintComponent() method.
super.paintComponent(g);// Always call superclass method first.
int height = getHeight();// Get height of component.
int width = getWidth();// Get width of component.
ins = getInsets();// Get the insets.
// Get dimensions of text
Graphics2D g2d = (Graphics2D) g;
Font font = new Font("Serif", Font.PLAIN, 12);
FontMetrics fontMetrics = g2d.getFontMetrics();
String xString = ("x-axis label");
int xStrWidth = fontMetrics.stringWidth(xString);
int xStrHeight = fontMetrics.getHeight();
String yString = "y-axis-label";
int yStrWidth = fontMetrics.stringWidth(yString);
int yStrHeight = fontMetrics.getHeight();
int leftStartPlotWindow = ins.left + 5 + yStrWidth;
int hPad = 3;
// Fill panel by plotting random data in a bar graph.
for (int i = leftStartPlotWindow + hPad; i <= width - leftStartPlotWindow - hPad + yStrWidth + 1; i += 4) {
int h = Math.abs(rand.nextInt(height - ins.bottom));//Get rand# betw/0 and max height of drawing area.
// If generated value w/in or too close to border, change it to just outside border.
if (h <= ins.top) {
h = ins.top + 1;
}
g.drawLine(i, Math.abs(height - ins.bottom - xStrHeight - 5), i, h);// Draw a line that represents data.
}
g.setColor(Color.blue);
g.drawRect(leftStartPlotWindow, ins.bottom + 2, width - leftStartPlotWindow - ins.right - hPad, height - xStrHeight - 6);
g.setColor(Color.red);
g.drawRect(ins.left, ins.bottom, width - ins.left - 1, height - ins.bottom - 1);
g.drawString(xString, (width / 2) - (xStrWidth / 2), height - ins.bottom - 6);
g.drawString(yString, ins.left, height / 2);
}
}
All data values will be positive, so I want all deflections to project northward above the blue bottom marker line, and I need to make sure that no deflections extend southward below the blue bottom marker line.
You need to calculate the random height so that all values fit into the space available. So the calculation would be something like:
int randomHeight = panelHeight - offset.top - offset.bottom - heightForTheXAxisText;
Then you don't have to worry about negative values or the top of the line extending outside the bounds of the panel.
all the examples I have seen for this involve rotating the entire panel using a graphics2d object. I do not want to rotate the entire panel. Instead, I just want to rotate the text of the y-axis label.
Set the rotation of the Graphics object, the draw the text, then restore the rotation of the Graphics object back to 0.
Or, you create create a new Graphcis object from the current Graphics object, then apply the rotation, draw the text and then dispose of the temporaray Graphics object.
JFreeChart addresses both issues by default, as shown in this example.
In your example,
You'll have to create the data model before trying to render it. Then you can scan it for min and max to determine the limits of your range axis. List<Double> may be a suitable choice.
You can rotate the range label by altering the graphics context's AffineTransform, as shown in RotateText.