001    /*
002     * Copyright (c) 2005 Einar Pehrson <einar@pehrson.nu>.
003     *
004     * This file is part of
005     * CleanSheets - a spreadsheet application for the Java platform.
006     *
007     * CleanSheets is free software; you can redistribute it and/or modify
008     * it under the terms of the GNU General Public License as published by
009     * the Free Software Foundation; either version 2 of the License, or
010     * (at your option) any later version.
011     *
012     * CleanSheets is distributed in the hope that it will be useful,
013     * but WITHOUT ANY WARRANTY; without even the implied warranty of
014     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
015     * GNU General Public License for more details.
016     *
017     * You should have received a copy of the GNU General Public License
018     * along with CleanSheets; if not, write to the Free Software
019     * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
020     */
021    package csheets.ui;
022    
023    import java.awt.Component;
024    import java.awt.event.ActionEvent;
025    import java.awt.event.KeyEvent;
026    import java.io.File;
027    import java.util.Properties;
028    
029    import javax.swing.AbstractAction;
030    import javax.swing.ImageIcon;
031    import javax.swing.JMenu;
032    import javax.swing.JMenuItem;
033    import javax.swing.JSeparator;
034    
035    import csheets.CleanSheets;
036    import csheets.SpreadsheetAppEvent;
037    import csheets.SpreadsheetAppListener;
038    import csheets.ui.ctrl.OpenAction;
039    import csheets.ui.ctrl.UIController;
040    
041    /**
042     * A menu for displaying recently opened files, and allowing the user to
043     * reopen them.
044     * @author Einar Pehrson
045     */
046    @SuppressWarnings("serial")
047    public class ReopenMenu extends JMenu implements SpreadsheetAppListener {
048    
049            /** The CleanSheets application */
050            private CleanSheets app;
051    
052            /** The maximum number of items in the menu */
053            private int maximumItems = 10;
054    
055            /** The application properties */
056            private Properties props;
057    
058            /** The number of menu items on the menu not denoting files */
059            private int nonReopenItems;
060    
061            /** The user interface controller */
062            private UIController uiController;
063    
064            /**
065             * Creates a reopen menu, and creates items using the given properties (if available).
066             * @param app the CleanSheets application
067             * @param uiController the user interface controller
068             */
069            public ReopenMenu(CleanSheets app, UIController uiController) {
070                    super("Reopen");
071    
072                    // Stores members
073                    this.app = app;
074                    this.uiController = uiController;
075                    this.props = app.getUserProperties();
076    
077                    // Configures menu
078                    app.addSpreadsheetAppListener(this);
079                    setMnemonic(KeyEvent.VK_R);
080                    setIcon(new ImageIcon(CleanSheets.class.getResource("res/img/reopen.gif")));
081    
082                    if (props != null) {
083                            // Loads recent files from properties and adds menu items
084                            String filename;
085                            for (int i = 0; (filename = props.getProperty("recentfile" + i)) != null; i++) {
086                                    File file = new File(filename);
087                                    if (file.exists()) addReopenItem(file, false);
088                            }
089                    }
090    
091                    // Adds removal items
092                    addSeparator();
093                    add(new RemoveObsoleteAction());
094                    add(new RemoveAllAction());
095                    nonReopenItems = 3;
096            }
097    
098            /**
099             * Adds an item for this file to the top of the reopen menu.
100             * @param file the filename of the file
101             * @return the item that was added
102             */
103            public JMenuItem addReopenItem(File file) {
104                    return addReopenItem(file, true);
105            }
106    
107            /**
108             * Adds an item for this file to the top of the reopen menu.
109             * @param file the filename of the file
110             * @param updateProperties whether to update properties after adding the iten
111             * @return the item that was added
112             */
113            private JMenuItem addReopenItem(File file, boolean updateProperties) {
114                    // Removes any existing identical items
115                    Component[] items = getMenuComponents();
116                    for (int i = 0; i < items.length; i++) {
117                            // Breaks at separator
118                            if (items[i] instanceof JSeparator) break;
119    
120                            // Removes item, if identical
121                            JMenuItem item = (JMenuItem)items[i];
122                            if (file.getAbsolutePath().equals(item.getText()))
123                                    remove(item);
124                    }
125    
126                    // Adds the item to the menu and trims the menu to the appropriate size
127                    JMenuItem item = insert(new ReopenAction(app, uiController, file), 0);
128                    while (getMenuComponentCount() - nonReopenItems > maximumItems) remove(maximumItems);
129    
130                    // Updates properties
131                    if (updateProperties) 
132                            updateProperties();
133    
134                    return item;
135            }
136    
137            /**
138             * Removes all obsolete file items from the reopen menu, 
139             * i.e. the items referring to files that don't exist.
140             */
141            public void removeObsolete() {
142                    Component[] items = getMenuComponents();
143                    for (int i = 0; i < items.length; i++) {
144                            // Breaks at separator
145                            if (items[i] instanceof JSeparator) break;
146    
147                            // Removes item, if obsolete
148                            JMenuItem item = (JMenuItem)items[i];
149                            if (!new File(item.getText()).exists())
150                                    remove(item);
151                    }
152    
153                    // Updates properties
154                    updateProperties();
155            }
156    
157            /**
158             * Removes all file items from the reopen menu, 
159             */
160            public void removeAll() {
161                    Component[] items = getMenuComponents();
162                    for (int i = 0; i < items.length; i++) {
163                            // Breaks at separator
164                            if (items[i] instanceof JSeparator) break;
165    
166                            // Removes item
167                            remove(items[i]);
168                    }
169    
170                    // Updates properties
171                    updateProperties();
172            }
173    
174            /**
175             * Sets the maximum number of reopen items in the menu.
176             * @param items the number of reopen items in the menu
177             */
178            public void setMaximumItems(int items) {
179                    this.maximumItems = items;
180            }
181    
182            /**
183             * Updates the recent files in the application properties.
184             */
185            private void updateProperties() {
186                    if (props != null) {
187                            // Stores the current recent files
188                            int i = 0;
189                            for (int n = getMenuComponentCount() - nonReopenItems; i < n; i++)
190                                    props.setProperty("recentfile" + (n - i - 1),
191                                            ((JMenuItem)getMenuComponent(i)).getText());
192    
193                            for (; (props.getProperty("recentfile" + i)) != null; i++)
194                                    props.remove("recentfile" + i);
195                    }
196            }
197    
198            public void workbookCreated(SpreadsheetAppEvent event) {}
199    
200            public void workbookLoaded(SpreadsheetAppEvent event) {
201                    addReopenItem(event.getFile(), true);
202            }
203    
204            public void workbookUnloaded(SpreadsheetAppEvent event) {}
205    
206            public void workbookSaved(SpreadsheetAppEvent event) {
207                    addReopenItem(event.getFile(), true);
208            }
209    
210            /**
211             * An action for reopening a spreadsheet.
212             * @author Einar Pehrson
213             */
214            @SuppressWarnings("serial")
215            protected static class ReopenAction extends OpenAction {
216    
217                    /** The file to open */
218                    private File file;
219    
220                    /**
221                     * Creates a new reopen action.
222                     * @param app the CleanSheets application
223                     * @param uiController the user interface controller
224                     * @param file the file to open
225                     */
226                    public ReopenAction(CleanSheets app, UIController uiController,
227                                    File file) {
228                            super(app, uiController, null);
229                            this.file = file;
230                            setEnabled(true);
231                            putValue(NAME, file.getAbsolutePath());
232                            putValue(ACTION_COMMAND_KEY, file.getAbsolutePath());
233                    }
234    
235                    protected void defineProperties() {
236                            putValue(SHORT_DESCRIPTION, null);
237                            putValue(SMALL_ICON, null);
238                    }
239    
240                    public File getFile() {
241                            return file;
242                    }
243            }
244    
245            /**
246             * An action for removing the obsolete items from the menu.
247             */
248            @SuppressWarnings("serial")
249            protected class RemoveObsoleteAction extends AbstractAction {
250    
251                    /**
252                     * Creates a new remove obsolete action.
253                     */
254                    public RemoveObsoleteAction() {
255                            // Configures action
256                            String name = "Remove obsolete";
257                            putValue(NAME, name);
258                            putValue(SHORT_DESCRIPTION, name);
259                            putValue(ACTION_COMMAND_KEY, name);
260                            putValue(MNEMONIC_KEY, KeyEvent.VK_O);
261                    }
262    
263                    public void actionPerformed(ActionEvent e) {
264                            removeObsolete();
265                    }
266            }
267    
268            /**
269             * An action for removing all items from the menu.
270             */
271            @SuppressWarnings("serial")
272            protected class RemoveAllAction extends AbstractAction {
273    
274                    /**
275                     * Creates a new remove all action.
276                     */
277                    public RemoveAllAction() {
278                            // Configures action
279                            String name = "Remove all";
280                            putValue(NAME, name);
281                            putValue(SHORT_DESCRIPTION, name);
282                            putValue(ACTION_COMMAND_KEY, name);
283                            putValue(MNEMONIC_KEY, KeyEvent.VK_R);
284                    }
285    
286                    public void actionPerformed(ActionEvent e) {
287                            removeAll();
288                    }
289            }
290    }