While working on some editors for this little
Tiamat
project I thought "
gee, it would be nice to add a recent file menu
like every other program on the planet has". It seemed like a
simple enough idea but turned out to be a tad more time-consuming than I first
estimated. Maybe I went about it totally wrong, you be the judge.
The logical route to go was to create an abstract class called
RecentFileMenu that extended
JMenu.
It was abstract because it contained a method used to notify the parent
class that a selection was made. Making this class abstract ensured the
method would be overridden. The design for the class ended up looking like
this:
There are a few private properties that store the list of recent file
entries, the maximum number of entries , the default text (used to hide
unset entries), and the path to where the contents of the menu are saved.
While reviewing this I had a couple of thoughts.. instead of using a
defaultText constant I could just as easily have used an empty string,
there'd be a minor performance gain from doing that. The recent file entries
are stored in a file called "[name].recent", I guess the Properties
class could have been used instead but that seems overly complicated. A more
elegant solution would be to have RecentFileMenu plug into the configuration
of the entire application. I wanted something quick and easy to implement
from a parent application so the fancy design patterns will have to
wait for next time.
So how does all this work? In the constructor the list of entries are
initialized to the default text, the name of the saved file is determined,
and the saved entries are loaded. If no saved entries are found then the
menu is visible but disabled.
/**
* Create a new instance of RecentFileMenu.
* @param name The name of this menu, not displayed but used to store the
list of recently used file names.
* @param count The number of recent files to store.
*/
public RecentFileMenu(String name,int count){
super();
this.setText("Recent");
this.setMnemonic('R');
this.itemCount=count;
//initialize default entries
this.recentEntries=new String[count];
for(int index=0;index<this.itemCount;index++){
this.recentEntries[index]=defaultText;
}
//figure out the name of the recent file
this.pathToSavedFile=System.getProperty("user.dir");
if((this.pathToSavedFile==null)||(this.pathToSavedFile.length()<=0)){
this.pathToSavedFile=new String(name+".recent"); //probably unreachable
} else if(this.pathToSavedFile.endsWith(File.separator)){
this.pathToSavedFile=this.pathToSavedFile+name+".recent";
} else{
this.pathToSavedFile=this.pathToSavedFile+File.separator+name+".recent";
}
//load the recent entries if they exist
File recentFile=new File(this.pathToSavedFile);
if(recentFile.exists()){
try{
LineNumberReader reader=new LineNumberReader(new FileReader(this.pathToSavedFile));
while(reader.ready()){
this.addEntry(reader.readLine(),false);
}
} catch(Exception x){
x.printStackTrace();
}
} else{ //disable
this.setEnabled(false);
}
}
The public addEntry method just calls the private addEntry, passing true in
the updateFile argument. The private add entry method moves all the menu
items down one position. If filePath already exists in the list of entries
it gets moved to the top, it shouldn't be possible to have the same entry
appear twice. If updateFile is true it saves the entries in the
.recent file.
/**
* Adds a new entry to the menu. Moves everything "down" one row.
* @param filePath The new path to add.
* @param updateFile Whether to update the saved file, only false when called from constructor.
*/
private void addEntry(String filePath,boolean updateFile){
//check if this is disabled
if(!this.isEnabled()){
this.setEnabled(true);
}
//clear the existing items
this.removeAll();
//move everything down one slot
int count=this.itemCount-1;
for(int index=count;index>0;index--){
//check for duplicate entry
if(!this.recentEntries[index-1].equalsIgnoreCase(filePath)){
this.recentEntries[index]=new String(this.recentEntries[index-1]);
}
}
//add the new item, check if it's not alredy the first item
if(!this.recentEntries[0].equalsIgnoreCase(filePath)){
this.recentEntries[0]=new String(filePath);
}
//add items back to the menu
for(int index=0;index<this.itemCount;index++){
JMenuItem menuItem=new JMenuItem();
menuItem.setText(this.recentEntries[index]);
if(this.recentEntries[index].equals(defaultText)){
menuItem.setVisible(false);
} else{
menuItem.setVisible(true);
menuItem.setToolTipText(this.recentEntries[index]);
menuItem.setActionCommand(this.recentEntries[index]);
menuItem.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent actionEvent){
onSelectFile(actionEvent.getActionCommand());
}
});
}
this.add(menuItem);
}
//update the file
if(updateFile){
try{
FileWriter writer=new FileWriter(new File(this.pathToSavedFile));
int topIndex=this.itemCount-1;
for(int index=topIndex;index>=0;index--){
if(!this.recentEntries[index].equals(defaultText)){
writer.write(this.recentEntries[index]);
writer.write("\n");
}
}
writer.flush();
writer.close();
} catch(Exception x){
x.printStackTrace();
}
}
}
Creating RecentFileMenu was a minor inconvenience, luckily using it is really
easy. Here is all the coding that's necessary:
//class properties
private RecentFileMenu recentMenu;
[....]
//code to setup the file menu
JMenu menuFile=new JMenu();
menuFile.setText("File");
menuFile.setMnemonic('F');
this.recentMenu=new RecentFileMenu("RecentFileMenu_Test",10){
public void onSelectFile(String filePath){
onRecentFile(filePath);
}
};
menuFile.add(recentMenu);
[....]
//code to handle onRecentFile
public void onRecentFile(String filePath){
call-your-open-file-routine(filePath);
}
[....]
//code to add a recent file to the menu
public void addRecentEntry(){
recentMenu.addEntry(browsePanel.getFilePath());
}
Everything on this site is free. I'll never use pop-ups or randomly
generated ads to support it. If you've found something here to be
especially helpful or entertaining please consider making a small
donation. This can be done through a secure PayPal transaction.
Thanks for visiting my little web page!
Legal Notes
Unless otherwise noted, all content is copyright (c) 2005-2006 Hugues Johnson and may not be redistributed in any form without express permission.
Java is registered trademark of Sun Microsystems
The logo for this page uses the Aardvark Cafe font (c) 2000 Harold
Lohner