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’
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]