/* EditFFTSampleFilterDialog.java created 2008-02-03
*
*/
package org.signalml.app.view.montage.filters;
import static org.signalml.app.util.i18n.SvarogI18n._;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.text.ParseException;
import javax.swing.AbstractAction;
import javax.swing.Box;
import javax.swing.GroupLayout;
import javax.swing.GroupLayout.Alignment;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JSpinner;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.SpinnerNumberModel;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.border.CompoundBorder;
import javax.swing.border.EmptyBorder;
import javax.swing.border.TitledBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import org.signalml.app.SvarogApplication;
import org.signalml.app.config.preset.Preset;
import org.signalml.app.config.preset.PresetManager;
import org.signalml.app.model.components.validation.ValidationErrors;
import org.signalml.app.model.montage.FFTSampleFilterTableModel;
import org.signalml.app.util.IconUtils;
import org.signalml.app.view.TablePopupMenuProvider;
import org.signalml.app.view.common.components.spinners.DoubleSpinner;
import org.signalml.app.view.common.components.spinners.FloatSpinner;
import org.signalml.app.view.montage.filters.charts.FFTFilterResponseChartGroupPanel;
import org.signalml.app.view.montage.filters.charts.FrequencyRangeSelection;
import org.signalml.app.view.montage.filters.charts.elements.SelectionHighlightRenderer;
import org.signalml.domain.montage.filter.FFTSampleFilter;
import org.signalml.domain.montage.filter.FFTSampleFilter.Range;
import org.signalml.plugin.export.SignalMLException;
import org.signalml.util.Util;
/** EditFFTSampleFilterDialog
*
*
* @author Michal Dobaczewski © 2007-2008 CC Otwarte Systemy Komputerowe Sp. z o.o.
*/
public class EditFFTSampleFilterDialog extends EditSampleFilterDialog implements PropertyChangeListener {
private static final long serialVersionUID = 1L;
private static final double FREQUENCY_SPINNER_STEP_SIZE = 0.25;
private FFTSampleFilter currentFilter;
/**
* A {@link JTextField} which can be used to edit the filter's description.
*/
private JTextField descriptionTextField;
private FFTSampleFilterTableModel tableModel;
private FFTSampleFilterTable table;
private JScrollPane tableScrollPane;
private JPanel newRangePanel;
private FloatSpinner fromFrequencySpinner;
private FloatSpinner toFrequencySpinner;
private DoubleSpinner coefficientSpinner;
private JCheckBox unlimitedCheckBox;
private JCheckBox multiplyCheckBox;
private AddNewRangeAction addNewRangeAction;
private RemoveRangeAction removeRangeAction;
private JButton addNewRangeButton;
private JButton removeRangeButton;
private FFTWindowTypePanel fftWindowTypePanel;
/**
* A panel for drawing and controling the filter responses.
* It contains all the charts visualizing the filter and associated
* spinners to control the maximum value shown on the x-axis.
*/
protected FFTFilterResponseChartGroupPanel graphsPanel;
public EditFFTSampleFilterDialog(Window w, boolean isModal) {
super(SvarogApplication.getManagerOfPresetsManagers().getFftFilterPresetManager(), w, isModal);
}
public EditFFTSampleFilterDialog(PresetManager presetManager) {
super(presetManager);
}
@Override
protected void initialize() {
setTitle(_("Edit FFT sample filter"));
addNewRangeAction = new AddNewRangeAction();
removeRangeAction = new RemoveRangeAction();
removeRangeAction.setEnabled(false);
super.initialize();
}
@Override
public JComponent createInterface() {
CompoundBorder border;
JPanel interfacePanel = new JPanel(new BorderLayout());
JPanel descriptionPanel = getDescriptionPanel();
JPanel graphPanel = getChartGroupPanelWithABorder();
JPanel addNewRangePanel = new JPanel(new BorderLayout(3, 3));
addNewRangePanel.setBorder(new TitledBorder(_("New range parameters")));
JPanel addNewRangeButtonPanel = new JPanel(new FlowLayout(FlowLayout.TRAILING, 3, 3));
addNewRangeButtonPanel.add(getAddNewRangeButton());
addNewRangePanel.add(getNewRangePanel(), BorderLayout.CENTER);
addNewRangePanel.add(addNewRangeButtonPanel, BorderLayout.SOUTH);
JPanel leftPanel = new JPanel(new BorderLayout());
leftPanel.add(graphPanel, BorderLayout.NORTH);
leftPanel.add(descriptionPanel, BorderLayout.CENTER);
leftPanel.add(addNewRangePanel, BorderLayout.SOUTH);
JPanel rightPanel = new JPanel(new BorderLayout(3, 3));
border = new CompoundBorder(
new TitledBorder(_("Ranges")),
new EmptyBorder(3, 3, 3, 3)
);
rightPanel.setBorder(border);
JPanel rightButtonPanel = new JPanel(new FlowLayout(FlowLayout.TRAILING, 0, 0));
rightButtonPanel.add(getRemoveRangeButton());
rightPanel.add(getTableScrollPane(), BorderLayout.CENTER);
rightPanel.add(rightButtonPanel, BorderLayout.SOUTH);
interfacePanel.add(leftPanel, BorderLayout.CENTER);
interfacePanel.add(rightPanel, BorderLayout.EAST);
interfacePanel.add(getFFTWindowTypePanel(), BorderLayout.SOUTH);
return interfacePanel;
}
/**
* Returns the {@link JPanel} containing a {@link JTextField} for setting
* the description for the currently edited filter.
* @return the {@link JPanel} with controls to edit the filter's
* description
*/
public JPanel getDescriptionPanel() {
JPanel descriptionPanel = new JPanel(new BorderLayout());
CompoundBorder border = new CompoundBorder(
new TitledBorder(_("Filter description")),
new EmptyBorder(3, 3, 3, 3));
descriptionPanel.setBorder(border);
descriptionPanel.add(getDescriptionTextField());
return descriptionPanel;
}
/**
* Returns the {@link JTextField} which is shown in this dialog and
* can be used to edit the filter's description.
* @return the {@link JTextField} to edit the filter's description
*/
public JTextField getDescriptionTextField() {
if (descriptionTextField == null) {
descriptionTextField = new JTextField();
descriptionTextField.setPreferredSize(new Dimension(200, 25));
}
return descriptionTextField;
}
public FFTSampleFilterTableModel getTableModel() {
if (tableModel == null) {
tableModel = new FFTSampleFilterTableModel();
}
return tableModel;
}
public FFTSampleFilterTable getTable() {
if (table == null) {
table = new FFTSampleFilterTable(getTableModel());
table.setPopupMenuProvider(new RangeTablePopupProvider());
table.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
@Override
public void valueChanged(ListSelectionEvent e) {
FFTSampleFilterTable filterTable = getTable();
boolean enabled = (filterTable.getModel().getRowCount() > 1 && !(filterTable.getSelectionModel().isSelectionEmpty()));
removeRangeAction.setEnabled(enabled);
}
});
table.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
FFTSampleFilterTable table = (FFTSampleFilterTable) e.getSource();
if (SwingUtilities.isLeftMouseButton(e) && (e.getClickCount() % 2) == 0) {
int selRow = table.rowAtPoint(e.getPoint());
if (selRow >= 0) {
Range range = currentFilter.getRangeAt(selRow);
float lowFrequency = range.getLowFrequency();
float highFrequency = range.getHighFrequency();
getFromFrequencySpinner().setValue((double) lowFrequency);
if (highFrequency <= lowFrequency) {
double maximumFrequency = getCurrentSamplingFrequency() / 2;
getToFrequencySpinner().setValue(Math.max(lowFrequency + FREQUENCY_SPINNER_STEP_SIZE, maximumFrequency));
getUnlimitedCheckBox().setSelected(true);
} else {
getToFrequencySpinner().setValue((double) highFrequency);
getUnlimitedCheckBox().setSelected(false);
}
getCoefficientSpinner().setValue(range.getCoefficient());
getMultiplyCheckBox().setSelected(false);
}
}
}
});
table.setToolTipText(_("Double click a row to copy parameters to new range parameters panel"));
KeyStroke del = KeyStroke.getKeyStroke("DELETE");
table.getInputMap(JComponent.WHEN_FOCUSED).put(del, "remove");
table.getActionMap().put("remove", removeRangeAction);
}
return table;
}
public JScrollPane getTableScrollPane() {
if (tableScrollPane == null) {
tableScrollPane = new JScrollPane(getTable());
tableScrollPane.setPreferredSize(new Dimension(250, 300));
}
return tableScrollPane;
}
public JPanel getNewRangePanel() {
if (newRangePanel == null) {
newRangePanel = new JPanel(null);
newRangePanel.setBorder(new EmptyBorder(3, 3, 3, 3));
GroupLayout layout = new GroupLayout(newRangePanel);
newRangePanel.setLayout(layout);
layout.setAutoCreateContainerGaps(false);
layout.setAutoCreateGaps(true);
JLabel fromFrequencyLabel = new JLabel(_("From (incl.) [Hz]"));
JLabel toFrequencyLabel = new JLabel(_("To (excl.) [Hz]"));
JLabel coefficientLabel = new JLabel(_("Coefficient"));
JLabel unlimitedLabel = new JLabel(_("Up to Fn"));
JLabel multiplyLabel = new JLabel(_("Multiply"));
Component filler1 = Box.createRigidArea(new Dimension(1, 25));
Component filler2 = Box.createRigidArea(new Dimension(1, 25));
GroupLayout.SequentialGroup hGroup = layout.createSequentialGroup();
hGroup.addGroup(
layout.createParallelGroup()
.addComponent(fromFrequencyLabel)
.addComponent(coefficientLabel)
);
hGroup.addGroup(
layout.createParallelGroup(Alignment.TRAILING)
.addComponent(getFromFrequencySpinner())
.addComponent(getCoefficientSpinner())
);
hGroup.addGroup(
layout.createParallelGroup()
.addComponent(toFrequencyLabel)
.addComponent(filler1)
);
hGroup.addGroup(
layout.createParallelGroup(Alignment.TRAILING)
.addComponent(getToFrequencySpinner())
.addComponent(filler2)
);
hGroup.addGroup(
layout.createParallelGroup()
.addComponent(unlimitedLabel)
.addComponent(multiplyLabel)
);
hGroup.addGroup(
layout.createParallelGroup(Alignment.TRAILING)
.addComponent(getUnlimitedCheckBox())
.addComponent(getMultiplyCheckBox())
);
layout.setHorizontalGroup(hGroup);
GroupLayout.SequentialGroup vGroup = layout.createSequentialGroup();
vGroup.addGroup(
layout.createParallelGroup(Alignment.BASELINE)
.addComponent(fromFrequencyLabel)
.addComponent(getFromFrequencySpinner())
.addComponent(toFrequencyLabel)
.addComponent(getToFrequencySpinner())
.addComponent(unlimitedLabel)
.addComponent(getUnlimitedCheckBox())
);
vGroup.addGroup(
layout.createParallelGroup(Alignment.BASELINE)
.addComponent(coefficientLabel)
.addComponent(getCoefficientSpinner())
.addComponent(filler1)
.addComponent(filler2)
.addComponent(multiplyLabel)
.addComponent(getMultiplyCheckBox())
);
layout.setVerticalGroup(vGroup);
}
return newRangePanel;
}
public FloatSpinner getFromFrequencySpinner() {
if (fromFrequencySpinner == null) {
fromFrequencySpinner = new FloatSpinner(new SpinnerNumberModel(0.0, 0.0, 4096.0, FREQUENCY_SPINNER_STEP_SIZE));
fromFrequencySpinner.setPreferredSize(new Dimension(80, 25));
fromFrequencySpinner.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
double value = fromFrequencySpinner.getValue();
double otherValue = getToFrequencySpinner().getValue();
if (value >= otherValue) {
getToFrequencySpinner().setValue(value + FREQUENCY_SPINNER_STEP_SIZE);
}
updateHighlights();
}
});
fromFrequencySpinner.setEditor(new JSpinner.NumberEditor(fromFrequencySpinner, "0.00"));
fromFrequencySpinner.setFont(fromFrequencySpinner.getFont().deriveFont(Font.PLAIN));
}
return fromFrequencySpinner;
}
public FloatSpinner getToFrequencySpinner() {
if (toFrequencySpinner == null) {
toFrequencySpinner = new FloatSpinner(new SpinnerNumberModel(FREQUENCY_SPINNER_STEP_SIZE, FREQUENCY_SPINNER_STEP_SIZE, 4096.0, FREQUENCY_SPINNER_STEP_SIZE));
toFrequencySpinner.setPreferredSize(new Dimension(80, 25));
toFrequencySpinner.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
double value = ((Number) toFrequencySpinner.getValue()).doubleValue();
double otherValue = ((Number) getFromFrequencySpinner().getValue()).doubleValue();
if (value <= otherValue) {
getFromFrequencySpinner().setValue(value - FREQUENCY_SPINNER_STEP_SIZE);
}
updateHighlights();
}
});
toFrequencySpinner.setEditor(new JSpinner.NumberEditor(toFrequencySpinner, "0.00"));
toFrequencySpinner.setFont(toFrequencySpinner.getFont().deriveFont(Font.PLAIN));
}
return toFrequencySpinner;
}
public JCheckBox getUnlimitedCheckBox() {
if (unlimitedCheckBox == null) {
unlimitedCheckBox = new JCheckBox();
unlimitedCheckBox.addItemListener(new ItemListener() {
private Float previousToFrequencyValue;
@Override
public void itemStateChanged(ItemEvent e) {
boolean unlimited = getUnlimitedCheckBox().isSelected();
double maximumFrequency = getCurrentSamplingFrequency() / 2;
double newToFrequencySpinnerValue;
if (unlimited) {
float toFrequencyValue = getToFrequencySpinner().getValue();
if (toFrequencyValue != maximumFrequency)
previousToFrequencyValue = toFrequencyValue;
newToFrequencySpinnerValue = maximumFrequency;
} else if (previousToFrequencyValue != null
&& previousToFrequencyValue != maximumFrequency) {
newToFrequencySpinnerValue = previousToFrequencyValue;
} else {
newToFrequencySpinnerValue = maximumFrequency - FREQUENCY_SPINNER_STEP_SIZE;
}
getToFrequencySpinner().setValue(newToFrequencySpinnerValue);
getToFrequencySpinner().setEnabled(!unlimited);
updateHighlights();
}
});
}
return unlimitedCheckBox;
}
public DoubleSpinner getCoefficientSpinner() {
if (coefficientSpinner == null) {
coefficientSpinner = new DoubleSpinner(new SpinnerNumberModel(0.0, 0.0, 100.0, 0.1));
coefficientSpinner.setPreferredSize(new Dimension(80, 25));
final JTextField editor = ((JTextField)((JSpinner.NumberEditor) coefficientSpinner.getEditor()).getComponent(0));
KeyStroke enter = KeyStroke.getKeyStroke("ENTER");
editor.getInputMap(JComponent.WHEN_FOCUSED).put(enter, "add");
editor.getActionMap().put("add", addNewRangeAction);
}
return coefficientSpinner;
}
public JCheckBox getMultiplyCheckBox() {
if (multiplyCheckBox == null) {
multiplyCheckBox = new JCheckBox();
}
return multiplyCheckBox;
}
public JButton getAddNewRangeButton() {
if (addNewRangeButton == null) {
addNewRangeButton = new JButton(addNewRangeAction);
}
return addNewRangeButton;
}
public JButton getRemoveRangeButton() {
if (removeRangeButton == null) {
removeRangeButton = new JButton(removeRangeAction);
}
return removeRangeButton;
}
public FFTWindowTypePanel getFFTWindowTypePanel() {
if (fftWindowTypePanel == null) {
fftWindowTypePanel = new FFTWindowTypePanel(true);
}
return fftWindowTypePanel;
}
@Override
protected boolean updateGraph() {
getChartGroupPanelWithABorder().updateGraphs(currentFilter);
return true;
}
/**
* Updates the rectangle which highlights the selected frequency range.
*/
@Override
protected void updateHighlights() {
double startFrequency = getFromFrequencySpinner().getValue();
double endFrequency = getToFrequencySpinner().getValue();
FrequencyRangeSelection selection = new FrequencyRangeSelection(startFrequency, endFrequency);
getChartGroupPanelWithABorder().setHighlightedSelection(selection);
}
@Override
public Preset getPreset() throws SignalMLException {
return currentFilter.duplicate();
}
@Override
public void setPreset(Preset preset) throws SignalMLException {
fillDialogFromModel(preset);
}
@Override
public void fillDialogFromModel(Object model) throws SignalMLException {
currentFilter = new FFTSampleFilter((FFTSampleFilter) model);
getTableModel().setFilter(currentFilter);
getFFTWindowTypePanel().fillPanelFromModel(currentFilter);
getDescriptionTextField().setText(currentFilter.getDescription());
updateGraph();
}
@Override
public void fillModelFromDialog(Object model) throws SignalMLException {
getFFTWindowTypePanel().fillModelFromPanel(currentFilter);
currentFilter.setDescription(getDescriptionTextField().getText());
// otherwise currentFilter should be up to date
((FFTSampleFilter) model).copyFrom(currentFilter);
}
@Override
public void validateDialog(Object model, ValidationErrors errors) throws SignalMLException {
super.validateDialog(model, errors);
getFFTWindowTypePanel().validatePanel(errors);
String description = getDescriptionTextField().getText();
if (description == null || description.isEmpty()) {
errors.addError(_("A filter must have a description"));
} else if (Util.hasSpecialChars(description)) {
errors.addError(_("Filter description must not contain control characters"));
}
}
@Override
public boolean supportsModelClass(Class<?> clazz) {
return FFTSampleFilter.class.isAssignableFrom(clazz);
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
String propertyName = evt.getPropertyName();
if (propertyName.equals(SelectionHighlightRenderer.SELECTION_CHANGED_PROPERTY)) {
FrequencyRangeSelection selection = (FrequencyRangeSelection)evt.getNewValue();
double lowerFrequency = selection.getLowerFrequency();
double higherFrequency = selection.getHigherFrequency();
getFromFrequencySpinner().setValue(lowerFrequency);
getToFrequencySpinner().setValue(higherFrequency);
if (higherFrequency == getMaximumFrequency())
getUnlimitedCheckBox().setSelected(true);
else
getUnlimitedCheckBox().setSelected(false);
}
}
protected class AddNewRangeAction extends AbstractAction {
private static final long serialVersionUID = 1L;
public AddNewRangeAction() {
super(_("Add or replace range"));
putValue(AbstractAction.SMALL_ICON, IconUtils.loadClassPathIcon("org/signalml/app/icon/addfftrange.png"));
}
@Override
public void actionPerformed(ActionEvent ev) {
if (currentFilter == null) {
return;
}
DoubleSpinner coefficientSpinner = getCoefficientSpinner();
try {
coefficientSpinner.commitEdit();
} catch (ParseException pe) {
UIManager.getLookAndFeel().provideErrorFeedback(coefficientSpinner);
}
FloatSpinner fromFrequencySpinner = getFromFrequencySpinner();
try {
fromFrequencySpinner.commitEdit();
} catch (ParseException pe) {
UIManager.getLookAndFeel().provideErrorFeedback(fromFrequencySpinner);
}
FloatSpinner toFrequencySpinner = getToFrequencySpinner();
try {
toFrequencySpinner.commitEdit();
} catch (ParseException pe) {
UIManager.getLookAndFeel().provideErrorFeedback(toFrequencySpinner);
}
float fromFrequency = fromFrequencySpinner.getValue();
boolean unlimited = getUnlimitedCheckBox().isSelected();
float toFrequency;
if (!unlimited) {
toFrequency = ((Number) toFrequencySpinner.getValue()).floatValue();
} else {
toFrequency = 0F;
}
double coefficient = ((Number) coefficientSpinner.getValue()).doubleValue();
Range range = currentFilter.new Range(fromFrequency, toFrequency, coefficient);
currentFilter.setRange(range, getMultiplyCheckBox().isSelected());
getTableModel().onUpdate();
updateGraph();
}
}
protected class RemoveRangeAction extends AbstractAction {
private static final long serialVersionUID = 1L;
public RemoveRangeAction() {
super(_("Remove range"));
putValue(AbstractAction.SMALL_ICON, IconUtils.loadClassPathIcon("org/signalml/app/icon/removefftrange.png"));
}
@Override
public void actionPerformed(ActionEvent ev) {
if (currentFilter == null) {
return;
}
int selectedRow = getTable().getSelectedRow();
if (selectedRow < 0) {
return;
}
currentFilter.removeRange(selectedRow);
FFTSampleFilterTableModel model = getTableModel();
model.onUpdate();
if (model.getRowCount() > 0) {
getTable().getSelectionModel().setSelectionInterval(selectedRow, selectedRow);
}
updateGraph();
}
}
protected class RangeTablePopupProvider implements TablePopupMenuProvider {
private JPopupMenu popupMenu;
@Override
public JPopupMenu getPopupMenu(int col, int row) {
return getDefaultPopupMenu();
}
@Override
public JPopupMenu getPopupMenu() {
return getPopupMenu(-1, -1);
}
private JPopupMenu getDefaultPopupMenu() {
if (popupMenu == null) {
popupMenu = new JPopupMenu();
popupMenu.add(removeRangeAction);
}
return popupMenu;
}
}
@Override
public void setCurrentSamplingFrequency(float currentSamplingFrequency) {
super.setCurrentSamplingFrequency(currentSamplingFrequency);
getChartGroupPanelWithABorder().setSamplingFrequency(currentSamplingFrequency);
if (getToFrequencySpinner().getValue() >= getMaximumFrequency())
getToFrequencySpinner().setValue(FREQUENCY_SPINNER_STEP_SIZE);
if (this.getFromFrequencySpinner().getValue() >= getMaximumFrequency())
getFromFrequencySpinner().setValue(0.0);
}
@Override
public FFTFilterResponseChartGroupPanel getChartGroupPanelWithABorder() {
if (graphsPanel == null) {
graphsPanel = new FFTFilterResponseChartGroupPanel(currentFilter);
graphsPanel.setSamplingFrequency(getCurrentSamplingFrequency());
graphsPanel.addSelectionChangedListener(this);
}
return graphsPanel;
}
}