/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ /* Part of the Processing project - http://processing.org Copyright (c) 2011-12 Ben Fry and Casey Reas This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package processing.app.ui; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; import javax.swing.JMenu; import javax.swing.JMenuItem; import processing.app.Base; import processing.app.Language; import processing.app.Library; import processing.app.Messages; import processing.app.Mode; import processing.app.Preferences; import processing.core.PApplet; // TODO this isn't pretty... probably better to do an internal instance fancy thing // dealing with renaming // before sketch save/rename, remove it from the recent list // after sketch save/rename add it to the list // (this is the more straightforward model, otherwise has lots of weird edge cases) public class Recent { static final String FILENAME = "recent.txt"; static final String VERSION = "2"; static Base base; static File file; static List<Record> records; /** actual menu used in the primary menu bar */ static JMenu mainMenu; /** copy of the menu to use in the toolbar */ static JMenu toolbarMenu; static public void init(Base b) { base = b; file = Base.getSettingsFile(FILENAME); mainMenu = new JMenu(Language.text("menu.file.recent")); toolbarMenu = new JMenu(Language.text("menu.file.open")); try { load(); } catch (IOException e) { e.printStackTrace(); } } static protected void load() throws IOException { records = new ArrayList<Record>(); if (file.exists()) { BufferedReader reader = PApplet.createReader(file); String version = reader.readLine(); if (version != null && version.equals(VERSION)) { String line = null; while ((line = reader.readLine()) != null) { // String[] pieces = PApplet.split(line, '\t'); // Record record = new Record(pieces[0], new EditorState(pieces[1])); // Record record = new Record(pieces[0]); //, new EditorState(pieces[1])); // records.add(record); if (new File(line).exists()) { // don't add ghost entries records.add(new Record(line)); } else { Messages.log("ghost file: " + line); } } } reader.close(); } updateMenu(mainMenu); updateMenu(toolbarMenu); } static protected void save() { file.setWritable(true, false); PrintWriter writer = PApplet.createWriter(file); writer.println(VERSION); for (Record record : records) { // System.out.println(record.getPath() + "\t" + record.getState()); // writer.println(record.path + "\t" + record.getState()); writer.println(record.path); // + "\t" + record.getState()); } writer.flush(); writer.close(); updateMenu(mainMenu); updateMenu(toolbarMenu); } static public JMenu getMenu() { return mainMenu; } static public JMenu getToolbarMenu() { return toolbarMenu; } static private void updateMenu(JMenu menu) { menu.removeAll(); String sketchbookPath = Base.getSketchbookFolder().getAbsolutePath(); for (Record rec : records) { updateMenuRecord(menu, rec, sketchbookPath); } } static private void updateMenuRecord(JMenu menu, final Record rec, String sketchbookPath) { try { String recPath = new File(rec.getPath()).getParent(); String purtyPath = null; if (recPath.startsWith(sketchbookPath)) { purtyPath = "sketchbook \u2192 " + recPath.substring(sketchbookPath.length() + 1); } else { List<Mode> modes = base.getModeList(); for (Mode mode : modes) { File examplesFolder = mode.getExamplesFolder(); String examplesPath = examplesFolder.getAbsolutePath(); if (recPath.startsWith(examplesPath)) { String modePrefix = mode.getTitle() + " "; if (mode.getTitle().equals("Standard")) { modePrefix = ""; // "Standard examples" is dorky } purtyPath = modePrefix + "examples \u2192 " + recPath.substring(examplesPath.length() + 1); break; } if (mode.coreLibraries != null) { for (Library lib : mode.coreLibraries) { examplesFolder = lib.getExamplesFolder(); examplesPath = examplesFolder.getAbsolutePath(); if (recPath.startsWith(examplesPath)) { purtyPath = lib.getName() + " examples \u2192 " + recPath.substring(examplesPath.length() + 1); break; } } } if (mode.contribLibraries != null) { for (Library lib : mode.contribLibraries) { examplesFolder = lib.getExamplesFolder(); examplesPath = examplesFolder.getAbsolutePath(); if (recPath.startsWith(examplesPath)) { purtyPath = lib.getName() + " examples \u2192 " + recPath.substring(examplesPath.length() + 1); break; } } } } } if (purtyPath == null) { String homePath = System.getProperty("user.home"); if (recPath.startsWith(homePath)) { // Not localized, but this is gravy. It'll work on OS X & EN Windows String desktopPath = homePath + File.separator + "Desktop"; if (recPath.startsWith(desktopPath)) { purtyPath = "Desktop \u2192 " + recPath.substring(desktopPath.length() + 1); } else { //purtyPath = "\u2302 \u2192 " + recPath.substring(homePath.length() + 1); //purtyPath = "Home \u2192 " + recPath.substring(homePath.length() + 1); String userName = new File(homePath).getName(); //purtyPath = "\u2302 " + userName + " \u2192 " + recPath.substring(homePath.length() + 1); purtyPath = userName + " \u2192 " + recPath.substring(homePath.length() + 1); } } else { purtyPath = recPath; } } // JMenuItem item = new JMenuItem(rec.getName() + " | " + purtyPath); JMenuItem item = new JMenuItem(purtyPath); item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { // Base will call handle() (below) which will cause this entry to // be removed from the list and re-added to the end. If already // opened, Base will bring the window forward, and also call handle() // so that it's re-queued to the newest slot in the Recent menu. base.handleOpen(rec.path); // if (rec.sketch == null) { // // this will later call 'add' to put it back on the stack // base.handleOpen(rec.path); //, rec.state); //// int index = findRecord(rec); //// if (index != -1) { //// records.remove(index); // remove from the list //// save(); // write the recent file with the latest //// } // } else { //// System.out.println("sketch not null in handleOpen: " + record.getPath()); // } } }); //menu.add(item); menu.insert(item, 0); } catch (Exception e) { // Strange things can happen... report them for the geeky and move on: // https://github.com/processing/processing/issues/2463 e.printStackTrace(); } } synchronized static public void remove(Editor editor) { int index = findRecord(editor.getSketch().getMainFilePath()); if (index != -1) { records.remove(index); } } // synchronized void handleRename(String oldPath, String newPath) { // int index = findRecord(oldPath); // if (index != -1) { // Record rec = records.get(index); // rec.path = newPath; // save(); // } else { // Base.log(this, "Could not find " + oldPath + // " in list of recent sketches to replace with " + newPath); // } // } // /** Opened from the recent sketches menu. */ // synchronized void handleOpen(Record record) { // if (record.sketch == null) { //// Editor editor = base.handleOpen(record.path, record.state); // base.handleOpen(record.path, record.state); // int index = findRecord(record); // if (index != -1) { // records.remove(index); // remove from the list //// if (editor != null) { //// record.sketch = editor.getSketch(); //// records.add(record); // move to the end of the line //// } // save(); // write the recent file with the latest // } // } else { //// System.out.println("sketch not null in handleOpen: " + record.getPath()); // } // // otherwise the other handleOpen() will be called once it opens // } /** * Called by Base when a new sketch is opened, to add the sketch to the last * entry on the Recent queue. If the sketch is already in the list, it is * first removed so it doesn't show up multiple times. */ synchronized static public void append(Editor editor) { if (!editor.getSketch().isUntitled()) { // If this sketch is already in the menu, remove it remove(editor); if (records.size() == Preferences.getInteger("recent.count")) { records.remove(0); // remove the first entry } // new Exception("adding to recent: " + editor.getSketch().getMainFilePath()).printStackTrace(System.out); // Record newRec = new Record(editor, editor.getSketch()); // records.add(newRec); records.add(new Record(editor)); // updateMenu(); save(); // } else { // new Exception("NOT adding to recent: " + editor.getSketch().getMainFilePath()).printStackTrace(System.out); } } synchronized static public void rename(Editor editor, String oldPath) { if (records.size() == Preferences.getInteger("recent.count")) { records.remove(0); // remove the first entry } int index = findRecord(oldPath); //check if record exists if (index != -1) { records.remove(index); } records.add(new Record(editor)); save(); } static int findRecord(String path) { for (int i = 0; i < records.size(); i++) { if (path.equals(records.get(i).path)) { return i; } } return -1; } // int findRecord(Record rec) { // int index = records.indexOf(rec); // if (index != -1) { // return index; // } // return findRecord(rec.path); //// index = 0; //// for (Record r : records) { //// System.out.println("checking " + r + "\n against " + rec); //// if (r.equals(rec)) { //// return index; //// } //// index++; //// } //// return -1; // } static class Record { String path; // if not loaded, this is non-null // EditorState state; // if not loaded, this is non-null // Sketch sketch; // Record(String path, EditorState state) { // this.path = path; // this.state = state; // } Record(String path) { this.path = path; } // Record(Editor editor, Sketch sketch) { // this.editor = editor; // this.sketch = sketch; // } Record(Editor editor) { this(editor.getSketch().getMainFilePath()); } String getName() { // Get the filename of the .pde (or .js or .py...) String name = path.substring(path.lastIndexOf(File.separatorChar) + 1); // Return the name with the extension removed return name.substring(0, name.indexOf('.')); } String getPath() { return path; } // String getPath() { // if (sketch != null) { // return sketch.getMainFilePath(); // } else { // return path; // } // } // EditorState getState() { //// return sketch == null ? state : editor.getEditorState(); // if (editor != null) { // update state if editor is open // state = editor.getEditorState(); // } // return state; // } // public boolean equals(Object o) { // Record r = (Record) o; // return getPath().equals(r.getPath()); // } } }