SyntheticObservationListPane.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.pane.list;

import java.awt.GridLayout;
import java.awt.Rectangle;

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.data.ValidObservation;
import org.aavso.tools.vstar.ui.dialog.MessageBox;
import org.aavso.tools.vstar.ui.mediator.Mediator;
import org.aavso.tools.vstar.ui.mediator.message.ObservationSelectionMessage;
import org.aavso.tools.vstar.ui.model.list.AbstractSyntheticObservationTableModel;
import org.aavso.tools.vstar.util.notification.Listener;

/**
 * This class is a component that renders synthetic observations, e.g. mean and
 * standard error of the average, model, or residual data.
 */
@SuppressWarnings("serial")
public class SyntheticObservationListPane<T extends AbstractSyntheticObservationTableModel>
		extends JPanel implements ListSelectionListener {

	private T obsTableModel;
	private JTable obsTable;
	private TableRowSorter<T> rowSorter;
	private ValidObservation lastObSelected = null;

	/**
	 * Constructor.
	 * 
	 * @param obsTableModel
	 *            The observation table model.
	 * @param summary
	 *            A summary of what the data represents (may be null).
	 */
	public SyntheticObservationListPane(T obsTableModel, String summary) {
		super(new GridLayout(1, 1));

		this.obsTableModel = obsTableModel;
		this.obsTable = new JTable(obsTableModel);

		// Enable table sorting by clicking on a column.
		rowSorter = new TableRowSorter<T>(obsTableModel);
		obsTable.setRowSorter(rowSorter);

		JScrollPane obsTableScrollPane = new JScrollPane(obsTable);
		this.add(obsTableScrollPane);

		// Listen for observation selection events. Notice that this class
		// also generates these, but ignores them if sent by itself.
		Mediator.getInstance().getObservationSelectionNotifier().addListener(
				createObservationSelectionListener());

		// List row selection handling.
		this.obsTable.getSelectionModel().addListSelectionListener(this);
	}

	/**
	 * @return the obsTableModel
	 */
	public T getObsTableModel() {
		return obsTableModel;
	}

	/**
	 * @return the obsTable
	 */
	public JTable getObsTable() {
		return obsTable;
	}

	/**
	 * @return the lastObSelected
	 */
	public ValidObservation getLastObSelected() {
		return lastObSelected;
	}

	// Returns an observation selection listener.
	private Listener<ObservationSelectionMessage> createObservationSelectionListener() {
		final JPanel parent = this;
		return new Listener<ObservationSelectionMessage>() {
			public void update(ObservationSelectionMessage message) {
				if (message.getSource() != parent) {
					ValidObservation ob = message.getObservation();
					Integer rowIndex = obsTableModel
							.getRowIndexFromObservation(ob);
					if (rowIndex != null && rowIndex >= 0) {
						try {
							// Convert to view index!
							rowIndex = obsTable.convertRowIndexToView(rowIndex);
							if (rowIndex >= 0
									&& rowIndex < obsTable.getRowCount()) {

								// 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) obsTable.getCellRect(
										rowIndex, 0, true).getWidth();
								int rowHeight = obsTable.getRowHeight(rowIndex);
								obsTable.scrollRectToVisible(new Rectangle(
										colWidth, rowHeight * rowIndex,
										colWidth, rowHeight));

								obsTable.setRowSelectionInterval(rowIndex,
										rowIndex);

								lastObSelected = ob;
							}
						} catch (ArrayIndexOutOfBoundsException e) {
							String msg = "Could not select row with index "
									+ rowIndex + " (table model: "
									+ obsTableModel.getClass().getSimpleName()
									+ ")";
							MessageBox.showMessageDialog(Mediator.getUI()
									.getComponent(),
									"Observation List Index Error", msg);
						}
					}
				}
			}

			public boolean canBeRemoved() {
				return true;
			}
		};
	}

	// List row selection event handler.
	// We send an observation selection event when the value has
	// "settled". This event could be consumed by other views such
	// as plots.
	public void valueChanged(ListSelectionEvent e) {
		if (e.getSource() == obsTable.getSelectionModel()
				&& obsTable.getRowSelectionAllowed()
				&& !e.getValueIsAdjusting()) {
			int row = obsTable.getSelectedRow();

			if (row >= 0) {
				row = obsTable.convertRowIndexToModel(row);
				ValidObservation ob = obsTableModel.getObs().get(row);
				ObservationSelectionMessage message = new ObservationSelectionMessage(
						ob, this);
				lastObSelected = ob;
				Mediator.getInstance().getObservationSelectionNotifier()
						.notifyListeners(message);
			}
		}
	}
}