Writing a customizer

I need to write a customizer for a dataset that includes properties such as color, border etc.

As an example, Ignition uses a text string to store the color values and border values. What class/classes do I use to translate from the text string that is stored to a java Color and from text string to a java Border?
Using regex, it is simple to write a converter from a string such as “color(200,250,210,250)” to an instance of a color object. However, the conversion from the text border description is a bit more complex. Besides, I should use code that is already available.

What classes support the development of the customizer itself? How do I learn how to use them?

Getting a customizer to display is easy enough. Albeit, I wondering if there is any special magic to setting the values of the following fields:

[code] public static final String VALUE_ACCEL_KEYSTROKE = “c”;
public static final String VALUE_COMMAND = “MyComponent Customizer”;

public static final Integer	VALUE_MNEMONIC = 1; 
public static final String	VALUE_NAME = "MyComponentCustomizer"; 
public static final String	VALUE_SHORT_DESCRIPTION = "Opens the customizer for the selected MyComponent"; 
public static final CustomizerDescriptor	VALUE_DESCRIPTOR
                        = new CustomizerDescriptor(
                                MyComponentCustomizer.class
                            , VALUE_NAME
                            , VALUE_COMMAND
                            , VALUE_MNEMONIC
                            , VALUE_SHORT_DESCRIPTION
                            , VALUE_ACCEL_KEYSTROKE);

[/code]

In particular, is there any special importance to the value of “VALUE_MNEMONIC”?
Accelerator keystroke doesn’t do what I expect, namely the short cut for the menu item to invoke the customizer.
I also am unable to set a title for the Customizer panel when it is displayed.
The VALUE_COMMAND appears to be the value displayed as the menu item and the SHORT_DESCRIPTION appears to be the tool tip in said menu.

It gets passed to a Java Action associated with the customizer. We tend to use null, and you’re certainly welcome to as well. If you want to use a value, use a value that would map to a character. One way to do it is KeyEvent.VK_1 KeyEvent.VK_1 is just a constant, but the intent is easier to read in the code than using ‘49’ :slight_smile:

I found a class called SwingColorEditor that appears close to what I need with respect to allowing the user to select colors. When used in the code presented below, it gives the following display:


However, I think for a customizer the following is more appropriate and I suspect I need a different class than the SwingColorEditor to achieve it:


Furthermore, I’m not clear how to interface SwingColorEditor with a TableCellEditor. Here is how I’ve used it in the TableCellRenderer:

[code]private class JJColorRenderer extends SwingColorEditor
implements TableCellRenderer {
boolean isBordered = true;

    public JJColorRenderer(boolean isBordered) {
        this.isBordered = isBordered;
        setOpaque(true); //MUST do this for background to show up.
    }

    @Override
    public Component getTableCellRendererComponent(
                            JTable table, Object color,
                            boolean isSelected, boolean hasFocus,
                            int row, int column) {
        Color newColor = parseColor((String)color);
        setValue(newColor);

// some stuff manipulating the border is removed for clarity

        return this.getCustomEditor();
    }
}[/code]

My current implementation of the TableCellEditor brings up a dialog with JColorChooser. It allows the user to select a color, but it is hardly in keeping with the Ignition look and feel.

The following works to a reasonable extent as a table cell editor for Colors. It gives a null pointer exception when a value is selected from the color wheel, suggesting that the SwingColorEditor instance is not quite initialized properly. Perhaps a call to .initComponents is in order. By applying the principle of symmetry, I figure that SwingBorderEditor and SwingIconEditor would be the classes I need for the Border columns and Icon columns respectively.

[code] private class ColorEditor extends AbstractCellEditor
implements TableCellEditor,
PropertyChangeListener {
Color currentColor;
String currentColorString;
SwingColorEditor swingcoloreditor;;

    public ColorEditor() {
        swingcoloreditor = new SwingColorEditor();
// setting BindableEnabled to false removes the Binding icon from the editor
        swingcoloreditor.setBindableEnabled(false);
// Need to catch the property change event to get the newly selected value
        swingcoloreditor.addPropertyChangeListener(this);
    }

    //Implement the one CellEditor method that AbstractCellEditor doesn't.
    @Override
    public Object getCellEditorValue() {
        return currentColorString;
    }

    //Implement the one method defined by TableCellEditor.
    @Override
    public Component getTableCellEditorComponent(JTable table,
                                                 Object value,
                                                 boolean isSelected,
                                                 int row,
                                                 int column) {
        currentColorString = (String)value;
        currentColor = parseColor(currentColorString);
        swingcoloreditor.setValue(currentColor);
        return swingcoloreditor.getCustomEditor();
    }

    @Override
    public void propertyChange(PropertyChangeEvent evt) {
        currentColor = (Color) swingcoloreditor.getValue();
        currentColorString = String.format(
                "color(%d,%d,%d,%d)"
                ,currentColor.getRed()
                ,currentColor.getGreen()
                ,currentColor.getBlue()
                ,currentColor.getAlpha()
                );
    }     
}[/code]

FYI, here is the error message that I get when I select a value from the color wheel

[quote]Exception in thread “AWT-EventQueue-2” java.lang.NullPointerException
at com.inductiveautomation.factorypmi.designer.property.editors.bb.SwingColorEditor.setValue(SwingColorEditor.java:164)
at com.inductiveautomation.factorypmi.designer.property.editors.bb.SwingColorEditor$2.stateChanged(SwingColorEditor.java:91)
at com.inductiveautomation.ignition.client.util.gui.color.ColorEditor.fireChange(ColorEditor.java:340)
at com.inductiveautomation.ignition.client.util.gui.color.ColorEditor.setSelectedColor(ColorEditor.java:309)
at com.inductiveautomation.ignition.client.util.gui.color.ColorEditor$2.stateChanged(ColorEditor.java:252)
at javax.swing.colorchooser.DefaultColorSelectionModel.fireStateChanged(DefaultColorSelectionModel.java:149)
at javax.swing.colorchooser.DefaultColorSelectionModel.setSelectedColor(DefaultColorSelectionModel.java:99)[/quote]
Despite the message, the selected value is passed through correctly to my table

Looks like perhaps I should be using the classes ColorEditor.ColorTableCellEditor and ColorEditor.ColorTableCellRenderer for processing my colors. It will take a touch of refactoring.

Yes indeed, this is much better

jtable.setDefaultRenderer(Color.class, new ColorEditor.ColorTableCellRenderer()); jtable.setDefaultEditor(Color.class, ColorEditor.createTableCellEditor());
No need for me to set up a custom renderer or cell editor. Nor are there any mysterious null pointer exceptions.

Unfortunately, I cannot find similar classes for Borders.

I’ve had no luck trying to find a class that supports translating a java Border to a string and back again. I can write my own that would support translating the simpler borders from string to border. However, I’ve no idea how to access the details of the border to write a translator from border to string. Any help would be most appreciated.

Yahoo tells me today that I’ve become the three eyed raven and I can no longer depend on Hodor.
Apparently Aesop did write:
So quando a gente se dedica pessoalmente ao trabalho, sem esperar ajuda alheia, ele vai para a frente.

So, this will have to do for the moment.

[code]public class BorderTranslator {
public static String toString(Border b) {
// translates border to a human readable form
if (b == null) {
return “border(empty)”;
}
if (b.getClass().equals( EmptyBorder.class)) {
return “border(empty)”;

    } else if (b.getClass().equals( EtchedBorder.class)) {
        EtchedBorder etchB = (EtchedBorder) b;
        int type = etchB.getEtchType();
        return String.format("border(etched;%d)",type);
        
    } else if (b.getClass().equals( BevelBorder.class)) {
        BevelBorder bevelB = (BevelBorder) b;
        int type = bevelB.getBevelType();
        return String.format("border(bevel;%d)",type);
        
    } else if (b.getClass().equals( BasicBorders.ButtonBorder.class)) {
        return String.format("border(button)");
        
    } else if (b.getClass().equals( BasicBorders.FieldBorder.class)) {
        return String.format("border(field)");
        
    } else if (b.getClass().equals( LineBorder.class)) {
        LineBorder lineB = (LineBorder) b;
        int type = lineB.getThickness();
        Color col = lineB.getLineColor();
        return String.format("border(line;%s;%d)", toString(col), type);
        
    } else if (b.getClass().equals( MatteBorder.class)) {
        MatteBorder matteB = (MatteBorder) b;
        Insets insets = matteB.getBorderInsets();
        Color col = matteB.getMatteColor();
        return String.format("border(matte;%s;%d;%d;%d;%d)"
                , toString(col)
                , insets.top,  insets.left
                , insets.bottom, insets.right);
    }
    return "border(empty)";     // for an unrecognized border make it an empty one
}
    
public static String toString(Color val) {
    return String.format(
                "color(%d,%d,%d,%d)"
                ,val.getRed()
                ,val.getGreen()
                ,val.getBlue()
                ,val.getAlpha()
                );
}

public Border toBorder(String s) {
    
    return BorderFactory.createEmptyBorder();
}

}[/code]

The following works for the moment to parse the border descriptor string into a Border object instance:

[code]public static Border toBorder(String s) {

    if (s.length() < 8) {
        return BorderFactory.createEmptyBorder();
    }
    String arglist = s.substring(7, s.length()-1);        // strip leading and trailing syntax "border(" and ")"
    String[] args = arglist.split(";");                   // get the arguments
    
    if ("empty".equalsIgnoreCase(args[0])) {
        return BorderFactory.createEmptyBorder();
        
    } else if ("etched".equalsIgnoreCase(args[0])) {
        return BorderFactory.createEtchedBorder(Integer.parseInt(args[1]));
        
    } else if ("bevel".equalsIgnoreCase(args[0])) {
        int i = Integer.parseInt(args[1]);
        if ( i == 1010 ) {
            return BorderFactory.createCompoundBorder(
                    BorderFactory.createBevelBorder(0)
                    ,BorderFactory.createBevelBorder(1)
            );
        }
        return BorderFactory.createBevelBorder(i);
    
    } else if ("field".equalsIgnoreCase(args[0])) {
        return new FieldBorder();
    
    } else if ("button".equalsIgnoreCase(args[0])) {
        return new ButtonBorder();        
   
    } else if ("line".equalsIgnoreCase(args[0])) {
        return createLineBorder(args);
        
    } else if ("matte".equalsIgnoreCase(args[0])) {
        return createMatteBorder(args);        
    }
    return BorderFactory.createEmptyBorder();
}[/code]