package edu.cmu.cs.eyetrack.gui;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import javax.swing.AbstractButton;
import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.ComboBoxModel;
import javax.swing.DefaultComboBoxModel;
import javax.swing.DefaultListCellRenderer;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JSpinner;
import javax.swing.JTextField;
import javax.swing.ListCellRenderer;
import javax.swing.SpinnerNumberModel;
import javax.swing.SpringLayout;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.filechooser.FileFilter;
import com.toedter.calendar.JDateChooser;
import edu.cmu.cs.eyetrack.gui.shapes.Stimulus;
import edu.cmu.cs.eyetrack.gui.shapes.Stimulus.StimulusClass;
import edu.cmu.cs.eyetrack.gui.shapes.StimulusFactory;
import edu.cmu.cs.eyetrack.gui.shapes.StimulusFactory.StimulusType;
import edu.cmu.cs.eyetrack.helper.SpringUtilities;
import edu.cmu.cs.eyetrack.helper.Util;
import edu.cmu.cs.eyetrack.helper.Util.PanelID;
import edu.cmu.cs.eyetrack.io.ResourceLoader;
import edu.cmu.cs.eyetrack.io.TestRecord;
import edu.cmu.cs.eyetrack.state.ColoradoTypedTrial;
import edu.cmu.cs.eyetrack.state.GameState;
import edu.cmu.cs.eyetrack.state.RandomGen;
import edu.cmu.cs.eyetrack.state.Settings;
import edu.cmu.cs.eyetrack.state.Settings.Gender;
import edu.cmu.cs.eyetrack.state.Settings.MotionConstraintType;
import edu.cmu.cs.eyetrack.state.Settings.MotionInterpolationType;
import edu.cmu.cs.eyetrack.state.Settings.TrialType;
@SuppressWarnings("serial")
public class StartMenuScreen extends Screen {
private String[] genderList = { "Male", "Female", "N/A" };
private String[] trialTypes = { "All Same", "All Different", "Same as Target" };
private String dateFormat = "MM/dd/yyyy";
private JTextField txtName, txtTestLocation, txtImageDirectory;
private JDateChooser datBirthdate, datTestDate;
private JComboBox cbxGender, cbxTrialType, cbxTargetType, cbxTargetColor;
private JSpinner spnDistractors, spnTrialCount, spnObjectSpeed, spnMinTrialLength, spnFramesPerSecond, spnSeed, spnGridXSize, spnGridYSize;
private JCheckBox chkRandomTarget, chkRandomWithReplacement, chkUseBackgroundImages, chkUseFullscreen;
private JRadioButton rdoMemCheckNone, rdoMemCheck2x2, rdoMemCheckNxN, rdoMotionPixel, rdoMotionGrid, rdoMotionLinear, rdoMotionRandom, rdoShapeTypeCMU, rdoShapeTypeUColorado;
public StartMenuScreen(EyeTrack owner, PanelID nextScreen) {
super(owner, nextScreen);
}
@Override
protected void initialize() {
ImageIcon iiLogoCMU = ResourceLoader.getInstance().getImageIcon("images/cmu_thumb_75.png");
JLabel lblCMU = new JLabel(iiLogoCMU);
//
// Initialize all the "ask for user's data" components
//
// User's name:
JLabel lblName = new JLabel("Name:", JLabel.TRAILING);
txtName = new JTextField(50);
txtName.setText("Default User");
txtName.addFocusListener(new SelectOnceFocusListener());
lblName.setLabelFor(txtName);
// User's gender, limited selection, defaults to Male
JLabel lblGender = new JLabel("Gender:", JLabel.TRAILING);
cbxGender = new JComboBox(genderList);
cbxGender.setSelectedIndex(0);
lblGender.setLabelFor(cbxGender);
// User's birthday (i.e., age). Defaults to Unix time 0
JLabel lblBirthdate = new JLabel("Birthdate:", JLabel.TRAILING);
datBirthdate = new JDateChooser();
datBirthdate.setDateFormatString(dateFormat);
datBirthdate.setDate(new Date(0));
lblBirthdate.setLabelFor(datBirthdate);
// When did this test occur? Defaults to today's date.
JLabel lblTestDate = new JLabel("Test Date:", JLabel.TRAILING);
datTestDate = new JDateChooser();
datTestDate.setDateFormatString(dateFormat);
datTestDate.setDate(new Date());
lblTestDate.setLabelFor(datTestDate);
// Where did this test occur? Defaults to CMU's labs.
JLabel lblTestLocation = new JLabel("Test Location:", JLabel.TRAILING);
txtTestLocation = new JTextField(50);
txtTestLocation.setText("Cognitive Development Lab");
txtTestLocation.addFocusListener(new SelectOnceFocusListener());
lblTestLocation.setLabelFor(txtTestLocation);
//
// Initialize all the components that deal with this specific test
//
// How many distractors are on the screen?
// Default value: 4; Minimum value: 0; Maximum value: 8; Increment: 1
JLabel lblDistractors = new JLabel("Distractors:", JLabel.TRAILING);
spnDistractors = new JSpinner(new SpinnerNumberModel(6, 0, 8, 1));
lblDistractors.setLabelFor(spnDistractors);
// What do the distractors look like (e.g., same shape as main object)?
JLabel lblTrialType = new JLabel("Trial Type:", JLabel.TRAILING);
cbxTrialType = new JComboBox(trialTypes);
cbxTrialType.setSelectedIndex(0);
lblTrialType.setLabelFor(cbxTrialType);
// How many trials (in a row) should we run?
// Default value: 4; Minimum value: 1; Maximum value: none; Increment: 1
JLabel lblTrialCount = new JLabel("Trial Count:", JLabel.TRAILING);
spnTrialCount = new JSpinner(new SpinnerNumberModel(3, 1, null, 1));
lblTrialCount.setLabelFor(spnTrialCount);
// Do we want a random target type/color per trial, or user-set?
// Need a different model for CMU, UColorado's shapes
final Map<Stimulus.StimulusClass, ComboBoxModel> nameModels = new HashMap<Stimulus.StimulusClass, ComboBoxModel>();
final Map<Stimulus.StimulusClass, ComboBoxModel> colorModels = new HashMap<Stimulus.StimulusClass, ComboBoxModel>();
GameState gameState = owner.getGameState();
for(Stimulus.StimulusClass stimulusClass : Arrays.asList( Stimulus.StimulusClass.values() )) {
// Load this subset of shapes and colors
StimulusFactory.getInstance().reset();
gameState.registerStimuli(stimulusClass);
// Make a model for the JComboBoxes based on these shapes
Map<String, Stimulus> nameMap = StimulusFactory.getInstance().getAllOfType(StimulusType.TARGET);
String[] targetTypes = new String[nameMap.size()];
nameMap.keySet().toArray(targetTypes);
ComboBoxModel cbxModel = new DefaultComboBoxModel(targetTypes);
nameModels.put(stimulusClass, cbxModel);
// Make a model for the JComboBoxes based on colors (Gray for UC, rainbow for CMU)
if(stimulusClass.equals(StimulusClass.UCOLORADO)) {
colorModels.put(stimulusClass, new DefaultComboBoxModel(new Color[] { Color.gray } ));
} else {
Color[] targetColors = new Color[RandomGen.getColorList().size()];
RandomGen.getColorList().toArray(targetColors);
colorModels.put(stimulusClass, new DefaultComboBoxModel(targetColors));
}
}
// UColorado now wants "Type 1" and "Type 2" trials
final ComboBoxModel uColoradoTypedTrialModel = new DefaultComboBoxModel(ColoradoTypedTrial.TRIAL_TYPE.values());
final JLabel lblTargetType = new JLabel("Type:", JLabel.TRAILING);
cbxTargetType = new JComboBox( nameModels.get(Stimulus.StimulusClass.CMU) );
cbxTargetType.setSelectedIndex((new Random()).nextInt(cbxTargetType.getItemCount()));
final JLabel lblTargetColor = new JLabel("Color:", JLabel.TRAILING);
cbxTargetColor = new JComboBox(colorModels.get(Stimulus.StimulusClass.CMU));
cbxTargetColor.setSelectedIndex((new Random()).nextInt(cbxTargetColor.getItemCount()));
cbxTargetColor.setRenderer(new ColorCellRenderer());
boolean targetEnabled = false; //Util.CMU_ONLY;
lblTargetType.setEnabled(targetEnabled); lblTargetColor.setEnabled(targetEnabled);
cbxTargetType.setEnabled(targetEnabled); cbxTargetColor.setEnabled(targetEnabled);
// Random Targets selected -> no dropdowns for specific target color/types; and vice versa
final JLabel lblRandomTarget = new JLabel("Target:", JLabel.TRAILING);
chkRandomTarget = new JCheckBox("Random?");
chkRandomTarget.setSelected( !cbxTargetType.isEnabled() );
chkRandomTarget.addItemListener(new ItemListener() {
//@Override //TODO Java 1.5 screams about this; remove when not caring about Java 1.5
public void itemStateChanged(ItemEvent e) {
cbxTargetType.setEnabled(!chkRandomTarget.isSelected());
lblTargetType.setEnabled(!chkRandomTarget.isSelected());
cbxTargetColor.setEnabled(!chkRandomTarget.isSelected() && !rdoShapeTypeUColorado.isSelected());
lblTargetColor.setEnabled(!chkRandomTarget.isSelected() && !rdoShapeTypeUColorado.isSelected());
chkRandomWithReplacement.setEnabled(chkRandomTarget.isSelected());
sanitizeNumTrials();
}
});
lblRandomTarget.setLabelFor(chkRandomTarget);
// If we're doing random target, sample with or without replacement?
chkRandomWithReplacement = new JCheckBox("Replace?");
chkRandomWithReplacement.setSelected( true );
chkRandomWithReplacement.addItemListener(new ItemListener() {
//@Override //TODO Java 1.5 screams about this; remove when not caring about Java 1.5
public void itemStateChanged(ItemEvent e) {
sanitizeNumTrials();
}
});
// Use either the original CMU shapes or the new ones from Sabine
final JLabel lblShapeType = new JLabel(
"<html>Stimuli:</html>",
JLabel.TRAILING);
ButtonGroup shapeTypeGroup = new ButtonGroup();
rdoShapeTypeCMU = new JRadioButton("CMU");
rdoShapeTypeUColorado = new JRadioButton("Colorado");
shapeTypeGroup.add(rdoShapeTypeCMU);
shapeTypeGroup.add(rdoShapeTypeUColorado);
rdoShapeTypeCMU.setSelected(true);
lblShapeType.setLabelFor(rdoShapeTypeCMU);
// Adjust dropdown full of shapes to be either CMU-only or Colorado-only
rdoShapeTypeCMU.addItemListener(new ItemListener() {
//@Override //TODO Java 1.5 screams about this; remove when not caring about Java 1.5
public void itemStateChanged(ItemEvent e) {
cbxTargetType.setModel( nameModels.get(Stimulus.StimulusClass.CMU) );
cbxTargetColor.setModel( colorModels.get(Stimulus.StimulusClass.CMU) );
if(chkRandomTarget.isSelected()) {
cbxTargetType.setSelectedIndex((new Random()).nextInt(cbxTargetType.getItemCount()));
}
cbxTargetColor.setEnabled(true);
lblTargetColor.setEnabled(true);
}
});
rdoShapeTypeUColorado.addItemListener(new ItemListener() {
//@Override //TODO Java 1.5 screams about this; remove when not caring about Java 1.5
public void itemStateChanged(ItemEvent e) {
//cbxTargetType.setModel( nameModels.get(Stimulus.StimulusClass.UCOLORADO) );
cbxTargetType.setModel(uColoradoTypedTrialModel);
cbxTargetColor.setModel( colorModels.get(Stimulus.StimulusClass.UCOLORADO) );
if(chkRandomTarget.isSelected()) {
cbxTargetType.setSelectedIndex((new Random()).nextInt(cbxTargetType.getItemCount()));
}
cbxTargetColor.setEnabled(false); // can't select colors for UColorado case
lblTargetColor.setEnabled(false);
}
});
// How fast should the objects (distractors and main object) move?
// Pixels per second
JLabel lblObjectSpeed = new JLabel(
"<html>Object Speed:<br><em>Pixels per second</em></html>",
JLabel.TRAILING);
spnObjectSpeed = new JSpinner(new SpinnerNumberModel(500, 1, null, 50));
lblObjectSpeed.setLabelFor(spnObjectSpeed);
// Trial lengths are randomized, but must be at least this long
// Measured in milliseconds
JLabel lblMinTrialLength = new JLabel(
"<html>Min. Trial<br>Length <em>(ms)</em>:</html>",
JLabel.TRAILING);
spnMinTrialLength = new JSpinner(new SpinnerNumberModel(10000, 1, null, 1000));
lblMinTrialLength.setLabelFor(spnMinTrialLength);
// How many frames per second should we update? This might need to be changed
// for different eyetrackers, eventually
JLabel lblFramesPerSecond = new JLabel(
"<html>Frames Per<br>Second:</html>",
JLabel.TRAILING);
spnFramesPerSecond = new JSpinner(new SpinnerNumberModel(30, 1, 120, 1));
lblFramesPerSecond.setLabelFor(spnFramesPerSecond);
// For now, we won't use this.
//spnFramesPerSecond.setEnabled(false);
//lblFramesPerSecond.setEnabled(false);
// The random movement of each object is controlled by a seed; for repetition's
// sake, allow the user to manually set a seed if desired.
JLabel lblSeed = new JLabel("Seed:", JLabel.TRAILING);
spnSeed = new JSpinner(new SpinnerNumberModel(System.currentTimeMillis(), 0, Long.MAX_VALUE, 1));
lblSeed.setLabelFor(spnSeed);
// Number of width boxes x height boxes on our testing grid
JLabel lblGridXSize = new JLabel("Grid Width:", JLabel.TRAILING);
JLabel lblGridYSize = new JLabel("Grid Height:", JLabel.TRAILING);
spnGridXSize = new JSpinner(new SpinnerNumberModel(3,1,10,1));
spnGridXSize.addChangeListener(new ChangeListener() {
//@Override //TODO Java 1.5 screams about this; remove when not caring about Java 1.5
public void stateChanged(ChangeEvent arg0) {
sanitizeNumDistractors();
}
});
spnGridYSize = new JSpinner(new SpinnerNumberModel(3,1,10,1));
spnGridYSize.addChangeListener(new ChangeListener() {
//@Override //TODO Java 1.5 screams about this; remove when not caring about Java 1.5
public void stateChanged(ChangeEvent arg0) {
sanitizeNumDistractors();
}
});
lblGridXSize.setLabelFor(spnGridXSize);
lblGridYSize.setLabelFor(spnGridYSize);
sanitizeNumDistractors();
// Should we use images in the background?
// If yes, enable the directory selection; if no, disable it.
JLabel lblUseBackgroundImages = new JLabel("Background:", JLabel.TRAILING);
final JLabel lblImageDirectory = new JLabel("Image Directory:", JLabel.TRAILING);
chkUseBackgroundImages = new JCheckBox("Use Images", false);
chkUseBackgroundImages.addChangeListener(new ChangeListener() {
//@Override //TODO Java 1.5 screams about this; remove when not caring about Java 1.5
public void stateChanged(ChangeEvent e) {
txtImageDirectory.setEnabled(chkUseBackgroundImages.isSelected());
txtImageDirectory.setForeground(Color.BLACK);
lblImageDirectory.setEnabled(chkUseBackgroundImages.isSelected());
if(txtImageDirectory.isEnabled() && txtImageDirectory.getText().isEmpty()) {
//txtImageDirectory.setText("<html><font color=#FF0000>Click to Set.</font></html>");\
txtImageDirectory.setText("Click to Set.");
}
}
});
lblUseBackgroundImages.setLabelFor(chkUseBackgroundImages);
// Load images for trials from a user-specified folder
// When the user selects the text box, pop up a directory browser to specify
// which directory to use for images
txtImageDirectory = new JTextField();
txtImageDirectory.setEnabled(chkUseBackgroundImages.isSelected());
txtImageDirectory.setEditable(false);
txtImageDirectory.addMouseListener(new DirectorySelectionListener(txtImageDirectory));
lblImageDirectory.setEnabled(chkUseBackgroundImages.isSelected());
lblImageDirectory.setLabelFor(txtImageDirectory);
//
JLabel lblUseMemoryCheck = new JLabel(
"<html>Memory<br>Check:</html>",
JLabel.TRAILING);
ButtonGroup memCheckGroup = new ButtonGroup();
rdoMemCheckNone = new JRadioButton("None");
rdoMemCheck2x2 = new JRadioButton("2x2");
rdoMemCheckNxN = new JRadioButton("NxN");
memCheckGroup.add(rdoMemCheckNone);
memCheckGroup.add(rdoMemCheck2x2);
memCheckGroup.add(rdoMemCheckNxN);
rdoMemCheck2x2.setSelected(true);
lblUseMemoryCheck.setLabelFor(rdoMemCheckNone);
JLabel lblUseFullscreen = new JLabel("", JLabel.TRAILING);
chkUseFullscreen = new JCheckBox("Fullscreen Trials", true);
lblUseFullscreen.setLabelFor(chkUseFullscreen);
// Constrain targets/distractors to trajectory changes on pixel-level, or center-of-grid level
JLabel lblMotionConstraint = new JLabel(
"<html>Motion<br>Constraint:</html>",
JLabel.TRAILING);
ButtonGroup motionConstraintGroup = new ButtonGroup();
rdoMotionPixel = new JRadioButton("Pixel");
rdoMotionGrid = new JRadioButton("Grid Center");
motionConstraintGroup.add(rdoMotionPixel);
motionConstraintGroup.add(rdoMotionGrid);
rdoMotionGrid.setSelected(true);
lblMotionConstraint.setLabelFor(rdoMotionPixel);
// Constrain targets/distractors to trajectory changes on pixel-level, or center-of-grid level
JLabel lblMotionInterpolation = new JLabel(
"<html>Motion<br>Interpolation:</html>",
JLabel.TRAILING);
ButtonGroup motionInterpGroup = new ButtonGroup();
rdoMotionLinear = new JRadioButton("Linear");
rdoMotionRandom = new JRadioButton("Random");
motionInterpGroup.add(rdoMotionLinear);
motionInterpGroup.add(rdoMotionRandom);
rdoMotionLinear.setSelected(true);
lblMotionInterpolation.setLabelFor(rdoMotionLinear);
//
// Exits on press; later in the experiment, Escape exits. Escape does not exit
// here because of all the spinners and text boxes taking focus.
//
JButton btnExit = new JButton("Exit");
btnExit.setVerticalTextPosition(AbstractButton.CENTER);
btnExit.setHorizontalTextPosition(AbstractButton.CENTER);
btnExit.setMnemonic(KeyEvent.VK_E);
btnExit.setToolTipText("Click to exit the program.");
btnExit.addActionListener(new ActionListener() {
//@Override //TODO Java 1.5 screams about this; remove when not caring about Java 1.5
public void actionPerformed(ActionEvent arg0) {
owner.killAndQuit();
}
});
//
// Upon press, button sets parameters and starts the test.
//
JButton btnStart = new JButton("Begin");
btnStart.setVerticalTextPosition(AbstractButton.CENTER);
btnStart.setHorizontalTextPosition(AbstractButton.CENTER);
btnStart.setMnemonic(KeyEvent.VK_B);
btnStart.setToolTipText("Click to begin testing.");
btnStart.addActionListener(new BeginButtonListener());
//
// After initializing all the objects, we can deal with how
// and where to place them on this screen
//
// One segment of the GUI deals only with the user's data; this is its own panel
JPanel userDataPanel = new JPanel(new SpringLayout());
userDataPanel.setBorder(BorderFactory.createTitledBorder("User Data"));
int numRows = 0;
userDataPanel.add(lblName); userDataPanel.add(txtName); numRows++;
userDataPanel.add(lblGender); userDataPanel.add(cbxGender); numRows++;
userDataPanel.add(lblBirthdate); userDataPanel.add(datBirthdate); numRows++;
userDataPanel.add(lblTestDate); userDataPanel.add(datTestDate); numRows++;
userDataPanel.add(lblTestLocation); userDataPanel.add(txtTestLocation); numRows++;
SpringUtilities.makeCompactGrid(userDataPanel,
numRows, 2, //rows, cols
6, 6, //initX, initY
6, 6); //xPad, yPad
// Another segment of the GUI deals only with the test trial's data; this is its own panel
JPanel trialDataPanel = new JPanel(new SpringLayout());
trialDataPanel.setBorder(BorderFactory.createTitledBorder("Trial Data"));
numRows = 0;
trialDataPanel.add(lblDistractors); trialDataPanel.add(spnDistractors); numRows++;
trialDataPanel.add(lblObjectSpeed); trialDataPanel.add(spnObjectSpeed); numRows++;
trialDataPanel.add(lblTrialType); trialDataPanel.add(cbxTrialType); numRows++;
trialDataPanel.add(lblTrialCount); trialDataPanel.add(spnTrialCount); numRows++;
if(Util.CMU_ONLY) {
trialDataPanel.add(new JPanel(new GridLayout(0,1)){{
add(lblRandomTarget); add(lblShapeType);
}});
trialDataPanel.add(new JPanel(new GridLayout(0,1)){{
add(new JPanel(new GridLayout(1,0)) {{
add(chkRandomTarget); add(chkRandomWithReplacement);
}});
add(new JPanel(new GridLayout(1,0)){{ add(rdoShapeTypeCMU); add(rdoShapeTypeUColorado);}});
}});
numRows++;
trialDataPanel.add(new JPanel(new GridLayout(0,1)){{
add(lblTargetType); add(lblTargetColor);
}});
trialDataPanel.add(new JPanel(new GridLayout(0,1)){{
add(cbxTargetType); add(cbxTargetColor);
}}); numRows++;
}
trialDataPanel.add(lblMinTrialLength); trialDataPanel.add(spnMinTrialLength); numRows++;
trialDataPanel.add(lblFramesPerSecond); trialDataPanel.add(spnFramesPerSecond); numRows++;
trialDataPanel.add(lblGridXSize); trialDataPanel.add(spnGridXSize); numRows++;
trialDataPanel.add(lblGridYSize); trialDataPanel.add(spnGridYSize); numRows++;
trialDataPanel.add(lblUseBackgroundImages); trialDataPanel.add(chkUseBackgroundImages); numRows++;
trialDataPanel.add(lblImageDirectory); trialDataPanel.add(txtImageDirectory); numRows++;
trialDataPanel.add(lblUseMemoryCheck); trialDataPanel.add(new JPanel(new GridLayout(1,0)){{ add(rdoMemCheckNone); add(rdoMemCheck2x2); if(Util.CMU_ONLY) { add(rdoMemCheckNxN); } }}); numRows++;
trialDataPanel.add(lblUseFullscreen); trialDataPanel.add(chkUseFullscreen); numRows++;
if(Util.CMU_ONLY) {
trialDataPanel.add(lblMotionConstraint); trialDataPanel.add(new JPanel(new GridLayout(1,0)){{ add(rdoMotionPixel); add(rdoMotionGrid);}}); numRows++;
}
if(Util.CMU_ONLY) {
trialDataPanel.add(lblMotionInterpolation); trialDataPanel.add(new JPanel(new GridLayout(1,0)){{ add(rdoMotionLinear); add(rdoMotionRandom);}}); numRows++;
}
// We're dividing an integer here, so add some spacers if numRows is not
// properly divisible. Always need exactly param2 * param3 = numItems.
if(numRows % 2 != 0) {
trialDataPanel.add(new JPanel()); trialDataPanel.add(new JPanel()); numRows++;
}
SpringUtilities.makeCompactGrid(trialDataPanel,
numRows/2, 4, //rows, cols
6, 6, //initX, initY
6, 6); //xPad, yPad
// One final segment of the GUI deals with bookkeeping and extraneous stuff
//JPanel extraDataPanel = new JPanel(new SpringLayout());
//extraDataPanel.setBorder(BorderFactory.createTitledBorder("Bookkeeping"));
//
//numRows = 0;
//extraDataPanel.add(lblSeed); extraDataPanel.add(spnSeed); numRows++;
//
//SpringUtilities.makeCompactGrid(extraDataPanel,
// numRows, 2, //rows, cols
// 6, 6, //initX, initY
// 6, 6); //xPad, yPad
//
// All user input is grouped together into a single panel
JPanel inputDataPanel = new JPanel(new GridBagLayout());
GridBagConstraints inputCons = new GridBagConstraints();
inputCons.fill = GridBagConstraints.BOTH;
inputCons.weighty = 0.33;
inputCons.gridx = 0;
inputCons.gridy = 0;
inputDataPanel.add(userDataPanel, inputCons);
inputCons.weighty = 0.67;
inputCons.gridx = 0;
inputCons.gridy = 1;
inputDataPanel.add(trialDataPanel, inputCons);
//inputDataPanel.add(extraDataPanel);
//
// All the buttons are grouped together in a panel (at the bottom of the screen)
JPanel buttonPanel = new JPanel(new GridBagLayout());
GridBagConstraints buttonCons = new GridBagConstraints();
buttonCons.fill = GridBagConstraints.HORIZONTAL;
buttonCons.weightx = 0.10;
buttonPanel.add(btnExit, buttonCons);
buttonCons.weightx = 0.90;
buttonPanel.add(btnStart, buttonCons);
// Finally, put all the different GUI segments together into one panel
this.setLayout(new BorderLayout());
this.add(inputDataPanel, BorderLayout.CENTER);
this.add(buttonPanel, BorderLayout.SOUTH);
//this.add(lblCMU, BorderLayout.NORTH);
}
private void sanitizeNumDistractors() {
// Maximum number of distractors is {width} x {height} - 1 (for the target)
int newMax = Integer.valueOf(spnGridXSize.getValue().toString()) * Integer.valueOf(spnGridYSize.getValue().toString()) - 1;
SpinnerNumberModel model = (SpinnerNumberModel) spnDistractors.getModel();
model.setMaximum(newMax);
if( Integer.valueOf(spnDistractors.getValue().toString()) > newMax ) {
model.setValue(newMax);
}
}
private void sanitizeNumTrials() {
// Maximum number of trials is infinite if we're sampling random targets with replacement,
// but it's only |# Stimulus| if we're sampling with replacement
Integer newMax = (chkRandomTarget.isSelected() && !chkRandomWithReplacement.isSelected()) ? cbxTargetType.getItemCount() : null;
SpinnerNumberModel model = (SpinnerNumberModel) spnTrialCount.getModel();
model.setMaximum(newMax);
if( null != newMax && Integer.valueOf(spnTrialCount.getValue().toString()) > newMax ) {
model.setValue(newMax);
}
}
@Override
protected void tearDown() {
}
protected class DirectorySelectionListener implements MouseListener {
private JTextField textField = null;
public DirectorySelectionListener(JTextField textField) {
super();
this.textField = textField;
}
//@Override //TODO Java 1.5 screams about this; remove when not caring about Java 1.5
public void mouseClicked(MouseEvent arg0) {
// Clicking on this shouldn't do anything if we're not using images
if(!textField.isEnabled()) {
return;
}
JFileChooser fileChooser = new JFileChooser("data/stimuli/winnie_the_pooh");
fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
String selectedDirectory = textField.getText();
// User selects a directory from which to draw background images
int response = fileChooser.showDialog(owner, "Select");
if (response == JFileChooser.APPROVE_OPTION) {
selectedDirectory = fileChooser.getSelectedFile().getAbsolutePath();
Util.dPrintln("Background images will be drawn from " + selectedDirectory);
} else {
return;
}
// Notify the user that we are loading images from his selected directory
textField.setText(selectedDirectory);
}
//@Override //TODO Java 1.5 screams about this; remove when not caring about Java 1.5
public void mouseEntered(MouseEvent arg0) {}
//@Override //TODO Java 1.5 screams about this; remove when not caring about Java 1.5
public void mouseExited(MouseEvent arg0) {}
//@Override //TODO Java 1.5 screams about this; remove when not caring about Java 1.5
public void mousePressed(MouseEvent arg0) {}
//@Override //TODO Java 1.5 screams about this; remove when not caring about Java 1.5
public void mouseReleased(MouseEvent arg0) {}
}
protected class BeginButtonListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
// If the user clicks on the Begin button, initialize
// the testing suite according to the user's parameters,
// and switch contexts to begin testing
// Grab user-specified details about the user from the GUI elements
String name = txtName.getText();
Gender gender = Gender.getGender(cbxGender.getSelectedItem().toString());
Date birthdate = datBirthdate.getDate();
Date testDate = datTestDate.getDate();
String testLocation = txtTestLocation.getText();
// Check to make sure the user wants to submit possibly ill-formed data
if(name.isEmpty() || testLocation.isEmpty()) {
String message = "<html>Some <em>non-critical</em> user data is incomplete.</html>\n" +
(name.isEmpty() ? " The user's name is blank.\n" : "") +
(testLocation.isEmpty() ? " The testing location is blank.\n" : "") +
"Are you sure you would like to continue?";
String[] options = {"Continue to testing.", "Cancel."};
int response = JOptionPane.showInternalOptionDialog(owner,
message,
"Non-Critical Error: User Confirmation Requested",
JOptionPane.YES_NO_OPTION,
JOptionPane.QUESTION_MESSAGE,
null,
options,
options[0]
);
// If the user wants to fix this non-critical errors, short circuit
if(response == 1) { return; }
}
// Load the correct set of stimuli, according to user input
StimulusClass stimulusClass = rdoShapeTypeCMU.isSelected() ? Stimulus.StimulusClass.CMU : Stimulus.StimulusClass.UCOLORADO;
StimulusFactory.getInstance().reset();
owner.getGameState().registerStimuli(stimulusClass);
Dimension trialDim = owner.getSize();
double insetX = 0;
double insetY = 0;
if(owner.getSize().getWidth() > owner.getSize().getHeight()) {
trialDim.setSize(trialDim.getHeight(), trialDim.getHeight());
insetX = 0.5*(owner.getSize().getWidth() - trialDim.getWidth());
} else if(owner.getSize().getWidth() < owner.getSize().getHeight()) {
trialDim.setSize(trialDim.getWidth(), trialDim.getWidth());
insetY = 0.5*(owner.getSize().getHeight() - trialDim.getHeight());
}
// Grab user-specified details about the trial from the GUI elements
int numDistractors = Integer.valueOf(spnDistractors.getValue().toString());
double objectSpeed = Double.valueOf(spnObjectSpeed.getValue().toString());
TrialType trialType = TrialType.getTrialType(cbxTrialType.getSelectedItem().toString());
int trialCount = Integer.valueOf(spnTrialCount.getValue().toString());
double trialLength = Double.valueOf(spnMinTrialLength.getValue().toString());
boolean usesRandomTarget = chkRandomTarget.isSelected();
boolean usesSamplingWithReplacement = chkRandomWithReplacement.isSelected();
Color specificTargetColor = (Color) cbxTargetColor.getSelectedItem();
double fps = Double.valueOf(spnFramesPerSecond.getValue().toString());
//long seed = Long.valueOf(spnSeed.getValue().toString());
long seed = System.currentTimeMillis();
int gridX = Integer.valueOf(spnGridXSize.getValue().toString());
int gridY = Integer.valueOf(spnGridYSize.getValue().toString());
int pixelWidth = (int) trialDim.getWidth();
int pixelHeight= (int) trialDim.getHeight();
boolean usesBackgroundImages = chkUseBackgroundImages.isSelected();
String backgroundImageDirectory = txtImageDirectory.getText();
Settings.MemoryCheckType memCheckType = rdoMemCheckNone.isSelected() ? Settings.MemoryCheckType.mNONE :
rdoMemCheck2x2.isSelected() ? Settings.MemoryCheckType.m2X2 :
Settings.MemoryCheckType.mNXN;
boolean usesFullscreen = chkUseFullscreen.isSelected();
MotionConstraintType motionConstraintType = MotionConstraintType.GRID;
if(Util.CMU_ONLY) {
motionConstraintType = rdoMotionPixel.isSelected() ? MotionConstraintType.PIXEL : MotionConstraintType.GRID;
}
MotionInterpolationType motionInterpolationType = MotionInterpolationType.LINEAR;
if(Util.CMU_ONLY) {
motionInterpolationType = rdoMotionLinear.isSelected() ? MotionInterpolationType.LINEAR : MotionInterpolationType.RANDOM;
}
// If the user selected a canonical, specific target, make it
// UColorado has a specific list per type I or II; deal with that now
Stimulus canonicalTarget = null;
ColoradoTypedTrial coloradoTypedTrial = null;
if(stimulusClass == Stimulus.StimulusClass.UCOLORADO) {
ColoradoTypedTrial.TRIAL_TYPE ucTrialType = ColoradoTypedTrial.TRIAL_TYPE.valueOf(cbxTargetType.getSelectedItem().toString());
coloradoTypedTrial = new ColoradoTypedTrial(ucTrialType);
usesSamplingWithReplacement = true;
trialCount = coloradoTypedTrial.getNumTrials();
trialType = TrialType.getTrialType("All Different");
} else {
Stimulus specificTargetType = StimulusFactory.getInstance().getAllOfType(StimulusType.TARGET).get( cbxTargetType.getSelectedItem().toString() );
canonicalTarget = specificTargetType.factoryClone(specificTargetColor);
}
// If we are using background images, load them now (before the trial starts)
ArrayList<ImageIcon> backgroundImages = null;
if(usesBackgroundImages) {
backgroundImages = ResourceLoader.getInstance().loadImagesFromDirectory(backgroundImageDirectory);
// Were any images found? If not, error out.
if(backgroundImages == null || backgroundImages.size() == 0) {
JOptionPane.showMessageDialog(owner,
"Could not find any valid images in this directory:\nDirectory: \"" + backgroundImageDirectory + "\"",
"Error: No images found.",
JOptionPane.ERROR_MESSAGE);
return;
}
}
// Finally, ask the user where he or she would like to save the data we gather
// We save to .csv file, so filter only those
FileWriter saveFile = null;
JFileChooser fileChooser = new JFileChooser();
fileChooser.setSelectedFile(new File(seed + ".csv"));
fileChooser.addChoosableFileFilter(new FileFilter() {
@Override
public boolean accept(File f) {
// Always let the user see directories
if(f.isDirectory()) {
return true;
}
// Filter all non-.csv files
String filetype = Util.getExtension(f);
if(filetype == null || filetype.equals("csv")) {
return true;
}
return false;
}
@Override
public String getDescription() {
return "CSV Files Only";
}
});
// The user enters a file, or he cancels; if he cancels, then return.
try {
int response = fileChooser.showSaveDialog(owner);
if(response == JFileChooser.APPROVE_OPTION) {
File selectedFile = fileChooser.getSelectedFile();
saveFile = new FileWriter(selectedFile);
Util.dPrintln("Data will be saved to file " + selectedFile.getAbsolutePath());
} else {
return;
}
} catch(IOException ioException) {
JOptionPane.showMessageDialog(owner,
"Could not create or open save file. Debug information follows.\n" + ioException.getMessage(),
"Error: Could open or create save file.",
JOptionPane.ERROR_MESSAGE);
return;
}
// One part of the game state is "Settings," which covers all the initial
// parameters and experimental setup
Settings settings = new Settings();
settings.setUser(settings.new User(name, gender, birthdate, testDate, testLocation));
settings.setExperiment(settings.new Experiment(numDistractors, objectSpeed, trialType, trialCount, trialLength, usesRandomTarget, usesSamplingWithReplacement, stimulusClass, canonicalTarget, coloradoTypedTrial, fps, seed, gridX, gridY, pixelWidth, pixelHeight, (int) insetX, (int) insetY, usesBackgroundImages, backgroundImageDirectory, memCheckType, usesFullscreen, motionConstraintType, motionInterpolationType));
// Alert the greater game state to our initialization parameters
GameState gameState = owner.getGameState();
gameState.setSettings(settings);
gameState.setIO(saveFile);
gameState.setBackgroundImages(backgroundImages);
// Start off the record of our full experiment
try {
TestRecord.getInstance().updateLog(gameState);
} catch(IOException ioException) {
JOptionPane.showMessageDialog(owner,
"Could not write to file. Debug information follows.\n" + ioException.getMessage(),
"Error: Could not write to save file.",
JOptionPane.ERROR_MESSAGE);
return;
}
// If the user does not want to use the Memory Check screens, we need
// to skip over them.
if( memCheckType.equals(Settings.MemoryCheckType.mNONE)) {
owner.getPanelMap().get(Util.PanelID.BUFFER1).setNextScreen(Util.PanelID.GAME);
}
// We're all set; switch to a game representing the values
// the user entered on this screen
owner.switchContext(nextScreen);
}
}
/*
* Only add to JTextFields!
*/
protected class SelectOnceFocusListener implements FocusListener {
boolean selectedBefore = false;
//@Override //TODO Java 1.5 screams about this; remove when not caring about Java 1.5
public void focusGained(FocusEvent e) {
// We only want to select the entire text field upon the first
// click; this is so the user can easily override default values.
// After the first click, the user is probably just trying to edit
// his or her input, not completely replace it.
if(!selectedBefore) {
((JTextField) e.getSource()).selectAll();
selectedBefore = true;
}
}
//@Override //TODO Java 1.5 screams about this; remove when not caring about Java 1.5
public void focusLost(FocusEvent e) {
}
}
static class ColorCellRenderer implements ListCellRenderer {
// Taken from http://www.java2s.com/Code/Java/Swing-JFC/ColorComboBoxComboBoxEditorDemo.htm
protected DefaultListCellRenderer defaultRenderer = new DefaultListCellRenderer();
//private final static Dimension preferredSize = new Dimension(0, 20);
public Component getListCellRendererComponent(JList list, Object value,
int index, boolean isSelected, boolean cellHasFocus) {
JLabel renderer = (JLabel) defaultRenderer
.getListCellRendererComponent(list, value, index,
isSelected, cellHasFocus);
if (value instanceof Color) {
renderer.setBackground((Color) value);
}
//renderer.setPreferredSize(preferredSize);
return renderer;
}
}
}