HarmonicInfoDialog.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.model;

import java.awt.Component;
import java.awt.FlowLayout;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.ArrayList;
import java.util.Map;
import java.util.TreeMap;

import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

import org.aavso.tools.vstar.ui.dialog.period.PeriodAnalysis2DChartPane;
import org.aavso.tools.vstar.ui.dialog.period.PeriodAnalysisDataTablePane;
import org.aavso.tools.vstar.ui.mediator.DocumentManager;
import org.aavso.tools.vstar.ui.mediator.Mediator;
import org.aavso.tools.vstar.ui.mediator.message.HarmonicSearchResultMessage;
import org.aavso.tools.vstar.ui.model.list.PeriodAnalysisDataTableModel;
import org.aavso.tools.vstar.util.Tolerance;
import org.aavso.tools.vstar.util.model.Harmonic;
import org.aavso.tools.vstar.util.period.PeriodAnalysisCoordinateType;
import org.aavso.tools.vstar.util.period.dcdft.PeriodAnalysisDataPoint;
import org.aavso.tools.vstar.util.prefs.NumericPrecisionPrefs;
import org.jfree.chart.plot.XYPlot;

/**
 * This dialog shows harmonics found from a search for harmonics of some
 * frequency. When an entry is selected, the cross-hair of the corresponding
 * plot is moved to pin-point the frequency.
 */
@SuppressWarnings("serial")
public class HarmonicInfoDialog extends JDialog implements
		ListSelectionListener {

	private HarmonicSearchResultMessage msg;
	//private PeriodAnalysis2DChartPane plotPane;
	private Component interfaceComponent;

	private double startX, startY;
	
	private ArrayList<Integer> startIndices;

	private JList harmonicList;
	private DefaultListModel harmonicListModel;

	private Map<String, Harmonic> harmonicMap;

	/**
	 * Constructor.
	 * 
	 * @param msg
	 *            The harmonic search result message.
	 * @param interfaceComponent
	 *            plot pane or data table.
	 */
	public HarmonicInfoDialog(HarmonicSearchResultMessage msg,
			Component interfaceComponent) {
		super(DocumentManager.findActiveWindow());

		this.setTitle("Harmonics");
		this.setModal(true);

		this.msg = msg;
		
		this.interfaceComponent = interfaceComponent;
		if (interfaceComponent instanceof PeriodAnalysis2DChartPane) {
			XYPlot plot = ((PeriodAnalysis2DChartPane)interfaceComponent).getChart().getXYPlot();
			startX = plot.getDomainCrosshairValue();
			startY = plot.getRangeCrosshairValue();
		} else if (interfaceComponent instanceof PeriodAnalysisDataTablePane) {
			JTable table = ((PeriodAnalysisDataTablePane)interfaceComponent).getTable();
			 int[] indices = table.getSelectedRows();
			 startIndices = new ArrayList<Integer>();
			 for (int row : indices) {
				 startIndices.add(row);
			 }
		}
		
		this.harmonicMap = new TreeMap<String, Harmonic>();

		JPanel topPane = new JPanel();
		topPane.setLayout(new BoxLayout(topPane, BoxLayout.PAGE_AXIS));
		topPane.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));

		topPane.add(createListPane());
		topPane.add(createButtonPane());

		getContentPane().add(topPane);
		pack();

		this.addWindowListener(new WindowAdapter() {
			public void windowClosing(WindowEvent we) {
				dismiss();
			}
		});
		
		setLocationRelativeTo(Mediator.getUI().getContentPane());
		setVisible(true);
	}

	private JPanel createListPane() {
		JPanel panel = new JPanel();
		panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));

		harmonicListModel = new DefaultListModel();

		for (Harmonic harmonic : msg.getHarmonics()) {
			String label = "Frequency: " + harmonic.toString() + " ("
					+ harmonic.getHarmonicNumber() + "f), Period: "
					+ NumericPrecisionPrefs.formatOther(harmonic.getPeriod());
			harmonicListModel.addElement(label);
			harmonicMap.put(label, harmonic);
		}

		harmonicList = new JList(harmonicListModel);
		harmonicList
				.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
		harmonicList.addListSelectionListener(this);
		JScrollPane modelListScroller = new JScrollPane(harmonicList);

		panel.add(modelListScroller);

		return panel;
	}

	private JPanel createButtonPane() {
		JPanel panel = new JPanel(new FlowLayout());

		JButton dismissButton = new JButton("Dismiss");
		dismissButton.addActionListener(createDismissButtonListener());
		dismissButton.setEnabled(true);
		panel.add(dismissButton);

		JButton copyButton = new JButton("Copy");
		copyButton.addActionListener(createCopyButtonListener());
		copyButton.setEnabled(true);
		panel.add(copyButton);
		
		this.getRootPane().setDefaultButton(dismissButton);

		return panel;
	}

	// List selection listener to update button states.
	public void valueChanged(ListSelectionEvent e) {
		if (e.getValueIsAdjusting() == false) {
			int index = harmonicList.getSelectedIndex();
			if (index != -1) {
				int selectedModelIndex = harmonicList.getSelectedIndex();
				String desc = (String) harmonicListModel
						.get(selectedModelIndex);
				Harmonic harmonic = harmonicMap.get(desc);
				double x;
				
				if (interfaceComponent instanceof PeriodAnalysis2DChartPane) {
					PeriodAnalysis2DChartPane plotPane = (PeriodAnalysis2DChartPane)interfaceComponent;
					if (plotPane.getModel().getDomainType() == PeriodAnalysisCoordinateType.FREQUENCY) { 
						x = harmonic.getFrequency();
					} else if (plotPane.getModel().getDomainType() == PeriodAnalysisCoordinateType.PERIOD) { 
						x = harmonic.getPeriod();
					} else {
						return;
					}
					plotPane.setCrossHair(x, 0);
				} else if (interfaceComponent instanceof PeriodAnalysisDataTablePane) {
					selectHarmonic(((PeriodAnalysisDataTablePane)interfaceComponent).getTable(), harmonic, msg.getTolerance());
				}
			}
		}
	}
	
	private void selectHarmonic(JTable table, Harmonic harmonic, double tolerance) {
		if (table.getModel() instanceof PeriodAnalysisDataTableModel) {
			PeriodAnalysisDataTableModel model = (PeriodAnalysisDataTableModel)(table.getModel());
			Integer closestRow = null;
			Double minDiff = null;
			int n = harmonic.getHarmonicNumber();
			//System.out.println("\nselectHarmonic");
			for (int row = 0; row < model.getRowCount(); row++) {
				PeriodAnalysisDataPoint dataPoint = model.getDataPointFromRow(row);
				double f = dataPoint.getFrequency();
				// use tolerance but also look for the closest point inside the tolerance!
				if (Tolerance.areClose(f / n, harmonic.getFrequency() / n, tolerance, false)) {
					double dif = Math.abs(f - harmonic.getFrequency());
					if (minDiff == null || dif < minDiff) {
						minDiff = dif;
						closestRow = row;
						//System.out.println("minDif = " + minDiff);
					}
				}
			}
			if (closestRow != null) {
				closestRow = table.convertRowIndexToView(closestRow);				
				ensureVisible(table, closestRow);
				boolean state = ((PeriodAnalysisDataTablePane)interfaceComponent).disableValueChangeEvent();
				try {
					table.setRowSelectionInterval(closestRow, closestRow);
				} finally {
					((PeriodAnalysisDataTablePane)interfaceComponent).setValueChangedDisabledState(state);
				}
			} else {
				throw new IllegalArgumentException("Harmonic not found");
			}
		}
	}

	private void dismiss() {
		setVisible(false);
		dispose();
		// Restore the plot's cross hair or table's selection(s).
		if (interfaceComponent instanceof PeriodAnalysis2DChartPane) {
			((PeriodAnalysis2DChartPane)interfaceComponent).setCrossHair(startX, startY);
		} else if (interfaceComponent instanceof PeriodAnalysisDataTablePane) {
			JTable table = ((PeriodAnalysisDataTablePane)interfaceComponent).getTable();
			if (startIndices != null) {
				if (startIndices.size() > 0) {
					ensureVisible(table, startIndices.get(0));
				}
				boolean state = ((PeriodAnalysisDataTablePane)interfaceComponent).disableValueChangeEvent();
				try {
					table.clearSelection();
					for (int row : startIndices) {
						table.addRowSelectionInterval(row, row);
					}
				} finally {
					((PeriodAnalysisDataTablePane)interfaceComponent).setValueChangedDisabledState(state);
				}
			}
		}
	}

	private void ensureVisible(JTable table, int row) {
		int colWidth = (int) table.getCellRect(row, 0, true).getWidth();
		int rowHeight = table.getRowHeight(row);
		table.scrollRectToVisible(new Rectangle(colWidth, rowHeight * row, colWidth, rowHeight));
	}
	
	// Return a listener for the "Dismiss" button.
	private ActionListener createDismissButtonListener() {
		return new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				dismiss();
			}
		};
	}
	
	// Return a listener for the "Dismiss" button.
	private ActionListener createCopyButtonListener() {
		return new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				String s = "";
				for (int i = 0; i < harmonicListModel.size(); i++) {
					s += harmonicListModel.get(i).toString() + "\n";
				}
				Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
				clipboard.setContents(new StringSelection(s) , null);
			}
		};
	}

}