Skip to main content

a BooleanPredicate for conditional highlighting

8 replies [Last post]
alavideo
Offline
Joined: 2008-03-31
Points: 0

Hello,

I was trying to figure out how to create a row highlighter based on a boolean column of my JXTable. Something very close is the supplied PatternPredicate. The problem is PatternPredicate uses a Pattern (obviously) and I had no idea how to create a pattern for a boolean column.

I thought about creating my own instance of the HighlightPredicate and overriding the isHighlighted method. With that approach, however, I loose the ability to highlight the entire row as can be done with the PatternPredicate (i.e, passing -1 or "ALL" for the target column). There is also no way to specify the single "test column" to use when evaluating if the highlight should be applied.

After looking at the PatternPredicate source I saw it would be pretty simple to use it as a template to create a "BooleanPredicate". I thought others in the community may find this useful so I'm posting the code here.

Issue/Note: There seems an issue with the underlying HighlightPredicate code. I'm not sure how to explain it, but it seems that when you have multiple highlighters applied it sometimes gets confused as to what background and foreground color to use. For example, I have a ColorHighligher which sets the foreground (font) to red and background to white if the number is negative. Then the "BooleanPredicate" highlighter sets the foreground (font) to white and the background to red on the row if the boolean test column is true. When the BooleanPredicate highlighter is activated, I end up with a red background and red foreground on the columns with negative numbers (i.e., can't see the text). If anyone knows how to correct this, please let me know.

Ok, here is the BooleanPredicate

<br />
/**<br />
 * Boolean highlight predicate based on Jeanette Winzenburg's PatternPredicate.<br />
 *<br />
 * @author Jim Merrell<br />
 */<br />
public class BooleanPredicate implements HighlightPredicate {</p>
<p>    public static final int ALL = -1;<br />
    private int highlightColumn;<br />
    private int testColumn;<br />
    private boolean testCondition;</p>
<p>    /**<br />
     * Instantiates a Predicate with the given boolean value and testColumn index<br />
     * (in model coordinates) highlighting all columns.<br />
     *  A column index of -1 is interpreted<br />
     * as "all". (PENDING: search forum for the exact definition, legacy<br />
     * base pattern and search behave differently?)<br />
     *<br />
     * @param testCondition the boolean value to test the cell value against<br />
     * @param testColumn the column index of the cell which contains the value<br />
     *   to test against the pattern<br />
     */<br />
    public BooleanPredicate(boolean testCondition, int testColumn) {<br />
        this(testCondition, testColumn, ALL);<br />
    }</p>
<p>    /**<br />
     * Instantiates a Predicate with the given boolean value and test-/decorate<br />
     * column index in model coordinates. A column index of -1 is interpreted<br />
     * as "all". (PENDING: search forum for the exact definition, legacy<br />
     * base pattern and search behave differently?)<br />
     *<br />
     * @param testCondition the boolean value to test the cell value against<br />
     * @param testColumn the column index of the cell which contains the value<br />
     *   to test against the pattern<br />
     * @param decorateColumn the column index of the cell which should be<br />
     *   decorated if the test against the value succeeds.<br />
     */<br />
    public BooleanPredicate(boolean testCondition, int testColumn, int decorateColumn) {<br />
        this.testCondition = testCondition;<br />
        this.testColumn = testColumn;<br />
        this.highlightColumn = decorateColumn;<br />
    }</p>
<p>    public boolean isHighlighted(Component renderer, ComponentAdapter adapter) {<br />
        if (isHighlightCandidate(renderer, adapter)) {<br />
            return test(renderer, adapter);<br />
        }<br />
        return false;<br />
    }</p>
<p>    /**<br />
     * Test the value. This is called only if the<br />
     * pre-check returned true, because accessing the<br />
     * value might be potentially costly<br />
     * @param renderer<br />
     * @param adapter<br />
     * @return<br />
     */<br />
    private boolean test(Component renderer, ComponentAdapter adapter) {<br />
        if (!adapter.isTestable(testColumn)) {<br />
            return false;<br />
        }<br />
        Object value = adapter.getValue(testColumn);</p>
<p>        return (value instanceof Boolean) && ((Boolean) value).booleanValue() == testCondition;</p>
<p>    }</p>
<p>    /**<br />
     * A quick pre-check.<br />
     *<br />
     * @param renderer<br />
     * @param adapter<br />
     * @return<br />
     */<br />
    private boolean isHighlightCandidate(Component renderer, ComponentAdapter adapter) {<br />
        return ((highlightColumn < 0) || (highlightColumn == adapter.viewToModel(adapter.column)));<br />
    }</p>
<p>    /**<br />
     *<br />
     * @return returns the column index to decorate (in model coordinates)<br />
     */<br />
    public int getHighlightColumn() {<br />
        return highlightColumn;<br />
    }</p>
<p>    /**<br />
     *<br />
     * @return returns the boolean value to test the cell value against<br />
     */<br />
    public boolean getTestCondition() {<br />
        return testCondition;<br />
    }</p>
<p>    /**<br />
     *<br />
     * @return the column to use for testing (in model coordinates)<br />
     */<br />
    public int getTestColumn() {<br />
        return testColumn;<br />
    }<br />
}</p>
<p>

Reply viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
alavideo
Offline
Joined: 2008-03-31
Points: 0

Thank you downingland for pointing me in this direction. I was able to put this information together and highlight just like I need. In case someone else is looking for a similar solution, here is what I did:

[code]

HighlightPredicate negativeNumberPredicate = new HighlightPredicate() {

public boolean isHighlighted(Component arg0, ComponentAdapter arg1) {
Object value = arg1.getValue();
return (value instanceof Number) && ((Number) value).doubleValue() < 0;
}
};

HighlightPredicate positiveNumberPredicate = new HighlightPredicate() {

public boolean isHighlighted(Component arg0, ComponentAdapter arg1) {
Object value = arg1.getValue();
return (value instanceof Number) && ((Number) value).doubleValue() > 0;
}
};

HighlightPredicate.ColumnHighlightPredicate numberColumnPredicate =
new HighlightPredicate.ColumnHighlightPredicate(myTable.getColumn("Col1").getModelIndex(),
myTable.getColumn("Col2").getModelIndex());

ColorHighlighter negativeNumberHighlighter =
new ColorHighlighter(new AndHighlightPredicate(negativeNumberPredicate, numberColumnPredicate), null, Color.RED);
ColorHighlighter positiveNumberHighlighter =
new ColorHighlighter(new AndHighlightPredicate(positiveNumberPredicate, numberColumnPredicate), null, DARK_GREEN);

myTable.addHighlighter(HighlighterFactory.createSimpleStriping(HighlighterFactory.GENERIC_GRAY));
myTable.addHighlighter(negativeNumberHighlighter);
myTable.addHighlighter(positiveNumberHighlighter);

int alertCol = myTable.getColumn("Alerts").getModelIndex();
BooleanPredicate alertPredicate = new BooleanPredicate(true, alertCol, -1);
ColorHighlighter alertHighlighter = new ColorHighlighter(alertPredicate, Color.RED, Color.WHITE);
myTable.addHighlighter(alertHighlighter);

[/code]

Thanks again!

Jim

alavideo
Offline
Joined: 2008-03-31
Points: 0

I think I have found what's causing the highlighter issue described in my earlier post. It has to do with the order in which the highlighters are added as well as to which component the highlighter was added (i.e., the table or the column).

As with most (all?) the examples I have seen on how to display negative numbers in red, positive in green, I created my own "ColorHighlighter" and then applied the highlighter to the column (using TableColumnExt.addHighlighter...). It seems that column highlighters are evaluated/applied/rendered after all the table highlighters have been rendered.

Since my BooleanPredicate highlighter is applied to the table, it was rendered first. If the boolean condition was true, the row background turned red. Then the column highlighter was evaluated and, if the value was negative, was applied turning the foreground red.

If I change my code so that the highlighters are added in the following order and the ColorHighlighter is applied to the [i]table[/i] instead of the column, the highlighting works fine. Example:

[code]
HighlightPredicate negativeNumberPredicate = new HighlightPredicate() {
public boolean isHighlighted(Component arg0, ComponentAdapter arg1) {
Object value = arg1.getValue();
return (value instanceof Number) && ((Number) value).doubleValue() < 0;
}
};

HighlightPredicate positiveNumberPredicate = new HighlightPredicate() {
public boolean isHighlighted(Component arg0, ComponentAdapter arg1) {
Object value = arg1.getValue();
return (value instanceof Number) && ((Number) value).doubleValue() > 0;
}
};

ColorHighlighter redHighlighter = new ColorHighlighter(negativeNumberPredicate, null, Color.RED);
ColorHighlighter darkGreenHighlighter = new ColorHighlighter(positiveNumberPredicate, null, DARK_GREEN);
myTable.addHighlighter(HighlighterFactory.createSimpleStriping(HighlighterFactory.GENERIC_GRAY));
myTable.addHighlighter(redHighlighter);
myTable.addHighlighter(darkGreenHighlighter);

int alertCol = myTable.getColumn("Alerts").getModelIndex();
BooleanPredicate alertPredicate = new BooleanPredicate(true, alertCol,-1);
ColorHighlighter alertHighlighter = new ColorHighlighter(alertPredicate, Color.RED, Color.WHITE);
myTable.addHighlighter(alertHighlighter);

[/code]

The problem is I don't want all number columns to turn green/red - just certain ones.

I have only used the HighlighterPredicate on JXTable, but I think it can be used on other components like JTree. I'm not sure how useful it would be for other components, but for tables, it would be useful to include "target column" and "test column" logic (as in PatternPredicate) in the default HighlighterPredicate implementation.

Regards,
Jim

downingland
Offline
Joined: 2008-05-27
Points: 0

You can "reach" into the component adapter to get additional information. I have a highlighter like:

[code]

public class SelectedRowHighlightPredicate implements HighlightPredicate {

@Override
public boolean isHighlighted(Component renderer, ComponentAdapter adapter) {
if ( adapter.getComponent() instanceof JTable) {
JTable table = (JTable) adapter.getComponent();
int row = table.getSelectedRow();
if ( row == -1) return false;
if (row == adapter.row) return true;
}
return false;
}
}

[/code]

You can pick up the other info you need to be more selective in the highlights. You should be able to use the adapter.column to see what column is being rendered and add that in the highlight logic.

alavideo
Offline
Joined: 2008-03-31
Points: 0

I was not aware of that. I guess this another reason to allow you to instantiate a HighlightPredicate with a target/test column. This would allow you to reference and compare the adapter col/row to them.

Actually, the more I think about how I'm currently using the ColorHighlighter, having the ability to include a single test column/target column parm in HighlightPredicate would add unnecessary overhead. The way it works now, I create a single "negative (red)" number ColorHighlighter and a single "positive (green)" number ColorHighlighter. Those two instances are then added to each column I want to apply the highlighter. If a target/test column was on a HighlightPredicate, I would have to instantiate new HighlightPredicates for each column, no? Perhaps a better solution would be to pass in a List of column indexes.

Then isHighlighted method of the HighlightPredicate could be something like this:

[code]
public boolean isHighlighted(Component arg0, ComponentAdapter arg1) {
Object value = arg1.getValue();
if (columnList != null && columnList.contains(arg1.viewToModel(arg1.column)) ) {
return (value instanceof Number) && ((Number) value).doubleValue() > 0;
}
return false;
}

[/code]

This way I could just create one for red, one for green.

Jim

downingland
Offline
Joined: 2008-05-27
Points: 0

I was looking at some other uses I have of highlighters and I think what you origianly set out to do could be done with with your negitive/positive highlights and useing the "andpredicate" and columnhighlight predicate

maybe like:
[code]
HighlightPredicate negativeNumberPredicate = new HighlightPredicate() {
public boolean isHighlighted(Component arg0, ComponentAdapter arg1) {
Object value = arg1.getValue();
return (value instanceof Number) && ((Number) value).doubleValue() < 0;
}
};

HighlightPredicate positiveNumberPredicate = new HighlightPredicate() {
public boolean isHighlighted(Component arg0, ComponentAdapter arg1) {
Object value = arg1.getValue();
return (value instanceof Number) && ((Number) value).doubleValue() > 0;
}
};

ColorHighlighter redHighlighter = new ColorHighlighter(negativeNumberPredicate, null, Color.RED);
ColorHighlighter darkGreenHighlighter = new ColorHighlighter(positiveNumberPredicate, null, DARK_GREEN);
myTable.addHighlighter(HighlighterFactory.createSimpleStriping(HighlighterFactory.GENERIC_GRAY));
myTable.addHighlighter(new AndHighlightPredicate(redHighlighter, new ColumnHighlightPredicate(myTable.getColumn("Alerts").getModelIndex()));
myTable.addHighlighter(new AndHighlightPredicate(darkGreenHighlighter), new ColumnHightLightPredicate(myTable.getColumn("Alerts").getModelIndex())));

[/code]

I didn't specifcally test this, but may be something to look at. But there is "and", "or", "not" predicates for combining tests.

alavideo
Offline
Joined: 2008-03-31
Points: 0

Ahhh! I think that is getting closer to what I want. I would still need to create a ColumnHighlightPredicate for for each number column I want to apply the red/green highlight to, then repeat the AndHighlighPredicate(red...)/AndHighlightPredicate(darkGreen....) for each column.

Question, is ColumnHighlightPredicate part of the org.jdesktop package? If this does work, it would be nice if we could pass in a List of column indexes to the ColumnHighlightPredicate. I have not looked closely at these classes, but I'm sure there is a simple/elegant solution to be found with the current code.

Thanks for your help!!

Jim

Message was edited by: alavideo

Looking closer at the AndHighlightPredicate I see I can pass in all the column highlightPredicates at the same time. Now to find where the ColumnHighlightPredicate is.... :-)

Message was edited by: alavideo

crud....passing in all column at the same time won't work. I need something like (redhighlight AND (col1 or col2 or col3....) I see there is also an OrHighlightPredicate. I think things would start to get rather complicated trying to add And and Or predicates all together. It still sounds like the simplest would be to allow a List of columns to be added to the HighlightPredicate so the isHighlighted method could reference it.

ok, I see ColumnHighlightPredicate is an inner class within HighlightPredicate and you can pass in multiple indexes! I think you have pointed me in the right direction.

Message was edited by: alavideo

downingland
Offline
Joined: 2008-05-27
Points: 0

Its in org.jdesktop.swingx.decorator

Source at src/java\org/jdesktop/swingx/decorator/HighlightPredicate.java

alavideo
Offline
Joined: 2008-03-31
Points: 0

I was on the same trail . :-)