working on tools menu

This commit is contained in:
benfry
2008-08-18 02:55:18 +00:00
parent 165cd107f2
commit ffd3e5a72e
6 changed files with 380 additions and 199 deletions

View File

@@ -55,11 +55,9 @@ public class Base {
boolean builtOnce;
// these are static because they're used by Sketch
static File examplesFolder;
static String examplesPath; // canonical path (for comparison)
static File librariesFolder;
static String librariesPath;
static private File examplesFolder;
static private File librariesFolder;
static private File toolsFolder;
// maps imported packages to their library folder
static Hashtable importToLibraryTable = new Hashtable();
@@ -152,10 +150,10 @@ public class Base {
platform.init(this);
// Get paths for the libraries and examples in the Processing folder
examplesFolder = new File(System.getProperty("user.dir"), "examples");
examplesPath = examplesFolder.getAbsolutePath();
librariesFolder = new File(System.getProperty("user.dir"), "libraries");
librariesPath = librariesFolder.getAbsolutePath();
String workingDirectory = System.getProperty("user.dir");
examplesFolder = new File(workingDirectory, "examples");
librariesFolder = new File(workingDirectory, "libraries");
toolsFolder = new File(workingDirectory, "tools");
// Get the sketchbook path, and make sure it's set properly
String sketchbookPath = Preferences.get("sketchbook.path");
@@ -663,7 +661,7 @@ public class Base {
* Asynchronous version of menu rebuild to be used on save and rename
* to prevent the interface from locking up until the menus are done.
*/
public void rebuildSketchbookMenu() {
protected void rebuildSketchbookMenus() {
//System.out.println("async enter");
//new Exception().printStackTrace();
SwingUtilities.invokeLater(new Runnable() {
@@ -678,7 +676,7 @@ public class Base {
}
public void rebuildToolbarMenu(JMenu menu) {
protected void rebuildToolbarMenu(JMenu menu) {
JMenuItem item;
menu.removeAll();
@@ -711,7 +709,7 @@ public class Base {
}
public void rebuildSketchbookMenu(JMenu menu) {
protected void rebuildSketchbookMenu(JMenu menu) {
//System.out.println("rebuilding sketchbook menu");
//new Exception().printStackTrace();
try {
@@ -1074,12 +1072,32 @@ public class Base {
}
static public String getExamplesPath() {
return examplesFolder.getAbsolutePath();
}
static public String getLibrariesPath() {
return librariesFolder.getAbsolutePath();
}
static public File getToolsFolder() {
return toolsFolder;
}
static public String getToolsPath() {
return toolsFolder.getAbsolutePath();
}
public File getSketchbookFolder() {
return new File(Preferences.get("sketchbook.path"));
}
public File getDefaultSketchbookFolder() {
protected File getDefaultSketchbookFolder() {
File sketchbookFolder = null;
try {
sketchbookFolder = platform.getDefaultSketchbookFolder();

View File

@@ -34,6 +34,8 @@ import java.awt.datatransfer.*;
import java.awt.event.*;
import java.awt.print.*;
import java.io.*;
import java.net.*;
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;
@@ -607,9 +609,113 @@ public class Editor extends JFrame {
protected JMenu buildToolsMenu() {
JMenuItem item;
JMenu menu = new JMenu("Tools");
addInternalTools(menu);
addTools(menu, Base.getToolsFolder());
addTools(menu, base.getSketchbookFolder());
System.out.println("done.");
return menu;
}
protected void addTools(JMenu menu, File sourceFolder) {
HashMap toolItems = new HashMap();
File[] folders = sourceFolder.listFiles(new FileFilter() {
public boolean accept(File folder) {
if (folder.isDirectory()) {
// System.out.println("checking " + folder);
File subfolder = new File(folder, "tool");
return subfolder.exists();
}
return false;
}
});
if (folders == null || folders.length == 0) {
return;
}
for (int i = 0; i < folders.length; i++) {
File toolDirectory = new File(folders[i], "tool");
try {
// add dir to classpath for .classes
//urlList.add(toolDirectory.toURL());
// add .jar files to classpath
File[] archives = toolDirectory.listFiles(new FilenameFilter() {
public boolean accept(File dir, String name) {
return (name.toLowerCase().endsWith(".jar") ||
name.toLowerCase().endsWith(".zip"));
}
});
URL[] urlList = new URL[archives.length];
for (int j = 0; j < urlList.length; j++) {
urlList[j] = archives[j].toURL();
}
URLClassLoader loader = new URLClassLoader(urlList);
// Alternatively, could use manifest files with special attributes:
// http://java.sun.com/j2se/1.3/docs/guide/jar/jar.html
// Example code for loading from a manifest file:
// http://forums.sun.com/thread.jspa?messageID=3791501
File infoFile = new File(toolDirectory, "tool.txt");
if (!infoFile.exists()) continue;
String[] info = PApplet.loadStrings(infoFile);
//Main-Class: org.poo.shoe.AwesomerTool
//String className = folders[i].getName();
String className = null;
for (int k = 0; k < info.length; k++) {
if (info[k].startsWith(";")) continue;
String[] pieces = PApplet.splitTokens(info[k], ": ");
if (pieces.length == 2) {
if (pieces[0].equals("Main-Class")) {
className = pieces[1];
}
}
}
// If no class name found, just move on.
if (className == null) continue;
Class toolClass = Class.forName(className, true, loader);
final Tool tool = (Tool) toolClass.newInstance();
tool.init(Editor.this);
String title = tool.getMenuTitle();
JMenuItem item = new JMenuItem(title);
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
SwingUtilities.invokeLater(tool);
}
});
//menu.add(item);
toolItems.put(title, item);
} catch (Exception e) {
e.printStackTrace();
}
}
ArrayList<String> toolList = new ArrayList(toolItems.keySet());
if (toolList.size() == 0) return;
menu.addSeparator();
Collections.sort(toolList);
for (String title : toolList) {
menu.add((JMenuItem) toolItems.get(title));
}
}
protected JMenu addInternalTools(JMenu menu) {
JMenuItem item;
item = newJMenuItem("Auto Format", 'T');
item.addActionListener(new ActionListener() {
synchronized public void actionPerformed(ActionEvent e) {
@@ -1634,97 +1740,109 @@ public class Editor extends JFrame {
* modifications (if any) to the previous sketch need to be saved.
*/
protected boolean handleOpenInternal(String path) {
try {
// check to make sure that this .pde file is
// in a folder of the same name
File file = new File(path);
File parentFile = new File(file.getParent());
String parentName = parentFile.getName();
String pdeName = parentName + ".pde";
File altFile = new File(file.getParent(), pdeName);
// check to make sure that this .pde file is
// in a folder of the same name
File file = new File(path);
File parentFile = new File(file.getParent());
String parentName = parentFile.getName();
String pdeName = parentName + ".pde";
File altFile = new File(file.getParent(), pdeName);
if (pdeName.equals(file.getName())) {
// no beef with this guy
if (pdeName.equals(file.getName())) {
// no beef with this guy
} else if (altFile.exists()) {
// user selected a .java from the same sketch,
// but open the .pde instead
path = altFile.getAbsolutePath();
//System.out.println("found alt file in same folder");
} else if (altFile.exists()) {
// user selected a .java from the same sketch,
// but open the .pde instead
path = altFile.getAbsolutePath();
//System.out.println("found alt file in same folder");
} else if (!path.endsWith(".pde")) {
Base.showWarning("Bad file selected",
"Processing can only open its own sketches\n" +
"and other files ending in .pde", null);
return false;
} else if (!path.endsWith(".pde")) {
Base.showWarning("Bad file selected",
"Processing can only open its own sketches\n" +
"and other files ending in .pde", null);
return false;
} else {
String properParent =
file.getName().substring(0, file.getName().length() - 4);
} else {
String properParent =
file.getName().substring(0, file.getName().length() - 4);
Object[] options = { "OK", "Cancel" };
String prompt =
"The file \"" + file.getName() + "\" needs to be inside\n" +
"a sketch folder named \"" + properParent + "\".\n" +
"Create this folder, move the file, and continue?";
Object[] options = { "OK", "Cancel" };
String prompt =
"The file \"" + file.getName() + "\" needs to be inside\n" +
"a sketch folder named \"" + properParent + "\".\n" +
"Create this folder, move the file, and continue?";
int result = JOptionPane.showOptionDialog(this,
prompt,
"Moving",
JOptionPane.YES_NO_OPTION,
JOptionPane.QUESTION_MESSAGE,
null,
options,
options[0]);
int result = JOptionPane.showOptionDialog(this,
prompt,
"Moving",
JOptionPane.YES_NO_OPTION,
JOptionPane.QUESTION_MESSAGE,
null,
options,
options[0]);
if (result == JOptionPane.YES_OPTION) {
// create properly named folder
File properFolder = new File(file.getParent(), properParent);
if (properFolder.exists()) {
Base.showWarning("Error",
"A folder named \"" + properParent + "\" " +
"already exists. Can't open sketch.", null);
return false;
}
if (!properFolder.mkdirs()) {
throw new IOException("Couldn't create sketch folder");
}
// copy the sketch inside
File properPdeFile = new File(properFolder, file.getName());
File origPdeFile = new File(path);
Base.copyFile(origPdeFile, properPdeFile);
// remove the original file, so user doesn't get confused
origPdeFile.delete();
// update with the new path
path = properPdeFile.getAbsolutePath();
} else if (result == JOptionPane.NO_OPTION) {
if (result == JOptionPane.YES_OPTION) {
// create properly named folder
File properFolder = new File(file.getParent(), properParent);
if (properFolder.exists()) {
Base.showWarning("Error",
"A folder named \"" + properParent + "\" " +
"already exists. Can't open sketch.", null);
return false;
}
if (!properFolder.mkdirs()) {
//throw new IOException("Couldn't create sketch folder");
Base.showWarning("Error",
"Could not create the sketch folder.", null);
return false;
}
// copy the sketch inside
File properPdeFile = new File(properFolder, file.getName());
File origPdeFile = new File(path);
try {
Base.copyFile(origPdeFile, properPdeFile);
} catch (IOException e) {
Base.showWarning("Error", "Could not copy to a proper location.", e);
return false;
}
// remove the original file, so user doesn't get confused
origPdeFile.delete();
// update with the new path
path = properPdeFile.getAbsolutePath();
} else if (result == JOptionPane.NO_OPTION) {
return false;
}
}
try {
sketch = new Sketch(this, path);
header.rebuild();
// Set the title of the window to "sketch_070752a - Processing 0126"
setTitle(sketch.getName() + " | " + WINDOW_TITLE);
// Disable untitled setting from previous document, if any
untitled = false;
// Store information on who's open and running
// (in case there's a crash or something that can't be recovered)
base.storeSketches();
Preferences.save();
// opening was successful
return true;
} catch (Exception e) {
e.printStackTrace();
statusError(e);
} catch (IOException e) {
Base.showWarning("Error", "Could not create the sketch.", e);
return false;
}
header.rebuild();
// Set the title of the window to "sketch_070752a - Processing 0126"
setTitle(sketch.getName() + " | " + WINDOW_TITLE);
// Disable untitled setting from previous document, if any
untitled = false;
// Store information on who's open and running
// (in case there's a crash or something that can't be recovered)
base.storeSketches();
Preferences.save();
// opening was successful
return true;
// } catch (Exception e) {
// e.printStackTrace();
// statusError(e);
// return false;
// }
}

View File

@@ -177,8 +177,8 @@ public class Sketch {
code = new SketchCode[list.length]; //codeCount];
hidden = new SketchCode[list.length]; //hiddenCount];
int codeCounter = 0;
int hiddenCounter = 0;
// int codeCounter = 0;
// int hiddenCounter = 0;
String[] extensions = getExtensions();
@@ -211,10 +211,10 @@ public class Sketch {
// it would be otherwise possible to sneak in nasty filenames. [0116]
if (Sketch.isSanitaryName(base)) {
if (hiddenX) {
hidden[hiddenCounter++] =
hidden[hiddenCount++] =
new SketchCode(new File(folder, filename), extension);
} else {
code[codeCounter++] =
code[codeCount++] =
new SketchCode(new File(folder, filename), extension);
}
}
@@ -222,13 +222,14 @@ public class Sketch {
}
}
codeCount = codeCounter;
// codeCount = codeCounter;
// some of the hidden files may be bad too, so use hiddenCounter
// added for rev 0121, fixes bug found by axel
hiddenCount = hiddenCounter;
code = (SketchCode[]) PApplet.subset(0, codeCount);
hidden = (SketchCode[]) PApplet.subset(0, hiddenCount);
// hiddenCount = hiddenCounter;
// System.out.println(codeCount + " " + hiddenCount);
code = (SketchCode[]) PApplet.subset(code, 0, codeCount);
hidden = (SketchCode[]) PApplet.subset(hidden, 0, hiddenCount);
// remove any entries that didn't load properly from codeCount
// int index = 0;
@@ -532,7 +533,7 @@ public class Sketch {
// get the changes into the sketchbook menu
// (re-enabled in 0115 to fix bug #332)
editor.base.rebuildSketchbookMenu();
editor.base.rebuildSketchbookMenus();
} else { // else if something besides code[0]
if (!current.renameTo(newFile, newExtension)) {
@@ -1005,7 +1006,7 @@ public class Sketch {
// Name changed, rebuild the sketch menus
//editor.sketchbook.rebuildMenusAsync();
editor.base.rebuildSketchbookMenu();
editor.base.rebuildSketchbookMenus();
// Make sure that it's not an untitled sketch
setUntitled(false);
@@ -2762,8 +2763,8 @@ public class Sketch {
*/
public boolean isReadOnly() {
String apath = folder.getAbsolutePath();
if (apath.startsWith(Base.examplesPath) ||
apath.startsWith(Base.librariesPath)) {
if (apath.startsWith(Base.getExamplesPath()) ||
apath.startsWith(Base.getLibrariesPath())) {
return true;
// canWrite() doesn't work on directories

View File

@@ -3,7 +3,7 @@
/*
Part of the Processing project - http://processing.org
Copyright (c) 2006 Ben Fry and Casey Reas
Copyright (c) 2006-08 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 as published by
@@ -40,12 +40,6 @@ import javax.swing.text.*;
* colors and paste them into your program. We didn't do any sort of
* auto-insert of colorMode() or fill() or stroke() code cuz we couldn't
* decide on a good way to do this.. your contributions welcome).
* <p/>
* There is currently a bug that causes the drawing area to show up
* slightly large on Windows and Linux. This is an annoyance but less
* important than me fixing some other stuff. If someone were to track
* down what's going wrong, I'd be super grateful.
* (<A HREF="http://dev.processing.org/bugs/show_bug.cgi?id=310">Bug 310</A>)
*/
public class ColorSelector implements DocumentListener {
@@ -137,8 +131,8 @@ public class ColorSelector implements DocumentListener {
public void show() {
frame.setVisible(true);
// You've got to be f--ing kidding me.. why did the following line get
// deprecated in the pile of s-- that follows it?
// You've got to be f--ing kidding me.. why did the following line
// get deprecated for the pile of s-- that follows it?
//frame.setCursor(Cursor.CROSSHAIR_CURSOR);
frame.setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
}

View File

@@ -0,0 +1,44 @@
/* -*- mode: jde; c-basic-offset: 2; indent-tabs-mode: nil -*- */
/*
Tool - interface implementation for the Processing tools menu
Part of the Processing project - http://processing.org
Copyright (c) 2008 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 as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
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.tools;
import processing.app.Editor;
/**
* Interface for items to be shown in the Tools menu.
*/
public interface Tool extends Runnable {
public void init(Editor editor);
public void run();
// Not doing shortcuts for now, no way to resolve between tools.
// Also would need additional modifiers for shift and alt.
//public char getShortcutKey();
public String getMenuTitle();
}

174
todo.txt
View File

@@ -1,4 +1,59 @@
0147 pde
X inside Sketch.java, don't hardwire the file extension types
X arduino uses .c, .cpp, .h instead of .java
X http://dev.processing.org/bugs/show_bug.cgi?id=807
tools
_ refactor code to use more getter/setter methods
X move the debug classes back into processing.app.debug
X this will help the library stuff
X dynamic tools menu (requires moving files around)
o this means can integrate the autoformat stuff
_ processing.mess/tools has chandler's template stuff
_ don't open more than one "create font" or "color selector"
_ http://dev.processing.org/bugs/show_bug.cgi?id=830
o need to write converter that will handle syntax changes
o convert from alpha to 1.0 syntax
o framerate() -> frameRate()
o basically find & replace with regexps
o acts just like find/replace.. go through and do by hand
o 3 column input file
o regexp to find, to replace, human readable description
o (void\s+)loop(\s+{) -> $1draw$2 (maintain all whitespace stuff)
o "The loop() method is now called draw() as of beta"
o "angleMode no longer exists, use radians() around your angles"
o (comment out the line)
X this would only fix the minor stuff, not the important stuff
X it also has a lot of potential problems and corner cases
X just not worth the effort
X tools api
X need to support the basic set of functions that are expected
X to be used, otherwise it's gonna be total hell when we make a real api
X getEditor()
X get/setSelStart/End
X get/setSelText
X get/setText
_ setCurrentTab(int index or String filename)
_ getTabs() ? maybe these are interface to the code class
_ clean up API for JEditTextArea before exposing the object
X make dynamically loaded plugins and "tools" menu
X http://dev.processing.org/bugs/show_bug.cgi?id=124
X tools api:
X init() -> run when p5 is first launched
o isAvailable() -> true/false whether the option should be dimmed
X skip this for now, not sending events to tools all the time
o show() and hide() -> open the window or run the tool
o is there a better name than show() since not all will be visible?
o maybe it's just a run() method launched via invokeLater()?
X can't really do show/hide from editor, make the tool handle this
o getDefaultShortcut() -> returns the default shortcut this tools wants
o store this in the data file instead
X not bothering with this for now, since can't handle overlaps
X needs to be able to get current sketch code
X look at the pretty-formatting code to see what else needs to be in api
o getMenu() -> return non-null the tool is a submenu
X no, bad idea.. don't want a zillion submenus on things
_ write basic documentation for how it's handled
_ use macosx dialogs for all of the editor stuff
@@ -143,6 +198,20 @@ _ set DYLD_LIBRARY_PATH to include .dylib and other framework stuff
_ java.library.path will only handle .jnilib stuff
_ need better error messages for broken api / library troubles
_ e.g. ocd is broken in 0125 because of method signature changes
_ could have library developers update compatability note
_ so they would need to test library and say "compatible with 0110"
_ before it would automatically update or show as an update
_ tools -> get library
_ library url: [ http://blahblah.org/something ]
_ this would grab something.version, and something.zip
_ maybe something.xml that would include a bunch of background
_ tools -> update libraries
_ this would check the something.version to see if it's newer
_ document how to add libraries: put in sketchbook (not libraries folder)
_ library/download.url and library/home.url
_ if there's a reference subfolder, add that to the help menu
o and enable it for "find in reference"
X nice idea but too error-prone
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
@@ -857,9 +926,6 @@ _ need to set dock icon title on osx
PDE / Sketch & Sketchbook
_ inside Sketch.java, don't hardwire the file extension types
_ arduino uses .c, .cpp, .h instead of .java
_ http://dev.processing.org/bugs/show_bug.cgi?id=807
_ don't reload sketch on "save as"
_ this can result in loss of data (undo is lost)
_ http://dev.processing.org/bugs/show_bug.cgi?id=433
@@ -919,61 +985,16 @@ _ maybe just open from a zip file, since psk doesn't help anything
TOOLS / General
_ refactor code to use more getter/setter methods
_ move the debug classes back into processing.app.debug
_ this will help the library stuff
_ dynamic tools menu (requires moving files around)
_ this means can integrate the autoformat stuff
_ processing.mess/tools has chandler's template stuff
_ don't open more than one "create font" or "color selector"
_ http://dev.processing.org/bugs/show_bug.cgi?id=830
_ need to write converter that will handle syntax changes
_ basically find & replace with regexps
_ framerate() -> frameRate()
_ convert from alpha to 1.0 syntax
_ acts just like find/replace.. go through and do by hand
_ 3 column input file
_ regexp to find, to replace, human readable description
_ (void\s+)loop(\s+{) -> $1draw$2 (maintain all whitespace stuff)
_ "The loop() method is now called draw() as of beta"
_ "angleMode no longer exists, use radians() around your angles"
_ (comment out the line)
_ tools -> shared code
_ Integrator / FloatThing / CameraStuff -- Update
_ to pull code from a local folder
_ update will update classes from shared in the current folder
_ tools api
_ need to support the basic set of functions that are expected
_ to be used, otherwise it's gonna be total hell when we make a real api
_ getEditor()
_ get/setSelStart/End
_ get/setSelText
_ get/setText
_ setCurrentTab(int index or String filename)
_ getTabs() ? maybe these are interface to the code class
_ clean up API for JEditTextArea before exposing the object
_ need method for showing prefs for the tools
_ add pref to handle date vs. increment on archive sketch
_ make dynamically loaded plugins and "tools" menu
_ http://dev.processing.org/bugs/show_bug.cgi?id=124
_ break out beautify as its own plugin
_ tools api:
_ init() -> run when p5 is first launched
_ isAvailable() -> true/false whether the option should be dimmed
_ skip this for now, not sending events to tools all the time
_ show() -> open the window or run the tool
_ is there a better name than show() since not all will be visible?
_ maybe it's just a run() method launched via invokeLater()?
_ hide() -> hide the tool's frame
_ getDefaultShortcut() -> returns the default shortcut this tools wants
_ store this in the data file instead
_ needs to be able to get current sketch code
_ look at the pretty-formatting code to see what else needs to be in api
o getMenu() -> return non-null the tool is a submenu
o no, bad idea.. don't want a zillion submenus on things
_ need a proper means to handle command keys for tools
_ need a proper means to handle command keys for tools (?)
_ http://dev.processing.org/bugs/show_bug.cgi?id=140
_ external editor -> add a command to launch
_ need method for showing prefs for the tools
_ i.e. add pref to handle date vs. increment on archive sketch
_ tools that need prefs will show their own GUI anyway
TOOLS / Ideas
_ add a command to launch an external editor
_ http://dev.processing.org/bugs/show_bug.cgi?id=141
_ add tool to "Add custom html to sketch"
_ that copies applet.html,
@@ -984,6 +1005,15 @@ _ tool to run in appletviewer? sun.applet.Main is appletviewer
_ import sketch from url (takes a zip from archive sketch)
_ ftp upload sketch
_ archive sketch direct to bug report
_ courseware
_ export sketch as applet when uploading
_ save sketch zip files
_ have a means to load them from "teacher" version of p5
_ figure out how to use the
_ shared code
_ Integrator / FloatThing / CameraStuff -- Update
_ to pull code from a local folder
_ update will update classes from shared in the current folder
TOOLS / Color Picker
@@ -992,37 +1022,12 @@ _ pasting into color picker doesn't update the color values
_ http://dev.processing.org/bugs/show_bug.cgi?id=503
TOOLS / Courseware
_ export sketch as applet when uploading
_ save sketch zip files, have a means to load them from "teacher" version of p5
_ figure out how to use the
TOOLS / Discourse Format
TOOLS / Copy for Discourse
_ occasional exception in "copy for discourse"
_ http://dev.processing.org/bugs/show_bug.cgi?id=729
TOOLS / Libraries
_ could have library developers update compatability note
_ so they would need to test library and say "compatible with 0110"
_ before it would automatically update or show as an update
_ tools -> get library
_ library url: [ http://blahblah.org/something ]
_ this would grab something.version, and something.zip
_ maybe something.xml that would include a bunch of background
_ tools -> update libraries
_ this would check the something.version to see if it's newer
_ document how to add libraries: put in sketchbook (not libraries folder)
_ library/download.url and library/home.url
_ if there's a reference subfolder, add that to the help menu
o and enable it for "find in reference"
X nice idea but too error-prone
TOOLS / Auto Format
_ autoformat fails when there's no newline
@@ -1056,7 +1061,8 @@ _ http://dev.processing.org/bugs/show_bug.cgi?id=760
_ too many right parens error when there are not
_ http://dev.processing.org/bugs/show_bug.cgi?id=867
_ code without a new line at end crashes
_
_ http://dev.processing.org/bugs/show_bug.cgi?id=880
_ break out as its own plugin
TOOLS / Create Font