Skip to main content

How to have a left aligned icon with right aligned text in a cell?

11 replies [Last post]
uvoigt
Offline
Joined: 2006-01-26

Is it possible to render a left aligned icon with right aligned text in a table/treetable cell?

My problem is that I can use the highlighters (great!) to right align the text and to put an icon to the renderer component. But the icon glues to the text and is not left aligned to the complete cell.

Any hints?
Thanks!
Ulrich

Reply viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
uvoigt
Offline
Joined: 2006-01-26

After the bugs have been fixed there is this simple solution (copied from the RendererVisualCheck test case):

        ListModel files = createFileListModel();
        final JXList list = new JXList(files);
        ComponentProvider text = new LabelProvider(StringValues.FILE_NAME, JLabel.TRAILING);
        final WrappingProvider wrapper = new WrappingProvider(IconValues.FILE_ICON, text, true);
        wrapper.setExtendsComponentOpacity(true);
        list.setCellRenderer(new DefaultListRenderer(wrapper));
        list.addHighlighter(HighlighterFactory.createSimpleStriping());
uvoigt
Offline
Joined: 2006-01-26

> >
> > 2. The HighlighterPredicates which could be used
> by
> > my solution must be put into the StringValue
> > implementation. Obviously this should be no
> problem
> > but may result in implementing the same thing
> twice
> > (one for HighlighterPredicates for other columns
> and
> > one for the special StringValue implementation).
> >
>
> hmmm ... the StringValue is meant to be used
> unconditionally: it has no context knowledge. The
> idea was to keep it really lightweight. Plus it is
> used in pattern matching (like in searching,
> filtering) related functionality, where context
> information is difficult to obtain. On the other
> hand, there seem to be occasional real world
> requirements which need conditional StringValues - as
> seen in occasional discussions here, all open-ended
> :-)
>
> Could you elaborate a bit why/how you need it?

Here is my use case:
I have a treetable model. Some of the cells are editable but only if some preconditions are met (application is connected, correct password entered etc.).

The potential editable cells are highlighted with an edit icon which is greyed out if the preconditions are not met.

Each precondition is implemented as a HighlightPredicate.
One Highlighter paints the greyed icon for all potentially editable cells in case the preconditions are not met.
Another highligher paints the colored icon for all potentially editable cells in case the preconditions are met.

At the moment I don't know how I could achieve this with a StringValue/IconValue combination without putting the precondition values to the user object. This would be the last hack I want to implement.

kleopatra
Offline
Joined: 2003-06-11

Ulrich,

thanks for filing the issue, added your example to the visual test to expose.

> > Could you elaborate a bit why/how you need it?
>
> Here is my use case:
> I have a treetable model. Some of the cells are
> editable but only if some preconditions are met
> (application is connected, correct password entered
> etc.).
>
> The potential editable cells are highlighted with an
> edit icon which is greyed out if the preconditions
> are not met.
>
> Each precondition is implemented as a
> HighlightPredicate.
> One Highlighter paints the greyed icon for all
> potentially editable cells in case the preconditions
> are not met.
> Another highligher paints the colored icon for all
> potentially editable cells in case the preconditions
> are met.

oookay, ... I see the problem. And no absolutely clean solution available, that is none without "smearing" responsibility a bit. What I would do is to

1. decide (is a bit arbitrary ;-) which of the states is the default and set the corresponding icon in the provider via an IconValue
2. have a Highlighter which replaces the icon if it detects the other state

or if that's too arbitrary for your taste, let the default icon be an empty placeholder and keep your logic with the two highlighters, both replacing the empty default with the appropriate state-dependent icon. Might be better because it keeps the logic in one place.

What do you think?

thanks
Jeanette

uvoigt
Offline
Joined: 2006-01-26

> oookay, ... I see the problem. And no absolutely
> clean solution available, that is none without
> "smearing" responsibility a bit. What I would do is
> to
>
> 1. decide (is a bit arbitrary ;-) which of the states
> is the default and set the corresponding icon in the
> provider via an IconValue
> 2. have a Highlighter which replaces the icon if it
> detects the other state
>
> or if that's too arbitrary for your taste, let the
> default icon be an empty placeholder and keep your
> logic with the two highlighters, both replacing the
> empty default with the appropriate state-dependent
> icon. Might be better because it keeps the logic in
> one place.
>
> What do you think?
>
> thanks
> Jeanette

Yeah. The 2nd solution works well for me (apart from the highlighter issue I filed above). The reason is that my highlighter logic is a bit more complicated than I posted.

There is only one thing the swingx package could improve:
The IconHighlighter does not support the WrappingIconPanel used by the WrappingProvider. So I had to override the doHighlight and canHighlight methods of the IconHighlighter.
What do you think? Shouldn't this be a default behaviour of the IconHighlighter?

Here's the code:
[code]
import java.awt.Component;

import javax.swing.Icon;

import org.jdesktop.swingx.decorator.ComponentAdapter;
import org.jdesktop.swingx.decorator.HighlightPredicate;
import org.jdesktop.swingx.decorator.IconHighlighter;
import org.jdesktop.swingx.renderer.WrappingIconPanel;

public class WrappingIconHighlighter
extends IconHighlighter
{
public WrappingIconHighlighter()
{
super();
}

public WrappingIconHighlighter(HighlightPredicate predicate, Icon icon)
{
super(predicate, icon);
}

public WrappingIconHighlighter(HighlightPredicate predicate)
{
super(predicate);
}

public WrappingIconHighlighter(Icon icon)
{
super(icon);
}

@Override
protected Component doHighlight(Component component, ComponentAdapter adapter)
{
if (component instanceof WrappingIconPanel)
{
WrappingIconPanel panel = (WrappingIconPanel)component;
panel.setIcon(getIcon());
return component;
}
return super.doHighlight(component, adapter);
}

@Override
protected boolean canHighlight(Component component, ComponentAdapter adapter)
{
return super.canHighlight(component, adapter) || (component instanceof WrappingIconPanel);
}

}
[/code]

Ulrich

kleopatra
Offline
Joined: 2003-06-11

>
> There is only one thing the swingx package could
> improve:

hehe - would be glad if it really were the only thing :-)

> The IconHighlighter does not support the
> WrappingIconPanel used by the WrappingProvider. So I
> had to override the doHighlight and canHighlight
> methods of the IconHighlighter.
> What do you think? Shouldn't this be a default
> behaviour of the IconHighlighter?
>

good idea - and smoothly possible, as WrappingProvider guarantees to reset both text and icon in each round of config. Will do, thanks!

Cheers
Jeanette

kleopatra
Offline
Joined: 2003-06-11

> good idea - and smoothly possible, as
> WrappingProvider guarantees to reset both text and
> icon in each round of config. Will do, thanks!
>

filed an issue to not forget

https://swingx.dev.java.net/issues/show_bug.cgi?id=1311

CU
Jeanette

kleopatra
Offline
Joined: 2003-06-11

Good to see developers experimenting :-) Just: a Highlighter is not supposed to replace the component it gets. Most probably it will get the default visuals wrong (see the list of properties documented in ComponentProvider which must be re-set to their defaults in each config round). If you need a custom component to display the content , a custom ComponentProvider is the answer.

So your basic approach is perfectly valid - use a custom panel to separate the icon and the text - just a suboptimal place to do it. Plus, SwingX already has a provider similar to your panel: WrappingProvider. Added a quick example the RendererVisualCheck (assuming a fileTreeNode as value), copied here for your convenience.

[code]
public void interactiveIconTextAlignment() {
ListModel files = createFileListModel();
JXList list = new JXList(files);
ComponentProvider text = new LabelProvider(StringValues.FILE_NAME, JLabel.TRAILING);
WrappingProvider wrapper = new WrappingProvider(IconValues.FILE_ICON, text, true);
list.setCellRenderer(new DefaultListRenderer(wrapper));
showWithScrollingInFrame(list, "alignment in wrappingProvider");
}

[/code]

HTH
Jeanette

uvoigt
Offline
Joined: 2006-01-26

I was quite sure that there is an alternative to my solution but I didn't found myself...

After my first quick and rough thoughts about the WrappingProvider solution I have some comments (might not be thought out):

1. The area of the Icon value does not reflect highlighter action (your code plus alternate striping). I am not sure if that's a bug?
[code]
public void interactiveIconTextAlignment() {
ListModel files = createFileListModel();
JXList list = new JXList(files);
ComponentProvider text = new LabelProvider(StringValues.FILE_NAME, JLabel.TRAILING);
WrappingProvider wrapper = new WrappingProvider(IconValues.FILE_ICON, text, true);
list.setCellRenderer(new DefaultListRenderer(wrapper));
list.addHighlighter(HighlighterFactory.createAlternateStriping());
showWithScrollingInFrame(list, "alignment in wrappingProvider");
}
[/code]

2. The HighlighterPredicates which could be used by my solution must be put into the StringValue implementation. Obviously this should be no problem but may result in implementing the same thing twice (one for HighlighterPredicates for other columns and one for the special StringValue implementation).

3. Why does the doHighlight method return a Component if this should always be the component parameter? Maybe the signature should be changed to void?

Ulrich

kleopatra
Offline
Joined: 2003-06-11

Ulrich,

>
> After my first quick and rough thoughts about the
> WrappingProvider solution I have some comments (might
> not be thought out):
>

all thoughts always welcome :-)

> 1. The area of the Icon value does not reflect
> highlighter action (your code plus alternate
> striping). I am not sure if that's a bug?

good catch! More a feature than a bug: the original use of WrappingProvider was in a tree where the provider's icon is the node icon. In most LAFs that's not highlighted (when selected, f.i.) When used in other collection components, it needs to be included. Hmm ... need some configuration option. Could you file an enhancement issue, please?

>
> 2. The HighlighterPredicates which could be used by
> my solution must be put into the StringValue
> implementation. Obviously this should be no problem
> but may result in implementing the same thing twice
> (one for HighlighterPredicates for other columns and
> one for the special StringValue implementation).
>

hmmm ... the StringValue is meant to be used unconditionally: it has no context knowledge. The idea was to keep it really lightweight. Plus it is used in pattern matching (like in searching, filtering) related functionality, where context information is difficult to obtain. On the other hand, there seem to be occasional real world requirements which need conditional StringValues - as seen in occasional discussions here, all open-ended :-)

Could you elaborate a bit why/how you need it?

> 3. Why does the doHighlight method return a Component
> if this should always be the component parameter?
> Maybe the signature should be changed to void?

a bit of rope to hand oneself It _can_ be done, but requires extreme care.

Thanks for your - very well thought out, btw :-) - input
Jeanette

hehe .. meant "a bit of rope to _hang_ oneself" ;-)

Message was edited by: kleopatra

uvoigt
Offline
Joined: 2006-01-26

> > 1. The area of the Icon value does not reflect
> > highlighter action (your code plus alternate
> > striping). I am not sure if that's a bug?
>
> good catch! More a feature than a bug: the original
> use of WrappingProvider was in a tree where the
> provider's icon is the node icon. In most LAFs that's
> not highlighted (when selected, f.i.) When used in
> other collection components, it needs to be included.
> Hmm ... need some configuration option. Could you
> file an enhancement issue, please?

Done: https://swingx.dev.java.net/issues/show_bug.cgi?id=1309

uvoigt
Offline
Joined: 2006-01-26

I solved myself. There is the drawback that this highlighter must be the last highlighter. Otherwise other highlighter might not do their job if they only work with a JLabel.

Any comments are still wellcome!

[code]
import java.awt.Component;

import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JLabel;

import org.jdesktop.swingx.JXLabel;
import org.jdesktop.swingx.JXPanel;
import org.jdesktop.swingx.decorator.AbstractHighlighter;
import org.jdesktop.swingx.decorator.ComponentAdapter;
import org.jdesktop.swingx.decorator.HighlightPredicate;

public class LeftAlignedIconHighlighter
extends AbstractHighlighter
{
private Icon icon;
private LeftAlignedIconPanel plLeftAligned;

public LeftAlignedIconHighlighter()
{
this((HighlightPredicate)null);
}

public LeftAlignedIconHighlighter(HighlightPredicate predicate)
{
this(predicate, null);
}

public LeftAlignedIconHighlighter(Icon icon)
{
this(null, icon);
}

public LeftAlignedIconHighlighter(HighlightPredicate predicate, Icon icon)
{
super(predicate);
this.plLeftAligned = new LeftAlignedIconPanel();
setIcon(icon);
this.plLeftAligned.setIcon(getIcon());
}

public void setIcon(Icon icon)
{
if (areEqual(icon, getIcon()))
{
return;
}
this.icon = icon;
this.plLeftAligned.setIcon(getIcon());
fireStateChanged();
}

public Icon getIcon()
{
return this.icon;
}

@Override
protected Component doHighlight(Component component, ComponentAdapter adapter)
{
if (getIcon() != null)
{
this.plLeftAligned.setComponent(component);
return this.plLeftAligned;
}
return component;
}

@Override
protected boolean canHighlight(Component component, ComponentAdapter adapter)
{
return component instanceof JLabel;
}

private static class LeftAlignedIconPanel
extends JXPanel
{
private final JXLabel lbIcon;
private Component component;

public LeftAlignedIconPanel()
{
super();
setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
this.lbIcon = new JXLabel();
add(this.lbIcon);
add(Box.createHorizontalGlue());
}

public void setIcon(Icon icon)
{
this.lbIcon.setIcon(icon);
}

public void setComponent(Component component)
{
if (this.component != null)
{
remove(this.component);
}
this.component = component;
if (this.component != null)
{
copyComponentSettings();
add(this.component);
}
}

private void copyComponentSettings()
{
if (this.component != null)
{
setBackground(this.component.getBackground());
setForeground(this.component.getForeground());
if (this.component instanceof JComponent)
{
JComponent jcomponent = (JComponent)this.component;
setBorder(jcomponent.getBorder());
jcomponent.setBorder(null);

jcomponent.setOpaque(false);
}
}
}

}

}
[/code]