001         package com.croftsoft.apps.infant;
002         
003         import java.awt.*;
004         import java.awt.event.*;
005         import java.io.*;
006         import java.text.*;
007         import java.util.*;
008         import javax.imageio.*;
009         import javax.swing.*;     
010         import javax.swing.border.*;
011         import javax.swing.event.*;
012         
013         import com.croftsoft.core.gui.LogPanel;
014         import com.croftsoft.core.gui.WindowLib;
015         import com.croftsoft.core.gui.event.UserInputListener;
016         import com.croftsoft.core.lang.NullArgumentException;
017         import com.croftsoft.core.lang.lifecycle.Destroyable;
018         import com.croftsoft.core.lang.lifecycle.Updatable;
019         import com.croftsoft.core.math.MathConstants;
020    import com.croftsoft.core.util.slot.Slot;
021         
022         /*********************************************************************
023         * View.
024         *  
025         * @version
026         *   $Id: InfantView.java,v 1.38 2008/09/20 05:01:49 croft Exp $
027         * @since
028         *   2006-01-03
029         * @author
030         *   <a href="https://www.croftsoft.com/">David Wallace Croft</a>
031         *********************************************************************/
032    
033         public final class  InfantView
034           implements Destroyable, Updatable
035         //////////////////////////////////////////////////////////////////////
036         //////////////////////////////////////////////////////////////////////
037         {
038             
039         private static final long  serialVersionUID = 0L;
040         
041         private static final DateFormat  dateFormat
042           = new SimpleDateFormat ( "yyyy-MM-dd HH:mm:ss Z" );       
043         
044         //
045         
046         private final InfantConfig          infantConfig;
047         
048         private final InfantAccessor        infantAccessor;
049         
050         private final Queue<InfantMessage>  eventQueue;
051         
052         private final Slot<InfantMessage>   requestSlot;
053         
054         private final JFrame                controlFrame;
055         
056         private final JFrame                displayFrame;
057         
058         private final InfantComponent       infantComponent;
059         
060         private final Set<JButton>          buttonSet;
061         
062         private final Map<String,Image>     imagePathToImageMap;
063         
064         private final SpinnerNumberModel
065           controllerIndexSpinnerNumberModel,
066           imageDisplayTimeSpinnerNumberModel,
067           interStimulusIntervalSpinnerNumberModel,
068           scaleSpinnerNumberModel;
069         
070         //////////////////////////////////////////////////////////////////////
071         //////////////////////////////////////////////////////////////////////
072         
073         public  InfantView (
074           final InfantConfig          infantConfig,
075           final InfantAccessor        infantAccessor,
076           final Queue<InfantMessage>  eventQueue,
077           final Slot<InfantMessage>   requestSlot )
078         //////////////////////////////////////////////////////////////////////
079         {
080           NullArgumentException.checkArgs (
081             this.infantConfig   = infantConfig,
082             this.infantAccessor = infantAccessor,
083             this.eventQueue     = eventQueue,
084             this.requestSlot    = requestSlot );
085           
086           buttonSet = new HashSet<JButton> ( );
087           
088           imageDisplayTimeSpinnerNumberModel = new SpinnerNumberModel (
089             infantAccessor.getImageDisplayTime ( )
090               * MathConstants.SECONDS_PER_NANOSECOND,
091             0.0,
092             Double.POSITIVE_INFINITY,
093             0.1 );
094                 
095           interStimulusIntervalSpinnerNumberModel = new SpinnerNumberModel (
096               infantAccessor.getInterStimulusInterval ( )
097                 * MathConstants.SECONDS_PER_NANOSECOND,
098               0.0,
099               Double.POSITIVE_INFINITY,
100               0.1 );
101           
102           controllerIndexSpinnerNumberModel = new SpinnerNumberModel (
103             new Integer ( infantConfig.getControllerIndex ( ) ),
104             new Integer ( 0 ),
105             new Integer ( Integer.MAX_VALUE ),
106             new Integer ( 1 ) );
107                 
108           scaleSpinnerNumberModel = new SpinnerNumberModel (
109             infantAccessor.getScale ( ),
110             0.00001,
111             10000.0,
112             0.1 );
113                 
114           this.imageDisplayTimeSpinnerNumberModel.addChangeListener (
115             new ChangeListener ( )
116             {
117               public void  stateChanged ( final ChangeEvent  changeEvent )
118               {
119                 final Object  value
120                   = imageDisplayTimeSpinnerNumberModel.getValue ( );
121               
122                 requestSlot.offer (
123                   new InfantMessage (
124                     InfantMessage.Type.DURATION,
125                     new Long (
126                       Math.round ( ( ( Double ) value ).doubleValue ( )
127                         * MathConstants.NANOSECONDS_PER_SECOND ) ) ) );
128               }
129             } );
130           
131           this.interStimulusIntervalSpinnerNumberModel.addChangeListener (
132             new ChangeListener ( )
133             {
134               public void  stateChanged ( final ChangeEvent  changeEvent )
135               {
136                 final Object  value
137                   = interStimulusIntervalSpinnerNumberModel.getValue ( );
138               
139                 requestSlot.offer (
140                   new InfantMessage (
141                     InfantMessage.Type.ISI,
142                     new Long (
143                       Math.round ( ( ( Double ) value ).doubleValue ( )
144                         * MathConstants.NANOSECONDS_PER_SECOND ) ) ) );                 
145               }
146             } );
147           
148           controllerIndexSpinnerNumberModel.addChangeListener (
149             new ChangeListener ( )
150             {
151               public void  stateChanged ( final ChangeEvent  changeEvent )
152               {
153                 final Object  value
154                   = controllerIndexSpinnerNumberModel.getValue ( );
155               
156                 requestSlot.offer (
157                   new InfantMessage (
158                     InfantMessage.Type.SET_CONTROLLER_INDEX,
159                     value ) );
160               }
161             } );
162           
163           scaleSpinnerNumberModel.addChangeListener (
164             new ChangeListener ( )
165             {
166               public void  stateChanged ( final ChangeEvent  changeEvent )
167               {
168                 final Object  value = scaleSpinnerNumberModel.getValue ( );
169               
170                 requestSlot.offer (
171                   new InfantMessage (
172                     InfantMessage.Type.SET_SCALE_REQUEST,
173                     value ) );
174               }
175             } );
176           
177           controlFrame = createControlFrame (
178             infantConfig,
179             buttonSet,
180             controllerIndexSpinnerNumberModel,
181             imageDisplayTimeSpinnerNumberModel,
182             interStimulusIntervalSpinnerNumberModel,
183             scaleSpinnerNumberModel );
184           
185           infantComponent
186             = new InfantComponent ( infantConfig, infantAccessor );
187           
188           displayFrame = createStimulusFrame (
189             infantComponent, requestSlot );
190           
191           imagePathToImageMap = new HashMap<String,Image> ( );
192           
193           final String [ ]  imagePaths = infantConfig.getImagePaths ( );
194           
195           try
196           {         
197             for ( int  id = 0; id < imagePaths.length; id++ )
198             {
199               final String  imagePath = imagePaths [ id ];
200               
201               final File  file = new File ( imagePath );
202             
203               final Image  image = ImageIO.read ( file );
204               
205               if ( image == null )
206               {
207                 infantConfig.getLog ( ).record (
208                   "no registered ImageReader for " + imagePath );
209               }
210               else
211               {           
212                 imagePathToImageMap.put ( imagePath, image );
213                 
214                 infantConfig.getLog ( ).record ( imagePath );
215               }
216             }
217           }
218           catch ( Exception  ex )
219           {
220             infantConfig.getLog ( ).record ( ex );
221           }
222         }
223         
224         //////////////////////////////////////////////////////////////////////
225         // accessor methods
226         //////////////////////////////////////////////////////////////////////
227         
228         public JFrame  getJFrame ( ) { return this.controlFrame; }
229         
230         //////////////////////////////////////////////////////////////////////
231         //////////////////////////////////////////////////////////////////////
232         
233         public void  addUserInputListener (
234           final UserInputListener  userInputListener )
235         //////////////////////////////////////////////////////////////////////
236         {
237           this.displayFrame.addKeyListener ( userInputListener );
238           
239           for ( JButton  button : this.buttonSet )
240           {
241             button.addActionListener ( userInputListener );
242           }
243         }
244         
245         //////////////////////////////////////////////////////////////////////
246         // lifecycle methods
247         //////////////////////////////////////////////////////////////////////
248         
249         public void  destroy ( )
250         //////////////////////////////////////////////////////////////////////
251         {
252           this.displayFrame.setVisible ( false );
253                 
254           this.controlFrame.setVisible ( false );
255           
256           this.displayFrame.dispose ( );
257           
258           this.controlFrame.dispose ( );             
259         }
260         
261         public void  update ( )
262         //////////////////////////////////////////////////////////////////////
263         {
264           InfantMessage  modelEvent = null;
265           
266           while ( ( modelEvent = this.eventQueue.poll ( ) ) != null )
267           {
268             final InfantMessage.Type  type = modelEvent.getType ( );
269             
270             switch ( type )
271             {
272               case DURATION_CHANGED:
273                 
274                 final Double  duration
275                   = new Double ( infantAccessor.getImageDisplayTime ( )
276                   * MathConstants.SECONDS_PER_NANOSECOND );
277                 
278                 final String  durationString
279                   = String.format ( "%1$1.3f", duration );
280                 
281                 final Double  oldDuration = ( Double )
282                   imageDisplayTimeSpinnerNumberModel.getValue ( );
283                 
284                 final String  oldDurationString
285                   = String.format ( "%1$1.3f", oldDuration );
286                 
287                 if ( !durationString.equals ( oldDurationString ) )
288                 {             
289                   imageDisplayTimeSpinnerNumberModel.setValue ( duration );
290                 }
291                 
292                 infantConfig.getLog ( ).record (
293                   createLogPrefix ( )
294                   + type.name ( )
295                   + " "
296                   + durationString );
297                 
298                 break;
299                 
300               case GET_SAVE_REPORT_FILENAME:
301                 
302                 getSaveReportFilename ( modelEvent );
303                 
304                 break;
305                 
306               case IMAGE:
307                 
308                 final String  imagePath
309                   = infantAccessor.getImagePath ( );
310                 
311                 final Image  image = imagePathToImageMap.get ( imagePath );
312                 
313                 infantComponent.setImage ( image );
314    /*             
315                 this.infantConfig.getLog ( ).record (
316                   createLogPrefix ( )
317                   + modelEvent.name ( )
318                   + " \""
319                   + ( imagePath == null ? "" : imagePath )  
320                   + "\"" );
321    */             
322                 
323                 // System.out.println ( imagePath );
324                 
325                 break;
326                 
327               case ISI_CHANGED:
328                 
329                 final Double  isi = new Double (
330                   infantAccessor.getInterStimulusInterval ( )
331                   * MathConstants.SECONDS_PER_NANOSECOND );
332                 
333                 final String  isiString
334                   = String.format ( "%1$1.3f", isi );
335                 
336                 final Double  oldIsi = ( Double )
337                   interStimulusIntervalSpinnerNumberModel.getValue ( );
338                 
339                 final String  oldIsiString
340                   = String.format ( "%1$1.3f", oldIsi );
341                 
342                 if ( !isiString.equals ( oldIsiString ) )
343                 {             
344                   this.interStimulusIntervalSpinnerNumberModel.setValue (
345                     isi );
346                 }
347                 
348                 this.infantConfig.getLog ( ).record (
349                   createLogPrefix ( )
350                   + type.name ( )
351                   + " "
352                   + isiString );
353                 
354                 break;
355                 
356               case LOAD_SETUP:
357                 
358                 loadSetup ( );
359                 
360                 break;
361                 
362               case STIMULUS_WINDOW_CLOSE:
363                 
364                 this.displayFrame.setVisible ( false );
365                 
366                 break;
367                 
368               case STIMULUS_WINDOW_OPEN:
369                 
370                 displayFrame.setExtendedState ( Frame.NORMAL );
371           
372                 displayFrame.setVisible ( true );
373                 
374                 displayFrame.requestFocus ( );
375                 
376                 break;
377                 
378               default:
379                 
380                 infantConfig.getLog ( ).record (
381                   createLogPrefix ( )
382                   + "InfantView:  unknown command:  " + type.name ( ) );             
383             }
384           }
385         }
386         
387         //////////////////////////////////////////////////////////////////////
388         //////////////////////////////////////////////////////////////////////
389         
390         private static JFrame  createControlFrame (
391           final InfantConfig        infantConfig,
392           final Set<JButton>        buttonSet,
393           final SpinnerNumberModel  controllerIndexSpinnerNumberModel,
394           final SpinnerNumberModel  imageDisplayTimeSpinnerNumberModel,
395           final SpinnerNumberModel  interStimulusIntervalSpinnerNumberModel,
396           final SpinnerNumberModel  scaleSpinnerNumberModel )
397         //////////////////////////////////////////////////////////////////////
398         {
399           final JFrame  controlFrame = new JFrame (
400             infantConfig.getFrameTitle ( ) );
401           
402           WindowLib.centerOnScreen (
403             controlFrame, infantConfig.getFrameSize ( ) );
404           
405           final Container  contentPane = controlFrame.getContentPane ( );
406           
407           contentPane.setLayout ( new BorderLayout ( ) );
408           
409           final LogPanel  logPanel
410             = new LogPanel ( infantConfig.getLogLinesMax ( ) );
411           
412           infantConfig.setLog ( logPanel );
413           
414           contentPane.add (
415             createLogScrollPane ( infantConfig, logPanel ),
416             BorderLayout.CENTER );
417           
418           final JPanel  spinnerPanel = new JPanel ( new GridBagLayout ( ) );
419           
420           final GridBagConstraints  gridBagConstraints
421             = new GridBagConstraints ( );
422           
423           gridBagConstraints.fill = GridBagConstraints.HORIZONTAL;
424           
425           gridBagConstraints.insets = new Insets ( 1, 4, 1, 0 );
426           
427           addImageDisplayTimeSpinner (
428             spinnerPanel,
429             gridBagConstraints,
430             imageDisplayTimeSpinnerNumberModel );
431           
432           addInterStimulusIntervalSpinner (
433             spinnerPanel,
434             gridBagConstraints,
435             interStimulusIntervalSpinnerNumberModel );
436           
437           addControllerIndexSpinner (
438             spinnerPanel,
439             gridBagConstraints,
440             controllerIndexSpinnerNumberModel );
441           
442           addScaleSpinner (
443             spinnerPanel,
444             gridBagConstraints,
445             scaleSpinnerNumberModel );
446           
447           final JPanel  buttonPanel = new JPanel ( new GridLayout ( 5, 1 ) );
448           
449           final JButton  loadSetupButton = new JButton ( "Load Setup" );
450           
451           loadSetupButton.setActionCommand (
452             InfantMessage.Type.LOAD_SETUP.name ( ) );
453           
454           buttonPanel.add ( loadSetupButton );
455           
456           final JButton  displayButton = new JButton ( "Begin Experiment" );
457           
458           displayButton.setActionCommand (
459             InfantMessage.Type.EXPERIMENT_BEGIN.name ( ) );
460           
461           buttonPanel.add ( displayButton );
462           
463           final JButton  endButton = new JButton ( "End Experiment" );
464           
465           endButton.setActionCommand (
466             InfantMessage.Type.EXPERIMENT_END.name ( ) );
467           
468           buttonPanel.add ( endButton );
469           
470           final JButton  resetButton = new JButton ( "Save Data" );
471           
472           resetButton.setActionCommand (
473             InfantMessage.Type.REQUEST_SAVE_FILENAME.name ( ) );
474           
475           buttonPanel.add ( resetButton );
476           
477           final JButton  clearButton = new JButton ( "Clear Log" );
478           
479           buttonPanel.add ( clearButton );
480           
481           buttonSet.add ( displayButton );
482           
483           buttonSet.add ( endButton );
484           
485           buttonSet.add ( resetButton );
486           
487           buttonSet.add ( loadSetupButton );
488           
489           // buttonSet.add ( clearButton );
490           
491           clearButton.addActionListener (
492             new ActionListener ( )
493             {
494               public void  actionPerformed ( final ActionEvent  actionEvent )
495               {
496                 logPanel.clear ( );
497               }
498             } );
499           
500           final JPanel  westPanel = new JPanel ( );
501           
502           westPanel.setLayout (
503             new BoxLayout ( westPanel, BoxLayout.Y_AXIS ) );
504           
505           westPanel.add ( spinnerPanel );
506           
507           westPanel.add ( buttonPanel );
508           
509           westPanel.add ( new Box.Filler (
510             new Dimension ( 0, 0 ),
511             new Dimension ( 0, Integer.MAX_VALUE ),
512             new Dimension ( 0, Integer.MAX_VALUE ) ) );
513           
514           contentPane.add ( westPanel, BorderLayout.WEST );
515           
516           return controlFrame;       
517         }
518         
519         private static JFrame  createStimulusFrame (
520           final InfantComponent      infantComponent,
521           final Slot<InfantMessage>  requestSlot )
522         //////////////////////////////////////////////////////////////////////
523         {       
524           final JFrame  displayFrame = new JFrame ( );
525           
526           displayFrame.addWindowListener (
527             new WindowAdapter ( )
528             {
529               @Override
530               public void  windowClosing ( final WindowEvent  windowEvent )
531               {
532                 requestSlot.offer (
533                   new InfantMessage (
534                     InfantMessage.Type.STIMULUS_WINDOW_CLOSING ) );
535               }
536             } );
537           
538           displayFrame.setUndecorated ( true );
539    
540           final Dimension  screenSize
541             = Toolkit.getDefaultToolkit ( ).getScreenSize ( );
542           
543           displayFrame.setBounds (
544             0, 0, screenSize.width, screenSize.height );
545           
546           final Container  displayContentPane
547             = displayFrame.getContentPane ( );
548           
549           displayContentPane.setLayout ( new BorderLayout ( ) );
550    
551           displayContentPane.add ( infantComponent, BorderLayout.CENTER );
552           
553           return displayFrame;       
554         }
555         
556         private static void  addControllerIndexSpinner (
557           final JPanel              spinnerPanel,
558           final GridBagConstraints  gridBagConstraints,
559           final SpinnerNumberModel  controllerIndexSpinnerNumberModel )
560         //////////////////////////////////////////////////////////////////////
561         {
562           final JSpinner  controllerIndexSpinner
563             = new JSpinner ( controllerIndexSpinnerNumberModel );
564           
565           final JSpinner.NumberEditor  numberEditor
566             = new JSpinner.NumberEditor (
567             controllerIndexSpinner, "0" );
568           
569           controllerIndexSpinner.setEditor ( numberEditor );
570           
571           final JLabel  jLabel = new JLabel ( "Controller" );
572           
573           jLabel.setBorder (
574             BorderFactory.createEmptyBorder ( 2, 2, 2, 2 ) );
575           
576           gridBagConstraints.gridx = 0;
577           
578           spinnerPanel.add ( jLabel, gridBagConstraints );
579           
580           gridBagConstraints.gridx = 1;
581           
582           spinnerPanel.add ( controllerIndexSpinner, gridBagConstraints );
583         }
584    
585         private static void  addImageDisplayTimeSpinner (
586           final JPanel              spinnerPanel,
587           final GridBagConstraints  gridBagConstraints,
588           final SpinnerNumberModel  imageDisplayTimeSpinnerNumberModel )
589         //////////////////////////////////////////////////////////////////////
590         {
591           final JSpinner  imageDisplayTimeSpinner
592             = new JSpinner ( imageDisplayTimeSpinnerNumberModel );
593           
594           final JSpinner.NumberEditor  numberEditor
595             = new JSpinner.NumberEditor (
596             imageDisplayTimeSpinner, "0.0##" );
597           
598           imageDisplayTimeSpinner.setEditor ( numberEditor );
599           
600           final JLabel  imageDisplayTimeLabel = new JLabel ( "Duration (s)" );
601           
602           imageDisplayTimeLabel.setBorder (
603             BorderFactory.createEmptyBorder ( 2, 2, 2, 2 ) );
604           
605           gridBagConstraints.gridx = 0;
606           
607           spinnerPanel.add ( imageDisplayTimeLabel, gridBagConstraints );
608           
609           gridBagConstraints.gridx = 1;
610           
611           spinnerPanel.add ( imageDisplayTimeSpinner, gridBagConstraints );
612         }
613    
614         private static void  addInterStimulusIntervalSpinner (
615           final JPanel              spinnerPanel,
616           final GridBagConstraints  gridBagConstraints,
617           final SpinnerNumberModel  interStimulusIntervalSpinnerNumberModel )
618         //////////////////////////////////////////////////////////////////////
619         {
620           final JSpinner  interStimulusIntervalSpinner
621             = new JSpinner ( interStimulusIntervalSpinnerNumberModel );
622           
623           final JSpinner.NumberEditor  numberEditor
624             = new JSpinner.NumberEditor (
625             interStimulusIntervalSpinner, "0.0##" );
626           
627           interStimulusIntervalSpinner.setEditor ( numberEditor );
628           
629           final JLabel  jLabel = new JLabel ( "ISI (s)" );
630           
631           jLabel.setBorder (
632             BorderFactory.createEmptyBorder ( 2, 2, 2, 2 ) );
633           
634           gridBagConstraints.gridx = 0;
635           
636           spinnerPanel.add ( jLabel, gridBagConstraints );
637           
638           gridBagConstraints.gridx = 1;
639           
640           spinnerPanel.add (
641             interStimulusIntervalSpinner, gridBagConstraints );
642         }
643    
644         private static void  addScaleSpinner (
645           final JPanel              spinnerPanel,
646           final GridBagConstraints  gridBagConstraints,
647           final SpinnerNumberModel  spinnerNumberModel )
648         //////////////////////////////////////////////////////////////////////
649         {
650           final JSpinner  jSpinner = new JSpinner ( spinnerNumberModel );
651           
652           final JSpinner.NumberEditor  numberEditor
653             = new JSpinner.NumberEditor ( jSpinner, "0.0##" );
654           
655           jSpinner.setEditor ( numberEditor );
656           
657           final JLabel  jLabel = new JLabel ( "Scale" );
658           
659           jLabel.setBorder (
660             BorderFactory.createEmptyBorder ( 2, 2, 2, 2 ) );
661           
662           gridBagConstraints.gridx = 0;
663           
664           spinnerPanel.add ( jLabel, gridBagConstraints );
665           
666           gridBagConstraints.gridx = 1;
667           
668           spinnerPanel.add ( jSpinner, gridBagConstraints );
669         }
670    
671         private static String  createLogPrefix ( )
672         //////////////////////////////////////////////////////////////////////
673         {
674           return dateFormat.format ( new Date ( ) ) + ":  ";
675         }
676         
677         private static JScrollPane  createLogScrollPane (
678           final InfantConfig  infantConfig,
679           final LogPanel      logPanel )
680         //////////////////////////////////////////////////////////////////////
681         {
682           logPanel.record ( infantConfig.getInfo ( ) );
683           
684           final JScrollPane  jScrollPane = new JScrollPane ( logPanel );
685           
686           jScrollPane.setBorder (
687             BorderFactory.createEtchedBorder ( EtchedBorder.RAISED ) );
688           
689           return jScrollPane;
690         }
691         
692         private void  loadSetup ( )
693         //////////////////////////////////////////////////////////////////////
694         {
695           final JFileChooser  jFileChooser
696             = new JFileChooser ( InfantConfig.getInfantSetupDirPath ( ) );
697           
698           final int  returnVal = jFileChooser.showOpenDialog ( controlFrame );
699           
700           if ( returnVal != JFileChooser.APPROVE_OPTION )
701           {
702             return;
703           }
704           
705           final File  file = jFileChooser.getSelectedFile ( );
706           
707           if ( file != null )
708           {
709             requestSlot.offer (
710               new InfantMessage (
711                 InfantMessage.Type.LOAD_SETUP,
712                 file ) );
713           }
714         }
715         
716         private void  getSaveReportFilename (
717           final InfantMessage  infantMessage )
718         //////////////////////////////////////////////////////////////////////
719         {
720           final InfantData  infantData
721             = ( InfantData ) infantMessage.getValue ( );
722           
723           final JFileChooser  jFileChooser = new JFileChooser (
724             InfantConfig.getInfantReportDirPath ( ) );
725           
726           final int  returnVal
727             = jFileChooser.showSaveDialog ( controlFrame );
728           
729           if ( returnVal != JFileChooser.APPROVE_OPTION )
730           {
731             return;
732           }
733           
734           final File  file = jFileChooser.getSelectedFile ( );
735           
736           if ( file != null )
737           {
738             requestSlot.offer (
739               new InfantMessage (
740                 InfantMessage.Type.SAVE_DATA,
741                 new Object [ ] { file, infantData } ) );
742           }
743         }
744         
745         //////////////////////////////////////////////////////////////////////
746         //////////////////////////////////////////////////////////////////////
747         }