JLabel shifts the text vertically down while displaying HTML - java

I would like to understand why a JLabel, rendering HTML, shifts the vertical position of it's output text, whereas a JLabel which renders non-HTML, does not.
Java version used: 1.6.0_37
Swing Look and Feel used: Windows ("com.sun.java.swing.plaf.windows.WindowsLookAndFeel")
OS: Windows 7 64 Bit
I did not put a SSCCE together since the code is really trivial. But if it helps please say so.
I rather give an examples using an images displaying the behavior:
I put a JPanel as the container around the JLabel to visualize the label's bounds. After setting the font and text for the JLabel, the
jLabel.getPreferredSize()
method returns the bounds of the rendered plain text or HTML (and this is the exact size I set for the surrounding JPanel). You can clearly see, that, if rendering HTML, the whole text is shifted a small amount down.
I would like to know why this happens and what I can do to correct the placement.
One workaround would be to translate the Graphics2D on which to render the text, to compensate the vertical shift, like this:
g2d.translate( 0, -20 );
But I don't know the correct y value in relation to the font metrics (e.g. font size). Anyway, this workaround also feels "wrong".
I really appreciate your answers, thanks a lot!

It seems that if we set the Font (family,size etc) for the HTML JLabel using setFont(..) the font is not rendered to the correct metrics of JLabel.
Here is an example I made to demonstrate (Both JLabels shown are using HTML):
A simple work around is to the the font size, family etc in HTML too.
As we can see the cyan HTML JLabel used setFont(..) (and was incorrectly rendered) while the green HTML JLabel used HTML to set the font and was rendered correctly:
JLabel labelHtml2 = new JLabel("<html><font size=10 family='Calibri'>" + text + "</font></html>");
Test.java:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Font;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test {
public static Font font = new Font("Calibri", Font.PLAIN, 38);
public Test() {
initComponents();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
new Test();
}
});
}
private void initComponents() {
final JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
String text = "Hello world";
//this label will not render correctly due to setting font via setFont(..)
JLabel labelHtml1 = new JLabel("<html>" + text + "</html>");
labelHtml1.setBackground(Color.CYAN);
labelHtml1.setOpaque(true);//so background will be painted
labelHtml1.setFont(font);
//this label will render correcty font is set via html
JLabel labelHtml2 = new JLabel("<html><font size=10 family='Calibri'>" + text + "</font></html>");
labelHtml2.setBackground(Color.GREEN);
labelHtml2.setOpaque(true);
//labelHtml2.setFont(font);
frame.add(labelHtml1, BorderLayout.NORTH);
frame.add(labelHtml2, BorderLayout.SOUTH);
frame.pack();
frame.setVisible(true);
}
}

Related

Java swing Button highlighting effect gone due to increased border size

I have a panel of buttons that represent an inventory, and in order to give emphasis to one button in particular I increased its border size. The border increases inward, however, which would be totally fine it didn't cover up the highlight effect buttons have when the mouse hovers over them. Is there any way to make the button highlight effect still show up in some way if it's covered up by the border? Maybe by increasing the highlight effect's border size?
This picture is an example of what I'm talking about. The top button is the emphasized button with the increased border size. The second button is a normal button being highlighted by the cursor(the mouse cursor doesn't show up in snip snapshots). As the picture shows, the increased border size is bigger than the highlight effect so when you try to highlight the first button seemingly nothing happens. The third/fourth are just normal buttons with no effect.
Sorry if the wording here is a little confusing but I'm not sure what the best way to phrase my probelm is.
Here is a simple example. The button with the red border no longer highlights when your mouse cursor hovers over it.
import javax.swing.*;
import javax.swing.border.Border;
import java.awt.*;
public class Test {
JFrame window;
JPanel screen1;
Border borderThick = BorderFactory.createLineBorder(Color.red, 3);
public Test(){
//Frame Window
window = new JFrame();
window.setSize(800,600);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.getContentPane().setBackground(Color.blue);
//Screen 1
screen1 = new JPanel();
screen1.setBounds(100, 100, 600, 205);
screen1.setBackground(Color.blue);
JButton buttonThickBorder = new JButton("Thick Button");
buttonThickBorder.setBorder(borderThick);
screen1.add(buttonThickBorder);
JButton buttonNormalButton = new JButton("NormalButton");
screen1.add(buttonNormalButton);
window.add(screen1);
window.setVisible(true);
}
public static void main(String[] args){
new Test();
}
}
JButtons come with their own default Border which is the one you are seeing by default (which comes with hover effects, press effects, etc). If you change the border to provide your own, then the default border is gone.
To fix this, one way is to implement your own border which will listen for hover events, which is easier done by listening to the JButton's model changes, like so:
import java.awt.Color;
import javax.swing.BorderFactory;
import javax.swing.ButtonModel;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.border.Border;
public class Custom {
private static void createAndShowGUI() {
JFrame window;
JPanel screen1;
Border borderThick = BorderFactory.createLineBorder(Color.red, 3);
//Frame Window
window = new JFrame();
window.setSize(400,300);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.getContentPane().setBackground(Color.blue);
//Screen 1
screen1 = new JPanel();
screen1.setBounds(100, 100, 600, 205);
screen1.setBackground(Color.blue);
JButton buttonThickBorder = new JButton("Thick Button");
buttonThickBorder.setBorder(borderThick);
screen1.add(buttonThickBorder);
//Changes:
Border borderThickRollover = BorderFactory.createLineBorder(Color.CYAN, 3);
buttonThickBorder.getModel().addChangeListener(e -> {
final ButtonModel model = (ButtonModel) e.getSource();
if (model.isRollover())
buttonThickBorder.setBorder(borderThickRollover);
else
buttonThickBorder.setBorder(borderThick);
});
JButton buttonNormalButton = new JButton("NormalButton");
screen1.add(buttonNormalButton);
window.add(screen1);
window.setVisible(true);
}
public static void main(final String[] args) {
SwingUtilities.invokeLater(Custom::createAndShowGUI);
}
}
Similarly you can listen for other events, like the button being pressed, armed, selected or enabled (all those via the button's model) and change the border accordingly. For even more events, you can use listeners on the button itself (like FocusListener for example).
One other way to fix the issue you are seeing is to use the default border itself, along with your custom border, by constructing a CompoundBorder, for example like so:
import java.awt.Color;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.border.Border;
public class Default {
private static void createAndShowGUI() {
JFrame window;
JPanel screen1;
//Border borderThick = BorderFactory.createLineBorder(Color.red, 3);
//Frame Window
window = new JFrame();
window.setSize(400,300);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.getContentPane().setBackground(Color.blue);
//Screen 1
screen1 = new JPanel();
screen1.setBounds(100, 100, 600, 205);
screen1.setBackground(Color.blue);
JButton buttonThickBorder = new JButton("Thick Button");
//buttonThickBorder.setBorder(borderThick);
screen1.add(buttonThickBorder);
//Changes:
Border borderThickRollover = BorderFactory.createCompoundBorder(BorderFactory.createLineBorder(Color.CYAN, 5), buttonThickBorder.getBorder());
buttonThickBorder.setBorder(borderThickRollover);
JButton buttonNormalButton = new JButton("NormalButton");
screen1.add(buttonNormalButton);
window.add(screen1);
window.setVisible(true);
}
public static void main(final String[] args) {
SwingUtilities.invokeLater(Default::createAndShowGUI);
}
}
This way you are getting the default border plus your extra border. This way you don't necessarily need to listen for model changes.
Notice, in the second example, that when you hover over the outside border of the compound one (ie the cyan sub-border) the button model changes to rolled over which makes the inside (default) border to be notified. This happens because the compound border is maintained by the button, so hovering the compound border anywhere will make the button's model rolled over. If this is not desired, then the simplest workaround I can think of right now is that you can just add the button inside a JPanel which will have a cyan LineBorder and install the button on that panel.
Generally speaking, you can obtain a JButton's default Border, without the need to actually construct a JButton, through the UIManager like so:
Border buttonDefaultBorder = javax.swing.UIManager.getBorder("Button.border");
...unless of course you install your own Look And Feel (L&F) which behaves differently, in which case you can obtain the default border after constructing the button, but that's a different story (and probably an erroneous L&F implementation).

Show multiple JPane images on a single JFrame

First off, I am new to JFrame and all the associated classes so I am still learning how to do this.
My current goal is to draw multiple images on a single JFrame. So far, I can get test2.png to draw, but not test1.png. Any suggestions or help understanding JFrame is appreciated. This is my main class:
package com.osj.oneshotjava;
import java.awt.Dimension;
import javax.swing.JFrame;
/**
*
* #author BCG04
*/
public class actorTest {
public static void main(String []args){
JFrame jFrame = new JFrame("OSJ actor test");
jFrame.setPreferredSize(new Dimension(640, 480)); // sets window size
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Actor2 Background = new Actor2(jFrame, "test1.png");
Actor2 testActor = new Actor2(jFrame, "test2.png");
jFrame.pack(); // automatically adjusts window size (also sets window size based on the maximum and minimum sizes)
jFrame.setVisible(true);
}
}
And this is Actor2:
package com.osj.oneshotjava;
import java.awt.BorderLayout;
import java.awt.image.BufferedImage;
import java.io.File;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
/**
*
* #author BCG04
*/
public class Actor2 { //Purpose: make it easer to add multiple images to a single JFrame using only a single call to Actor2's constuctor rather than repeating the same section of code for each image.
private BufferedImage image = null;
private JLabel jLabel = null;
public Actor2(JFrame jFrame, String filename){
try
{ // try to load a image 'filename' into 'image'
image = ImageIO.read(new File(filename));
}
catch (Exception e)
{
e.printStackTrace(); // if loading fails, print the error
System.exit(1); // then exit with an error code 1 'unsuccessful exit'
}
ImageIcon imageIcon = new ImageIcon(image); // create a new ImageIcon that contains 'image'
JPanel jPanel = new JPanel();
jLabel = new JLabel();
jLabel.setIcon(imageIcon); // set JLabel 'jLabel' to contain 'imageIcon'
jPanel.add(jLabel);
jFrame.getContentPane().add(jPanel, BorderLayout.CENTER); // makes window visible?
}
public JLabel getJLabel(){
return jLabel;
}
}
Edit:
-removed Thread.sleep(1000); and setLocation(90, 90); since they were not relevant to the question or the problem and I originally had them in to test whether I could move images.
-removed jLabel.setBounds as it did not seem to do anything.
+added a comment clarifying Actor2's goal.
I'd like to clarify my end goal, I would like to create a simple 2d game that uses Java.
Here is a complete, self contained example that is close to what you're after. It is to demonstrate the use of a layout manager.
import javax.swing.*;
import java.awt.*;
import java.awt.image.*;
import java.awt.event.*;
public class DuelingJLabels{
public static void startGui(){
JFrame frame = new JFrame();
JPanel scene = new JPanel();
Actor red = new Actor(Color.RED);
Actor blue = new Actor(Color.BLUE);
//scene.setLayout( null );
scene.add(red.image);
scene.add(blue.image);
//scene.setPreferredSize( new Dimension(512, 512) );
frame.add(scene, BorderLayout.CENTER);
frame.pack();
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
static class Actor{
int x, y;
JLabel image;
public Actor(Color c){
BufferedImage a = new BufferedImage(64, 64, BufferedImage.TYPE_INT_ARGB);
Graphics g = a.getGraphics();
g.setColor(c);
g.fillOval(0, 0, 64, 64);
image = new JLabel();
image.setIcon(new ImageIcon(a));
image.setLocation( x, y );
image.setSize( 64, 64);
image.addMouseListener( new MouseAdapter(){
#Override
public void mouseClicked(MouseEvent evt){
x = x+64;
if(x>=448){
x = 0;
y += 64;
}
image.setLocation(x, y);
}
});
}
}
public static void main(String[] args){
EventQueue.invokeLater( DuelingJLabels::startGui );
}
}
Take note of the line scene.setLayout(null); if you run the example with that line commented out, then you will see two circles side by side. That is because we are letting swing handle the layout. scene is a JPanel with a FlowLayout by default.
Now when you click the circles, nothing happens* because we tell the new position but the layout manager resets the position.
*Actually they move sometimes, but if you trigger a re-validation then they get moved by the layout manager.
So now remove the comment on scene.setLayout(null); and notice the difference.
The frame is tiny, and we have to manually resize it to see our scene.
There is only one circle.
If you click on the circle, it moves.
That's because we have told swing to not use a layout manager for the JPanel scene. That means it will not reposition the components in the scene for us, and it will not adjust the sizes for us either.
The other line that is commented setPreferredSize makes scene tell the parent component a size it would like to be at. If you uncomment that line then the JFrame will not start out incredibly small. You should only use that with custom components, otherwise you can end up conflicting with the layout manager.
Another tool, which I have found usefull is the JLayeredPane because it gives you some depth. I also think the example is good.
Finally, another technique for putting custom graphics arbitrarily is to #Override paintComponent. That way you can draw whatever, where-ever on your component.

Ugly font rendering in Swing

Is there a way to improve font rendering in Swing JTextFields? Here's what I'm getting right now:
As you can see, it looks pretty jagged. Is there a way I can improve that? I'm using the GTKLookAndFeel, just in case anyone needs to know.
(I looked at this question, but it didn't help much.)
SSCCE:
public class foo extends JFrame{
foo(){
add(new JTextField);
setVisible(true);
}
}
I'm on Linux, so that might have something to do with it. I'm using Infinality in linux for better fonts.
Using the sscce below, I see the following appearance with the GTK+ L&F.
Addendum: As a workaround, you might try setting a UI delegate property, for example,
JTextField tf = new JTextField();
UIManager.put("TextField.font", tf.getFont().deriveFont(Font.BOLD));
import component.Laf;
import java.awt.EventQueue;
import java.awt.GridLayout;
import javax.swing.JFrame;
import javax.swing.JPasswordField;
import javax.swing.JTextField;
/** #see http://stackoverflow.com/a/18969361/230513 */
public class Test {
private void display() {
JFrame f = new JFrame("Test");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setLayout(new GridLayout(0, 1));
// http://stackoverflow.com/a/11949899/230513
f.add(Laf.createToolBar(f));
f.add(new JTextField("bla#foo.com"));
f.add(new JPasswordField("*****"));
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new Test().display();
}
});
}
}
To get a smother text, you should enable anti-aliasing:
$ java -jar lib/application.jar -Dswing.aatext=true
Using this code you can change the font and font color in a JTextArea.
We have created a JTextArea called txt. With a few simple lines of code you can change its font, color & size settings:
Font font = new Font("Verdana", Font.BOLD, 12);
txt.setFont(font);
txt.setForeground(Color.BLUE);
There are several font settings in the Font class including PLAIN, BOLD, ITALIC and 13 different colors in the Color class (listed below).
BLACK
BLUE
CYAN
DARK_GRAY
GRAY GREEN
LIGHT_GRAY
MAGENTA
ORANGE
PINK
RED
WHITE
YELLOW

TextArea Multi-Color text

For my application, I am going to have syntax highlighting, in my text box. What I am not sure of, is how to do multi-color in one box instead of just one color.
I know I can do this, but it sets all of the text to one color.
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package ssccee;
import java.awt.Color;
import java.awt.Font;
import javax.swing.JFrame;
import javax.swing.JTextArea;
/**
*
* #author ryannaddy
*/
public class Sscce extends JFrame{
JTextArea txt = new JTextArea();
public Sscce(){
setLayout(null);
txt.setBounds(3, 3, 300, 200);
add(txt);
Font font = new Font("Verdana", Font.BOLD, 12);
txt.setFont(font);
txt.setForeground(Color.BLUE);
txt.setText("\n \n JTextArea font & color change example");
}
public static void main(String[] args){
Sscce jtxt = new Sscce();
jtxt.setSize(313, 233);
jtxt.setTitle("JTextArea font & color settings");
jtxt.show();
}
}
So, how can I accomplish this?
A JTextArea is a plain text component. It can display text in different fonts, but all of the text is in the same font. You'll want to use something like JSyntaxPane.
A very simple to use and extend JEditorKit that supports few languages. The main goal is to make it easy to have nice looking Java Swing Editors with support for Syntax Highlighting.

Layout trouble within JTextPane

I am sorry for the lack of a better title, but I have no idea how to specify the error further, since I don't understand its nature. Maybe someone can edit it, when the problem is understood.
I am writing an application in which the user can add icons into a text field. I obviously picked a JTextPane to display the text and the icons. After playing around with the insertComponent() function of the class I ran into some weird layout problems, so I decided to lookup the tutorial at oracle.com. After looking at the source code of the example, I decided to do the same and also use styles to add components to the underlying StyledDocument. It was when I started the first test run, when I discovered, that the layout problems stayed the same.
So, what is actually happening?
What I intended the text pane to show is "abcOdefO", but as you can tell by the screenshot, the two icons (circles) have some space to the right of them. I want the icon to be treated as a slightly larger character, so it should only occupy as much space as it needs, not (availableSpace / numberOfIcons), which seems to be what it actually occupies.
When typing another character at the caret position:
This is even weirder. If the icons have MouseListeners, all 4 visible circles trigger the event. If I drag the frame to another window or minimize and restore it, the weird parts vanish and the frame looks like the first image (except for the additional character). So I guess, this part of my problem is fixed with a call to repaint() at the correct location - but where?
This is the code that produces the images seen above:
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.RenderingHints;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextPane;
import javax.swing.text.BadLocationException;
import javax.swing.text.Style;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyleContext;
import javax.swing.text.StyledDocument;
public class TextPaneTestPanel extends JPanel {
private class myIcon extends JPanel {
private final int side;
private final int padding = 1;
public myIcon(int size) {
this.side = size - 2 * padding;
this.setSize(size, size);
this.setPreferredSize(getSize());
this.setMinimumSize(getSize());
}
public void paint(Graphics g) {
Graphics2D g2d = (Graphics2D) g.create();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setColor(Color.BLACK);
g2d.setStroke(new BasicStroke(2));
g2d.drawOval(padding, padding, side, side);
}
}
private final JTextPane textPane;
public TextPaneTestPanel() {
textPane = new JTextPane();
StyledDocument doc = textPane.getStyledDocument();
Style def = StyleContext.getDefaultStyleContext().getStyle(StyleContext.DEFAULT_STYLE);
StyleConstants.setFontFamily(def, "Monospaced");
Style regular = doc.addStyle("regular", def);
try {
doc.insertString(0, "abc", regular);
Style s1 = doc.addStyle("icon1", regular);
StyleConstants.setComponent(s1, new myIcon(20));
doc.insertString(3, " ", s1);
doc.insertString(4, "def", regular);
Style s2 = doc.addStyle("icon2", regular);
StyleConstants.setComponent(s2, new myIcon(20));
doc.insertString(7, " ", s2);
} catch (BadLocationException e1) {
e1.printStackTrace();
}
this.setLayout(new GridBagLayout());
this.add(textPane, new GridBagConstraints(0, 0, 1, 1, 1, 1, GridBagConstraints.CENTER, GridBagConstraints.BOTH,
new Insets(4, 4, 4, 4), 0, 0));
}
public static void main(String[] args) {
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);
TextPaneTestPanel panel = new TextPaneTestPanel();
frame.getContentPane().add(panel);
frame.setSize(300, 100);
frame.setVisible(true);
}
}
To sum up my questions:
What causes the space to appear after an icon?
Where do I add the repaint() or revalidate() to fix the problem seen in image #2?
P.S.: I know, that my "icon" does not implement Icon, but it shouldn't be necessary, since JTextPanes can handle all sorts of Components.
1.What causes the space to appear after an icon?
Well, you have the following code:
this.setMinimumSize(getSize());
What about the maximum size?
How to fix the problem seen in image #2?
Custom painting is done by overriding the paintComponent() method, not the paint() method and don't forget to invoke super.paintComponent.
An Icon would be more appropriate here since all you are doing is custom painting. Or even a JComponent, but not a JPanel which is a Container used for holding other components. Plus is you use an Icon you don't have the size problems that you have with a panel, or JComponent since that method is specifically implemented as part of the interface.

Categories