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}