Skip to main content

Using a painter on JTextfield in swing 1.0 ?

24 replies [Last post]
paultaylor
Offline
Joined: 2003-12-04
Points: 0

Hi I wanted to try beautifying my app using the swingx painters stuff, but Im restricted to using swing 1.0 and the component I want to beautify is a JTextfield, there is a JXTextField in swing 1.6 but not 1.0 , any ideas

thanks Paul

Reply viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
kschaefe
Offline
Joined: 2006-06-08
Points: 0

Look at what PromptSupport is doing for background painters. See PromptTextUI.PainterHighlighter.

Karl

paultaylor
Offline
Joined: 2003-12-04
Points: 0

I started looking at it, and got lost.

Do you think it would just be possible to plonk the classes into swingx1.0 or do they rely on too much java 6 stuff (iI Couldnt see any Java 6 stuff)

kschaefe
Offline
Joined: 2006-06-08
Points: 0

You should be able to use PromptTextUI.PainterHighlighter in a 1.5 context. Nothing overly 1.6 in there that I know of.

Karl

paultaylor
Offline
Joined: 2003-12-04
Points: 0

I not sure that PromptTextUI.PainterHighlighter is quite what I want because I just want to write the textfields background, im not not using a highlighter for modifying parts of the text. I tried copy over classes from 1.6 to 1.5 and got it compiling

I created a JXTextField
[code]
static class JTextFieldRubberStamp extends JXTextField
{
public JTextFieldRubberStamp()
{
super();
PromptSupport.setBackgroundPainter(new GlossPainter(Color.GREEN),this);
}
[/code]

but it seemingly fails because of the L&F Im using

[code]
Exception in thread "AWT-EventQueue-0" java.lang.IllegalArgumentException: ui implementation not supported: class com.jgoodies.looks.windows.WindowsTextFieldUI
at org.jdesktop.swingx.plaf.TextUIWrapper$DefaultWrapper.wrapUI(TextUIWrapper.java:136)
at org.jdesktop.swingx.plaf.TextUIWrapper$DefaultWrapper.wrapUI(TextUIWrapper.java:106)
at org.jdesktop.swingx.plaf.TextUIWrapper.replaceUIIfNeeded(TextUIWrapper.java:60)
at org.jdesktop.swingx.plaf.TextUIWrapper$DefaultWrapper.replaceUIIfNeeded(TextUIWrapper.java:147)
at org.jdesktop.swingx.plaf.TextUIWrapper.install(TextUIWrapper.java:42)
at org.jdesktop.swingx.prompt.PromptSupport.setBackgroundPainter(PromptSupport.java:288)
at com.jthink.jaikoz.cellrenderer.SimpleCellTextRenderer$JTextFieldRubberStamp.(SimpleCellTextRenderer.java:384)

[/code]

I had simplified the wrapUI method in TextUIWrapper to cut down all the classes i was importing, assuming that JXTextField would indeed use TextUI as its UI but appears this is not the case.

[code]
public PromptTextUI wrapUI(JTextComponent textComponent) {
TextUI textUI = textComponent.getUI();

if (textUI instanceof PromptTextUI) {
return (PromptTextUI) textUI;
}
throw new IllegalArgumentException("ui implementation not supported: "
+ textUI.getClass());
}
[/code]

kschaefe
Offline
Joined: 2006-06-08
Points: 0

That's not what I'm suggesting. Don't use PromptSupport to do it.

Simply copy out the painter I mentioned and use it. You can add the Highlighter to the textfield directly.

Karl

paultaylor
Offline
Joined: 2003-12-04
Points: 0

hehehe,thanks for your patience

Okay done that, it certainly has an effectbut Im totally confused.

In my existing code I used jtextfield.setBackground() to se the background, and I only used a highlighter to highlight white space at the start or end of the text, (i.e user entered ' fred ' the spaces would be displayed in a differnt color)

In the new code I now define my highter as follows
[code]
private static Highlighter.HighlightPainter highlightPainter = null;
private static Highlighter defaultHighlighter = null;
defaultHighlighter = new PainterHighlighter(new MattePainter(new GradientPaint(0,0,Color.ORANGE,10,0,Color.RED,true)));
highlightPainter = new DefaultHighlighter.DefaultHighlightPainter(Color.BLUE);
[/code]
and my highlighter code is

[code]
void highlightWhitespaceText(JTextField text)
{
text.setHighlighter(defaultHighlighter);
try
{
Matcher m = whitespaceStartPattern.matcher(text.getText());
if (m.matches())
{
text.getHighlighter().addHighlight(m.start(1), m.end(1), highlightPainter);
}
m = whitespaceEndPattern.matcher(text.getText());
if (m.matches())
{
text.getHighlighter().addHighlight(m.start(1), m.end(1), highlightPainter);
}
}
catch (BadLocationException ble)
{
//
}
}

[/code]

The net result is the background of the cells in the table show with the gradient paint, and no additional highlight is shown where text has spaces.

What I want to happen is that text background is shown one colour due to one condition (it has been modified, if not modified leave it white) and if the value has whitespace the whitespace is highlighted a differnt colour

kschaefe
Offline
Joined: 2006-06-08
Points: 0

Can you supply a small runnable demo that shows the original behavior and how the PainterHighlighter is interfering?

Karl

paultaylor
Offline
Joined: 2003-12-04
Points: 0

I think a couple of screenshots would make it clear, but where can I put them ?

paultaylor
Offline
Joined: 2003-12-04
Points: 0

Okay screenshots here http://www.jthink.net/jaikoz/jsp/scratch/swingx.jsp

Its occurred to me since posting this (been a while since Iv emade any swingx changes)
that instead of setting the background of the JTextfield or setting this highlighter fot the text, maybe I should be using the org.jdesktop.swingx.decorator.PainterHighlighter to paint the table cell then I can use the existing highlighter code for highlighting white space ?

kschaefe
Offline
Joined: 2006-06-08
Points: 0

Yeah, that's a table, so you're best off using the table constructs, but if you need to maintain the painters during editing that wouldn't work without an editor that supports painters. As for a painter that will not paint in the text region, you will need to build something custom. Here are the steps in the recipe:

1. Use SwingUtilities.layoutCompoundLabel to find the text rectangle.
2. Remove the text rectangle from the painters clip.
3. Paint the painters.
4. Restore the clipping region.

Karl

paultaylor
Offline
Joined: 2003-12-04
Points: 0

No it doent need to show the colours whilst editing the field.

I dont want to not paint in the text region, but Im confused that just installing the painter highlighter on the Jtextfield meant it highlighted the whole of the text field, I thought it should only highlight areas of the text field when call text.getHighlighter().addHighlight. But because a texfield only supports one highlighter that method isnt going to work for me anyway.

So having created a decorator highlighter that displays okay when i use HighlightPredicate.ALWAYS Im just trying to work out whether I can create a HighlightPredicate which returns true on a cell by cell basis depending upon whether a cell has its modified flag set.

kschaefe
Offline
Joined: 2006-06-08
Points: 0

Create a custom HighlightPredicate. The ComponentAdapter provided to the isHighlighted method contains a variety of data about the table. You can introspect the table for the data that you need. Where are you keeping this modified flag?

Karl

paultaylor
Offline
Joined: 2003-12-04
Points: 0

The saga continues, my existing predicate subclasses were arther complex but actually my new predicate was nice and simple

[code]
public class CellModifiedPredicate implements HighlightPredicate
{
public CellModifiedPredicate()
{

}

public boolean isHighlighted(Component renderer, ComponentAdapter adapter)
{
Cell cell = (Cell)((JaikozMainDataTable) adapter.getComponent()).getValueAt(adapter.row,adapter.column);
if(cell!=null)
{
if(cell.isStatusEdited())
{
return true;
}
}
return false;
}
}
[/code]

Now when I initilise the table highlighter as follows it works perfectly

[code]
modifiedHighlighter = new ColorHighlighter(new CellModifiedPredicate(),Color.BLUE, null);
[/code]

but thw whole point of this isn the first place was that I wanted to be able to use a painter so I could have a gradient colour rather than just a solid block so I chnaged the line to
[code]
modifiedHighlighter = new PainterHighlighter(new CellModifiedPredicate(),new MattePainter(new GradientPaint(0,0,Color.GREEN,10,0,Color.RED,true)));
[/code]

and it doesnt work, it has no effect, why ?

kschaefe
Offline
Joined: 2006-06-08
Points: 0

Are you using a PainterAware cell renderer?

Karl

paultaylor
Offline
Joined: 2003-12-04
Points: 0

What does PainterWare mean ?

It occurred to me whilst out that my renderer was overriding paint methods to do nothing for perfiormance reasons

[code]
static class JTextFieldRubberStamp extends JTextField
{
public JTextFieldRubberStamp()
{
super();
}

public boolean isOpaque()
{
return true;
}

/**
* Overridden for performance reasons.
* See the Implementation Note
* for more information.
*
* @since 1.5
*/
public void invalidate()
{
}

/**
* Overridden for performance reasons.
* See the Implementation Note
* for more information.
*/
public void validate()
{
}

/**
* Overridden for performance reasons.
* See the Implementation Note
* for more information.
*/
public void revalidate()
{
}

/**
* Overridden for performance reasons.
* See the Implementation Note
* for more information.
*/
public void repaint(long tm, int x, int y, int width, int height)
{
}

/**
* Overridden for performance reasons.
* See the Implementation Note
* for more information.
*/
public void repaint(Rectangle r)
{
}

/**
* Overridden for performance reasons.
* See the Implementation Note
* for more information.
*
* @since 1.5
*/
public void repaint()
{
}

/**
* From Christmas Tree Example
* This override is only appropriate if this will never contain any
* children AND the Graphics is not clobbered during painting.
* @param g
*/
public void paint(Graphics g)
{
ui.update(g, this);
}
}
[/code]

but Ive changed it to

[code]
static class JTextFieldRubberStamp extends JTextField
{
public JTextFieldRubberStamp()
{
super();
}

public boolean isOpaque()
{
return true;
}

/**
* From Christmas Tree Example
* This override is only appropriate if this will never contain any
* children AND the Graphics is not clobbered during painting.
* @param g
*/
public void paint(Graphics g)
{
ui.update(g, this);
}
[/code]

and chnaged isOpaque() to true, and it hasnt made any chnages.

kschaefe
Offline
Joined: 2006-06-08
Points: 0

PainterAware is an interface that the rendering component must implement for the PainterHighlighter to work, cf. JRendererLabel. Is there a reason you are rolling your own renderer?

Karl

paultaylor
Offline
Joined: 2003-12-04
Points: 0

Oh man now I am lost, Im implmenting the highlighter at the table level because I didnt think there was a way to set a painter for a JTextField, but from this interface it looks like I could set the painter directly if I have my renderer class implement PainterAware, but how does that fit in with the higherlighter and the painter tied to the highlighter, it seems you would use one method or the other.

I use my own component because I want to highlight whitespace in the text (as shown in the screenshot), this can't be done with a JLabel based renderer, and I dont think there are any existing JTextField renderers.

kschaefe
Offline
Joined: 2006-06-08
Points: 0

I'd kind of like to see this whitespace highlighting to understand what you're talking about. Why can't a normal renderer and highlighter do that?

Karl

paultaylor
Offline
Joined: 2003-12-04
Points: 0

Well the term renderer and highlighter seem to have multiple meanings so I'm not sure what you mean by normal renderer/highlighter. I am using a 'normal renderer' (i.e a java swing renderer) and this can only be applied to a JTextfield, there arent any 'normal renderers' that use a textfield for rendering are there ?

This is the code for highlighting whitespace, and the first screenshot on the earlier link shows the effect of the whitespace highlighter (its the part of the field highlighted purple)

[cpde]
void highlightWhitespaceText(JTextField text)
{
text.setHighlighter(defaultHighlighter);
try
{
Matcher m = whitespaceStartPattern.matcher(text.getText());
if (m.matches())
{
text.getHighlighter().addHighlight(m.start(1), m.end(1), highlightPainter);
}
m = whitespaceEndPattern.matcher(text.getText());
if (m.matches())
{
text.getHighlighter().addHighlight(m.start(1), m.end(1), highlightPainter);
}
}
catch (BadLocationException ble)
{
//
}
}

[/code]

paultaylor
Offline
Joined: 2003-12-04
Points: 0

So to summarise

I want to be able to highlight individual characters of text of rendered in a cell in a table (this can be solid colour), I also want to be able to set the background of the table cell according to the data in the table (this needs to be a gradient colour). Im tied to using Java 1.5 at the moment, althoughh even if I use java 1.6 Im not clear how to do it because JXTextField doesnt implement PainterAware.

1. Use of org.jdesktop.swingx.decorator.PainterHighlighter against cells in a JXTable will only work if the cells of that table are rendered using a class that implements PainterAware.

2. Only subclasses of JTextComponent can use javax.swing.text.Highlighter to highlight individual characters within text.

3. There are no PainterAware renderers that subclass JTextComponent.

So I need to create a subclass of JTextfield that implements PainterAware, bugger.

kschaefe
Offline
Joined: 2006-06-08
Points: 0

Spot on. That's actually a rather interesting requirement and from our standpoint, I wonder if we should add a JTextField-based provider for renderering. Text highlighting is rather difficult outside of JTextComponent. Adding PainterAware to your current renderer is not difficult; JRendererLabel can be used as a roadmap to do everything that you want.

So fallout on our end:
1. We should think about adding a JTextField provider for renderers.
2. We should add a TextHighlighter for placing javax.swing.text.Highlighters into rendering textfields.

Anything else?

Karl

paultaylor
Offline
Joined: 2003-12-04
Points: 0

Okay, unfortunatelly mere mortals like myself find swingx very useful when it just works bur rather difficult to customize, I had one attempt at a TextComponent based renderer and then gave up, as it stands it just shows empty field with no text.

As for anything else, I also have another renderer based on a JPanel, which consists of a text field and a button to the right of it (currently the colour is changed using JPanel.setBackground()). I notice that JXPanel is painter enable but not PainterAware so dont know if it can be used as a renderer which will honour the highlighter or not.

[code]
import org.jdesktop.swingx.painter.Painter;
import org.jdesktop.swingx.renderer.PainterAware;

import javax.swing.*;
import java.awt.*;

public class PainterAwareTextRenderer extends JTextField implements PainterAware
{

protected Painter painter;
private boolean strict;

/**
*
*/
public PainterAwareTextRenderer() {
super();
setOpaque(true);
}

/**
* Overridden for performance reasons.

* PENDING: Think about Painters and opaqueness?
*
*/
@Override
public boolean isOpaque() {
Color back = getBackground();
Component p = getParent();
if (p != null) {
p = p.getParent();
}
// p should now be the JTable.
boolean colorMatch = (back != null) && (p != null) &&
back.equals(p.getBackground()) &&
p.isOpaque();
return !colorMatch && super.isOpaque();
}

/**
* {@inheritDoc}
*/
public void setPainter(Painter painter) {
Painter old = getPainter();
this.painter = painter;
firePropertyChange("painter", old, getPainter());
}

/**
* {@inheritDoc}
*/
public Painter getPainter() {
return painter;
}
/**
* {@inheritDoc}

*
* Overridden to inject Painter's painting.

* TODO: cleanup logic - see JRendererCheckBox.
*
*/
@Override
protected void paintComponent(Graphics g) {
if (painter != null) {
// we have a custom (background) painter
// try to inject if possible
// there's no guarantee - some LFs have their own background
// handling elsewhere
if (isOpaque()) {
// replace the paintComponent completely
paintComponentWithPainter((Graphics2D) g);
} else {
// transparent apply the background painter before calling super
paintPainter(g);
super.paintComponent(g);
}
} else {
// nothing to worry about - delegate to super
super.paintComponent(g);
}
}

/**
*
* Hack around AbstractPainter.paint bug which disposes the Graphics.
* So here we give it a scratch to paint on.

* TODO - remove again, the issue is fixed?
*
* @param g the graphics to paint on
*/
private void paintPainter(Graphics g) {
// fail fast: we assume that g must not be null
// which throws an NPE here instead deeper down the bowels
// this differs from corresponding core implementation!
Graphics2D scratch = (Graphics2D) g.create();
try {
painter.paint(scratch, this, getWidth(), getHeight());
}
finally {
scratch.dispose();
}
}

// public void setStrictWidth(boolean strict) {
// this.strict = strict;
// }
//
// @Override
// public Dimension getMaximumSize() {
// if (strict) {
// return super.getMaximumSize();
// }
// Dimension max = super.getMaximumSize();
// max.width = Integer.MAX_VALUE - 1;
// return max;
// }

/**
* PRE: painter != null, isOpaque()
* @param g
*/
protected void paintComponentWithPainter(Graphics2D g) {
// 1. be sure to fill the background
// 2. paint the painter
// by-pass ui.update and hook into ui.paint directly
if (ui != null) {
// fail fast: we assume that g must not be null
// which throws an NPE here instead deeper down the bowels
// this differs from corresponding core implementation!
Graphics2D scratchGraphics = (Graphics2D) g.create();
try {
scratchGraphics.setColor(getBackground());
scratchGraphics.fillRect(0, 0, getWidth(), getHeight());
paintPainter(g);
ui.paint(scratchGraphics, this);
}
finally {
scratchGraphics.dispose();
}
}
}

/**
* {@inheritDoc}

*
* Overridden to not automatically de/register itself from/to the ToolTipManager.
* As rendering component it is not considered to be active in any way, so the
* manager must not listen.
*/
@Override
public void setToolTipText(String text) {
putClientProperty(TOOL_TIP_TEXT_KEY, text);
}

/**
* Overridden for performance reasons.
* See the Implementation Note
* for more information.
*
* @since 1.5
*/
@Override
public void invalidate() {}

/**
* Overridden for performance reasons.
* See the Implementation Note
* for more information.
*/
@Override
public void validate() {}

/**
* Overridden for performance reasons.
* See the Implementation Note
* for more information.
*/
@Override
public void revalidate() {}

/**
* Overridden for performance reasons.
* See the Implementation Note
* for more information.
*/
@Override
public void repaint(long tm, int x, int y, int width, int height) {}

/**
* Overridden for performance reasons.
* See the Implementation Note
* for more information.
*/
@Override
public void repaint(Rectangle r) { }

/**
* Overridden for performance reasons.
* See the Implementation Note
* for more information.
*
* @since 1.5
*/
@Override
public void repaint() {
}

/**
* Overridden for performance reasons.
* See the Implementation Note
* for more information.
*/
@Override
protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
// Strings get interned...
if ("text".equals(propertyName)) {
super.firePropertyChange(propertyName, oldValue, newValue);
}
}

/**
* Overridden for performance reasons.
* See the Implementation Note
* for more information.
*/
@Override
public void firePropertyChange(String propertyName, boolean oldValue, boolean newValue) { }

}
[/code]

kschaefe
Offline
Joined: 2006-06-08
Points: 0

Thanks for the code in the post. I will try to take a look at it later tonight.

Karl

paultaylor
Offline
Joined: 2003-12-04
Points: 0

Thankyou !

I tried out using JXPanel for my panel renderer, and found that the highlighters had no effect but setting backgroundPainter() within the renderer did work

Thought you might be interested in is what I have so far
http://www.jthink.net/jaikoz/jsp/scratch/newrenderers.jpg

Status column is a JXLabelRenderer and correctly honours the decorator painter highlighter.
Album column is my JTextField based renderer and doesnt honour highlighter, just has block colour from setting background
Genre column uses a subclass of JXPanel, doesnt honour table highlighter, but did get the effect I want with setbackgroundpainter.
Same for Artwork column