start of cleanups
[rmh3093.git] / motsim / MOTSIM_Controller.java
blobe5e6a84f05c06e31e07925678f013d1d0be168ed
1 /**
2 * MOTSIM_Controller.java
3 * motsim
4 */
6 /*
7 Copyright (c) 2008, Ryan M. Hope
8 All rights reserved.
10 Redistribution and use in source and binary forms, with or without modification,
11 are permitted provided that the following conditions are met:
13 * Redistributions of source code must retain the above copyright notice,
14 this list of conditions and the following disclaimer.
15 * Redistributions in binary form must reproduce the above copyright notice,
16 this list of conditions and the following disclaimer in the documentation
17 and/or other materials provided with the distribution.
18 * Neither the name of the project nor the names of its contributors may be
19 used to endorse or promote products derived from this software without
20 specific prior written permission.
22 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
23 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
24 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
25 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
26 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
27 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
29 ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
31 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34 import java.awt.Font;
35 import java.awt.event.ActionEvent;
36 import java.awt.event.ActionListener;
37 import java.awt.event.KeyEvent;
38 import java.awt.event.KeyListener;
39 import java.awt.event.MouseEvent;
40 import java.awt.event.MouseListener;
41 import java.awt.event.MouseMotionListener;
42 import java.awt.event.WindowEvent;
43 import java.awt.event.WindowListener;
44 import java.awt.font.TextLayout;
45 import java.util.ArrayList;
46 import java.util.HashSet;
47 import java.util.Random;
49 public class MOTSIM_Controller {
51 MOTSIM_Model model;
52 MOTSIM_View view;
54 TrialDataDynamic tdd;
56 Random rand;
58 /**
59 * Create a new controller object for MOTSIM. MOTSIM_Controller does most
60 * of the grunt works for MOTSIM.
62 * @param model MOTSIM_Model
63 * @param view MOTSIM_View
65 public MOTSIM_Controller(MOTSIM_Model model, MOTSIM_View view) {
66 rand = new Random(); // A random number generator
67 this.model = model;
68 this.view = view;
69 /* Generate some data dynamically for testing purposes */
70 tdd = new TrialDataDynamic(view.screen_size);
74 /**
75 * Register action listeners relating to the control panel
77 public void registerCPActionListeners() {
78 view.cp.addStartListener(new StartListener());
81 /**
82 * Register action listeners relating to the trial panel
84 public void registerTPActionListeners() {
85 view.tp.addWindowListener(new WindowEventListener());
86 view.tp.addMouseListener(new MouseClickListener());
87 view.tp.addMouseMotionListener(new MouseMovementListener());
88 view.tp.addKeyListener(new KeyPressListener());
91 /**
92 * Update target positions based on X and Y velocities
94 protected synchronized void moveTargets() {
95 int sleep = 20; // Sleep for 20ms to maintain ~50fps
96 int[] entropy = null;
97 int spawn = 0;
98 HashSet<Target> remove = new HashSet<Target>();
99 for (int i=0; i<model.targets.size(); i++) {
100 Target t = model.targets.get(i);
101 boolean flip = false;
103 * If target hits a border spawn a new random target on border
105 if (model.borderstyle == "Spawn") {
106 if (((t.x + t.velX) > (view.screen_size.width - t.bounds.getWidth() - 1)) ||
107 ((t.x + t.velX) < 0) ||
108 ((t.y + t.velY) > view.screen_size.height-view.tp.mh) ||
109 ((t.y + t.velY - t.bounds.getHeight()) < 0)) {
110 remove.add(t);
111 spawn++;
112 continue;
116 * If target hits a border flip is angle and velocity so that it
117 * reflects like a mirror
119 else {
120 if (((t.x + t.velX + t.bounds.getWidth()) > view.screen_size.width) ||
121 ((t.x + t.velX) < 0)) {
122 t.velX = t.velX * -1;
123 flip = true;
125 if (((t.y + t.velY) > view.screen_size.height-view.tp.mh) ||
126 ((t.y + t.velY - t.bounds.getHeight()) < 0)) {
127 t.velY = t.velY * -1;
128 flip = true;
130 if (flip==true) {
131 t.moveAngle = t.moveAngle + 180;
132 t.move = true;
136 * Add any entropy to the move angle if necessary
138 if (model.entropy != "None") {
139 if (t.move == false) {
140 if (model.entropy == "Low") {
141 entropy = tdd.generateEntropyLow();
142 } else if (model.entropy == "Medium") {
143 entropy = tdd.generateEntropyMedium();
144 } else if (model.entropy == "High") {
145 entropy = tdd.generateEntropyHigh();
147 t.moveAngle = (t.moveAngle + entropy[rand.nextInt(10000)]) % 360;
148 t.move = true;
149 } else {
150 t.steps++;
151 if (t.steps > 5) {
152 t.move = false;
153 t.steps = 0;
156 t.velX = tdd.calcAngleMoveX(t.moveAngle);
157 t.velY = tdd.calcAngleMoveY(t.moveAngle);
159 t.x = t.x + t.velX * t.velocityMOD;
160 t.y = t.y + t.velY * t.velocityMOD;
163 * After moving all the targets, if in "Spawn" mode, remove targets that
164 * hit the border and add the same number of new targets to the array
166 if (model.borderstyle == "Spawn") {
167 model.targets.removeAll(remove);
168 tdd.spawnTargetOnBorder(model.targets, spawn);
170 view.tp.repaint(); // Update screen
171 try {
173 * Sleep so that we dont eat up all the CPU power
175 model.duration = model.duration + sleep;
176 Thread.sleep(sleep);
177 } catch (InterruptedException e) {
183 * Changes blink state
185 private void flipTargetColor() {
186 if (model.blink)
187 model.blink = false;
188 else
189 model.blink = true;
193 * Main Trial Tread
195 * @author rmh3093
198 class TrialThread implements Runnable {
200 public void run() {
201 while (true) {
202 // First move the targets around the screen
203 while (model.move_targets) {
204 if (model.duration < model.maxduration) {
205 moveTargets();
206 } else {
207 model.move_targets = false;
208 model.mask_targets = true;
211 // After trial duration mask targets and pick a random target
212 if (model.mask_targets) {
213 view.tp.repaint();
214 model.target = rand.nextInt(model.targets.size());
216 // If SA query style "B" blink target
217 while (model.blink_targets && model.mask_targets) {
218 flipTargetColor();
219 view.tp.repaint();
221 // If SA query style "A" click on target
222 while (model.find_target && model.mask_targets) {
223 synchronized (model.trialThread) {
224 try {
225 model.trialThread.wait();
226 } catch (InterruptedException e1) {
229 view.tp.repaint();
231 // Trial is over, wait
232 model.waiting = true;
233 synchronized (model.trialThread) {
234 try {
235 model.trialThread.wait();
236 } catch (InterruptedException e1) {
239 model.waiting = false;
245 void startNewTrial() {
246 /* Initialize trial values */
247 model.maxduration = 1000 * (Integer)view.cp.duration_spinner.getValue();
248 model.entropy = view.cp.entropy_combobox.getSelectedItem().toString();
249 model.defaultvelocities = view.cp.velocity_combobox.getSelectedItem().toString();
250 view.startTrial();
251 registerTPActionListeners();
252 model.target_font = new Font(view.tp.getFont().getName(),
253 view.tp.getFont().getStyle(),
254 (Integer)view.cp.fontsize_spinner.getValue());
255 model.query_font = new Font(view.tp.getFont().getName(),
256 view.tp.getFont().getStyle(), model.query_font_size);
257 view.tp.bufferGraphics.setFont(model.target_font);
258 model.duration = 0;
259 model.borderstyle = view.cp.borderstyle_combobox.getSelectedItem().toString();
260 view.tp.mh = model.query_font.getSize() + model.query_font_size;
261 /* Generate targets */
262 model.targets = new ArrayList<Target>(tdd.generate_targets(
263 (Integer)view.cp.targets_spinner.getValue()));
264 for (int i=0; i<model.targets.size(); i++) {
265 Target t = model.targets.get(i);
266 /* Adjust base speed of targets */
267 t.velocityMOD = (Double)view.cp.speed_spinner.getValue();
268 /* If necessary make velocities based on gaussian distribution */
269 if (model.defaultvelocities == "Gaussian") {
270 t.velocityMOD = t.velocityMOD * (1+Math.round(Math.pow(rand.nextGaussian(),2)))/2;
272 /* Get bounds of targets */
273 TextLayout tl = new TextLayout(t.callsign, model.target_font, view.tp.frc);
274 t.bounds = tl.getBounds();
275 /* Make sure all targets start on screen */
276 int max;
277 max = view.screen_size.width - (int)Math.round(t.bounds.getWidth());
278 if (t.x > max)
279 t.x = max - 1;
280 max = view.screen_size.height - view.tp.mh;
281 if (t.y > max)
282 t.y = max - 1;
283 if (t.y < t.bounds.getHeight())
284 t.y = t.bounds.getHeight() + 1;
286 model.mask_targets = false;
287 model.blink_targets = false;
288 model.find_target = false;
289 String type = view.cp.sa_query_combobox.getSelectedItem().toString();
290 if (type == "A")
291 model.find_target = true;
292 if (type == "B")
293 model.blink_targets = true;
294 model.selected = -1;
295 model.move_targets = true;
296 if (model.trialThread == null) {
297 model.trialThread = new Thread(new TrialThread());
298 model.trialThread.start();
299 } else {
300 synchronized (model.trialThread) {
301 model.trialThread.notifyAll();
307 * Event Listeners
310 class WindowEventListener implements WindowListener {
312 public void windowActivated(WindowEvent e) {
315 public void windowClosed(WindowEvent e) {
318 public void windowClosing(WindowEvent e) {
321 public void windowDeactivated(WindowEvent e) {
322 view.tp.stopTrial();
323 //model.gd.setDisplayMode(model.display_mode_preferred);
326 public void windowDeiconified(WindowEvent e) {
329 public void windowIconified(WindowEvent e) {
332 public void windowOpened(WindowEvent e) {
337 class MouseMovementListener implements MouseMotionListener {
339 public void mouseDragged(MouseEvent e) {
342 public void mouseMoved(MouseEvent e) {
343 if (model.mask_targets && model.find_target) {
344 model.mouse_x = e.getX();
345 model.mouse_y = e.getY();
346 synchronized (model.trialThread) {
347 model.trialThread.notifyAll();
354 class MouseClickListener implements MouseListener {
356 public void mouseClicked(MouseEvent e) {
357 if (model.mask_targets && model.find_target) {
358 model.mouse_x_clk = e.getX();
359 model.mouse_y_clk = e.getY();
360 synchronized (model.trialThread) {
361 model.trialThread.notifyAll();
366 public void mouseEntered(MouseEvent e) {
369 public void mouseExited(MouseEvent e) {
372 public void mousePressed(MouseEvent e) {
375 public void mouseReleased(MouseEvent e) {
380 class KeyPressListener implements KeyListener {
382 public void keyPressed(KeyEvent e) {
385 public void keyReleased(KeyEvent e) {
386 // Stop trial if target is selected and "Enter" is pressed
387 if (model.find_target && model.mask_targets &&
388 (model.selected!=-1) &&
389 (e.getKeyCode()==KeyEvent.VK_ENTER)) {
390 view.tp.stopTrial();
391 view.tp.setVisible(false);
395 public void keyTyped(KeyEvent e) {
400 class StartListener implements ActionListener {
402 public void actionPerformed(ActionEvent e) {
403 if ((view.cp.collectdata_checkbox.getSelectedObjects()!=null) &&
404 !(view.cp.sx_panel.isVisible())) {
405 view.collectDemographics();
406 } else {
407 view.cp.sx_panel.setVisible(false);
408 startNewTrial();