Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: jpl2/PsionLink.java


1   package jpl2;
2   
3   /***********************************************************************
4    *   JavaPsionLink 2.0, a java implementation of the psion link protocol
5    *   Copyright (C) 2002, 2003  John S Montgomery (john.montgomery@lineone.net)
6    *
7    *   This program is free software; you can redistribute it and/or modify
8    *   it under the terms of the GNU Lesser General Public License as published by
9    *   the Free Software Foundation; either version 2.1 of the License, or
10   *   (at your option) any later version.
11   *
12   *   This program is distributed in the hope that it will be useful,
13   *   but WITHOUT ANY WARRANTY; without even the implied warranty of
14   *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15   *   GNU Lesser General Public License for more details.
16   *
17   *   You should have received a copy of the GNU Lesser General Public License
18   *   along with this program; if not, write to the Free Software
19   *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20   ************************************************************************/
21  
22  import jpl2.common.*;
23  import jpl2.common.gui.*;
24  import jpl2.link.*;
25  import jpl2.link.channels.*;
26  import jpl2.link.layers.*;
27  import jpl2.link.gui.*;
28  import jpl2.link.backup.*;
29  import jpl2.link.backup.gui.BackupDialog;
30  import jpl2.link.backup.gui.RestoreDialog;
31  import jpl2.convert.gui.*;
32  import java.io.*;
33  //import javax.comm.*;
34  import java.awt.*;
35  import java.awt.event.*;
36  import java.util.*;
37  //import java.text.MessageFormat;
38  //import PsionBackup; // sun 1.4 compiler does not like this
39  
40  
41  // TODO remember size and positions of all windows?
42  // maybe use JPLToolkit and assign ids to windows?
43  
44  /** The main class.
45   **/
46  public class PsionLink {
47  
48    public static boolean DEBUG = false;
49  
50    ///////////////////////////////////////
51    // the main window
52    private Frame frame = null;
53    ///////////////////////////////////////
54  
55    ///////////////////////////////////////
56    // are we connected?
57    private boolean connected = false;
58    ///////////////////////////////////////
59  
60    ///////////////////////////////////////
61    // layers
62    private PhysicalLayer physicalLayer = null;
63    private DataLinkLayer dataLinkLayer = null;
64    private SessionLayer  sessionLayer  = null;
65    ///////////////////////////////////////
66  
67    ///////////////////////////////////////
68    // components
69    private PsionFileBrowser psionBrowser = null;
70    private DataLinkCanvas dataLinkCanvas = null;
71    private Component localDirectoryLabel = null;
72    ///////////////////////////////////////
73  
74    ///////////////////////////////////////
75    // handles opening files direct from psion
76    private OpenPsionFileHandler openHandler = new OpenPsionFileHandler();
77    ///////////////////////////////////////
78    
79    ///////////////////////////////////////
80    // menus
81    private Object fileMenu   = null;
82    private Object diskMenu   = null;
83    private Object editMenu   = null;
84    private Object syncMenu     = null;
85    private Object convert      = null;
86    ////////////////////////////////////////
87    // menuitems
88    private Object get        = null;
89    private Object putFile     = null;
90    private Object putFolder   = null;
91    private Object backupMenu   = null;
92    private Object restoreMenu  = null;
93    private Object connect    = null;
94    private Object downloadTo   = null;
95    private Object selectAll    = null;
96    private Object pasteToPsion = null;
97    private Object byName       = null;
98    private Object byType       = null;
99    private Object byDate       = null;
100   private Object bySize       = null;
101   private Object syncTime     = null;
102 
103   //private MenuItem disconnect = null;
104   ///////////////////////////////////////
105 
106   ///////////////////////////////////////
107   // ref to self for inner classes
108   private static PsionLink self = null;
109   ///////////////////////////////////////
110 
111   ///////////////////////////////////////
112   // current directory to put files
113   private String downloadDirectory = null;
114   ///////////////////////////////////////
115 
116 
117   private Preference prefs = null;
118   private static JPLToolkit tk = null;
119   //private static ResourceBundle resources = JPLToolkit.getBundle();
120   private WorkerThread worker = null;
121   
122   private int widthPref, heightPref;
123   
124   public PsionLink() {
125     self = this;
126 
127     tk = JPLToolkit.getToolkit();
128     worker = new WorkerThread();
129     
130     frame = tk.makeFrame( "" );
131     frame.setBackground( SystemColor.control );
132 
133     showStatus( "@res:notConnected" );
134 
135     prefs = JPLToolkit.getPreferences();
136 
137     widthPref = prefs.getPreference( "width", -1 );
138     heightPref = prefs.getPreference( "height", -1 );
139     
140     // under 1.2 we would use onExit event handler to
141     // save the windows state, but at the moment we save
142     // the state whenever a change occurs
143     frame.addComponentListener(
144       new ComponentAdapter() {
145 
146         public void componentMoved( ComponentEvent ce ) {
147           // store location moved to
148           Point p = frame.getLocation();
149           prefs.setPreference( "x", p.x );
150           prefs.setPreference( "y", p.y );
151           ////
152         }
153         
154         public void componentResized( ComponentEvent ce ) {
155           // store size
156           Dimension size = frame.getSize();
157           prefs.setPreference( "width", size.width );
158           prefs.setPreference( "height", size.height );
159           ////
160         }
161 
162       } );
163 
164     
165 
166     // load in the previously saved location
167     int x = prefs.getPreference( "x", 0 );
168     int y = prefs.getPreference( "y", 0 );
169     
170     DEBUG = prefs.getPreference( "print_debug", false );
171     
172     // check it's in bounds of the screen
173     Toolkit awtk = frame.getToolkit();
174     Dimension screenSize = awtk.getScreenSize();
175     // ensure there is at least a little bit to grab hold of
176     x = Math.max( 0, Math.min( x, screenSize.width-10 ) );
177     y = Math.max( 0, Math.min( y, screenSize.height-10 ) );
178 
179     frame.setLocation( x, y );
180     ////
181 
182     downloadDirectory = prefs.getPreference( "downloadDirectory", System.getProperty( "user.dir" ) );
183 
184     File downloadDir = new File( downloadDirectory );
185     if ( !downloadDir.exists() ) {
186       // if the stored directory does not exist, reset to user.dir
187       downloadDirectory = System.getProperty( "user.dir" );
188       prefs.setPreference( "downloadDirectory", downloadDirectory );
189     }
190 
191     psionBrowser = new PsionFileBrowser( frame, worker, openHandler );
192 
193     tk.add( frame, psionBrowser.getContainer(), BorderLayout.CENTER );
194 
195     localDirectoryLabel = tk.makeLabel( "@res:downloadLabel", new Object[] {  downloadDirectory } );
196     
197     
198     dataLinkCanvas = new DataLinkCanvas();
199     Container bottom = tk.makePanel();
200     bottom.setLayout( new BorderLayout() );
201     bottom.add( localDirectoryLabel, BorderLayout.CENTER );
202     bottom.add( dataLinkCanvas, BorderLayout.EAST );
203     tk.add( frame, bottom, BorderLayout.SOUTH );
204 
205     makeMenus();
206 
207     tk.installDragAndDrop( this, psionBrowser.getBrowserPane() );
208     
209     
210     worker.start();
211   }
212 
213   public static Frame getMainFrame() {
214     return self.frame;
215   }
216 
217   public static void showStatus( String status ) {
218     status = JPLToolkit.getResourceString( status );
219     String title = JPLToolkit.getResourceString( "@res:appTitle", new Object[] { status } );
220     if ( DEBUG )
221       System.out.println( title );
222     self.frame.setTitle( title );
223   }
224   
225   public static void showStatus( String status, Object[] params ) {
226     status = JPLToolkit.getResourceString( status, params );
227     String title = JPLToolkit.getResourceString( "@res:appTitle", new Object[] { status } );
228     if ( DEBUG )
229       System.out.println( title );
230     self.frame.setTitle( title );
231   }
232 
233   /** Try to add some more explanation for common exceptions.
234    **/
235   private static String makeHumanReadable( Exception e ) {
236     /*CharArrayWriter chars = new CharArrayWriter();
237     PrintWriter pw = new PrintWriter( chars );
238     e.printStackTrace( pw );
239     pw.flush();
240     String str = chars.toString();
241     return str.replace( '\r', '\n' );*/
242     if ( e instanceof PsionLinkException ) {
243       int reason = ((PsionLinkException)e).getReasonCode();
244       switch( reason ) {
245         case PsionLinkException.CONNECTING_TO_SELF:
246           return "@res:CONNECTING_TO_SELF";
247         
248         case PsionLinkException.NO_CONNECTION_CONFIRMATION:
249           return "@res:NO_CONNECTION_CONFIRMATION";
250 
251         case PsionLinkException.NO_ACKNOWLEDGEMENT:
252           return "@res:NO_ACKNOWLEDGEMENT";
253         
254         case PsionLinkException.INTERRUPTED_CONNECTION:
255           return "@res:INTERRUPTED_CONNECTION";
256         
257         case PsionLinkException.FRAME_RECEIVE_TIMED_OUT:
258           return "@res:FRAME_RECEIVE_TIMED_OUT";
259         
260       }
261     }
262     
263     return e.toString();
264   }
265 
266   /** Handle an exception.  ie show a dialog reporting the error and disconnect
267    *  if indicated to do so.
268    **/
269   public static void handleException( Exception e, boolean disconnect ) {
270     if ( DEBUG || e instanceof RuntimeException ) {
271       e.printStackTrace();
272     }
273     tk.showMessageDialog( self.frame, "@res:error", makeHumanReadable( e ) );
274     if ( disconnect ) {
275       // reset the connection
276       self.disconnect();
277     }
278   }
279 
280   public boolean isConnected() {
281     return connected;
282   }
283 
284   public SessionLayer getSessionLayer() {
285     return sessionLayer;
286   }
287   
288   public WorkerThread getWorkerThread() {
289     return worker;
290   }
291   
292   public void disconnect() {
293     try {
294       if ( dataLinkLayer != null )
295         dataLinkLayer.disconnect();
296     }
297     catch( Exception e ) {
298       handleException( e, false );
299     }
300     
301     try {
302       if ( physicalLayer != null )
303         physicalLayer.disconnect();
304     }
305     catch( Exception e ) {
306       handleException( e, false );
307     }
308     
309     // prod anything waiting for data
310     if ( worker != null )
311       worker.interrupt();
312     
313     physicalLayer = null;
314     dataLinkLayer = null;
315     sessionLayer  = null;
316 
317     tk.setEnabled( get, false );
318     tk.setEnabled( putFile, false );
319     tk.setEnabled( putFolder, false );
320     tk.setEnabled( backupMenu, false);
321     tk.setEnabled( restoreMenu, false);
322     tk.setEnabled( connect, true );
323     tk.setEnabled( pasteToPsion, false );
324     tk.removeAll( diskMenu );
325     tk.setEnabled( diskMenu, false );
326     tk.setEnabled( syncMenu, false );
327 
328     dataLinkCanvas.reset();
329     psionBrowser.reset();
330     showStatus( "@res:notConnected" );
331     connected = false;
332   }
333 
334   private boolean uploadFolder = false;
335 
336   private void makeMenus() {
337     fileMenu = tk.makeMenu( "@res:fileMenu" );
338 
339     // CONNECTION /////////////////////////
340     connect = tk.makeMenuItem( "@res:connect",
341       new MenuShortcut( KeyEvent.VK_N ),
342       "connect",
343       new ActionListener() {
344         public void actionPerformed( ActionEvent ae ) {
345           tk.setEnabled( connect, false );
346           //System.out.println( "connect1" );
347           worker.schedule( new Runnable() {
348             public void run() {
349                //System.out.println( "connect2" );
350               //synchronized( psionBrowser ) {
351                  //System.out.println( "connect3" );
352                  
353                 try {
354                   if ( !selectPort() ) {
355                     tk.setEnabled( connect, true );
356                      return;
357                   }
358                 }
359                 catch( Exception e ) {
360                   handleException( e, true );
361                 }
362                 catch( Error e ) {
363                   // TODO move into resource
364                   tk.showMessageDialog( frame, "Error", "Could not select a serial port.\nPlease ensure the \"javax.comm\" library is installed correctly.");
365                 }
366               //}
367             }
368           });
369 
370         }
371       } );
372 
373     tk.addMenuItem( fileMenu, connect );
374     
375     tk.addSeparator( fileMenu );
376     
377     // GET //////////////////////////
378     get = tk.makeMenuItem( "@res:get",
379       new MenuShortcut( KeyEvent.VK_G ),
380       "get",
381       new ActionListener() {
382         public void actionPerformed( ActionEvent ae ) {
383           frame.setCursor( Cursor.getPredefinedCursor( Cursor.WAIT_CURSOR ) );
384           frame.setEnabled( false );
385           worker.schedule( new Runnable() {
386               public void run() {
387                 try {
388                   downloadSelected();
389                 }
390                 catch( Exception e ) {
391                   handleException( e, true );
392                 }
393                 frame.setEnabled( true );
394                 frame.setCursor( Cursor.getPredefinedCursor( Cursor.DEFAULT_CURSOR ) );
395               }
396             } );
397           
398         }
399       } );
400     tk.setEnabled( get, false );
401 
402     tk.addMenuItem( fileMenu, get );
403     /////////////////////////////////////
404 
405     // PUT //////////////////////////////
406     // control/apple-P
407 
408     ActionListener putListener = new ActionListener() {
409       public void actionPerformed( ActionEvent ae ) {
410         frame.setCursor( Cursor.getPredefinedCursor( Cursor.WAIT_CURSOR ) );
411         frame.setEnabled( false );
412         uploadFolder = !ae.getActionCommand().equals( "put" );
413         worker.schedule( new Runnable() {
414 
415             public void run() {
416               try {
417                 upload( uploadFolder );
418               }
419               catch( Exception e ) {
420                 handleException( e, true );
421               }
422               frame.setEnabled( true );
423               frame.setCursor( Cursor.getPredefinedCursor( Cursor.DEFAULT_CURSOR ) );
424             }
425                 
426           } );
427 
428       }
429     };
430 
431     putFile = tk.makeMenuItem( "@res:putFile",
432       new MenuShortcut( KeyEvent.VK_P ),
433       "put", putListener );
434     tk.setEnabled( putFile, false );
435     tk.addMenuItem( fileMenu, putFile );
436 
437     // shift-control/apple-P
438     putFolder = tk.makeMenuItem( "@res:putFolder",
439       new MenuShortcut( KeyEvent.VK_P, true ),
440       "putfolder",
441       putListener );
442 
443     tk.setEnabled( putFolder, false );
444     if ( tk.canChooseFolders() )
445       tk.addMenuItem( fileMenu, putFolder );
446     //////////////////////////////////////////////
447     
448     tk.addSeparator( fileMenu );
449     
450     // BACKUP ////////////////////////////////////
451     backupMenu = tk.makeMenuItem( "@res:backup", 
452       null,
453       "backup",
454       new ActionListener() {
455         public void actionPerformed( ActionEvent ae ) {
456           try {
457             PsionBackup pback = new PsionBackup(sessionLayer);
458             BackupDialog dialog = new BackupDialog( frame, worker, sessionLayer.getRFSVChannel().getAllDrives(), pback );
459             dialog.show();
460           }
461           catch( Exception e ) {
462             e.printStackTrace();
463           }
464         }
465       } );
466       
467     // RESTORE ////////////////////////////////////
468     restoreMenu = tk.makeMenuItem( "@res:restore", 
469       null,
470       "restore",
471       new ActionListener() {
472         public void actionPerformed( ActionEvent ae ) {
473           try {
474             PsionBackup pback = new PsionBackup(sessionLayer);
475             RestoreDialog dialog = new RestoreDialog( frame, worker, sessionLayer.getRFSVChannel().getAllDrives(), pback );
476             dialog.show();
477           }
478           catch( Exception e ) {
479             e.printStackTrace();
480           }
481         }
482       } );
483     
484 
485     
486     tk.setEnabled( backupMenu, false);
487     tk.addMenuItem( fileMenu, backupMenu );
488     
489     tk.setEnabled( restoreMenu, false);
490     tk.addMenuItem( fileMenu, restoreMenu );
491     ///////////////////////////////////////
492 
493     tk.addSeparator( fileMenu );
494     
495     
496     Object closeMenuItem = 
497       tk.makeMenuItem( "@res:closeWindow",
498                new MenuShortcut( KeyEvent.VK_W ),
499                "close",
500                new ActionListener() {
501                 public void actionPerformed( ActionEvent ae ) {
502                   frame.dispatchEvent( new WindowEvent( frame, WindowEvent.WINDOW_CLOSING ) );
503                 }
504               } );
505       
506     tk.addMenuItem( fileMenu, closeMenuItem );
507     ////////////////////////////////////////
508 
509     // EDIT MENU ///////////////////////////
510     editMenu = tk.makeMenu( "@res:editMenu" );
511     downloadTo = tk.makeMenuItem( "@res:downloadTo",
512       null,
513       "changefolder",
514       new ActionListener() {
515         public void actionPerformed( ActionEvent ae ) {
516           changeDownloadDirectory();
517         }
518       } );
519 
520     if ( tk.canChooseFolders() )
521       tk.addMenuItem( editMenu, downloadTo );
522 
523     selectAll = tk.makeMenuItem( "@res:selectAll",
524       new MenuShortcut( KeyEvent.VK_A ),
525       "selectall",
526       new ActionListener() {
527         public void actionPerformed( ActionEvent ae ) {
528           psionBrowser.selectAll();
529         }
530       } );
531 
532     tk.addMenuItem( editMenu, selectAll );
533 
534     Object sortMenu = tk.makeMenu( "@res:sortMenu" );
535 
536     tk.addMenuItem( editMenu, sortMenu );
537 
538     ActionListener sortListener = new ActionListener() {
539       public void actionPerformed( ActionEvent ae ) {
540         Object source = ae.getSource();
541         int sortBy = PsionFileBrowser.SORT_BY_NAME;
542         String sortString = "name";
543         if ( source == byType ) {
544           sortBy = PsionFileBrowser.SORT_BY_TYPE;
545           sortString = "type";
546         }
547         else if ( source == byDate ) {
548           sortBy = PsionFileBrowser.SORT_BY_DATE;
549           sortString = "date";
550         }
551         else if ( source == bySize ) {
552           sortBy = PsionFileBrowser.SORT_BY_SIZE;
553           sortString = "size";
554         }
555 
556         // disable the menu item that has just been
557         // used and enable the others
558         tk.setEnabled( byName, byName != source );
559         tk.setEnabled( byType, byType != source );
560         tk.setEnabled( byDate, byDate != source );
561         tk.setEnabled( bySize, bySize != source );
562         psionBrowser.setSortMethod( sortBy );
563         prefs.setPreference( "sort", sortString );
564       }
565     };
566 
567 
568 
569     byName = tk.makeMenuItem( "@res:byName", null, "byname", sortListener );
570     byType = tk.makeMenuItem( "@res:byType", null, "bytype", sortListener );
571     byDate = tk.makeMenuItem( "@res:byDate", null, "bydate", sortListener );
572     bySize = tk.makeMenuItem( "@res:bySize", null, "bysize", sortListener );
573 
574     String sortMethod = prefs.getPreference( "sort", "name" );
575 
576     if ( sortMethod.equals( "name" ) ) {
577       tk.setEnabled( byName, false );
578       psionBrowser.setSortMethod( PsionFileBrowser.SORT_BY_NAME );
579     }
580     else if ( sortMethod.equals( "type" ) ) {
581       tk.setEnabled( byType, false );
582       psionBrowser.setSortMethod( PsionFileBrowser.SORT_BY_TYPE );
583     }
584     else if ( sortMethod.equals( "date" ) ) {
585       tk.setEnabled( byDate, false );
586       psionBrowser.setSortMethod( PsionFileBrowser.SORT_BY_DATE );
587     }
588     else if ( sortMethod.equals( "size" ) ) {
589       tk.setEnabled( bySize, false );
590       psionBrowser.setSortMethod( PsionFileBrowser.SORT_BY_SIZE );
591     }
592 
593 
594 
595     tk.addMenuItem( sortMenu, byName );
596     tk.addMenuItem( sortMenu, byType );
597     tk.addMenuItem( sortMenu, byDate );
598     tk.addMenuItem( sortMenu, bySize );
599 
600     pasteToPsion = tk.makeMenuItem( "@res:pasteToPsion",
601       new MenuShortcut( KeyEvent.VK_V ),
602       "paste",
603       new ActionListener() {
604         public void actionPerformed( ActionEvent ae ) {
605           worker.schedule( new Runnable() {
606             public void run() {
607                try {
608 
609                 ClipBdChannel clip = sessionLayer.getClipBdChannel();
610                 clip.pasteToPsion();
611                }
612                catch( Exception e ) {
613                  handleException( e, true );
614                }
615             }
616           } );
617         }
618       } );
619     tk.setEnabled( pasteToPsion, false );
620 
621     tk.addMenuItem( editMenu, pasteToPsion );
622     
623     tk.addSeparator( editMenu );
624     
625     // pref menus //
626     
627     Object preferencesMenu = tk.makeMenu( "@res:preferencesMenu" );
628     
629     Object generalPrefs = tk.makeMenuItem( "@res:generalPreferences",
630       null,
631       "generalprefs",
632       new ActionListener() {
633         public void actionPerformed( ActionEvent ae ) {
634           PreferenceDialog.showGeneralPrefs( frame );
635         }
636       } );
637     
638     tk.addMenuItem( preferencesMenu, generalPrefs );
639     
640     Object connectionPrefs = tk.makeMenuItem( "@res:connectionPreferences",
641       null,
642       "connectionprefs",
643       new ActionListener() {
644         public void actionPerformed( ActionEvent ae ) {
645           PreferenceDialog.showConnectionPrefs( frame );
646         }
647       } );
648 
649     tk.addMenuItem( preferencesMenu, connectionPrefs );
650     
651     
652     Object conversionPrefs = tk.makeMenuItem( "@res:conversionPreferences",
653       null,
654       "conversionprefs",
655       new ActionListener() {
656         public void actionPerformed( ActionEvent ae ) {
657           openHandler.showConversionPrefsDialog( frame );
658         }
659       } );
660 
661     tk.addMenuItem( preferencesMenu, conversionPrefs );
662     
663     Object printPrefs = tk.makeMenuItem( "@res:printingPreferences",
664       null,
665       "printingprefs",
666       new ActionListener() {
667         public void actionPerformed( ActionEvent ae ) {
668           PreferenceDialog.showPrintingPrefs( frame );
669         }
670       } );
671     
672     tk.addMenuItem( preferencesMenu, printPrefs );
673     
674     Object filePrefs = tk.makeMenuItem( "@res:filePreferences",
675       null,
676       "fileprefs",
677       new ActionListener() {
678         public void actionPerformed( ActionEvent ae ) {
679           openHandler.showFilePrefsDialog( frame );
680         }
681       } );
682     
683     tk.addMenuItem( preferencesMenu, filePrefs );
684     
685     tk.addMenuItem( editMenu, preferencesMenu );
686     ///////////////////////////////////////////
687 
688     // DISK MENU (filled in after connection) //////////
689     diskMenu = tk.makeMenu( "@res:diskMenu" );
690     tk.setEnabled( diskMenu, false );
691     ////////////////////////////////////////////////////
692 
693 
694     // SYNC ////////////////////////////////////////////
695     syncMenu = tk.makeMenu( "@res:syncMenu" );
696     tk.setEnabled( syncMenu, false );
697     syncTime = tk.makeMenuItem( "@res:syncTime",
698       null,
699       "synctime",
700       new ActionListener() {
701         public void actionPerformed( ActionEvent ae ) {
702           worker.schedule( new Runnable() {
703             public void run() {
704                try {
705 
706                 RPCSChannel rpcs = sessionLayer.getRPCSChannel();
707                 rpcs.syncTime();
708                }
709                catch( Exception e ) {
710                  handleException( e, true );
711                }
712             }
713           } );
714         }
715       } );
716     tk.addMenuItem( syncMenu, syncTime );
717     ////////////////////////////////////////////////////
718 
719     // CONVERT /////////////////////////////////////////
720     convert = tk.makeMenu( "@res:convertMenu" );
721 
722     Object convertFiles = tk.makeMenuItem( "@res:convert", null, "convertfiles",
723       new ActionListener() {
724         public void actionPerformed( ActionEvent ae ) {
725           convertFiles();
726         }
727       } );
728 
729     tk.addMenuItem( convert, convertFiles );
730     ////////////////////////////////////////////////////
731 
732 
733     // HELP MENU ///////////////////////////////////////
734     Object  helpMenu = tk.makeMenu( "@res:helpMenu" );
735     String aboutJPL2 = JPLToolkit.getResourceString( "@res:aboutJPL2", new Object[] { "JPL 2.0" } );
736     Object about = tk.makeMenuItem( aboutJPL2, null, "about",
737       new ActionListener() {
738         public void actionPerformed( ActionEvent ae ) {
739           aboutJPL();
740         }
741       } );
742     tk.addMenuItem( helpMenu, about );
743     ////////////////////////////////////////////////////
744 
745 
746     Object menuBar = tk.makeMenuBar();
747     tk.addMenu( menuBar, fileMenu );
748     tk.addMenu( menuBar, editMenu );
749     tk.addMenu( menuBar, diskMenu );
750     tk.addMenu( menuBar, syncMenu );
751     tk.addMenu( menuBar, convert );
752     tk.setHelpMenu( menuBar, helpMenu );
753 
754     tk.setMenuBar( frame, menuBar );
755   }
756 
757   /** Centre a Window, realtive to this frame.
758    **/
759   public void centreWindow( Window w ) {
760     tk.centreWindow( frame, w );
761   }
762 
763   private void changeDownloadDirectory() {
764     File file = tk.chooseFolder( frame, "@res:changeDownloadFolder" );
765     if ( file != null ) {
766       downloadDirectory = file.toString();
767       tk.setText( localDirectoryLabel, "@res:downloadLabel", new Object[] {  downloadDirectory } );
768       localDirectoryLabel.repaint();
769       // store preference
770       prefs.setPreference( "downloadDirectory", downloadDirectory );
771     }
772   }
773 
774 
775   private void convertFiles() {
776     ConvertFileDialog convertDialog = new ConvertFileDialog();
777     Frame dialog = convertDialog.getWindow();
778     dialog.pack();
779     centreWindow( dialog );
780     convertDialog.show();
781   }
782 
783   public static PsionLink getLink() {
784     return self;
785   }
786   
787   /** Connects, using serial port and baud rate stored in prefs.
788    *  Convenience method, to be used by people who need to programmaticaly
789    *  control the link.
790    **/
791   public void connect() throws IOException {
792     String port = prefs.getPreference( "serialPort", null );
793     int    baud = prefs.getPreference( "baud", 115200 );
794     if ( port != null )
795       connect( port, baud );
796   }
797   
798   /** Start the link, connecting using the port and baudrate specified.
799    **/
800   public void connect( String portName, int baudRate ) throws IOException {
801     
802     showStatus( "@res:connecting" );
803 
804     physicalLayer = new PhysicalLayer();
805     
806     if ( !physicalLayer.connect( portName, baudRate ) ) {
807       tk.showMessageDialog( frame, "@res:error", "Unable to connect to: " + portName );
808       disconnect();
809       return;
810     }
811     
812     if ( sessionLayer != null )
813       sessionLayer.deleteObserver( dataLinkCanvas );
814     if ( dataLinkLayer != null )
815       dataLinkLayer.deleteObserver( dataLinkCanvas );
816     
817     dataLinkLayer = new DataLinkLayer();
818     sessionLayer  = new SessionLayer( worker );
819 
820     physicalLayer.setDataLinkLayer( dataLinkLayer );
821     dataLinkLayer.setPhysicalLayer( physicalLayer );
822     dataLinkLayer.setSessionLayer(  sessionLayer );
823     sessionLayer.setDataLinkLayer(  dataLinkLayer );
824 
825     sessionLayer.addObserver( dataLinkCanvas );
826     dataLinkLayer.addObserver( dataLinkCanvas );
827 
828     dataLinkLayer.setTimeout( physicalLayer.getTimeout() );
829 
830     dataLinkLayer.connect();
831     
832     if ( !dataLinkLayer.isSIBOConnection() ) {
833       sessionLayer.connect();
834 
835       psionBrowser.setSessionLayer( sessionLayer );
836 
837       tk.setEnabled( get, true );
838       tk.setEnabled( putFolder, true );
839       tk.setEnabled( backupMenu, true );
840       tk.setEnabled( restoreMenu, true );
841       tk.setEnabled( putFile, true );
842       tk.setEnabled( pasteToPsion, true );
843       tk.setEnabled( syncMenu, true );
844       connected = true;
845       
846       worker.schedule( new Runnable() {
847             public void run() {
848               prepareDiskMenu();
849             }
850           } );
851     }
852     else {
853       // SIBO Connection
854       throw new IOException( "SIBO Connection established, but SIBO devices not yet supported." );
855     }
856   }
857 
858 
859   /** Prompt the user to select a port and baud rate, and then connects.
860    **/
861   public boolean selectPort() throws IOException {
862 
863     final Dialog dialog = tk.makeDialog( frame, "@res:selectSerialPort", true );
864     tk.setLayout( dialog, new GridLayout( 3, 1 ) );
865 
866     Vector ports = new Vector();
867     String port = null;
868 
869     String[] portNames = PhysicalLayer.getSerialPortNames();
870 
871     if ( portNames.length == 0 ) {
872       tk.showMessageDialog( frame, "No Serial Ports", "No Serial Ports where found.\nThis may be because you do not have access permission." );
873       return false;
874     }
875 
876     Component portChoice = tk.makeChoice( portNames, null );
877 
878     String[] bauds = { "115200", "57600", "38400", "19200", "9600" };
879     Component baudChoice = tk.makeChoice( bauds, null );
880 
881     // retrieve prefs
882     port = prefs.getPreference( "serialPort", port );
883     String baud = prefs.getPreference( "baud", bauds[ 0 ] );
884 
885     if ( DEBUG )
886       System.out.println( "Prefs " + port + "@" + baud );
887     
888     // make sure prefs are valid choices
889     tk.setSelected( portChoice, port );
890     tk.setSelected( baudChoice, baud );
891     //////////////////////////
892 
893 
894     final StringBuffer actionResult = new StringBuffer();
895 
896     ActionListener actions = new ActionListener() {
897       public void actionPerformed( ActionEvent ae ) {
898         String action = ae.getActionCommand();
899         actionResult.append( action );
900         dialog.dispose();
901       }
902     };
903 
904     Component done   = tk.makeButton( "@res:connectButton", "done",   actions );
905     Component cancel = tk.makeButton( "@res:cancelButton",  "cancel", actions );
906 
907 
908     Container panel = tk.makePanel();
909 
910     tk.add( dialog, portChoice );
911     tk.add( dialog, baudChoice );
912     panel.add( cancel );
913     panel.add( done );
914     tk.add( dialog, panel );
915 
916     tk.setDefaultButton( dialog, done );
917     tk.setCancelButton( dialog, cancel );
918 
919     dialog.pack();
920     centreWindow( dialog );
921 
922     dialog.requestFocus();
923     dialog.show();
924 
925     String action = actionResult.toString();
926     if ( action.equals( "done" ) ) {
927 
928       port = tk.getSelected( portChoice );
929 
930       baud = tk.getSelected( baudChoice );
931       
932       PsionLink.showStatus( port + "@" + baud );
933       
934       try {
935 
936         connect( port, Integer.parseInt( baud ) );
937 
938         // store prefs
939         prefs.setPreference( "serialPort", port );
940         prefs.setPreference( "baud", baud );
941         
942         return true;
943       }
944       catch( java.lang.Error e ) {
945         // need this for keyspan adapter
946         // when used with rxtx seems to 
947         // mess up with an unsatisfied link error
948         // if the correct port is not used
949         tk.showMessageDialog( frame, e.toString(), "An Error occurred trying to open\n this serial port.\nPlease try a different port." );
950         return false;
951       }
952     }
953 
954     return false;
955   }
956 
957   private void prepareDiskMenu() {
958     try {
959       tk.setEnabled( diskMenu, true );
960       tk.removeAll( diskMenu );
961       RFSVChannel rfsv = sessionLayer.getRFSVChannel();
962       byte[] drives = rfsv.getDriveList();
963       for ( int i = 0; i < drives.length; i++ ) {
964         if ( drives[ i ] != 0 ) {
965           Drive drive = rfsv.volume( i );
966           if ( drive != null ) {
967             // add to drive menu
968             char letter = drive.getDriveLetter();
969 
970             // key codes VK_A - VK_Z are same as ascii A-Z
971             letter = Character.toUpperCase( letter );
972             int keyCode = (int)letter;
973 
974             Object disk = tk.makeMenuItem( letter + " \"" + drive.getLabel() + "\"",
975               new MenuShortcut( keyCode, true ),
976               String.valueOf( letter ),
977               new ActionListener() {
978                 public void actionPerformed( ActionEvent ae ) {
979                   String action = ae.getActionCommand();
980                   //System.out.println( action );
981                   psionBrowser.cd( action + ":\\" );
982                 }
983               } );
984 
985             tk.addMenuItem( diskMenu, disk );
986           }
987         }
988       }
989     }
990     catch( Exception e ) {
991       handleException( e, true );
992     }
993   }
994 
995 
996   private void upload( boolean folder ) throws IOException {
997     File file = folder? tk.chooseFolder( frame, "@res:uploadFolder" ) : tk.chooseOpenFile( frame, "@res:uploadFile" );
998 
999     if ( file != null ) {
1000      upload( new File[] { file } );
1001    }
1002  }
1003
1004  /** Upload the files to the current directory.
1005   **/
1006  public void upload( File[] files ) throws IOException {
1007    upload( files, prefs.getPreference( "uploadHiddenFiles", false ) );
1008  }
1009  
1010  /** Upload the files to the current directory.
1011   **/
1012  public void upload( File[] files, boolean uploadHiddenFiles ) throws IOException {
1013    
1014    FileDownload fileDownload = new FileDownload( frame, "@res:uploading" );
1015    fileDownload.show();
1016
1017    fileDownload.setNumFiles( 0 );
1018
1019    RFSVChannel rfsv = sessionLayer.getRFSVChannel();
1020
1021    String toDir = rfsv.sessionPath();
1022
1023    Vector filesList = new Vector();
1024    Hashtable parents = new Hashtable();
1025
1026    for ( int i = 0; i < files.length; i++ ) {
1027      File file = files[ i ];
1028      if ( !uploadHiddenFiles && JPLToolkit.isHiddenFile( file ) )
1029        continue;
1030        
1031      filesList.addElement( file );
1032      parents.put( file, file.getParent() );
1033      if ( file.isDirectory() )
1034        expandDir( fileDownload, file, filesList, file.getParent(), parents, uploadHiddenFiles );
1035    }
1036    fileDownload.setNumFiles( filesList.size() );
1037
1038    for ( int i = 0; i < filesList.size(); i++ ) {
1039      if ( fileDownload.isCancelled() )
1040        return;
1041      File filei = (File)filesList.elementAt( i );
1042
1043      String fromDir = (String)parents.get( filei );
1044
1045      String path = filei.getParent() + File.separatorChar;
1046      String subPath = path.substring( fromDir.length()+1 );
1047      subPath = subPath.replace( File.separatorChar, '\\' );
1048
1049      String psionPath = toDir + subPath;
1050
1051      PsionFile psionFile = new PsionFile( psionPath, filei.getName(), sessionLayer );
1052
1053
1054      fileDownload.setCurrentFile( filei );
1055
1056      if ( filei.isDirectory() ) {
1057        String psionDir = psionFile.getParent() + psionFile.getName();
1058        if ( !psionDir.endsWith( "\\") )
1059        psionDir = psionDir + "\\";
1060        if ( !rfsv.pathTest( psionDir ) )
1061          rfsv.makeDir( psionDir );
1062      }
1063      else
1064        upload( fileDownload, filei, psionFile );
1065
1066      fileDownload.setNumFiles( filesList.size()-i-1 );
1067    }
1068
1069    fileDownload.cancel();
1070
1071    psionBrowser.refresh();
1072
1073    psionBrowser.deselect();
1074
1075    // then select the files we just uploaded,
1076    // so we can see where they are
1077    for ( int i = 0; i < files.length; i++ ) {
1078      File file = files[ i ];
1079      psionBrowser.selectFile( file.getName() );
1080    }
1081  }
1082
1083
1084  private void expandDir( FileDownload fileDownload, File file, Vector files, String parent, Hashtable parents, boolean uploadHiddenFiles ) {
1085    String[] children = file.list();
1086    for ( int i = 0; i < children.length; i++ ) {
1087      File child = new File( file, children[ i ] );
1088      if ( fileDownload.isCancelled() )
1089        return;
1090
1091      if ( !uploadHiddenFiles && JPLToolkit.isHiddenFile( child ) )
1092        continue;
1093              
1094      files.addElement( child );
1095
1096      parents.put( child, parent );
1097
1098      if ( child.isDirectory() )
1099        expandDir( fileDownload, child, files, parent, parents, uploadHiddenFiles );
1100
1101    }
1102    fileDownload.setNumFiles( files.size() );
1103  }
1104
1105  
1106  private void upload( FileDownload fileDownload, File from, PsionFile to ) throws IOException {
1107
1108    fileDownload.setProgressMax( (int)from.length() );
1109    fileDownload.setProgressValue( 0 );
1110
1111    fileDownload.checkOverwrite( to );
1112    if ( fileDownload.isCancelled() )
1113      return;
1114
1115    try {
1116      
1117      InputStream in   = new BufferedInputStream( new FileInputStream( from ) );
1118      OutputStream out = new PsionFileOutputStream( to );
1119  
1120      fileDownload.transfer( (int)(from.length()), in, out );
1121
1122      in.close();
1123      out.flush();
1124      out.close();
1125      
1126      if ( prefs.getPreference( "preserve_modtimes", false ) ) {
1127        to.setModified( from.lastModified() );
1128      }
1129    }
1130    catch( EpocException e ) {
1131      if ( e.getErrorCode() == ErrorCode.IN_USE ) {
1132        String txt = JPLToolkit.getResourceString( "@res:fileInUse", new Object[] { to.toString() } );
1133        if ( !tk.doQueryDialog( frame, "@res:fileInUseTitle", txt ) )
1134          fileDownload.cancel();
1135      }
1136      else 
1137        throw new EpocException( e.getErrorCode() );
1138    }
1139  }
1140
1141    private /*synchronized*/ void downloadSelected() throws IOException, InterruptedException {
1142     downloadSelected( downloadDirectory );
1143  }
1144
1145  private /*synchronized*/ void expandDir( FileDownload fileDownload, PsionFile dir, Vector files ) throws IOException, InterruptedException {
1146    PsionFile[] dirFiles = dir.list();
1147    for ( int i = 0; i < dirFiles.length; i++ ) {
1148      if ( fileDownload.isCancelled() )
1149        return;
1150
1151      PsionFile file = dirFiles[ i ];
1152      if ( file.isDirectory() )
1153        expandDir( fileDownload, file, files );
1154      else
1155        files.addElement( file );
1156    }
1157    fileDownload.setNumFiles( files.size() );
1158    //wait( 10 );
1159  }
1160
1161  public /*synchronized*/ void downloadSelected( String toDir ) throws IOException, InterruptedException {
1162
1163    FileIcon[] icons = psionBrowser.getSelectedIcons();
1164    if ( icons.length == 0 )
1165      return;
1166
1167    FileDownload fileDownload = new FileDownload( frame, "@res:downloading" );
1168    fileDownload.show();
1169
1170    fileDownload.setNumFiles( 0 );
1171    //wait( 10 );
1172
1173    String fromDir = sessionLayer.getRFSVChannel().sessionPath();
1174
1175
1176    Vector files = new Vector();
1177    for ( int i = 0; i < icons.length; i++ ) {
1178      if ( fileDownload.isCancelled() )
1179        return;
1180
1181      PsionFile file = icons[ i ].getFile();
1182      if ( file.isDirectory() )
1183        expandDir( fileDownload, file, files );
1184      else
1185        files.addElement( file );
1186    }
1187    fileDownload.setNumFiles( files.size() );
1188    //wait( 10 );
1189
1190    for ( int i = 0; i < files.size(); i++ ) {
1191      if ( fileDownload.isCancelled() )
1192        break;
1193
1194      PsionFile file = (PsionFile)files.elementAt( i );
1195      fileDownload.setCurrentFile( file );
1196
1197      //wait( 10 );
1198      download( fileDownload, file, toDir, fromDir );
1199      fileDownload.setNumFiles( files.size()-1-i );
1200    }
1201
1202    fileDownload.cancel();
1203    sessionLayer.getRFSVChannel().setSessionPath( fromDir );
1204  }
1205
1206  private /*synchronized*/ void download( FileDownload fileDownload, PsionFile file, String toDir, String fromDir ) throws IOException {
1207    String fullPath = file.getParent() + file.getName();
1208    //System.out.println( "download " + fullPath );
1209    //System.out.println( "from " + fromDir );
1210    String subPath  = fullPath.substring( fromDir.length() );
1211    //System.out.println( "subpath " + subPath );
1212    subPath = subPath.replace( '\\', File.separatorChar );
1213    //System.out.println( "subpath " + subPath );
1214    File to = new File( toDir + File.separatorChar + subPath );
1215
1216    download( fileDownload, file, to );
1217  }
1218
1219  
1220
1221  private /*synchronized*/ void download( FileDownload fileDownload, PsionFile from, File to ) throws IOException {
1222
1223    fileDownload.setProgressMax( from.getSize() );
1224    fileDownload.setProgressValue( 0 );
1225
1226    fileDownload.checkOverwrite( to );
1227    if ( fileDownload.isCancelled() )
1228      return;
1229
1230    // ensure the directory we are
1231    // about to write this to exists
1232    File parent = new File( to.getParent() );
1233    if ( !parent.exists() )
1234      parent.mkdirs();
1235
1236    try {
1237      PsionFileInputStream in = new PsionFileInputStream( from );
1238      OutputStream out        = new BufferedOutputStream( new FileOutputStream( to ) );
1239
1240      fileDownload.transfer( from.getSize(), in, out );
1241      
1242      in.close();
1243      out.flush();
1244      out.close();
1245      
1246      if ( prefs.getPreference( "preserve_modtimes", false ) ) {
1247        JPLToolkit.setFileLastModified( to, from.getModified() );
1248      }
1249    }
1250    catch( EpocException e ) {
1251      if ( e.getErrorCode() == ErrorCode.IN_USE ) {
1252        String txt = JPLToolkit.getResourceString( "@res:fileInUse", new Object[] { to.toString() } );
1253        if ( !tk.doQueryDialog( frame, "@res:fileInUseTitle", txt ) )
1254          fileDownload.cancel();
1255      }
1256      else 
1257        throw new EpocException( e.getErrorCode() );
1258    }
1259  }
1260
1261
1262  private static final String gpl =
1263
1264  "JavaPsionLink 2.0, a java implementation of the psion link protocol\n" +
1265    "Copyright (C) 2002, 2003  John S Montgomery (john.montgomery@lineone.net)\n" +
1266  "\n" +
1267    "This program is free software; you can redistribute it and/or modify\n" +
1268    "it under the terms of the GNU Lesser General Public License as published by\n" +
1269    "the Free Software Foundation; either version 2.1 of the License, or\n" +
1270    "(at your option) any later version.\n" +
1271   "\n" +
1272    "This program is distributed in the hope that it will be useful,\n" +
1273    "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" +
1274    "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n" +
1275    "GNU Lesser General Public License for more details.\n" +
1276   "\n" +
1277    "You should have received a copy of the GNU Lesser General Public License\n" +
1278    "along with this program; if not, write to the Free Software\n" +
1279    "Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA\n";
1280
1281  private static final String gifEncoder = "GIFEncoder 0.90 Copyright (C) 4/21/96 Adam Doppelt";
1282
1283
1284  private Dialog aboutDialog = null;
1285
1286  /** Display an about dialog box for JPL.
1287   **/
1288  public void aboutJPL() {
1289    String aboutJPL2 = JPLToolkit.getResourceString( "@res:aboutJPL2", new Object[] { "JPL 2.0" } );
1290    tk.showMessageDialog( frame, aboutJPL2, gpl + "\n \n" + gifEncoder );
1291  }
1292                
1293
1294  public void show() {
1295    frame.pack();
1296    
1297    Dimension size = frame.getSize();
1298    Dimension minSize = frame.getMinimumSize();
1299    
1300    // never smaller than minimum
1301    if ( widthPref < 0 )
1302      widthPref = size.width;
1303    else if ( widthPref < minSize.width )
1304      widthPref = minSize.width;
1305      
1306    if ( heightPref < 0 )
1307      heightPref = size.height;
1308    else if ( heightPref < minSize.height )
1309      heightPref = minSize.height;
1310    
1311    frame.setSize( widthPref, heightPref );
1312    
1313    frame.validate();
1314    
1315    frame.show();
1316    frame.requestFocus();
1317    if ( prefs.getPreference( "auto_connect", false ) ) {
1318      //System.out.println( "connect" );
1319      worker.schedule( new Runnable() {
1320        public void run() {
1321          try {
1322            // give it a little bit of time
1323             // before trying to connect
1324             Thread.sleep( 100 );
1325            tk.setEnabled( connect, false );
1326            connect();
1327          }
1328          catch( Exception e ) {
1329            handleException( e, true );
1330          }
1331        }
1332      } );
1333    }
1334  }
1335  
1336  
1337  public static void main( String[] args ) {
1338    //System.out.println( JPLToolkit.createTemporaryFile( "test", "txt" ) );
1339    try {
1340      PsionLink link = new PsionLink();
1341
1342      //link.pack();
1343      link.show();
1344
1345      
1346      
1347      //JPLToolkit tk = JPLToolkit.getToolkit();
1348      //tk.showMessageDialog( link.frame, "A quick test", "Does this work?" );
1349
1350      //System.out.println( tk.doQueryDialog( link.frame, "A quick test", "Does this work?" ) );
1351
1352    }
1353    catch( Throwable e ) {
1354      e.printStackTrace();
1355      Frame frame = null;
1356      if ( self != null && self.frame != null )
1357        frame = self.frame;
1358      else
1359        frame = new Frame();
1360      tk.showMessageDialog( frame, "error", e.toString() );
1361    }
1362
1363  }
1364
1365}