MeanSourcePane.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.series;

import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Enumeration;

import javax.swing.AbstractButton;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
import javax.swing.JPanel;
import javax.swing.JRadioButton;

import org.aavso.tools.vstar.data.SeriesType;
import org.aavso.tools.vstar.ui.model.plot.ObservationAndMeanPlotModel;
import org.aavso.tools.vstar.ui.pane.plot.ObservationAndMeanPlotPane;
import org.aavso.tools.vstar.util.locale.LocaleProps;

/**
 * This class represents a pane with radio buttons showing which series is to be
 * used as the source to create the means series. The source series can be
 * modified.
 */
@SuppressWarnings("serial")
public class MeanSourcePane extends JPanel implements ActionListener {

	private ObservationAndMeanPlotModel obsPlotModel;
	private ObservationAndMeanPlotPane obsPlotPane;

	private int seriesNum;
	private int lastSelectedSeriesNum;

	private ButtonGroup seriesGroup;

	private JRadioButton filteredRadioButton;
	private JRadioButton modelRadioButton;
	private JRadioButton residualsRadioButton;

	/**
	 * Constructor.
	 * 
	 * @param obsPlotModel
	 *            The plot model.
	 * @param plotPane
	 *            An observation and mean plot pane.
	 */
	public MeanSourcePane(ObservationAndMeanPlotModel obsPlotModel,
			ObservationAndMeanPlotPane plotPane) {
		super();

		this.obsPlotModel = obsPlotModel;
		this.obsPlotPane = plotPane;

		this.setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
		this.setBorder(BorderFactory.createTitledBorder(LocaleProps
				.get("MEAN_SERIES_SOURCE")));
		this
				.setToolTipText("Select series that will be the source of the means series.");

		this.seriesNum = obsPlotPane.getObsModel().getMeanSourceSeriesNum();

		addSeriesRadioButtons();
	}

	// Create a radio button for each series, selecting the one
	// that corresponds to the current mean source series.
	private void addSeriesRadioButtons() {
		JPanel panel = new JPanel();
		panel.setLayout(new BoxLayout(panel, BoxLayout.LINE_AXIS));

		seriesGroup = new ButtonGroup();

		panel.add(createDataSeriesRadioButtons());
		panel.add(createDerivedSeriesRadioButtons());
		JPanel userPanel = createUserDefinedSeriesRadioButtons();
		if (userPanel != null) {
			panel.add(userPanel);
		}
		this.add(panel);
	}

	private JPanel createDataSeriesRadioButtons() {
		JPanel panel = new JPanel();
		panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS));
		panel.setBorder(BorderFactory.createTitledBorder(LocaleProps
				.get("DATA_TITLE")));

		for (SeriesType series : this.obsPlotPane.getObsModel().getSeriesKeys()) {
			if (!series.isSynthetic() && !series.isUserDefined()) {
				String seriesName = series.getDescription();
				JRadioButton seriesRadioButton = new JRadioButton(seriesName);
				seriesRadioButton.setActionCommand(seriesName);
				seriesRadioButton.addActionListener(this);
				seriesRadioButton.setEnabled(isSeriesNonEmpty(series));
				panel.add(seriesRadioButton);
				panel.add(Box.createRigidArea(new Dimension(3, 3)));
				seriesGroup.add(seriesRadioButton);
				checkInitialMeanSourceSeries(series, seriesRadioButton);
			}
		}

		// Ensure the panel is wide enough for textual border.
		panel.add(Box.createRigidArea(new Dimension(75, 1)));

		return panel;
	}

	private JPanel createDerivedSeriesRadioButtons() {
		JPanel panel = new JPanel();
		panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS));
		panel.setBorder(BorderFactory.createTitledBorder(LocaleProps
				.get("ANALYSIS_TITLE")));

		// Filtered series.
		filteredRadioButton = new JRadioButton(SeriesType.Filtered
				.getDescription());
		filteredRadioButton.setActionCommand(SeriesType.Filtered
				.getDescription());
		filteredRadioButton.addActionListener(this);
		filteredRadioButton.setEnabled(isSeriesNonEmpty(SeriesType.Filtered));
		panel.add(filteredRadioButton);
		panel.add(Box.createRigidArea(new Dimension(3, 3)));
		seriesGroup.add(filteredRadioButton);
		checkInitialMeanSourceSeries(SeriesType.Filtered, filteredRadioButton);

		JPanel subPanel = new JPanel();
		subPanel.setLayout(new BoxLayout(subPanel, BoxLayout.PAGE_AXIS));
		subPanel.setBorder(BorderFactory.createTitledBorder(LocaleProps
				.get("MODEL_TITLE")));
		subPanel.add(Box.createRigidArea(new Dimension(75, 1)));

		// Model series.
		modelRadioButton = new JRadioButton(SeriesType.Model.getDescription());
		modelRadioButton.setActionCommand(SeriesType.Model.getDescription());
		modelRadioButton.addActionListener(this);
		modelRadioButton.setEnabled(isSeriesNonEmpty(SeriesType.Model));
		subPanel.add(modelRadioButton);
		subPanel.add(Box.createRigidArea(new Dimension(3, 3)));
		seriesGroup.add(modelRadioButton);
		checkInitialMeanSourceSeries(SeriesType.Model, modelRadioButton);

		// Residuals series.
		residualsRadioButton = new JRadioButton(SeriesType.Residuals
				.getDescription());
		residualsRadioButton.setActionCommand(SeriesType.Residuals
				.getDescription());
		residualsRadioButton.addActionListener(this);
		residualsRadioButton.setEnabled(isSeriesNonEmpty(SeriesType.Residuals));
		subPanel.add(residualsRadioButton);
		subPanel.add(Box.createRigidArea(new Dimension(3, 3)));
		seriesGroup.add(residualsRadioButton);
		checkInitialMeanSourceSeries(SeriesType.Residuals, residualsRadioButton);

		panel.add(subPanel);

		return panel;
	}

	private JPanel createUserDefinedSeriesRadioButtons() {
		JPanel panel = new JPanel();
		panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS));
		panel.setBorder(BorderFactory.createTitledBorder(LocaleProps
				.get("USER_DEFINED_TITLE")));

		boolean anyObs = false;

		for (SeriesType series : this.obsPlotPane.getObsModel().getSeriesKeys()) {
			if (series.isUserDefined()) {
				// Ignore user-defined series with no corresponding data in the
				// current dataset.
				Integer seriesNum = obsPlotModel.getSrcTypeToSeriesNumMap()
						.get(series);

				if (obsPlotModel.getSeriesNumToObSrcListMap().get(seriesNum)
						.isEmpty()) {
					continue;
				} else {
					if (!anyObs) {
						anyObs = true;
					}
				}

				String seriesName = series.getDescription();
				JRadioButton seriesRadioButton = new JRadioButton(seriesName);
				seriesRadioButton.setActionCommand(seriesName);
				seriesRadioButton.addActionListener(this);
				seriesRadioButton.setEnabled(isSeriesNonEmpty(series));
				panel.add(seriesRadioButton);
				panel.add(Box.createRigidArea(new Dimension(3, 3)));
				seriesGroup.add(seriesRadioButton);
				checkInitialMeanSourceSeries(series, seriesRadioButton);
			}
		}

		if (!anyObs) {
			panel = null;
		} else {
			// Ensure the panel is wide enough for textual border.
			panel.add(Box.createRigidArea(new Dimension(75, 1)));
		}
		
		return panel;
	}

	/**
	 * Determine whether the specified series is the current mean source series,
	 * i.e. the series from which the means series is derived. If it is, the
	 * radio button is selected and the series is recorded for this session as
	 * the last mean source series.
	 * 
	 * @param series
	 *            The series type.
	 * @param seriesRadioButton
	 *            The series radio button.
	 */
	private void checkInitialMeanSourceSeries(SeriesType series,
			JRadioButton seriesRadioButton) {
		if (obsPlotPane.getObsModel().getSrcTypeToSeriesNumMap().get(series) == obsPlotPane
				.getObsModel().getMeanSourceSeriesNum()) {
			seriesRadioButton.setSelected(true);
			lastSelectedSeriesNum = obsPlotPane.getObsModel()
					.getMeanSourceSeriesNum();
		}
	}

	/**
	 * Does the specified series have corresponding observations?
	 * 
	 * @param type
	 *            The series type in question.
	 * @return Whether or not there are observations for this series type.
	 */
	private boolean isSeriesNonEmpty(SeriesType type) {
		Integer num = obsPlotPane.getObsModel().getSrcTypeToSeriesNumMap().get(
				type);

		// This series exists and has obs (or not), so allow it to be selected
		// (or not).
		boolean hasObs = obsPlotPane.getObsModel().getSeriesNumToObSrcListMap()
				.containsKey(num)
				&& !obsPlotPane.getObsModel().getSeriesNumToObSrcListMap().get(
						num).isEmpty();

		return hasObs;
	}

	// This method will be called when a radio button is selected.
	// If the selected series is different from the model's current
	// mean source series number (in the model), set the model's mean
	// source series number.

	public void actionPerformed(ActionEvent e) {
		String seriesName = e.getActionCommand();
		this.seriesNum = obsPlotPane.getObsModel().getSrcTypeToSeriesNumMap()
				.get(SeriesType.getSeriesFromDescription(seriesName));

		if (this.seriesNum != obsPlotPane.getObsModel()
				.getMeanSourceSeriesNum()) {
			obsPlotPane.setMeanSourceSeriesNum(this.seriesNum);
			boolean changed = obsPlotPane.changeMeansSeries(obsPlotPane
					.getObsModel().getTimeElementsInBin());
			if (changed) {
				lastSelectedSeriesNum = obsPlotPane.getObsModel()
						.getMeanSourceSeriesNum();
			} else {
				// Means not changed (e.g. too few data points in selected
				// series), so restore radio buttons to last selected.
				SeriesType seriesToRevertTo = obsPlotPane.getObsModel()
						.getSeriesNumToSrcTypeMap().get(lastSelectedSeriesNum);

				Enumeration<AbstractButton> elts = seriesGroup.getElements();

				while (elts.hasMoreElements()) {
					AbstractButton radioButton = elts.nextElement();
					String label = radioButton.getActionCommand();
					if (SeriesType.getSeriesFromDescription(label) == seriesToRevertTo) {
						radioButton.setSelected(true);
						obsPlotPane
								.setMeanSourceSeriesNum(lastSelectedSeriesNum);
					}
				}
			}
		}
	}

	/**
	 * @return the seriesNum
	 */
	public int getSeriesNum() {
		return seriesNum;
	}
}