/*
* Geotoolkit.org - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2003-2012, Open Source Geospatial Foundation (OSGeo)
* (C) 2009-2012, Geomatys
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.geotoolkit.gui.swing;
import java.util.Map;
import java.util.Date;
import java.util.HashMap;
import java.util.ResourceBundle;
import java.util.MissingResourceException;
import java.util.StringTokenizer;
import java.util.Objects;
import java.lang.reflect.Array;
import java.text.NumberFormat;
import java.text.DateFormat;
import java.text.Format;
import java.awt.*;
import javax.swing.*;
import javax.swing.table.TableModel;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.AbstractTableModel;
import java.awt.geom.AffineTransform;
import java.awt.image.ColorModel;
import java.awt.image.DataBuffer;
import javax.media.jai.util.Range;
import javax.media.jai.KernelJAI;
import javax.media.jai.LookupTableJAI;
import javax.media.jai.OperationNode;
import javax.media.jai.OperationDescriptor;
import javax.media.jai.PerspectiveTransform;
import javax.media.jai.ParameterListDescriptor;
import javax.media.jai.RegistryElementDescriptor;
import org.apache.sis.measure.Angle;
import org.apache.sis.measure.AngleFormat;
import org.apache.sis.util.logging.Logging;
import org.apache.sis.util.Classes;
import org.geotoolkit.gui.swing.image.KernelEditor;
import org.geotoolkit.internal.swing.SwingUtilities;
import org.geotoolkit.resources.Vocabulary;
import static java.awt.GridBagConstraints.*;
import static org.apache.sis.util.Numbers.*;
import org.geotoolkit.image.internal.Adapters;
/**
* An editor for arbitrary parameter object. The parameter value can be any {@link Object}.
* The editor content will changes according the parameter class. For example, the content
* will be a {@link KernelEditor} if the parameter is an instance of {@link KernelJAI}.
* Currently supported parameter type includes:
* <p>
* <ul>
* <li>Individual {@linkplain String string}, {@linkplain Number number}, {@linkplain Date date}
* or {@linkplain Angle angle}.</li>
* <li>Table of any primitive type ({@code int[]}, {@code float[]}, etc.).</li>
* <li>Matrix of any primitive type ({@code int[][]}, {@code float[][]}, etc.).</li>
* <li>JAI {@linkplain LookupTableJAI lookup table}, which are display in tabular format.</li>
* <li>{@linkplain AffineTransform Affine transform} and {@linkplain PerspectiveTransform
* perspective transform}, which are display like a matrix.</li>
* <li>Convolution {@linkplain KernelJAI kernel}, which are display in a {@link KernelEditor}.</li>
* </ul>
*
* @author Martin Desruisseaux (IRD)
* @version 3.12
*
* @see org.geotoolkit.gui.swing.image.KernelEditor
* @see org.geotoolkit.gui.swing.image.ImageProperties
* @see org.geotoolkit.gui.swing.image.OperationTreeBrowser
*
* @since 2.0
* @module
*
* @todo This class do not yet support the edition of parameter value.
*/
@SuppressWarnings("serial")
public class ParameterEditor extends JComponent {
/** Key for {@link String} node. */ private static final String STRING = "String";
/** Key for {@link Boolean} node. */ private static final String BOOLEAN = "Boolean";
/** Key for {@link Number} node. */ private static final String NUMBER = "Number";
/** Key for {@link Angle} node. */ private static final String ANGLE = "Angle";
/** Key for {@link Date} node. */ private static final String DATE = "Date";
/** Key for {@link KernelJAI} node. */ private static final String KERNEL = "Kernel";
/** Key for any kind of table node. */ private static final String TABLE = "Table";
/** Key for unrecognized types. */ private static final String DEFAULT = "Default";
/**
* The set of {@linkplain Component component} editors created up to date.
*/
private final Map<String,Component> editors = new HashMap<>();
/**
* The properties panel for parameters. The content for this panel
* depends on the selected item, but usually includes the following:
* <p>
* <ul>
* <li>A {@link JTextField} for simple parameters (numbers, string, etc.)</li>
* <li>A {@link JList} for enumerated parameters.</li>
* <li>A {@link JTable} for any kind of array parameter and {@link LookupTableJAI}.</li>
* <li>A {@link KernelEditor} for {@link KernelJAI} parameters.</li>
* </ul>
*/
private final Container cards = new JPanel(new CardLayout());
/**
* The label for parameter or image description.
* Usually displayed on top of parameter editor.
*/
private final JLabel description = new JLabel(" ", JLabel.CENTER);
/**
* The current value in the process of being edited. This object is usually an instance of
* {@link Number}, {@link KernelJAI}, {@link LookupTableJAI} or some other parameter object.
*
* @see #setParameterValue
*/
private Object value;
/**
* The editor widget currently in use.
*
* @see #setParameterValue
* @see #getEditor
*/
private Component editor;
/**
* The editor model currently in use. This is often the model used by the editor widget.
*/
private Editor model;
/**
* {@code true} if this widget is editable.
*/
private static final boolean editable = false;
/**
* Constructs an initially empty parameter editor.
*/
public ParameterEditor() {
setLayout(new BorderLayout());
description.setBorder(
BorderFactory.createCompoundBorder(description.getBorder(),
BorderFactory.createCompoundBorder(
BorderFactory.createCompoundBorder(
BorderFactory.createEmptyBorder(6, 9, 6, 9),
BorderFactory.createLineBorder(description.getForeground())),
BorderFactory.createEmptyBorder(6, 0, 6, 0))));
add(description, BorderLayout.NORTH );
add(cards, BorderLayout.CENTER);
setPreferredSize(new Dimension(400,250));
}
/**
* Returns the parameter value currently edited, or {@code null} if none.
*
* @return the parameter value currently edited, or {@code null}.
*/
public Object getParameterValue() {
return (model != null) ? model.getValue() : value;
}
/**
* Sets the value to edit. The editor content will be updated according the value type.
* For example if the value is an instance of {@link KernelJAI}, then the editor content
* will be changed to a {@link KernelEditor}.
*
* @param value The value to edit. This object is usually an instance of {@link Number},
* {@link KernelJAI}, {@link LookupTableJAI} or some other parameter object.
*/
public void setParameterValue(final Object value) {
final Object oldValue = this.value;
if (!Objects.deepEquals(value, oldValue)) {
this.value = value;
updateEditor();
firePropertyChange("value", oldValue, value);
}
}
/**
* Returns the description currently shown, or {@code null} if none. This is usually a short
* description of the parameter being edited. The string may contain simple HTML tags.
*
* @return The description currently shown, or {@code null}.
*/
public String getDescription() {
String text = description.getText();
if (text != null) {
text = text.trim();
if (text.isEmpty()) {
text = null;
}
}
return text;
}
/**
* Sets the description to shown. This is usually a short description of the parameter
* being edited. Simple HTML tags are allowed since the description will be rendered
* with {@link JLabel}, which allows such tags.
*
* @param description The description to be shown.
*/
public void setDescription(String description) {
if (description == null || description.isEmpty()) {
description = " ";
}
this.description.setText(description);
if (model != null) {
model.setValueRange(null, null);
}
}
/**
* Convenience method for setting the parameter description from a JAI operation node. This
* method fetches the description from the {@linkplain OperationDescriptor operation descriptor}
* associated with the given node, if any. Then it invokes {@link #setDescription(String)} with
* the description found, or a description built from other informations like the parameter type
* if no explicit description was found.
*
* @param operation The operation node for the current parameter.
* @param index The parameter index, or {@code -1} if unknown.
*
* @since 2.3
*/
public void setDescription(final OperationNode operation, final int index) {
String description = null;
Class<?> type = null;
Range range = null;
if (operation != null) {
final String name, mode;
final RegistryElementDescriptor element;
final ParameterListDescriptor param;
name = operation.getOperationName();
mode = operation.getRegistryModeName();
element = operation.getRegistry().getDescriptor(mode, name);
param = element.getParameterListDescriptor(mode);
/*
* If a parameter is specified, gets the parameter type and its range of valid
* values.
*/
String pname = null;
if (index >= 0 && index < param.getNumParameters()) {
pname = param.getParamNames()[index];
type = param.getParamClasses()[index];
range = param.getParamValueRange(param.getParamNames()[index]);
}
/*
* If the descriptor is an operation, gets the localized operation
* description or the parameter description.
*/
if (element instanceof OperationDescriptor) {
final String key;
final OperationDescriptor descriptor = (OperationDescriptor) element;
final ResourceBundle resources = descriptor.getResourceBundle(getLocale());
if (index >= 0) {
key = "arg" + index + "Desc";
} else {
key = "Description";
}
try {
description = resources.getString(key);
} catch (MissingResourceException ignore) {
// No description for this parameter. Try a global description.
try {
description = resources.getString("Description");
} catch (MissingResourceException exception) {
/*
* No description at all for this operation. Not a big deal;
* just left the description empty. Log the exception with a
* low level, since this warning is not really important. The
* level is slightly higher than in 'RegisteredOperationBrowser'
* since we have tried the global operation description as well.
*/
Logging.recoverableException(null, ParameterEditor.class, "setDescription", exception);
}
}
}
/*
* Concatenates the parameter name and description as a HTML string.
* This block can be disabled if only the description as plain text is wanted.
*/
if (true) {
final StringBuilder html = new StringBuilder("<html><center><b>").append(name);
if (pname != null) {
html.append(' ').append(pname);
}
html.append("</b>");
if (description != null) {
html.append("<br>").append(description);
}
description = html.append("</center></html>").toString();
}
}
setDescription(description);
if (model != null) {
model.setValueRange(type, range);
}
}
/**
* Returns the component used for editing the parameter. The component class depends on the
* class of the value set by the last call to {@link #setParameterValue}. The editor may be
* an instance of {@link KernelEditor}, {@link JTable}, {@link JTextField}, {@link JList} or
* any other suitable component.
*
* @return The editor, or {@code null} if no value has been set.
*/
public Component getEditor() {
return editor;
}
/**
* Returns the editor for the given name. If an editor is found, it will be bring
* on top of the card layout (i.e. will become the visible editor). Otherwise, this
* method returns {@code null}.
*
* @param name The editor name. Should be one of {@link #NUMBER}, {@link #KERNEL} and
* similar constants.
* @return The editor, or {@code null}.
*/
private Component getEditor(final String name) {
final Component panel = editors.get(name);
((CardLayout) cards.getLayout()).show(cards, name);
return panel;
}
/**
* Adds the specified editor. No editor must exists for the specified name prior to this
* call. The editor will be bring on top of the card layout (i.e. will become the visible
* panel).
*
* @param name The editor name. Should be one of {@link #NUMBER}, {@link #KERNEL} and
* similar constants.
* @param editor The editor.
* @param scroll {@code true} if the editor should be wrapped into a {@link JScrollPane}
* prior its addition to the container.
*/
private void addEditor(final String name, Component editor, final boolean scroll) {
if (editors.put(name, editor) != null) {
throw new IllegalStateException(name); // Should not happen.
}
if (scroll) {
editor = new JScrollPane(editor);
}
cards.add(editor, name);
((CardLayout) cards.getLayout()).show(cards, name);
}
/**
* Updates the editor according the current {@link #value}. If a suitable editors already
* exists for the value class, it will be reused. Otherwise, a new editor will be created
* on the fly.
*
* The {@link #editor} field will be set to the component used for editing the parameter.
* This component may be an instance of {@link KernelEditor}, {@link JTable},
* {@link JTextField}, {@link JList} or any other suitable component.
*
* The {@link #model} field will be set to the model used by the editor widget.
*/
@SuppressWarnings("fallthrough")
private void updateEditor() {
Object value = this.value;
/*
* In the special case where the value is an array with only one element, extract
* the element and use a specialized editor as if the element wasn't in an array.
*/
while (value != null && value.getClass().isArray() && Array.getLength(value) == 1) {
value = Array.get(value, 0);
}
/*
* String --- Uses a JTextField editor.
*/
if (value instanceof String) {
Singleton editor = (Singleton) getEditor(STRING);
if (editor == null) {
editor = new Singleton(null);
addEditor(STRING, editor, false);
}
editor.setValue(value);
this.editor = editor.field;
this.model = editor;
return;
}
/*
* Boolean --- Uses a JTextField editor.
*/
if (value instanceof Boolean) {
Singleton editor = (Singleton) getEditor(BOOLEAN);
if (editor == null) {
editor = new Singleton(null); // TODO: we should define some kind of BooleanFormat.
addEditor(BOOLEAN, editor, false);
}
editor.setValue(value);
this.editor = editor.field;
this.model = editor;
return;
}
/*
* Number --- Uses a JFormattedTextField editor.
*/
if (value instanceof Number) {
Singleton editor = (Singleton) getEditor(NUMBER);
if (editor == null) {
editor = new Singleton(NumberFormat.getInstance(getLocale()));
addEditor(NUMBER, editor, false);
}
editor.setValue(value);
this.editor = editor.field;
this.model = editor;
return;
}
/*
* Date --- Uses a JFormattedTextField editor.
*/
if (value instanceof Date) {
Singleton editor = (Singleton) getEditor(DATE);
if (editor == null) {
editor = new Singleton(DateFormat.getDateTimeInstance(
DateFormat.LONG, DateFormat.LONG, getLocale()));
addEditor(DATE, editor, false);
}
editor.setValue(value);
this.editor = editor.field;
this.model = editor;
return;
}
/*
* Angle --- Uses a JFormattedTextField editor.
*/
if (value instanceof Angle) {
Singleton editor = (Singleton) getEditor(ANGLE);
if (editor == null) {
editor = new Singleton(AngleFormat.getInstance(getLocale()));
addEditor(ANGLE, editor, false);
}
editor.setValue(value);
this.editor = editor.field;
this.model = editor;
return;
}
/*
* AffineTransform --- converts to a matrix for processing by the general matrix case.
*/
if (value instanceof AffineTransform) {
final AffineTransform transform = (AffineTransform) value;
value = new double[][] {
{transform.getScaleX(), transform.getShearX(), transform.getTranslateX()},
{transform.getShearY(), transform.getScaleY(), transform.getTranslateY()},
{0, 0, 1}
};
}
/*
* PerspectiveTransform --- converts to a matrix for processing by the general matrix case.
*/
if (value instanceof PerspectiveTransform) {
final double[][] matrix = new double[3][3];
((PerspectiveTransform) value).getMatrix(matrix);
value = matrix;
}
/*
* Any table or matrix --- uses a JTable editor.
*/
if (value != null) {
final Class<?> elementClass = value.getClass().getComponentType();
if (elementClass != null) {
final TableModel model;
if (elementClass.isArray()) {
model = new Matrix((Object[]) value);
} else {
model = new Table(new Object[] {value}, 0, 0);
}
JTable editor = (JTable) getEditor(TABLE);
if (editor == null) {
editor = new NumberedTable(model);
addEditor(TABLE, editor, true);
} else {
editor.setModel(model);
}
this.editor = editor;
this.model = (Editor) model;
return;
}
}
/*
* LookupTableJAI --- Uses a JTable editor.
*/
if (value instanceof LookupTableJAI) {
final LookupTableJAI table = (LookupTableJAI) value;
final Object[] data;
int mask = 0;
switch (table.getDataType()) {
case DataBuffer.TYPE_BYTE: data = table.getByteData(); mask=0xFF; break;
case DataBuffer.TYPE_USHORT: mask = 0xFFFF; // Fall through
case DataBuffer.TYPE_SHORT: data = table.getShortData(); break;
case DataBuffer.TYPE_INT: data = table.getIntData(); break;
case DataBuffer.TYPE_FLOAT: data = table.getFloatData(); break;
case DataBuffer.TYPE_DOUBLE: data = table.getDoubleData(); break;
default: this.editor=null; this.model=null; return;
}
final Table model = new Table(data, table.getOffset(), mask);
JTable editor = (JTable) getEditor(TABLE);
if (editor == null) {
editor = new NumberedTable(model);
addEditor(TABLE, editor, true);
} else {
editor.setModel(model);
}
this.editor = editor;
this.model = model;
return;
}
/*
* KernelJAI --- Uses a KernelEditor.
*/
if (value instanceof KernelJAI) {
KernelEditor editor = (KernelEditor) getEditor(KERNEL);
if (editor == null) {
editor = new KernelEditor();
editor.addDefaultKernels();
addEditor(KERNEL, editor, false);
}
editor.setKernel((KernelJAI) value);
this.editor = editor;
this.model = null; // TODO: Set the editor.
return;
}
/*
* Default case --- Uses a JTextArea
*/
JTextArea editor = (JTextArea) getEditor(DEFAULT);
if (editor == null) {
editor = new JTextArea();
editor.setEditable(false);
editor.setFont(Font.decode("Monospaced"));
addEditor(DEFAULT, editor, true);
}
String text = String.valueOf(value);
if (text.indexOf('\n') < 0 && text.indexOf('\r') < 0 && text.indexOf(' ') >= 0) {
if (value instanceof ColorModel) {
text = multilines(text);
}
}
editor.setText(text);
this.editor = editor;
this.model = null; // TODO: Set the editor.
}
/**
* Transforms the given single line into a multilines text. This method expects a
* string having the following pattern:
*
* {@preformat text
* <optional header:> key1 = value1 key2 = value2 key3 = value3 ...
* }
*
* Each key-value pair will be formatted on its own line.
*/
private static String multilines(final String text) {
int splitAt = text.indexOf(':') + 1;
final StringBuilder buffer = new StringBuilder(splitAt).append(text, 0, splitAt);
final StringTokenizer tk = new StringTokenizer(text.substring(splitAt));
int state = 0; // 0=new line, 1=key, 2=value.
while (tk.hasMoreTokens()) {
if (state == 0) {
buffer.append("\n ");
state = 1;
}
final String token = tk.nextToken();
buffer.append(' ').append(token);
if (state == 2) {
state = 0;
} else if (token.equals("=")) {
state = 2;
}
}
return buffer.toString();
}
/**
* The interface for editor capable to returns the edited value.
*
* @author Martin Desruisseaux (IRD)
* @version 3.00
*
* @since 2.0
* @module
*
* @todo This interface should have a {@code setEditable(boolean)} method.
*/
private interface Editor {
/**
* Returns the edited value.
*/
Object getValue();
/**
* Sets the type and the range of valid values.
*/
void setValueRange(final Class<?> type, final Range range);
}
/**
* An editor panel for editing a single value. The value if usually an instance of
* {@link Number}, {@link Date}, {@link Angle}, {@link Boolean} or {@link String}.
*
* @author Martin Desruisseaux (IRD)
* @version 3.00
*
* @since 2.0
* @module
*
* @todo This editor should use {@code JSpinner}, but we need to gets
* the minimum and maximum values first since spinner needs bounds.
*/
@SuppressWarnings("serial")
private static final class Singleton extends JComponent implements Editor {
/**
* The data type.
*/
private final JLabel type = new JLabel();
/**
* The minimum allowed value.
*/
private final JLabel range = new JLabel();
/**
* The text field for editing the value.
*/
private final JTextField field;
/**
* Construct an editor for value using the specified format.
*/
public Singleton(final Format format) {
setLayout(new GridBagLayout());
if (format != null) {
field = new JFormattedTextField(format);
} else {
field = new JTextField();
}
field.setEditable(editable);
final Vocabulary resources = Vocabulary.getResources(getLocale());
final GridBagConstraints c = new GridBagConstraints();
c.gridx=0; c.gridwidth=1; c.insets.left=9; c.fill=HORIZONTAL;
c.gridy=0; add(new JLabel(resources.getLabel(Vocabulary.Keys.Type )), c);
c.gridy++; add(new JLabel(resources.getLabel(Vocabulary.Keys.Range)), c);
c.gridy++; add(new JLabel(resources.getLabel(Vocabulary.Keys.Value)), c);
c.gridx=1; c.weightx=1; c.insets.right=9;
c.gridy=0; add(type, c);
c.gridy++; add(range, c);
c.gridy++; add(field, c);
}
/**
* Set the value to be edited.
*/
public void setValue(final Object value) {
if (field instanceof JFormattedTextField) {
((JFormattedTextField) field).setValue(value);
} else {
field.setText(String.valueOf(value));
}
}
/**
* Returns the edited value.
*/
@Override
public Object getValue() {
if (field instanceof JFormattedTextField) {
return ((JFormattedTextField) field).getValue();
} else {
return field.getText();
}
}
/**
* Sets the type and the range of valid values.
*/
@Override
public void setValueRange(Class<?> classe, final Range range) {
String type = null;
String rtxt = null;
if (classe != null) {
while (classe.isArray()) {
classe = classe.getComponentType();
}
type = Classes.getShortName(classe);
classe = primitiveToWrapper(classe);
boolean isInteger = false;
if (isFloat(classe) || (isInteger = isInteger(classe)) == true) {
type = Vocabulary.format(isInteger ? Vocabulary.Keys.SignedInteger_1 :
Vocabulary.Keys.RealNumber_1, primitiveBitCount(classe)) +
" (" + type + ')';
}
}
if (range != null) {
rtxt = Adapters.convert(range).toString();
}
this.type .setText(type);
this.range.setText(rtxt);
}
}
/**
* The table for viewing data here the first column is row number (typically index of an array).
* The only difference compared to standard {@link JTable} is that the first column is rendered
* in a different color, providing that the underlying model is {@link Table}. Otherwise (in
* particular if the underlying model is {@link Matrix}), this class does nothing special.
*
* @author Martin Desruisseaux (Geomatys)
* @version 3.00
*
* @since 3.00
* @module
*/
@SuppressWarnings("serial")
private static final class NumberedTable extends JTable {
/**
* The table cell renderer for the first column.
*/
private final TableCellRenderer rowHeaders;
/**
* {@code true} if the first column should be rendered as row header.
*/
private boolean hasHeaders;
/**
* Creates a new table initialized to the given model.
*/
public NumberedTable(final TableModel model) {
super(model);
rowHeaders = SwingUtilities.setupAsRowHeader(this);
hasHeaders = (model instanceof Table);
}
/**
* Sets a new table model.
*/
@Override
public void setModel(final TableModel model) {
hasHeaders = (model instanceof Table);
super.setModel(model);
}
/**
* Returns the cell renderer for the given row.
*/
@Override
public TableCellRenderer getCellRenderer(final int row, final int column) {
if (hasHeaders && column == 0) {
return rowHeaders;
}
return super.getCellRenderer(row, column);
}
}
/**
* Table model for table parameters (including {@link LookupTableJAI}.
* Instance of this class are created by {@link #updateEditor} when first needed.
*
* @author Martin Desruisseaux (IRD)
* @version 3.00
*
* @since 2.0
* @module
*/
@SuppressWarnings("serial")
private static final class Table extends AbstractTableModel implements Editor {
/**
* The table (usually an instance of {@code double[][]}).
*/
private final Object[] table;
/**
* The offset parameter (a {@link LookupTableJAI} property).
*/
private final int offset;
/**
* The mask to apply on unsigned values, or 0 if the values are signed.
*/
private final int mask;
/**
* Constructs a model for the given table.
*
* @param table The table (usually an instance of {@code double[][]}).
* @param offset The offset parameter (a {@link LookupTableJAI} property).
* @param mask The mask to apply on unsigned values, or 0 if the values are signed.
*/
public Table(final Object[] table, final int offset, final int mask) {
this.table = table;
this.offset = offset;
this.mask = mask;
}
/**
* Returns the number of rows in the table.
*/
@Override
public int getRowCount() {
int count = 0;
for (int i=0; i<table.length; i++) {
final int length = Array.getLength(table[i]);
if (length > count) {
count = length;
}
}
return count;
}
/**
* Returns the number of columns in the model.
*/
@Override
public int getColumnCount() {
return Array.getLength(table) + 1;
}
/**
* Returns the name of the column at the specified index.
*/
@Override
public String getColumnName(final int index) {
switch (index) {
case 0: return Vocabulary.format(Vocabulary.Keys.Index);
default: return Vocabulary.format(Vocabulary.Keys.Value);
}
}
/**
* Returns the most specific superclass for all the cell values.
*/
@Override
public Class<?> getColumnClass(final int index) {
if (index == 0) return String.class; // Row headers.
if (mask != 0) return Integer.class; // Type used for unsigned values.
return primitiveToWrapper(table[index-1].getClass().getComponentType());
}
/**
* Tells if the specified cell is editable.
*/
@Override
public boolean isCellEditable(final int row, final int column) {
return editable && column != 0;
}
/**
* Returns the value at the specified index.
*/
@Override
public Object getValueAt(final int row, final int column) {
if (column == 0) {
return (row + offset) + " ";
}
final Object array = table[column-1];
if (mask != 0) {
return Integer.valueOf(Array.getInt(array, row) & mask);
}
return Array.get(array, row);
}
/**
* Sets the value at the given index.
*/
@Override
public void setValueAt(final Object value, final int row, final int column) {
Array.set(table[column-1], row, value);
}
/**
* Returns the edited value.
*/
@Override
public Object getValue() {
return table;
}
/**
* Sets the type and the range of valid values.
* The default implementation does nothing.
*/
@Override
public void setValueRange(final Class<?> type, final Range range) {
}
}
/**
* Table model for matrix parameters. Instance of this class
* are created by {@link #updateEditor} when first needed.
*
* @author Martin Desruisseaux (IRD)
* @version 3.00
*
* @since 2.0
* @module
*/
@SuppressWarnings("serial")
private static final class Matrix extends AbstractTableModel implements Editor {
/**
* The matrix (usually an instance of {@code double[][]}).
*/
private final Object[] matrix;
/**
* Construct a model for the given matrix.
*
* @param matrix The matrix (usually an instance of {@code double[][]}).
*/
public Matrix(final Object[] matrix) {
this.matrix = matrix;
}
/**
* Returns the number of rows in the matrix.
*/
@Override
public int getRowCount() {
return matrix.length;
}
/**
* Returns the number of columns in the model. This is the length of the longest
* row in the matrix.
*/
@Override
public int getColumnCount() {
int count = 0;
for (int i=0; i<matrix.length; i++) {
final int length = Array.getLength(matrix[i]);
if (length > count) {
count = length;
}
}
return count;
}
/**
* Returns the name of the column at the specified index.
*/
@Override
public String getColumnName(final int index) {
return Integer.toString(index);
}
/**
* Returns the most specific superclass for all the cell values.
*/
@Override
public Class<?> getColumnClass(final int index) {
return primitiveToWrapper(matrix.getClass().getComponentType().getComponentType());
}
/**
* Tells if the specified cell is editable.
*/
@Override
public boolean isCellEditable(final int row, final int column) {
return editable;
}
/**
* Returns the value at the specified index.
*/
@Override
public Object getValueAt(final int row, final int column) {
final Object array = matrix[row];
return (column < Array.getLength(array)) ? Array.get(array, column) : null;
}
/**
* Sets the value at the given index.
*/
@Override
public void setValueAt(final Object value, final int row, final int column) {
Array.set(matrix[row], column, value);
}
/**
* Returns the edited value.
*/
@Override
public Object getValue() {
return matrix;
}
/**
* Sets the type and the range of valid values.
* The default implementation does nothing.
*/
@Override
public void setValueRange(final Class<?> type, final Range range) {
}
}
}