diff --git a/app/Base.java b/app/Base.java index ff53d2f9b..d646e00de 100644 --- a/app/Base.java +++ b/app/Base.java @@ -52,30 +52,63 @@ import com.ice.jni.registry.*; * files and images, etc) that comes from that. */ public class Base { - static final int VERSION = 1; + static final int VERSION = 3; static final String VERSION_NAME = "0004 Alpha"; - static public int platform; - - // platform IDs for platform + // platform IDs for PApplet.platform static final int WINDOWS = 1; static final int MACOS9 = 2; static final int MACOSX = 3; static final int LINUX = 4; static final int OTHER = 0; - - - // moved from PApplet - // in preperation of detaching the IDE from the - // Arduino core classes - - /** + + // used by split, all the standard whitespace chars + // (uncludes unicode nbsp, that little bostage) + + static final String WHITESPACE = " \t\n\r\f\u00A0"; + + /** + * Path of filename opened on the command line, + * or via the MRJ open document handler. + */ + static String openedAtStartup; + + Editor editor; + + /** + * "1.3" or "1.1" or whatever (just the first three chars) + */ + public static final String javaVersionName = + System.getProperty("java.version").substring(0,3); + + /** + * Version of Java that's in use, whether 1.1 or 1.3 or whatever, + * stored as a float. + *

+ * Note that because this is stored as a float, the values may + * not be exactly 1.3 or 1.4. Instead, make sure you're + * comparing against 1.3f or 1.4f, which will have the same amount + * of error (i.e. 1.40000001). This could just be a double, but + * since Processing only uses floats, it's safer to do this, + * because there's no good way to specify a double with the preproc. + */ + public static final float javaVersion = + new Float(javaVersionName).floatValue(); + + /** + * Current platform in use, one of the + * PConstants WINDOWS, MACOSX, MACOS9, LINUX or OTHER. + */ + static public int platform; + + /** * Current platform in use. *

* Equivalent to System.getProperty("os.name"), just used internally. */ - static public String platformName = System.getProperty("os.name"); + static public String platformName = + System.getProperty("os.name"); static { // figure out which operating system @@ -105,23 +138,6 @@ public class Base { } } - // used by split, all the standard whitespace chars - // (uncludes unicode nbsp, that little bostage) - - static final String WHITESPACE = " \t\n\r\f\u00A0"; - - - - - /** - * Path of filename opened on the command line, - * or via the MRJ open document handler. - */ - static String openedAtStartup; - - Editor editor; - - static public void main(String args[]) { // make sure that this is running on java 1.4 @@ -179,6 +195,9 @@ public class Base { e.printStackTrace(); } + // use native popups so they don't look so crappy on osx + JPopupMenu.setDefaultLightWeightPopupEnabled(false); + // build the editor object editor = new Editor(); @@ -214,7 +233,6 @@ public class Base { * returns true if running on windows. */ static public boolean isWindows() { - return platform == WINDOWS; } @@ -223,7 +241,6 @@ public class Base { * true if running on linux. */ static public boolean isLinux() { - return platform == LINUX; } @@ -363,26 +380,40 @@ public class Base { } - static public File getBuildFolder() { - String buildPath = Preferences.get("build.path"); - if (buildPath != null) return new File(buildPath); + static File buildFolder; - File folder = new File(getTempFolder(), "build"); - if (!folder.exists()) folder.mkdirs(); - return folder; + static public File getBuildFolder() { + if (buildFolder == null) { + String buildPath = Preferences.get("build.path"); + if (buildPath != null) { + buildFolder = new File(buildPath); + + } else { + //File folder = new File(getTempFolder(), "build"); + //if (!folder.exists()) folder.mkdirs(); + buildFolder = createTempFolder("build"); + buildFolder.deleteOnExit(); + } + } + return buildFolder; } /** * Get the path to the platform's temporary folder, by creating * a temporary temporary file and getting its parent folder. + *
+ * Modified for revision 0094 to actually make the folder randomized + * to avoid conflicts in multi-user environments. (Bug 177) */ - static public File getTempFolder() { + static public File createTempFolder(String name) { try { - File ignored = File.createTempFile("ignored", null); - String tempPath = ignored.getParent(); - ignored.delete(); - return new File(tempPath); + File folder = File.createTempFile(name, null); + //String tempPath = ignored.getParent(); + //return new File(tempPath); + folder.delete(); + folder.mkdirs(); + return folder; } catch (Exception e) { e.printStackTrace(); @@ -548,6 +579,55 @@ public class Base { // ................................................................. + // someone needs to be slapped + //static KeyStroke closeWindowKeyStroke; + + /** + * Return true if the key event was a Ctrl-W or an ESC, + * both indicators to close the window. + * Use as part of a keyPressed() event handler for frames. + */ + /* + static public boolean isCloseWindowEvent(KeyEvent e) { + if (closeWindowKeyStroke == null) { + int modifiers = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(); + closeWindowKeyStroke = KeyStroke.getKeyStroke('W', modifiers); + } + return ((e.getKeyCode() == KeyEvent.VK_ESCAPE) || + KeyStroke.getKeyStrokeForEvent(e).equals(closeWindowKeyStroke)); + } + */ + + + /** + * Registers key events for a Ctrl-W and ESC with an ActionListener + * that will take care of disposing the window. + */ + static public void registerWindowCloseKeys(JRootPane root, //Window window, + ActionListener disposer) { + /* + JRootPane root = null; + if (window instanceof JFrame) { + root = ((JFrame)window).getRootPane(); + } else if (window instanceof JDialog) { + root = ((JDialog)window).getRootPane(); + } + */ + + KeyStroke stroke = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0); + root.registerKeyboardAction(disposer, stroke, + JComponent.WHEN_IN_FOCUSED_WINDOW); + + int modifiers = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(); + stroke = KeyStroke.getKeyStroke('W', modifiers); + root.registerKeyboardAction(disposer, stroke, + JComponent.WHEN_IN_FOCUSED_WINDOW); + } + + + // ................................................................. + + /** * Given the reference filename from the keywords list, * builds a URL and passes it to openURL. @@ -858,7 +938,9 @@ public class Base { static public void removeDir(File dir) { if (dir.exists()) { removeDescendants(dir); - dir.delete(); + if (!dir.delete()) { + System.err.println("Could not delete " + dir); + } } } @@ -917,6 +999,46 @@ public class Base { } + /** + * Gets a list of all files within the specified folder, + * and returns a list of their relative paths. + * Ignores any files/folders prefixed with a dot. + */ + static public String[] listFiles(String path, boolean relative) { + return listFiles(new File(path), relative); + } + + static public String[] listFiles(File folder, boolean relative) { + String path = folder.getAbsolutePath(); + Vector vector = new Vector(); + listFiles(relative ? (path + File.separator) : "", path, vector); + String outgoing[] = new String[vector.size()]; + vector.copyInto(outgoing); + return outgoing; + } + + static protected void listFiles(String basePath, + String path, Vector vector) { + File folder = new File(path); + String list[] = folder.list(); + if (list == null) return; + + for (int i = 0; i < list.length; i++) { + if (list[i].charAt(0) == '.') continue; + + File file = new File(path, list[i]); + String newPath = file.getAbsolutePath(); + if (newPath.startsWith(basePath)) { + newPath = newPath.substring(basePath.length()); + } + vector.add(newPath); + if (file.isDirectory()) { + listFiles(basePath, newPath, vector); + } + } + } + + /** * Equivalent to the one in PApplet, but static (die() is removed) */ @@ -954,7 +1076,7 @@ public class Base { return null; } - ////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////// // STRINGS diff --git a/app/Compiler.java b/app/Compiler.java index 72c959320..d7b627cc2 100644 --- a/app/Compiler.java +++ b/app/Compiler.java @@ -76,7 +76,9 @@ public class Compiler implements MessageConsumer { String userdir = System.getProperty("user.dir") + File.separator; - String baseCommandCompiler[] = new String[] { + LibraryManager libraryManager = new LibraryManager(); + + String preCommandCompiler[] = new String[] { ((!Base.isMacOS()) ? "tools/avr/bin/avr-gcc" : userdir + "tools/avr/bin/avr-gcc"), "-c", // compile, don't link @@ -86,11 +88,19 @@ public class Compiler implements MessageConsumer { "-w", // surpress all warnings "-mmcu=" + Preferences.get("build.mcu"), "-DF_CPU=" + Preferences.get("build.f_cpu"), - " ", - " " }; - String baseCommandCompilerCPP[] = new String[] { + // use lib directories as include paths + String[] libDirs = libraryManager.getFolderPaths(); + + // Last two arguments will specify the file being compiled and the output file. + String[] baseCommandCompiler = new String[preCommandCompiler.length + libDirs.length + 2]; + System.arraycopy(preCommandCompiler, 0, baseCommandCompiler, 0, preCommandCompiler.length); + for (int i = 0; i < libDirs.length; ++i) { + baseCommandCompiler[preCommandCompiler.length + i] = "-I" + libDirs[i]; + } + + String preCommandCompilerCPP[] = new String[] { ((!Base.isMacOS()) ? "tools/avr/bin/avr-g++" : userdir + "tools/avr/bin/avr-g++"), "-c", // compile, don't link @@ -101,46 +111,33 @@ public class Compiler implements MessageConsumer { "-fno-exceptions", "-mmcu=" + Preferences.get("build.mcu"), "-DF_CPU=" + Preferences.get("build.f_cpu"), - " ", - " " }; - String baseCommandLinker[] = new String[] { + // use lib directories as include paths + // Last two arguments will specify the file being compiled and the output file. + String[] baseCommandCompilerCPP = new String[preCommandCompilerCPP.length + libDirs.length + 2]; + System.arraycopy(preCommandCompilerCPP, 0, baseCommandCompilerCPP, 0, preCommandCompilerCPP.length); + for (int i = 0; i < libDirs.length; ++i) { + baseCommandCompilerCPP[preCommandCompilerCPP.length + i] = "-I" + libDirs[i]; + } + + String preCommandLinker[] = new String[] { ((!Base.isMacOS()) ? "tools/avr/bin/avr-gcc" : userdir + "tools/avr/bin/avr-gcc"), " ", "-mmcu=" + Preferences.get("build.mcu"), "-o", " ", -// ((!Base.isMacOS()) ? "" : userdir) + "lib/uart.o", -// ((!Base.isMacOS()) ? "" : userdir) + "lib/buffer.o", -// ((!Base.isMacOS()) ? "" : userdir) + "lib/timer.o", -// ((!Base.isMacOS()) ? "" : userdir) + "lib/wiring.o", -// ((!Base.isMacOS()) ? "" : userdir) + "lib/pins_arduino.o", - //((!Base.isMacOS()) ? "lib/WApplet.o" : - //userdir + "lib/WApplet.o"), - //((!Base.isMacOS()) ? "lib/WSerial.o" : - //userdir + "lib/WSerial.o"), - //((!Base.isMacOS()) ? "lib/WTimer.o" : - //userdir + "lib/WTimer.o"), - //((!Base.isMacOS()) ? "lib/Servo.o" : - //userdir + "lib/Servo.o"), - ////((!Base.isMacOS()) ? "lib/Wire.o" : - //// userdir + "lib/Wire.o"), - ////((!Base.isMacOS()) ? "lib/WServo.o" : - //// userdir + "lib/WServo.o"), - //((!Base.isMacOS()) ? "lib/WDisplay.o" : - //userdir + "lib/WDisplay.o"), - //((!Base.isMacOS()) ? "lib/WEncoder.o" : - //userdir + "lib/WEncoder.o"), - //((!Base.isMacOS()) ? "lib/WInterrupts.o" : - //userdir + "lib/WInterrupts.o"), - //((!Base.isMacOS()) ? "lib/WCounter.o" : - //userdir + "lib/WCounter.o"), - //((!Base.isMacOS()) ? "tools/avr/avr/lib/libm.a" : - //userdir + "tools/avr/avr/lib/libm.a") }; + // use lib object files during include + String[] libObjectFiles = libraryManager.getObjectFiles(); + String[] baseCommandLinker = new String[preCommandLinker.length + libObjectFiles.length]; + System.arraycopy(preCommandLinker, 0, baseCommandLinker, 0, preCommandLinker.length); + for (int i = 0; i < libObjectFiles.length; ++i) { + baseCommandLinker[preCommandLinker.length + i] = libObjectFiles[i]; + } + String baseCommandObjcopy[] = new String[] { ((!Base.isMacOS()) ? "tools/avr/bin/avr-objcopy" : userdir + "tools/avr/bin/avr-objcopy"), @@ -295,8 +292,8 @@ public class Compiler implements MessageConsumer { Process process; boolean compiling = true; for(int i = 0; i < fileCount; i++) { - baseCommandCompiler[8] = sourceNames[i]; - baseCommandCompiler[9] = "-o"+ objectNames[i]; + baseCommandCompiler[baseCommandCompiler.length - 2] = sourceNames[i]; + baseCommandCompiler[baseCommandCompiler.length - 1] = "-o"+ objectNames[i]; //System.arraycopy(baseCommandCompiler.length //for(int j = 0; j < baseCommandCompiler.length; j++) { // System.out.println(baseCommandCompiler[j]); @@ -325,8 +322,8 @@ public class Compiler implements MessageConsumer { } for(int i = 0; i < fileCountCPP; i++) { - baseCommandCompilerCPP[9] = sourceNamesCPP[i]; - baseCommandCompilerCPP[10] = "-o"+ objectNamesCPP[i]; + baseCommandCompilerCPP[baseCommandCompilerCPP.length - 2] = sourceNamesCPP[i]; + baseCommandCompilerCPP[baseCommandCompilerCPP.length - 1] = "-o"+ objectNamesCPP[i]; //for(int j = 0; j < baseCommandCompilerCPP.length; j++) { // System.out.println(baseCommandCompilerCPP[j]); //} diff --git a/app/Editor.java b/app/Editor.java index 8cd23024a..09065a9a6 100644 --- a/app/Editor.java +++ b/app/Editor.java @@ -30,6 +30,8 @@ import processing.app.syntax.*; import processing.app.tools.*; import java.awt.*; +import java.awt.datatransfer.*; +import java.awt.dnd.*; import java.awt.event.*; import java.io.*; import java.lang.reflect.*; @@ -46,10 +48,8 @@ import javax.swing.undo.*; import com.oroinc.text.regex.*; import com.apple.mrj.*; - import gnu.io.*; - public class Editor extends JFrame implements MRJAboutHandler, MRJQuitHandler, MRJPrefsHandler, MRJOpenDocumentHandler //, MRJOpenApplicationHandler @@ -101,27 +101,27 @@ public class Editor extends JFrame //Point presentLocation; //Window presentationWindow; RunButtonWatcher watcher; - Runner runtime; + //Runner runtime; - //JMenuItem exportAppItem; + JMenuItem exportAppItem; JMenuItem saveMenuItem; JMenuItem saveAsMenuItem; - - //ButtonGroup serialGroup; - JMenu serialSubMenu; - JMenu serialRateSubMenu; + JMenu serialMenu; + JMenu serialRateMenu; SerialMenuListener serialMenuListener; - // - boolean debugging; boolean running; boolean presenting; + boolean debugging; // undo fellers JMenuItem undoItem, redoItem; protected UndoAction undoAction; protected RedoAction redoAction; UndoManager undo; + // used internally, and only briefly + CompoundEdit compoundEdit; + //static public UndoManager undo = new UndoManager(); // editor needs this guy // @@ -129,7 +129,7 @@ public class Editor extends JFrame //SketchHistory history; // TODO re-enable history Sketchbook sketchbook; //Preferences preferences; - FindReplace find; + //FindReplace find; //static Properties keywords; // keyword -> reference html lookup @@ -175,7 +175,7 @@ public class Editor extends JFrame setJMenuBar(menubar); // doesn't matter when this is created, just make it happen at some point - find = new FindReplace(Editor.this); + //find = new FindReplace(Editor.this); Container pain = getContentPane(); pain.setLayout(new BorderLayout()); @@ -240,28 +240,87 @@ public class Editor extends JFrame listener = new EditorListener(this, textarea); pain.add(box); - /* - // set the undo stuff for this feller - Document document = textarea.getDocument(); - //document.addUndoableEditListener(new PdeUndoableEditListener()); - document.addUndoableEditListener(new UndoableEditListener() { - public void undoableEditHappened(UndoableEditEvent e) { - if (undo != null) { - //System.out.println(e.getEdit()); - undo.addEdit(e.getEdit()); - undoAction.updateUndoState(); - redoAction.updateRedoState(); + DropTarget dt = new DropTarget(this, new DropTargetListener() { + + public void dragEnter(DropTargetDragEvent event) { + // debug messages for diagnostics + //System.out.println("dragEnter " + event); + event.acceptDrag(DnDConstants.ACTION_COPY); + } + + public void dragExit(DropTargetEvent event) { + //System.out.println("dragExit " + event); + } + + public void dragOver(DropTargetDragEvent event) { + //System.out.println("dragOver " + event); + event.acceptDrag(DnDConstants.ACTION_COPY); + } + + public void dropActionChanged(DropTargetDragEvent event) { + //System.out.println("dropActionChanged " + event); + } + + public void drop(DropTargetDropEvent event) { + //System.out.println("drop " + event); + event.acceptDrop(DnDConstants.ACTION_COPY); + + Transferable transferable = event.getTransferable(); + DataFlavor flavors[] = transferable.getTransferDataFlavors(); + int successful = 0; + + for (int i = 0; i < flavors.length; i++) { + try { + //System.out.println(flavors[i]); + //System.out.println(transferable.getTransferData(flavors[i])); + java.util.List list = + (java.util.List) transferable.getTransferData(flavors[i]); + for (int j = 0; j < list.size(); j++) { + Object item = list.get(j); + if (item instanceof File) { + File file = (File) item; + + // see if this is a .pde file to be opened + String filename = file.getName(); + if (filename.endsWith(".pde")) { + String name = filename.substring(0, filename.length() - 4); + File parent = file.getParentFile(); + if (name.equals(parent.getName())) { + handleOpenFile(file); + return; + } + } + + if (sketch.addFile(file)) { + successful++; + } + } + } + + } catch (Exception e) { + e.printStackTrace(); + } + } + + if (successful == 0) { + error("No files were added to the sketch."); + + } else if (successful == 1) { + message("One file added to the sketch."); + + } else { + message(successful + " files added to the sketch."); } } }); - */ } /** - * Hack for #@#)$(* Mac OS X. - * This appears to only be required on OS X 10.2, and this code - * isn't even being hit on OS X 10.3 or Windows. + * Hack for #@#)$(* Mac OS X 10.2. + *

+ * This appears to only be required on OS X 10.2, and is not + * even being called on later versions of OS X or Windows. */ public Dimension getMinimumSize() { //System.out.println("getting minimum size"); @@ -435,47 +494,19 @@ public class Editor extends JFrame JMenuItem item; JMenu menu = new JMenu("File"); - /* - menu.add(item = new JMenuItem("do the editor thing")); + item = newJMenuItem("New", 'N'); item.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - textarea.getPainter().setFont(new Font("Courier", Font.PLAIN, 36)); - } - }); - */ - - if (!Preferences.getBoolean("export.library")) { - item = newJMenuItem("New", 'N'); - item.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - handleNew(false); - } - }); - menu.add(item); - - } else { - item = newJMenuItem("New Sketch", 'N'); - item.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - handleNew(false); - } - }); - menu.add(item); - - item = new JMenuItem("New Library"); - item.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - handleNewLibrary(); - } - }); - menu.add(item); - } + public void actionPerformed(ActionEvent e) { + handleNew(false); + } + }); + menu.add(item); menu.add(sketchbook.getOpenMenu()); saveMenuItem = newJMenuItem("Save", 'S'); saveMenuItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { - handleSave(); + handleSave(false); } }); menu.add(saveMenuItem); @@ -496,14 +527,18 @@ public class Editor extends JFrame }); menu.add(item); -// exportAppItem = newJMenuItem("Export Application", 'E', true); -// exportAppItem.addActionListener(new ActionListener() { -// public void actionPerformed(ActionEvent e) { -// handleExportApp(); -// } -// }); -// menu.add(exportAppItem); - + /*exportAppItem = newJMenuItem("Export Application", 'E', true); + exportAppItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + //buttons.activate(EditorButtons.EXPORT); + //SwingUtilities.invokeLater(new Runnable() { + //public void run() { + handleExportApplication(); + //}}); + } + }); + menu.add(exportAppItem); + */ menu.addSeparator(); item = newJMenuItem("Page Setup", 'P', true); @@ -552,14 +587,14 @@ public class Editor extends JFrame }); menu.add(item); - //item = newJMenuItem("Present", 'R', true); - //item.addActionListener(new ActionListener() { - // public void actionPerformed(ActionEvent e) { - // handleRun(true); - // } - // }); - //menu.add(item); - + /*item = newJMenuItem("Present", 'R', true); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + handleRun(true); + } + }); + menu.add(item); + */ item = new JMenuItem("Stop"); item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { @@ -583,7 +618,7 @@ public class Editor extends JFrame if (Base.isWindows() || Base.isMacOS()) { // no way to do an 'open in file browser' on other platforms // since there isn't any sort of standard - item = new JMenuItem("Show Sketch Folder"); + item = newJMenuItem("Show Sketch Folder", 'K', false); item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { //Base.openFolder(sketchDir); @@ -599,123 +634,79 @@ public class Editor extends JFrame } - // taken from an ancient version of processing - class SerialMenuListener implements ActionListener { - - SerialMenuListener() {} - - public void actionPerformed(ActionEvent actionevent) { - int count = serialSubMenu.getItemCount(); - Object to; - - for (int i = 0; i < count; i++) { - to = serialSubMenu.getItem(i); - if ( to instanceof JCheckBoxMenuItem) ((JCheckBoxMenuItem)serialSubMenu.getItem(i)).setState(false); - } - - JCheckBoxMenuItem item = (JCheckBoxMenuItem)actionevent.getSource(); - item.setState(true); - String name = item.getLabel(); - - Preferences.set("serial.port", name); - //System.out.println("port set to " + name); - } - - - } - - // manages the serial port speed menu - class SerialRateMenuListener implements ActionListener { - - SerialRateMenuListener() {} - - public void actionPerformed(ActionEvent actionevent) { - int count = serialRateSubMenu.getItemCount(); - Object to; - - for (int i = 0; i < count; i++) { - to = serialRateSubMenu.getItem(i); - if ( to instanceof JCheckBoxMenuItem) ((JCheckBoxMenuItem)serialRateSubMenu.getItem(i)).setState(false); - } - - JCheckBoxMenuItem item = (JCheckBoxMenuItem)actionevent.getSource(); - item.setState(true); - String name = item.getLabel(); - - Preferences.set("serial.debug_rate", name); - //System.out.println("serial port speed set to " + name); - } - - - } - - - - protected JMenu buildToolsMenu() { - JMenuItem item; - JMenuItem rbMenuItem; - JMenuItem cbMenuItem; - SerialRateMenuListener srml = new SerialRateMenuListener(); - String[] portRates = { + JMenuItem item; + JMenuItem rbMenuItem; + JMenuItem cbMenuItem; + SerialRateMenuListener srml = new SerialRateMenuListener(); + String[] portRates = { "300","1200","2400","4800","9600","14400", "19200","28800","38400","57600","115200" }; - serialMenuListener = new SerialMenuListener(); + serialMenuListener = new SerialMenuListener(); JMenu menu = new JMenu("Tools"); item = newJMenuItem("Auto Format", 'T', false); item.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { + synchronized public void actionPerformed(ActionEvent e) { new AutoFormat(Editor.this).show(); - + //handleBeautify(); } }); menu.add(item); - menu.addSeparator(); - - // The serial options - - serialSubMenu = new JMenu("Serial Port"); -// item = newJMenuItem("Update List", 'E', false); -// item.addActionListener(new ActionListener() { -// public void actionPerformed(ActionEvent e) { -// // if (debug) displayResult("Serial Port List Updated"); -// //updateSerial(); -// } -// }); + /*item = new JMenuItem("Create Font..."); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + //new CreateFont().show(sketch.dataFolder); + new CreateFont(Editor.this).show(); + } + }); + menu.add(item); + */ + + item = new JMenuItem("Archive Sketch"); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + new Archiver(Editor.this).show(); + //Archiver archiver = new Archiver(); + //archiver.setup(Editor.this); + //archiver.show(); + } + }); + menu.add(item); - //serialGroup = new ButtonGroup(); - populateSerialMenu(); - menu.add(serialSubMenu); + item = new JMenuItem("Export Folder..."); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + new ExportFolder(Editor.this).show(); + } + }); + menu.add(item); + menu.addSeparator(); - // End of The serial options + serialMenu = new JMenu("Serial Port"); + populateSerialMenu(); + menu.add(serialMenu); + + serialRateMenu = new JMenu("Serial Monitor Baud Rate"); - // menu.addSeparator(); - - // add the serial speed submenu - - serialRateSubMenu = new JMenu("Serial Monitor Baud Rate"); - - //serialSubMenu.add(item); - //serialSubMenu.addSeparator(); - ButtonGroup group = new ButtonGroup(); + ButtonGroup group = new ButtonGroup(); - String curr_rate = Preferences.get("serial.debug_rate"); + String curr_rate = Preferences.get("serial.debug_rate"); for (int i = 0; i < portRates.length; i++) { rbMenuItem = new JCheckBoxMenuItem(portRates[i], portRates[i].equals(curr_rate)); rbMenuItem.addActionListener(srml); group.add(rbMenuItem); - serialRateSubMenu.add(rbMenuItem); + serialRateMenu.add(rbMenuItem); } - menu.add(serialRateSubMenu); + menu.add(serialRateMenu); - menu.addSeparator(); + menu.addSeparator(); item = new JMenuItem("Burn Bootloader"); item.addActionListener(new ActionListener() { @@ -739,49 +730,114 @@ public class Editor extends JFrame public void menuCanceled(MenuEvent e) {} public void menuDeselected(MenuEvent e) {} public void menuSelected(MenuEvent e) { + //System.out.println("Tools menu selected."); populateSerialMenu(); } }); return menu; } + + // taken from an ancient version of processing + class SerialMenuListener implements ActionListener { + //public SerialMenuListener() { } + + public void actionPerformed(ActionEvent e) { + if(serialMenu == null) { + System.out.println("serialMenu is null"); + return; + } + int count = serialMenu.getItemCount(); + for (int i = 0; i < count; i++) { + ((JCheckBoxMenuItem)serialMenu.getItem(i)).setState(false); + } + + JCheckBoxMenuItem item = (JCheckBoxMenuItem)e.getSource(); + item.setState(true); + String name = item.getLabel(); + + //System.out.println(item.getLabel()); + Preferences.set("serial.port", name); + //System.out.println("set to " + get("serial.port")); + } + + /* + public void actionPerformed(ActionEvent e) { + System.out.println(e.getSource()); + String name = e.getActionCommand(); + PdeBase.properties.put("serial.port", name); + System.out.println("set to " + get("serial.port")); + //editor.skOpen(path + File.separator + name, name); + // need to push "serial.port" into PdeBase.properties + } + */ + } + + // manages the serial port speed menu + class SerialRateMenuListener implements ActionListener { + + SerialRateMenuListener() {} + + public void actionPerformed(ActionEvent actionevent) { + int count = serialRateMenu.getItemCount(); + Object to; + + for (int i = 0; i < count; i++) { + to = serialRateMenu.getItem(i); + if ( to instanceof JCheckBoxMenuItem) ((JCheckBoxMenuItem)serialRateMenu.getItem(i)).setState(false); + } + + JCheckBoxMenuItem item = (JCheckBoxMenuItem)actionevent.getSource(); + item.setState(true); + String name = item.getLabel(); + + Preferences.set("serial.debug_rate", name); + //System.out.println("serial port speed set to " + name); + } + + + } protected void populateSerialMenu() { - // getting list of ports + // getting list of ports JMenuItem rbMenuItem; - serialSubMenu.removeAll(); - - try - { - for (Enumeration enumeration = CommPortIdentifier.getPortIdentifiers(); enumeration.hasMoreElements();) - { - CommPortIdentifier commportidentifier = (CommPortIdentifier)enumeration.nextElement(); - if (commportidentifier.getPortType() == CommPortIdentifier.PORT_SERIAL) - { - String curr_port = commportidentifier.getName(); - rbMenuItem = new JCheckBoxMenuItem(curr_port, curr_port.equals(Preferences.get("serial.port"))); - rbMenuItem.addActionListener(serialMenuListener); - //serialGroup.add(rbMenuItem); - serialSubMenu.add(rbMenuItem); - } - } - - } - - catch (Exception exception) - { - System.out.println("error retrieving port list"); - exception.printStackTrace(); - } + //System.out.println("Clearing serial port menu."); - if (serialSubMenu.getItemCount() == 0) { - serialSubMenu.setEnabled(false); - } + serialMenu.removeAll(); - //serialSubMenu.addSeparator(); - //serialSubMenu.add(item); + try + { + for (Enumeration enumeration = CommPortIdentifier.getPortIdentifiers(); enumeration.hasMoreElements();) + { + CommPortIdentifier commportidentifier = (CommPortIdentifier)enumeration.nextElement(); + //System.out.println("Found communication port: " + commportidentifier); + if (commportidentifier.getPortType() == CommPortIdentifier.PORT_SERIAL) + { + //System.out.println("Adding port to serial port menu: " + commportidentifier); + String curr_port = commportidentifier.getName(); + rbMenuItem = new JCheckBoxMenuItem(curr_port, curr_port.equals(Preferences.get("serial.port"))); + rbMenuItem.addActionListener(serialMenuListener); + //serialGroup.add(rbMenuItem); + serialMenu.add(rbMenuItem); + } + } + + } + + catch (Exception exception) + { + System.out.println("error retrieving port list"); + exception.printStackTrace(); + } + + if (serialMenu.getItemCount() == 0) { + serialMenu.setEnabled(false); + } + + //serialMenu.addSeparator(); + //serialMenu.add(item); } @@ -882,7 +938,7 @@ public class Editor extends JFrame item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { textarea.cut(); - sketch.setModified(); + sketch.setModified(true); } }); menu.add(item); @@ -899,7 +955,7 @@ public class Editor extends JFrame item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { textarea.paste(); - sketch.setModified(); + sketch.setModified(true); } }); menu.add(item); @@ -917,7 +973,9 @@ public class Editor extends JFrame item = newJMenuItem("Find...", 'F'); item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { - find.show(); + new FindReplace(Editor.this).show(); + //find.show(); + //find.setVisible(true); } }); menu.add(item); @@ -927,6 +985,8 @@ public class Editor extends JFrame public void actionPerformed(ActionEvent e) { // TODO find next should only be enabled after a // search has actually taken place + //find.find(true); + FindReplace find = new FindReplace(Editor.this); //.show(); find.find(true); } }); @@ -986,11 +1046,17 @@ public class Editor extends JFrame undoItem.setEnabled(true); undoItem.setText(undo.getUndoPresentationName()); putValue(Action.NAME, undo.getUndoPresentationName()); + if (sketch != null) { + sketch.setModified(true); // 0107 + } } else { this.setEnabled(false); undoItem.setEnabled(false); undoItem.setText("Undo"); putValue(Action.NAME, "Undo"); + if (sketch != null) { + sketch.setModified(false); // 0107 + } } } } @@ -1015,7 +1081,7 @@ public class Editor extends JFrame protected void updateRedoState() { if (undo.canRedo()) { - this.setEnabled(true); + //this.setEnabled(true); redoItem.setEnabled(true); redoItem.setText(undo.getRedoPresentationName()); putValue(Action.NAME, undo.getRedoPresentationName()); @@ -1095,64 +1161,24 @@ public class Editor extends JFrame } - /** - * Called to update the text but not switch to a different - * set of code (which would affect the undo manager). - */ - //public void setText(String what) { //, boolean discardUndo) { - //setText(what, 0, 0); - //} - - /** * Called to update the text but not switch to a different * set of code (which would affect the undo manager). */ public void setText(String what, int selectionStart, int selectionEnd) { + beginCompoundEdit(); textarea.setText(what); + endCompoundEdit(); + + // make sure that a tool isn't asking for a bad location + selectionStart = + Math.max(0, Math.min(selectionStart, textarea.getDocumentLength())); + selectionEnd = + Math.max(0, Math.min(selectionStart, textarea.getDocumentLength())); textarea.select(selectionStart, selectionEnd); - textarea.requestFocus(); // get the caret blinking - } - - - /** - * Called by Sketch when the tab is changed or a new set of files are opened. - */ - /* - public void setText(String currentProgram, - int selectionStart, int selectionEnd, - UndoManager currentUndo) { - //System.out.println("setting text, changing undo"); - this.undo = null; - - //if (discardUndo) undo.discardAllEdits(); - - // don't set the undo object yet otherwise gets hokey - textarea.setText(currentProgram); - textarea.select(selectionStart, selectionEnd); - textarea.requestFocus(); // get the caret blinking - - this.undo = currentUndo; - undoAction.updateUndoState(); - redoAction.updateRedoState(); - } - */ - - /* - public void setDocument(SyntaxDocument document, - int selectionStart, int selectionStop, - int scrollPosition, UndoManager undo) { - - textarea.setDocument(document, selectionStart, selectionStop, - scrollPosition); textarea.requestFocus(); // get the caret blinking - - this.undo = undo; - undoAction.updateUndoState(); - redoAction.updateRedoState(); } - */ /** @@ -1179,7 +1205,10 @@ public class Editor extends JFrame // connect the undo listener to the editor code.document.addUndoableEditListener(new UndoableEditListener() { public void undoableEditHappened(UndoableEditEvent e) { - if (undo != null) { + if (compoundEdit != null) { + compoundEdit.addEdit(e.getEdit()); + + } else if (undo != null) { undo.addEdit(e.getEdit()); undoAction.updateUndoState(); redoAction.updateRedoState(); @@ -1200,12 +1229,24 @@ public class Editor extends JFrame redoAction.updateRedoState(); } + public void beginCompoundEdit() { + compoundEdit = new CompoundEdit(); + } + + public void endCompoundEdit() { + compoundEdit.end(); + undo.addEdit(compoundEdit); + undoAction.updateUndoState(); + redoAction.updateRedoState(); + compoundEdit = null; + } + public void handleRun(boolean present) { doClose(); running = true; - buttons.run(); + buttons.activate(EditorButtons.RUN); message("Compiling..."); // do this for the terminal window / dos prompt / etc for (int i = 0; i < 10; i++) System.out.println(); @@ -1291,17 +1332,15 @@ public class Editor extends JFrame } public void run() { - /* while (Thread.currentThread() == thread) { - if (runtime == null) { + /*if (runtime == null) { stop(); } else { - // FIXME remove dependance from core libs - //if (runtime.applet != null) { - // if (runtime.applet.finished) { - // stop(); - //} + if (runtime.applet != null) { + if (runtime.applet.finished) { + stop(); + } //buttons.running(!runtime.applet.finished); } else if (runtime.process != null) { @@ -1310,13 +1349,12 @@ public class Editor extends JFrame } else { stop(); } - } + }*/ try { Thread.sleep(250); } catch (InterruptedException e) { } //System.out.println("still inside runner thread"); } - */ } public void stop() { @@ -1329,6 +1367,7 @@ public class Editor extends JFrame public void handleSerial() { if (!debugging) { console.clear(); + buttons.activate(EditorButtons.SERIAL); serialPort = new Serial(true); debugging = true; } else { @@ -1343,6 +1382,7 @@ public class Editor extends JFrame } else { doStop(); } + buttons.clear(); } @@ -1350,11 +1390,11 @@ public class Editor extends JFrame * Stop the applet but don't kill its window. */ public void doStop() { + //if (runtime != null) runtime.stop(); if (debugging) { serialPort.dispose(); debugging = false; } - if (runtime != null) runtime.stop(); if (watcher != null) watcher.stop(); message(EMPTY); @@ -1371,32 +1411,31 @@ public class Editor extends JFrame * mode, this will always be called instead of doStop(). */ public void doClose() { - /* //if (presenting) { //presentationWindow.hide(); //} else { - try { + //try { // the window will also be null the process was running // externally. so don't even try setting if window is null // since Runner will set the appletLocation when an // external process is in use. - //if (runtime.window != null) { - appletLocation = runtime.window.getLocation(); - } - } catch (NullPointerException e) { } +// if (runtime.window != null) { +// appletLocation = runtime.window.getLocation(); +// } + //} catch (NullPointerException e) { } //} //if (running) doStop(); doStop(); // need to stop if runtime error - try { - if (runtime != null) { + //try { + /*if (runtime != null) { runtime.close(); // kills the window runtime = null; // will this help? - } - } catch (Exception e) { } + }*/ + //} catch (Exception e) { } //buttons.clear(); // done by doStop - */ + sketch.cleanup(); // [toxi 030903] @@ -1450,7 +1489,7 @@ public class Editor extends JFrame options[0]); if (result == JOptionPane.YES_OPTION) { - handleSave(); + handleSave(true); checkModified2(); } else if (result == JOptionPane.NO_OPTION) { @@ -1480,15 +1519,20 @@ public class Editor extends JFrame /** * New was called (by buttons or by menu), first check modified * and if things work out ok, handleNew2() will be called. - * + *

* If shift is pressed when clicking the toolbar button, then * force the opposite behavior from sketchbook.prompt's setting */ - public void handleNew(boolean shift) { - doStop(); - handleNewShift = shift; - handleNewLibrary = false; - checkModified(HANDLE_NEW); + public void handleNew(final boolean shift) { + buttons.activate(EditorButtons.NEW); + + SwingUtilities.invokeLater(new Runnable() { + public void run() { + doStop(); + handleNewShift = shift; + handleNewLibrary = false; + checkModified(HANDLE_NEW); + }}); } @@ -1539,6 +1583,7 @@ public class Editor extends JFrame "An error occurred while creating\n" + "a new sketch. Arduino must now quit.", e); } + buttons.clear(); } @@ -1556,15 +1601,22 @@ public class Editor extends JFrame * Open a sketch given the full path to the .pde file. * Pass in 'null' to prompt the user for the name of the sketch. */ - public void handleOpen(String path) { - if (path == null) { // "open..." selected from the menu - path = sketchbook.handleOpen(); - if (path == null) return; - } - doClose(); - //doStop(); - handleOpenPath = path; - checkModified(HANDLE_OPEN); + public void handleOpen(final String ipath) { + // haven't run across a case where i can verify that this works + // because open is usually very fast. + buttons.activate(EditorButtons.OPEN); + + SwingUtilities.invokeLater(new Runnable() { + public void run() { + String path = ipath; + if (path == null) { // "open..." selected from the menu + path = sketchbook.handleOpen(); + if (path == null) return; + } + doClose(); + handleOpenPath = path; + checkModified(HANDLE_OPEN); + }}); } @@ -1692,7 +1744,32 @@ public class Editor extends JFrame // there is no handleSave1 since there's never a need to prompt - public void handleSave() { + /** + * Actually handle the save command. If 'force' is set to false, + * this will happen in another thread so that the message area + * will update and the save button will stay highlighted while the + * save is happening. If 'force' is true, then it will happen + * immediately. This is used during a quit, because invokeLater() + * won't run properly while a quit is happening. This fixes + * Bug 276. + */ + public void handleSave(boolean force) { + doStop(); + buttons.activate(EditorButtons.SAVE); + + if (force) { + handleSave2(); + } else { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + handleSave2(); + } + }); + } + } + + + public void handleSave2() { message("Saving..."); try { if (sketch.save()) { @@ -1718,21 +1795,24 @@ public class Editor extends JFrame public void handleSaveAs() { doStop(); + buttons.activate(EditorButtons.SAVE); - message("Saving..."); - try { - if (sketch.saveAs()) { - message("Done Saving."); - sketchbook.rebuildMenus(); - } else { - message("Save Cancelled."); - } - - } catch (Exception e) { - // show the error as a message in the window - error(e); - } - buttons.clear(); + SwingUtilities.invokeLater(new Runnable() { + public void run() { + message("Saving..."); + try { + if (sketch.saveAs()) { + message("Done Saving."); + sketchbook.rebuildMenus(); + } else { + message("Save Cancelled."); + } + } catch (Exception e) { + // show the error as a message in the window + error(e); + } + buttons.clear(); + }}); } @@ -1746,37 +1826,86 @@ public class Editor extends JFrame synchronized public void handleExport() { if(debugging) doStop(); + buttons.activate(EditorButtons.EXPORT); console.clear(); //String what = sketch.isLibrary() ? "Applet" : "Library"; //message("Exporting " + what + "..."); message("Uploading to I/O Board..."); + SwingUtilities.invokeLater(new Runnable() { + public void run() { + try { + //boolean success = sketch.isLibrary() ? + //sketch.exportLibrary() : sketch.exportApplet(); + boolean success = sketch.exportApplet(new Target( + System.getProperty("user.dir") + File.separator + "lib" + + File.separator + "targets", Preferences.get("build.target"))); + if (success) { + message("Done uploading."); + } else { + // error message will already be visible + } + } catch (RunnerException e) { + message("Error during upload."); + //e.printStackTrace(); + error(e); + } catch (Exception e) { + e.printStackTrace(); + } + buttons.clear(); + }}); + } + + + synchronized public void handleExportApp() { + message("Exporting application..."); try { - //boolean success = sketch.isLibrary() ? - //sketch.exportLibrary() : sketch.exportApplet(); - boolean success = sketch.exportApplet(new Target( - System.getProperty("user.dir") + File.separator + "lib" + - File.separator + "targets", Preferences.get("build.target"))); - if (success) { - message("Done uploading."); + if (sketch.exportApplication()) { + message("Done exporting."); } else { // error message will already be visible } - } catch (RunnerException e) { - message("Error during upload."); - //e.printStackTrace(); - error(e); } catch (Exception e) { + message("Error during export."); e.printStackTrace(); } buttons.clear(); } - synchronized public void handleExportApp() { - message("Arduino - Export - app"); + /** + * Checks to see if the sketch has been modified, and if so, + * asks the user to save the sketch or cancel the export. + * This prevents issues where an incomplete version of the sketch + * would be exported, and is a fix for + * Bug 157 + */ + public boolean handleExportCheckModified() { + if (!sketch.modified) return true; + + Object[] options = { "OK", "Cancel" }; + int result = JOptionPane.showOptionDialog(this, + "Save changes before export?", + "Save", + JOptionPane.OK_CANCEL_OPTION, + JOptionPane.QUESTION_MESSAGE, + null, + options, + options[0]); + if (result == JOptionPane.OK_OPTION) { + handleSave(true); + + } else { + // why it's not CANCEL_OPTION is beyond me (at least on the mac) + // but f-- it.. let's get this shite done.. + //} else if (result == JOptionPane.CANCEL_OPTION) { + message("Export canceled, changes must first be saved."); + buttons.clear(); + return false; + } + return true; } - + /** * Quit, but first ask user if it's ok. Also store preferences * to disk just in case they want to quit. Final exit() happens @@ -1799,6 +1928,7 @@ public class Editor extends JFrame Preferences.save(); sketchbook.clean(); + console.handleQuit(); //System.out.println("exiting here"); System.exit(0); @@ -1887,6 +2017,14 @@ public class Editor extends JFrame // ................................................................... + /** + * Show an error int the status bar. + */ + public void error(String what) { + status.error(what); + } + + public void error(Exception e) { if (e == null) { System.err.println("Editor.error() was passed a null exception."); @@ -1905,7 +2043,7 @@ public class Editor extends JFrame if (mess.indexOf(javaLang) == 0) { mess = mess.substring(javaLang.length()); } - status.error(mess); + error(mess); } e.printStackTrace(); } @@ -1923,37 +2061,23 @@ public class Editor extends JFrame String rxString = "RuntimeException: "; if (mess.indexOf(rxString) == 0) { mess = mess.substring(rxString.length()); + //System.out.println("MESS3: " + mess); } String javaLang = "java.lang."; if (mess.indexOf(javaLang) == 0) { mess = mess.substring(javaLang.length()); } - status.error(mess); - - buttons.clearRun(); + error(mess); + buttons.clear(); } - /* - public void finished() { - running = false; - buttons.clearRun(); - message("Done."); - } - */ - public void message(String msg) { status.notice(msg); } - /* - public void messageClear(String msg) { - status.unnotice(msg); - } - */ - // ................................................................... @@ -1962,7 +2086,6 @@ public class Editor extends JFrame * Returns the edit popup menu. */ class TextAreaPopup extends JPopupMenu { - //protected ReferenceKeys referenceItems = new ReferenceKeys(); String currentDir = System.getProperty("user.dir"); String referenceFile = null; @@ -1977,6 +2100,7 @@ public class Editor extends JFrame cutItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { textarea.cut(); + sketch.setModified(true); } }); this.add(cutItem); @@ -1993,6 +2117,7 @@ public class Editor extends JFrame item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { textarea.paste(); + sketch.setModified(true); } }); this.add(item); diff --git a/app/EditorButtons.java b/app/EditorButtons.java index 3aa995de3..3867d271a 100644 --- a/app/EditorButtons.java +++ b/app/EditorButtons.java @@ -41,9 +41,11 @@ public class EditorButtons extends JComponent implements MouseInputListener { }; static final int BUTTON_COUNT = title.length; - static final int BUTTON_WIDTH = 27; //Preferences.GRID_SIZE; - static final int BUTTON_HEIGHT = 32; //Preferences.GRID_SIZE; - static final int BUTTON_GAP = 15; //BUTTON_WIDTH / 2; + /// height, width of the toolbar buttons + static final int BUTTON_WIDTH = 27; + static final int BUTTON_HEIGHT = 32; + /// amount of space between groups of buttons on the toolbar + static final int BUTTON_GAP = 15; static final int RUN = 0; static final int STOP = 1; @@ -59,7 +61,7 @@ public class EditorButtons extends JComponent implements MouseInputListener { static final int ACTIVE = 2; Editor editor; - boolean disableRun; + //boolean disableRun; //Label status; Image offscreen; @@ -72,7 +74,7 @@ public class EditorButtons extends JComponent implements MouseInputListener { Image rollover[]; Image active[]; int currentRollover; - int currentSelection; + //int currentSelection; JPopupMenu popup; @@ -112,7 +114,7 @@ public class EditorButtons extends JComponent implements MouseInputListener { // see EditorStatus.java for details. //bgcolor = Preferences.getColor("buttons.bgcolor"); bgcolor = new Color(0x04, 0x4F, 0x6F); - + status = ""; statusFont = Preferences.getFont("buttons.status.font"); @@ -234,10 +236,10 @@ public class EditorButtons extends JComponent implements MouseInputListener { if (sel == -1) return; if (state[sel] != ACTIVE) { - if (!(disableRun && ((sel == RUN) || (sel == STOP)))) { - setState(sel, ROLLOVER, true); - currentRollover = sel; - } + //if (!(disableRun && ((sel == RUN) || (sel == STOP)))) { + setState(sel, ROLLOVER, true); + currentRollover = sel; + //} } } @@ -284,6 +286,10 @@ public class EditorButtons extends JComponent implements MouseInputListener { public void mouseExited(MouseEvent e) { + // if the popup menu for is visible, don't register this, + // because the popup being set visible will fire a mouseExited() event + if ((popup != null) && popup.isVisible()) return; + if (state[OPEN] != INACTIVE) { setState(OPEN, INACTIVE, true); } @@ -295,27 +301,78 @@ public class EditorButtons extends JComponent implements MouseInputListener { public void mousePressed(MouseEvent e) { - int x = e.getX(); - int y = e.getY(); + final int x = e.getX(); + final int y = e.getY(); int sel = findSelection(x, y); ///if (sel == -1) return false; if (sel == -1) return; currentRollover = -1; - currentSelection = sel; - if (!(disableRun && ((sel == RUN) || (sel == STOP)))) { - setState(sel, ACTIVE, true); - } + //int currentSelection = sel; + //if (!(disableRun && ((sel == RUN) || (sel == STOP)))) { + // moving the handling of this over into the editor + //setState(sel, ACTIVE, true); + //} - if (currentSelection == OPEN) { + //if (currentSelection == OPEN) { + //switch (currentSelection) { + switch (sel) { + case RUN: + //if (!disableRun) { + editor.handleRun(e.isShiftDown()); + //} + break; + + case STOP: + //if (!disableRun) { + //setState(RUN, INACTIVE, true); + //setInactive(); + editor.handleStop(); + //} + break; + + case OPEN: if (popup == null) { //popup = new JPopupMenu(); popup = editor.sketchbook.getPopupMenu(); + // no events properly being fired, so nevermind + /* + popup.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + System.out.println("action " + e); + } + }); + popup.addComponentListener(new ComponentAdapter() { + public void componentHidden(ComponentEvent e) { + System.out.println("hidden " + e); + } + }); + */ add(popup); } - //editor.sketchbook.rebuildPopup(popup); - popup.show(this, x, y); - } + //activate(OPEN); + //SwingUtilities.invokeLater(new Runnable() { + //public void run() { + popup.show(EditorButtons.this, x, y); + //}}); + break; + + case NEW: + editor.handleNew(e.isShiftDown()); + break; + + case SAVE: + editor.handleSave(false); + break; + + case EXPORT: + editor.handleExport(); + break; + + case SERIAL: + editor.handleSerial(); + break; + } } @@ -323,6 +380,7 @@ public class EditorButtons extends JComponent implements MouseInputListener { public void mouseReleased(MouseEvent e) { + /* switch (currentSelection) { case RUN: if (!disableRun) { @@ -344,14 +402,47 @@ public class EditorButtons extends JComponent implements MouseInputListener { case SERIAL: editor.handleSerial(); break; } currentSelection = -1; + */ } - public void disableRun(boolean what) { - disableRun = what; + //public void disableRun(boolean what) { + //disableRun = what; + //} + + + /* + public void run() { + if (inactive == null) return; + clear(); + setState(RUN, ACTIVE, true); + } + */ + + + public void running(boolean yesno) { + setState(RUN, yesno ? ACTIVE : INACTIVE, true); } + /** + * Set a particular button to be active. + */ + public void activate(int what) { + if (inactive == null) return; + setState(what, ACTIVE, true); + } + + + //public void clearRun() { + //if (inactive == null) return; + //setState(RUN, INACTIVE, true); + //} + + + /** + * Clear all the state of all buttons. + */ public void clear() { // (int button) { if (inactive == null) return; @@ -363,24 +454,6 @@ public class EditorButtons extends JComponent implements MouseInputListener { } - public void run() { - if (inactive == null) return; - clear(); - setState(RUN, ACTIVE, true); - } - - - public void running(boolean yesno) { - setState(RUN, yesno ? ACTIVE : INACTIVE, true); - } - - - public void clearRun() { - if (inactive == null) return; - setState(RUN, INACTIVE, true); - } - - public void message(String msg) { //status.setText(msg + " "); // don't mind the hack status = msg; diff --git a/app/EditorConsole.java b/app/EditorConsole.java index 5c451295b..27a5abaee 100644 --- a/app/EditorConsole.java +++ b/app/EditorConsole.java @@ -28,7 +28,7 @@ import java.awt.event.*; import java.io.*; import javax.swing.*; import javax.swing.text.*; - +import java.util.*; /** * Message console that sits below the editing area. @@ -41,7 +41,7 @@ public class EditorConsole extends JScrollPane { Editor editor; JTextPane consoleTextPane; - StyledDocument consoleDoc; + BufferedStyledDocument consoleDoc; MutableAttributeSet stdStyle; MutableAttributeSet errStyle; @@ -51,6 +51,10 @@ public class EditorConsole extends JScrollPane { //int maxCharCount; int maxLineCount; + static File errFile; + static File outFile; + static File tempFolder; + static PrintStream systemOut; static PrintStream systemErr; @@ -66,9 +70,9 @@ public class EditorConsole extends JScrollPane { maxLineCount = Preferences.getInteger("console.length"); - consoleTextPane = new JTextPane(); + consoleDoc = new BufferedStyledDocument(10000, maxLineCount); + consoleTextPane = new JTextPane(consoleDoc); consoleTextPane.setEditable(false); - consoleDoc = consoleTextPane.getStyledDocument(); // necessary? MutableAttributeSet standard = new SimpleAttributeSet(); @@ -115,15 +119,20 @@ public class EditorConsole extends JScrollPane { systemOut = System.out; systemErr = System.err; + tempFolder = Base.createTempFolder("console"); try { String outFileName = Preferences.get("console.output.file"); if (outFileName != null) { - stdoutFile = new FileOutputStream(outFileName); + outFile = new File(tempFolder, outFileName); + stdoutFile = new FileOutputStream(outFile); + //outFile.deleteOnExit(); } String errFileName = Preferences.get("console.error.file"); if (errFileName != null) { - stderrFile = new FileOutputStream(outFileName); + errFile = new File(tempFolder, errFileName); + stderrFile = new FileOutputStream(errFile); + //errFile.deleteOnExit(); } } catch (IOException e) { Base.showWarning("Console Error", @@ -151,6 +160,52 @@ public class EditorConsole extends JScrollPane { if (Base.isMacOS()) { setBorder(null); } + + // periodically post buffered messages to the console + // should the interval come from the preferences file? + new javax.swing.Timer(250, new ActionListener() { + public void actionPerformed(ActionEvent evt) { + // only if new text has been added + if (consoleDoc.hasAppendage) { + // insert the text that's been added in the meantime + consoleDoc.insertAll(); + // always move to the end of the text as it's added + consoleTextPane.setCaretPosition(consoleDoc.getLength()); + } + } + }).start(); + } + + + /** + * Close the streams so that the temporary files can be deleted. + *

+ * File.deleteOnExit() cannot be used because the stdout and stderr + * files are inside a folder, and have to be deleted before the + * folder itself is deleted, which can't be guaranteed when using + * the deleteOnExit() method. + */ + public void handleQuit() { + // replace original streams to remove references to console's streams + System.setOut(systemOut); + System.setErr(systemErr); + + // close the PrintStream + consoleOut.close(); + consoleErr.close(); + + // also have to close the original FileOutputStream + // otherwise it won't be shut down completely + try { + stdoutFile.close(); + stderrFile.close(); + } catch (IOException e) { + e.printStackTrace(systemOut); + } + + outFile.delete(); + errFile.delete(); + tempFolder.delete(); } @@ -174,10 +229,8 @@ public class EditorConsole extends JScrollPane { synchronized public void message(String what, boolean err, boolean advance) { if (err) { systemErr.print(what); - //systemErr.print("CE" + what); } else { systemOut.print(what); - //systemOut.print("CO" + what); } if (advance) { @@ -204,66 +257,13 @@ public class EditorConsole extends JScrollPane { * and eventually leads to EditorConsole.appendText(), which directly * updates the Swing text components, causing deadlock. *

- * A quick hack from Francis Li (who found this to be a problem) - * wraps the contents of appendText() into a Runnable and uses - * SwingUtilities.invokeLater() to ensure that the updates only - * occur on the main event dispatching thread, and that appears - * to have solved the problem. - *

- * unfortunately this is probably extremely slow and helping cause - * some of the general print() and println() mess.. need to fix - * up so that it's using a proper queue instead. + * Updates are buffered to the console and displayed at regular + * intervals on Swing's event-dispatching thread. (patch by David Mellis) */ synchronized private void appendText(String txt, boolean e) { - final String text = txt; - final boolean err = e; - SwingUtilities.invokeLater(new Runnable() { - public void run() { - try { - // check how many lines have been used so far - // if too many, shave off a few lines from the beginning - Element element = consoleDoc.getDefaultRootElement(); - int lineCount = element.getElementCount(); - int overage = lineCount - maxLineCount; - if (overage > 0) { - // if 1200 lines, and 1000 lines is max, - // find the position of the end of the 200th line - //systemOut.println("overage is " + overage); - Element lineElement = element.getElement(overage); - if (lineElement == null) return; // do nuthin - - int endOffset = lineElement.getEndOffset(); - // remove to the end of the 200th line - consoleDoc.remove(0, endOffset); - } - - // make sure this line doesn't go over 32k chars - lineCount = element.getElementCount(); // may have changed - Element currentElement = element.getElement(lineCount-1); - int currentStart = currentElement.getStartOffset(); - int currentEnd = currentElement.getEndOffset(); - //systemOut.println(currentEnd - currentStart); - if (currentEnd - currentStart > 10000) { // force a newline - consoleDoc.insertString(consoleDoc.getLength(), "\n", - err ? errStyle : stdStyle); - } - - // add the text to the end of the console, - consoleDoc.insertString(consoleDoc.getLength(), text, - err ? errStyle : stdStyle); - - // always move to the end of the text as it's added - consoleTextPane.setCaretPosition(consoleDoc.getLength()); - - } catch (BadLocationException e) { - // ignore the error otherwise this will cause an infinite loop - // maybe not a good idea in the long run? - } - } - }); + consoleDoc.appendString(txt, e ? errStyle : stdStyle); } - public void clear() { try { consoleDoc.remove(0, consoleDoc.getLength()); @@ -332,3 +332,87 @@ class EditorConsoleStream extends OutputStream { } } } + + +/** + * Buffer updates to the console and output them in batches. For info, see: + * http://java.sun.com/products/jfc/tsc/articles/text/element_buffer and + * http://javatechniques.com/public/java/docs/gui/jtextpane-speed-part2.html + * appendString() is called from multiple threads, and insertAll from the + * swing event thread, so they need to be synchronized + */ +class BufferedStyledDocument extends DefaultStyledDocument { + ArrayList elements = new ArrayList(); + int maxLineLength, maxLineCount; + int currentLineLength = 0; + boolean needLineBreak = false; + boolean hasAppendage = false; + + public BufferedStyledDocument(int maxLineLength, int maxLineCount) { + this.maxLineLength = maxLineLength; + this.maxLineCount = maxLineCount; + } + + /** buffer a string for insertion at the end of the DefaultStyledDocument */ + public synchronized void appendString(String str, AttributeSet a) { + // do this so that it's only updated when needed (otherwise console + // updates every 250 ms when an app isn't even running.. see bug 180) + hasAppendage = true; + + // process each line of the string + while (str.length() > 0) { + // newlines within an element have (almost) no effect, so we need to + // replace them with proper paragraph breaks (start and end tags) + if (needLineBreak || currentLineLength > maxLineLength) { + elements.add(new ElementSpec(a, ElementSpec.EndTagType)); + elements.add(new ElementSpec(a, ElementSpec.StartTagType)); + currentLineLength = 0; + } + + if (str.indexOf('\n') == -1) { + elements.add(new ElementSpec(a, ElementSpec.ContentType, + str.toCharArray(), 0, str.length())); + currentLineLength += str.length(); + needLineBreak = false; + str = str.substring(str.length()); // eat the string + } else { + elements.add(new ElementSpec(a, ElementSpec.ContentType, + str.toCharArray(), 0, str.indexOf('\n') + 1)); + needLineBreak = true; + str = str.substring(str.indexOf('\n') + 1); // eat the line + } + } + } + + /** insert the buffered strings */ + public synchronized void insertAll() { + ElementSpec[] elementArray = new ElementSpec[elements.size()]; + elements.toArray(elementArray); + + try { + // check how many lines have been used so far + // if too many, shave off a few lines from the beginning + Element element = super.getDefaultRootElement(); + int lineCount = element.getElementCount(); + int overage = lineCount - maxLineCount; + if (overage > 0) { + // if 1200 lines, and 1000 lines is max, + // find the position of the end of the 200th line + //systemOut.println("overage is " + overage); + Element lineElement = element.getElement(overage); + if (lineElement == null) return; // do nuthin + + int endOffset = lineElement.getEndOffset(); + // remove to the end of the 200th line + super.remove(0, endOffset); + } + super.insert(super.getLength(), elementArray); + + } catch (BadLocationException e) { + // ignore the error otherwise this will cause an infinite loop + // maybe not a good idea in the long run? + } + elements.clear(); + hasAppendage = false; + } +} diff --git a/app/EditorHeader.java b/app/EditorHeader.java index 45e43cc6f..2fc60c90f 100644 --- a/app/EditorHeader.java +++ b/app/EditorHeader.java @@ -339,8 +339,10 @@ public class EditorHeader extends JComponent { Sketch sketch = editor.sketch; if (sketch != null) { for (int i = 0; i < sketch.hiddenCount; i++) { - item = new JMenuItem(sketch.hidden[i].name); - item.setActionCommand(sketch.hidden[i].name); + item = new JMenuItem(sketch.hidden[i].name + + Sketch.flavorExtensionsShown[sketch.hidden[i].flavor]); + item.setActionCommand(sketch.hidden[i].name + + Sketch.flavorExtensionsShown[sketch.hidden[i].flavor]); item.addActionListener(unhideListener); unhide.add(item); } @@ -360,7 +362,8 @@ public class EditorHeader extends JComponent { } }; for (int i = 0; i < sketch.codeCount; i++) { - item = new JMenuItem(sketch.code[i].name); + item = new JMenuItem(sketch.code[i].name + + Sketch.flavorExtensionsShown[sketch.code[i].flavor]); item.addActionListener(jumpListener); menu.add(item); } diff --git a/app/EditorListener.java b/app/EditorListener.java index 53f21d57a..8f6c41ad7 100644 --- a/app/EditorListener.java +++ b/app/EditorListener.java @@ -35,13 +35,25 @@ import javax.swing.event.*; /** * Filters key events for tab expansion/indent/etc. + *

+ * For version 0099, some changes have been made to make the indents + * smarter. There are still issues though: + * + indent happens when it picks up a curly brace on the previous line, + * but not if there's a blank line between them. + * + It also doesn't handle single indent situations where a brace + * isn't used (i.e. an if statement or for loop that's a single line). + * It shouldn't actually be using braces. + * Solving these issues, however, would probably best be done by a + * smarter parser/formatter, rather than continuing to hack this class. */ public class EditorListener { Editor editor; JEditTextArea textarea; boolean externalEditor; - boolean expandTabs; + boolean tabsExpand; + boolean tabsIndent; + int tabSize; String tabString; boolean autoIndent; @@ -61,8 +73,9 @@ public class EditorListener { public void applyPreferences() { - expandTabs = Preferences.getBoolean("editor.tabs.expand"); - int tabSize = Preferences.getInteger("editor.tabs.size"); + tabsExpand = Preferences.getBoolean("editor.tabs.expand"); + //tabsIndent = Preferences.getBoolean("editor.tabs.indent"); + tabSize = Preferences.getInteger("editor.tabs.size"); tabString = Editor.EMPTY.substring(0, tabSize); autoIndent = Preferences.getBoolean("editor.indent"); externalEditor = Preferences.getBoolean("editor.external"); @@ -74,7 +87,13 @@ public class EditorListener { //} - // called by JEditTextArea inside processKeyEvent + /** + * Intercepts key pressed events for JEditTextArea. + *

+ * Called by JEditTextArea inside processKeyEvent(). Note that this + * won't intercept actual characters, because those are fired on + * keyTyped(). + */ public boolean keyPressed(KeyEvent event) { // don't do things if the textarea isn't editable if (externalEditor) return false; @@ -92,21 +111,148 @@ public class EditorListener { } // TODO i don't like these accessors. clean em up later. - if (!editor.sketch.current.modified) { + if (!editor.sketch.modified) { if ((code == KeyEvent.VK_BACK_SPACE) || (code == KeyEvent.VK_TAB) || (code == KeyEvent.VK_ENTER) || ((c >= 32) && (c < 128))) { - editor.sketch.setModified(); + editor.sketch.setModified(true); } } + if ((code == KeyEvent.VK_UP) && + ((event.getModifiers() & KeyEvent.CTRL_MASK) != 0)) { + // back up to the last empty line + char contents[] = textarea.getText().toCharArray(); + //int origIndex = textarea.getCaretPosition() - 1; + int caretIndex = textarea.getCaretPosition(); + + int index = calcLineStart(caretIndex - 1, contents); + //System.out.println("line start " + (int) contents[index]); + index -= 2; // step over the newline + //System.out.println((int) contents[index]); + boolean onlySpaces = true; + while (index > 0) { + if (contents[index] == 10) { + if (onlySpaces) { + index++; + break; + } else { + onlySpaces = true; // reset + } + } else if (contents[index] != ' ') { + onlySpaces = false; + } + index--; + } + // if the first char, index will be -2 + if (index < 0) index = 0; + + if ((event.getModifiers() & KeyEvent.SHIFT_MASK) != 0) { + textarea.setSelectionStart(caretIndex); + textarea.setSelectionEnd(index); + } else { + textarea.setCaretPosition(index); + } + event.consume(); + return true; + + } else if ((code == KeyEvent.VK_DOWN) && + ((event.getModifiers() & KeyEvent.CTRL_MASK) != 0)) { + char contents[] = textarea.getText().toCharArray(); + int caretIndex = textarea.getCaretPosition(); + + int index = caretIndex; + int lineStart = 0; + boolean onlySpaces = false; // don't count this line + while (index < contents.length) { + if (contents[index] == 10) { + if (onlySpaces) { + index = lineStart; // this is it + break; + } else { + lineStart = index + 1; + onlySpaces = true; // reset + } + } else if (contents[index] != ' ') { + onlySpaces = false; + } + index++; + } + // if the first char, index will be -2 + //if (index < 0) index = 0; + + //textarea.setSelectionStart(index); + //textarea.setSelectionEnd(index); + if ((event.getModifiers() & KeyEvent.SHIFT_MASK) != 0) { + textarea.setSelectionStart(caretIndex); + textarea.setSelectionEnd(index); + } else { + textarea.setCaretPosition(index); + } + event.consume(); + return true; + } + + switch ((int) c) { case 9: // expand tabs - if (expandTabs) { - //tc.replaceSelection(tabString); + if (tabsExpand) { textarea.setSelectedText(tabString); event.consume(); return true; + + } else if (tabsIndent) { + // this code is incomplete + + // if this brace is the only thing on the line, outdent + //char contents[] = getCleanedContents(); + char contents[] = textarea.getText().toCharArray(); + // index to the character to the left of the caret + int prevCharIndex = textarea.getCaretPosition() - 1; + + // now find the start of this line + int lineStart = calcLineStart(prevCharIndex, contents); + + int lineEnd = lineStart; + while ((lineEnd < contents.length - 1) && + (contents[lineEnd] != 10)) { + lineEnd++; + } + + // get the number of braces, to determine whether this is an indent + int braceBalance = 0; + int index = lineStart; + while ((index < contents.length) && + (contents[index] != 10)) { + if (contents[index] == '{') { + braceBalance++; + } else if (contents[index] == '}') { + braceBalance--; + } + index++; + } + + // if it's a starting indent, need to ignore it, so lineStart + // will be the counting point. but if there's a closing indent, + // then the lineEnd should be used. + int where = (braceBalance > 0) ? lineStart : lineEnd; + int indent = calcBraceIndent(where, contents); + if (indent == -1) { + // no braces to speak of, do nothing + indent = 0; + } else { + indent += tabSize; + } + + // and the number of spaces it has + int spaceCount = calcSpaceCount(prevCharIndex, contents); + + textarea.setSelectionStart(lineStart); + textarea.setSelectionEnd(lineStart + spaceCount); + textarea.setSelectedText(Editor.EMPTY.substring(0, indent)); + + event.consume(); + return true; } break; @@ -115,13 +261,18 @@ public class EditorListener { if (autoIndent) { char contents[] = textarea.getText().toCharArray(); - // this is the position of the caret, if the textarea - // only used a single kind of line ending + // this is the previous character + // (i.e. when you hit return, it'll be the last character + // just before where the newline will be inserted) int origIndex = textarea.getCaretPosition() - 1; + // NOTE all this cursing about CRLF stuff is probably moot + // NOTE since the switch to JEditTextArea, which seems to use + // NOTE only LFs internally (thank god). disabling for 0099. // walk through the array to the current caret position, // and count how many weirdo windows line endings there are, // which would be throwing off the caret position number + /* int offset = 0; int realIndex = origIndex; for (int i = 0; i < realIndex-1; i++) { @@ -130,38 +281,140 @@ public class EditorListener { realIndex++; } } - // back up until \r \r\n or \n.. @#($* cross platform - //System.out.println(origIndex + " offset = " + offset); origIndex += offset; // ARGH!#(* WINDOWS#@($* + */ - int index = origIndex; - int spaceCount = 0; - boolean finished = false; - while ((index != -1) && (!finished)) { - if ((contents[index] == 10) || - (contents[index] == 13)) { - finished = true; - index++; // maybe ? - } else { - index--; // new + int spaceCount = calcSpaceCount(origIndex, contents); + //int origCount = spaceCount; + + // now before inserting this many spaces, walk forward from + // the caret position, so that the number of spaces aren't + // just being duplicated again + int index = origIndex + 1; + int extraCount = 0; + while ((index < contents.length) && + (contents[index] == ' ')) { + //spaceCount--; + extraCount++; + index++; + } + + // hitting return on a line with spaces *after* the caret + // can cause trouble. for simplicity's sake, just ignore this case. + //if (spaceCount < 0) spaceCount = origCount; + if (spaceCount - extraCount > 0) { + spaceCount -= extraCount; + } + + // if the last character was a left curly brace, then indent + if (origIndex != -1) { + if (contents[origIndex] == '{') { + spaceCount += tabSize; } } + + String insertion = "\n" + Editor.EMPTY.substring(0, spaceCount); + textarea.setSelectedText(insertion); + + // mark this event as already handled + event.consume(); + return true; + } + break; + + case '}': + if (autoIndent) { + // first remove anything that was there (in case this multiple + // characters are selected, so that it's not in the way of the + // spaces for the auto-indent + if (textarea.getSelectionStart() != textarea.getSelectionEnd()) { + textarea.setSelectedText(""); + } + + // if this brace is the only thing on the line, outdent + char contents[] = textarea.getText().toCharArray(); + // index to the character to the left of the caret + int prevCharIndex = textarea.getCaretPosition() - 1; + + // backup from the current caret position to the last newline, + // checking for anything besides whitespace along the way. + // if there's something besides whitespace, exit without + // messing any sort of indenting. + int index = prevCharIndex; + boolean finished = false; + while ((index != -1) && (!finished)) { + if (contents[index] == 10) { + finished = true; + index++; + } else if (contents[index] != ' ') { + // don't do anything, this line has other stuff on it + return false; + } else { + index--; + } + } + if (!finished) return false; // brace with no start + int lineStartIndex = index; + + /* + // now that we know things are ok to be indented, walk + // backwards to the last { to see how far its line is indented. + // this isn't perfect cuz it'll pick up commented areas, + // but that's not really a big deal and can be fixed when + // this is all given a more complete (proper) solution. + index = prevCharIndex; + int braceDepth = 1; + finished = false; + while ((index != -1) && (!finished)) { + if (contents[index] == '}') { + // aww crap, this means we're one deeper + // and will have to find one more extra { + braceDepth++; + index--; + } else if (contents[index] == '{') { + braceDepth--; + if (braceDepth == 0) { + finished = true; + } // otherwise just teasing, keep going.. + } else { + index--; + } + } + // never found a proper brace, be safe and don't do anything + if (!finished) return false; + + // check how many spaces on the line with the matching open brace + int pairedSpaceCount = calcSpaceCount(index, contents); + //System.out.println(pairedSpaceCount); + */ + int pairedSpaceCount = calcBraceIndent(prevCharIndex, contents); //, 1); + if (pairedSpaceCount == -1) return false; + + /* + // now walk forward and figure out how many spaces there are while ((index < contents.length) && (index >= 0) && (contents[index++] == ' ')) { spaceCount++; } + */ - // seems that \r is being inserted anyway - // so no need to insert the platform's line separator - String insertion = "\n" + Editor.EMPTY.substring(0, spaceCount); - //tc.replaceSelection(insertion); - textarea.setSelectedText(insertion); - // microsoft vm version: - //tc.setCaretPosition(oldCarrot + insertion.length() - 1); - // sun vm version: - // tc.setCaretPosition(oldCarrot + insertion.length()); + // number of spaces found on this line + //int newSpaceCount = Math.max(0, spaceCount - tabSize); + // number of spaces on this current line + //int spaceCount = calcSpaces(caretIndex, contents); + //System.out.println("spaces is " + spaceCount); + //String insertion = "\n" + Editor.EMPTY.substring(0, spaceCount); + //int differential = newSpaceCount - spaceCount; + //System.out.println("diff is " + differential); + //int newStart = textarea.getSelectionStart() + differential; + //textarea.setSelectionStart(newStart); + //textarea.setSelectedText("}"); + textarea.setSelectionStart(lineStartIndex); + textarea.setSelectedText(Editor.EMPTY.substring(0, pairedSpaceCount)); + + // mark this event as already handled event.consume(); return true; } @@ -169,4 +422,143 @@ public class EditorListener { } return false; } + + + /** + * Return the index for the first character on this line. + */ + protected int calcLineStart(int index, char contents[]) { + // backup from the current caret position to the last newline, + // so that we can figure out how far this line was indented + int spaceCount = 0; + boolean finished = false; + while ((index != -1) && (!finished)) { + if ((contents[index] == 10) || + (contents[index] == 13)) { + finished = true; + //index++; // maybe ? + } else { + index--; // new + } + } + // add one because index is either -1 (the start of the document) + // or it's the newline character for the previous line + return index + 1; + } + + + /** + * Calculate the number of spaces on this line. + */ + protected int calcSpaceCount(int index, char contents[]) { + index = calcLineStart(index, contents); + + int spaceCount = 0; + // now walk forward and figure out how many spaces there are + while ((index < contents.length) && (index >= 0) && + (contents[index++] == ' ')) { + spaceCount++; + } + return spaceCount; + } + + + /** + * Walk back from 'index' until the brace that seems to be + * the beginning of the current block, and return the number of + * spaces found on that line. + */ + protected int calcBraceIndent(int index, char contents[]) { + // now that we know things are ok to be indented, walk + // backwards to the last { to see how far its line is indented. + // this isn't perfect cuz it'll pick up commented areas, + // but that's not really a big deal and can be fixed when + // this is all given a more complete (proper) solution. + int braceDepth = 1; + boolean finished = false; + while ((index != -1) && (!finished)) { + if (contents[index] == '}') { + // aww crap, this means we're one deeper + // and will have to find one more extra { + braceDepth++; + //if (braceDepth == 0) { + //finished = true; + //} + index--; + } else if (contents[index] == '{') { + braceDepth--; + if (braceDepth == 0) { + finished = true; + } + index--; + } else { + index--; + } + } + // never found a proper brace, be safe and don't do anything + if (!finished) return -1; + + // check how many spaces on the line with the matching open brace + //int pairedSpaceCount = calcSpaceCount(index, contents); + //System.out.println(pairedSpaceCount); + return calcSpaceCount(index, contents); + } + + + /** + * Get the character array and blank out the commented areas. + * This hasn't yet been tested, the plan was to make auto-indent + * less gullible (it gets fooled by braces that are commented out). + */ + protected char[] getCleanedContents() { + char c[] = textarea.getText().toCharArray(); + + int index = 0; + while (index < c.length - 1) { + if ((c[index] == '/') && (c[index+1] == '*')) { + c[index++] = 0; + c[index++] = 0; + while ((index < c.length - 1) && + !((c[index] == '*') && (c[index+1] == '/'))) { + c[index++] = 0; + } + + } else if ((c[index] == '/') && (c[index+1] == '/')) { + // clear out until the end of the line + while ((index < c.length) && (c[index] != 10)) { + c[index++] = 0; + } + if (index != c.length) { + index++; // skip over the newline + } + } + } + return c; + } + + /* + protected char[] getCleanedContents() { + char c[] = textarea.getText().toCharArray(); + boolean insideMulti; // multi-line comment + boolean insideSingle; // single line double slash + + //for (int i = 0; i < c.length - 1; i++) { + int index = 0; + while (index < c.length - 1) { + if (insideMulti && (c[index] == '*') && (c[index+1] == '/')) { + insideMulti = false; + index += 2; + } else if ((c[index] == '/') && (c[index+1] == '*')) { + insideMulti = true; + index += 2; + } else if ((c[index] == '/') && (c[index+1] == '/')) { + // clear out until the end of the line + while (c[index] != 10) { + c[index++] = 0; + } + index++; + } + } + } + */ } diff --git a/app/EditorStatus.java b/app/EditorStatus.java index 191f4b88f..67ededc34 100644 --- a/app/EditorStatus.java +++ b/app/EditorStatus.java @@ -422,7 +422,7 @@ public class EditorStatus extends JPanel implements ActionListener { } else if (e.getSource() == yesButton) { // answer was in response to "save changes?" unprompt(); - editor.handleSave(); + editor.handleSave(true); editor.checkModified2(); } else if (e.getSource() == cancelButton) { diff --git a/app/FindReplace.java b/app/FindReplace.java index 7006a5f45..43f6ce43a 100644 --- a/app/FindReplace.java +++ b/app/FindReplace.java @@ -29,10 +29,21 @@ import javax.swing.*; /** - * Find & Replace window for the processing editor. + * Find & Replace window for the Processing editor. + *

+ * One major annoyance in this is that the window is re-created each time + * that "Find" is called. This is because Mac OS X has a strange focus + * issue with windows that are re-shown with setVisible() or show(). + * requestFocusInWindow() properly sets the focus to the find field, + * however, just a short moment later, the focus is set to null. Even + * trying to catch this scenario and request it again doesn't seem to work. + * Most likely this is some annoyance buried deep in one of Apple's docs, + * or in the doc for the focus stuff (I tend to think the former because + * Windows doesn't seem to be quite so beligerent). Filed as + * Bug 244 + * should anyone have clues about how to fix. */ -public class FindReplace extends JFrame - implements ActionListener, KeyListener { +public class FindReplace extends JFrame implements ActionListener { static final int BIG = 13; static final int SMALL = 6; @@ -41,16 +52,17 @@ public class FindReplace extends JFrame JTextField findField; JTextField replaceField; + static String findString; + static String replaceString; JButton replaceButton; JButton replaceAllButton; JButton findButton; JCheckBox ignoreCaseBox; - boolean ignoreCase; - - KeyStroke windowClose; + static boolean ignoreCase = true; + /// true when there's something selected in the editor boolean found; @@ -74,6 +86,27 @@ public class FindReplace extends JFrame pain.add(replaceField = new JTextField(20)); Dimension d2 = findField.getPreferredSize(); + if (findString != null) findField.setText(findString); + if (replaceString != null) replaceField.setText(replaceString); + //System.out.println("setting find str to " + findString); + //findField.requestFocusInWindow(); + + //pain.setDefault + /* + findField.addFocusListener(new FocusListener() { + public void focusGained(FocusEvent e) { + System.out.println("Focus gained " + e.getOppositeComponent()); + } + + public void focusLost(FocusEvent e) { + System.out.println("Focus lost "); // + e.getOppositeComponent()); + if (e.getOppositeComponent() == null) { + requestFocusInWindow(); + } + } + }); + */ + // +1 since it's better to tend downwards int yoff = (1 + d2.height - d1.height) / 2; @@ -82,7 +115,7 @@ public class FindReplace extends JFrame replaceLabel.setBounds(BIG, BIG + d2.height + SMALL + yoff, d1.width, d1.height); - ignoreCase = true; + //ignoreCase = true; ignoreCaseBox = new JCheckBox("Ignore Case"); ignoreCaseBox.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { @@ -110,12 +143,8 @@ public class FindReplace extends JFrame } pain.add(buttons); - // 0069 TEMPORARILY DISABLED! - //replaceAllButton.setEnabled(false); - // to fix ugliness.. normally macosx java 1.3 puts an // ugly white border around this object, so turn it off. - //if (Base.platform == Base.MACOSX) { if (Base.isMacOS()) { buttons.setBorder(null); } @@ -146,7 +175,7 @@ public class FindReplace extends JFrame replaceButton.setEnabled(false); // so that typing will go straight to this field - findField.requestFocus(); + //findField.requestFocus(); // make the find button the blinky default getRootPane().setDefaultButton(findButton); @@ -161,48 +190,62 @@ public class FindReplace extends JFrame (screen.height - high) / 2, wide, high); // add key listener to trap esc and ctrl/cmd-w - findField.addKeyListener(this); - replaceField.addKeyListener(this); - addKeyListener(this); + /* + KeyListener listener = new KeyAdapter() { + public void keyPressed(KeyEvent e) { + if (Base.isCloseWindowEvent(e)) hide(); + } + }; + findField.addKeyListener(listener); + replaceField.addKeyListener(listener); + addKeyListener(listener); + */ + ActionListener disposer = new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { + //hide(); + handleClose(); + } + }; + setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); + addWindowListener(new WindowAdapter() { + public void windowClosing(WindowEvent e) { + handleClose(); + } + }); + Base.registerWindowCloseKeys(getRootPane(), disposer); + + /* // hack to to get first field to focus properly on osx // though this still doesn't seem to work addWindowListener(new WindowAdapter() { public void windowActivated(WindowEvent e) { //System.out.println("activating"); - findField.requestFocus(); - findField.selectAll(); + //boolean ok = findField.requestFocusInWindow(); + //System.out.println("got " + ok); + //findField.selectAll(); } }); + */ } - /** - * Handle window closing commands for ctrl/cmd-W or hitting ESC. - */ - public void keyPressed(KeyEvent e) { - if (windowClose == null) { - int modifiers = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(); - windowClose = KeyStroke.getKeyStroke('W', modifiers); - } - if ((e.getKeyCode() == KeyEvent.VK_ESCAPE) || - (KeyStroke.getKeyStrokeForEvent(e).equals(windowClose))) { - hide(); - //} else { - //System.out.println("event " + e); - } + public void handleClose() { + //System.out.println("handling close now"); + findString = findField.getText(); + replaceString = replaceField.getText(); + + // this object should eventually become dereferenced + hide(); } - public void keyReleased(KeyEvent e) { } - - public void keyTyped(KeyEvent e) { } - /* public void show() { + findField.requestFocusInWindow(); super.show(); - findField.selectAll(); - findField.requestFocus(); + //findField.selectAll(); + //findField.requestFocus(); } */ @@ -266,9 +309,10 @@ public class FindReplace extends JFrame } - // replace the current selection with whatever's in the - // replacement text field - + /** + * Replace the current selection with whatever's in the + * replacement text field. + */ public void replace() { if (!found) return; // don't replace if nothing found @@ -284,15 +328,17 @@ public class FindReplace extends JFrame editor.textarea.setSelectedText(replaceField.getText()); //editor.setSketchModified(true); //editor.sketch.setCurrentModified(true); - editor.sketch.setModified(); + editor.sketch.setModified(true); // don't allow a double replace replaceButton.setEnabled(false); } - // keep doing find and replace alternately until nothing more found - + /** + * Replace everything that matches by doing find and replace + * alternately until nothing more found. + */ public void replaceAll() { // move to the beginning editor.textarea.select(0, 0); diff --git a/app/Library.java b/app/Library.java new file mode 100755 index 000000000..4d3c6974c --- /dev/null +++ b/app/Library.java @@ -0,0 +1,444 @@ +/* + Library.java - Library System for Wiring + Copyright (c) 2006 Nicholas Zambetti. All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +package processing.app; + +import java.io.*; +import java.util.*; +import java.util.zip.*; + +import java.awt.*; +import java.awt.event.*; + +import javax.swing.*; +import javax.swing.event.*; + +/* + * Provides information about and builds a library + */ +public class Library implements MessageConsumer{ + + private File libFolder; + private LibraryManager libManager; + RunnerException exception; + + static final String BUGS_URL = "https://developer.berlios.de/bugs/?group_id=3590"; + static final String SUPER_BADNESS = "Compiler error, please submit this code to " + BUGS_URL; + + /* + * Create a Library + */ + public Library(LibraryManager manager, File folder) + { + libFolder = folder; + libManager = manager; + + /* for debug output + System.out.println("library: " + getName()); + System.out.println("folder: " + getFolder()); + System.out.println("built: " + isBuilt()); + System.out.println("buildable: " + isBuildable()); + System.out.println("o files: " + getObjectFiles().length); + System.out.println("c files: " + getCSourceFiles().length); + System.out.println("cpp files: " + getCPPSourceFiles().length); + */ + } + + /* + * Directory of library + * @return File object of library's folder + */ + public File getFolder() + { + return libFolder; + } + + /* + * The name of library + * @return String with library name, derived from folder + * note: this will be eventually taken from xml description file + */ + public String getName() + { + return libFolder.getName(); + } + + /* + * Tests if library is built + * @return True if library has .o files, false otherwise + */ + public boolean isBuilt() + { + FileFilter onlyObjectFiles = new FileFilter() { + public boolean accept(File file) { + return file.getName().endsWith(".o"); + } + }; + if(0 < (libFolder.listFiles(onlyObjectFiles)).length){ + return true; + } + return false; + } + + /* + * Tests if library is buildable + * @return True if library has .cpp files, false otherwise + */ + public boolean isBuildable() + { + FileFilter onlySourceFiles = new FileFilter() { + public boolean accept(File file) { + return (file.getName()).endsWith(".cpp"); + } + }; + if(0 < (libFolder.listFiles(onlySourceFiles)).length){ + return true; + } + return false; + } + + /* + * Tests if library is unbuilt but buildable + * @return True if library has .cpp files but no .o files, false otherwise + */ + public boolean isUnbuiltBuildable() + { + if(isBuildable()){ + if(!isBuilt()){ + return true; + } + } + return false; + } + + /* + * Finds examples folder + * @return "examples" folder as file object or null + */ + private File getExamplesFolder() + { + FileFilter filter = new FileFilter() { + public boolean accept(File file) { + if(file.isDirectory()){ + if((file.getName()).equalsIgnoreCase("examples")){ + return true; + } + } + return false; + } + }; + File[] files = libFolder.listFiles(filter); + if(files.length > 0){ + return files[0]; + } + return null; + } + + /* + * Populates example menu or submenu with files + */ + private void populateWithExamples(File folder, JMenu menu, ActionListener listener) { + FileFilter onlyfolders = new FileFilter() { + public boolean accept(File file) { + return file.isDirectory(); + } + }; + File[] folders = folder.listFiles(onlyfolders); + File file; + JMenu submenu; + JMenuItem item; + for(int i = 0; i < folders.length; ++i){ + file = new File(folders[i], folders[i].getName() + ".pde"); + if(file.exists()){ + item = new JMenuItem(folders[i].getName()); + item.setActionCommand(file.getAbsolutePath()); + item.addActionListener(listener); + menu.add(item); + }else{ + submenu = new JMenu(folders[i].getName()); + populateWithExamples(folders[i], submenu, listener); + menu.add(submenu); + } + } + } + + /* + * Builds and returns an examples menu + * @return JMenu object with example files, or null if none + */ + public JMenu getExamplesMenu(ActionListener listener) { + JMenu submenu; + File examplesFolder = getExamplesFolder(); + if(null != examplesFolder){ + submenu = new JMenu("Library-" + getName()); + populateWithExamples(examplesFolder, submenu, listener); + return submenu; + } + return null; + } + + /* + * List of object files for linking + * @return Array of library's object files as File objects + */ + public File[] getObjectFiles() + { + FileFilter onlyObjectFiles = new FileFilter() { + public boolean accept(File file) { + return (file.getName()).endsWith(".o"); + } + }; + return libFolder.listFiles(onlyObjectFiles); + } + + /* + * List of header source files for inclusion + * @return Array of library's header source files as File objects + */ + public File[] getHeaderFiles() + { + FileFilter onlyHFiles = new FileFilter() { + public boolean accept(File file) { + return (file.getName()).endsWith(".h"); + } + }; + return libFolder.listFiles(onlyHFiles); + } + + /* + * List of C source files for compiling + * @return Array of library's C source files as File objects + */ + private File[] getCSourceFiles() + { + FileFilter onlyCFiles = new FileFilter() { + public boolean accept(File file) { + return (file.getName()).endsWith(".c"); + } + }; + return libFolder.listFiles(onlyCFiles); + } + + /* + * List of C++ source files for compiling + * @return Array of library's C++ source files as File objects + */ + private File[] getCPPSourceFiles() + { + FileFilter onlyCPPFiles = new FileFilter() { + public boolean accept(File file) { + return (file.getName()).endsWith(".cpp"); + } + }; + return libFolder.listFiles(onlyCPPFiles); + } + + /* + * Attempt to build library + * @return true on successful build, false otherwise + */ + public boolean build() throws RunnerException + { + if(isBuildable()){ + String userDir = System.getProperty("user.dir") + File.separator; + + String[] baseCompileCommandC = new String[] { + ((!Base.isMacOS()) ? "tools/avr/bin/avr-gcc" : userDir + "tools/avr/bin/avr-gcc"), + "-c", + "-g", + "-Os", + "-Wall", + "-mmcu=" + Preferences.get("build.mcu"), + "-DF_CPU=" + Preferences.get("build.f_cpu"), + "-Ilib", + "-I" + getFolder(), + }; + + String[] baseCompileCommandCPP = new String[] { + ((!Base.isMacOS()) ? "tools/avr/bin/avr-g++" : userDir + "tools/avr/bin/avr-g++"), + "-c", + "-g", + "-Os", + "-Wall", + "-fno-exceptions", + "-mmcu=" + Preferences.get("build.mcu"), + "-DF_CPU=" + Preferences.get("build.f_cpu"), + "-Ilib", + "-I" + getFolder(), + }; + + // use built lib directories in include paths when searching for headers + // this allows libs to use other libs easily + String[] libDirs = libManager.getFolderPaths(); + String[] compileCommandC = new String[baseCompileCommandC.length + libDirs.length + 2]; + String[] compileCommandCPP = new String[baseCompileCommandCPP.length + libDirs.length + 2]; + System.arraycopy(baseCompileCommandC, 0, compileCommandC, 0, baseCompileCommandC.length); + System.arraycopy(baseCompileCommandCPP, 0, compileCommandCPP, 0, baseCompileCommandCPP.length); + for (int i = 0; i < libDirs.length; ++i) { + compileCommandC[baseCompileCommandC.length + i] = "-I" + libDirs[i]; + compileCommandCPP[baseCompileCommandCPP.length + i] = "-I" + libDirs[i]; + } + + File[] sourcesC = getCSourceFiles(); + File[] sourcesCPP = getCPPSourceFiles(); + + // execute the compiler, and create threads to deal + // with the input and error streams + // + int result = 0; + try { + String nameSansExtension; + Process process; + boolean compiling = true; + + // compile c sources + for(int i = 0; i < sourcesC.length; ++i) { + nameSansExtension = sourcesC[i].getName(); + nameSansExtension = nameSansExtension.substring(0, nameSansExtension.length() - 2); // -2 because ".c" + + compileCommandC[compileCommandC.length - 2] = sourcesC[i].getPath(); + compileCommandC[compileCommandC.length - 1] = "-o" + getFolder() + File.separator + nameSansExtension + ".o"; + + process = Runtime.getRuntime().exec(compileCommandC); + new MessageSiphon(process.getInputStream(), this); + new MessageSiphon(process.getErrorStream(), this); + + // wait for the process to finish. if interrupted + // before waitFor returns, continue waiting + // + compiling = true; + while (compiling) { + try { + result = process.waitFor(); + //System.out.println("result is " + result); + compiling = false; + } catch (InterruptedException ignored) { } + } + if (exception != null) { + exception.hideStackTrace = true; + throw exception; + } + if(result != 0){ + return false; + } + } + + // compile c++ sources + for(int i = 0; i < sourcesCPP.length; ++i) { + nameSansExtension = sourcesCPP[i].getName(); + nameSansExtension = nameSansExtension.substring(0, nameSansExtension.length() - 4); // -4 because ".cpp" + + compileCommandCPP[compileCommandCPP.length - 2] = sourcesCPP[i].getPath(); + compileCommandCPP[compileCommandCPP.length - 1] = "-o" + getFolder() + File.separator + nameSansExtension + ".o"; + + process = Runtime.getRuntime().exec(compileCommandCPP); + new MessageSiphon(process.getInputStream(), this); + new MessageSiphon(process.getErrorStream(), this); + + // wait for the process to finish. if interrupted + // before waitFor returns, continue waiting + // + compiling = true; + while (compiling) { + try { + result = process.waitFor(); + //System.out.println("result is " + result); + compiling = false; + } catch (InterruptedException ignored) { } + } + if (exception != null) { + exception.hideStackTrace = true; + throw exception; + } + if(result != 0){ + return false; + } + } + } catch (Exception e) { + String msg = e.getMessage(); + if ((msg != null) && (msg.indexOf("avr-gcc: not found") != -1)) { + Base.showWarning("Compiler error", + "Could not find the compiler.\n" + + "avr-gcc is missing from your PATH,\n" + + "see readme.txt for help.", null); + return false; + + } else if ((msg != null) && (msg.indexOf("avr-g++: not found") != -1)) { + Base.showWarning("Compiler error", + "Could not find the compiler.\n" + + "avr-g++ is missing from your PATH,\n" + + "see readme.txt for help.", null); + return false; + + } else { + e.printStackTrace(); + result = -1; + } + } + + // an error was queued up by message() + if (exception != null) { + throw exception; + } + + if (result != 0 && result != 1 ) { + Base.openURL(BUGS_URL); + throw new RunnerException(SUPER_BADNESS); + } + + // success would mean that 'result' is set to zero + return (result == 0); // ? true : false; + } + return false; // library is not buildable (contains no sources) + } + + /** + * Part of the MessageConsumer interface, this is called + * whenever a piece (usually a line) of error message is spewed + * out from the compiler. The errors are parsed for their contents + * and line number, which is then reported back to Editor. + */ + public void message(String inString) { + // This receives messages as full lines, so a newline needs + // to be added as they're printed to the console. + + // always print all compilation output for library writers! + String outString = ""; + + // shorten file paths so that they are friendlier + int start = 0; + int end = 0; + String substring = libFolder.getPath() + File.separator; + StringBuffer result = new StringBuffer(); + while ((end = inString.indexOf(substring, start)) >= 0) { + result.append(inString.substring(start, end)); + start = end + substring.length(); + } + result.append(inString.substring(start)); + outString = result.toString(); + + System.err.print(outString); + + // prepare error for throwing + if (inString.indexOf("error") != -1){ + exception = new RunnerException("Error building library \"" + getName() + "\""); + } + } + +} diff --git a/app/LibraryManager.java b/app/LibraryManager.java new file mode 100755 index 000000000..f12c2ae2f --- /dev/null +++ b/app/LibraryManager.java @@ -0,0 +1,217 @@ +/* + LibraryManager.java - Library System for Wiring + Copyright (c) 2006 Nicholas Zambetti. All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +package processing.app; + +import java.io.*; +import java.util.*; +import java.util.zip.*; + +import java.awt.event.*; + +import javax.swing.*; + +/* + * Provides information about and builds libraries + */ +public class LibraryManager { + + private File libDir; + private List libraries = new ArrayList(); + + /* + * Create a LibraryManager. + */ + public LibraryManager() + { + String userDir = System.getProperty("user.dir") + File.separator; + libDir = new File( + ((!Base.isMacOS()) ? "" : userDir) + "lib" + File.separator + + "targets" + File.separator + "libraries"); + refreshLibraries(); + } + + /* + * Scans for libraries and refreshes internal list + */ + private void refreshLibraries() + { + FileFilter onlyDirs = new FileFilter() { + public boolean accept(File file) { + return file.isDirectory(); + } + }; + libraries.clear(); + File[] libs = libDir.listFiles(onlyDirs); + for(int i = 0; i < libs.length; ++i){ + libraries.add(new Library(this, libs[i])); + } + } + + /* + * Returns a collection of all library objects + * @return A read-only collection of Library objects + */ + public Collection getAll() { + refreshLibraries(); + return Collections.unmodifiableList(libraries); + } + + /* + * Returns a collection of all built library objects + * @return A read-only collection of built Library objects + */ + public Collection getBuiltLibraries() { + refreshLibraries(); + List builtLibraries = new ArrayList(); + Library library; + ListIterator libIterator = libraries.listIterator(); + while(libIterator.hasNext()){ + library = (Library)libIterator.next(); + if(library.isBuilt()){ + builtLibraries.add(library); + } + } + return Collections.unmodifiableList(builtLibraries); + } + + /* + * Returns a collection of all buildable library objects + * @return A read-only collection of built Library objects + */ + public Collection getLibrariesToBuild() { + refreshLibraries(); + List buildableLibraries = new ArrayList(); + Library library; + ListIterator libIterator = libraries.listIterator(); + while(libIterator.hasNext()){ + library = (Library)libIterator.next(); + if(library.isUnbuiltBuildable()){ + buildableLibraries.add(library); + } + } + return Collections.unmodifiableList(buildableLibraries); + } + + /* + * Gathers paths to object files + * @return Array of strings of paths to object files + */ + public String[] getObjectFiles() { + ArrayList filesArrayList = new ArrayList(); + Collection builtLibraries = getBuiltLibraries(); + Library library; + File[] files; + Iterator libIterator = builtLibraries.iterator(); + while(libIterator.hasNext()){ + library = (Library)libIterator.next(); + files = library.getObjectFiles(); + for(int i = 0; i < files.length; ++i){ + filesArrayList.add(files[i].getPath()); + } + } + String[] filesArray = new String[filesArrayList.size()]; + filesArrayList.toArray(filesArray); + return filesArray; + } + + /* + * Gathers filenames of header files + * @return Array of strings of filenames of header files + */ + public String[] getHeaderFiles() { + ArrayList filesArrayList = new ArrayList(); + Collection builtLibraries = getBuiltLibraries(); + Library library; + File[] files; + Iterator libIterator = builtLibraries.iterator(); + while(libIterator.hasNext()){ + library = (Library)libIterator.next(); + files = library.getHeaderFiles(); + for(int i = 0; i < files.length; ++i){ + filesArrayList.add(files[i].getName()); + } + } + String[] filesArray = new String[filesArrayList.size()]; + filesArrayList.toArray(filesArray); + return filesArray; + } + + /* + * Gathers paths to library folders + * @return Array of strings of paths to library folders + */ + public String[] getFolderPaths() { + ArrayList foldersArrayList = new ArrayList(); + Collection builtLibraries = getBuiltLibraries(); + Library library; + Iterator libIterator = builtLibraries.iterator(); + while(libIterator.hasNext()){ + library = (Library)libIterator.next(); + foldersArrayList.add(library.getFolder().getPath()); + } + String[] foldersArray = new String[foldersArrayList.size()]; + foldersArrayList.toArray(foldersArray); + return foldersArray; + } + + /* + * Builds unbuilt libraries + * @return Number of libraries built as int, -1 & exception on error + */ + public int buildAllUnbuilt() throws RunnerException { + Collection buildableLibraries = getLibrariesToBuild(); + Library library; + Iterator libIterator = buildableLibraries.iterator(); + int countBuilt = 0; + while(libIterator.hasNext()){ + library = (Library)libIterator.next(); + //System.out.println("Building library \"" + library.getName() + "\""); + try { + if(library.build()){ + ++countBuilt; + }else{ + return -1; + } + }catch (RunnerException re) { + throw new RunnerException(re.getMessage()); + } catch (Exception ex) { + throw new RunnerException(ex.toString()); + } + } + return countBuilt; + } + + /* + * Populates examples menu with library folders + */ + public void populateExamplesMenu(JMenu examplesMenu, ActionListener listener) { + Library library; + Collection libraries = getBuiltLibraries(); + Iterator iterator = libraries.iterator(); + JMenu libraryExamples; + while(iterator.hasNext()){ + library = (Library)iterator.next(); + libraryExamples = library.getExamplesMenu(listener); + if(null != libraryExamples){ + examplesMenu.add(libraryExamples); + } + } + } +} diff --git a/app/Preferences.java b/app/Preferences.java index 11dd38069..1672b37d4 100644 --- a/app/Preferences.java +++ b/app/Preferences.java @@ -41,6 +41,7 @@ import javax.swing.filechooser.*; import javax.swing.text.*; import javax.swing.undo.*; +//import processing.core.PApplet; /** @@ -50,7 +51,7 @@ import javax.swing.undo.*; * properties files are iso8859-1, which is highly likely to * be a problem when trying to save sketch folders and locations. */ -public class Preferences extends JComponent { +public class Preferences { // what to call the feller @@ -72,16 +73,32 @@ public class Preferences extends JComponent { static final String PROMPT_OK = "OK"; static final String PROMPT_BROWSE = "Browse"; - // mac needs it to be 70, windows needs 66, linux needs 76 + /** + * Standardized width for buttons. Mac OS X 10.3 wants 70 as its default, + * Windows XP needs 66, and Linux needs 76, so 76 seems proper. + */ + static public int BUTTON_WIDTH = 76; - static int BUTTON_WIDTH = 76; - static int BUTTON_HEIGHT = 24; + /** + * Standardized button height. Mac OS X 10.3 (Java 1.4) wants 29, + * presumably because it now includes the blue border, where it didn't + * in Java 1.3. Windows XP only wants 23 (not sure what default Linux + * would be). Because of the disparity, on Mac OS X, it will be set + * inside a static block. + */ + static public int BUTTON_HEIGHT = 24; + static { + if (Base.isMacOS()) BUTTON_HEIGHT = 29; + } // value for the size bars, buttons, etc static final int GRID_SIZE = 33; - // gui variables + + // indents and spacing standards. these probably need to be modified + // per platform as well, since macosx is so huge, windows is smaller, + // and linux is all over the map static final int GUI_BIG = 13; static final int GUI_BETWEEN = 10; @@ -89,14 +106,12 @@ public class Preferences extends JComponent { // gui elements - //JFrame frame; - JDialog frame; + JDialog dialog; int wide, high; JTextField sketchbookLocationField; JCheckBox sketchPromptBox; JCheckBox sketchCleanBox; - //JCheckBox exportLibraryBox; JCheckBox externalEditorBox; JCheckBox checkUpdatesBox; @@ -111,7 +126,6 @@ public class Preferences extends JComponent { static Hashtable table = new Hashtable();; static File preferencesFile; - //boolean firstTime; // first time this feller has been run static public void init() { @@ -152,9 +166,6 @@ public class Preferences extends JComponent { // next load user preferences file - //File home = new File(System.getProperty("user.home")); - //File arduinoHome = new File(home, "Arduino"); - //preferencesFile = new File(home, PREFS_FILE); preferencesFile = Base.getSettingsFile(PREFS_FILE); if (!preferencesFile.exists()) { @@ -181,14 +192,12 @@ public class Preferences extends JComponent { public Preferences() { - // setup frame for the prefs + // setup dialog for the prefs - //frame = new JFrame("Preferences"); - frame = new JDialog(editor, "Preferences", true); - //frame.setResizable(false); + dialog = new JDialog(editor, "Preferences", true); + dialog.setResizable(false); - //Container pain = this; - Container pain = frame.getContentPane(); + Container pain = dialog.getContentPane(); pain.setLayout(null); int top = GUI_BIG; @@ -284,20 +293,6 @@ public class Preferences extends JComponent { top += d.height + GUI_BETWEEN; - // [ ] Enable export to "Library" - - /* - exportLibraryBox = new JCheckBox("Enable advanced \"Library\" features" + - " (requires restart)"); - exportLibraryBox.setEnabled(false); - pain.add(exportLibraryBox); - d = exportLibraryBox.getPreferredSize(); - exportLibraryBox.setBounds(left, top, d.width, d.height); - right = Math.max(right, left + d.width); - top += d.height + GUI_BETWEEN; - */ - - // [ ] Use external editor externalEditorBox = new JCheckBox("Use external editor"); @@ -320,21 +315,6 @@ public class Preferences extends JComponent { // More preferences are in the ... - /* - String blather = - "More preferences can be edited directly\n" + - "in the file " + preferencesFile.getAbsolutePath(); - //"More preferences are in the 'lib' folder inside text files\n" + - //"named preferences.txt and pde_" + - //Base.platforms[Base.platform] + ".properties"; - - JTextArea textarea = new JTextArea(blather); - textarea.setEditable(false); - textarea.setBorder(new EmptyBorder(0, 0, 0, 0)); - textarea.setBackground(null); - textarea.setFont(new Font("Dialog", Font.PLAIN, 12)); - pain.add(textarea); - */ label = new JLabel("More preferences can be edited directly in the file"); pain.add(label); @@ -362,9 +342,6 @@ public class Preferences extends JComponent { // [ OK ] [ Cancel ] maybe these should be next to the message? - //right = Math.max(right, left + d.width + GUI_BETWEEN + - // BUTTON_WIDTH + GUI_SMALL + BUTTON_WIDTH); - button = new JButton(PROMPT_OK); button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { @@ -376,9 +353,6 @@ public class Preferences extends JComponent { d2 = button.getPreferredSize(); BUTTON_HEIGHT = d2.height; - // smoosh up to the line before - //top -= BUTTON_HEIGHT; - h = right - (BUTTON_WIDTH + GUI_SMALL + BUTTON_WIDTH); button.setBounds(h, top, BUTTON_WIDTH, BUTTON_HEIGHT); h += BUTTON_WIDTH + GUI_SMALL; @@ -398,32 +372,39 @@ public class Preferences extends JComponent { // finish up wide = right + GUI_BIG; - high = top + GUI_SMALL; //GUI_BIG; - setSize(wide, high); + high = top + GUI_SMALL; + //setSize(wide, high); // closing the window is same as hitting cancel button - frame.addWindowListener(new WindowAdapter() { + dialog.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { disposeFrame(); } }); - Container content = frame.getContentPane(); - content.setLayout(new BorderLayout()); - content.add(this, BorderLayout.CENTER); - - frame.pack(); + ActionListener disposer = new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { + disposeFrame(); + } + }; + Base.registerWindowCloseKeys(dialog.getRootPane(), disposer); Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); - frame.setLocation((screen.width - wide) / 2, + dialog.setLocation((screen.width - wide) / 2, (screen.height - high) / 2); + dialog.pack(); // get insets + Insets insets = dialog.getInsets(); + dialog.setSize(wide + insets.left + insets.right, + high + insets.top + insets.bottom); + + // handle window closing commands for ctrl/cmd-W or hitting ESC. - addKeyListener(new KeyAdapter() { + pain.addKeyListener(new KeyAdapter() { public void keyPressed(KeyEvent e) { KeyStroke wc = Editor.WINDOW_CLOSE_KEYSTROKE; if ((e.getKeyCode() == KeyEvent.VK_ESCAPE) || @@ -435,6 +416,26 @@ public class Preferences extends JComponent { } + /* + protected JRootPane createRootPane() { + System.out.println("creating root pane esc received"); + + ActionListener actionListener = new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { + //setVisible(false); + System.out.println("esc received"); + } + }; + + JRootPane rootPane = new JRootPane(); + KeyStroke stroke = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0); + rootPane.registerKeyboardAction(actionListener, stroke, + JComponent.WHEN_IN_FOCUSED_WINDOW); + return rootPane; + } + */ + + public Dimension getPreferredSize() { return new Dimension(wide, high); } @@ -447,7 +448,7 @@ public class Preferences extends JComponent { * Close the window after an OK or Cancel. */ public void disposeFrame() { - frame.dispose(); + dialog.dispose(); } @@ -487,7 +488,7 @@ public class Preferences extends JComponent { externalEditorBox.setSelected(getBoolean("editor.external")); checkUpdatesBox.setSelected(getBoolean("update.check")); - frame.show(); + dialog.show(); } diff --git a/app/Sketch.java b/app/Sketch.java index bd61638d0..56f68250d 100644 --- a/app/Sketch.java +++ b/app/Sketch.java @@ -1,7 +1,7 @@ /* -*- mode: jde; c-basic-offset: 2; indent-tabs-mode: nil -*- */ /* - Part of the Arduino project - http://arduino.berlios.de/ + Part of the Processing project - http://processing.org Copyright (c) 2004-05 Ben Fry and Casey Reas Copyright (c) 2001-04 Massachusetts Institute of Technology @@ -19,8 +19,6 @@ 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 - - $Id$ */ package processing.app; @@ -67,7 +65,12 @@ public class Sketch { public File codeFolder; static final int PDE = 0; - static final int JAVA = 1; + static final int CPP = 1; + static final int C = 2; + static final int HEADER = 3; + + static final String flavorExtensionsReal[] = new String[] { ".pde", ".cpp", ".c", ".h" }; + static final String flavorExtensionsShown[] = new String[] { "", ".cpp", ".c", ".h" }; public SketchCode current; int codeCount; @@ -104,6 +107,8 @@ public class Sketch { name = mainFilename.substring(0, mainFilename.length() - 4); } else if (mainFilename.endsWith(".c")) { name = mainFilename.substring(0, mainFilename.length() - 2); + } else if (mainFilename.endsWith(".h")) { + name = mainFilename.substring(0, mainFilename.length() - 2); } else if (mainFilename.endsWith(".cpp")) { name = mainFilename.substring(0, mainFilename.length() - 4); } @@ -157,9 +162,11 @@ public class Sketch { for (int i = 0; i < list.length; i++) { if (list[i].endsWith(".pde")) codeCount++; else if (list[i].endsWith(".c")) codeCount++; + else if (list[i].endsWith(".h")) codeCount++; else if (list[i].endsWith(".cpp")) codeCount++; else if (list[i].endsWith(".pde.x")) hiddenCount++; else if (list[i].endsWith(".c.x")) hiddenCount++; + else if (list[i].endsWith(".h.x")) hiddenCount++; else if (list[i].endsWith(".cpp.x")) hiddenCount++; } @@ -180,13 +187,19 @@ public class Sketch { code[codeCounter++] = new SketchCode(list[i].substring(0, list[i].length() - 2), new File(folder, list[i]), - JAVA); + C); + + } else if (list[i].endsWith(".h")) { + code[codeCounter++] = + new SketchCode(list[i].substring(0, list[i].length() - 2), + new File(folder, list[i]), + HEADER); } else if (list[i].endsWith(".cpp")) { code[codeCounter++] = new SketchCode(list[i].substring(0, list[i].length() - 4), new File(folder, list[i]), - JAVA); + CPP); } else if (list[i].endsWith(".pde.x")) { hidden[hiddenCounter++] = @@ -198,12 +211,17 @@ public class Sketch { hidden[hiddenCounter++] = new SketchCode(list[i].substring(0, list[i].length() - 4), new File(folder, list[i]), - JAVA); + C); + } else if (list[i].endsWith(".h.x")) { + hidden[hiddenCounter++] = + new SketchCode(list[i].substring(0, list[i].length() - 4), + new File(folder, list[i]), + HEADER); } else if (list[i].endsWith(".cpp.x")) { hidden[hiddenCounter++] = new SketchCode(list[i].substring(0, list[i].length() - 6), new File(folder, list[i]), - JAVA); + CPP); } } @@ -314,8 +332,7 @@ public class Sketch { renamingCode = true; String prompt = (current == code[0]) ? "New name for sketch:" : "New name for file:"; - String oldName = - (current.flavor == PDE) ? current.name : current.name + ".cpp"; + String oldName = current.name + flavorExtensionsShown[current.flavor]; editor.status.edit(prompt, oldName); } @@ -348,6 +365,7 @@ public class Sketch { } if (newName.trim().equals(".c") || + newName.trim().equals(".h") || newName.trim().equals(".pde") || newName.trim().equals(".cpp")) { return; @@ -363,23 +381,28 @@ public class Sketch { newName = newName.substring(0, newName.length() - 4); newFlavor = PDE; - } else if (newName.endsWith(".c") || newName.endsWith(".cpp")) { + } else if (newName.endsWith(".c") || newName.endsWith(".cpp") || + newName.endsWith(".h")) { // don't show this error if creating a new tab if (renamingCode && (code[0] == current)) { Base.showWarning("Problem with rename", - "The main .pde file cannot be .c or .cpp file.\n" + + "The main .pde file cannot be .c, .cpp, or .h file.\n" + "(It may be time for your to graduate to a\n" + "\"real\" programming environment)", null); return; } newFilename = newName; - if(newName.endsWith(".c")) + if(newName.endsWith(".c")) { newName = newName.substring(0, newName.length() - 2); - else if(newName.endsWith(".cpp")) + newFlavor = C; + } if(newName.endsWith(".h")) { + newName = newName.substring(0, newName.length() - 2); + newFlavor = HEADER; + } else if(newName.endsWith(".cpp")) { newName = newName.substring(0, newName.length() - 4); - newFlavor = JAVA; - + newFlavor = CPP; + } } else { newFilename = newName + ".pde"; newFlavor = PDE; @@ -538,7 +561,7 @@ public class Sketch { sortCode(); // set the new guy as current - setCurrent(newName); + setCurrent(newName + flavorExtensionsShown[newFlavor]); // update the tabs //editor.header.repaint(); @@ -572,7 +595,8 @@ public class Sketch { Object[] options = { "OK", "Cancel" }; String prompt = (current == code[0]) ? "Are you sure you want to delete this sketch?" : - "Are you sure you want to delete \"" + current.name + "\"?"; + "Are you sure you want to delete \"" + current.name + + flavorExtensionsShown[current.flavor] + "\"?"; int result = JOptionPane.showOptionDialog(editor, prompt, "Delete", @@ -684,9 +708,14 @@ public class Sketch { public void unhideCode(String what) { SketchCode unhideCode = null; - + String name = what.substring(0, + (what.indexOf(".") == -1 ? what.length() : what.indexOf("."))); + String extension = what.indexOf(".") == -1 ? "" : + what.substring(what.indexOf(".")); + for (int i = 0; i < hiddenCount; i++) { - if (hidden[i].name.equals(what)) { + if (hidden[i].name.equals(name) && + Sketch.flavorExtensionsShown[hidden[i].flavor].equals(extension)) { //unhideIndex = i; unhideCode = hidden[i]; @@ -730,8 +759,8 @@ public class Sketch { /** * Sets the modified value for the code in the frontmost tab. */ - public void setModified() { - current.modified = true; + public void setModified(boolean state) { + current.modified = state; calcModified(); } @@ -996,6 +1025,26 @@ public class Sketch { // it move instead of copy, they can do it by hand File sourceFile = new File(directory, filename); + // now do the work of adding the file + addFile(sourceFile); + } + + + /** + * Add a file to the sketch. + *

+ * .pde or .java files will be added to the sketch folder.
+ * .jar, .class, .dll, .jnilib, and .so files will all + * be added to the "code" folder.
+ * All other files will be added to the "data" folder. + *

+ * If they don't exist already, the "code" or "data" folder + * will be created. + *

+ * @return true if successful. + */ + public boolean addFile(File sourceFile) { + String filename = sourceFile.getName(); File destFile = null; boolean addingCode = false; @@ -1012,6 +1061,7 @@ public class Sketch { } else if (filename.toLowerCase().endsWith(".pde") || filename.toLowerCase().endsWith(".c") || + filename.toLowerCase().endsWith(".h") || filename.toLowerCase().endsWith(".cpp")) { destFile = new File(this.folder, filename); addingCode = true; @@ -1028,7 +1078,7 @@ public class Sketch { "This file has already been copied to the\n" + "location where you're trying to add it.\n" + "I ain't not doin nuthin'.", null); - return; + return false; } // in case the user is "adding" the code in an attempt @@ -1036,10 +1086,12 @@ public class Sketch { if (!sourceFile.equals(destFile)) { try { Base.copyFile(sourceFile, destFile); + } catch (IOException e) { Base.showWarning("Error adding file", "Could not add '" + filename + "' to the sketch.", e); + return false; } } @@ -1050,9 +1102,15 @@ public class Sketch { if (newName.toLowerCase().endsWith(".pde")) { newName = newName.substring(0, newName.length() - 4); newFlavor = PDE; - } else { + } else if (newName.toLowerCase().endsWith(".c")) { newName = newName.substring(0, newName.length() - 2); - newFlavor = JAVA; + newFlavor = C; + } else if (newName.toLowerCase().endsWith(".h")) { + newName = newName.substring(0, newName.length() - 2); + newFlavor = HEADER; + } else { // ".cpp" + newName = newName.substring(0, newName.length() - 4); + newFlavor = CPP; } // see also "nameCode" for identical situation @@ -1062,6 +1120,7 @@ public class Sketch { setCurrent(newName); editor.header.repaint(); } + return true; } @@ -1145,8 +1204,15 @@ public class Sketch { * based on a name (used by codeNew and codeRename). */ protected void setCurrent(String findName) { + SketchCode unhideCode = null; + String name = findName.substring(0, + (findName.indexOf(".") == -1 ? findName.length() : findName.indexOf("."))); + String extension = findName.indexOf(".") == -1 ? "" : + findName.substring(findName.indexOf(".")); + for (int i = 0; i < codeCount; i++) { - if (findName.equals(code[i].name)) { + if (name.equals(code[i].name) && + Sketch.flavorExtensionsShown[code[i].flavor].equals(extension)) { setCurrent(i); return; } @@ -1264,6 +1330,20 @@ public class Sketch { */ protected String build(Target target, String buildPath, String suggestedClassName) throws RunnerException { + + // build unbuilt buildable libraries + // completely independent from sketch, so run all the time + LibraryManager libraryManager = new LibraryManager(); + try { + libraryManager.buildAllUnbuilt(); + } catch (RunnerException re) { + throw new RunnerException(re.getMessage()); + } catch (Exception ex) { + throw new RunnerException(ex.toString()); + } + // update sketchbook menu, this adds examples of any built libs + editor.sketchbook.rebuildMenus(); + // make sure the user didn't hide the sketch folder ensureExistence(); @@ -1316,7 +1396,7 @@ public class Sketch { // check to see if multiple files that include a .java file externalRuntime = false; for (int i = 0; i < codeCount; i++) { - if (code[i].flavor == JAVA) { + if (code[i].flavor == C || code[i].flavor == CPP) { externalRuntime = true; break; } @@ -1382,7 +1462,6 @@ public class Sketch { //System.out.println(); } else { - //code[0].preprocName = className + "." + Preferences.get("build.extension"); code[0].preprocName = className + ".cpp"; } @@ -1408,6 +1487,7 @@ public class Sketch { } errorLine -= code[errorFile].preprocOffset; errorLine -= preprocessor.prototypeCount; + errorLine -= preprocessor.headerCount; throw new RunnerException(re.getMessage(), errorFile, errorLine, re.getColumn()); @@ -1449,6 +1529,7 @@ public class Sketch { } errorLine -= code[errorFile].preprocOffset; errorLine -= preprocessor.prototypeCount; + errorLine -= preprocessor.headerCount; throw new RunnerException(tsre.getMessage(), errorFile, errorLine, errorColumn); @@ -1507,13 +1588,12 @@ public class Sketch { // 3. then loop over the code[] and save each .java file for (int i = 0; i < codeCount; i++) { - if (code[i].flavor == JAVA) { + if (code[i].flavor == CPP || code[i].flavor == C || code[i].flavor == HEADER) { // no pre-processing services necessary for java files // just write the the contents of 'program' to a .java file // into the build directory. uses byte stream and reader/writer // shtuff so that unicode bunk is properly handled - //String filename = code[i].name + "." + Preferences.get("build.extension"); - String filename = code[i].name + ".cpp"; + String filename = code[i].name + flavorExtensionsReal[code[i].flavor]; try { Base.saveFile(code[i].program, new File(buildPath, filename)); } catch (IOException e) { @@ -1538,7 +1618,7 @@ public class Sketch { } catch (RunnerException re) { throw new RunnerException(re.getMessage(), re.file, - re.line - preprocessor.prototypeCount, + re.line - preprocessor.prototypeCount - preprocessor.headerCount, re.column); } catch (Exception ex) { // TODO better method for handling this? diff --git a/app/Sketchbook.java b/app/Sketchbook.java index 08c2be03a..460974a4c 100644 --- a/app/Sketchbook.java +++ b/app/Sketchbook.java @@ -382,10 +382,18 @@ public class Sketchbook { } catch (IOException e) { e.printStackTrace(); } + + LibraryManager libManager = new LibraryManager(); + ActionListener listener = new ActionListener() { + public void actionPerformed(ActionEvent e) { + editor.handleOpen(e.getActionCommand()); + } + }; try { JMenu examplesMenu = new JMenu("Examples"); addSketches(examplesMenu, examplesFolder); + libManager.populateExamplesMenu(examplesMenu, listener); menu.add(examplesMenu); } catch (IOException e) { e.printStackTrace(); @@ -542,8 +550,10 @@ public class Sketchbook { list[i].equals("CVS")) continue; File subfolder = new File(folder, list[i]); + if (!subfolder.isDirectory()) continue; + File exported = new File(subfolder, "library"); - File entry = new File(exported, list[i] + ".jar"); + File entry = new File(exported, list[i] + ".o"); // if a .jar file of the same prefix as the folder exists // inside the 'library' subfolder of the sketch if (entry.exists()) { @@ -556,7 +566,7 @@ public class Sketchbook { Base.showMessage("Ignoring bad sketch name", mess); continue; } - +/* // get the path for all .jar files in this code folder String libraryClassPath = Compiler.contentsToClassPath(exported); @@ -565,12 +575,12 @@ public class Sketchbook { librariesClassPath += File.pathSeparatorChar + libraryClassPath; // need to associate each import with a library folder - String packages[] = new String[0]; - //Compiler.packageListFromClassPath(libraryClassPath); + String packages[] = + Compiler.packageListFromClassPath(libraryClassPath); for (int k = 0; k < packages.length; k++) { importToLibraryTable.put(packages[k], exported); } - +*/ JMenuItem item = new JMenuItem(list[i]); item.addActionListener(listener); item.setActionCommand(entry.getAbsolutePath()); @@ -589,7 +599,7 @@ public class Sketchbook { } } return ifound; - } + /*return false;*/ } /** diff --git a/app/preproc/PdePreprocessor.java b/app/preproc/PdePreprocessor.java index 37262f4c9..97d95a802 100755 --- a/app/preproc/PdePreprocessor.java +++ b/app/preproc/PdePreprocessor.java @@ -74,6 +74,9 @@ public class PdePreprocessor { // stores number of built user-defined function prototypes public int prototypeCount = 0; + // stores number of included library headers written + public int headerCount = 0; + /** * These may change in-between (if the prefs panel adds this option) * so grab them here on construction. @@ -236,24 +239,22 @@ public class PdePreprocessor { String returntype, functioname, parameterlist, prototype; java.util.LinkedList prototypes = new java.util.LinkedList(); //System.out.println("prototypes:"); - //if (Preferences.get("build.extension").equals("cpp")) { - while(matcher.contains(input, pattern)){ - result = matcher.getMatch(); - //System.out.println(result); - returntype = result.group(1).toString(); - functioname = result.group(2).toString(); - parameterlist = result.group(3).toString().replace('\n', ' '); - prototype = returntype + " " + functioname + "(" + parameterlist + ");"; - if(0 == functioname.compareTo("setup")){ - continue; - } - if(0 == functioname.compareTo("loop")){ - continue; - } - prototypes.add(prototype); - //System.out.println(prototype); + while(matcher.contains(input, pattern)){ + result = matcher.getMatch(); + //System.out.println(result); + returntype = result.group(1).toString(); + functioname = result.group(2).toString(); + parameterlist = result.group(3).toString().replace('\n', ' '); + prototype = returntype + " " + functioname + "(" + parameterlist + ");"; + if(0 == functioname.compareTo("setup")){ + continue; } - //} + if(0 == functioname.compareTo("loop")){ + continue; + } + prototypes.add(prototype); + //System.out.println(prototype); + } // store # of prototypes so that line number reporting can be adjusted prototypeCount = prototypes.size(); @@ -263,6 +264,7 @@ public class PdePreprocessor { // through so that the line numbers when the compiler reports errors // match those that will be highlighted in the PDE IDE // + //System.out.println(program); WLexer lexer = new WLexer(programReader); //lexer.setTokenObjectClass("antlr.CommonHiddenStreamToken"); lexer.setTokenObjectClass("processing.app.preproc.CToken"); @@ -329,7 +331,6 @@ public class PdePreprocessor { // output the code // WEmitter emitter = new WEmitter(lexer.getPreprocessorInfoChannel()); - //File streamFile = new File(buildPath, name + "." + Preferences.get("build.extension")); File streamFile = new File(buildPath, name + ".cpp"); PrintStream stream = new PrintStream(new FileOutputStream(streamFile)); @@ -381,6 +382,16 @@ public class PdePreprocessor { */ void writeHeader(PrintStream out, String className, java.util.LinkedList prototypes) { out.print("#include \"WProgram.h\"\n"); + + // print library headers + LibraryManager libraryManager = new LibraryManager(); + String[] headerFiles = libraryManager.getHeaderFiles(); + for(int i = 0; i < headerFiles.length; ++i){ + out.print("#include \"" + headerFiles[i] + "\"\n"); + } + + // record number of header lines written for error line adjustment + headerCount = headerFiles.length; // print user defined prototypes while(0 < prototypes.size()){ diff --git a/app/syntax/JEditTextArea.java b/app/syntax/JEditTextArea.java index 942039748..ba367d360 100644 --- a/app/syntax/JEditTextArea.java +++ b/app/syntax/JEditTextArea.java @@ -1647,7 +1647,7 @@ public class JEditTextArea extends JComponent inputHandler.keyTyped(evt); break; case KeyEvent.KEY_PRESSED: - if (!editorListener.keyPressed(evt)) { + if ((editorListener != null) && !editorListener.keyPressed(evt)) { inputHandler.keyPressed(evt); } break; diff --git a/app/syntax/TextAreaPainter.java b/app/syntax/TextAreaPainter.java index fb7eeeef3..b1e4ccc82 100644 --- a/app/syntax/TextAreaPainter.java +++ b/app/syntax/TextAreaPainter.java @@ -475,6 +475,42 @@ public class TextAreaPainter extends JComponent implements TabExpander Token currentLineTokens; Segment currentLine; + /** + * Accessor used by tools that want to hook in and grab the formatting. + */ + public int getCurrentLineIndex() { + return currentLineIndex; + } + + /** + * Accessor used by tools that want to hook in and grab the formatting. + */ + public void setCurrentLineIndex(int what) { + currentLineIndex = what; + } + + /** + * Accessor used by tools that want to hook in and grab the formatting. + */ + public Token getCurrentLineTokens() { + return currentLineTokens; + } + + /** + * Accessor used by tools that want to hook in and grab the formatting. + */ + public void setCurrentLineTokens(Token tokens) { + currentLineTokens = tokens; + } + + /** + * Accessor used by tools that want to hook in and grab the formatting. + */ + public Segment getCurrentLine() { + return currentLine; + } + + // protected members protected JEditTextArea textArea; diff --git a/app/tools/Archiver.java b/app/tools/Archiver.java new file mode 100755 index 000000000..facf49dd5 --- /dev/null +++ b/app/tools/Archiver.java @@ -0,0 +1,156 @@ +/* -*- mode: jde; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Archiver - plugin tool for archiving sketches + Part of the Processing project - http://processing.org + + Copyright (c) 2004-05 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.*; + +import java.io.*; +import java.text.*; +import java.util.*; +import java.util.zip.*; + + +public class Archiver { + Editor editor; + + // someday these will be settable + boolean useDate = true; //false; + int digits = 3; + + NumberFormat numberFormat; + SimpleDateFormat dateFormat; + + + public Archiver(Editor editor) { + this.editor = editor; + + numberFormat = NumberFormat.getInstance(); + numberFormat.setGroupingUsed(false); // no commas + numberFormat.setMinimumIntegerDigits(digits); + + dateFormat = new SimpleDateFormat("yyMMdd"); + } + + + public void show() { + // first save the sketch so that things don't archive strangely + boolean success = false; + try { + success = editor.sketch.save(); + } catch (Exception e) { + e.printStackTrace(); + } + if (!success) { + Base.showWarning("Couldn't archive sketch", + "Archiving the sketch has been canceled because\n" + + "the sketch couldn't save properly.", null); + return; + } + + File location = editor.sketch.folder; + String name = location.getName(); + File parent = new File(location.getParent()); + + //System.out.println("loc " + location); + //System.out.println("par " + parent); + + File newbie = null; + String namely = null; + int index = 0; + do { + if (useDate) { + String purty = dateFormat.format(new Date()); + String stamp = purty + ((char) ('a' + index)); + namely = name + "-" + stamp; + newbie = new File(parent, namely + ".zip"); + + } else { + String diggie = numberFormat.format(index + 1); + namely = name + "-" + diggie; + newbie = new File(parent, namely + ".zip"); + } + index++; + } while (newbie.exists()); + + try { + //System.out.println(newbie); + FileOutputStream zipOutputFile = new FileOutputStream(newbie); + ZipOutputStream zos = new ZipOutputStream(zipOutputFile); + + // recursively fill the zip file + buildZip(location, name, zos); + + // close up the jar file + zos.flush(); + zos.close(); + + editor.message("Created archive " + newbie.getName() + "."); + + } catch (IOException e) { + e.printStackTrace(); + } + } + + + public void buildZip(File dir, String sofar, + ZipOutputStream zos) throws IOException { + String files[] = dir.list(); + for (int i = 0; i < files.length; i++) { + if (files[i].equals(".") || + files[i].equals("..")) continue; + + File sub = new File(dir, files[i]); + String nowfar = (sofar == null) ? + files[i] : (sofar + "/" + files[i]); + + if (sub.isDirectory()) { + // directories are empty entries and have / at the end + ZipEntry entry = new ZipEntry(nowfar + "/"); + //System.out.println(entry); + zos.putNextEntry(entry); + zos.closeEntry(); + buildZip(sub, nowfar, zos); + + } else { + ZipEntry entry = new ZipEntry(nowfar); + entry.setTime(sub.lastModified()); + zos.putNextEntry(entry); + zos.write(Base.grabFile(sub)); + zos.closeEntry(); + } + } + } +} + + + /* + int index = 0; + SimpleDateFormat formatter = new SimpleDateFormat("yyMMdd"); + String purty = formatter.format(new Date()); + do { + newbieName = "sketch_" + purty + ((char) ('a' + index)); + newbieDir = new File(newbieParentDir, newbieName); + index++; + } while (newbieDir.exists()); + */ diff --git a/app/tools/AutoFormat.java b/app/tools/AutoFormat.java index 2ca38272b..a24bc6021 100644 --- a/app/tools/AutoFormat.java +++ b/app/tools/AutoFormat.java @@ -3,7 +3,7 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2005 Ben Fry and Casey Reas + Copyright (c) 2005-06 Ben Fry and Casey Reas Copyright (c) 2003 Martin Gomez, Ateneo de Manila University This program is free software; you can redistribute it and/or modify @@ -31,122 +31,20 @@ import java.util.StringTokenizer; /** - * Alternate handler for dealing with auto format, - * contributed by Martin Gomez. + * Alternate handler for dealing with auto format. + * Contributed by Martin Gomez, additional bug fixes by Ben Fry. */ public class AutoFormat { Editor editor; - - public AutoFormat(Editor editor) { - this.editor = editor; - } - - - /* - public void show() { - String prog = editor.textarea.getText(); - - // TODO re-enable history - //history.record(prog, SketchHistory.BEAUTIFY); - - //int tabSize = Preferences.getInteger("editor.tabs.size"); - - char program[] = prog.toCharArray(); - StringBuffer buffer = new StringBuffer(); - boolean gotBlankLine = false; - int index = 0; - int level = 0; - - while (index != program.length) { - int begin = index; - while ((program[index] != '\n') && - (program[index] != '\r')) { - index++; - if (program.length == index) - break; - } - int end = index; - if (index != program.length) { - if ((index+1 != program.length) && - // treat \r\n from windows as one line - (program[index] == '\r') && - (program[index+1] == '\n')) { - index += 2; - } else { - index++; - } - } // otherwise don't increment - - String line = new String(program, begin, end-begin); - line = line.trim(); - - if (line.length() == 0) { - if (!gotBlankLine) { - // let first blank line through - buffer.append('\n'); - gotBlankLine = true; - } - } else { - //System.out.println(level); - int idx = -1; - String myline = line.substring(0); - while (myline.lastIndexOf('}') != idx) { - idx = myline.indexOf('}'); - myline = myline.substring(idx+1); - level--; - } - //for (int i = 0; i < level*2; i++) { - // TODO i've since forgotten how i made this work (maybe it's even - // a bug) but for now, level is incrementing/decrementing in - // steps of two. in the interest of getting a release out, - // i'm just gonna roll with that since this function will prolly - // be replaced entirely and there are other things to worry about. - for (int i = 0; i < tabSize * level / 2; i++) { - buffer.append(' '); - } - buffer.append(line); - buffer.append('\n'); - //if (line.charAt(0) == '{') { - //level++; - //} - idx = -1; - myline = line.substring(0); - while (myline.lastIndexOf('{') != idx) { - idx = myline.indexOf('{'); - myline = myline.substring(idx+1); - level++; - } - gotBlankLine = false; - } - } - - // save current (rough) selection point - int selectionEnd = editor.textarea.getSelectionEnd(); - - // replace with new bootiful text - editor.setText(buffer.toString(), false); - - // make sure the caret would be past the end of the text - if (buffer.length() < selectionEnd - 1) { - selectionEnd = buffer.length() - 1; - } - - // at least in the neighborhood - editor.textarea.select(selectionEnd, selectionEnd); - - editor.sketch.setModified(); - //buttons.clear(); - } - */ - + static final int BLOCK_MAXLEN = 1024; StringBuffer strOut; - String formattedText; + //String formattedText; int indentValue; String indentChar; - String uhOh = null; - String theStuff; + //String uhOh = null; + //String theStuff; int EOF; BufferedInputStream bin = null; int nBytesRead, indexBlock, lineLength, lineNumber; @@ -167,7 +65,6 @@ public class AutoFormat { int s_tabs[][]; String w_if_, w_else, w_for, w_ds, w_case, w_cpp_comment, w_jdoc; int jdoc, j; - int BLOCK_MAXLEN; char string[]; byte bstring[]; byte bblank; @@ -183,10 +80,15 @@ public class AutoFormat { String line_feed; - static int outfil; // temporary + //static int outfil; // temporary - public void comment() { + public AutoFormat(Editor editor) { + this.editor = editor; + } + + + public void comment() throws IOException { int save_s_flg; save_s_flg = s_flg; @@ -194,7 +96,7 @@ public class AutoFormat { c = string[j++] = getchr(); // extra char while (done == 0) { c = string[j++] = getchr(); - while(c != '/') { + while ((c != '/') && (j < string.length)) { if(c == '\n' || c == '\r') { lineNumber++; putcoms(); @@ -216,7 +118,7 @@ public class AutoFormat { } - public char get_string() { + public char get_string() throws IOException { char ch; ch = '*'; while (true) { @@ -275,8 +177,9 @@ public class AutoFormat { } - public void fprintf(int outfil, String out_string) { - int out_len = out_string.length(); + //public void fprintf(int outfil, String out_string) { + public void fprintf(String out_string) { + //int out_len = out_string.length(); String j_string = new String(string); strOut.append(out_string); } @@ -287,491 +190,6 @@ public class AutoFormat { } - public void setUhOh(String s) { - uhOh = s; - } - - - public String grabUhOh() { - return uhOh; - } - - - public void show() { - StringBuffer onechar; - - theStuff = editor.textarea.getText(); - strOut = new StringBuffer(); - indentValue = Preferences.getInteger("editor.tabs.size"); - indentChar = new String(" "); - - lineNumber = 0; - BLOCK_MAXLEN = 256; - c_level = if_lev = level = e_flg = paren = 0; - a_flg = q_flg = j = b_flg = tabs = 0; - if_flg = peek = -1; - peekc = '`'; - s_flg = 1; - bblank = ' '; - jdoc = 0; - - s_level = new int[10]; - sp_flg = new int[20][10]; - s_ind = new int[20][10]; - s_if_lev = new int[10]; - s_if_flg = new int[10]; - ind = new int[10]; - p_flg = new int[10]; - s_tabs = new int[20][10]; - - w_else = new String ("else"); - w_if_ = new String ("if"); - w_for = new String ("for"); - w_ds = new String ("default"); - w_case = new String ("case"); - w_cpp_comment = new String ("//"); - w_jdoc = new String ("/**"); - line_feed = new String ("\n"); - - try { // opening input string - // open for input - ByteArrayInputStream in = - new ByteArrayInputStream(theStuff.getBytes()); - - // add buffering to that InputStream - bin = new BufferedInputStream(in); - - } catch(Exception e) { - System.out.println(e.toString()); - } - - // read as long as there is something to read - EOF = 0; // = 1 set in getchr when EOF - - bArray = new byte[BLOCK_MAXLEN]; - string = new char[BLOCK_MAXLEN]; - try { // the whole process - for (int ib = 0; ib < BLOCK_MAXLEN; ib++) bArray[ib] = '\0'; - - lineLength = nBytesRead = 0; - // read up a block - remember how many bytes read - nBytesRead = bin.read(bArray); - strBlock = new String(bArray); - - lineLength = nBytesRead; - lineNumber = 1; - indexBlock = -1; - j = 0; - while(EOF == 0) - { - c = getchr(); - switch(c) - { - default: - string[j++] = c; - if(c != ',') - { - l_char = c; - } - break; - case ' ': - case '\t': - if(lookup(w_else) == 1) - { - gotelse(); - if(s_flg == 0 || j > 0)string[j++] = c; - indent_puts(); - s_flg = 0; - break; - } - if(s_flg == 0 || j > 0)string[j++] = c; - break; - case '\r': /* for MS Windows 95 */ - case '\n': - lineNumber++; - if (EOF==1) - { - break; - } - String j_string = new String(string); - - e_flg = lookup(w_else); - if(e_flg == 1) gotelse(); - if (lookup_com(w_cpp_comment) == 1) - { - if (string[j] == '\n') - { - string[j] = '\0'; - j--; - } - } - - indent_puts(); - fprintf(outfil, line_feed); - s_flg = 1; - if(e_flg == 1) - { - p_flg[level]++; - tabs++; - } - else - if(p_char == l_char) - { - a_flg = 1; - } - break; - case '{': - if(lookup(w_else) == 1)gotelse(); - s_if_lev[c_level] = if_lev; - s_if_flg[c_level] = if_flg; - if_lev = if_flg = 0; - c_level++; - if(s_flg == 1 && p_flg[level] != 0) - { - p_flg[level]--; - tabs--; - } - string[j++] = c; - indent_puts(); - getnl() ; - indent_puts(); - fprintf(outfil,"\n"); - tabs++; - s_flg = 1; - if(p_flg[level] > 0) - { - ind[level] = 1; - level++; - s_level[level] = c_level; - } - break; - case '}': - c_level--; - if (c_level < 0) - { - EOF = 1; - string[j++] = c; - indent_puts(); - break; - } - if((if_lev = s_if_lev[c_level]-1) < 0)if_lev = 0; - if_flg = s_if_flg[c_level]; - indent_puts(); - tabs--; - p_tabs(); - peekc = getchr(); - if( peekc == ';') - { - onechar = new StringBuffer(); - onechar.append(c); /* } */ - onechar.append(';'); - fprintf(outfil, onechar.toString()); - peek = -1; - peekc = '`'; - } - else - { - onechar = new StringBuffer(); - onechar.append(c); - fprintf(outfil, onechar.toString()); - peek = 1; - } - getnl(); - indent_puts(); - fprintf(outfil,"\n"); - s_flg = 1; - if(c_level < s_level[level]) - if(level > 0) level--; - if(ind[level] != 0) - { - tabs -= p_flg[level]; - p_flg[level] = 0; - ind[level] = 0; - } - break; - case '"': - case '\'': - string[j++] = c; - cc = getchr(); - while(cc != c) - { - // max. length of line should be 256 - string[j++] = cc; - - if(cc == '\\') - { - cc = string[j++] = getchr(); - } - if(cc == '\n') - { - lineNumber++; - indent_puts(); - s_flg = 1; - } - cc = getchr(); - - } - string[j++] = cc; - if(getnl() == 1) - { - l_char = cc; - peek = 1; - peekc = '\n'; - } - break; - case ';': - string[j++] = c; - indent_puts(); - if(p_flg[level] > 0 && ind[level] == 0) - { - tabs -= p_flg[level]; - p_flg[level] = 0; - } - getnl(); - indent_puts(); - fprintf(outfil,"\n"); - s_flg = 1; - if(if_lev > 0) - if(if_flg == 1) - { - if_lev--; - if_flg = 0; - } - else if_lev = 0; - break; - case '\\': - string[j++] = c; - string[j++] = getchr(); - break; - case '?': - q_flg = 1; - string[j++] = c; - break; - case ':': - string[j++] = c; - peekc = getchr(); - if(peekc == ':') - { - indent_puts(); - fprintf (outfil,":"); - peek = -1; - peekc = '`'; - break; - } - else - { - int double_colon = 0; - peek = 1; - } - - if(q_flg == 1) - { - q_flg = 0; - break; - } - if(lookup(w_ds) == 0 && lookup(w_case) == 0) - { - s_flg = 0; - indent_puts(); - } - else - { - tabs--; - indent_puts(); - tabs++; - } - peekc = getchr(); - if(peekc == ';') - { - fprintf(outfil,";"); - peek = -1; - peekc = '`'; - } - else - { - peek = 1; - } - getnl(); - indent_puts(); - fprintf(outfil,"\n"); - s_flg = 1; - break; - - case '/': - c0 = string[j]; - string[j++] = c; - peekc = getchr(); - - if(peekc == '/') - { - string[j++] = peekc; - peekc = '`'; - peek = -1; - cpp_comment(); - fprintf(outfil,"\n"); - break; - } - else - { - peek = 1; - } - - if(peekc != '*') { - break; - } - else - { - if (j > 0) string[j--] = '\0'; - if (j > 0) indent_puts(); - string[j++] = '/'; - string[j++] = '*'; - peek = -1; - peekc = '`'; - comment(); - break; - } - case '#': - string[j++] = c; - cc = getchr(); - while(cc != '\n') - { - string[j++] = cc; - cc = getchr(); - } - string[j++] = cc; - s_flg = 0; - indent_puts(); - s_flg = 1; - break; - case ')': - paren--; - if (paren < 0) - { - EOF = 1; - } - string[j++] = c; - indent_puts(); - if(getnl() == 1) - { - peekc = '\n'; - peek = 1; - if(paren != 0) - { - a_flg = 1; - } - else if(tabs > 0) - { - p_flg[level]++; - tabs++; - ind[level] = 0; - } - } - break; - case '(': - string[j++] = c; - paren++; - if ((lookup(w_for) == 1)) - { - c = get_string(); - while(c != ';') c = get_string(); - ct=0; - int for_done = 0; - while (for_done==0) - { - c = get_string(); - while(c != ')') - { - if(c == '(') ct++; - c = get_string(); - } - if(ct != 0) - { - ct--; - } - else for_done = 1; - } /* endwhile for_done */ - paren--; - if (paren < 0) - { - EOF = 1; - } - indent_puts(); - if(getnl() == 1) - { - peekc = '\n'; - peek = 1; - p_flg[level]++; - tabs++; - ind[level] = 0; - } - break; - } - - if(lookup(w_if_) == 1) - { - indent_puts(); - s_tabs[c_level][if_lev] = tabs; - sp_flg[c_level][if_lev] = p_flg[level]; - s_ind[c_level][if_lev] = ind[level]; - if_lev++; - if_flg = 1; - } - } /* end switch */ - - String j_string = new String(string); - - } // end while not EOF - - //formattedText = strOut.toString(); - - // save current (rough) selection point - int selectionEnd = editor.textarea.getSelectionEnd(); - - // make sure the caret would be past the end of the text - if (strOut.length() < selectionEnd - 1) { - selectionEnd = strOut.length() - 1; - } - - // replace with new bootiful text - // selectionEnd hopefully at least in the neighborhood - editor.setText(strOut.toString(), selectionEnd, selectionEnd); - - editor.sketch.setModified(); - - bin.close(); // close buff - - } catch (IOException ioe) { - editor.error(ioe); - //ioe.printStackTrace(); - } - - - // () {} check - - String ck_paren = new String("left"); - if (paren < 0) ck_paren = "right"; - - if (paren != 0) { - setUhOh("Uh oh... too many " + ck_paren + " parentheses."); - - } else { // check braces only if parens are ok - ck_paren = "left"; - if (c_level < 0) { - ck_paren = "right"; - } else if (c_level != 0) { - setUhOh("Uh oh... too many " + ck_paren + " curled braces."); - } - } - } - - - /* throw back the stuff to the editor */ - public String getFormattedText() - { - return formattedText; - } - - /* special edition of put string for comment processing */ public void putcoms() { @@ -793,26 +211,30 @@ public class AutoFormat { { if ((last_char != ';') && (sav_s_flg==1) ) { - fprintf(outfil, strBuffer.substring(i,j)); + //fprintf(outfil, strBuffer.substring(i,j)); + fprintf(strBuffer.substring(i,j)); } else { - fprintf(outfil, strBuffer); + //fprintf(outfil, strBuffer); + fprintf(strBuffer); } } else { if (string[i]=='*' || jdoc == 0) - fprintf (outfil, " "+strBuffer.substring(i,j)); + //fprintf (outfil, " "+strBuffer.substring(i,j)); + fprintf (" "+strBuffer.substring(i,j)); else - fprintf (outfil, " * "+strBuffer.substring(i,j)); + //fprintf (outfil, " * "+strBuffer.substring(i,j)); + fprintf (" * "+strBuffer.substring(i,j)); } j = 0; string[0] = '\0'; } } - public void cpp_comment() + public void cpp_comment() throws IOException { c = getchr(); while(c != '\n' && c != '\r' && j<133) @@ -842,7 +264,7 @@ public class AutoFormat { } - public char getchr() + public char getchr() throws IOException { if((peek < 0) && (last_char != ' ') && (last_char != '\t')) { @@ -862,8 +284,8 @@ public class AutoFormat { for (int ib=0; ib 0) { nBytesRead = bin.read(bArray); @@ -876,14 +298,15 @@ public class AutoFormat { } else { + //System.out.println("eof a"); EOF = 1; peekc = '\0'; } - } - catch(IOException ioe) - { - System.out.println(ioe.toString()); - } + //} + //catch(IOException ioe) + //{ + //System.out.println(ioe.toString()); + //} } else { @@ -909,13 +332,14 @@ public class AutoFormat { } /* read to new_line */ - public int getnl() + public int getnl() throws IOException { int save_s_flg; save_s_flg = tabs; peekc = getchr(); - while(peekc == '\t' || peekc == ' ') - { + //while ((peekc == '\t' || peekc == ' ') && + // (j < string.length)) { + while (peekc == '\t' || peekc == ' ') { string[j++] = peekc; peek = -1; peekc = '`'; @@ -1008,4 +432,512 @@ public class AutoFormat { } return (1); } -} \ No newline at end of file + + + public void show() { + StringBuffer onechar; + + String originalText = editor.textarea.getText(); + strOut = new StringBuffer(); + indentValue = Preferences.getInteger("editor.tabs.size"); + indentChar = new String(" "); + + lineNumber = 0; + //BLOCK_MAXLEN = 256; + c_level = if_lev = level = e_flg = paren = 0; + a_flg = q_flg = j = b_flg = tabs = 0; + if_flg = peek = -1; + peekc = '`'; + s_flg = 1; + bblank = ' '; + jdoc = 0; + + s_level = new int[10]; + sp_flg = new int[20][10]; + s_ind = new int[20][10]; + s_if_lev = new int[10]; + s_if_flg = new int[10]; + ind = new int[10]; + p_flg = new int[10]; + s_tabs = new int[20][10]; + + w_else = new String ("else"); + w_if_ = new String ("if"); + w_for = new String ("for"); + w_ds = new String ("default"); + w_case = new String ("case"); + w_cpp_comment = new String ("//"); + w_jdoc = new String ("/**"); + line_feed = new String ("\n"); + + // read as long as there is something to read + EOF = 0; // = 1 set in getchr when EOF + + bArray = new byte[BLOCK_MAXLEN]; + string = new char[BLOCK_MAXLEN]; + try { // the whole process + // open for input + ByteArrayInputStream in = + new ByteArrayInputStream(originalText.getBytes()); + + // add buffering to that InputStream + bin = new BufferedInputStream(in); + + for (int ib = 0; ib < BLOCK_MAXLEN; ib++) bArray[ib] = '\0'; + + lineLength = nBytesRead = 0; + // read up a block - remember how many bytes read + nBytesRead = bin.read(bArray); + strBlock = new String(bArray); + + lineLength = nBytesRead; + lineNumber = 1; + indexBlock = -1; + j = 0; + while (EOF == 0) + { + c = getchr(); + switch(c) + { + default: + string[j++] = c; + if(c != ',') + { + l_char = c; + } + break; + + case ' ': + case '\t': + if(lookup(w_else) == 1) + { + gotelse(); + if(s_flg == 0 || j > 0)string[j++] = c; + indent_puts(); + s_flg = 0; + break; + } + if(s_flg == 0 || j > 0)string[j++] = c; + break; + + case '\r': // for MS Windows 95 + case '\n': + lineNumber++; + if (EOF==1) + { + break; + } + String j_string = new String(string); + + e_flg = lookup(w_else); + if(e_flg == 1) gotelse(); + if (lookup_com(w_cpp_comment) == 1) + { + if (string[j] == '\n') + { + string[j] = '\0'; + j--; + } + } + + indent_puts(); + //fprintf(outfil, line_feed); + fprintf(line_feed); + s_flg = 1; + if(e_flg == 1) + { + p_flg[level]++; + tabs++; + } + else + if(p_char == l_char) + { + a_flg = 1; + } + break; + + case '{': + if(lookup(w_else) == 1)gotelse(); + s_if_lev[c_level] = if_lev; + s_if_flg[c_level] = if_flg; + if_lev = if_flg = 0; + c_level++; + if(s_flg == 1 && p_flg[level] != 0) + { + p_flg[level]--; + tabs--; + } + string[j++] = c; + indent_puts(); + getnl() ; + indent_puts(); + //fprintf(outfil,"\n"); + fprintf("\n"); + tabs++; + s_flg = 1; + if(p_flg[level] > 0) + { + ind[level] = 1; + level++; + s_level[level] = c_level; + } + break; + + case '}': + c_level--; + if (c_level < 0) + { + EOF = 1; + //System.out.println("eof b"); + string[j++] = c; + indent_puts(); + break; + } + if ((if_lev = s_if_lev[c_level]-1) < 0) + if_lev = 0; + if_flg = s_if_flg[c_level]; + indent_puts(); + tabs--; + p_tabs(); + peekc = getchr(); + if( peekc == ';') + { + onechar = new StringBuffer(); + onechar.append(c); // the } + onechar.append(';'); + //fprintf(outfil, onechar.toString()); + fprintf(onechar.toString()); + peek = -1; + peekc = '`'; + } + else + { + onechar = new StringBuffer(); + onechar.append(c); + //fprintf(outfil, onechar.toString()); + fprintf(onechar.toString()); + peek = 1; + } + getnl(); + indent_puts(); + //fprintf(outfil,"\n"); + fprintf("\n"); + s_flg = 1; + if(c_level < s_level[level]) + if(level > 0) level--; + if(ind[level] != 0) + { + tabs -= p_flg[level]; + p_flg[level] = 0; + ind[level] = 0; + } + break; + + case '"': + case '\'': + string[j++] = c; + cc = getchr(); + while(cc != c) + { + // max. length of line should be 256 + string[j++] = cc; + + if(cc == '\\') + { + cc = string[j++] = getchr(); + } + if(cc == '\n') + { + lineNumber++; + indent_puts(); + s_flg = 1; + } + cc = getchr(); + + } + string[j++] = cc; + if(getnl() == 1) + { + l_char = cc; + peek = 1; + peekc = '\n'; + } + break; + + case ';': + string[j++] = c; + indent_puts(); + if(p_flg[level] > 0 && ind[level] == 0) + { + tabs -= p_flg[level]; + p_flg[level] = 0; + } + getnl(); + indent_puts(); + //fprintf(outfil,"\n"); + fprintf("\n"); + s_flg = 1; + if(if_lev > 0) + if(if_flg == 1) + { + if_lev--; + if_flg = 0; + } + else if_lev = 0; + break; + + case '\\': + string[j++] = c; + string[j++] = getchr(); + break; + + case '?': + q_flg = 1; + string[j++] = c; + break; + + case ':': + string[j++] = c; + peekc = getchr(); + if(peekc == ':') + { + indent_puts(); + //fprintf (outfil,":"); + fprintf(":"); + peek = -1; + peekc = '`'; + break; + } + else + { + int double_colon = 0; + peek = 1; + } + + if(q_flg == 1) + { + q_flg = 0; + break; + } + if(lookup(w_ds) == 0 && lookup(w_case) == 0) + { + s_flg = 0; + indent_puts(); + } + else + { + tabs--; + indent_puts(); + tabs++; + } + peekc = getchr(); + if(peekc == ';') + { + //fprintf(outfil,";"); + fprintf(";"); + peek = -1; + peekc = '`'; + } + else + { + peek = 1; + } + getnl(); + indent_puts(); + //fprintf(outfil,"\n"); + fprintf("\n"); + s_flg = 1; + break; + + case '/': + c0 = string[j]; + string[j++] = c; + peekc = getchr(); + + if(peekc == '/') + { + string[j++] = peekc; + peekc = '`'; + peek = -1; + cpp_comment(); + //fprintf(outfil,"\n"); + fprintf("\n"); + break; + } + else + { + peek = 1; + } + + if(peekc != '*') { + break; + } + else + { + if (j > 0) string[j--] = '\0'; + if (j > 0) indent_puts(); + string[j++] = '/'; + string[j++] = '*'; + peek = -1; + peekc = '`'; + comment(); + break; + } + + case '#': + string[j++] = c; + cc = getchr(); + while(cc != '\n') + { + string[j++] = cc; + cc = getchr(); + } + string[j++] = cc; + s_flg = 0; + indent_puts(); + s_flg = 1; + break; + + case ')': + paren--; + if (paren < 0) + { + EOF = 1; + //System.out.println("eof c"); + } + string[j++] = c; + indent_puts(); + if(getnl() == 1) + { + peekc = '\n'; + peek = 1; + if(paren != 0) + { + a_flg = 1; + } + else if(tabs > 0) + { + p_flg[level]++; + tabs++; + ind[level] = 0; + } + } + break; + + case '(': + string[j++] = c; + paren++; + if ((lookup(w_for) == 1)) + { + c = get_string(); + while(c != ';') c = get_string(); + ct=0; + int for_done = 0; + while (for_done==0) + { + c = get_string(); + while(c != ')') + { + if(c == '(') ct++; + c = get_string(); + } + if(ct != 0) + { + ct--; + } + else for_done = 1; + } // endwhile for_done + paren--; + if (paren < 0) + { + EOF = 1; + //System.out.println("eof d"); + } + indent_puts(); + if(getnl() == 1) + { + peekc = '\n'; + peek = 1; + p_flg[level]++; + tabs++; + ind[level] = 0; + } + break; + } + + if(lookup(w_if_) == 1) + { + indent_puts(); + s_tabs[c_level][if_lev] = tabs; + sp_flg[c_level][if_lev] = p_flg[level]; + s_ind[c_level][if_lev] = ind[level]; + if_lev++; + if_flg = 1; + } + } // end switch + + //System.out.println("string len is " + string.length); + //if (EOF == 1) System.out.println(string); + String j_string = new String(string); + + } // end while not EOF + + /* + int bad; + while ((bad = bin.read()) != -1) { + System.out.print((char) bad); + } + */ + /* + char bad; + //while ((bad = getchr()) != 0) { + while (true) { + getchr(); + if (peek != -1) { + System.out.print(last_char); + } else { + break; + } + } + */ + + // save current (rough) selection point + int selectionEnd = editor.textarea.getSelectionEnd(); + + // make sure the caret would be past the end of the text + if (strOut.length() < selectionEnd - 1) { + selectionEnd = strOut.length() - 1; + } + + bin.close(); // close buff + + String formattedText = strOut.toString(); + if (formattedText.equals(originalText)) { + editor.message("No changes necessary for Auto Format."); + + } else { + // replace with new bootiful text + // selectionEnd hopefully at least in the neighborhood + editor.setText(formattedText, selectionEnd, selectionEnd); + editor.sketch.setModified(true); + + // warn user if there are too many parens in either direction + if (paren != 0) { + editor.error("Warning: Too many " + + ((paren < 0) ? "right" : "left") + + " parentheses."); + + } else if (c_level != 0) { // check braces only if parens are ok + editor.error("Warning: Too many " + + ((c_level < 0) ? "right" : "left") + + " curly braces."); + } else { + editor.message("Auto Format finished."); + } + } + + } catch (Exception e) { + editor.error(e); + } + } +} diff --git a/app/tools/ExportFolder.java b/app/tools/ExportFolder.java new file mode 100755 index 000000000..e4e7492bb --- /dev/null +++ b/app/tools/ExportFolder.java @@ -0,0 +1,120 @@ +/* -*- mode: jde; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + ExportFolder - tool to export all sketches within a certain folder + Part of the Processing project - http://processing.org + + Copyright (c) 2005-06 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.*; + +import java.io.*; +import java.text.*; +import java.util.*; +import java.util.zip.*; + +import javax.swing.*; + + +public class ExportFolder { + Editor editor; + static JFileChooser fc; + + + public ExportFolder(Editor editor) { + this.editor = editor; + + if (fc == null) { + fc = new JFileChooser(); + fc.setSelectedFile(new File(Sketchbook.getSketchbookPath())); + fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + } + } + + + public void show() { + if (fc.showOpenDialog(new JDialog()) != JFileChooser.APPROVE_OPTION) { + return; + } + + File folder = fc.getSelectedFile(); + // export everything under this folder + + Vector sketches = new Vector(); + try { + addSketches(sketches, folder); + } catch (IOException e) { + e.printStackTrace(); + } + + boolean success = true; + int counter = 0; + + try { + // iterate through the list + Enumeration en = sketches.elements(); + while (en.hasMoreElements()) { + editor.message("Exporting sketch " + (++counter) + + " of " + sketches.size()); + String path = (String) en.nextElement(); + editor.handleOpen(path); + // success may not be that useful, usually an ex is thrown + success = editor.sketch.exportApplet(new Target( + System.getProperty("user.dir") + File.separator + "lib" + + File.separator + "targets", Preferences.get("build.target"))); + if (!success) break; + //System.out.println("success was " + success); + } + } catch (Exception e) { + editor.error(e); + success = false; + //e.printStackTrace(); + } + + if (success) { + editor.message("Done exporting."); + } // else the error message will be visible + } + + + protected void addSketches(Vector sketches, File folder) throws IOException { + + // skip .DS_Store files, etc + if (!folder.isDirectory()) return; // false; + + String list[] = folder.list(); + // if a bad folder or something like that, this might come back null + if (list == null) return; // false; + + for (int i = 0; i < list.length; i++) { + if (list[i].charAt(0) == '.') continue; + + File subfolder = new File(folder, list[i]); + File entry = new File(subfolder, list[i] + ".pde"); + // if a .pde file of the same prefix as the folder exists.. + if (entry.exists()) { + sketches.add(entry.getAbsolutePath()); + + } else if (subfolder.isDirectory()) { // only follow if a dir + addSketches(sketches, subfolder); + } + } + } +} diff --git a/build/macosx/Arduino.xcodeproj/project.pbxproj b/build/macosx/Arduino.xcodeproj/project.pbxproj index ee8d031fe..dd4247912 100644 --- a/build/macosx/Arduino.xcodeproj/project.pbxproj +++ b/build/macosx/Arduino.xcodeproj/project.pbxproj @@ -219,6 +219,10 @@ 33AF61B30965C54B00B514A9 /* WTreeParser.java in Sources */ = {isa = PBXBuildFile; fileRef = 33FFFE520965BD110016AC38 /* WTreeParser.java */; }; 33AF61B40965C54B00B514A9 /* JEditTextArea.java in Sources */ = {isa = PBXBuildFile; fileRef = 33FFFE630965BD110016AC38 /* JEditTextArea.java */; }; 33AF61B50965C54B00B514A9 /* Base.java in Sources */ = {isa = PBXBuildFile; fileRef = 33FFFE240965BD100016AC38 /* Base.java */; }; + 33BEDDB109D6DC1300430D5B /* Library.java in Sources */ = {isa = PBXBuildFile; fileRef = 33BEDDAF09D6DC1300430D5B /* Library.java */; }; + 33BEDDB209D6DC1300430D5B /* LibraryManager.java in Sources */ = {isa = PBXBuildFile; fileRef = 33BEDDB009D6DC1300430D5B /* LibraryManager.java */; }; + 33BEDDD509D6E8D800430D5B /* Archiver.java in Sources */ = {isa = PBXBuildFile; fileRef = 33BEDDD309D6E8D800430D5B /* Archiver.java */; }; + 33BEDDD609D6E8D800430D5B /* ExportFolder.java in Sources */ = {isa = PBXBuildFile; fileRef = 33BEDDD409D6E8D800430D5B /* ExportFolder.java */; }; 33CF03B209662CB700F2C9A9 /* arduino.icns in Resources */ = {isa = PBXBuildFile; fileRef = 33CF03B009662CA800F2C9A9 /* arduino.icns */; }; 33CF03CC09662DC000F2C9A9 /* mrj.jar in CopyFiles */ = {isa = PBXBuildFile; fileRef = 33AF620C0965D67900B514A9 /* mrj.jar */; settings = {JAVA_ARCHIVE_SUBDIR = ../shared/lib; }; }; 33CF03CD09662DC000F2C9A9 /* RXTXcomm.jar in CopyFiles */ = {isa = PBXBuildFile; fileRef = 33AF620F0965D67A00B514A9 /* RXTXcomm.jar */; settings = {JAVA_ARCHIVE_SUBDIR = ../shared/lib; }; }; @@ -371,6 +375,10 @@ 33AF620D0965D67900B514A9 /* oro.jar */ = {isa = PBXFileReference; lastKnownFileType = archive.jar; path = oro.jar; sourceTree = ""; }; 33AF620E0965D67A00B514A9 /* registry.jar */ = {isa = PBXFileReference; lastKnownFileType = archive.jar; path = registry.jar; sourceTree = ""; }; 33AF620F0965D67A00B514A9 /* RXTXcomm.jar */ = {isa = PBXFileReference; lastKnownFileType = archive.jar; path = RXTXcomm.jar; sourceTree = ""; }; + 33BEDDAF09D6DC1300430D5B /* Library.java */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.java; path = Library.java; sourceTree = ""; }; + 33BEDDB009D6DC1300430D5B /* LibraryManager.java */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.java; path = LibraryManager.java; sourceTree = ""; }; + 33BEDDD309D6E8D800430D5B /* Archiver.java */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.java; path = Archiver.java; sourceTree = ""; }; + 33BEDDD409D6E8D800430D5B /* ExportFolder.java */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.java; path = ExportFolder.java; sourceTree = ""; }; 33CF03B009662CA800F2C9A9 /* arduino.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; name = arduino.icns; path = dist/arduino.icns; sourceTree = ""; }; 33DD8FB6096AC8DA0013AF8F /* Arduino.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Arduino.app; sourceTree = BUILT_PRODUCTS_DIR; }; 33FF01DC0965BD160016AC38 /* examples.zip */ = {isa = PBXFileReference; lastKnownFileType = archive.zip; path = examples.zip; sourceTree = ""; }; @@ -738,6 +746,8 @@ 33FFFE220965BD100016AC38 /* app */ = { isa = PBXGroup; children = ( + 33BEDDAF09D6DC1300430D5B /* Library.java */, + 33BEDDB009D6DC1300430D5B /* LibraryManager.java */, 33FFFE240965BD100016AC38 /* Base.java */, 33FFFE260965BD100016AC38 /* Compiler.java */, 33FFFE270965BD100016AC38 /* Editor.java */, @@ -847,6 +857,8 @@ 33FFFE710965BD110016AC38 /* tools */ = { isa = PBXGroup; children = ( + 33BEDDD309D6E8D800430D5B /* Archiver.java */, + 33BEDDD409D6E8D800430D5B /* ExportFolder.java */, 33FFFE720965BD110016AC38 /* AutoFormat.java */, ); path = tools; @@ -1126,6 +1138,10 @@ 33AF61B40965C54B00B514A9 /* JEditTextArea.java in Sources */, 33AF61B50965C54B00B514A9 /* Base.java in Sources */, 332D4DB609CF147F00BF81F6 /* Sizer.java in Sources */, + 33BEDDB109D6DC1300430D5B /* Library.java in Sources */, + 33BEDDB209D6DC1300430D5B /* LibraryManager.java in Sources */, + 33BEDDD509D6E8D800430D5B /* Archiver.java in Sources */, + 33BEDDD609D6E8D800430D5B /* ExportFolder.java in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/readme.txt b/readme.txt index acd20b3ce..6bd07b97d 100644 --- a/readme.txt +++ b/readme.txt @@ -53,8 +53,13 @@ PWM now working on pin 11 (in addition to pins 9 and 10). Slowed PWM frequency (on all three PWM pins) to 1KHz. Now give an error if compiled sketch is too big. Fixed abs(), min(), max(), and constrain() macros. +Added menu items to the IDE to burn bootloader. +Now display binary sketch size on upload, and give error if too big. +Added C++ serial library. +Resynced with Processing/Wiring IDE code (improved auto-format, faster logging +to serial monitor console, other bug fixes) -0003 +0003 - 2006.01.16 API Changes Reversed the analog input pins to correspond to newer boards. This means