// --------------------------------------- // Restricted Focus Viewer (RFV) Program // Version 2.1 // Language: Java 2 // October, 2000 // Copyright (C) 2000 Anthony R. Jansen // --------------------------------------- // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // 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 General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // --------------------------------------- // RFV.java // // The core class that controls the RFV. // It is responsible for dealing with all // user interactions with the RFV, and // controlling the windows. // --------------------------------------- import java.io.*; import java.util.*; import java.awt.event.*; import java.awt.*; public class RFV implements KeyListener, MouseListener, MouseMotionListener, ActionListener { // Variables for RFV status // ------------------------ private boolean wait_to_quit; private int block_number; private int component_number; private boolean dot_cursor_active; // Needed to determine motion blur speed // ------------------------------------- private int last_x; private int last_y; private long last_time; // Required components // ------------------- private RFV_Defaults defaults; private RFV_Setup_Window setup_window; private RFV_Display_Window display_window; private RFV_Timer block_timer; private RFV_Timer motion_blur_timer; private RFV_Motion_Blur_Thread motion_blur_thread; private RFV_Time_Limit_Thread time_limit_thread; private RFV_Stimulus last_stimulus; private String subject_id; private String input_filename; // I/O file handling variables // --------------------------- private RFV_Input_File input_file; private RFV_Output_File output_file; private LinkedList component_list; // Constants // --------- public static final String NO_BUTTONS_LABEL = "No Buttons Present"; public static final String TIME_LIMIT_LABEL = "Time Limit Expired"; // Constructor which accepts the input filename // as its argument and displays the setup window // --------------------------------------------- public RFV(String filename) { input_filename = filename; setup_window = new RFV_Setup_Window(this); input_file = new RFV_Input_File(filename); readDefaultsAndComponents(); setup_window.showComponents(component_list, this); } // Reads in the components // ----------------------- private void readDefaultsAndComponents() { String token; // Expect defaults parameter first // ------------------------------- token = input_file.readToken(); if (!token.equalsIgnoreCase("defaults")) RFV_Error.die(1, input_file.getLineNumber()); defaults = new RFV_Defaults(input_file, setup_window.getGraphics()); // Read in the component labels // ---------------------------- component_list = new LinkedList(); token = input_file.readToken(); while (!token.equals(RFV_Input_File.EOF)) { if (!token.equalsIgnoreCase("component")) RFV_Error.die(2, input_file.getLineNumber()); input_file.readOpenBracket(); token = input_file.readLabel(); checkLabelDuplication(token); component_list.add(input_file.getFileIndex(token)); input_file.matchOpenBrackets(1); token = input_file.readToken(); } // Check that there are components // ------------------------------- if (component_list.size() == 0) RFV_Error.die(3); } // Method to ensure each component label is unique // ----------------------------------------------- private void checkLabelDuplication(String new_label) { RFV_File_Index index; try { for (int i = 0; i < component_list.size(); i++) { index = (RFV_File_Index) component_list.get(i); if ((index.label).equals(new_label)) RFV_Error.die(4, input_file.getLineNumber(), new_label); } } catch(IndexOutOfBoundsException e) { RFV_Error.die(5); } } // Deal with run button press on setup window // If settings are valid, create display window // -------------------------------------------- public void actionPerformed(ActionEvent ae) { // Check settings are valid // ------------------------ if (setup_window == null || !setup_window.isValidSettings()) return; // Get settings and remove setup window // ------------------------------------ subject_id = setup_window.getSubjectID(); component_list = setup_window.getPresentationOrder(component_list); setup_window.hide(); setup_window.dispose(); setup_window = null; // Open the output file // -------------------- output_file = new RFV_Output_File(subject_id, input_filename); // Initialize the timers and variables // ----------------------------------- block_timer = new RFV_Timer(); motion_blur_timer = new RFV_Timer(); motion_blur_thread = null; wait_to_quit = false; dot_cursor_active = false; block_number = 1; component_number = 0; // Create display window and add listeners for interaction // ------------------------------------------------------- display_window = new RFV_Display_Window(defaults, false); display_window.addKeyListener(this); nextBlock(); } // Loads up the next block // ----------------------- private synchronized void nextBlock() { LinkedList stimulus_list; RFV_Stimulus stimulus; RFV_File_Index index; if (wait_to_quit) return; display_window.blankDisplay(); display_window.paint(display_window.getGraphics()); // If first block of a component, need to // set the file pointer to that component // -------------------------------------- if (block_number == 1) { // If all components done, wait to quit // ------------------------------------ if (component_number >= component_list.size()) { // Set up end of experiment message // -------------------------------- input_file.setFileIndex(new RFV_File_Index( "End of Experiment", (long) 0, -1, "( display( v_space() text_line(\"Experiment " + "Complete\") text_line(\"Thank you\") v_space()))")); display_window.readBlock(input_file); display_window.paint(display_window.getGraphics()); output_file.emptyLine(); wait_to_quit = true; return; } try { index = (RFV_File_Index) component_list.get(component_number); input_file.setFileIndex(index); output_file.emptyLine(); output_file.writeLine("Component: " + index.label); } catch(IndexOutOfBoundsException e) { RFV_Error.die(5); } } // Read in block parameter // ----------------------- String token = input_file.readToken(); if (!token.equalsIgnoreCase("block")) RFV_Error.die(6, input_file.getLineNumber(), token); display_window.readBlock(input_file); // Advance block number, and check if end of component // --------------------------------------------------- output_file.writeLine("Block " + block_number); block_number++; if (input_file.isCloseBracket()) { component_number++; block_number = 1; } // If block contains stimuli, write details to output file // ------------------------------------------------------- stimulus_list = display_window.getBlock().getStimulusList(); try { for (int i = 0; i < stimulus_list.size(); i++) { stimulus = (RFV_Stimulus) stimulus_list.get(i); output_file.writeStimulus(i+1, stimulus.getXOffset(), stimulus.getYOffset()); } } catch(IndexOutOfBoundsException e) { RFV_Error.die(7); } startBlock(); } // Method to start the newly loaded block // -------------------------------------- private void startBlock() { // Check if time limit is present // ------------------------------ time_limit_thread = null; if (display_window.getBlock().isTimeLimit()) { time_limit_thread = new RFV_Time_Limit_Thread(this, display_window.getBlock().getTimeLimitDuration()); if (time_limit_thread == null) RFV_Error.die(8); time_limit_thread.setPriority(Thread.MIN_PRIORITY + 1); } // Reset timers, and show block // ---------------------------- display_window.setPointerCursor(); dot_cursor_active = false; last_stimulus = null; display_window.paint(display_window.getGraphics()); block_timer.reset(); if (time_limit_thread != null) time_limit_thread.start(); // Add mouse listeners again // ------------------------- display_window.addMouseListener(this); display_window.addMouseMotionListener(this); } // Exit the program if the 'Q' key is pressed, // but not if 'Q' is being typed in a text field // --------------------------------------------- public void keyTyped(KeyEvent ke) { if (ke.getKeyChar() == 'q' || ke.getKeyChar() == 'Q') System.exit(0); } // If mouse is clicked over a button, complete the // current block and begin loading the next block // ----------------------------------------------- public synchronized void mousePressed(MouseEvent me) { RFV_Button button; LinkedList button_list; String label = NO_BUTTONS_LABEL; long time; // Get the elapsed time for the trial first // ---------------------------------------- time = block_timer.getElapsed(); // If waiting to quit or a strict time limit is present, ignore // ------------------------------------------------------------ if (wait_to_quit || display_window.getBlock().isTimeLimitStrict()) return; // If zero buttons, on to next block // --------------------------------- button_list = display_window.getBlock().getButtonList(); if (button_list.size() == 0) { endBlock(label, time); return; } // Otherwise, check each button to see if it is pressed // ---------------------------------------------------- try { for (int i = 0; i < button_list.size(); i++) { button = (RFV_Button) button_list.get(i); if (button.inElement(me.getX(), me.getY())) { label = button.getLabel(); endBlock(label, time); return; } } } catch(IndexOutOfBoundsException e) { RFV_Error.die(9); } } // If end of block, stop threads, see if feedback is need // and record the response given if necessary // ------------------------------------------------------ private synchronized void endBlock(String label, long time) { LinkedList index_list; RFV_File_Index index; RFV_File_Index temp_index; // First stop all threads // ---------------------- if (time_limit_thread != null) time_limit_thread.interrupt(); if (motion_blur_thread != null) motion_blur_thread.interrupt(); // Remove the mouse listeners // -------------------------- display_window.removeMouseListener(this); display_window.removeMouseMotionListener(this); // Record the response // ------------------- output_file.writeResponse(label, time); // Check if feedback needed // ------------------------ index_list = display_window.getBlock().getFeedbackList(); try { for (int i = 0; i < index_list.size(); i++) { index = (RFV_File_Index) index_list.get(i); if ((index.label).equals(label)) { temp_index = input_file.getFileIndex("temp"); input_file.setFileIndex(index); display_window.readFeedbackBlock(input_file, (int) time); input_file.setFileIndex(temp_index); output_file.writeLine("Feedback"); startBlock(); return; } } } catch(IndexOutOfBoundsException e) { RFV_Error.die(10); } nextBlock(); } // If mouse moved, see if focus window needs to be updated // ------------------------------------------------------- public synchronized void mouseMoved(MouseEvent me) { LinkedList stimulus_list; RFV_Stimulus stimulus; long time; // Get the elapsed time for the trial first // ---------------------------------------- time = block_timer.getElapsed(); // If waiting to quit or there are no stimuli present at all, ignore // ----------------------------------------------------------------- stimulus_list = display_window.getBlock().getStimulusList(); if (wait_to_quit || stimulus_list.size() == 0) return; // See which stimulus mouse is over // -------------------------------- try { for (int i = 0; i < stimulus_list.size(); i++) { stimulus = (RFV_Stimulus) stimulus_list.get(i); if (stimulus.inElement(me.getX(), me.getY())) { updateStimulus(i, stimulus, time, me.getX(), me.getY()); return; } } } catch(IndexOutOfBoundsException e) { RFV_Error.die(7); } // If not over any stimulus, check cursor // Redraw last stimulus if needed // -------------------------------------- if (dot_cursor_active) { display_window.setPointerCursor(); dot_cursor_active = false; if (last_stimulus != null) { last_stimulus.mouseOff(); last_stimulus.draw(display_window.getGraphics()); last_stimulus = null; } } output_file.writeMousePosition(0, time, me.getX(), me.getY(), false); } // Updates the stimulus based on mouse movement // -------------------------------------------- private synchronized void updateStimulus(int num, RFV_Stimulus stimulus, long time, int xc, int yc) { boolean motion_blur = false; double delta_x; double delta_y; double delta_time; double distance_sqrd; double max_speed_mod; // If no previous stimulus // ----------------------- if (!dot_cursor_active) { display_window.setDotCursor(); dot_cursor_active = true; last_stimulus = stimulus; } // If different previous stimulus // ------------------------------ else if (!stimulus.equals(last_stimulus)) { if (last_stimulus != null) { last_stimulus.mouseOff(); last_stimulus.draw(display_window.getGraphics()); } last_stimulus = stimulus; } // If previously on the same stimulus, calculate if motion blur needed // ------------------------------------------------------------------- else { delta_time = (double) (time - last_time); delta_time /= (double) 1000; delta_x = (double) (xc - last_x); delta_y = (double) (yc - last_y); // Compare mouse speed (pixels per second) // --------------------------------------- distance_sqrd = delta_x * delta_x + delta_y * delta_y; max_speed_mod = stimulus.getMotionBlurSpeedSqrd() * delta_time * delta_time; if (distance_sqrd > max_speed_mod) { motion_blur = true; motionBlur(); } } // Update co-ordinates and redraw stimulus // --------------------------------------- last_x = xc; last_y = yc; last_time = time; stimulus.mouseOn(xc, yc, motion_blur); stimulus.draw(display_window.getGraphics()); output_file.writeMousePosition(num+1, time, xc, yc, motion_blur); } // Method to generate thread to deactivate motion blur if needed // ------------------------------------------------------------- private synchronized void motionBlur() { motion_blur_timer.reset(); if (motion_blur_thread == null) { motion_blur_thread = new RFV_Motion_Blur_Thread(this); if (motion_blur_thread == null) RFV_Error.die(11); motion_blur_thread.setPriority(Thread.MIN_PRIORITY); motion_blur_thread.start(); } } // Method to end the motion blur, killing the motion // blur thread, and updating the appropriate stimulus // -------------------------------------------------- public synchronized void endMotionBlur() { if (last_stimulus != null) { last_stimulus.mouseOn(last_x, last_y, false); last_stimulus.draw(display_window.getGraphics()); } motion_blur_thread = null; } // Returns the elapsed motion blur timer value // ------------------------------------------- public long getElapsedMotionBlurTime() { return motion_blur_timer.getElapsed(); } // If time limit expires, complete the current // block and begin loading the next block // ------------------------------------------- public synchronized void timeLimitExpired() { String label; long time; time = block_timer.getElapsed(); label = TIME_LIMIT_LABEL; endBlock(label, time); } // Extra methods from the KeyListener, MouseListener // and MouseMotionListener interfaces that were not used // ----------------------------------------------------- public void keyPressed(KeyEvent ke) { ; } public void keyReleased(KeyEvent ke) { ; } public void mouseDragged(MouseEvent me) { ; } public void mouseEntered(MouseEvent me) { ; } public void mouseExited(MouseEvent me) { ; } public void mouseClicked(MouseEvent me) { ; } public void mouseReleased(MouseEvent me) { ; } // Main method to start the program, accepting // the input filename as its only argument // ------------------------------------------- static public void main(String argv[]) { if (argv.length != 1) { System.out.println(" "); System.out.println("Restricted Focus Viewer (RFV), Version 2.1"); System.out.println(" "); System.out.println("Copyright (C) 2000 Anthony R. Jansen"); System.out.println(" "); System.out.println("This program may only be copied under the " + "terms of the GNU General Public"); System.out.println("License, which may be found along with " + "the program documentation on the"); System.out.println("RFV website at " + "http://www.csse.monash.edu.au/~tonyj/RFV/"); System.out.println(" "); System.exit(0); } RFV rfv = new RFV(argv[0]); } }