We now have the tools needed to put a graphical user interface onto the FileViewer application we've been developing. The back end doesn't need to change at all. It's still based on the same filter streams we've used for the last several chapters. However, instead of reading filenames from the command line, we can get them from a file chooser. Instead of dumping the files on System.out, we can display them in a text area. And instead of relying on the user remembering a lot of confusing command-line switches, we can provide simple radio buttons for the user to choose from. This has the added advantage of making it easy to repeatedly interpret the same file according to different filters.
Figure 18-6 shows the finished application. This gives you some idea of what the code is aiming at. Initially, I started with a pencil-and-paper sketch, but I'll spare you my inartistic renderings. The single JFrame window is organized with a border layout. The west panel contains various controls for determining how the data is interpreted. The east panel contains the JFileChooser used to select the file. Notice that the Approve button has been customized to say "View File" rather than "Open". Ideally, I'd like to make the Cancel button say "Quit" instead, but the JFileChooser class doesn't allow you to do that without using resource bundles, a subject I would prefer to leave for another book. The south panel contains a scroll pane. Inside the scroll pane is a streamed text area.
Figure 18-6. The FileViewer
Let's begin the exegesis of the code where I began writing it, with the user interface. The main driver class is FileViewer, shown in Example 18-14. This class extends JFrame. Its constructor doesn't do a lot. Most of the work is relegated to the init( ) method, which sets up the user interface composed of the three parts previously described and centers the whole frame on the primary display.
Example 18-14. FileViewer
import javax.swing.*; import java.io.*; import java.awt.*; import java.awt.event.*; import com.elharo.io.ui.JStreamedTextArea; public class FileViewer extends JFrame implements ActionListener { private JFileChooser chooser = new JFileChooser( ); private JStreamedTextArea theView = new JStreamedTextArea( ); private ModePanel mp = new ModePanel( ); public FileViewer( ) { super("FileViewer"); } public void init( ) { chooser.setApproveButtonText("View File"); chooser.setApproveButtonMnemonic('V'); chooser.addActionListener(this); this.getContentPane( ).add(BorderLayout.CENTER, chooser); JScrollPane sp = new JScrollPane(theView); sp.setPreferredSize(new Dimension(640, 400)); this.getContentPane( ).add(BorderLayout.SOUTH, sp); this.getContentPane( ).add(BorderLayout.WEST, mp); this.pack( ); // Center on display. Dimension display = getToolkit().getScreenSize( ); Dimension bounds = this.getSize( ); int x = (display.width - bounds.width)/2; int y = (display.height - bounds.height)/2; if (x < 0) x = 10; if (y < 0) y = 15; this.setLocation(x, y); } public void actionPerformed(ActionEvent evt) { if (evt.getActionCommand( ).equals(JFileChooser.APPROVE_SELECTION)) { File f = chooser.getSelectedFile( ); if (f != null) { theView.reset( ); try { InputStream in = new FileInputStream(f); in = new ProgressMonitorInputStream(this, "Reading...", in); OutputStream out = theView.getOutputStream( ); FileDumper5.dump(in, out, mp.getMode(), mp.isBigEndian( ), mp.isDeflated(), mp.isGZipped(), mp.getPassword( )); } catch (IOException ex) { JOptionPane.showMessageDialog(this, ex.getMessage( ), "I/O Error", JOptionPane.ERROR_MESSAGE); } } } else if (evt.getActionCommand( ).equals(JFileChooser.CANCEL_SELECTION)) { this.setVisible(false); this.dispose( ); } } public static void main(String[] args) { FileViewer viewer = new FileViewer( ); viewer.init( ); // This is a single window application viewer.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); viewer.setVisible(true); } } |
FileViewer implements the ActionListener interface. However, the action events that its actionPerformed( ) method responds to are fired by the file chooser, indicating that the user pressed the View File button.
When the user presses the View File button, the mode panel is read to determine exactly how the file is to be interpreted. These parameters and the selected file are fed to the static FileDumper5.dumpFile( ) method from Chapter 12.
The next new class in this application is the ModePanel, shown in Example 18-15. This class provides a simple user interface to allow the user to specify the format the file is in, whether and how it's compressed, and the password, if any. This part of the GUI is completely contained inside this class. Other methods that need access to this information can query the ModePanel for it through any of several public getter methods. They do not need to concern themselves with the internal details of the ModePanel GUI.
Example 18-15. ModePanel
import java.awt.*; import javax.swing.*; public class ModePanel extends JPanel { private JCheckBox bigEndian = new JCheckBox("Big Endian", true); private JCheckBox deflated = new JCheckBox("Deflated", false); private JCheckBox gzipped = new JCheckBox("GZipped", false); private ButtonGroup dataTypes = new ButtonGroup( ); private JRadioButton asciiRadio = new JRadioButton("ASCII"); private JRadioButton decimalRadio = new JRadioButton("Decimal"); private JRadioButton hexRadio = new JRadioButton("Hexadecimal"); private JRadioButton shortRadio = new JRadioButton("Short"); private JRadioButton intRadio = new JRadioButton("Int"); private JRadioButton longRadio = new JRadioButton("Long"); private JRadioButton floatRadio = new JRadioButton("Float"); private JRadioButton doubleRadio = new JRadioButton("Double"); private JTextField password = new JPasswordField( ); public ModePanel( ) { this.setLayout(new GridLayout(13, 1)); this.add(bigEndian); this.add(deflated); this.add(gzipped); this.add(asciiRadio); asciiRadio.setSelected(true); this.add(decimalRadio); this.add(hexRadio); this.add(shortRadio); this.add(intRadio); this.add(longRadio); this.add(floatRadio); this.add(doubleRadio); dataTypes.add(asciiRadio); dataTypes.add(decimalRadio); dataTypes.add(hexRadio); dataTypes.add(shortRadio); dataTypes.add(intRadio); dataTypes.add(longRadio); dataTypes.add(floatRadio); dataTypes.add(doubleRadio); this.add(password); } public boolean isBigEndian( ) { return bigEndian.isSelected( ); } public boolean isDeflated( ) { return deflated.isSelected( ); } public boolean isGZipped( ) { return gzipped.isSelected( ); } public int getMode( ) { if (asciiRadio.isSelected( )) return FileDumper6.ASC; else if (decimalRadio.isSelected( )) return FileDumper6.DEC; else if (hexRadio.isSelected( )) return FileDumper6.HEX; else if (shortRadio.isSelected( )) return FileDumper6.SHORT; else if (intRadio.isSelected( )) return FileDumper6.INT; else if (longRadio.isSelected( )) return FileDumper6.LONG; else if (floatRadio.isSelected( )) return FileDumper6.FLOAT; else if (doubleRadio.isSelected( )) return FileDumper6.DOUBLE; else return FileDumper6.ASC; } public String getPassword( ) { return password.getText( ); } } |
And there you have it: a graphical file viewer application. The I/O code hasn't changed at all, but the resulting application is much easier to use. One final piece remains before we can say the file viewer is complete. Chapter 20 adds support for many additional text encodings besides the ASCII used here.
Basic I/O
Introducing I/O
Output Streams
Input Streams
Data Sources
File Streams
Network Streams
Filter Streams
Filter Streams
Print Streams
Data Streams
Streams in Memory
Compressing Streams
JAR Archives
Cryptographic Streams
Object Serialization
New I/O
Buffers
Channels
Nonblocking I/O
The File System
Working with Files
File Dialogs and Choosers
Text
Character Sets and Unicode
Readers and Writers
Formatted I/O with java.text
Devices
The Java Communications API
USB
The J2ME Generic Connection Framework
Bluetooth
Character Sets