2 * MOTSIM_Controller.java
7 Copyright (c) 2008, Ryan M. Hope
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.
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
{
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
69 /* Generate some data dynamically for testing purposes */
70 tdd
= new TrialDataDynamic(view
.screen_size
);
75 * Register action listeners relating to the control panel
77 public void registerCPActionListeners() {
78 view
.cp
.addStartListener(new StartListener());
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());
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
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)) {
116 * If target hits a border flip is angle and velocity so that it
117 * reflects like a mirror
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;
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;
131 t
.moveAngle
= t
.moveAngle
+ 180;
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;
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
173 * Sleep so that we dont eat up all the CPU power
175 model
.duration
= model
.duration
+ sleep
;
177 } catch (InterruptedException e
) {
183 * Changes blink state
185 private void flipTargetColor() {
198 class TrialThread
implements Runnable
{
202 // First move the targets around the screen
203 while (model
.move_targets
) {
204 if (model
.duration
< model
.maxduration
) {
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
) {
214 model
.target
= rand
.nextInt(model
.targets
.size());
216 // If SA query style "B" blink target
217 while (model
.blink_targets
&& model
.mask_targets
) {
221 // If SA query style "A" click on target
222 while (model
.find_target
&& model
.mask_targets
) {
223 synchronized (model
.trialThread
) {
225 model
.trialThread
.wait();
226 } catch (InterruptedException e1
) {
231 // Trial is over, wait
232 model
.waiting
= true;
233 synchronized (model
.trialThread
) {
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();
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
);
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 */
277 max
= view
.screen_size
.width
- (int)Math
.round(t
.bounds
.getWidth());
280 max
= view
.screen_size
.height
- view
.tp
.mh
;
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();
291 model
.find_target
= true;
293 model
.blink_targets
= true;
295 model
.move_targets
= true;
296 if (model
.trialThread
== null) {
297 model
.trialThread
= new Thread(new TrialThread());
298 model
.trialThread
.start();
300 synchronized (model
.trialThread
) {
301 model
.trialThread
.notifyAll();
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
) {
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
)) {
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();
407 view
.cp
.sx_panel
.setVisible(false);