Custom JTextArea wrap word style - java

I've got a JTextArea with setLineWrap(true) and (for now) setWrapStyleWord(true).
The text which is contained in that textArea contains almost no white space, and so the wrapping never occurs. But the text is semi-colon separated. And so I'd like to achieve a wrap style at the ";" instead of at the " ".
With the following text:
hello;world;foo;bar;I am the Wizard;of;Oz
Wrapping like
hello;world;foo;bar;
I am the Wizard;
of;Oz
Instead of (with setWrapStyleWord(true))
hello;world;foo;bar;I
am the Wizard;of;Oz
or
Instead of (with setWrapStyleWord(false))
hello;world;foo;bar;I a
m the Wizard;of;Oz
Any idea on how to realize this ?

The solution is to tweak the ComponentUI used by the JTextArea. See this discussion.
One could create directly a new ComponentUI and assign it to the JTextArea, but I recommend to create a new JTextArea subclass handling all this under the radar.
The Component
package com.zparkingb.swing;
import com.zparkingb.swing.ui.SeparatedTextAreaUI;
import javax.swing.JTextArea;
import javax.swing.UIManager;
import javax.swing.text.Document;
public class ZSeparatedTextArea extends JTextArea {
private final Character wordSeparator;
private static final String uiClassID = "SeparatedTextAreaUI";
public ZSeparatedTextArea(Character separator) {
super();
this.wordSeparator = separator;
}
public ZSeparatedTextArea(Character separator, String text) {
super(text);
this.wordSeparator = separator;
}
public ZSeparatedTextArea(Character separator, int rows, int columns) {
super(rows, columns);
this.wordSeparator = separator;
}
public ZSeparatedTextArea(Character separator, String text, int rows, int columns) {
super(text, rows, columns);
this.wordSeparator = separator;
}
public ZSeparatedTextArea(Character separator, Document doc) {
super(doc);
this.wordSeparator = separator;
}
public ZSeparatedTextArea(Character separator, Document doc, String text, int rows, int columns) {
super(doc, text, rows, columns);
this.wordSeparator = separator;
}
public Character getWordSeparator() {
return wordSeparator;
}
public void setUI(SeparatedTextAreaUI ui) {
super.setUI(ui);
}
#Override
public void updateUI() {
if (UIManager.get(getUIClassID()) != null) {
SeparatedTextAreaUI ui = (SeparatedTextAreaUI) UIManager.getUI(this);
setUI(ui);
}
else {
setUI(new SeparatedTextAreaUI());
}
}
public SeparatedTextAreaUI getUI() {
return (SeparatedTextAreaUI) ui;
}
#Override
public String getUIClassID() {
return uiClassID;
}
}
The ComponentUI
package com.zparkingb.swing.ui;
import com.zparkingb.swing.ZSeparatedTextArea;
import javax.swing.JComponent;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicTextAreaUI;
import javax.swing.text.Element;
import javax.swing.text.View;
public class SeparatedTextAreaUI extends BasicTextAreaUI {
private ZSeparatedTextArea textArea = null;
/**
* Creates the view for an element. Returns a SeparatedWrappedPlainView (or WrappedPlainView/PlainView if no wordSeparator is provided)
*
* #param elem the element
*
* #return the view
*/
public View create(Element elem) {
if (textArea.getWordSeparator() == null)
return super.create(elem);
View v = new SeparatedWrappedPlainView(textArea.getWordSeparator(), elem);
return v;
}
public static ComponentUI createUI(JComponent c) {
return new SeparatedTextAreaUI();
}
#Override
public void installUI(JComponent c) {
textArea = (ZSeparatedTextArea) c;
super.installUI(c);
}
#Override
public void uninstallUI(JComponent c) {
super.uninstallUI(c);
textArea = null;
}
}
The View
This one is more tricky. As some information required to do the word wrapping are private in SeparatedWrappedPlainView they have been duplicated (e.g. metrics)
package com.zparkingb.swing.ui;
import java.awt.Container;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.Shape;
import java.text.BreakIterator;
import javax.swing.text.BadLocationException;
import javax.swing.text.Element;
import javax.swing.text.Segment;
import javax.swing.text.TabExpander;
import javax.swing.text.Utilities;
import javax.swing.text.WrappedPlainView;
class SeparatedWrappedPlainView extends WrappedPlainView {
private int _tabBase;
private FontMetrics _metrics;
private final Character wordSeparator;
public SeparatedWrappedPlainView(Character wordSeparator, Element elem) {
super(elem);
assert(wordSeparator!=null);
this.wordSeparator=wordSeparator;
}
/**
* Rem: Copied from WrappedPlainView only to be able to use our own
* getBreakLocation instead of the {#link Utilities#getBreakLocation} used by default
*/
#Override
protected int calculateBreakPosition(int p0, int p1) {
Segment s = new Segment();
try {
getDocument().getText(p0, p1 - p0, s);
} catch (BadLocationException ex) {
assert false : "Couldn't load text";
}
int width = getWidth();
int pos;
pos = p0 + getBreakLocation(s, _metrics, _tabBase, _tabBase + width, this, p0);
return pos;
}
/**
* Rem: Copied from {#link Utilities#getBreakLocation} in order to
* to break the text on our separator instead of on whitespaces.
*/
private int getBreakLocation(Segment s, FontMetrics metrics, float x0, float x, TabExpander e, int startOffset) {
char[] txt = s.array;
int txtOffset = s.offset;
int txtCount = s.count;
int index = Utilities.getTabbedTextOffset(s, metrics, x0, x, e, startOffset, false); //, null, useFPIAPI);
if (index >= txtCount - 1) {
return txtCount;
}
for (int i = txtOffset + index; i >= txtOffset; i--) {
char ch = txt[i];
if (ch < 256) {
// break on separator
if (wordSeparator.equals(ch)) {
index = i - txtOffset + 1;
break;
}
}
else {
// a multibyte char found; use BreakIterator to find line break
BreakIterator bit = BreakIterator.getLineInstance();
bit.setText(s);
int breakPos = bit.preceding(i + 1);
if (breakPos > txtOffset) {
index = breakPos - txtOffset;
}
break;
}
}
return index;
}
void _updateMetrics() {
Container component = getContainer();
_metrics = component.getFontMetrics(component.getFont());
}
#Override
public void paint(Graphics g, Shape a) {
Rectangle r = a instanceof Rectangle ? (Rectangle) a : a.getBounds();
_tabBase = r.x;
_updateMetrics();
super.paint(g, a);
}
#Override
public float getPreferredSpan(int axis) {
_updateMetrics();
return super.getPreferredSpan(axis);
}
#Override
public float getMaximumSpan(int axis) {
_updateMetrics();
return super.getMaximumSpan(axis);
}
#Override
public float getMinimumSpan(int axis) {
_updateMetrics();
return super.getMinimumSpan(axis);
}
#Override
public void setSize(float width, float height) {
_updateMetrics();
super.setSize(width, height);
}
}

Related

How to get ellipsis in a JLabel containing HTML?

When I combine HTML tags into the JLabel text I am loosing the ellipsis behavior that is shown when the space is too small to display the complete text. In my specific case, it is a TableCellRenderer which extends JLabel (swing's default or other). Now, when the column width is too small for the text to be shown fully, it is not showing the ellipsis.
See the image below for example:
For the left column I wrapped the text at the renderer with HTML: setText("<html>" + "<strong>" + value.toString() + "</strong>" + "</html>");. As you can see when the column width is too small to contain the text, it is just cut. The right column however, showing the date and time and using DefaultTableCellRenderer is showing ellipsis when it fails to contain the complete text.
So my question is, can I have both? Meaning, wrapping the text with HTML and still get the ellipsis?
UPDATE:
I found the reason for not getting the ellipsis when using HTML. I followed the code from JComponent#paintComponent(Graphics g) all the way down to BasicLabelUI#layoutCL(...). See the following code snippet taken from the last. It is only clipping the string if it does not have the html property (which is true when the label text is wrapped with html). Yet I have no idea how to work around it:
v = (c != null) ? (View) c.getClientProperty("html") : null;
if (v != null) {
textR.width = Math.min(availTextWidth,
(int) v.getPreferredSpan(View.X_AXIS));
textR.height = (int) v.getPreferredSpan(View.Y_AXIS);
} else {
textR.width = SwingUtilities2.stringWidth(c, fm, text);
lsb = SwingUtilities2.getLeftSideBearing(c, fm, text);
if (lsb < 0) {
// If lsb is negative, add it to the width and later
// adjust the x location. This gives more space than is
// actually needed.
// This is done like this for two reasons:
// 1. If we set the width to the actual bounds all
// callers would have to account for negative lsb
// (pref size calculations ONLY look at width of
// textR)
// 2. You can do a drawString at the returned location
// and the text won't be clipped.
textR.width -= lsb;
}
if (textR.width > availTextWidth) {
text = SwingUtilities2.clipString(c, fm, text,
availTextWidth);
textR.width = SwingUtilities2.stringWidth(c, fm, text);
}
textR.height = fm.getHeight();
}
As long as the HTML content is simple, as in your question, the ellipsis showing can be accomplished with a custom made JLabel. Here is a working example.
Just resize the window and you see the ellipsis appear and disappear and the text cut appropriately as the label resizes with the window.
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import javax.swing.JLabel;
import javax.swing.border.Border;
public class SimpleHTMLJLabel extends JLabel
{
private static final long serialVersionUID = -1799635451172963826L;
private String textproper;
private String ellipsis = "...";
private int textproperwidth;
private FontMetrics fontMetrics;
private int ellipsisWidth;
private int insetsHorizontal;
private int borderHorizontal;
public SimpleHTMLJLabel(String textstart, String textproper, String textend)
{
super(textstart + textproper + textend);
this.textproper = textproper;
insetsHorizontal = getInsets().left + getInsets().right;
fontMetrics = getFontMetrics(getFont());
calculateWidths();
addComponentListener(new ComponentAdapter() {
public void componentResized(ComponentEvent e)
{
int availablewidth = getWidth();
if (textproperwidth > availablewidth - (insetsHorizontal + borderHorizontal))
{
String clippedtextproper = textproper;
while (clippedtextproper.length() > 0
&& fontMetrics.stringWidth(clippedtextproper) + ellipsisWidth > availablewidth - (insetsHorizontal + borderHorizontal))
{
clippedtextproper = clipText(clippedtextproper);
}
setText(textstart + clippedtextproper + ellipsis + textend);
} else
{
setText(textstart + textproper + textend);
}
}
});
}
private void calculateWidths()
{
if (textproper != null)
{
textproperwidth = fontMetrics.stringWidth(textproper);
}
if (ellipsis != null)
{
ellipsisWidth = fontMetrics.stringWidth(ellipsis);
}
}
#Override
public void setFont(Font font)
{
super.setFont(font);
fontMetrics = getFontMetrics(getFont());
calculateWidths();
}
private String clipText(String clippedtextproper)
{
return clippedtextproper.substring(0, clippedtextproper.length() - 1);
}
#Override
public void setBorder(Border border)
{
super.setBorder(border);
borderHorizontal = border.getBorderInsets(this).left + border.getBorderInsets(this).right;
}
}
MAIN
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class Main
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run()
{
JFrame window = new JFrame();
window.setResizable(true);
window.setTitle("Label Test");
window.getContentPane().add(getContent());
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setSize(400, 200);
window.setLocationRelativeTo(null);
window.setVisible(true);
}
});
}
protected static Component getContent()
{
JPanel panel = new JPanel(new BorderLayout());
SimpleHTMLJLabel label = new SimpleHTMLJLabel("<html><strong>", "TEST1test2TEST3test4TEST5test6TEST7test8TEST", "</strong></html>");
label.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createLineBorder(Color.BLUE, 5),
BorderFactory.createEmptyBorder(10, 10, 10, 10)));
label.setFont(label.getFont().deriveFont(20F));
panel.add(label, BorderLayout.CENTER);
return panel;
}
}
I'm going to say: No, you can't have both.
I think if you want custom styling and ellipsis you will have to do it yourself without HTML and with a custom TableCellRenderer.
If you want to try and have your cake and eat it too, you might be able to get there by creating your own View object and setting it with c.putClientProperty("html", value) but I suspect that the HTML rendering code has no notion of ellipsing (text-overflow is an HTML 5ish feature) so you would have to figure out how to teach it to do that. I suspect this would be very difficult and much harder than just writing your own TableCellRenderer.
Here is a modified version of SimpleHTMLJLabel which is using code above
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.io.IOException;
import java.io.StringReader;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import javax.swing.JLabel;
import javax.swing.border.Border;
import javax.swing.text.MutableAttributeSet;
import javax.swing.text.html.HTML.Tag;
import javax.swing.text.html.HTMLEditorKit.ParserCallback;
public class SimpleHTMLJLabel extends JLabel {
private static final String ellipsis = "...";
private static final String Set = null;
private int textproperwidth;
private FontMetrics fontMetrics;
private int ellipsisWidth;
private int insetsHorizontal;
private int borderHorizontal;
private List<Entry<String, String>> lines;
static String HTML = "<HTML>";
public SimpleHTMLJLabel() {
insetsHorizontal = getInsets().left + getInsets().right;
fontMetrics = getFontMetrics(getFont());
calculateWidths();
addComponentListener(new ComponentAdapter() {
#Override
public void componentResized(ComponentEvent e) {
int availablewidth = getWidth();
renderHtml(availablewidth);
}
});
}
public SimpleHTMLJLabel(String text) {
this();
setText(text);
}
public SimpleHTMLJLabel(List<Entry<String, String>> lines) {
this();
this.lines = lines;
calculateWidths();
super.setText(HTML + toHtml(lines));
}
#Override
public void setText(String text) {
if (text.toUpperCase().startsWith(HTML)) {
this.lines = parseHtml(text);
calculateWidths();
super.setText(HTML + toHtml(lines));
return;
}
super.setText(text);
}
private List<Entry<String, String>> parseHtml(String text) {
List<Entry<String, String>> ret = new ArrayList<>();
java.util.Map<Tag, MutableAttributeSet> tags = new HashMap<>();
try {
(new javax.swing.text.html.parser.ParserDelegator()).parse(new StringReader(text), new ParserCallback() {
#Override
public void handleEndTag(Tag t, int pos) {
//TODO clean handle MutableAttributeSet a
tags.remove(t);
}
#Override
public void handleStartTag(javax.swing.text.html.HTML.Tag t, MutableAttributeSet a, int pos) {
if (t == Tag.HTML) return;
if (t == Tag.P) return;
if (t == Tag.BR) return;
if (t == Tag.BODY) return;
tags.put(t,a);
}
#Override
public void handleText(char[] data, int pos) {
String formats = tags.entrySet().stream().map(t -> "<" + t.getKey() + getAttrib(t.getValue) + ">").collect(Collectors.joining());
ret.add(new AbstractMap.SimpleEntry<>(formats, new String(data)));
}
private String getAttrib(MutableAttributeSet t) {
// TODO Auto-generated method stub
//return " style='color:red'";
return " " + t;
}
}, false);
} catch (IOException e) {
e.printStackTrace();
}
return ret;
}
private static String toEndTag(String s) {
return s.replace("<", "</");
}
private static String toHtml(List<Entry<String, String>> lines) {
return lines.stream().map(s -> s.getKey() + s.getValue() + toEndTag(s.getKey())).collect(Collectors.joining());
}
private static String toPlain(List<Entry<String, String>> lines) {
return lines.stream().map(s -> s.getValue()).collect(Collectors.joining(" "));
}
static private List<Entry<String, String>> clipText(List<Entry<String, String>> properList) {
Entry<String, String> last = properList.get(properList.size() - 1);
List<Entry<String, String>> ret = properList.subList(0, properList.size() - 1);
String newlastValue = truncate(last.getValue());
if (newlastValue.isEmpty()) {
return ret;
}
List<Entry<String, String>> retNew = new ArrayList<>();
retNew.addAll(ret);
retNew.add(new AbstractMap.SimpleEntry<>(last.getKey(), newlastValue));
return retNew;
}
static private String truncate(String newlastValue) {
newlastValue = newlastValue.substring(0, newlastValue.length() - 1);
while (newlastValue.endsWith(" ")) {
newlastValue = newlastValue.substring(0, newlastValue.length() - 1);
}
return newlastValue;
}
private void calculateWidths() {
if (lines != null) {
textproperwidth = fontMetrics.stringWidth(toPlain(lines));
}
ellipsisWidth = fontMetrics.stringWidth(ellipsis);
}
#Override
public void setFont(Font font) {
super.setFont(font);
fontMetrics = getFontMetrics(getFont());
calculateWidths();
}
#Override
public void setBorder(Border border) {
super.setBorder(border);
borderHorizontal = border.getBorderInsets(this).left + border.getBorderInsets(this).right;
}
}
When using my code in TableCellRenderer you need to resize immediately in constructor time, but when you do not have a size of the column:
public SimpleHTMLJLabel() {
...
addComponentListener(new ComponentAdapter() {
#Override
public void componentResized(ComponentEvent e) {
int availablewidth = getWidth();
renderHtml(availablewidth);
}
});
}
protected void renderHtml(int availablewidth) {
if (lines == null || availablewidth == 0) return;
System.out.println("renderHtml " + textproperwidth + ">" + availablewidth);
if (textproperwidth > availablewidth - (insetsHorizontal + borderHorizontal)) {
List<Entry<String, String>> properList = clipText(lines);
while (properList.size() > 0 && fontMetrics.stringWidth(toPlain(properList)) + ellipsisWidth > availablewidth - (insetsHorizontal + borderHorizontal)) {
properList = clipText(properList);
}
SimpleHTMLJLabel.super.setText(HTML + toHtml(properList) + ellipsis);
} else {
SimpleHTMLJLabel.super.setText(HTML + toHtml(lines));
}
}
#Override
public void reshape(int x, int y, int w, int h) {
if (w > 0) renderHtml(w - 5);
super.reshape(x, y, w, h);
}
and in JTable
table = new JTable(model) {
#Override
public Component prepareRenderer(TableCellRenderer renderer, int row, int column) {
Component c = super.prepareRenderer(renderer, row, column);
TableColumn col = table.getColumnModel().getColumn(column);
javax.swing.table.DefaultTableCellRenderer.UIResource csss;
SimpleHTMLJLabel lab = new SimpleHTMLJLabel(((JLabel)
//lab.setXXX( c.getXXX)); for font bcolor, color, border, etc
lab.setText(c.getText());
lab.renderHtml(col.getWidth() - 5);
return lab;
}
};
One can reuse html-labell component to save GC

After running an Eclipse SWT application it shows many exceptions

After running my SWT application it doesn’t have any error at the time of compilation.
But after running it will shows the output for few sec and then the eclipse instance will become not responsive.
Please help me to avoid exceptions
I try to increase heap size.anybody is here to help me.....plzzzzzzzzzzzzzzzzzzzz
here i will give my code. i think my logic also have a problem. but i dont know how to correct it. i just follow on book to do this.here it is a program for clock. and i did in each movement of second hand a new thread is created. i want to make it as one thread.
it shows unable to create new native threads
activator.java
package com.packtpub.e4.clock.ui;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Tray;
import org.eclipse.swt.widgets.TrayItem;
import org.eclipse.ui.plugin.AbstractUIPlugin;
import org.osgi.framework.BundleContext;
/**
* The activator class controls the plug-in life cycle
*/
public class Activator extends AbstractUIPlugin {
// The plug-in ID
public static final String PLUGIN_ID = "com.packtpub.e4.clock.ui"; //$NON-NLS-1$
// The shared instance
private static Activator plugin;
private TrayItem trayItem;
private Image image;
private Shell shell;
/**
* The constructor
*/
public Activator() {
}
/*
* (non-Javadoc)
* #see org.eclipse.ui.plugin.AbstractUIPlugin#start(org.osgi.framework.BundleContext)
*/
public void start(BundleContext context) throws Exception {
super.start(context);
plugin = this;
final Display display = Display.getDefault();
display.asyncExec(new Runnable() {
public void run() {
image = new Image(display, Activator.class.getResourceAsStream("/icons/sample.gif"));
Tray tray = display.getSystemTray();
if (tray != null && image != null) {
trayItem = new TrayItem(tray, SWT.NONE);
trayItem.setToolTipText("Hello World");
trayItem.setVisible(true);
trayItem.setText("Hello World");
trayItem.setImage(new Image(trayItem.getDisplay(),
Activator.class.getResourceAsStream("/icons/sample.gif")));
}
trayItem.addSelectionListener(new SelectionListener() {
public void widgetSelected(SelectionEvent e) {
if (shell == null) {
shell = new Shell(trayItem.getDisplay());
shell.setLayout(new FillLayout());
new ClockWidget(shell, SWT.NONE, new RGB(255, 0, 255));
shell.pack();
}
shell.open();
}
#Override
public void widgetDefaultSelected(SelectionEvent e) {
// TODO Auto-generated method stub
}
});
}
});
}
/*
* (non-Javadoc)
* #see org.eclipse.ui.plugin.AbstractUIPlugin#stop(org.osgi.framework.BundleContext)
*/
public void stop(BundleContext context) throws Exception {
if (trayItem != null && !trayItem.isDisposed()) {
Display.getDefault().asyncExec(new Runnable() {
public void run() {
if (trayItem != null && !trayItem.isDisposed())
trayItem.dispose();
}
});
}
if (image != null && !image.isDisposed()) {
Display.getDefault().asyncExec(new Runnable() {
public void run() {
if (image != null && !image.isDisposed())
image.dispose();
}
});
}
if (shell != null && !shell.isDisposed()) {
Display.getDefault().asyncExec(new Runnable() {
public void run() {
if (shell != null && !shell.isDisposed())
shell.dispose();
}
});
}
}
/**
* Returns the shared instance
*
* #return the shared instance
*/
public static Activator getDefault() {
return plugin;
}
/**
* Returns an image descriptor for the image file at the given
* plug-in relative path
*
* #param path the path
* #return the image descriptor
*/
public static ImageDescriptor getImageDescriptor(String path) {
return imageDescriptorFromPlugin(PLUGIN_ID, path);
}
}
clockwidget.java
package com.packtpub.e4.clock.ui;
import java.util.Date;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
public class ClockWidget extends Canvas
{
private final Color color;
private int offset;
public void setOffset(int offset)
{
this.offset = offset;
}
public Color getColor()
{
return color;
}
public int getOffset()
{
return offset;
}
public ClockWidget(Composite parent, int style,RGB rgb)
{
super(parent, style);
this.color = new Color(parent.getDisplay(),rgb);
addDisposeListener(new DisposeListener()
{
public void widgetDisposed(DisposeEvent e)
{
if(color != null && !color.isDisposed())
color.dispose();
}
});
addPaintListener(new PaintListener()
{
public void paintControl(PaintEvent e)
{
ClockWidget.this.paintControl(e);
}
});
}
public void paintControl(PaintEvent e)
{
#SuppressWarnings("deprecation")
int seconds = new Date().getSeconds();
int arc = (15-seconds) * 6 % 360;
e.gc.setBackground(color);
e.gc.fillArc(e.x,e.y,e.width-1,e.height-1,arc-1,2);
e.gc.drawArc(e.x,e.y,e.width-1,e.height-1,0,360);
e.gc.setBackground(e.display.getSystemColor(SWT.COLOR_BLACK));
#SuppressWarnings("deprecation")
int hours = new Date().getHours() + offset;
arc = (3 - hours) * 30 % 360;
e.gc.fillArc(e.x, e.y, e.width-1, e.height-1, arc - 5, 10);
new Thread("TickTock")
{
public void run()
{
while (!ClockWidget.this.isDisposed())
{
ClockWidget.this.getDisplay().asyncExec(
new Runnable()
{
public void run()
{
if (!ClockWidget.this.isDisposed())
ClockWidget.this.redraw();
}
});
try
{
Thread.sleep(99999);
}
catch (InterruptedException e)
{
System.out.println("#clock"+e.toString());
return;
}
}
}
}.start();
}
public Point computeSize(int w,int h,boolean changed)
{
int size;
if(w == SWT.DEFAULT)
{
size = h;
}
else if (h == SWT.DEFAULT)
{
size = w;
}
else
{
size = Math.min(w,h);
}
if(size == SWT.DEFAULT)
size = 50;
return new Point(size,size);
}
}
SampleView.java
package com.packtpub.e4.clock.ui.views;
import java.util.TimeZone;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.layout.RowLayout;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.part.*;
import org.eclipse.swt.SWT;
import com.packtpub.e4.clock.ui.ClockWidget;
public class SampleView extends ViewPart {
private Combo timezones;
public void createPartControl(Composite parent) {
try{
RowLayout layout = new RowLayout(SWT.HORIZONTAL);
parent.setLayout(layout);
Object[] oo=parent.getDisplay().getDeviceData().objects;
int c = 0;
for (int j = 0; j < oo.length; j++)
if (oo[j] instanceof Color)
c++;
System.err.println("There are " + c + " Color instances");
final ClockWidget clock1 =new ClockWidget(parent, SWT.NONE, new RGB(255,0,0));
//final ClockWidget clock2 =new ClockWidget(parent, SWT.NONE, new RGB(0,255,0));
//final ClockWidget clock3 =new ClockWidget(parent, SWT.NONE, new RGB(0,0,255));
//clock1.setLayoutData(new RowData(20,20));
//clock3.setLayoutData(new RowData(100,100));
String[] ids = TimeZone.getAvailableIDs();
timezones = new Combo(parent, SWT.SIMPLE);
timezones.setVisibleItemCount(5);
for (int i = 0; i < ids.length; i++) {
timezones.add(ids[i]);
timezones.addSelectionListener(new SelectionListener() {
public void widgetSelected(SelectionEvent e) {
String z = timezones.getText();
TimeZone tz = z == null ? null : TimeZone.getTimeZone(z);
TimeZone dt = TimeZone.getDefault();
int offset = tz == null ? 0 : (
tz.getOffset(System.currentTimeMillis()) -
dt.getOffset(System.currentTimeMillis())) / 3600000;
clock1.setOffset(offset);
clock1.redraw();
}
public void widgetDefaultSelected(SelectionEvent e) {
clock1.setOffset(0);
clock1.redraw();
}
});
}
}catch(Exception e){
System.out.println("# SampleView.java"+e.toString());
}
}
public void setFocus() {
timezones.setFocus();
}
}
i got the answer... here the thread call is happend in clockwidgets paintcontrol function cut it from there and paste it on clockwidget constructor.
then the code will work proper
package com.packtpub.e4.clock.ui;
import java.util.Date;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
public class ClockWidget extends Canvas
{
private final Color color;
private int offset;
public void setOffset(int offset)
{
this.offset = offset;
}
public Color getColor()
{
return color;
}
public int getOffset()
{
return offset;
}
public ClockWidget(Composite parent, int style,RGB rgb)
{
super(parent, style);
this.color = new Color(parent.getDisplay(),rgb);
addDisposeListener(new DisposeListener()
{
public void widgetDisposed(DisposeEvent e)
{
if(color != null && !color.isDisposed())
color.dispose();
}
});
new Thread("TickTock")
{
public void run()
{
while (!ClockWidget.this.isDisposed())
{
ClockWidget.this.getDisplay().asyncExec(
new Runnable()
{
public void run()
{
if (!ClockWidget.this.isDisposed())
ClockWidget.this.redraw();
}
});
try
{
Thread.sleep(1000);
}
catch (InterruptedException e)
{
return;
}
}
}
}.start();
addPaintListener(new PaintListener()
{
public void paintControl(PaintEvent e)
{
ClockWidget.this.paintControl(e);
}
});
}
public void paintControl(PaintEvent e)
{
#SuppressWarnings("deprecation")
int seconds = new Date().getSeconds();
int arc = (15-seconds) * 6 % 360;
e.gc.setBackground(color);
e.gc.fillArc(e.x,e.y,e.width-1,e.height-1,arc-1,2);
e.gc.drawArc(e.x,e.y,e.width-1,e.height-1,0,360);
e.gc.setBackground(e.display.getSystemColor(SWT.COLOR_BLACK));
#SuppressWarnings("deprecation")
int hours = new Date().getHours() + offset;
arc = (3 - hours) * 30 % 360;
e.gc.fillArc(e.x, e.y, e.width-1, e.height-1, arc - 5, 10);
}
public Point computeSize(int w,int h,boolean changed)
{
int size;
if(w == SWT.DEFAULT)
{
size = h;
}
else if (h == SWT.DEFAULT)
{
size = w;
}
else
{
size = Math.min(w,h);
}
if(size == SWT.DEFAULT)
size = 50;
return new Point(size,size);
}
}

Set row height depend on JTextArea height

I have a problem and really don't know how to solve it.
I used some solutions from this forum but they don't work.
This is the piece of code:
package own_components.custom_components;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JTable;
import javax.swing.JTextArea;
import javax.swing.ListSelectionModel;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableCellRenderer;
import own_components.localizable_components.LocalizableComponent;
import localization.GUILocalizationTags;
import localization.LocalizationManager;
public class OutputJTable extends JTable implements CustomComponent
{
private CustomTableModel dataModel = new CustomTableModel();
private List<String[]> data = new ArrayList<String[]>();
private final int COLUMNS_AMOUNT = 3;
private final int _1ST_COL_WIDTH = 25;
private final int _2ST_COL_WIDTH = 45;
private final int _3ND_COL_WIDTH = 505;
public OutputJTable()
{
setModel(dataModel);
setTableProperties();
dataModel.addTableModelListener(new TableModelListener(){
#Override
public void tableChanged(TableModelEvent paramTableModelEvent)
{
fitRowsHeight();
}
});
}
private void setTableProperties()
{
//some properties of table
}
public void setResultOutput(List<String[]> result)
{
data = new ArrayList<String[]>();
data.add(new String[] { "l", "code", "222222222222222222222222222 22ddddddddddddddddddddddddddd22222222222222222222222222222222" });
data.add(new String[] { "l", "code", "sssssssssssssssssssssssssssssss sssssssssssssssssssssssssssssssssssssssssssssssssss222222222" });
dataModel.fireTableDataChanged();
}
private void fitRowsHeight()
{
for (int row = 0; row < getRowCount(); row++)
{
int rowHeight = getRowHeight();
Component comp = prepareRenderer(getCellRenderer(row, 2), row, 2);
rowHeight = Math.max(rowHeight, comp.getSize().height);
setRowHeight(row, rowHeight);
}
}
public int getSelectedRow()
{
return selectedRow;
}
private class CustomTableModel extends AbstractTableModel implements LocalizableComponent
{
private static final long serialVersionUID = -992340559233338699L;
private String[] columnsNames = { "a", "b", "c" };
#Override
public String getColumnName(int paramInt)
{
return columnsNames[paramInt];
}
#Override
public boolean isCellEditable(int paramInt1, int paramInt2)
{
return false;
}
#Override
public int getColumnCount()
{
return COLUMNS_AMOUNT;
}
#Override
public int getRowCount()
{
return data.size();
}
#Override
public String getValueAt(int arg0, int arg1)
{
return data.get(arg0)[arg1];
}
#Override
public void useTranslatedText(String tag)
{
columnsNames[1] = tag;
getColumnModel().getColumn(2).setHeaderValue(tag);
repaint();
}
#Override
public void registerToLocalization(LocalizationManager lm, String key)
{
lm.registerToTranslationList(this, GUILocalizationTags.OUT_TAB_DESCRIPTION);
}
}
private class CustomTableRenderer extends DefaultTableCellRenderer
{
JTextArea cellTemp = new JTextArea();
#Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column)
{
cellTemp = new JTextArea(data.get(row)[column]);
cellTemp.setLineWrap(true);
return cellTemp;
}
}
}
This is little bit long but rather simple: my table uses custom cell renderer which contains JTextArea. I use JTA because I need Strings wrapping. After put such JTextAreas I expect to set row heights to highest JTA in a row.
And here is the problem. In code above I expect to receive JTA.height but I still receive "0". The same situation with JTA.getRows().
I really don't understand why. Can anybody explain me what is wrong with this code?
This is working JTable with wrapped strings.
(I used solutions introduced by mKorbel in this thread How to make a JTable column to contain not JTextFields, but JTextAreas)
package own_components.custom_components;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.util.ArrayList;
import java.util.List;
import javax.swing.BorderFactory;
import javax.swing.JTable;
import javax.swing.JTextArea;
import javax.swing.ListSelectionModel;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellRenderer;
import javax.swing.text.View;
public class OutputJTable extends JTable
{
private static final long serialVersionUID = 1L;
private List<String[]> data = new ArrayList<String[]>();
private CustomTableModel dataModel = new CustomTableModel();
private final int COLUMNS_AMOUNT = 3;
private final int _1ST_COL_WIDTH = 25;
private final int _2ST_COL_WIDTH = 45;
private final int _3ND_COL_WIDTH = 505;
private int selectedRow = -1;
public OutputJTable()
{
setModel(dataModel);
setDefaultRenderer(Object.class, new CustomTableRenderer());
setTableProperties();
}
/**
* Sets basic table properties.
*/
private void setTableProperties()
{
setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
getColumnModel().getColumn(0).setMaxWidth(_1ST_COL_WIDTH);
getColumnModel().getColumn(0).setMinWidth(_1ST_COL_WIDTH);
getColumnModel().getColumn(1).setMaxWidth(_2ST_COL_WIDTH);
getColumnModel().getColumn(1).setMinWidth(_2ST_COL_WIDTH);
getColumnModel().getColumn(2).setMaxWidth(_3ND_COL_WIDTH);
getColumnModel().getColumn(2).setMinWidth(_3ND_COL_WIDTH);
setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
setIntercellSpacing(new Dimension(0, 0));
setShowGrid(false);
}
/**
* Receives data used to modified data showned in table.
* This should be only access point to add data used by data model (which is used by jtable).
*
* #param result
*/
public void setResultOutput(List<String[]> result)
{
data = new ArrayList<String[]>();
data = result;
dataModel.fireTableDataChanged();
}
#Override
public void doLayout()
{
super.doLayout();
for (int row = 0; row < getRowCount(); row++)
{
JTextArea a = (JTextArea) prepareRenderer(getDefaultRenderer(Object.class), row, 2);
int rowHeight = (int) a.getUI().getRootView(a).getView(0).getPreferredSpan(View.Y_AXIS) + getIntercellSpacing().height;
setRowHeight(row, rowHeight);
}
}
/**
* Returns which row is selected. Main purpose of this method is provide data to PrintManager what should be printed.
*/
public int getSelectedRow()
{
return selectedRow;
}
#Override
public void changeSelection(int rowIndex, int columnIndex, boolean toggle, boolean extend)
{
if (rowIndex != selectedRow)
{
selectedRow = rowIndex;
}
else
{
selectedRow = -1;
}
super.changeSelection(rowIndex, columnIndex, true, false);
}
/**
* This is model used to fill this table with data.
*/
private class CustomTableModel extends DefaultTableModel implements LocalizableComponent
{
private static final long serialVersionUID = -992340559233338699L;
private String[] columnsNames = { "a", "b", "c" };
#Override
public String getColumnName(int paramInt)
{
return columnsNames[paramInt];
}
#Override
public boolean isCellEditable(int paramInt1, int paramInt2)
{
return false;
}
#Override
public int getColumnCount()
{
return COLUMNS_AMOUNT;
}
#Override
public int getRowCount()
{
return data.size();
}
#Override
public String getValueAt(int arg0, int arg1)
{
return data.get(arg0)[arg1];
}
}
/**
* This class is used to render single cell.
*/
private class CustomTableRenderer extends JTextArea implements TableCellRenderer
{
private final Color SELECTION_BORDER = new Color(200, 200, 200);
private final Color ODD_BACKGR_COLOR = new Color(240, 240, 240);
private final Color EVEN_BACKGR_COLOR = Color.WHITE;
CustomTableRenderer()
{
setLineWrap(true);
setWrapStyleWord(true);
setEditable(false);
setFont(getFont());
}
#Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column)
{
setText((String) value);
setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
if (isSelected)
{
setBackground(SELECTION_BORDER);
}
else if (row % 2 != 0)
{
setBackground(ODD_BACKGR_COLOR);
}
else
{
setBackground(EVEN_BACKGR_COLOR);
}
return this;
}
}
}
Remarks:
formating of row height is based on third column, if you want to take under consideration all columns, you have to use additional "for" loop in doLayout(),
'dataModel' of this JTable is based on the List 'model',
setResultOutput() expect String[3]
Thanks for everybodys help.
Regards.

JTable Multiple Header Rows

I am using a JTable in my application and wish to have 2 rows for headings, similar to this:
Is this even possible or will I have to do something else? If so, what? Using Supertitle-titleA, SuperTitle-titleB will take up too much space and make information redundant.
We had the same requirement in our last project. What I have found is an Implementation for a GroupableTableHeader on java2s.com. However, I have pimped it a bit, although I cannot recall what exactly. Beneath is the implementation of the three classes as how we use them.
ColumnGroup.java
import java.awt.Component;
import java.awt.Dimension;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.swing.JTable;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
/**
* ColumnGroup
*
* #version 1.0 20.10.1998
* #author Nobuo Tamemasa
*/
public class ColumnGroup {
protected TableCellRenderer renderer;
protected List<TableColumn> columns;
protected List<ColumnGroup> groups;
protected String text;
protected int margin = 0;
public ColumnGroup(String text) {
this(text, null);
}
public ColumnGroup(String text, TableCellRenderer renderer) {
this.text = text;
this.renderer = renderer;
this.columns = new ArrayList<TableColumn>();
this.groups = new ArrayList<ColumnGroup>();
}
public void add(TableColumn column) {
columns.add(column);
}
public void add(ColumnGroup group) {
groups.add(group);
}
/**
* #param column
* TableColumn
*/
public List<ColumnGroup> getColumnGroups(TableColumn column) {
if (!contains(column)) {
return Collections.emptyList();
}
List<ColumnGroup> result = new ArrayList<ColumnGroup>();
result.add(this);
if (columns.contains(column)) {
return result;
}
for (ColumnGroup columnGroup : groups) {
result.addAll(columnGroup.getColumnGroups(column));
}
return result;
}
private boolean contains(TableColumn column) {
if (columns.contains(column)) {
return true;
}
for (ColumnGroup group : groups) {
if (group.contains(column)) {
return true;
}
}
return false;
}
public TableCellRenderer getHeaderRenderer() {
return renderer;
}
public void setHeaderRenderer(TableCellRenderer renderer) {
this.renderer = renderer;
}
public String getHeaderValue() {
return text;
}
public Dimension getSize(JTable table) {
TableCellRenderer renderer = this.renderer;
if (renderer == null) {
renderer = table.getTableHeader().getDefaultRenderer();
}
Component comp = renderer.getTableCellRendererComponent(table, getHeaderValue() == null || getHeaderValue().trim().isEmpty() ? " "
: getHeaderValue(), false, false, -1, -1);
int height = comp.getPreferredSize().height;
int width = 0;
for (ColumnGroup columnGroup : groups) {
width += columnGroup.getSize(table).width;
}
for (TableColumn tableColumn : columns) {
width += tableColumn.getWidth();
width += margin;
}
return new Dimension(width, height);
}
public void setColumnMargin(int margin) {
this.margin = margin;
for (ColumnGroup columnGroup : groups) {
columnGroup.setColumnMargin(margin);
}
}
}
GroupableTableHeader.java
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
/**
* GroupableTableHeader
*
* #version 1.0 20.10.1998
* #author Nobuo Tamemasa
*/
#SuppressWarnings("serial")
public class GroupableTableHeader extends JTableHeader {
#SuppressWarnings("unused")
private static final String uiClassID = "GroupableTableHeaderUI";
protected List<ColumnGroup> columnGroups = new ArrayList<ColumnGroup>();
public GroupableTableHeader(TableColumnModel model) {
super(model);
setUI(new GroupableTableHeaderUI());
setReorderingAllowed(false);
// setDefaultRenderer(new MultiLineHeaderRenderer());
}
#Override
public void updateUI() {
setUI(new GroupableTableHeaderUI());
}
#Override
public void setReorderingAllowed(boolean b) {
super.setReorderingAllowed(false);
}
public void addColumnGroup(ColumnGroup g) {
columnGroups.add(g);
}
public List<ColumnGroup> getColumnGroups(TableColumn col) {
for (ColumnGroup group : columnGroups) {
List<ColumnGroup> groups = group.getColumnGroups(col);
if (!groups.isEmpty()) {
return groups;
}
}
return Collections.emptyList();
}
public void setColumnMargin() {
int columnMargin = getColumnModel().getColumnMargin();
for (ColumnGroup group : columnGroups) {
group.setColumnMargin(columnMargin);
}
}
}
GroupableTableHeaderUI.java
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.swing.JComponent;
import javax.swing.UIManager;
import javax.swing.plaf.basic.BasicTableHeaderUI;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
public class GroupableTableHeaderUI extends BasicTableHeaderUI {
protected GroupableTableHeader getHeader() {
return (GroupableTableHeader) header;
}
#Override
public void paint(Graphics g, JComponent c) {
Rectangle clipBounds = g.getClipBounds();
if (header.getColumnModel().getColumnCount() == 0) {
return;
}
int column = 0;
Dimension size = header.getSize();
Rectangle cellRect = new Rectangle(0, 0, size.width, size.height);
Map<ColumnGroup, Rectangle> groupSizeMap = new HashMap<ColumnGroup, Rectangle>();
for (Enumeration<TableColumn> enumeration = header.getColumnModel().getColumns(); enumeration.hasMoreElements();) {
cellRect.height = size.height;
cellRect.y = 0;
TableColumn aColumn = enumeration.nextElement();
List<ColumnGroup> groups = getHeader().getColumnGroups(aColumn);
int groupHeight = 0;
for (ColumnGroup group : groups) {
Rectangle groupRect = groupSizeMap.get(group);
if (groupRect == null) {
groupRect = new Rectangle(cellRect);
Dimension d = group.getSize(header.getTable());
groupRect.width = d.width;
groupRect.height = d.height;
groupSizeMap.put(group, groupRect);
}
paintCell(g, groupRect, group);
groupHeight += groupRect.height;
cellRect.height = size.height - groupHeight;
cellRect.y = groupHeight;
}
cellRect.width = aColumn.getWidth();
if (cellRect.intersects(clipBounds)) {
paintCell(g, cellRect, column);
}
cellRect.x += cellRect.width;
column++;
}
}
private void paintCell(Graphics g, Rectangle cellRect, int columnIndex) {
TableColumn aColumn = header.getColumnModel().getColumn(columnIndex);
TableCellRenderer renderer = aColumn.getHeaderRenderer();
if (renderer == null) {
renderer = getHeader().getDefaultRenderer();
}
Component c = renderer.getTableCellRendererComponent(header.getTable(), aColumn.getHeaderValue(), false, false,
-1, columnIndex);
c.setBackground(UIManager.getColor("control"));
rendererPane.paintComponent(g, c, header, cellRect.x, cellRect.y, cellRect.width, cellRect.height, true);
}
private void paintCell(Graphics g, Rectangle cellRect, ColumnGroup cGroup) {
TableCellRenderer renderer = cGroup.getHeaderRenderer();
if (renderer == null) {
renderer = getHeader().getDefaultRenderer();
}
Component component = renderer.getTableCellRendererComponent(header.getTable(), cGroup.getHeaderValue(), false,
false, -1, -1);
rendererPane
.paintComponent(g, component, header, cellRect.x, cellRect.y, cellRect.width, cellRect.height, true);
}
private int getHeaderHeight() {
int headerHeight = 0;
TableColumnModel columnModel = header.getColumnModel();
for (int column = 0; column < columnModel.getColumnCount(); column++) {
TableColumn aColumn = columnModel.getColumn(column);
TableCellRenderer renderer = aColumn.getHeaderRenderer();
if (renderer == null) {
renderer = getHeader().getDefaultRenderer();
}
Component comp = renderer.getTableCellRendererComponent(header.getTable(), aColumn.getHeaderValue(), false,
false, -1, column);
int cHeight = comp.getPreferredSize().height;
List<ColumnGroup> groups = getHeader().getColumnGroups(aColumn);
for (ColumnGroup group : groups) {
cHeight += group.getSize(header.getTable()).height;
}
headerHeight = Math.max(headerHeight, cHeight);
}
return headerHeight;
}
#Override
public Dimension getPreferredSize(JComponent c) {
int width = 0;
for (Enumeration<TableColumn> enumeration = header.getColumnModel().getColumns(); enumeration.hasMoreElements();) {
TableColumn aColumn = enumeration.nextElement();
width += aColumn.getPreferredWidth();
}
return createHeaderSize(width);
}
private Dimension createHeaderSize(int width) {
TableColumnModel columnModel = header.getColumnModel();
width += columnModel.getColumnMargin() * columnModel.getColumnCount();
if (width > Integer.MAX_VALUE) {
width = Integer.MAX_VALUE;
}
return new Dimension(width, getHeaderHeight());
}
}
Yes, you need to supply your own JTableHeader. The difficult comes in trying to layout it out.
You'll need to look at JTableHeader.getHeaderRect(column), this tells the renderer how to layout the column headers.
You're going to have to take into consideration the height of each column renderer as well as the height of the component you want to use a label renderer (I'd suggest using the Header's column renderer as a bases, but that's up to you)
Take account of space between columns (when more then two columns join to group - right border of group column become invisible).
Here is solution:
public Dimension getSize(JTable table) {
Component comp = renderer.getTableCellRendererComponent(
table, getHeaderValue(), false, false,-1, -1);
int height = comp.getPreferredSize().height;
int width = 0;
Enumeration e = v.elements();
int testNum = 0;
while (e.hasMoreElements()) {
Object obj = e.nextElement();
if (obj instanceof TableColumn) {
TableColumn aColumn = (TableColumn)obj;
width += aColumn.getWidth()-table.getIntercellSpacing().width;
width += margin;
} else {
width += ((ColumnGroup)obj).getSize(table).width-table.getIntercellSpacing().width;
}
}
return new Dimension(width+2*table.getIntercellSpacing().width, height);
}

Java JTextField with input hint

I would like to add a hint value to my javax.swing.JTextField. It should look like Firefox rendering of <input type="text" title="bla">. This creates an edit field with the text 'bla' in the background. If the textbox has focus the title-text disappears and just reappears if the user leaves the editbox without text.
Is there a (free) swing component that does something like this?
You could create your own:
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import javax.swing.*;
public class Main {
public static void main(String[] args) {
final JFrame frame = new JFrame();
frame.setLayout(new BorderLayout());
final JTextField textFieldA = new HintTextField("A hint here");
final JTextField textFieldB = new HintTextField("Another hint here");
frame.add(textFieldA, BorderLayout.NORTH);
frame.add(textFieldB, BorderLayout.CENTER);
JButton btnGetText = new JButton("Get text");
btnGetText.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
String message = String.format("textFieldA='%s', textFieldB='%s'",
textFieldA.getText(), textFieldB.getText());
JOptionPane.showMessageDialog(frame, message);
}
});
frame.add(btnGetText, BorderLayout.SOUTH);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.setVisible(true);
frame.pack();
}
}
class HintTextField extends JTextField implements FocusListener {
private final String hint;
private boolean showingHint;
public HintTextField(final String hint) {
super(hint);
this.hint = hint;
this.showingHint = true;
super.addFocusListener(this);
}
#Override
public void focusGained(FocusEvent e) {
if(this.getText().isEmpty()) {
super.setText("");
showingHint = false;
}
}
#Override
public void focusLost(FocusEvent e) {
if(this.getText().isEmpty()) {
super.setText(hint);
showingHint = true;
}
}
#Override
public String getText() {
return showingHint ? "" : super.getText();
}
}
If you're still on Java 1.5, replace the this.getText().isEmpty() with this.getText().length() == 0.
Here is a simple way that looks good in any L&F:
public class HintTextField extends JTextField {
public HintTextField(String hint) {
_hint = hint;
}
#Override
public void paint(Graphics g) {
super.paint(g);
if (getText().length() == 0) {
int h = getHeight();
((Graphics2D)g).setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
Insets ins = getInsets();
FontMetrics fm = g.getFontMetrics();
int c0 = getBackground().getRGB();
int c1 = getForeground().getRGB();
int m = 0xfefefefe;
int c2 = ((c0 & m) >>> 1) + ((c1 & m) >>> 1);
g.setColor(new Color(c2, true));
g.drawString(_hint, ins.left, h / 2 + fm.getAscent() / 2 - 2);
}
}
private final String _hint;
}
Here is a single class copy/paste solution:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import javax.swing.plaf.basic.BasicTextFieldUI;
import javax.swing.text.JTextComponent;
public class HintTextFieldUI extends BasicTextFieldUI implements FocusListener {
private String hint;
private boolean hideOnFocus;
private Color color;
public Color getColor() {
return color;
}
public void setColor(Color color) {
this.color = color;
repaint();
}
private void repaint() {
if(getComponent() != null) {
getComponent().repaint();
}
}
public boolean isHideOnFocus() {
return hideOnFocus;
}
public void setHideOnFocus(boolean hideOnFocus) {
this.hideOnFocus = hideOnFocus;
repaint();
}
public String getHint() {
return hint;
}
public void setHint(String hint) {
this.hint = hint;
repaint();
}
public HintTextFieldUI(String hint) {
this(hint,false);
}
public HintTextFieldUI(String hint, boolean hideOnFocus) {
this(hint,hideOnFocus, null);
}
public HintTextFieldUI(String hint, boolean hideOnFocus, Color color) {
this.hint = hint;
this.hideOnFocus = hideOnFocus;
this.color = color;
}
#Override
protected void paintSafely(Graphics g) {
super.paintSafely(g);
JTextComponent comp = getComponent();
if(hint!=null && comp.getText().length() == 0 && (!(hideOnFocus && comp.hasFocus()))){
if(color != null) {
g.setColor(color);
} else {
g.setColor(comp.getForeground().brighter().brighter().brighter());
}
int padding = (comp.getHeight() - comp.getFont().getSize())/2;
g.drawString(hint, 2, comp.getHeight()-padding-1);
}
}
#Override
public void focusGained(FocusEvent e) {
if(hideOnFocus) repaint();
}
#Override
public void focusLost(FocusEvent e) {
if(hideOnFocus) repaint();
}
#Override
protected void installListeners() {
super.installListeners();
getComponent().addFocusListener(this);
}
#Override
protected void uninstallListeners() {
super.uninstallListeners();
getComponent().removeFocusListener(this);
}
}
Use it like this:
TextField field = new JTextField();
field.setUI(new HintTextFieldUI("Search", true));
Note that it is happening in protected void paintSafely(Graphics g).
Take a look at this one: http://code.google.com/p/xswingx/
It is not very difficult to implement it by yourself, btw. A couple of listeners and custom renderer and voila.
For any Swing component (that is, anything that extends JComponent), you can call the setToolTipText(String) method.
For more information, reference the following links:
API Documentation for setToolTipText
"How to Use Tool Tips" tutorial
Have look at WebLookAndFeel at https://github.com/mgarin/weblaf/
WebTextField txtName = new com.alee.laf.text.WebTextField();
txtName.setHideInputPromptOnFocus(false);
txtName.setInputPrompt("Name");
txtName.setInputPromptFont(new java.awt.Font("Ubuntu", 0, 18));
txtName.setInputPromptForeground(new java.awt.Color(102, 102, 102));
txtName.setInputPromptPosition(0);
If you still look for a solution, here's one that combined other answers (Bart Kiers and culmat) for your reference:
import javax.swing.*;
import javax.swing.text.JTextComponent;
import java.awt.*;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
public class HintTextField extends JTextField implements FocusListener
{
private String hint;
public HintTextField ()
{
this("");
}
public HintTextField(final String hint)
{
setHint(hint);
super.addFocusListener(this);
}
public void setHint(String hint)
{
this.hint = hint;
setUI(new HintTextFieldUI(hint, true));
//setText(this.hint);
}
public void focusGained(FocusEvent e)
{
if(this.getText().length() == 0)
{
super.setText("");
}
}
public void focusLost(FocusEvent e)
{
if(this.getText().length() == 0)
{
setHint(hint);
}
}
public String getText()
{
String typed = super.getText();
return typed.equals(hint)?"":typed;
}
}
class HintTextFieldUI extends javax.swing.plaf.basic.BasicTextFieldUI implements FocusListener
{
private String hint;
private boolean hideOnFocus;
private Color color;
public Color getColor()
{
return color;
}
public void setColor(Color color)
{
this.color = color;
repaint();
}
private void repaint()
{
if(getComponent() != null)
{
getComponent().repaint();
}
}
public boolean isHideOnFocus()
{
return hideOnFocus;
}
public void setHideOnFocus(boolean hideOnFocus)
{
this.hideOnFocus = hideOnFocus;
repaint();
}
public String getHint()
{
return hint;
}
public void setHint(String hint)
{
this.hint = hint;
repaint();
}
public HintTextFieldUI(String hint)
{
this(hint, false);
}
public HintTextFieldUI(String hint, boolean hideOnFocus)
{
this(hint, hideOnFocus, null);
}
public HintTextFieldUI(String hint, boolean hideOnFocus, Color color)
{
this.hint = hint;
this.hideOnFocus = hideOnFocus;
this.color = color;
}
protected void paintSafely(Graphics g)
{
super.paintSafely(g);
JTextComponent comp = getComponent();
if(hint != null && comp.getText().length() == 0 && (!(hideOnFocus && comp.hasFocus())))
{
if(color != null)
{
g.setColor(color);
}
else
{
g.setColor(Color.gray);
}
int padding = (comp.getHeight() - comp.getFont().getSize()) / 2;
g.drawString(hint, 5, comp.getHeight() - padding - 1);
}
}
public void focusGained(FocusEvent e)
{
if(hideOnFocus) repaint();
}
public void focusLost(FocusEvent e)
{
if(hideOnFocus) repaint();
}
protected void installListeners()
{
super.installListeners();
getComponent().addFocusListener(this);
}
protected void uninstallListeners()
{
super.uninstallListeners();
getComponent().removeFocusListener(this);
}
}
Usage:
HintTextField field = new HintTextField();
field.setHint("Here's a hint");
This can be achieved by using a focus listener to update the text field content.
Make the class implement the focus listener interface:
class YourClass implements FocusListener
Add a method to catch when focus is gained that blanks the field:
public void focusGained(FocusEvent e) {
if(JTextField1.getText().equals("Username")) {
JTextField1.setText("");
}
}
Add a method to catch when focus is lost to redisplay the default entry if the field was blank:
public void focusLost(FocusEvent e) {
if(JTextField1.getText().equals("")) {
JTextField1.setText("Username");
// you should prevent the form from being processed in this state
// as it will literally contain "Username" for the username
}
}
Register your class as the focus listener for text field:
textField.addFocusListener(this);
Learn more at How to Write a Focus Listener in the Java Tutorials.
Here is a fully working example based on Adam Gawne-Cain's earlier Posting. His solution is simple and actually works exceptionally well.
I've used the following text in a Grid of multiple Fields:
H__|__WWW__+__XXXX__+__WWW__|__H
this makes it possible to easily verify the x/y alignment of the hinted text.
A couple of observations:
- there are any number of solutions out there, but many only work superficially and/or are buggy
- sun.tools.jconsole.ThreadTab.PromptingTextField is a simple solution, but it only shows the prompting text when the Field doesn't have the focus & it's private, but nothing a little cut-and-paste won't fix.
The following works on JDK 8 and upwards:
import java.awt.*;
import java.util.stream.*;
import javax.swing.*;
/**
* #author DaveTheDane, based on a suggestion from Adam Gawne-Cain
*/
public final class JTextFieldPromptExample extends JFrame {
private static JTextField newPromptedJTextField (final String text, final String prompt) {
final String promptPossiblyNullButNeverWhitespace = prompt == null || prompt.trim().isEmpty() ? null : prompt;
return new JTextField(text) {
#Override
public void paintComponent(final Graphics USE_g2d_INSTEAD) {
final Graphics2D g2d = (Graphics2D) USE_g2d_INSTEAD;
super.paintComponent(g2d);
// System.out.println("Paint.: " + g2d);
if (getText().isEmpty()
&& promptPossiblyNullButNeverWhitespace != null) {
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
final Insets ins = getInsets();
final FontMetrics fm = g2d.getFontMetrics();
final int cB = getBackground().getRGB();
final int cF = getForeground().getRGB();
final int m = 0xfefefefe;
final int c2 = ((cB & m) >>> 1) + ((cF & m) >>> 1); // "for X in (A, R, G, B) {Xnew = (Xb + Xf) / 2}"
/*
* The hint text color should be halfway between the foreground and background colors so it is always gently visible.
* The variables c0,c1,m,c2 calculate the halfway color's ARGB fields simultaneously without overflowing 8 bits.
* Swing sets the Graphics' font to match the JTextField's font property before calling the "paint" method,
* so the hint font will match the JTextField's font.
* Don't think there are any side effects because Swing discards the Graphics after painting.
* Adam Gawne-Cain, Aug 6 2019 at 15:55
*/
g2d.setColor(new Color(c2, true));
g2d.drawString(promptPossiblyNullButNeverWhitespace, ins.left, getHeight() - fm.getDescent() - ins.bottom);
/*
* y Coordinate based on Descent & Bottom-inset seems to align Text spot-on.
* DaveTheDane, Apr 10 2020
*/
}
}
};
}
private static final GridBagConstraints GBC_LEFT = new GridBagConstraints();
private static final GridBagConstraints GBC_RIGHT = new GridBagConstraints();
/**/ static {
GBC_LEFT .anchor = GridBagConstraints.LINE_START;
GBC_LEFT .fill = GridBagConstraints.HORIZONTAL;
GBC_LEFT .insets = new Insets(8, 8, 0, 0);
GBC_RIGHT.gridwidth = GridBagConstraints.REMAINDER;
GBC_RIGHT.fill = GridBagConstraints.HORIZONTAL;
GBC_RIGHT.insets = new Insets(8, 8, 0, 8);
}
private <C extends Component> C addLeft (final C component) {
this .add (component);
this.gbl.setConstraints(component, GBC_LEFT);
return component;
}
private <C extends Component> C addRight(final C component) {
this .add (component);
this.gbl.setConstraints(component, GBC_RIGHT);
return component;
}
private static final String ALIGN = "H__|__WWW__+__XXXX__+__WWW__|__H";
private final GridBagLayout gbl = new GridBagLayout();
public JTextFieldPromptExample(final String title) {
super(title);
this.setLayout(gbl);
final java.util.List<JTextField> texts = Stream.of(
addLeft (newPromptedJTextField(ALIGN + ' ' + "Top-Left" , ALIGN)),
addRight(newPromptedJTextField(ALIGN + ' ' + "Top-Right" , ALIGN)),
addLeft (newPromptedJTextField(ALIGN + ' ' + "Middle-Left" , ALIGN)),
addRight(newPromptedJTextField( null , ALIGN)),
addLeft (new JTextField("x" )),
addRight(newPromptedJTextField("x", "" )),
addLeft (new JTextField(null )),
addRight(newPromptedJTextField(null, null)),
addLeft (newPromptedJTextField(ALIGN + ' ' + "Bottom-Left" , ALIGN)),
addRight(newPromptedJTextField(ALIGN + ' ' + "Bottom-Right", ALIGN)) ).collect(Collectors.toList());
final JButton button = addRight(new JButton("Get texts"));
/**/ addRight(Box.createVerticalStrut(0)); // 1 last time forces bottom inset
this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
this.setPreferredSize(new Dimension(740, 260));
this.pack();
this.setResizable(false);
this.setVisible(true);
button.addActionListener(e -> {
texts.forEach(text -> System.out.println("Text..: " + text.getText()));
});
}
public static void main(final String[] args) {
SwingUtilities.invokeLater(() -> new JTextFieldPromptExample("JTextField with Prompt"));
}
}

Categories