/*
* GuessDatesDialog.java
*
* Copyright (c) 2002-2015 Alexei Drummond, Andrew Rambaut and Marc Suchard
*
* This file is part of BEAST.
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership and licensing.
*
* BEAST is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* BEAST 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with BEAST; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301 USA
*/
package dr.app.beauti.tipdatepanel;
import dr.app.beauti.options.DateGuesser;
import dr.app.beauti.options.STARBEASTOptions;
import dr.app.beauti.util.TextUtil;
import dr.app.gui.components.RealNumberField;
import jam.mac.Utils;
import jam.panels.OptionsPanel;
import java.io.*;
import javax.swing.filechooser.FileNameExtensionFilter;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import java.awt.*;
import java.awt.event.*;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.prefs.Preferences;
/**
* @author Andrew Rambaut
* @author Alexei Drummond
* @version $Id: PriorDialog.java,v 1.4 2006/09/05 13:29:34 rambaut Exp $
*/
public class GuessDatesDialog {
public static Preferences PREFS = Preferences.userNodeForPackage(GuessDatesDialog.class);
public static final String DELIMIT_RADIO_KEY = "delimitRadio";
public static final String ORDER_COMBO_KEY = "orderCombo";
public static final String PREFIX_TEXT_KEY = "prefixText";
public static final String REGEX_TEXT_KEY = "regexText";
public static final String LOAD_TEXT_KEY = "loadText";
public static final String PARSE_RADIO_KEY = "parseRadio";
public static final String OFFSET_CHECK_KEY = "offsetCheck";
public static final String OFFSET_TEXT_KEY = "offsetText";
public static final String UNLESS_CHECK_KEY = "unlessCheck";
public static final String UNLESS_TEXT_KEY = "unlessText";
public static final String OFFSET2_TEXT_KEY = "offset2Text";
public static final String DATE_FORMAT_TEXT_KEY = "dateFormatText";
private JFrame frame;
private File loadFile;
private final OptionsPanel optionPanel;
private final JRadioButton orderRadio = new JRadioButton("Defined just by its order", true);
private final JComboBox orderCombo = new JComboBox(new String[]{"first", "second", "third",
"fourth", "fourth from last",
"third from last", "second from last", "last"});
private final JLabel orderLabel = new JLabel("Order:");
private final JLabel prefixLabel = new JLabel("Prefix:");
private final JRadioButton prefixRadio = new JRadioButton("Defined by a prefix and its order", false);
private final JTextField prefixText = new JTextField(16);
private final JRadioButton regexRadio = new JRadioButton("Defined by regular expression (REGEX)", false);
private final JTextField regexText = new JTextField(16);
private final JRadioButton numericalRadio = new JRadioButton("Parse as a number", true);
private final JRadioButton calendarRadio = new JRadioButton("Parse as a calendar date", true);
private final JRadioButton calendar2Radio = new JRadioButton("Parse calendar dates with variable precision", true);
private final JCheckBox offsetCheck = new JCheckBox("Add the following value to each: ", false);
private final RealNumberField offsetText = new RealNumberField();
private final JCheckBox unlessCheck = new JCheckBox("...unless less than:", false);
private final RealNumberField unlessText = new RealNumberField();
private final RealNumberField offset2Text = new RealNumberField();
private final JTextField dateFormatText = new JTextField(16);
private String description = "Guess Dates for Taxa";
private final int defaultDelimitRadioOption;
private final int defaultOrderCombo;
private final String defaultPrefixText;
private final String defaultRegexText;
private final int defaultParseRadioOption;
private final boolean defaultOffsetCheckOption;
private final String defaultOffsetText;
private final boolean defaultUnlessCheckOption;
private final String defaultUnlessText;
private final String defaultOffset2Text;
private final String defaultDateFormatText;
public GuessDatesDialog(final JFrame frame) {
this.frame = frame;
defaultDelimitRadioOption = PREFS.getInt(DELIMIT_RADIO_KEY, 0);
defaultOrderCombo = PREFS.getInt(ORDER_COMBO_KEY, 0);
defaultPrefixText = PREFS.get(PREFIX_TEXT_KEY, "");
defaultRegexText = PREFS.get(REGEX_TEXT_KEY, "");
defaultParseRadioOption = PREFS.getInt(PARSE_RADIO_KEY, 0);
defaultOffsetCheckOption = PREFS.getBoolean(OFFSET_CHECK_KEY, false);
defaultOffsetText = PREFS.get(OFFSET_TEXT_KEY, "1900");
defaultUnlessCheckOption = PREFS.getBoolean(UNLESS_CHECK_KEY, false);
defaultUnlessText = PREFS.get(UNLESS_TEXT_KEY, "16");
defaultOffset2Text = PREFS.get(OFFSET2_TEXT_KEY, "2000");
defaultDateFormatText = PREFS.get(DATE_FORMAT_TEXT_KEY, "yyyy-MM-dd");
optionPanel = new OptionsPanel(12, 12);
}
private void setupPanel(boolean parsingFromFile) {
optionPanel.removeAll();
if (parsingFromFile) {
} else {
optionPanel.addLabel("The date is given by a numerical field in the taxon label that is:");
optionPanel.addSpanningComponent(orderRadio);
// optionPanel.addSeparator();
optionPanel.addSpanningComponent(prefixRadio);
optionPanel.addComponents(orderLabel, orderCombo);
optionPanel.addComponents(prefixLabel, prefixText);
prefixLabel.setEnabled(false);
prefixText.setEnabled(false);
regexText.setEnabled(false);
optionPanel.addComponents(regexRadio, regexText);
optionPanel.addSeparator();
}
optionPanel.addSpanningComponent(numericalRadio);
offsetText.setValue(1900);
offsetText.setColumns(16);
offsetText.setEnabled(false);
optionPanel.addComponents(offsetCheck, offsetText);
Calendar calendar = GregorianCalendar.getInstance();
int year = calendar.get(Calendar.YEAR) - 1999;
unlessText.setValue(year);
unlessText.setColumns(16);
unlessText.setEnabled(false);
optionPanel.addComponents(unlessCheck, unlessText);
offset2Text.setValue(2000);
offset2Text.setColumns(16);
offset2Text.setEnabled(false);
final JLabel offset2Label = new JLabel("...in which case add:");
optionPanel.addComponents(offset2Label, offset2Text);
optionPanel.addSpanningComponent(calendarRadio);
final JLabel dateFormatLabel = new JLabel("Date format:");
final JButton helpButton = new JButton(Utils.isMacOSX() ? "" : "?");
helpButton.putClientProperty("JButton.buttonType", "help");
JPanel panel = new JPanel();
panel.add(dateFormatText);
panel.add(helpButton);
panel.setOpaque(false);
optionPanel.addComponents(dateFormatLabel, panel);
dateFormatText.setText("yyyy-MM-dd");
optionPanel.addSpanningComponent(calendar2Radio);
dateFormatLabel.setEnabled(false);
dateFormatText.setEnabled(false);
numericalRadio.setToolTipText("Parse the date field as a decimal number");
calendarRadio.setToolTipText("Parse the date field using a standard date format specification");
calendar2Radio.setToolTipText("Parse the date field yyyy[-mm[-dd]] with possibly missing month or day");
helpButton.addActionListener(new ActionListener() {
public void actionPerformed(final ActionEvent actionEvent) {
JScrollPane scrollPane = TextUtil.createHTMLScrollPane(
DATE_FORMAT_HELP, new Dimension(560,480));
JOptionPane.showMessageDialog(frame, scrollPane,
"Date format help",
JOptionPane.PLAIN_MESSAGE);
}
});
offsetCheck.addItemListener(new ItemListener() {
public void itemStateChanged(ItemEvent e) {
offsetText.setEnabled(offsetCheck.isSelected());
unlessCheck.setEnabled(offsetCheck.isSelected());
unlessText.setEnabled(offsetCheck.isSelected() && unlessCheck.isSelected());
offset2Label.setEnabled(offsetCheck.isSelected() && unlessCheck.isSelected());
offset2Text.setEnabled(offsetCheck.isSelected() && unlessCheck.isSelected());
}
});
unlessCheck.addItemListener(new ItemListener() {
public void itemStateChanged(ItemEvent e) {
unlessText.setEnabled(unlessCheck.isSelected());
offset2Label.setEnabled(unlessCheck.isSelected());
offset2Text.setEnabled(unlessCheck.isSelected());
}
});
if (!parsingFromFile) {
ButtonGroup group = new ButtonGroup();
group.add(orderRadio);
group.add(prefixRadio);
group.add(regexRadio);
orderRadio.setSelected(true);
ItemListener listener = new ItemListener() {
public void itemStateChanged(ItemEvent e) {
orderLabel.setEnabled(!regexRadio.isSelected());
orderCombo.setEnabled(!regexRadio.isSelected());
prefixLabel.setEnabled(prefixRadio.isSelected());
prefixText.setEnabled(prefixRadio.isSelected());
regexText.setEnabled(regexRadio.isSelected());
}
};
orderRadio.addItemListener(listener);
prefixRadio.addItemListener(listener);
regexRadio.addItemListener(listener);
}
ButtonGroup group = new ButtonGroup();
group.add(numericalRadio);
group.add(calendarRadio);
group.add(calendar2Radio);
ItemListener listener = new ItemListener() {
public void itemStateChanged(ItemEvent e) {
boolean nrs = numericalRadio.isSelected();
boolean ocs = offsetCheck.isSelected();
boolean ucs = unlessCheck.isSelected();
boolean crs = calendarRadio.isSelected();
offsetCheck.setEnabled(nrs);
offsetText.setEnabled(nrs && ocs);
unlessCheck.setEnabled(nrs && ocs);
unlessText.setEnabled(nrs && ocs && ucs);
offset2Label.setEnabled(nrs && ocs && ucs);
offset2Text.setEnabled(nrs && ocs && ucs);
dateFormatLabel.setEnabled(crs);
dateFormatText.setEnabled(crs);
}
};
numericalRadio.addItemListener(listener);
calendarRadio.addItemListener(listener);
calendar2Radio.addItemListener(listener);
offsetCheck.setSelected(defaultOffsetCheckOption);
offsetText.setText(defaultOffsetText);
unlessCheck.setSelected(defaultUnlessCheckOption);
unlessText.setText(defaultUnlessText);
offset2Text.setText(defaultOffset2Text);
dateFormatText.setText(defaultDateFormatText);
orderCombo.setSelectedIndex(defaultOrderCombo);
prefixText.setText(defaultPrefixText);
regexText.setText(defaultRegexText);
orderCombo.setSelectedIndex(defaultOrderCombo);
prefixText.setText(defaultPrefixText);
regexText.setText(defaultRegexText);
// set from preferences defaults...
switch (defaultDelimitRadioOption) {
case 0: orderRadio.setSelected(true); break;
case 1: prefixRadio.setSelected(true); break;
case 2: regexRadio.setSelected(true); break;
//case 3: loadRadio.setSelected(true); break; // Not allow loadRadio in prefs to avoid dialog confusion.
default: throw new IllegalArgumentException("unknown radio option");
}
switch (defaultParseRadioOption) {
case 0: numericalRadio.setSelected(true); break;
case 1: calendarRadio.setSelected(true); break;
case 2: calendar2Radio.setSelected(true); break;
default: throw new IllegalArgumentException("unknown radio option");
}
}
public int showDialog() {
return showDialog(false);
}
public int showDialog(boolean parsingFromFile) {
setupPanel(parsingFromFile);
JOptionPane optionPane = new JOptionPane(optionPanel,
JOptionPane.QUESTION_MESSAGE,
JOptionPane.OK_CANCEL_OPTION,
null,
null,
null);
optionPane.setBorder(new EmptyBorder(12, 12, 12, 12));
final JDialog dialog = optionPane.createDialog(frame, description);
dialog.pack();
dialog.setVisible(true);
int result = JOptionPane.CANCEL_OPTION;
Integer value = (Integer) optionPane.getValue();
if (value != null && value != -1) {
result = value;
setPreferencesFromDialog();
}
return result;
}
private void setPreferencesFromDialog() {
PREFS.putInt(DELIMIT_RADIO_KEY,
(orderRadio.isSelected() ? 0 :
(prefixRadio.isSelected() ? 1 :
(regexRadio.isSelected() ? 2 : -1))));
PREFS.putInt(ORDER_COMBO_KEY, orderCombo.getSelectedIndex());
PREFS.put(PREFIX_TEXT_KEY, prefixText.getText());
PREFS.put(REGEX_TEXT_KEY, regexText.getText());
PREFS.putInt(PARSE_RADIO_KEY,
(numericalRadio.isSelected() ? 0 :
(calendarRadio.isSelected() ? 1 :
(calendar2Radio.isSelected() ? 2 : -1))));
PREFS.putBoolean(OFFSET_CHECK_KEY, offsetCheck.isSelected());
PREFS.put(OFFSET_TEXT_KEY, offsetText.getText());
PREFS.putBoolean(UNLESS_CHECK_KEY, unlessCheck.isSelected());
PREFS.put(UNLESS_TEXT_KEY, unlessText.getText());
PREFS.put(OFFSET2_TEXT_KEY, offset2Text.getText());
PREFS.put(DATE_FORMAT_TEXT_KEY, dateFormatText.getText());
}
public void setupGuesser(DateGuesser guesser) {
guesser.order = orderCombo.getSelectedIndex();
guesser.fromLast = false;
if (guesser.order > 3) {
guesser.fromLast = true;
guesser.order = 8 - guesser.order - 1;
}
if (orderRadio.isSelected()) {
guesser.guessType = DateGuesser.GuessType.ORDER;
} else if (prefixRadio.isSelected()) {
guesser.guessType = DateGuesser.GuessType.PREFIX;
guesser.prefix = prefixText.getText();
} else if (regexRadio.isSelected()) {
guesser.guessType = DateGuesser.GuessType.REGEX;
guesser.regex = regexText.getText();
} else {
throw new IllegalArgumentException("unknown radio button selected");
}
guesser.parseCalendarDatesAndPrecision = calendar2Radio.isSelected();
guesser.parseCalendarDates = calendarRadio.isSelected();
guesser.calendarDateFormat = dateFormatText.getText();
guesser.offset = 0.0;
guesser.unlessLessThan = 0.0;
if (offsetCheck.isSelected()) {
guesser.offset = offsetText.getValue();
if (unlessCheck.isSelected()) {
guesser.unlessLessThan = unlessText.getValue();
guesser.offset2 = offset2Text.getValue();
}
}
}
public void setDescription(String description) {
this.description = description;
}
private static final String DATE_FORMAT_HELP =
"<h4>Date and Time Patterns</h4>\n" +
" <p>\n" +
" Date and time formats are specified by <em>date and time pattern</em>\n" +
" strings.\n" +
" Within date and time pattern strings, unquoted letters from\n" +
" <code>'A'</code> to <code>'Z'</code> and from <code>'a'</code> to\n" +
" <code>'z'</code> are interpreted as pattern letters representing the\n" +
" components of a date or time string.\n" +
" Text can be quoted using single quotes (<code>'</code>) to avoid\n" +
" interpretation.\n" +
" <code>\"''\"</code> represents a single quote.\n" +
" All other characters are not interpreted; they're simply copied into the\n" +
" output string during formatting or matched against the input string\n" +
" during parsing.\n" +
" <p>\n" +
" The following pattern letters are defined (all other characters from\n" +
" <code>'A'</code> to <code>'Z'</code> and from <code>'a'</code> to\n" +
" <code>'z'</code> are reserved):\n" +
" <blockquote>\n" +
" <table border=0 cellspacing=3 cellpadding=0 summary=\"Chart shows pattern letters, date/time component, presentation, and examples.\">\n" +
" <tr bgcolor=\"#ccccff\">\n" +
" <th align=left>Letter\n" +
" <th align=left>Date or Time Component\n" +
" <th align=left>Presentation\n" +
" <th align=left>Examples\n" +
" <tr>\n" +
" <td><code>G</code>\n" +
" <td>Era designator\n" +
" <td><a href=\"#text\">Text</a>\n" +
" <td><code>AD</code>\n" +
" <tr bgcolor=\"#eeeeff\">\n" +
" <td><code>y</code>\n" +
" <td>Year\n" +
" <td><a href=\"#year\">Year</a>\n" +
" <td><code>1996</code>; <code>96</code>\n" +
" <tr>\n" +
" <td><code>M</code>\n" +
" <td>Month in year\n" +
" <td><a href=\"#month\">Month</a>\n" +
" <td><code>July</code>; <code>Jul</code>; <code>07</code>\n" +
" <tr bgcolor=\"#eeeeff\">\n" +
" <td><code>w</code>\n" +
" <td>Week in year\n" +
" <td><a href=\"#number\">Number</a>\n" +
" <td><code>27</code>\n" +
" <tr>\n" +
" <td><code>W</code>\n" +
" <td>Week in month\n" +
" <td><a href=\"#number\">Number</a>\n" +
" <td><code>2</code>\n" +
" <tr bgcolor=\"#eeeeff\">\n" +
" <td><code>D</code>\n" +
" <td>Day in year\n" +
" <td><a href=\"#number\">Number</a>\n" +
" <td><code>189</code>\n" +
" <tr>\n" +
" <td><code>d</code>\n" +
" <td>Day in month\n" +
" <td><a href=\"#number\">Number</a>\n" +
" <td><code>10</code>\n" +
" <tr bgcolor=\"#eeeeff\">\n" +
" <td><code>F</code>\n" +
" <td>Day of week in month\n" +
" <td><a href=\"#number\">Number</a>\n" +
" <td><code>2</code>\n" +
" <tr>\n" +
" <td><code>E</code>\n" +
" <td>Day in week\n" +
" <td><a href=\"#text\">Text</a>\n" +
" <td><code>Tuesday</code>; <code>Tue</code>\n" +
" <tr bgcolor=\"#eeeeff\">\n" +
" <td><code>a</code>\n" +
" <td>Am/pm marker\n" +
" <td><a href=\"#text\">Text</a>\n" +
" <td><code>PM</code>\n" +
" <tr>\n" +
" <td><code>H</code>\n" +
" <td>Hour in day (0-23)\n" +
" <td><a href=\"#number\">Number</a>\n" +
" <td><code>0</code>\n" +
" <tr bgcolor=\"#eeeeff\">\n" +
" <td><code>k</code>\n" +
" <td>Hour in day (1-24)\n" +
" <td><a href=\"#number\">Number</a>\n" +
" <td><code>24</code>\n" +
" <tr>\n" +
" <td><code>K</code>\n" +
" <td>Hour in am/pm (0-11)\n" +
" <td><a href=\"#number\">Number</a>\n" +
" <td><code>0</code>\n" +
" <tr bgcolor=\"#eeeeff\">\n" +
" <td><code>h</code>\n" +
" <td>Hour in am/pm (1-12)\n" +
" <td><a href=\"#number\">Number</a>\n" +
" <td><code>12</code>\n" +
" <tr>\n" +
" <td><code>m</code>\n" +
" <td>Minute in hour\n" +
" <td><a href=\"#number\">Number</a>\n" +
" <td><code>30</code>\n" +
" <tr bgcolor=\"#eeeeff\">\n" +
" <td><code>s</code>\n" +
" <td>Second in minute\n" +
" <td><a href=\"#number\">Number</a>\n" +
" <td><code>55</code>\n" +
" <tr>\n" +
" <td><code>S</code>\n" +
" <td>Millisecond\n" +
" <td><a href=\"#number\">Number</a>\n" +
" <td><code>978</code>\n" +
" <tr bgcolor=\"#eeeeff\">\n" +
" <td><code>z</code>\n" +
" <td>Time zone\n" +
" <td><a href=\"#timezone\">General time zone</a>\n" +
" <td><code>Pacific Standard Time</code>; <code>PST</code>; <code>GMT-08:00</code>\n" +
" <tr>\n" +
" <td><code>Z</code>\n" +
" <td>Time zone\n" +
" <td><a href=\"#rfc822timezone\">RFC 822 time zone</a>\n" +
" <td><code>-0800</code>\n" +
" </table>\n" +
" </blockquote>\n" +
" Pattern letters are usually repeated, as their number determines the\n" +
" exact presentation:\n" +
" <ul>\n" +
" <li><strong><a name=\"text\">Text:</a></strong>\n" +
" For formatting, if the number of pattern letters is 4 or more,\n" +
" the full form is used; otherwise a short or abbreviated form\n" +
" is used if available.\n" +
" For parsing, both forms are accepted, independent of the number\n" +
" of pattern letters.\n" +
" <li><strong><a name=\"number\">Number:</a></strong>\n" +
" For formatting, the number of pattern letters is the minimum\n" +
" number of digits, and shorter numbers are zero-padded to this amount.\n" +
" For parsing, the number of pattern letters is ignored unless\n" +
" it's needed to separate two adjacent fields.\n" +
" <li><strong><a name=\"year\">Year:</a></strong>\n" +
" If the formatter's <A HREF=\"../../java/text/DateFormat.html#getCalendar()\"><CODE>Calendar</CODE></A> is the Gregorian\n" +
" calendar, the following rules are applied.<br>\n" +
" <ul>\n" +
" <li>For formatting, if the number of pattern letters is 2, the year\n" +
" is truncated to 2 digits; otherwise it is interpreted as a\n" +
" <a href=\"#number\">number</a>.\n" +
" <li>For parsing, if the number of pattern letters is more than 2,\n" +
" the year is interpreted literally, regardless of the number of\n" +
" digits. So using the pattern \"MM/dd/yyyy\", \"01/11/12\" parses to\n" +
" Jan 11, 12 A.D.\n" +
" <li>For parsing with the abbreviated year pattern (\"y\" or \"yy\"),\n" +
" <code>SimpleDateFormat</code> must interpret the abbreviated year\n" +
" relative to some century. It does this by adjusting dates to be\n" +
" within 80 years before and 20 years after the time the <code>SimpleDateFormat</code>\n" +
" instance is created. For example, using a pattern of \"MM/dd/yy\" and a\n" +
" <code>SimpleDateFormat</code> instance created on Jan 1, 1997, the string\n" +
" \"01/11/12\" would be interpreted as Jan 11, 2012 while the string \"05/04/64\"\n" +
" would be interpreted as May 4, 1964.\n" +
" During parsing, only strings consisting of exactly two digits, as defined by\n" +
" <A HREF=\"../../java/lang/Character.html#isDigit(char)\"><CODE>Character.isDigit(char)</CODE></A>, will be parsed into the default century.\n" +
" Any other numeric string, such as a one digit string, a three or more digit\n" +
" string, or a two digit string that isn't all digits (for example, \"-1\"), is\n" +
" interpreted literally. So \"01/02/3\" or \"01/02/003\" are parsed, using the\n" +
" same pattern, as Jan 2, 3 AD. Likewise, \"01/02/-3\" is parsed as Jan 2, 4 BC.\n" +
" </ul>\n" +
" Otherwise, calendar system specific forms are applied.\n" +
" For both formatting and parsing, if the number of pattern\n" +
" letters is 4 or more, a calendar specific <A HREF=\"../../java/util/Calendar.html#LONG\">long form</A> is used. Otherwise, a calendar\n" +
" specific <A HREF=\"../../java/util/Calendar.html#SHORT\">short or abbreviated form</A>\n" +
" is used.\n" +
" <li><strong><a name=\"month\">Month:</a></strong>\n" +
" If the number of pattern letters is 3 or more, the month is\n" +
" interpreted as <a href=\"#text\">text</a>; otherwise,\n" +
" it is interpreted as a <a href=\"#number\">number</a>.\n" +
" </ul>";
}