// // Copyright (c) 2009 Alexei Svitkine // // Permission is hereby granted, free of charge, to any person // obtaining a copy of this software and associated documentation // files (the "Software"), to deal in the Software without // restriction, including without limitation the rights to use, // copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following // conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. // package com.fizzysoft.sdu; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.beans.XMLDecoder; import java.beans.XMLEncoder; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.lang.ref.SoftReference; import java.util.LinkedList; import java.util.Properties; import javax.swing.Action; import javax.swing.Icon; import javax.swing.JMenu; import javax.swing.JMenuItem; import javax.swing.JFileChooser; import javax.swing.event.MenuEvent; import javax.swing.event.MenuListener; /** * RecentDocumentsManager is an abstract base class that provides * management of the "Open Recent" menu that is useful to desktop * applications. Subclasses are expected to provide implementations * of the writeRecentDocs(), readRecentDocs() and openFile() methods. * * @author Alexei Svitkine * @version November 24, 2009 */ public abstract class RecentDocumentsManager { private static final boolean isMac = (System.getProperty("mrj.version") != null); private int maxRecentDocuments; protected RecentDocumentsManager() { maxRecentDocuments = 10; } protected abstract void writeRecentDocs(byte[] data); protected abstract byte[] readRecentDocs(); protected abstract void openFile(File file, ActionEvent event); public JMenu createOpenRecentMenu() { return new OpenRecentMenu(); } public void addDocument(File file, Properties properties) { String fileLocation = null; try { fileLocation = file.getCanonicalPath(); } catch (IOException e) { } if (fileLocation != null) { LinkedList<RecentDocument> recentDocuments = loadListOfRecentDocuments(); LinkedList<RecentDocument> newRecentDocuments = new LinkedList<RecentDocument>(); for (RecentDocument rd: recentDocuments) { String rdFileLocation = rd.getLocation(); if (!fileLocation.equals(rdFileLocation)) { newRecentDocuments.add(rd); } } newRecentDocuments.addFirst(new RecentDocument(fileLocation, properties)); while (newRecentDocuments.size() > maxRecentDocuments) { newRecentDocuments.removeLast(); } saveListOfRecentDocuments(newRecentDocuments); } } public void clear() { LinkedList<RecentDocument> recentDocuments = new LinkedList<RecentDocument>(); saveListOfRecentDocuments(recentDocuments); } public int getMaxRecentDocuments() { return maxRecentDocuments; } public void setMaxRecentDocuments(int maxRecentDocuments) { this.maxRecentDocuments = maxRecentDocuments; } protected LinkedList<RecentDocument> loadListOfRecentDocuments() { LinkedList<RecentDocument> recentDocuments = new LinkedList<RecentDocument>(); byte[] data = readRecentDocs(); if (data != null && data.length > 0) { ByteArrayInputStream in = new ByteArrayInputStream(data); XMLDecoder decoder = new XMLDecoder(in); try { while (recentDocuments.size() < maxRecentDocuments) { RecentDocument rd = (RecentDocument) decoder.readObject(); if (rd.isValid()) { recentDocuments.add(rd); } } } catch (ArrayIndexOutOfBoundsException exception) { } finally { decoder.close(); } } return recentDocuments; } protected void saveListOfRecentDocuments(LinkedList<RecentDocument> recentDocuments) { ByteArrayOutputStream out = new ByteArrayOutputStream(); XMLEncoder encoder = new XMLEncoder(out); for (RecentDocument doc : recentDocuments) encoder.writeObject(doc); encoder.close(); writeRecentDocs(out.toByteArray()); } private static SoftReference<JFileChooser> iconFileChooserRef; protected Icon getFileIcon(File file) { JFileChooser fc = null; if (iconFileChooserRef != null) { fc = iconFileChooserRef.get(); } if (fc == null) { fc = new JFileChooser(); iconFileChooserRef = new SoftReference<JFileChooser>(fc); } return fc.getIcon(file); } public static class RecentDocument { private String location; // path private Properties properties; public RecentDocument() { } public RecentDocument(String location, Properties properties) { this.location = location; this.properties = properties; } public void setLocation(String location) { this.location = location; } public String getLocation() { return location; } public void setProperties(Properties properties) { this.properties = properties; } public Properties getProperties() { return properties; } public boolean isValid() { return new File(location).exists(); } } private class RecentDocumentMenuItem extends JMenuItem implements ActionListener { private RecentDocument rd; public RecentDocumentMenuItem(RecentDocument rd) { super(); this.rd = rd; File file = new File(rd.getLocation()); Icon fileIcon = getFileIcon(file); if (fileIcon != null) { setIcon(fileIcon); } setText(file.getName()); addActionListener(this); } public void actionPerformed(ActionEvent event) { openFile(new File(rd.getLocation()), event); } } private class OpenRecentMenu extends JMenu { private OpenRecentMenuUpdater updater; private JMenuItem clearItem; public OpenRecentMenu() { super("Open Recent"); clearItem = new JMenuItem("Clear Menu"); if (!isMac) { setMnemonic(KeyEvent.VK_T); clearItem.setMnemonic(KeyEvent.VK_M); } updater = new OpenRecentMenuUpdater(); populateWithRecentDocuments(); // the only strong reference to the updater is from this menu // once the menu is no longer reachable, the weak property listener will unregister addMenuListener(updater); } private void populateWithRecentDocuments() { populateWithRecentDocuments(loadListOfRecentDocuments()); } private void populateWithRecentDocuments(LinkedList<RecentDocument> recentDocuments) { removeAll(); clearItem.addActionListener(updater); if (recentDocuments.size() > 0) { for (RecentDocument rd: recentDocuments) { add(new RecentDocumentMenuItem(rd)); /* TODO: resolve conflicts between different items w/ same name */ } addSeparator(); clearItem.setEnabled(true); } else { clearItem.setEnabled(false); } add(clearItem); } private class OpenRecentMenuUpdater implements MenuListener, ActionListener { public void menuSelected(MenuEvent evt) { populateWithRecentDocuments(); } public void menuCanceled(MenuEvent evt) {} public void menuDeselected(MenuEvent evt) {} public void actionPerformed(ActionEvent evt) { // "Clear Menu" RecentDocumentsManager.this.clear(); populateWithRecentDocuments(); } } } }