ObservationFilterPane.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.filter;

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.JComboBox;
import javax.swing.JPanel;
import javax.swing.JTextField;

import org.aavso.tools.vstar.data.ValidObservation;
import org.aavso.tools.vstar.data.filter.IObservationFieldMatcher;
import org.aavso.tools.vstar.data.filter.ObservationFilter;
import org.aavso.tools.vstar.data.filter.ObservationMatcherOp;
import org.aavso.tools.vstar.ui.mediator.message.ObservationSelectionMessage;

/**
 * This class represents a single filter pane.
 */
@SuppressWarnings("serial")
public class ObservationFilterPane extends JPanel {

	private final static String NONE = "                                       ";
	private final static int TEXT_WIDTH = 15 * 10;

	private JComboBox filterNamesList;
	private JComboBox filterOpsList;
	// TODO: for booleans this should be a checkbox;
	// for enums a combo-box; base on currFilter.getType()
	// Need IValueWidget.{getValue() => String,setValue(String)}
	private JTextField valueField;

	private ActionListener filterOpsListener;

	private IObservationFieldMatcher currFilter;
	private ObservationMatcherOp currOp;

	private ValidObservation observation;

	/**
	 * Constructor
	 */
	public ObservationFilterPane() {
		this.setLayout(new BoxLayout(this, BoxLayout.LINE_AXIS));
		this.setBorder(BorderFactory.createEtchedBorder());

		// Create the filter name menu.
		filterNamesList = new JComboBox(new String[] { NONE });
		for (String filterName : ObservationFilter.MATCHERS.keySet()) {
			filterNamesList.addItem(filterName);
		}
		filterNamesList.addActionListener(createFilterNameListener());

		this.add(filterNamesList);

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

		// Create the filter operations menu.
		filterOpsList = new JComboBox(new String[] { NONE });
		filterOpsListener = createFilterOpsListener();
		this.add(filterOpsList);

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

		// Create the value text field.
		valueField = new JTextField();
		valueField.setMinimumSize(new Dimension(TEXT_WIDTH, 20));
		valueField.setMaximumSize(new Dimension(TEXT_WIDTH, 20));
		valueField.setPreferredSize(new Dimension(TEXT_WIDTH, 20));
		this.add(valueField);

		// JPanel p = new JPanel(new BorderLayout());
		// p.add(new JLabel(""), BorderLayout.CENTER);
		// set min/max/pref sizes then add to map of Class<?> =? IValueWidget

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

		currFilter = null;
		currOp = null;

		observation = null;
	}

	/**
	 * Return a field matcher corresponding to the selection. If no matcher is
	 * selected, null is returned. If the value of the text field does not
	 * conform to the filter's type, an exception is thrown.
	 * 
	 * @return A field matcher or null.
	 * @throws IllegalArgumentException
	 *             if the entered value does not match the type of the filter.
	 */
	public IObservationFieldMatcher getFieldMatcher()
			throws IllegalArgumentException {
		IObservationFieldMatcher matcher = null;

		if (currFilter != null) {
			matcher = currFilter.create(valueField.getText().trim(), currOp);
			if (matcher == null) {
				String msg = "Invalid " + currFilter.getDisplayName()
						+ " value: '" + valueField.getText() + "'";
				throw new IllegalArgumentException(msg);
			}
		}

		return matcher;
	}

	/**
	 * This method resets this pane's filter-related members and UI elements so
	 * that no filter is selected.
	 */
	public void resetFilter() {
		currFilter = null;
		currOp = null;
		filterNamesList.setSelectedItem(NONE);
		filterOpsList.removeAllItems();
		filterOpsList.addItem(NONE);
		valueField.setText("");
	}

	/**
	 * Tell this filter pane to set its field value now, or when a filter has
	 * been selected, using the specified observation. If null is passed and a
	 * current filter is selected, the value field is cleared.
	 * 
	 * @param msg
	 *            The observation selection message from which to extract the
	 *            observation; may be null.
	 */
	public void useObservation(ObservationSelectionMessage msg) {
		if (msg != null) {
			this.observation = msg.getObservation();
		} else {
			this.observation = null;
		}

		if (currFilter != null) {
			currFilter.setSelectedObservationMessage(msg);
			
			if (this.observation != null) {
				valueField.setText(currFilter
						.getTestValueFromObservation(this.observation));
			} else {
				valueField.setText("");
			}
		}
	}

	// Listen to the name list in order to update the operations list
	// and related values according to the operations specific to the
	// selected filter.
	private ActionListener createFilterNameListener() {
		return new ActionListener() {
			public void actionPerformed(ActionEvent e) {

				// Re-populate the operations list according to the
				// selected matcher.
				String name = (String) filterNamesList.getSelectedItem();

				if (NONE.equals(name)) {
					resetFilter();
				} else {
					IObservationFieldMatcher filter = ObservationFilter.MATCHERS
							.get(name);
					// Update operator list and current values if a different
					// matcher has been selected.
					if (filter != currFilter) {
						filterOpsList.removeAllItems();
						currFilter = filter;
						filterOpsList.removeActionListener(filterOpsListener);
						for (ObservationMatcherOp op : currFilter
								.getMatcherOps()) {
							filterOpsList.addItem(op.toString());
						}
						filterOpsList.addActionListener(filterOpsListener);
						String opName = (String) filterOpsList.getItemAt(0);
						currOp = ObservationMatcherOp.fromString(opName);
						String testValue = null;
						if (observation != null) {
							testValue = currFilter
									.getTestValueFromObservation(observation);
						} else {
							testValue = currFilter.getDefaultTestValue();
						}
						valueField
								.setText((testValue == null) ? "" : testValue);
					}
				}
			}
		};
	}

	// Listen to the operators list in order to update the
	// current operator value.
	private ActionListener createFilterOpsListener() {
		return new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				String opName = (String) filterOpsList.getSelectedItem();

				if (!NONE.equals(opName)) {
					currOp = ObservationMatcherOp.fromString(opName);
				}
			}
		};
	}
}