Java Tip 119: Don't know much about file history?

Enhance your file processing application by adding a file history mechanism

Every programmer is familiar with a text editor either as part of an IDE or as a standalone tool. Often you must reedit source code you have recently closed. Rather than use the file chooser by first selecting the Open file menu (a sometimes time-consuming process), you can just click on the file menu and get a list of your last edited files. This list is typically enumerated and sorted with the most recently processed filename at the top.

My "Java Tip 100: Add a History Mechanism to JFileChooser" enhanced the JFileChooser component itself by providing a directory history as well as a file previewer in the form of a JFileChooser accessory. Now we will enhance the File menu in the JFrame's menubar by providing a history mechanism for filenames.

In this tip, we'll develop three classes to enhance a sample application with such a file history mechanism. The DemoFileHistory class is a short GUI-based demo application. (Note that this demo uses a JButton feature that requires JDK 1.3 or later to compile and run.) The FileHistory class encapsulates all the code you need to create the file history mechanism, maintain the file list, and save and restore the list after the application has been closed or reopened, respectively. The third class is the IFileHistory Java interface, which is defined inside the FileHistory class. IFileHistory encapsulates the five methods that your GUI frame must implement in order to satisfy the mechanism's needs.

Develop the demo application

Let's start by developing a small demo application. This application extends Swing's JFrame. It has a menubar and toolbar at the top, a label canvas in the center, and a status bar at the bottom. Figure 1 illustrates this application.

Figure 1. The demo application with File and Options menus. Click on thumbnail to view full-size image.

The menubar contains two menus: File and Options. The File menu has a New item that clears the main screen area. The New item's activation is reported in the status line. We will enhance the File menu's Open file item with our file history mechanism. We have corresponding toolbar icons for New and Open. Menu items and toolbar actions are connected to Swing action objects. If you look at the source code, so far this is classic Swing application development.

The user maintains the filename history list using the Options menu. For our demo, the Options menu has only one item called File History, which you activate to get your current file history list. (See Figure 2 below.) By clicking a list item (i.e. a filename), you can select the item to open or delete it from the list with the Delete button shown in Figure 2. Using the Shift key, you can select a contiguous item range; using the CTRL key, you can select a noncontiguous item range -- as done in other applications.

Figure 2. The File History maintenance window. Click on thumbnail to view full-size image.

If you open a file with the Open menu or the Open icon you get a JFileChooser that asks you to select your file. After selecting the filename (or an abbreviated form), the filename is displayed and mirrored in the center of the application frame. The status line reflects the pathname, simulating a file processing activity. If you open three different files and you click on the File menu, you get a list as shown in Figure 3.

Figure 3. Demo application with file history list. Click on thumbnail to view full-size image.

Each filename path is abbreviated using a special algorithm that shows the first and last parts of a name. Currently, the abbreviation reflects the typical MS Windows user experience -- if you move the mouse pointer over an item, then you get a tooltip that shows you the entire pathname. The tooltip is aligned so that you can still see the item number. Note that due to a Swing restriction the tooltip can only be as wide as your application frame.

Demo hot spots

Now I'd like to mention a few "hot spots" in our demo application. The term hot spot is also used with frameworks, marking the point where you can extend it. In our context, these points are the links to which we will connect our FileHistory class.

First, you have to initialize FileHistory:

   fileHistory = new FileHistory(this); // init FileHistory with our frame as the only parameter.
   fileHistory.initFileMenuHistory(); // Start file history initialization.

Second, you write a small exit() method called in your windowClosing() method and your exit item handler's actionPerformed() method:

   this.addWindowListener(new WindowAdapter() {
      public void windowClosing(WindowEvent e) {
         exit();
      }
   });
   JMenuItem exitMenuItem = new JMenuItem("Exit");
   exitMenuItem.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
         exit();
      }
   });

The exit() method looks as follows:

/**
 * Close the frame, save the file history entries, and exit the demo.
 */
 private void exit() {
    setVisible(false);
    dispose();
    fileHistory.saveHistoryEntries(); // Save pathname entries for next session.
    System.exit(0);
}

The call to the fileHistory.saveHistoryEntries() method in the exit() method is the most interesting part. That call saves the filename list in a special configuration file for the next session to use.

Third, you have to implement the FileHistory.IFileHistory interface in your frame class. The interface consists of five easy-to-implement methods:

/** * Get the application name to identify the configuration file in the * the USER_HOME directory. This name should be unique in this directory. * * @return the application name. */ public String getApplicationName();

/** * Get a handle to the frame's File menu. * * @return the frame's File menu. */ public JMenu getFileMenu();

/** * Return the size of the main application frame. * It is used to center the File History maintenance window. * * @return the main GUI frame's size. */ public Dimension getSize();

/** * Return the main application frame. * It is used to center the file history maintenance window. * * @return the main GUI frame instance. */ public JFrame getFrame();

/** * Perform the load file activity. * * @param path is the pathname of the loaded file. */ public void loadFile(String pathname); }

Then you connect your file chooser's output to the FileHistory object in the internal OpenAction class:

private final class OpenAction extends AbstractAction {

OpenAction() { super("Open..."); }

public void actionPerformed(ActionEvent e) { JFileChooser fileChooser = new JFileChooser(); fileChooser.setCurrentDirectory(new File(System.getProperty("user.dir"))); int result = fileChooser.showOpenDialog(DemoFileHistory.this); if (result == JFileChooser.APPROVE_OPTION) { File f = fileChooser.getSelectedFile(); if (f == null) { return; } if (f.getName().equals("")) { return; } String path = f.getAbsolutePath(); status.setText("Open " + path); fileHistory.insertPathname(path); // Hook into FileHistory class. loadFile(path); // Do the file processing activity in the application. } } }

After the user chooses a filename, you provide the FileHistory object with the complete pathname by calling fileHistory.insertPathname(path). Finally, you have to provide an inner class, such as HistoryManagementAction, to handle the File History menu item selection in the Options menu:

private final class HistoryManagementAction extends AbstractAction {

HistoryManagementAction() { super("File History..."); }

public void actionPerformed(ActionEvent e) { fileHistory.processList(); // Hook into FileHistory class. } }

That's all! Your application now connects to the file history mechanism and is ready for use. Next, we'll look at the

FileHistory

class. Note that you need JDK 1.3 to compile and run the provided demo class

DemoFileHistory.java

, which shows you how to provide all the necessary activities mentioned above.

The FileHistory class

The FileHistory class uses the hot spots you provided with the IFileHistory interface and implements the necessary actions to manage all the file history details. You can find the source code in Resources.

The FileHistory constructor creates the filename used for writing and reading the user's filename list. You can set the list size with a system property called itemnames.max. Its default is nine, meaning the user can see a maximum of nine items in the file menu list. If you add a tenth item, the oldest item in the list is deleted.

We use the Java serialization mechanism to write the lists to a file and read the lists from that file during application startup. We do this in FileHistory's initFileMenuHistory() and saveHistoryEntries() methods, respectively. Because the items in the file menu list may be abbreviated, we have to handle two ArrayLists: one for item names and another for complete pathnames. These pathnames are necessary for the tooltips and for the loadFile() method parameter.

To display the tooltip correctly, we subclass JMenuItem and override its getToolTipLocation() method:

private final class MenuItemWithFixedTooltip extends JMenuItem {

public MenuItemWithFixedTooltip(String text) { super(text); }

public Point getToolTipLocation(MouseEvent e) { Graphics g = getGraphics(); FontMetrics metrics = g.getFontMetrics(g.getFont()); String prefix = itemnameHistory.size() <= 9 ? "8: " : "88: "; int prefixWidth = metrics.stringWidth(prefix); int x = JButton.TRAILING + JButton.LEADING -1 + prefixWidth; return new Point(x, 0); } }

This code that aligns our tooltip text to the right of the item number in the opened File menu item list is somewhat tricky (see Figure 3). We assume one or two digits for item numbers, represented by the String "8" or "88".

The main work to build the item list is done in the insertPathname() method (see Resources). This recursive procedure inserts the item name in the right place, removes items that extend the maximum list size, and handles the placement of a line separator. The FileHistory class's last method is related to the History menu and presents a filename list together with Delete and Close buttons. With this list, the user can configure the current item list in the file menu.

Looking back

In this tip, we've created a reusable file history mechanism that you can connect to any Swing GUI application in just a few steps. I have tested the utility with JDK 1.2 and JDK 1.3 on Windows NT and Windows 2000. (The code has also been tested on JDK 1.3 under Linux.) It is a powerful tool and gives a file processing application a professional touch.

Klaus Berg has studied electrical engineering and informatics at the University of Karlsruhe in Germany and works on system monitoring and performance engineering. Currently he is architect and implementer in a team at Siemens that is building a Java tool to test and monitor distributed systems. His research interests comprise Java GUI development and professional print and export support for Java applications.

Learn more about this topic