StarSelectorDialog.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;
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Calendar;
import java.util.List;
import java.util.regex.Pattern;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import org.aavso.tools.vstar.data.DateInfo;
import org.aavso.tools.vstar.data.SeriesType;
import org.aavso.tools.vstar.plugin.PluginComponentFactory;
import org.aavso.tools.vstar.ui.dialog.series.AIDSeriesSelectionPane;
import org.aavso.tools.vstar.ui.mediator.Mediator;
import org.aavso.tools.vstar.ui.mediator.message.StarGroupChangedMessage;
import org.aavso.tools.vstar.util.Pair;
import org.aavso.tools.vstar.util.date.AbstractDateUtil;
import org.aavso.tools.vstar.util.help.Help;
import org.aavso.tools.vstar.util.locale.LocaleProps;
import org.aavso.tools.vstar.util.locale.NumberParser;
import org.aavso.tools.vstar.util.notification.Listener;
import org.aavso.tools.vstar.util.prefs.NumericPrecisionPrefs;
/**
* This dialog allows the user to select a star.
*/
@SuppressWarnings("serial")
public class StarSelectorDialog extends AbstractOkCancelDialog {
private static AbstractDateUtil dateUtil = AbstractDateUtil.getInstance();
private Container contentPane;
private StarGroupSelectionPane starGroupSelectionPane;
private JTextField starField;
private JTextField minJDField;
private JTextField maxJDField;
private TextArea obsCodesField;
private TextArea velaFilterField;
private JCheckBox allDataCheckBox;
private JCheckBox additiveLoadCheckbox;
private JCheckBox minFieldsCheckbox;
// TODO: add a show counts button/checkbox that displays the counts for data
// based upon the criteria specified in a dialog similar to Info
private AIDSeriesSelectionPane aidSeriesPane;
private String starName;
private String auid;
private DateInfo minDate;
private DateInfo maxDate;
private boolean wantAllData;
private Calendar cal;
private int year, month, day;
private static Pattern whitespacePattern = Pattern.compile("^\\s*$");
// Regex pattern for AUID (AAVSO unique ID per star).
private static Pattern auidPattern = Pattern.compile("^\\d{3}\\-\\w{3}\\-\\d{3}$");
private String pluginDocName = null;
/**
* Constructor (singleton)
*/
private StarSelectorDialog() {
super(LocaleProps.get("NEW_STAR_FROM_AID_DLG_TITLE"));
this.starName = null;
this.auid = null;
this.minDate = null;
this.maxDate = null;
this.wantAllData = false;
cal = Calendar.getInstance();
// Ensure the JD is well past today by adding one day, so that recently
// added observations in AID will fall into the JD range. See ticket
// #509 Make
// default JD range slightly into future for AID loads:
// https://sourceforge.net/p/vstar/bugs-and-features/509/
cal.add(Calendar.DAY_OF_MONTH, 1);
year = cal.get(Calendar.YEAR);
month = cal.get(Calendar.MONTH) + 1; // 0..11 -> 1..12
day = cal.get(Calendar.DAY_OF_MONTH);
contentPane = this.getContentPane();
JPanel leftPane = new JPanel();
leftPane.setLayout(new BoxLayout(leftPane, BoxLayout.PAGE_AXIS));
leftPane.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
leftPane.setToolTipText("Select a star from drop-down or enter a name, AUID or alias.");
JPanel starFieldPane = createStarFieldPane();
starGroupSelectionPane = new StarGroupSelectionPane(starField, false);
leftPane.add(starGroupSelectionPane);
leftPane.add(Box.createRigidArea(new Dimension(10, 10)));
leftPane.add(starFieldPane);
leftPane.add(Box.createRigidArea(new Dimension(10, 10)));
leftPane.add(createMinJDFieldPane());
leftPane.add(Box.createRigidArea(new Dimension(10, 10)));
leftPane.add(createMaxJDFieldPane());
leftPane.add(Box.createRigidArea(new Dimension(10, 10)));
leftPane.add(createOptionsPane());
JPanel rightPane = new JPanel();
rightPane.setLayout(new BoxLayout(rightPane, BoxLayout.PAGE_AXIS));
rightPane.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
rightPane.add(createSeriesSelectionPane());
rightPane.add(createObsCodesPane());
leftPane.add(Box.createRigidArea(new Dimension(10, 10)));
rightPane.add(createVeLaFilterPane());
// Default layout manager of content pane is BorderLayout
contentPane.add(leftPane, BorderLayout.LINE_START);
contentPane.add(rightPane, BorderLayout.LINE_END);
// contentPane.add(createButtonPane(), BorderLayout.PAGE_END);
contentPane.add(createButtonPane2(), BorderLayout.PAGE_END);
// this.addWindowListener(this.createWindowListener());
this.pack();
starGroupSelectionPane.requestFocusInWindow();
}
// Getters
/**
* @return the starName; a valid value is null in the case where an auid is
* entered in the "Other Star" field rather than being selected from the
* pull-down menu.
*/
public String getStarName() {
return starName;
}
/**
* @return the auid; a valid value is null in the case where a name is entered
* in the "Other Star" field rather than being selected from the
* pull-down menu.
*/
public String getAuid() {
return auid;
}
/**
* @return the minDate
*/
public DateInfo getMinDate() {
return minDate;
}
/**
* @return the maxDate
*/
public DateInfo getMaxDate() {
return maxDate;
}
/**
* @return return whether we want all the data
*/
public boolean wantAllData() {
return wantAllData;
}
/**
* Return whether or not the load is additive.
*
* @return Whether or not the load is additive.
*/
public boolean isLoadAdditive() {
return additiveLoadCheckbox.isSelected();
}
/**
* Return whether or not to load minimal fields.
*
* @return Whether or not to load minimal fields.
*/
public boolean loadMinimalFields() {
return minFieldsCheckbox.isSelected();
}
/**
* Return the selected series.
*/
public List<SeriesType> getSelectedSeries() {
return aidSeriesPane.getSelectedSeries();
}
/**
* Return a comma-delimited string of observer codes or null if there are none.
*/
public String getObsCodes() {
String obscodes = null;
String text = obsCodesField.getValue();
if (text.trim().length() > 0) {
StringBuffer obscodesBuf = new StringBuffer();
String[] fields = text.split("\\s+");
for (int i = 0; i < fields.length; i++) {
obscodesBuf.append(fields[i].trim());
if (i < fields.length - 1) {
obscodesBuf.append(",");
}
}
obscodes = obscodesBuf.toString();
}
return obscodes;
}
/**
* Returns the content of the VeLa filter field.
*
* @return the string content of the VeLa filter field.
*/
public String getVeLaFilter() {
return velaFilterField.getValue().trim();
}
/**
* @return has the dialog been cancelled? TODO: isn't this in base class?
*/
public boolean isCancelled() {
return cancelled;
}
// Setters
/**
* Set the star field text.
*
* @param text The text to be set in the star field.
*/
public void setStarField(String text) {
starField.setText(text);
}
public void setPluginDocName(String pluginDocName) {
this.pluginDocName = pluginDocName;
}
// GUI components
private JPanel createStarFieldPane() {
JPanel panel = new JPanel();
panel.setLayout(new BoxLayout(panel, BoxLayout.LINE_AXIS));
panel.setBorder(BorderFactory.createTitledBorder(LocaleProps.get("NEW_STAR_FROM_AID_DLG_OTHER_STAR")));
starField = new JTextField();
starField.addActionListener(createStarFieldActionListener());
starField.setToolTipText("Enter star name, alias or AUID");
panel.add(starField);
return panel;
}
private JPanel createMinJDFieldPane() {
JPanel panel = new JPanel();
panel.setLayout(new BoxLayout(panel, BoxLayout.LINE_AXIS));
panel.setBorder(BorderFactory.createTitledBorder(LocaleProps.get("NEW_STAR_FROM_AID_DLG_MINIMUM_JD")));
double jd = dateUtil.calendarToJD(year - 2, month, day);
minJDField = new JTextField(NumericPrecisionPrefs.formatTime(jd));
// minJDField.addActionListener(createMinJDFieldActionListener());
// minJDField.addFocusListener(createMinJDFieldFocusListener());
minJDField.getDocument().addDocumentListener(createJDFieldDocumentListener(minJDField));
minJDField.setToolTipText(dateUtil.jdToCalendar(jd));
panel.add(minJDField);
JButton convButton = new JButton("...");
convButton.setName("ButtonMinJD");
convButton.addActionListener(createConvButtonListener());
panel.add(convButton);
return panel;
}
private JPanel createMaxJDFieldPane() {
JPanel panel = new JPanel();
panel.setLayout(new BoxLayout(panel, BoxLayout.LINE_AXIS));
panel.setBorder(BorderFactory.createTitledBorder(LocaleProps.get("NEW_STAR_FROM_AID_DLG_MAXIMUM_JD")));
double jd = dateUtil.calendarToJD(year, month, day);
maxJDField = new JTextField(NumericPrecisionPrefs.formatTime(jd));
// maxJDField.addActionListener(createMaxJDFieldActionListener());
// maxJDField.addFocusListener(createMaxJDFieldFocusListener());
maxJDField.getDocument().addDocumentListener(createJDFieldDocumentListener(maxJDField));
maxJDField.setToolTipText(dateUtil.jdToCalendar(jd));
panel.add(maxJDField);
JButton convButton = new JButton("...");
convButton.setName("ButtonMaxJD");
convButton.addActionListener(createConvButtonListener());
panel.add(convButton);
return panel;
}
private ActionListener createConvButtonListener() {
return new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (e.getSource() instanceof JButton) {
JButton b = (JButton) e.getSource();
JTextField field = null;
String dialogTitle = null;
if ("ButtonMinJD".equals(b.getName())) {
field = minJDField;
dialogTitle = LocaleProps.get("NEW_STAR_FROM_AID_DLG_MINIMUM_JD");
} else if ("ButtonMaxJD".equals(b.getName())) {
field = maxJDField;
dialogTitle = LocaleProps.get("NEW_STAR_FROM_AID_DLG_MAXIMUM_JD");
}
if (field != null) {
String dateText = field.getText();
Double date = NumberParser.parseDouble(dateText);
DateToJdDialog dlg = new DateToJdDialog(dialogTitle);
dlg.setJD(date);
dlg.showDialog();
if (!dlg.isCancelled()) {
field.setToolTipText(null);
double jd = dlg.getJD();
field.setText(NumericPrecisionPrefs.formatTime(jd));
field.setToolTipText(dateUtil.jdToCalendar(jd));
}
}
}
}
};
}
private DocumentListener createJDFieldDocumentListener(JTextField source) {
return new DocumentListener() {
private JTextField sourceField = source;
@Override
public void insertUpdate(DocumentEvent e) {
updateToolTip(e);
}
@Override
public void removeUpdate(DocumentEvent e) {
updateToolTip(e);
}
@Override
public void changedUpdate(DocumentEvent e) {
updateToolTip(e);
}
private void updateToolTip(DocumentEvent e) {
if (sourceField != null) {
String calendarString = null;
String text = sourceField.getText().trim();
if (!"".equals(text)) {
try {
double d = NumberParser.parseDouble(text);
calendarString = dateUtil.jdToCalendar(d);
} catch (Exception ex) {
calendarString = null;
}
}
sourceField.setToolTipText(calendarString);
// System.out.println("Tooltip for " + sourceField.getName() + " was set to: " +
// (calendarString == null ? "<null>" : calendarString));
}
}
};
}
private JPanel createOptionsPane() {
JPanel panel = new JPanel();
panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS));
panel.setBorder(BorderFactory.createTitledBorder("Options"));
panel.add(createAdditiveLoadCheckboxPane());
panel.add(Box.createRigidArea(new Dimension(10, 10)));
panel.add(createMinFieldsCheckBoxPane());
panel.add(Box.createRigidArea(new Dimension(10, 10)));
panel.add(createAllDataCheckBoxPane());
return panel;
}
private JPanel createMinFieldsCheckBoxPane() {
JPanel panel = new JPanel();
minFieldsCheckbox = new JCheckBox("Minimal Fields?");
panel.add(minFieldsCheckbox);
return panel;
}
private JPanel createAdditiveLoadCheckboxPane() {
JPanel panel = new JPanel();
additiveLoadCheckbox = new JCheckBox("Add to current?");
panel.add(additiveLoadCheckbox);
return panel;
}
private JPanel createAllDataCheckBoxPane() {
JPanel panel = new JPanel();
allDataCheckBox = new JCheckBox(LocaleProps.get("NEW_STAR_FROM_AID_DLG_ALL_DATA"));
allDataCheckBox.addActionListener(createAllDataCheckBoxActionListener());
panel.add(allDataCheckBox, BorderLayout.CENTER);
return panel;
}
private JPanel createSeriesSelectionPane() {
JPanel panel = new JPanel();
panel.setBorder(BorderFactory.createTitledBorder(""));
aidSeriesPane = new AIDSeriesSelectionPane();
panel.add(aidSeriesPane);
return panel;
}
private JPanel createObsCodesPane() {
Pair<TextArea, JPanel> pair = PluginComponentFactory.createTextAreaPane("Observer Codes",
"Observer codes, separated by spaces", 1, 20);
obsCodesField = pair.first;
return pair.second;
}
/**
* This component creates a VeLa Filter pane.
*/
private JPanel createVeLaFilterPane() {
Pair<TextArea, JPanel> pair = PluginComponentFactory.createVeLaFilterPane();
velaFilterField = pair.first;
return pair.second;
}
// Event handlers
// Return a listener for the star field.
private ActionListener createStarFieldActionListener() {
return new ActionListener() {
public void actionPerformed(ActionEvent e) {
// Nothing to do
}
};
}
// // Return listeners for the minimum Julian Day field.
//
// private ActionListener createMinJDFieldActionListener() {
// return new ActionListener() {
// public void actionPerformed(ActionEvent e) {
// // checkInput();
// minJDField.setToolTipText(dateUtil.jdToCalendar(NumberParser
// .parseDouble(minJDField.getText())));
// }
// };
// }
//
// private FocusListener createMinJDFieldFocusListener() {
// return new FocusListener() {
// String prevString = "";
//
// public void focusGained(FocusEvent e) {
// }
//
// public void focusLost(FocusEvent e) {
// String current = minJDField.getText();
// if (!prevString.equals(current)) {
// minJDField.setToolTipText(dateUtil
// .jdToCalendar(NumberParser.parseDouble(current)));
// prevString = current;
// }
// }
// };
// }
// Return a listener for the all-data checkbox.
private ActionListener createAllDataCheckBoxActionListener() {
return new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (allDataCheckBox.isSelected()) {
// Disable date fields to avoid usability confusion.
minJDField.setEnabled(false);
maxJDField.setEnabled(false);
} else {
// We've unchecked all data so we want to reenable fields
// leaving the values that are there.
minJDField.setEnabled(true);
maxJDField.setEnabled(true);
}
}
};
}
// // Return listeners for the maximum Julian Day field.
//
// private ActionListener createMaxJDFieldActionListener() {
// return new ActionListener() {
// public void actionPerformed(ActionEvent e) {
// // checkInput();
// maxJDField.setToolTipText(dateUtil.jdToCalendar(NumberParser
// .parseDouble(maxJDField.getText())));
// }
// };
// }
//
// private FocusListener createMaxJDFieldFocusListener() {
// return new FocusListener() {
// String prevString = "";
//
// public void focusGained(FocusEvent e) {
// }
//
// public void focusLost(FocusEvent e) {
// String current = maxJDField.getText();
// if (!prevString.equals(current)) {
// maxJDField.setToolTipText(dateUtil
// .jdToCalendar(NumberParser.parseDouble(current)));
// prevString = current;
// }
// }
// };
// }
class UpdateListener implements Listener<StarGroupChangedMessage> {
@Override
public void update(StarGroupChangedMessage info) {
starGroupSelectionPane.refreshGroups();
}
@Override
public boolean canBeRemoved() {
return false;
}
}
// Check that we have valid input in an appropriate subset
// of dialog widgets. The dialog will not be dismissed until
// there is an entry in the star text box and a date range
// or the all-data checkbox is selected.
private void checkInput() {
String text = starField.getText();
if (!whitespacePattern.matcher(text).matches()) {
// AUID or star name?
text = sanitise(text);
if (auidPattern.matcher(text).matches()) {
auid = text.trim();
} else {
starName = text.trim();
}
} else {
// There's nothing in the text field, so use the
// selected star group item. Note that by setting
// the star name, we will force the lookup of star
// info from the database.
starName = starGroupSelectionPane.getSelectedStarName();
}
// Is the all-data checkbox selected?
wantAllData = allDataCheckBox.isSelected();
if (!wantAllData) {
// Valid Julian Date range?
try {
String minJDText = minJDField.getText();
minDate = new DateInfo(NumberParser.parseDouble(minJDText));
} catch (NumberFormatException e) {
MessageBox.showErrorDialog(Mediator.getUI().getComponent(), "Minimum Julian Day", e);
}
try {
String maxJDText = maxJDField.getText();
maxDate = new DateInfo(NumberParser.parseDouble(maxJDText));
} catch (NumberFormatException e) {
MessageBox.showErrorDialog(Mediator.getUI().getComponent(), "Maximum Julian Day", e);
}
}
// Can we dismiss the dialog?
if ((starName != null || auid != null) && ((minDate != null && maxDate != null) || wantAllData)
&& !getSelectedSeries().isEmpty()) {
cancelled = false;
setVisible(false);
dispose();
}
}
private String sanitise(String str) {
return str.replace("\'", "");
}
protected void helpAction() {
Help.openPluginHelp(pluginDocName);
}
protected void cancelAction() {
// Nothing to do.
}
protected void okAction() {
checkInput();
}
/**
* Reset this dialog's state so that we don't process old state. This is invoked
* by the base class's showDialog() method.
*/
public void reset() {
// These fields will either be set to non-null values
// by checkInput() before the dialog is dismissed, or
// they will be irrelevant if wantAllData is true.
this.auid = null;
this.starName = null;
this.minDate = null;
this.maxDate = null;
this.wantAllData = false;
}
@Override
public void showDialog() {
starGroupSelectionPane.refreshGroups();
super.showDialog();
}
// Singleton
private static StarSelectorDialog instance = new StarSelectorDialog();
public static StarSelectorDialog getInstance() {
return instance;
}
}