package edu.cmu.cs.eyetrack.gui; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import edu.cmu.cs.eyetrack.gui.shapes.Stimulus; import edu.cmu.cs.eyetrack.gui.shapes.StimulusFactory; import edu.cmu.cs.eyetrack.gui.shapes.Stimulus.StimulusClass; import edu.cmu.cs.eyetrack.gui.shapes.StimulusFactory.StimulusType; import edu.cmu.cs.eyetrack.helper.Util; import edu.cmu.cs.eyetrack.helper.Util.PanelID; import edu.cmu.cs.eyetrack.state.Settings; import edu.cmu.cs.eyetrack.state.Settings.Experiment; import edu.cmu.cs.eyetrack.state.Trial; @SuppressWarnings("serial") public class LineupScreen extends Screen { private Trial activeTrial; private List<Stimulus> stimList; private Stimulus stimTarget; // Timestamp when the lineup first displays to the user; used for RTs on click private long initializationTime; public LineupScreen(EyeTrack owner, PanelID nextScreen) { super(owner, nextScreen); addMouseListener(new LineupMouseListener()); addKeyListener(new KeyListener() { //@Override //TODO Java 1.5 screams about this; remove when not caring about Java 1.5 public void keyPressed(KeyEvent e) { if(e.getKeyCode() == KeyEvent.VK_SPACE) { processSelectionEvent(null, false, 0); } } //@Override //TODO Java 1.5 screams about this; remove when not caring about Java 1.5 public void keyReleased(KeyEvent e) {} //@Override //TODO Java 1.5 screams about this; remove when not caring about Java 1.5 public void keyTyped(KeyEvent e) {} }); } @Override protected void initialize() { Experiment exp = owner.getGameState().getSettings().getExperiment(); // Grab all the stimuli that were used in the real trial activeTrial = owner.getGameState().getActiveTrial(); // Shuffle a copy of the stimulus list, display in this order //stimList = new ArrayList<Stimulus>( activeTrial.getStimList() ); //Collections.shuffle(stimList, owner.getGameState().getRandom()); List<Stimulus> factoryAllStims = StimulusFactory.getInstance().createOneOfEach(StimulusType.TARGET); stimList = new ArrayList<Stimulus>(); stimTarget = activeTrial.getTarget(); // Randomly line stimuli up; user tries to select the one s/he followed earlier int numColumns, colWidth, numRows, rowHeight; if(exp.getMemCheckType().equals(Settings.MemoryCheckType.m2X2)) { numColumns = 2; numRows = 2; } else { numColumns = (int) Math.ceil( Math.sqrt( factoryAllStims.size() ) ); numRows = (int) Math.floor( Math.sqrt( factoryAllStims.size() - numColumns ) + 1); } colWidth = exp.getPixelWidth() / numColumns; rowHeight = exp.getPixelHeight() / numRows; // Each stimulus can have only one color, and each stimulus must have a unique color Set<Color> safeColors = new HashSet<Color>( owner.getGameState().getRandomGen().getRandomColors()); // If we are randomizing colors (standard CMU experiments), remove illegal colors; // for UColorado, they want all gray all the time so it is the only color and is always legal if(!owner.getGameState().getSettings().getExperiment().getStimulusClass().equals(StimulusClass.UCOLORADO)) { for(Stimulus stimulus : activeTrial.getStims()) { safeColors.remove( stimulus.getColor() ); } } for(Stimulus stimulus : factoryAllStims) { // Hack to make sure colors match up boolean colorOkay = false; for(Stimulus trialStim : activeTrial.getStims()) { if(trialStim.isEquivalent(stimulus)) { stimulus.setColor( trialStim.getColor() ); colorOkay = true; } } if(!colorOkay) { Color newColor; if(owner.getGameState().getSettings().getExperiment().getStimulusClass().equals(StimulusClass.UCOLORADO)) { newColor = owner.getGameState().getRandomGen().getRandomColors().get(0); } else { if(!safeColors.iterator().hasNext()) { // If we have no colors left, this behavior is undefined; return black? newColor = Color.BLACK; } else { // Grab the next unassigned color, not taken by previously seen stims or other random gens newColor = safeColors.iterator().next(); } } // Set the stimulus to own this color; nobody else can use it stimulus.setColor( newColor ); safeColors.remove( newColor ); } stimList.add(stimulus); } // Randomize placement of target and distractors Collections.shuffle(stimList); // CMU only wants to display 4 stimuli. if(exp.getMemCheckType().equals(Settings.MemoryCheckType.m2X2)) { List<Stimulus> shortenedList = new ArrayList<Stimulus>(); // Must have target from last trial shortenedList.add(stimTarget); // Add up to three other distractors that were in the trial, if three exist for(Stimulus stimulus : activeTrial.getStims()) { boolean isUnique = true; for(Stimulus usedStim : shortenedList) { if(usedStim.isEquivalent(stimulus)) { isUnique = false; } } if(isUnique) { shortenedList.add(stimulus); } if(shortenedList.size() >= 4) { break; } } // If we've exhausted the trial's target and distractors, add random stimuli until we hit 4 for(Stimulus stimulus : stimList) { if(shortenedList.size() >= 4) { break; } boolean isUnique = true; for(Stimulus trialStim : shortenedList) { if(trialStim.isEquivalent(stimulus)) { isUnique = false; } } if(isUnique) { shortenedList.add(stimulus); } } stimList = shortenedList; } // Randomize placement of target and distractors Collections.shuffle(stimList); // Place the corrected stimulus in their correct locations int colIdx=0, rowIdx=0; for(Stimulus stimulus : stimList) { if(colIdx >= numColumns) { colIdx = 0; rowIdx++; } // Send polygon to middle of its grid int xPos = exp.getInsetX() + (int) ((colIdx+0.5) * colWidth); int yPos = exp.getInsetY() + (int) ((rowIdx+0.5) * rowHeight); //System.out.println("Moving " + stimulus.getName() + " to position <" + xPos + ", " + yPos + ">."); stimulus.move(xPos, yPos); colIdx++; } setLayout(new BorderLayout()); initializationTime = System.currentTimeMillis(); } @Override public void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2d = (Graphics2D) g; // Draw each of our stimuli for(Stimulus stimulus : stimList) { stimulus.draw(g2d); } } protected void processSelectionEvent(Stimulus selectedStim, boolean correct, long responseTime) { // Determine which distractor the participant clicked on; record it activeTrial.setLineupStimulus(selectedStim); activeTrial.setLineupClickCorrect(correct); activeTrial.setLineupClickRT(responseTime); owner.switchContext(nextScreen); } private class LineupMouseListener implements MouseListener { //@Override //TODO Java 1.5 screams about this; remove when not caring about Java 1.5 public void mouseClicked(MouseEvent e) { int x = e.getX(), y = e.getY(); Stimulus selectedStimulus = null; boolean clickCorrect = false; // Which object did I click, if any? for(Stimulus candidate : stimList) { // If we click inside the bounding box of a stimulus, record this // and break immediately (does not handle overlapping bounding boxes) if( candidate.getShape().getBounds().contains(x,y) ) { selectedStimulus = candidate; // Shallow equality okay since we're comparing the same pointer //if(selectedStimulus.getName() == stimTarget.getName()) { if(selectedStimulus.isEquivalent( stimTarget )) { clickCorrect = true; } break; } } // User didn't select a stimulus at all if(selectedStimulus == null) { Util.dPrintln("User clicked <" + x + ", " + y + ">, which was not a valid stimulus."); return; } long responseTime = System.currentTimeMillis() - initializationTime; Util.dPrintln("User " + (clickCorrect ? "correctly" : "incorrectly") + " selected stimulus " + selectedStimulus.toString() + " after " + responseTime + " ms."); processSelectionEvent(selectedStimulus, clickCorrect, responseTime); } //@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) {} } @Override protected void tearDown() { // Remove all the distractors; we want a different // random lineup the next time around } }