PlotControlDialog.java

/**
 * VStar: a statistical analysOis 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;

import java.awt.Container;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JDialog;
import javax.swing.JPanel;
import javax.swing.JScrollPane;

import org.aavso.tools.vstar.ui.NamedComponent;
import org.aavso.tools.vstar.ui.dialog.series.MeanSourcePane;
import org.aavso.tools.vstar.ui.dialog.series.SeriesVisibilityPane;
import org.aavso.tools.vstar.ui.mediator.AnalysisType;
import org.aavso.tools.vstar.ui.mediator.DocumentManager;
import org.aavso.tools.vstar.ui.mediator.Mediator;
import org.aavso.tools.vstar.ui.model.plot.ObservationAndMeanPlotModel;
import org.aavso.tools.vstar.ui.pane.plot.ObservationAndMeanPlotPane;
import org.aavso.tools.vstar.ui.pane.plot.TimeElementsInBinSettingPane;
import org.aavso.tools.vstar.util.locale.LocaleProps;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.plot.SeriesRenderingOrder;

/**
 * A dialog that controls the features of plots.
 */
@SuppressWarnings("serial")
public class PlotControlDialog extends JDialog {

	private DocumentManager docMgr;
	
	private AnalysisType analysisType;

	// Series-related panes.
	protected SeriesVisibilityPane seriesVisibilityPane;
	protected MeanSourcePane meanSourcePane;

	// Show error bars?
	protected JCheckBox errorBarCheckBox;

	// Show cross-hairs?
	protected JCheckBox crossHairCheckBox;

	// Show inverted range?
	protected JCheckBox invertRangeCheckBox;
	
	// Show series in inverse order?
	protected JCheckBox invertSeriesOrderCheckBox;

	// Should the means series elements be joined visually?
	protected JCheckBox joinMeansCheckBox;

	protected JButton dismissButton;

	protected TimeElementsInBinSettingPane timeElementsInBinSettingPane;

	// Taken from each loaded dataset.
	protected ObservationAndMeanPlotPane plotPane;
	protected ObservationAndMeanPlotModel obsModel;
	protected NamedComponent extra;

	/**
	 * Constructor
	 * 
	 * @param title
	 *            The dialog title.
	 * @param plotPane
	 *            The plot pane.
	 * @param timeElementsInBinSettingPane
	 *            The component that captures time elements in bin setting (for
	 *            raw or phase plots).
	 * @param extra
	 *            Additional component to be added.
	 * @param analysisType
	 *            The analysis type which is the context of creation of this
	 *            dialog.
	 */
	public PlotControlDialog(String title, ObservationAndMeanPlotPane plotPane,
			TimeElementsInBinSettingPane timeElementsInBinSettingPane,
			NamedComponent extra, AnalysisType analysisType) {
		super(DocumentManager.findActiveWindow());
		this.analysisType = analysisType;

		setTitle(title);
		setModal(true);

		docMgr = Mediator.getInstance().getDocumentManager();
		
		this.plotPane = plotPane;
		this.obsModel = plotPane.getObsModel();
		this.extra = extra;

		this.timeElementsInBinSettingPane = timeElementsInBinSettingPane;

		createContent();
	}

	/**
	 * Close the dialog.
	 */
	public void close() {
		setVisible(false);
		dispose();
	}

	/**
	 * Create the dialog content.
	 */
	protected void createContent() {
		Container contentPane = this.getContentPane();

		JPanel topPane = new JPanel();
		topPane.setLayout(new BoxLayout(topPane, BoxLayout.PAGE_AXIS));

		topPane.add(createChartControlPanel(extra));
		topPane.add(Box.createRigidArea(new Dimension(75, 10)));

		topPane.add(createButtonPane());

		contentPane.add(topPane);
		this.pack();

		setLocationRelativeTo(Mediator.getUI().getContentPane());
		this.getRootPane().setDefaultButton(dismissButton);
	}

	/**
	 * Populate a panel that can contains chart control widgets.
	 * 
	 * @param extra
	 *            Additional component to be added.
	 * @return The chart control panel to be added.
	 */
	protected JPanel createChartControlPanel(NamedComponent extra) {
		JPanel chartControlPanel = new JPanel();
		chartControlPanel.setLayout(new BoxLayout(chartControlPanel,
				BoxLayout.PAGE_AXIS));
		chartControlPanel.setBorder(BorderFactory.createEtchedBorder());

		chartControlPanel.add(createSeriesChangePane());

		JPanel showCheckBoxPanel = new JPanel();
		showCheckBoxPanel.setBorder(BorderFactory
				.createTitledBorder(LocaleProps.get("SHOW_TITLE")));
		showCheckBoxPanel.setLayout(new BoxLayout(showCheckBoxPanel,
				BoxLayout.PAGE_AXIS));

		JPanel subPanel = new JPanel();
		subPanel.setLayout(new BoxLayout(subPanel, BoxLayout.LINE_AXIS));

		// A checkbox to show/hide error bars.
		errorBarCheckBox = new JCheckBox(LocaleProps.get("ERROR_BARS_CHECKBOX"));
		errorBarCheckBox.setSelected(docMgr.shouldShowErrorBars());
		errorBarCheckBox.addActionListener(createErrorBarCheckBoxListener());
		showCheckBoxPanel.add(errorBarCheckBox);

		// A checkbox to show/hide cross hairs.
		crossHairCheckBox = new JCheckBox(
				LocaleProps.get("CROSSHAIRS_CHECKBOX"));
		crossHairCheckBox.setSelected(docMgr.shouldShowCrossHairs());
		crossHairCheckBox.addActionListener(createCrossHairCheckBoxListener());
		showCheckBoxPanel.add(crossHairCheckBox);

		subPanel.add(showCheckBoxPanel);

		// A checkbox to invert (or not) the range axis.
		invertRangeCheckBox = new JCheckBox(LocaleProps.get("INVERT_RANGE"));
		invertRangeCheckBox.setSelected(docMgr.shouldInvertRange());
		invertRangeCheckBox
				.addActionListener(createInvertRangeCheckBoxListener());
		showCheckBoxPanel.add(invertRangeCheckBox);
		
		// A checkbox to invert (or not) the order of series.
		invertSeriesOrderCheckBox = new JCheckBox(LocaleProps.get("INVERT_SERIES_ORDER"));
		invertSeriesOrderCheckBox.setSelected(docMgr.shouldInvertSeriesOrder());
		invertSeriesOrderCheckBox
				.addActionListener(createInvertSeriesOrderCheckBoxListener());
		showCheckBoxPanel.add(invertSeriesOrderCheckBox);
		

		subPanel.add(showCheckBoxPanel);

		JPanel meanChangePanel = new JPanel();
		meanChangePanel.setBorder(BorderFactory.createTitledBorder(LocaleProps
				.get("MEAN_SERIES_UPDATE_TITLE")));
		meanChangePanel.setLayout(new BoxLayout(meanChangePanel,
				BoxLayout.PAGE_AXIS));

		meanChangePanel.add(Box.createRigidArea(new Dimension(75, 10)));
		
		// A checkbox to determine whether or not to join mean
		// series elements.
		joinMeansCheckBox = new JCheckBox(
				LocaleProps.get("JOIN_MEANS_CHECKBOX"));
		joinMeansCheckBox.setSelected(docMgr.shouldJoinMeans());
		joinMeansCheckBox.addActionListener(createJoinMeansCheckBoxListener());
		meanChangePanel.add(joinMeansCheckBox);

		meanChangePanel.add(Box.createRigidArea(new Dimension(75, 10)));

		// Add an update time-elements-in-bin component.
		meanChangePanel.add(timeElementsInBinSettingPane);

		subPanel.add(meanChangePanel);

		chartControlPanel.add(subPanel);

		// Add extra component, if there is one.
		if (extra != null) {
			JPanel extraPane = new JPanel();
			extraPane.setBorder(BorderFactory.createTitledBorder(extra
					.getName()));
			extraPane.add(extra.getComponent());
			chartControlPanel.add(extraPane);
		}

		return chartControlPanel;
	}

	// Creates and returns the series change (visibility and mean source) pane.
	private JPanel createSeriesChangePane() {
		JPanel seriesChangePane = new JPanel();
		seriesChangePane.setLayout(new BoxLayout(seriesChangePane,
				BoxLayout.PAGE_AXIS));
		seriesChangePane.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));

		JPanel seriesPane = new JPanel();
		seriesPane.setLayout(new BoxLayout(seriesPane, BoxLayout.LINE_AXIS));
		seriesVisibilityPane = new SeriesVisibilityPane(obsModel, analysisType);
		seriesPane.add(seriesVisibilityPane);

		meanSourcePane = new MeanSourcePane(obsModel, plotPane);
		seriesPane.add(meanSourcePane);

		seriesChangePane.add(new JScrollPane(seriesPane));

		seriesChangePane.add(Box.createRigidArea(new Dimension(10, 10)));

		return seriesChangePane;
	}

	// Returns a listener for the error bar visibility checkbox.
	private ActionListener createErrorBarCheckBoxListener() {
		return new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				toggleErrorBars();
			}
		};
	}

	// Returns a listener for the cross-hair visibility checkbox.
	private ActionListener createCrossHairCheckBoxListener() {
		return new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				toggleCrossHairs();
			}
		};
	}

	// Returns a listener for the range axis inversion checkbox.
	private ActionListener createInvertRangeCheckBoxListener() {
		return new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				toggleRangeAxisInversion();
			}
		};
	}
	
	// Returns a listener for the series order inversion checkbox.
	private ActionListener createInvertSeriesOrderCheckBoxListener() {
		return new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				toggleSeriesOrderInversion();
			}
		};
	}

	// Return a listener for the "join means visually" checkbox.
	private ActionListener createJoinMeansCheckBoxListener() {
		return new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				toggleJoinMeansSetting();
			}
		};
	}

	/**
	 * Show/hide the error bars.
	 */
	private void toggleErrorBars() {
		docMgr.toggleErrorBarState();
		plotPane.getRenderer().setDrawYError(docMgr.shouldShowErrorBars());
	}

	/**
	 * Show/hide the cross hairs.
	 */
	private void toggleCrossHairs() {
		docMgr.toggleCrossHairState();
		JFreeChart chart = plotPane.getChartPanel().getChart();
		chart.getXYPlot().setDomainCrosshairVisible(docMgr.shouldShowCrossHairs());
		chart.getXYPlot().setRangeCrosshairVisible(docMgr.shouldShowCrossHairs());
	}

	/**
	 * Invert (or not) the range axis.
	 */
	private void toggleRangeAxisInversion() {
		docMgr.toggleRangeAxisInversionState();
		plotPane.getChartPanel().getChart().getXYPlot().getRangeAxis()
				.setInverted(docMgr.shouldInvertRange());

	}
	
	/**
	 * Invert (or not) the order of series.
	 */
	private void toggleSeriesOrderInversion() {
		docMgr.toggleSeriesOrderInversionState();
		plotPane.getChartPanel().getChart().getXYPlot()
			.setSeriesRenderingOrder(docMgr.shouldInvertSeriesOrder() ? 
					SeriesRenderingOrder.REVERSE : 
						SeriesRenderingOrder.FORWARD);
	}

	// Toggle the "join means" setting which dictates whether or
	// not the means are visually joined (by lines).
	private void toggleJoinMeansSetting() {
		docMgr.toggleJoinMeansState();
		plotPane.getRenderer().setSeriesLinesVisible(
				this.obsModel.getMeansSeriesNum(), docMgr.shouldJoinMeans());
	}

	protected JPanel createButtonPane() {
		JPanel panel = new JPanel();
		panel.setLayout(new BoxLayout(panel, BoxLayout.LINE_AXIS));

		dismissButton = new JButton(LocaleProps.get("DISMISS_BUTTON"));
		dismissButton.addActionListener(createDismissButtonListener());
		panel.add(dismissButton);

		this.getRootPane().setDefaultButton(dismissButton);

		return panel;
	}

	// Returns a dismiss button listener.
	protected ActionListener createDismissButtonListener() {
		return new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				close();
			}
		};
	}
}