PeriodAnalysisTopHitsTablePane.java
/**
* VStar: a statistical analysis tool for variable star data.
* Copyright (C) 2010 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.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import javax.swing.JButton;
import javax.swing.JPanel;
import javax.swing.event.ListSelectionEvent;
import org.aavso.tools.vstar.exception.AlgorithmError;
import org.aavso.tools.vstar.ui.dialog.MessageBox;
import org.aavso.tools.vstar.ui.dialog.period.refinement.RefinementParameterDialog;
import org.aavso.tools.vstar.ui.mediator.Mediator;
import org.aavso.tools.vstar.ui.mediator.message.PeriodAnalysisRefinementMessage;
import org.aavso.tools.vstar.ui.mediator.message.PeriodAnalysisSelectionMessage;
import org.aavso.tools.vstar.ui.model.list.PeriodAnalysisDataTableModel;
import org.aavso.tools.vstar.util.notification.Listener;
import org.aavso.tools.vstar.util.period.IPeriodAnalysisAlgorithm;
import org.aavso.tools.vstar.util.period.PeriodAnalysisCoordinateType;
import org.aavso.tools.vstar.util.period.dcdft.PeriodAnalysisDataPoint;
import org.aavso.tools.vstar.util.period.dcdft.PeriodAnalysisDataPointComparator;
import org.aavso.tools.vstar.util.prefs.NumericPrecisionPrefs;
/**
* Top hits table pane.
*/
@SuppressWarnings("serial")
public class PeriodAnalysisTopHitsTablePane extends PeriodAnalysisDataTablePane {
private Set<PeriodAnalysisDataPoint> refinedDataPoints;
private Set<PeriodAnalysisDataPoint> resultantDataPoints;
private JButton refineButton;
private Listener<PeriodAnalysisRefinementMessage> periodAnalysisRefinementListener;
/**
* Constructor.
*
* @param topHitsModel The top hits data model.
* @param fullDataModel The full data data model.
* @param algorithm The period analysis algorithm.
*/
public PeriodAnalysisTopHitsTablePane(PeriodAnalysisDataTableModel topHitsModel,
PeriodAnalysisDataTableModel fullDataModel, IPeriodAnalysisAlgorithm algorithm) {
super(topHitsModel, algorithm);
refinedDataPoints = new TreeSet<PeriodAnalysisDataPoint>(PeriodAnalysisDataPointComparator.instance);
resultantDataPoints = new TreeSet<PeriodAnalysisDataPoint>(PeriodAnalysisDataPointComparator.instance);
}
protected JPanel createButtonPanel() {
JPanel buttonPane = super.createButtonPanel();
String refineName = algorithm.getRefineByFrequencyName();
if (refineName != null) {
refineButton = new JButton(refineName);
refineButton.setEnabled(false);
refineButton.addActionListener(createRefineButtonHandler());
buttonPane.add(refineButton, BorderLayout.LINE_START);
}
return buttonPane;
}
// Refine button listener.
private ActionListener createRefineButtonHandler() {
final JPanel parent = this;
return new ActionListener() {
public void actionPerformed(ActionEvent e) {
// Collect frequencies to be used in refinement, ensuring that
// we don't try to use a frequency that has already been used
// for refinement. We also do not want to use the result of a
// previous refinement.
List<Double> freqs = new ArrayList<Double>();
int[] selectedTableRowIndices = table.getSelectedRows();
List<PeriodAnalysisDataPoint> inputDataPoints = new ArrayList<PeriodAnalysisDataPoint>();
for (int row : selectedTableRowIndices) {
int modelRow = table.convertRowIndexToModel(row);
PeriodAnalysisDataPoint dataPoint = model.getDataPointFromRow(modelRow);
if (!refinedDataPoints.contains(dataPoint)) {
inputDataPoints.add(dataPoint);
freqs.add(dataPoint.getFrequency());
} else {
String msg = String.format(
"Top Hit with frequency %s" + " and power %s" + " has previously been used.",
NumericPrecisionPrefs.formatOther(dataPoint.getFrequency()),
NumericPrecisionPrefs.formatOther(dataPoint.getPower()));
MessageBox.showErrorDialog(parent, algorithm.getRefineByFrequencyName(), msg);
freqs.clear();
break;
}
if (resultantDataPoints.contains(dataPoint)) {
String msg = String.format(
"Top Hit with frequency %s" + " and power %s"
+ " was generated by %s so cannot be used.",
dataPoint.getFrequency(), dataPoint.getPower(), algorithm.getRefineByFrequencyName());
MessageBox.showErrorDialog(parent, algorithm.getRefineByFrequencyName(), msg);
freqs.clear();
break;
}
}
if (!freqs.isEmpty()) {
try {
RefinementParameterDialog dialog = new RefinementParameterDialog(parent, freqs, 6);
if (!dialog.isCancelled()) {
List<Double> variablePeriods = dialog.getVariablePeriods();
List<Double> lockedPeriods = dialog.getLockedPeriods();
// Perform a refinement operation and get the new
// top-hits resulting from the refinement.
List<PeriodAnalysisDataPoint> newTopHits = algorithm.refineByFrequency(freqs,
variablePeriods, lockedPeriods);
// Mark input frequencies as refined so we don't
// try to refine them again.
refinedDataPoints.addAll(inputDataPoints);
// Update the model and tell anyone else who might
// be interested.
Map<PeriodAnalysisCoordinateType, List<Double>> data = algorithm.getResultSeries();
Map<PeriodAnalysisCoordinateType, List<Double>> topHits = algorithm.getTopHits();
model.setData(topHits);
PeriodAnalysisRefinementMessage msg = new PeriodAnalysisRefinementMessage(this, data,
topHits, newTopHits);
msg.setTag(Mediator.getParentDialogName(PeriodAnalysisTopHitsTablePane.this));
Mediator.getInstance().getPeriodAnalysisRefinementNotifier().notifyListeners(msg);
}
} catch (AlgorithmError ex) {
MessageBox.showErrorDialog(parent, algorithm.getRefineByFrequencyName(),
ex.getLocalizedMessage());
} catch (InterruptedException ex) {
// Do nothing; just return.
}
}
}
};
}
/**
* 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(PeriodAnalysisTopHitsTablePane.this), info))
return;
if (info.getSource() != parent) {
// Find data point in top hits 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
// top hits table since there's more data in the full
// dataset than there is here!
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 {
boolean state = disableValueChangeEvent();
try {
table.clearSelection();
} finally {
setValueChangedDisabledState(state);
}
}
} else {
enableButtons();
}
}
@Override
public boolean canBeRemoved() {
return true;
}
};
}
/**
* @see org.aavso.tools.vstar.ui.dialog.period.PeriodAnalysisDataTablePane#enableButtons()
*/
@Override
protected void enableButtons() {
super.enableButtons();
if (refineButton != null) {
refineButton.setEnabled(true);
}
}
/**
* 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.
*/
@Override
public void valueChanged(ListSelectionEvent e) {
if (isValueChangeDisabled())
return;
if (e.getSource() == table.getSelectionModel() && table.getRowSelectionAllowed() && !e.getValueIsAdjusting()) {
// Which row in the top hits table was selected?
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);
}
}
}
// Create a period analysis refinement listener which adds refinement
// results to a collection that is checked to ensure that the user does not
// select them (or the originating data row) again.
private Listener<PeriodAnalysisRefinementMessage> createRefinementListener() {
return new Listener<PeriodAnalysisRefinementMessage>() {
@Override
public void update(PeriodAnalysisRefinementMessage info) {
if (!Mediator.isMsgForDialog(Mediator.getParentDialog(PeriodAnalysisTopHitsTablePane.this), info))
return;
resultantDataPoints.addAll(info.getNewTopHits());
}
@Override
public boolean canBeRemoved() {
return true;
}
};
}
/**
* @see org.aavso.tools.vstar.ui.dialog.period.PeriodAnalysisDataTablePane#startup()
*/
@Override
public void startup() {
super.startup();
periodAnalysisRefinementListener = createRefinementListener();
Mediator.getInstance().getPeriodAnalysisRefinementNotifier().addListener(periodAnalysisRefinementListener);
}
/**
* @see org.aavso.tools.vstar.ui.dialog.period.PeriodAnalysisDataTablePane#cleanup()
*/
@Override
public void cleanup() {
super.cleanup();
Mediator.getInstance().getPeriodAnalysisRefinementNotifier()
.removeListenerIfWilling(periodAnalysisRefinementListener);
}
}