/* * Copyright (C) 2011 Brockmann Consult GmbH (info@brockmann-consult.de) * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 3 of the License, or (at your option) * any later version. * This program 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 General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, see http://www.gnu.org/licenses/ */ package org.esa.snap.core.gpf.ui.mosaic; import com.bc.ceres.swing.TableLayout; import com.bc.ceres.swing.binding.BindingContext; import org.esa.snap.core.datamodel.Band; import org.esa.snap.core.datamodel.Product; import org.esa.snap.core.jexp.impl.Tokenizer; import org.esa.snap.core.util.ArrayUtils; import org.esa.snap.core.util.MouseEventFilterFactory; import org.esa.snap.core.util.StringUtils; import org.esa.snap.ui.AppContext; import org.esa.snap.ui.ModalDialog; import org.esa.snap.ui.UIUtils; import org.esa.snap.ui.product.BandChooser; import org.esa.snap.ui.product.ProductExpressionPane; import org.esa.snap.ui.tool.ToolButtonFactory; import javax.swing.AbstractButton; import javax.swing.AbstractCellEditor; import javax.swing.BorderFactory; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.ListSelectionModel; import javax.swing.UIManager; import javax.swing.border.Border; import javax.swing.border.EmptyBorder; import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.DefaultTableModel; import javax.swing.table.JTableHeader; import javax.swing.table.TableCellEditor; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableColumn; import javax.swing.table.TableColumnModel; import java.awt.BorderLayout; import java.awt.Component; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.IOException; import java.util.ArrayList; import java.util.List; class MosaicExpressionsPanel extends JPanel { private static final int PREFERRED_TABLE_WIDTH = 520; private final AppContext appContext; private final BindingContext bindingCtx; private JTable variablesTable; private JTable conditionsTable; private MosaicFormModel mosaicModel; MosaicExpressionsPanel(AppContext appContext, MosaicFormModel model) { this.appContext = appContext; mosaicModel = model; this.bindingCtx = new BindingContext(model.getPropertySet()); init(); } private void init() { final TableLayout tableLayout = new TableLayout(1); tableLayout.setTableAnchor(TableLayout.Anchor.WEST); tableLayout.setTableFill(TableLayout.Fill.BOTH); tableLayout.setTableWeightX(1.0); tableLayout.setTableWeightY(1.0); tableLayout.setTablePadding(3, 3); setLayout(tableLayout); add(createVariablesPanel()); add(createConditionsPanel()); } private Component createVariablesPanel() { final String labelName = "Variables"; /*I18N*/ final TableLayout layout = new TableLayout(1); layout.setTableAnchor(TableLayout.Anchor.WEST); layout.setTableFill(TableLayout.Fill.BOTH); layout.setTablePadding(3, 3); layout.setTableWeightX(1.0); layout.setTableWeightY(1.0); layout.setRowWeightY(0, 0.0); final JPanel panel = new JPanel(layout); panel.setBorder(BorderFactory.createTitledBorder(labelName)); panel.setName(labelName); panel.add(createVariablesButtonPanel(labelName)); panel.add(createVariablesTable(labelName)); return panel; } private JPanel createVariablesButtonPanel(String labelName) { final JPanel variableButtonsPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 5, 0)); variableButtonsPanel.setName(labelName); final Component bandFilterButton = createBandFilterButton(); bandFilterButton.setName(labelName + "_bandFilter"); variableButtonsPanel.add(bandFilterButton); final Component newVariableButton = createNewVariableButton(); newVariableButton.setName(labelName + "_newVariable"); variableButtonsPanel.add(newVariableButton); final Component removeVariableButton = createRemoveVariableButton(); removeVariableButton.setName(labelName + "_removeVariable"); variableButtonsPanel.add(removeVariableButton); final Component moveVariableUpButton = createMoveVariableUpButton(); moveVariableUpButton.setName(labelName + "moveVariableUp"); variableButtonsPanel.add(moveVariableUpButton); final Component moveVariableDownButton = createMoveVariableDownButton(); moveVariableDownButton.setName(labelName + "moveVariableDown"); variableButtonsPanel.add(moveVariableDownButton); bindingCtx.addPropertyChangeListener("updateMode", new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { final boolean enabled = Boolean.FALSE.equals(evt.getNewValue()); bandFilterButton.setEnabled(enabled); newVariableButton.setEnabled(enabled); removeVariableButton.setEnabled(enabled); moveVariableUpButton.setEnabled(enabled); moveVariableDownButton.setEnabled(enabled); } }); return variableButtonsPanel; } private Component createConditionsPanel() { final String labelName = "Conditions"; final TableLayout layout = new TableLayout(1); layout.setTableAnchor(TableLayout.Anchor.WEST); layout.setTableFill(TableLayout.Fill.BOTH); layout.setTablePadding(3, 3); layout.setTableWeightX(1.0); layout.setTableWeightY(0.0); layout.setRowWeightY(1, 1.0); final JPanel panel = new JPanel(layout); panel.setName(labelName); panel.setBorder(BorderFactory.createTitledBorder(labelName)); final JPanel conditionsButtonsPanel = createConditionsButtonPanel(labelName); panel.add(conditionsButtonsPanel); panel.add(createConditionsTable(labelName)); panel.add(createCombinePanel()); return panel; } private JPanel createConditionsButtonPanel(String labelName) { final JPanel conditionButtonsPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 5, 0)); conditionButtonsPanel.setName(labelName); final Component newConditionButton = createNewConditionButton(); newConditionButton.setName(labelName + "_newCondition"); conditionButtonsPanel.add(newConditionButton); final Component removeConditionButton = createRemoveConditionButton(); removeConditionButton.setName(labelName + "_removeCondition"); conditionButtonsPanel.add(removeConditionButton); final Component moveConditionUpButton = createMoveConditionUpButton(); moveConditionUpButton.setName(labelName + "moveConditionUp"); conditionButtonsPanel.add(moveConditionUpButton); final Component moveConditionDownButton = createMoveConditionDownButton(); moveConditionDownButton.setName(labelName + "moveConditionDown"); conditionButtonsPanel.add(moveConditionDownButton); bindingCtx.addPropertyChangeListener("updateMode", new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { final boolean enabled = Boolean.FALSE.equals(evt.getNewValue()); newConditionButton.setEnabled(enabled); removeConditionButton.setEnabled(enabled); moveConditionUpButton.setEnabled(enabled); moveConditionDownButton.setEnabled(enabled); } }); return conditionButtonsPanel; } private JPanel createCombinePanel() { final JPanel combinePanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); final JComboBox combineComboBox = new JComboBox(); bindingCtx.bind("combine", combineComboBox); bindingCtx.bindEnabledState("combine", false, "updateMode", true); final String displayName = bindingCtx.getPropertySet().getDescriptor("combine").getDisplayName(); combinePanel.add(new JLabel(displayName + ":")); combinePanel.add(combineComboBox); return combinePanel; } private Component createNewConditionButton() { AbstractButton newConditionsButton = createButton("icons/Plus24.gif", "newCondition"); newConditionsButton.setToolTipText("Add new processing condition"); /*I18N*/ newConditionsButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { final int rows = conditionsTable.getRowCount(); addRow(conditionsTable, new Object[]{"condition_" + rows, "", false}); /*I18N*/ } }); return newConditionsButton; } private Component createRemoveConditionButton() { AbstractButton removeConditionButton = createButton("icons/Minus24.gif", "removeCondition"); removeConditionButton.setToolTipText("Remove selected rows."); /*I18N*/ removeConditionButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { removeRows(conditionsTable, conditionsTable.getSelectedRows()); } }); return removeConditionButton; } private Component createMoveConditionUpButton() { AbstractButton moveConditionUpButton = createButton("icons/MoveUp24.gif", "moveConditionUp"); moveConditionUpButton.setToolTipText("Move up selected rows."); /*I18N*/ moveConditionUpButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { moveRowsUp(conditionsTable, conditionsTable.getSelectedRows()); } }); return moveConditionUpButton; } private Component createMoveConditionDownButton() { AbstractButton moveConditionDownButton = createButton("icons/MoveDown24.gif", "moveConditionDown"); moveConditionDownButton.setToolTipText("Move down selected rows."); /*I18N*/ moveConditionDownButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { moveRowsDown(conditionsTable, conditionsTable.getSelectedRows()); } }); return moveConditionDownButton; } private JScrollPane createConditionsTable(final String labelName) { conditionsTable = new JTable() { private static final long serialVersionUID = 1L; @Override public Class getColumnClass(int column) { if (column == 2) { return Boolean.class; } else { return super.getColumnClass(column); } } }; conditionsTable.setName(labelName); conditionsTable.setRowSelectionAllowed(true); bindingCtx.bind("conditions", new ConditionsTableAdapter(conditionsTable)); bindingCtx.bindEnabledState("conditions", false, "updateMode", true); conditionsTable.addMouseListener(createExpressionEditorMouseListener(conditionsTable, true)); final JTableHeader tableHeader = conditionsTable.getTableHeader(); tableHeader.setName(labelName); tableHeader.setReorderingAllowed(false); tableHeader.setResizingAllowed(true); final TableColumnModel columnModel = conditionsTable.getColumnModel(); columnModel.setColumnSelectionAllowed(false); final TableColumn nameColumn = columnModel.getColumn(0); nameColumn.setPreferredWidth(100); nameColumn.setCellRenderer(new TCR()); final TableColumn expressionColumn = columnModel.getColumn(1); expressionColumn.setPreferredWidth(360); expressionColumn.setCellRenderer(new TCR()); final ExprEditor cellEditor = new ExprEditor(true); expressionColumn.setCellEditor(cellEditor); bindingCtx.addPropertyChangeListener("updateMode", new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { final boolean enabled = Boolean.FALSE.equals(evt.getNewValue()); cellEditor.button.setEnabled(enabled); } }); final TableColumn outputColumn = columnModel.getColumn(2); outputColumn.setPreferredWidth(40); final JScrollPane pane = new JScrollPane(conditionsTable); pane.setName(labelName); pane.setPreferredSize(new Dimension(PREFERRED_TABLE_WIDTH, 80)); return pane; } private Component createBandFilterButton() { AbstractButton variableFilterButton = createButton("icons/Copy16.gif", "bandButton"); variableFilterButton.setToolTipText("Choose the bands to process"); /*I18N*/ variableFilterButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { Product product; try { product = mosaicModel.getReferenceProduct(); } catch (IOException ioe) { appContext.handleError(ioe.getMessage(), ioe); return; } if (product != null) { final String[] availableBandNames = product.getBandNames(); final Band[] allBands = product.getBands(); final List dataVector = ((DefaultTableModel) variablesTable.getModel()).getDataVector(); final List<Band> existingBands = new ArrayList<Band>(dataVector.size()); for (Object aDataVector : dataVector) { List row = (List) aDataVector; final String name = (String) row.get(0); final String expression = (String) row.get(1); if (name == null || expression == null || !StringUtils.contains(availableBandNames, name.trim()) || !name.trim().equals(expression.trim())) { continue; } existingBands.add(product.getBand(name.trim())); } final BandChooser bandChooser = new BandChooser(appContext.getApplicationWindow(), "Band Chooser", null, allBands, /*I18N*/ existingBands.toArray( new Band[existingBands.size()]), true ); if (bandChooser.show() == ModalDialog.ID_OK) { final Band[] selectedBands = bandChooser.getSelectedBands(); for (Band selectedBand : selectedBands) { if (!existingBands.contains(selectedBand)) { final String name = selectedBand.getName(); final String expression = Tokenizer.createExternalName(name); addRow(variablesTable, new Object[]{name, expression}); } else { existingBands.remove(selectedBand); } } final int[] rowsToRemove = new int[0]; final List newDataVector = ((DefaultTableModel) variablesTable.getModel()).getDataVector(); for (Band existingBand : existingBands) { String bandName = existingBand.getName(); final int rowIndex = getBandRow(newDataVector, bandName); if (rowIndex > -1) { ArrayUtils.addToArray(rowsToRemove, rowIndex); } } removeRows(variablesTable, rowsToRemove); } } } }); return variableFilterButton; } private static int getBandRow(List newDataVector, String bandName) { for (int i = 0; i < newDataVector.size(); i++) { List row = (List) newDataVector.get(i); if (bandName.equals(row.get(0)) && bandName.equals(row.get(1))) { return i; } } return -1; } private Component createNewVariableButton() { AbstractButton newVariableButton = createButton("icons/Plus24.gif", "newVariable"); newVariableButton.setToolTipText("Add new processing variable"); /*I18N*/ newVariableButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { final int rows = variablesTable.getRowCount(); addRow(variablesTable, new Object[]{"variable_" + rows, ""}); /*I18N*/ } }); return newVariableButton; } private Component createRemoveVariableButton() { AbstractButton removeVariableButton = createButton("icons/Minus24.gif", "removeVariable"); removeVariableButton.setToolTipText("Remove selected rows."); /*I18N*/ removeVariableButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { removeRows(variablesTable, variablesTable.getSelectedRows()); } }); return removeVariableButton; } private Component createMoveVariableUpButton() { AbstractButton moveVariableUpButton = createButton("icons/MoveUp24.gif", "moveVariableUp"); moveVariableUpButton.setToolTipText("Move up selected rows."); /*I18N*/ moveVariableUpButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { moveRowsUp(variablesTable, variablesTable.getSelectedRows()); } }); return moveVariableUpButton; } private Component createMoveVariableDownButton() { AbstractButton moveVariableDownButton = createButton("icons/MoveDown24.gif", "moveVariableDown"); moveVariableDownButton.setToolTipText("Move down selected rows."); /*I18N*/ moveVariableDownButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { moveRowsDown(variablesTable, variablesTable.getSelectedRows()); } }); return moveVariableDownButton; } private JScrollPane createVariablesTable(final String labelName) { variablesTable = new JTable(); variablesTable.setName(labelName); variablesTable.setRowSelectionAllowed(true); bindingCtx.bind("variables", new VariablesTableAdapter(variablesTable)); bindingCtx.bindEnabledState("variables", false, "updateMode", true); variablesTable.addMouseListener(createExpressionEditorMouseListener(variablesTable, false)); final JTableHeader tableHeader = variablesTable.getTableHeader(); tableHeader.setName(labelName); tableHeader.setReorderingAllowed(false); tableHeader.setResizingAllowed(true); final TableColumnModel columnModel = variablesTable.getColumnModel(); columnModel.setColumnSelectionAllowed(false); final TableColumn nameColumn = columnModel.getColumn(0); nameColumn.setPreferredWidth(100); nameColumn.setCellRenderer(new TCR()); final TableColumn expressionColumn = columnModel.getColumn(1); expressionColumn.setPreferredWidth(400); expressionColumn.setCellRenderer(new TCR()); final ExprEditor exprEditor = new ExprEditor(false); expressionColumn.setCellEditor(exprEditor); bindingCtx.addPropertyChangeListener("updateMode", new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { final boolean enabled = Boolean.FALSE.equals(evt.getNewValue()); exprEditor.button.setEnabled(enabled); } }); final JScrollPane scrollPane = new JScrollPane(variablesTable); scrollPane.setName(labelName); scrollPane.setPreferredSize(new Dimension(PREFERRED_TABLE_WIDTH, 150)); return scrollPane; } private static AbstractButton createButton(final String path, String name) { final AbstractButton button = ToolButtonFactory.createButton(UIUtils.loadImageIcon(path), false); button.setName(name); return button; } private MouseListener createExpressionEditorMouseListener(final JTable table, final boolean booleanExpected) { final MouseAdapter mouseListener = new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { if (e.getClickCount() == 2) { final int column = table.getSelectedColumn(); if (column == 1) { table.removeEditor(); final int row = table.getSelectedRow(); final String[] value = new String[]{(String) table.getValueAt(row, column)}; final int i = editExpression(value, booleanExpected); if (ModalDialog.ID_OK == i) { table.setValueAt(value[0], row, column); } } } } }; return MouseEventFilterFactory.createFilter(mouseListener); } private int editExpression(String[] value, final boolean booleanExpected) { Product product; try { product = mosaicModel.getReferenceProduct(); } catch (IOException ioe) { appContext.handleError(ioe.getMessage(), ioe); return 0; } if (product == null) { final String msg = "No source product specified."; appContext.handleError(msg, new IllegalStateException(msg)); return 0; } final ProductExpressionPane pep; if (booleanExpected) { pep = ProductExpressionPane.createBooleanExpressionPane(new Product[]{product}, product, appContext.getPreferences()); } else { pep = ProductExpressionPane.createGeneralExpressionPane(new Product[]{product}, product, appContext.getPreferences()); } pep.setCode(value[0]); final int i = pep.showModalDialog(appContext.getApplicationWindow(), value[0]); if (i == ModalDialog.ID_OK) { value[0] = pep.getCode(); } return i; } private class ExprEditor extends AbstractCellEditor implements TableCellEditor { private final JButton button; private String[] value; private ExprEditor(final boolean booleanExpected) { button = new JButton("..."); final Dimension preferredSize = button.getPreferredSize(); preferredSize.setSize(25, preferredSize.getHeight()); button.setPreferredSize(preferredSize); value = new String[1]; final ActionListener actionListener = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { final int i = editExpression(value, booleanExpected); if (i == ModalDialog.ID_OK) { fireEditingStopped(); } else { fireEditingCanceled(); } } }; button.addActionListener(actionListener); } /** * Returns the value contained in the editor. * * @return the value contained in the editor */ @Override public Object getCellEditorValue() { return value[0]; } /** * Sets an initial <code>value</code> for the editor. This will cause the editor to <code>stopEditing</code> * and lose any partially edited value if the editor is editing when this method is called. <p> * <p> * Returns the component that should be added to the client's <code>Component</code> hierarchy. Once installed * in the client's hierarchy this component will then be able to draw and receive user input. * * @param table the <code>JTable</code> that is asking the editor to edit; can be <code>null</code> * @param value the value of the cell to be edited; it is up to the specific editor to interpret and draw the * value. For example, if value is the string "true", it could be rendered as a string or it could be rendered * as a check box that is checked. <code>null</code> is a valid value * @param isSelected true if the cell is to be rendered with highlighting * @param row the row of the cell being edited * @param column the column of the cell being edited * @return the component for editing */ @Override public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { final JPanel renderPanel = new JPanel(new BorderLayout()); final DefaultTableCellRenderer defaultRenderer = new DefaultTableCellRenderer(); final Component label = defaultRenderer.getTableCellRendererComponent(table, value, isSelected, false, row, column); renderPanel.add(label); renderPanel.add(button, BorderLayout.EAST); this.value[0] = (String) value; return renderPanel; } } private static void addRow(final JTable table, final Object[] rowData) { table.removeEditor(); ((DefaultTableModel) table.getModel()).addRow(rowData); final int row = table.getRowCount() - 1; final int numCols = table.getColumnModel().getColumnCount(); for (int i = 0; i < Math.min(numCols, rowData.length); i++) { Object o = rowData[i]; table.setValueAt(o, row, i); } selectRows(table, row, row); } private static void moveRowsDown(final JTable table, final int[] rows) { final int maxRow = table.getRowCount() - 1; for (int row1 : rows) { if (row1 == maxRow) { return; } } table.removeEditor(); int[] selectedRows = rows.clone(); for (int i = rows.length - 1; i > -1; i--) { int row = rows[i]; ((DefaultTableModel) table.getModel()).moveRow(row, row, row + 1); selectedRows[i] = row + 1; } selectRows(table, selectedRows); } private static void moveRowsUp(final JTable table, final int[] rows) { for (int row1 : rows) { if (row1 == 0) { return; } } table.removeEditor(); int[] selectedRows = rows.clone(); for (int i = 0; i < rows.length; i++) { int row = rows[i]; ((DefaultTableModel) table.getModel()).moveRow(row, row, row - 1); selectedRows[i] = row - 1; } selectRows(table, selectedRows); } private static void removeRows(final JTable table, final int[] rows) { table.removeEditor(); for (int i = rows.length - 1; i > -1; i--) { int row = rows[i]; ((DefaultTableModel) table.getModel()).removeRow(row); } } private static void selectRows(final JTable table, final int[] rows) { final ListSelectionModel selectionModel = table.getSelectionModel(); selectionModel.clearSelection(); for (int row : rows) { selectionModel.addSelectionInterval(row, row); } } private static void selectRows(JTable table, int min, int max) { final int numRows = max + 1 - min; if (numRows <= 0) { return; } selectRows(table, prepareRows(numRows, min)); } private static int[] prepareRows(final int numRows, int min) { final int[] rows = new int[numRows]; for (int i = 0; i < rows.length; i++) { rows[i] = min + i; } return rows; } private static class TCR extends JLabel implements TableCellRenderer { private static final Border noFocusBorder = new EmptyBorder(1, 1, 1, 1); /** * Creates a <code>JLabel</code> instance with no image and with an empty string for the title. The label is * centered vertically in its display area. The label's contents, once set, will be displayed on the leading * edge of the label's display area. */ private TCR() { setOpaque(true); setBorder(noFocusBorder); } @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { final boolean enabled = table.isEnabled(); setText((String) value); if (isSelected) { super.setForeground(table.getSelectionForeground()); super.setBackground(table.getSelectionBackground()); } else if (!enabled) { super.setForeground(UIManager.getColor("TextField.inactiveForeground")); super.setBackground(table.getBackground()); } else { super.setForeground(table.getForeground()); super.setBackground(table.getBackground()); } setFont(table.getFont()); if (hasFocus) { setBorder(UIManager.getBorder("Table.focusCellHighlightBorder")); if (table.isCellEditable(row, column)) { super.setForeground(UIManager.getColor("Table.focusCellForeground")); super.setBackground(UIManager.getColor("Table.focusCellBackground")); } } else { setBorder(noFocusBorder); } setValue(value); return this; } private void setValue(Object value) { setText(value == null ? "" : value.toString()); } } }