PeriodAnalysisDataTablePane.java
/**
* VStar: a statistical analysis tool for variable star data.
* Copyright (C) 2009 AAVSO (http://www.aavso.org/)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.aavso.tools.vstar.ui.dialog.period;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.GridLayout;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.TableRowSorter;
import org.aavso.tools.vstar.ui.dialog.MessageBox;
import org.aavso.tools.vstar.ui.dialog.model.HarmonicInfoDialog;
import org.aavso.tools.vstar.ui.dialog.model.HarmonicInputDialog;
import org.aavso.tools.vstar.ui.mediator.Mediator;
import org.aavso.tools.vstar.ui.mediator.message.HarmonicSearchResultMessage;
import org.aavso.tools.vstar.ui.mediator.message.PeriodAnalysisSelectionMessage;
import org.aavso.tools.vstar.ui.model.list.PeriodAnalysisDataTableModel;
import org.aavso.tools.vstar.util.IStartAndCleanup;
import org.aavso.tools.vstar.util.comparator.FormattedDoubleComparator;
import org.aavso.tools.vstar.util.locale.LocaleProps;
import org.aavso.tools.vstar.util.model.Harmonic;
import org.aavso.tools.vstar.util.model.PeriodAnalysisDerivedMultiPeriodicModel;
import org.aavso.tools.vstar.util.notification.Listener;
import org.aavso.tools.vstar.util.period.IPeriodAnalysisAlgorithm;
import org.aavso.tools.vstar.util.period.dcdft.PeriodAnalysisDataPoint;
/**
* This class represents a period analysis data table pane.
*/
@SuppressWarnings("serial")
public class PeriodAnalysisDataTablePane extends JPanel implements ListSelectionListener, IStartAndCleanup {
protected JTable table;
protected PeriodAnalysisDataTableModel model;
protected TableRowSorter<PeriodAnalysisDataTableModel> rowSorter;
protected JButton modelButton;
protected IPeriodAnalysisAlgorithm algorithm;
protected boolean wantModelButton;
protected Map<Double, List<Harmonic>> freqToHarmonicsMap;
protected Listener<HarmonicSearchResultMessage> harmonicSearchResultListener;
protected Listener<PeriodAnalysisSelectionMessage> periodAnalysisSelectionListener;
private String tablePaneID = null;
private boolean valueChangedDisabledState = false;
/**
* Constructor
*
* @param model The period analysis table model.
* @param algorithm The period analysis algorithm.
* @param wantModelButton Add a model button?
*/
public PeriodAnalysisDataTablePane(PeriodAnalysisDataTableModel model, IPeriodAnalysisAlgorithm algorithm,
boolean wantModelButton) {
super(new GridLayout(1, 1));
this.model = model;
this.algorithm = algorithm;
this.wantModelButton = wantModelButton;
freqToHarmonicsMap = new HashMap<Double, List<Harmonic>>();
table = new JTable(model);
JScrollPane scrollPane = new JScrollPane(table);
this.add(scrollPane);
table.getSelectionModel().addListSelectionListener(this);
table.setColumnSelectionAllowed(false);
table.setRowSelectionAllowed(true);
table.setAutoCreateRowSorter(true);
FormattedDoubleComparator comparator = FormattedDoubleComparator.getInstance();
rowSorter = new TableRowSorter<PeriodAnalysisDataTableModel>(model);
for (int i = 0; i < model.getColumnCount(); i++) {
rowSorter.setComparator(i, comparator);
}
table.setRowSorter(rowSorter);
setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
add(createButtonPanel());
}
/**
* Constructor for a period analysis data table pane with a model button.
*
* @param model The period analysis table model.
* @param algorithm The period analysis algorithm.
*/
public PeriodAnalysisDataTablePane(PeriodAnalysisDataTableModel model, IPeriodAnalysisAlgorithm algorithm) {
this(model, algorithm, true);
}
protected JPanel createButtonPanel() {
JPanel buttonPane = new JPanel();
modelButton = new JButton(LocaleProps.get("CREATE_MODEL_BUTTON"));
modelButton.setEnabled(false);
modelButton.addActionListener(createModelButtonHandler());
if (!wantModelButton) {
modelButton.setVisible(false);
}
buttonPane.add(modelButton, BorderLayout.LINE_END);
return buttonPane;
}
/**
* We send a period analysis selection message when the table selection value
* has "settled". This event could be consumed by other views such as plots.
*/
public void valueChanged(ListSelectionEvent e) {
if (isValueChangeDisabled())
return;
if (e.getSource() == table.getSelectionModel() && table.getRowSelectionAllowed() && !e.getValueIsAdjusting()) {
int row = table.getSelectedRow();
if (row >= 0) {
row = table.convertRowIndexToModel(row);
PeriodAnalysisSelectionMessage message = new PeriodAnalysisSelectionMessage(this,
model.getDataPointFromRow(row), row);
message.setTag(Mediator.getParentDialogName(this));
Mediator.getInstance().getPeriodAnalysisSelectionNotifier().notifyListeners(message);
}
}
}
// Model button listener.
protected ActionListener createModelButtonHandler() {
return new ActionListener() {
public void actionPerformed(ActionEvent e) {
// TODO: add a user selected frequencies protected method
// that can be called from an abstract method that implements
// the modelling operation
// TODO: only this vcaries: userSelectedFreqs.add(dataPoint.getFrequency());
List<PeriodAnalysisDataPoint> dataPoints = new ArrayList<PeriodAnalysisDataPoint>();
int[] selectedTableRowIndices = table.getSelectedRows();
if (selectedTableRowIndices.length < 1) {
MessageBox.showMessageDialog(LocaleProps.get("CREATE_MODEL_BUTTON"), "Please select a row");
return;
}
for (int row : selectedTableRowIndices) {
int modelRow = table.convertRowIndexToModel(row);
PeriodAnalysisDataPoint dataPoint = model.getDataPointFromRow(modelRow);
dataPoints.add(dataPoint);
}
modelAction(dataPoints);
}
};
}
/**
* Modelling action. Subclasses can override.<br/>
* Note: should really make this class abstract on this method.
*
* @param dataPoints The selected data points.
*/
protected void modelAction(List<PeriodAnalysisDataPoint> dataPoints) {
final JPanel parent = this;
List<Double> userSelectedFreqs = dataPoints.stream().map(point -> point.getFrequency()).collect(Collectors.toList());
HarmonicInputDialog dialog = new HarmonicInputDialog(parent, userSelectedFreqs, freqToHarmonicsMap);
if (!dialog.isCancelled()) {
List<Harmonic> harmonics = dialog.getHarmonics();
if (!harmonics.isEmpty()) {
try {
PeriodAnalysisDerivedMultiPeriodicModel model = new PeriodAnalysisDerivedMultiPeriodicModel(
dataPoints.get(0), harmonics, algorithm);
Mediator.getInstance().performModellingOperation(model);
} catch (Exception ex) {
MessageBox.showErrorDialog(parent, "Modelling", ex.getLocalizedMessage());
}
} else {
MessageBox.showErrorDialog("Create Model", "Period list error");
}
}
}
/**
* A listener to store the latest harmonic search result in a mapping from
* (fundamental) frequency to harmonics.
*/
protected Listener<HarmonicSearchResultMessage> createHarmonicSearchResultListener() {
final PeriodAnalysisDataTablePane tablePane = this;
return new Listener<HarmonicSearchResultMessage>() {
@Override
public void update(HarmonicSearchResultMessage info) {
if (!Mediator.isMsgForDialog(Mediator.getParentDialog(tablePane), info))
return;
freqToHarmonicsMap.put(info.getDataPoint().getFrequency(), info.getHarmonics());
String id = tablePane.getTablePaneID();
String currentID = info.getIDstring();
if (currentID != null && currentID.equals(id)) {
if (info.getHarmonics().size() > 0) {
new HarmonicInfoDialog(info, tablePane);
} else {
MessageBox.showMessageDialog("Harmonics", "No top hit for this frequency");
}
}
}
@Override
public boolean canBeRemoved() {
return true;
}
};
}
/**
* Select the row in the table corresponding to the period analysis selection.
* We also enable the "refine" button.
*/
protected Listener<PeriodAnalysisSelectionMessage> createPeriodAnalysisListener() {
final Component parent = this;
return new Listener<PeriodAnalysisSelectionMessage>() {
@Override
public void update(PeriodAnalysisSelectionMessage info) {
if (!Mediator.isMsgForDialog(Mediator.getParentDialog(PeriodAnalysisDataTablePane.this), info))
return;
if (info.getSource() != parent) {
// Find data point in table.
int row = -1;
for (int i = 0; i < model.getRowCount(); i++) {
if (model.getDataPointFromRow(i).equals(info.getDataPoint())) {
row = i;
break;
}
}
// Note that the row may not correspond to anything in the
// data table, e.g. in the case of period analysis
// refinement.
if (row != -1) {
// Convert to view index!
row = table.convertRowIndexToView(row);
// Scroll to an arbitrary column (zeroth) within
// the selected row, then select that row.
// Assumption: we are specifying the zeroth cell
// within row i as an x,y coordinate relative to
// the top of the table pane.
// Note that we could call this on the scroll
// pane, which would then forward the request to
// the table pane anyway.
int colWidth = (int) table.getCellRect(row, 0, true).getWidth();
int rowHeight = table.getRowHeight(row);
table.scrollRectToVisible(new Rectangle(colWidth, rowHeight * row, colWidth, rowHeight));
boolean state = disableValueChangeEvent();
try {
table.setRowSelectionInterval(row, row);
} finally {
setValueChangedDisabledState(state);
}
enableButtons();
}
} else {
enableButtons();
}
}
@Override
public boolean canBeRemoved() {
return true;
}
};
}
/**
* Enable the buttons on this pane.
*/
protected void enableButtons() {
modelButton.setEnabled(true);
}
@Override
public void startup() {
harmonicSearchResultListener = createHarmonicSearchResultListener();
Mediator.getInstance().getHarmonicSearchNotifier().addListener(harmonicSearchResultListener);
periodAnalysisSelectionListener = createPeriodAnalysisListener();
Mediator.getInstance().getPeriodAnalysisSelectionNotifier().addListener(periodAnalysisSelectionListener);
}
@Override
public void cleanup() {
Mediator.getInstance().getHarmonicSearchNotifier().removeListenerIfWilling(harmonicSearchResultListener);
Mediator.getInstance().getPeriodAnalysisSelectionNotifier()
.removeListenerIfWilling(periodAnalysisSelectionListener);
}
public void setTablePaneID(String tablePaneID) {
this.tablePaneID = tablePaneID;
}
public String getTablePaneID() {
return tablePaneID;
}
/**
* @return the table
*/
public JTable getTable() {
return table;
}
public boolean disableValueChangeEvent() {
boolean state = valueChangedDisabledState;
valueChangedDisabledState = true;
return state;
}
public void setValueChangedDisabledState(boolean state) {
valueChangedDisabledState = state;
}
public boolean isValueChangeDisabled() {
return valueChangedDisabledState;
}
}