From 0df6ce1580863dae986877132cbe5f15640ff04c Mon Sep 17 00:00:00 2001 From: francisli Date: Thu, 27 Jan 2005 06:00:01 +0000 Subject: [PATCH] Initial revision --- app/PdeCompiler.java | 560 ++++++++++ app/PdeEditor.java | 1862 +++++++++++++++++++++++++++++++++ app/PdeEditorButtons.java | 417 ++++++++ app/PdeEmulator.java | 48 + app/PdePreprocessor.java | 419 ++++++++ app/PdePreverifier.java | 55 + app/PdeSketch.java | 2034 ++++++++++++++++++++++++++++++++++++ build/shared/lib/mobile.mf | 6 + build/windows/build.xml | 111 ++ 9 files changed, 5512 insertions(+) create mode 100755 app/PdeCompiler.java create mode 100755 app/PdeEditor.java create mode 100755 app/PdeEditorButtons.java create mode 100755 app/PdeEmulator.java create mode 100755 app/PdePreprocessor.java create mode 100755 app/PdePreverifier.java create mode 100755 app/PdeSketch.java create mode 100755 build/shared/lib/mobile.mf create mode 100755 build/windows/build.xml diff --git a/app/PdeCompiler.java b/app/PdeCompiler.java new file mode 100755 index 000000000..b03ae431d --- /dev/null +++ b/app/PdeCompiler.java @@ -0,0 +1,560 @@ +/* -*- mode: jde; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + PdeCompiler - default compiler class that connects to jikes + Part of the Processing project - http://processing.org + + Copyright (c) 2001-03 + Ben Fry, Massachusetts Institute of Technology and + Casey Reas, Interaction Design Institute Ivrea + + 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 +*/ + +import processing.core.*; + +import java.io.*; +import java.util.*; +import java.util.zip.*; +import javax.swing.*; + +public class PdeCompiler implements PdeMessageConsumer { + static final String BUGS_URL = + "http://processing.org/bugs/"; + static final String SUPER_BADNESS = + "Compiler error, please submit this code to " + BUGS_URL; + + PdeSketch sketch; + String buildPath; + + //String buildPath; + //String className; + //File includeFolder; + PdeException exception; + //PdeEditor editor; + + /* + public PdeCompiler(String buildPath, String className, + File includeFolder, PdeEditor editor) { + this.buildPath = buildPath; + this.includeFolder = includeFolder; + this.className = className; + this.editor = editor; + } + + + public boolean compile(PrintStream leechErr) { + */ + + public PdeCompiler() { } // consider this a warning, you werkin soon. + + + public boolean compile(PdeSketch sketch, String buildPath, String bootClassPath) + throws PdeException { + + this.sketch = sketch; + this.buildPath = buildPath; + + // the pms object isn't used for anything but storage + PdeMessageStream pms = new PdeMessageStream(this); + + String baseCommand[] = new String[] { + // user.dir is folder containing P5 (and therefore jikes) + // macosx needs the extra path info. linux doesn't like it, though + // windows doesn't seem to care. write once, headache anywhere. + ((PdeBase.platform != PdeBase.MACOSX) ? "jikes" : + System.getProperty("user.dir") + File.separator + "jikes"), + + // this doesn't help much.. also java 1.4 seems to not support + // -source 1.1 for javac, and jikes seems to also have dropped it. + // for versions of jikes that don't complain, "final int" inside + // a function doesn't throw an error, so it could just be a + // ms jvm error that this sort of thing doesn't work. blech. + //"-source", + //"1.1", + + // necessary to make output classes compatible with 1.1 + // i.e. so that exported applets can work with ms jvm on the web + "-target", + PdePreferences.get("preproc.jdk_version"), //"1.1", + // let the incompatability headache begin + + // used when run without a vm ("expert" mode) + "-bootclasspath", + bootClassPath, + + // needed for macosx so that the classpath is set properly + // also for windows because qtjava will most likely be here + // and for linux, it just doesn't hurt + "-classpath", + sketch.classPath, //calcClassPath(includeFolder), + + "-nowarn", // we're not currently interested in warnings + "+E", // output errors in machine-parsable format + "-d", buildPath // output the classes in the buildPath + //buildPath + File.separator + className + ".java" // file to compile + }; + + // make list of code files that need to be compiled + // (some files are skipped if they contain no class) + String preprocNames[] = new String[sketch.codeCount]; + int preprocCount = 0; + for (int i = 0; i < sketch.codeCount; i++) { + if (sketch.code[i].preprocName != null) { + preprocNames[preprocCount++] = sketch.code[i].preprocName; + } + } + String command[] = new String[baseCommand.length + preprocCount]; + System.arraycopy(baseCommand, 0, command, 0, baseCommand.length); + // append each of the files to the command string + for (int i = 0; i < preprocCount; i++) { + command[baseCommand.length + i] = + buildPath + File.separator + preprocNames[i]; + } + + /* + String command[] = new String[baseCommand.length + sketch.codeCount]; + System.arraycopy(baseCommand, 0, command, 0, baseCommand.length); + // append each of the files to the command string + for (int i = 0; i < sketch.codeCount; i++) { + command[baseCommand.length + i] = + buildPath + File.separator + sketch.code[i].preprocName; + } + */ + + //for (int i = 0; i < command.length; i++) { + //System.out.println("cmd " + i + ": " + command[i]); + //} + + firstErrorFound = false; // haven't found any errors yet + secondErrorFound = false; + + int result = 0; // pre-initialized to quiet a bogus warning from jikes + try { + // execute the compiler, and create threads to deal + // with the input and error streams + // + Process process = Runtime.getRuntime().exec(command); + + PdeMessageSiphon errSiphon = new PdeMessageSiphon(process.getErrorStream(), this); + PdeMessageSiphon inSiphon = new PdeMessageSiphon(process.getInputStream(), this); + + // wait for the process to finish. if interrupted + // before waitFor returns, continue waiting + // + boolean compiling = true; + while (compiling) { + try { + result = process.waitFor(); + //System.out.println("result is " + result); + compiling = false; + } catch (InterruptedException ignored) { + } + } + + } catch (Exception e) { + String msg = e.getMessage(); + if ((msg != null) && (msg.indexOf("jikes: not found") != -1)) { + //System.err.println("jikes is missing"); + PdeBase.showWarning("Compiler error", + "Could not find the compiler.\n" + + "jikes 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(), barf this back to build() + // which will barf it back to PdeEditor. if you're having trouble + // discerning the imagery, consider how cows regurgitate their food + // to digest it, and the fact that they have five stomaches. + // + //System.out.println("throwing up " + exception); + if (exception != null) throw exception; + + // if the result isn't a known, expected value it means that something + // is fairly wrong, one possibility is that jikes has crashed. + // + if (result != 0 && result != 1 ) { + //exception = new PdeException(SUPER_BADNESS); + //editor.error(exception); // this will instead be thrown + PdeBase.openURL(BUGS_URL); + throw new PdeException(SUPER_BADNESS); + } + + // success would mean that 'result' is set to zero + return (result == 0); // ? true : false; + } + + + boolean firstErrorFound; + boolean secondErrorFound; + + /** + * Part of the PdeMessageConsumer 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 PdeEditor. + */ + public void message(String s) { + // This receives messages as full lines, so a newline needs + // to be added as they're printed to the console. + System.err.println(s); + + // ignore cautions + if (s.indexOf("Caution") != -1) return; + + // jikes always uses a forward slash character as its separator, + // so replace any platform-specific separator characters before + // attemping to compare + // + String buildPathSubst = buildPath.replace(File.separatorChar, '/') + "/"; + + String partialTempPath = null; + int partialStartIndex = -1; //s.indexOf(partialTempPath); + int fileIndex = -1; // use this to build a better exception + + // iterate through the project files to see who's causing the trouble + for (int i = 0; i < sketch.codeCount; i++) { + if (sketch.code[i].preprocName == null) continue; + + partialTempPath = buildPathSubst + sketch.code[i].preprocName; + partialStartIndex = s.indexOf(partialTempPath); + if (partialStartIndex != -1) { + fileIndex = i; + //System.out.println("fileIndex is " + fileIndex); + break; + } + } + //+ className + ".java"; + + // if the partial temp path appears in the error message... + // + //int partialStartIndex = s.indexOf(partialTempPath); + if (partialStartIndex != -1) { + + // skip past the path and parse the int after the first colon + // + String s1 = s.substring(partialStartIndex + + partialTempPath.length() + 1); + int colon = s1.indexOf(':'); + int lineNumber = Integer.parseInt(s1.substring(0, colon)); + //System.out.println("pde / line number: " + lineNumber); + + if (fileIndex == 0) { // main class, figure out which tab + for (int i = 1; i < sketch.codeCount; i++) { + if (sketch.code[i].flavor == PdeSketch.PDE) { + if (sketch.code[i].lineOffset < lineNumber) { + fileIndex = i; + //System.out.println("i'm thinkin file " + i); + } + } + } + if (fileIndex != 0) { // if found another culprit + lineNumber -= sketch.code[fileIndex].lineOffset; + //System.out.println("i'm sayin line " + lineNumber); + } + } + + //String s2 = s1.substring(colon + 2); + int err = s1.indexOf("Error:"); + if (err != -1) { + + // if the first error has already been found, then this must be + // (at least) the second error found + if (firstErrorFound) { + secondErrorFound = true; + return; + } + + // if executing at this point, this is *at least* the first error + firstErrorFound = true; + + //err += "error:".length(); + String description = s1.substring(err + "Error:".length()); + description = description.trim(); + + String hasLoop = "The method \"void loop();\" with default access"; + if (description.indexOf(hasLoop) != -1) { + description = + "Rename loop() to draw() in Processing 0070 and higher"; + } + + String constructorProblem = + "No applicable overload was found for a constructor of type"; + if (description.indexOf(constructorProblem) != -1) { + //"simong.particles.ParticleSystem". Perhaps you wanted the overloaded version "ParticleSystem();" instead? + int nextSentence = description.indexOf("\".") + 3; + description = description.substring(nextSentence); + } + + String overloadProblem = "No applicable overload"; + if (description.indexOf(overloadProblem) != -1) { + int nextSentence = description.indexOf("\".") + 3; + description = description.substring(nextSentence); + } + + // c:/fry/processing/build/windows/work/lib/build/Temporary_6858_2476.java:1:34:1:41: Semantic Error: You need to modify your classpath, sourcepath, bootclasspath, and/or extdirs setup. Package "poo/shoe" could not be found in: + String classpathProblem = "You need to modify your classpath"; + if (description.indexOf(classpathProblem) != -1) { + if (description.indexOf("quicktime/std") != -1) { + // special case for the quicktime libraries + description = + "To run sketches that use the Processing video library, " + + "you must first install QuickTime for Java."; + + } else { + int nextSentence = description.indexOf(". Package") + 2; + description = + description.substring(nextSentence, description.indexOf(':')) + + " the code folder or in any libraries."; + } + } + + if (description.indexOf("Type java.io.Serializable was not found.") != -1) { + return; + } + if (description.indexOf("Type java.lang.Cloneable was not found.") != -1) { + return; + } + + //System.out.println("description = " + description); + //System.out.println("creating exception " + exception); + exception = new PdeException(description, fileIndex, lineNumber-1, -1); + + // NOTE!! major change here, this exception will be queued + // here to be thrown by the compile() function + //editor.error(exception); + + } else { + System.err.println("i suck: " + s); + } + + } else { + // this isn't the start of an error line, so don't attempt to parse + // a line number out of it. + + // if the second error hasn't been discovered yet, these lines + // are probably associated with the first error message, + // which is already in the status bar, and are likely to be + // of interest to the user, so spit them to the console. + // + if (!secondErrorFound) { + System.err.println(s); + } + } + } + + + static String bootClassPath; + + static public String calcBootClassPath() { + if (bootClassPath == null) { + String additional = ""; + if (PdeBase.platform == PdeBase.MACOSX) { + additional = + contentsToClassPath(new File("/System/Library/Java/Extensions/")); + } + bootClassPath = System.getProperty("sun.boot.class.path") + additional; + } + return bootClassPath; + } + + + /// + + + /** + * Return the path for a folder, with appended paths to + * any .jar or .zip files inside that folder. + * This will prepend a colon (or whatever the path separator is) + * so that it can be directly appended to another path string. + * + * This will always add the root folder as well, and doesn't bother + * checking to see if there are any .class files in the folder or + * within a subfolder. + */ + static public String contentsToClassPath(File folder) { + if (folder == null) return ""; + + StringBuffer abuffer = new StringBuffer(); + String sep = System.getProperty("path.separator"); + + try { + // add the folder itself in case any unzipped files + String path = folder.getCanonicalPath(); + abuffer.append(sep); + abuffer.append(path); + + if (!path.endsWith(File.separator)) { + path += File.separator; + } + //System.out.println("path is " + path); + + String list[] = folder.list(); + for (int i = 0; i < list.length; i++) { + if (list[i].toLowerCase().endsWith(".jar") || + list[i].toLowerCase().endsWith(".zip")) { + abuffer.append(sep); + abuffer.append(path); + abuffer.append(list[i]); + } + } + } catch (IOException e) { + e.printStackTrace(); // this would be odd + } + //System.out.println("included path is " + abuffer.toString()); + //packageListFromClassPath(abuffer.toString()); // WHY? + return abuffer.toString(); + } + + + /** + * A classpath, separated by the path separator, will contain + * a series of .jar/.zip files or directories containing .class + * files, or containing subdirectories that have .class files. + * + * @param path the input classpath + * @return array of possible package names + */ + static public String[] packageListFromClassPath(String path) { + Hashtable table = new Hashtable(); + String pieces[] = + PApplet.split(path, File.pathSeparatorChar); + + for (int i = 0; i < pieces.length; i++) { + //System.out.println("checking piece '" + pieces[i] + "'"); + if (pieces[i].length() == 0) continue; + + if (pieces[i].toLowerCase().endsWith(".jar") || + pieces[i].toLowerCase().endsWith(".zip")) { + packageListFromZip(pieces[i], table); + + } else { // it's another type of file or directory + File dir = new File(pieces[i]); + if (dir.exists() && dir.isDirectory()) { + packageListFromFolder(dir, null, table); + //importCount = magicImportsRecursive(dir, null, + // table); + //imports, importCount); + } + } + } + int tableCount = table.size(); + String output[] = new String[tableCount]; + int index = 0; + Enumeration e = table.keys(); + while (e.hasMoreElements()) { + output[index++] = ((String) e.nextElement()).replace('/', '.'); + } + //System.arraycopy(imports, 0, output, 0, importCount); + return output; + } + + + static private void packageListFromZip(String filename, Hashtable table) { + try { + ZipFile file = new ZipFile(filename); + Enumeration entries = file.entries(); + while (entries.hasMoreElements()) { + ZipEntry entry = (ZipEntry) entries.nextElement(); + + if (!entry.isDirectory()) { + String name = entry.getName(); + + if (name.endsWith(".class")) { + int slash = name.lastIndexOf('/'); + if (slash == -1) continue; + + String pname = name.substring(0, slash); + if (table.get(pname) == null) { + table.put(pname, new Object()); + } + } + } + } + } catch (IOException e) { + System.err.println("Ignoring " + filename + " (" + e.getMessage() + ")"); + //e.printStackTrace(); + } + } + + + /** + * Make list of package names by traversing a directory hierarchy. + * Each time a class is found in a folder, add its containing set + * of folders to the package list. If another folder is found, + * walk down into that folder and continue. + */ + static private void packageListFromFolder(File dir, String sofar, + Hashtable table) { + //String imports[], + //int importCount) { + //System.err.println("checking dir '" + dir + "'"); + boolean foundClass = false; + 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]); + if (sub.isDirectory()) { + String nowfar = + (sofar == null) ? files[i] : (sofar + "." + files[i]); + packageListFromFolder(sub, nowfar, table); + //System.out.println(nowfar); + //imports[importCount++] = nowfar; + //importCount = magicImportsRecursive(sub, nowfar, + // imports, importCount); + } else if (!foundClass) { // if no classes found in this folder yet + if (files[i].endsWith(".class")) { + //System.out.println("unique class: " + files[i] + " for " + sofar); + table.put(sofar, new Object()); + foundClass = true; + } + } + } + //return importCount; + } + + /* + static public int magicImportsRecursive(File dir, String sofar, + Hashtable table) { + //String imports[], + //int importCount) { + System.err.println("checking dir '" + dir + "'"); + 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]); + if (sub.isDirectory()) { + String nowfar = (sofar == null) ? + files[i] : (sofar + "." + files[i]); + //System.out.println(nowfar); + imports[importCount++] = nowfar; + + importCount = magicImportsRecursive(sub, nowfar, + imports, importCount); + } + } + return importCount; + } + */ +} diff --git a/app/PdeEditor.java b/app/PdeEditor.java new file mode 100755 index 000000000..e462a9fd7 --- /dev/null +++ b/app/PdeEditor.java @@ -0,0 +1,1862 @@ +/* -*- mode: jde; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + PdeEditor - main editor panel for the processing development environment + Part of the Processing project - http://processing.org + + Except where noted, code is written by Ben Fry and + Copyright (c) 2001-04 Massachusetts Institute of Technology + + 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 +*/ + +import java.awt.*; +import java.awt.event.*; +import java.io.*; +import java.lang.reflect.*; +import java.net.*; +import java.util.*; +import java.util.zip.*; + +import javax.swing.*; +import javax.swing.border.*; +import javax.swing.event.*; +import javax.swing.text.*; +import javax.swing.undo.*; + +import com.oroinc.text.regex.*; + +import com.apple.mrj.*; + + +public class PdeEditor extends JFrame +implements MRJAboutHandler, MRJQuitHandler, MRJPrefsHandler +{ + // yeah + static final String WINDOW_TITLE = "Processing"; + + // p5 icon for the window + Image icon; + + // otherwise, if the window is resized with the message label + // set to blank, it's preferredSize() will be fukered + static final String EMPTY = + " " + + " " + + " "; + + static final int HANDLE_NEW = 1; + static final int HANDLE_OPEN = 2; + static final int HANDLE_QUIT = 3; + int checkModifiedMode; + String handleOpenPath; + boolean handleNewShift; + boolean handleNewLibrary; + //String handleSaveAsPath; + //String openingName; + + PdeEditorButtons buttons; + PdeEditorHeader header; + PdeEditorStatus status; + PdeEditorConsole console; + + JSplitPane splitPane; + JPanel consolePanel; + + // currently opened program + public PdeSketch sketch; + + public JEditTextArea textarea; + PdeEditorListener listener; + + // runtime information and window placement + Point appletLocation; + Point presentLocation; + Window presentationWindow; + RunButtonWatcher watcher; + PdeRuntime runtime; + + //boolean externalRuntime; + //String externalPaths; + //File externalCode; + + JMenuItem exportAppItem; + JMenuItem saveMenuItem; + JMenuItem saveAsMenuItem; + //JMenuItem beautifyMenuItem; + + // + + boolean running; + boolean presenting; + + // undo fellers + JMenuItem undoItem, redoItem; + protected UndoAction undoAction; + protected RedoAction redoAction; + static public UndoManager undo = new UndoManager(); // editor needs this guy + + // + + //PdeHistory history; // TODO re-enable history + PdeSketchbook sketchbook; + PdePreferences preferences; + PdeEditorFind find; + + //static Properties keywords; // keyword -> reference html lookup + + + public PdeEditor() { + super(WINDOW_TITLE + " - " + PdeBase.VERSION); + // this is needed by just about everything else + preferences = new PdePreferences(); + + // #@$*(@#$ apple.. always gotta think different + MRJApplicationUtils.registerAboutHandler(this); + MRJApplicationUtils.registerPrefsHandler(this); + MRJApplicationUtils.registerQuitHandler(this); + + // set the window icon + try { + icon = PdeBase.getImage("icon.gif", this); + setIconImage(icon); + } catch (Exception e) { } // fail silently, no big whup + + + // add listener to handle window close box hit event + addWindowListener(new WindowAdapter() { + public void windowClosing(WindowEvent e) { + handleQuit(); + } + }); + + PdeKeywords keywords = new PdeKeywords(); + // TODO re-enable history + //history = new PdeHistory(this); + sketchbook = new PdeSketchbook(this); + + JMenuBar menubar = new JMenuBar(); + menubar.add(buildFileMenu()); + menubar.add(buildEditMenu()); + menubar.add(buildSketchMenu()); + menubar.add(buildToolsMenu()); + // what platform has their help menu way on the right? + //if ((PdeBase.platform == PdeBase.WINDOWS) || + //menubar.add(Box.createHorizontalGlue()); + menubar.add(buildHelpMenu()); + + setJMenuBar(menubar); + + // doesn't matter when this is created, just make it happen at some point + find = new PdeEditorFind(PdeEditor.this); + + Container pain = getContentPane(); + pain.setLayout(new BorderLayout()); + + buttons = new PdeEditorButtons(this); + pain.add("West", buttons); + + JPanel rightPanel = new JPanel(); + rightPanel.setLayout(new BorderLayout()); + + header = new PdeEditorHeader(this); + rightPanel.add(header, BorderLayout.NORTH); + + textarea = new JEditTextArea(new PdeTextAreaDefaults()); + textarea.setRightClickPopup(new TextAreaPopup()); + textarea.setTokenMarker(new PdeKeywords()); + + textarea.setHorizontalOffset(5); + //textarea.setBorder(new EmptyBorder(0, 20, 0, 0)); + //textarea.setBackground(Color.white); + + // assemble console panel, consisting of status area and the console itself + consolePanel = new JPanel(); + //System.out.println(consolePanel.getInsets()); + consolePanel.setLayout(new BorderLayout()); + + status = new PdeEditorStatus(this); + consolePanel.add(status, BorderLayout.NORTH); + + console = new PdeEditorConsole(this); + consolePanel.add(console, BorderLayout.CENTER); + + splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, + textarea, consolePanel); + + splitPane.setOneTouchExpandable(true); + // repaint child panes while resizing + splitPane.setContinuousLayout(true); + // if window increases in size, give all of increase to textarea (top pane) + splitPane.setResizeWeight(1D); + + // to fix ugliness.. normally macosx java 1.3 puts an + // ugly white border around this object, so turn it off. + if (PdeBase.platform == PdeBase.MACOSX) { + splitPane.setBorder(null); + } + + // the default size on windows is too small and kinda ugly + int dividerSize = PdePreferences.getInteger("editor.divider.size"); + if (dividerSize != 0) { + splitPane.setDividerSize(dividerSize); + } + + rightPanel.add(splitPane, BorderLayout.CENTER); + + pain.add("Center", rightPanel); + + // hopefully these are no longer needed w/ swing + // (har har har.. that was wishful thinking) + listener = new PdeEditorListener(this, textarea); + textarea.pdeEditorListener = listener; + + // set the undo stuff for this feller + Document document = textarea.getDocument(); + document.addUndoableEditListener(new PdeUndoableEditListener()); + + Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); + if ((PdeBase.platform == PdeBase.MACOSX) || + (PdeBase.platform == PdeBase.MACOS9)) { + presentationWindow = new Frame(); + + // mrj is still (with version 2.2.x) a piece of shit, + // and doesn't return valid insets for frames + //presentationWindow.pack(); // make a peer so insets are valid + //Insets insets = presentationWindow.getInsets(); + // the extra +20 is because the resize boxes intrude + Insets insets = new Insets(21, 5, 5 + 20, 5); + + presentationWindow.setBounds(-insets.left, -insets.top, + screen.width + insets.left + insets.right, + screen.height + insets.top + insets.bottom); + } else { + presentationWindow = new Frame(); + //((Frame)presentationWindow).setUndecorated(true); + try { + Method undecoratedMethod = + Frame.class.getMethod("setUndecorated", + new Class[] { Boolean.TYPE }); + undecoratedMethod.invoke(presentationWindow, + new Object[] { Boolean.TRUE }); + } catch (Exception e) { } + //} catch (NoSuchMethodException e) { } + //} catch (NoSuchMethodError e) { } + + presentationWindow.setBounds(0, 0, screen.width, screen.height); + } + + Label label = new Label("stop"); + label.addMouseListener(new MouseAdapter() { + public void mousePressed(MouseEvent e) { + setVisible(true); + doClose(); + }}); + + Dimension labelSize = new Dimension(60, 20); + presentationWindow.setLayout(null); + presentationWindow.add(label); + label.setBounds(5, screen.height - 5 - labelSize.height, + labelSize.width, labelSize.height); + + Color presentationBgColor = + PdePreferences.getColor("run.present.bgcolor"); + presentationWindow.setBackground(presentationBgColor); + + textarea.addFocusListener(new FocusAdapter() { + public void focusGained(FocusEvent e) { + if (presenting == true) { + try { + presentationWindow.toFront(); + runtime.applet.requestFocus(); + } catch (Exception ex) { } + } + } + }); + + this.addFocusListener(new FocusAdapter() { + public void focusGained(FocusEvent e) { + if (presenting == true) { + try { + presentationWindow.toFront(); + runtime.applet.requestFocus(); + } catch (Exception ex) { } + } + } + }); + + // moved from the PdeRuntime window to the main presentation window + // [toxi 030903] + presentationWindow.addKeyListener(new KeyAdapter() { + public void keyPressed(KeyEvent e) { + //System.out.println("window got " + e); + if (e.getKeyCode() == KeyEvent.VK_ESCAPE) { + runtime.stop(); + doClose(); + } else { + // pass on the event to the applet [toxi 030903] + runtime.applet.keyPressed(e); + } + } + }); + } + + + /** + * Hack for #@#)$(* Mac OS X. + */ + public Dimension getMinimumSize() { + return new Dimension(500, 500); + } + + + // ................................................................... + + + /** + * Post-constructor setup for the editor area. Loads the last + * sketch that was used (if any), and restores other Editor settings. + * The complement to "storePreferences", this is called when the + * application is first launched. + */ + public void restorePreferences() { + // figure out window placement + + Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); + boolean windowPositionInvalid = false; + + if (PdePreferences.get("last.screen.height") != null) { + // if screen size has changed, the window coordinates no longer + // make sense, so don't use them unless they're identical + int screenW = PdePreferences.getInteger("last.screen.width"); + int screenH = PdePreferences.getInteger("last.screen.height"); + + if ((screen.width != screenW) || (screen.height != screenH)) { + windowPositionInvalid = true; + } + } else { + windowPositionInvalid = true; + } + + if (windowPositionInvalid) { + //System.out.println("using default size"); + int windowH = PdePreferences.getInteger("default.window.height"); + int windowW = PdePreferences.getInteger("default.window.width"); + setBounds((screen.width - windowW) / 2, + (screen.height - windowH) / 2, + windowW, windowH); + // this will be invalid as well, so grab the new value + PdePreferences.setInteger("last.divider.location", + splitPane.getDividerLocation()); + } else { + setBounds(PdePreferences.getInteger("last.window.x"), + PdePreferences.getInteger("last.window.y"), + PdePreferences.getInteger("last.window.width"), + PdePreferences.getInteger("last.window.height")); + } + + + // last sketch that was in use + + //String sketchName = PdePreferences.get("last.sketch.name"); + String sketchPath = PdePreferences.get("last.sketch.path"); + //PdeSketch sketchTemp = new PdeSketch(sketchPath); + + if ((sketchPath != null) && (new File(sketchPath)).exists()) { + // don't check modified because nothing is open yet + handleOpen2(sketchPath); + + } else { + handleNew2(true); + } + + + // location for the console/editor area divider + + int location = PdePreferences.getInteger("last.divider.location"); + splitPane.setDividerLocation(location); + + + // read the preferences that are settable in the preferences window + + applyPreferences(); + } + + + /** + * Read and apply new values from the preferences, either because + * the app is just starting up, or the user just finished messing + * with things in the Preferences window. + */ + public void applyPreferences() { + + // apply the setting for 'use external editor' + boolean external = PdePreferences.getBoolean("editor.external"); + + textarea.setEditable(!external); + saveMenuItem.setEnabled(!external); + saveAsMenuItem.setEnabled(!external); + //beautifyMenuItem.setEnabled(!external); + + TextAreaPainter painter = textarea.getPainter(); + if (external) { + // disable line highlight and turn off the caret when disabling + Color color = PdePreferences.getColor("editor.external.bgcolor"); + painter.setBackground(color); + painter.lineHighlight = false; + textarea.setCaretVisible(false); + + } else { + Color color = PdePreferences.getColor("editor.bgcolor"); + painter.setBackground(color); + painter.lineHighlight = + PdePreferences.getBoolean("editor.linehighlight"); + textarea.setCaretVisible(true); + } + + // in case tab expansion stuff has changed + listener.applyPreferences(); + + // in case moved to a new location + sketchbook.rebuildMenus(); + } + + + /** + * Store preferences about the editor's current state. + * Called when the application is quitting. + */ + public void storePreferences() { + //System.out.println("storing preferences"); + + // window location information + Rectangle bounds = getBounds(); + PdePreferences.setInteger("last.window.x", bounds.x); + PdePreferences.setInteger("last.window.y", bounds.y); + PdePreferences.setInteger("last.window.width", bounds.width); + PdePreferences.setInteger("last.window.height", bounds.height); + + Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); + PdePreferences.setInteger("last.screen.width", screen.width); + PdePreferences.setInteger("last.screen.height", screen.height); + + // last sketch that was in use + //PdePreferences.set("last.sketch.name", sketchName); + //PdePreferences.set("last.sketch.name", sketch.name); + PdePreferences.set("last.sketch.path", sketch.getMainFilePath()); + + // location for the console/editor area divider + int location = splitPane.getDividerLocation(); + PdePreferences.setInteger("last.divider.location", location); + } + + + // ................................................................... + + + protected JMenu buildFileMenu() { + JMenuItem item; + JMenu menu = new JMenu("File"); + + if (!PdePreferences.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); + } + + /* + item = newJMenuItem("New code", 'N', true); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + handleNewCode(); + } + }); + menu.add(item); + */ + + /* + item = newJMenuItem("Open", 'O'); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + handleOpen(null); + } + }); + menu.add(item); + menu.add(sketchbook.rebuildMenu()); + menu.add(sketchbook.getExamplesMenu()); + */ + menu.add(sketchbook.getOpenMenu()); + + saveMenuItem = newJMenuItem("Save", 'S'); + saveMenuItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + handleSave(); + } + }); + menu.add(saveMenuItem); + + saveAsMenuItem = newJMenuItem("Save As...", 'S', true); + saveAsMenuItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + handleSaveAs(); + } + }); + menu.add(saveAsMenuItem); + +//// mobile: removing standard export/export-app features for now. may +//// bring them back later so that MIDlets can be demoed on web pages. +/* + item = newJMenuItem("Export", 'E'); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + handleExport(); + } + }); + menu.add(item); + + exportAppItem = newJMenuItem("Export Application", 'E', true); + exportAppItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + handleExportApp(); + } + }); + menu.add(exportAppItem); + */ +//// mobile: export MIDlet menu option + item = newJMenuItem("Export MIDlet", 'E', true); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + handleExportMIDlet(); + } + }); + menu.add(item); + + menu.addSeparator(); + + item = newJMenuItem("Page Setup", 'P', true); + item.setEnabled(false); + menu.add(item); + + item = newJMenuItem("Print", 'P'); + item.setEnabled(false); + menu.add(item); + + // macosx already has its own preferences and quit menu + if (PdeBase.platform != PdeBase.MACOSX) { + menu.addSeparator(); + + item = new JMenuItem("Preferences"); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + handlePrefs(); + } + }); + menu.add(item); + + menu.addSeparator(); + + item = newJMenuItem("Quit", 'Q'); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + handleQuit(); + } + }); + menu.add(item); + } + return menu; + } + + + protected JMenu buildSketchMenu() { + JMenuItem item; + JMenu menu = new JMenu("Sketch"); + +//// mobile: removing standard run/present menus +/* + item = newJMenuItem("Run", 'R'); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + handleRun(false); + } + }); + menu.add(item); + + item = newJMenuItem("Present", 'R', true); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + handleRun(true); + } + }); + menu.add(item); +*/ +//// mobile: run MIDlet in emulator + item = newJMenuItem("Run in Emulator", 'R'); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + handleRunEmulator(); + } + }); + menu.add(item); + + //menu.add(newJMenuItem("Stop", 'T')); + menu.add(new JMenuItem("Stop")); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + handleStop(); + } + }); + menu.addSeparator(); + + // + + item = new JMenuItem("Add File..."); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + sketch.addFile(); + } + }); + menu.add(item); + + menu.add(sketchbook.getImportMenu()); + + if ((PdeBase.platform == PdeBase.WINDOWS) || + (PdeBase.platform == PdeBase.MACOSX)) { + // 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.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + //PdeBase.openFolder(sketchDir); + PdeBase.openFolder(sketch.folder); + } + }); + menu.add(item); + } + + // TODO re-enable history + //history.attachMenu(menu); + return menu; + } + + + protected JMenu buildToolsMenu() { + JMenuItem item; + JMenu menu = new JMenu("Tools"); + + item = new JMenuItem("Auto Format"); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + handleBeautify(); + } + }); + menu.add(item); + + item = new JMenuItem("Create Font..."); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + new PdeFontBuilder().show(sketch.dataFolder); + } + }); + menu.add(item); + + item = new JMenuItem("Archive Sketch"); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + //new PdeFontBuilder().show(sketch.dataFolder); + Archiver archiver = new Archiver(); + archiver.setup(PdeEditor.this); + archiver.show(); + } + }); + menu.add(item); + + return menu; + } + + + protected JMenu buildHelpMenu() { + JMenu menu = new JMenu("Help"); + JMenuItem item; + + item = new JMenuItem("Environment"); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + PdeBase.openURL(System.getProperty("user.dir") + File.separator + + "reference" + File.separator + "environment" + + File.separator + "index.html"); + } + }); + menu.add(item); + + item = new JMenuItem("Reference"); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + PdeBase.openURL(System.getProperty("user.dir") + File.separator + + "reference" + File.separator + "index.html"); + } + }); + menu.add(item); + + item = newJMenuItem("Find in Reference", 'F', true); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + if (textarea.isSelectionActive()) { + String text = textarea.getSelectedText(); + if (text.length() == 0) { + message("First select a word to find in the reference."); + + } else { + String referenceFile = PdeKeywords.getReference(text); + if (referenceFile == null) { + message("No reference available for \"" + text + "\""); + } else { + PdeBase.showReference(referenceFile); + } + } + } + } + }); + menu.add(item); + + item = newJMenuItem("Visit Processing.org", '5'); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + PdeBase.openURL("http://processing.org/"); + } + }); + menu.add(item); + + // macosx already has its own about menu + if (PdeBase.platform != PdeBase.MACOSX) { + menu.addSeparator(); + item = new JMenuItem("About Processing"); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + handleAbout(); + } + }); + menu.add(item); + } + + return menu; + } + + + public JMenu buildEditMenu() { + JMenu menu = new JMenu("Edit"); + JMenuItem item; + + undoItem = newJMenuItem("Undo", 'Z'); + undoItem.addActionListener(undoAction = new UndoAction()); + menu.add(undoItem); + + redoItem = newJMenuItem("Redo", 'Y'); + redoItem.addActionListener(redoAction = new RedoAction()); + menu.add(redoItem); + + menu.addSeparator(); + + // TODO "cut" and "copy" should really only be enabled + // if some text is currently selected + item = newJMenuItem("Cut", 'X'); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + textarea.cut(); + sketch.setModified(); + } + }); + menu.add(item); + + item = newJMenuItem("Copy", 'C'); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + textarea.copy(); + } + }); + menu.add(item); + + item = newJMenuItem("Paste", 'V'); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + textarea.paste(); + sketch.setModified(); + } + }); + menu.add(item); + + item = newJMenuItem("Select All", 'A'); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + textarea.selectAll(); + } + }); + menu.add(item); + + menu.addSeparator(); + + item = newJMenuItem("Find...", 'F'); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + find.show(); + } + }); + menu.add(item); + + item = newJMenuItem("Find Next", 'G'); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + // TODO find next should only be enabled after a + // search has actually taken place + find.find(true); + } + }); + menu.add(item); + + return menu; + } + + + /** + * Convenience method for the antidote to overthought + * swing api mess for setting accelerators. + */ + static public JMenuItem newJMenuItem(String title, int what) { + return newJMenuItem(title, what, false); + } + + + /** + * A software engineer, somewhere, needs to have his abstraction + * taken away. I hear they jail people in third world countries for + * writing the sort of crappy api that would require a four line + * helpher function to *set the command key* for a menu item. + */ + static public JMenuItem newJMenuItem(String title, + int what, boolean shift) { + JMenuItem menuItem = new JMenuItem(title); + int modifiers = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(); + if (shift) modifiers |= ActionEvent.SHIFT_MASK; + menuItem.setAccelerator(KeyStroke.getKeyStroke(what, modifiers)); + return menuItem; + } + + + // ................................................................... + + + // This one listens for edits that can be undone. + protected class PdeUndoableEditListener implements UndoableEditListener { + public void undoableEditHappened(UndoableEditEvent e) { + // Remember the edit and update the menus. + undo.addEdit(e.getEdit()); + undoAction.updateUndoState(); + redoAction.updateRedoState(); + } + } + + + class UndoAction extends AbstractAction { + public UndoAction() { + super("Undo"); + this.setEnabled(false); + } + + public void actionPerformed(ActionEvent e) { + try { + undo.undo(); + } catch (CannotUndoException ex) { + //System.out.println("Unable to undo: " + ex); + //ex.printStackTrace(); + } + updateUndoState(); + redoAction.updateRedoState(); + } + + protected void updateUndoState() { + if (undo.canUndo()) { + this.setEnabled(true); + undoItem.setEnabled(true); + putValue(Action.NAME, undo.getUndoPresentationName()); + } else { + this.setEnabled(false); + undoItem.setEnabled(false); + putValue(Action.NAME, "Undo"); + } + } + } + + + class RedoAction extends AbstractAction { + public RedoAction() { + super("Redo"); + this.setEnabled(false); + } + + public void actionPerformed(ActionEvent e) { + try { + undo.redo(); + } catch (CannotRedoException ex) { + //System.out.println("Unable to redo: " + ex); + //ex.printStackTrace(); + } + updateRedoState(); + undoAction.updateUndoState(); + } + + protected void updateRedoState() { + if (undo.canRedo()) { + this.setEnabled(true); + redoItem.setEnabled(true); + putValue(Action.NAME, undo.getRedoPresentationName()); + } else { + this.setEnabled(false); + redoItem.setEnabled(false); + putValue(Action.NAME, "Redo"); + } + } + } + + + // ................................................................... + + + // interfaces for MRJ Handlers, but naming is fine + // so used internally for everything else + + public void handleAbout() { + //System.out.println("the about box will now be shown"); + final Image image = PdeBase.getImage("about.jpg", this); + int w = image.getWidth(this); + int h = image.getHeight(this); + final Window window = new Window(this) { + public void paint(Graphics g) { + g.drawImage(image, 0, 0, null); + + /* + // does nothing.. + Graphics2D g2 = (Graphics2D) g; + g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, + RenderingHints.VALUE_ANTIALIAS_OFF); + */ + + g.setFont(new Font("SansSerif", Font.PLAIN, 11)); + g.setColor(Color.white); + g.drawString(PdeBase.VERSION, 50, 30); + } + }; + window.addMouseListener(new MouseAdapter() { + public void mousePressed(MouseEvent e) { + window.dispose(); + } + }); + Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); + window.setBounds((screen.width-w)/2, (screen.height-h)/2, w, h); + window.show(); + } + + + /** + * Show the (already created on app init) preferences window. + */ + public void handlePrefs() { + // since this can't actually block, it'll hide + // the editor window while the prefs are open + preferences.showFrame(this); + // and then call applyPreferences if 'ok' is hit + // and then unhide + + // may need to rebuild sketch and other menus + //applyPreferences(); + + // next have editor do its thing + //editor.appyPreferences(); + } + + + // ................................................................... + + + /** + * Get the contents of the current buffer. Used by the PdeSketch class. + */ + public String getText() { + return textarea.getText(); + } + + + /** + * Called by PdeEditorHeader when the tab is changed + * (or a new set of files are opened). + * @param discardUndo true if undo info to this point should be ignored + */ + public void setText(String what, boolean discardUndo) { + textarea.setText(what); + + if (discardUndo) undo.discardAllEdits(); + + textarea.select(0, 0); // move to the beginning of the document + textarea.requestFocus(); // get the caret blinking + } + + + public void handleRun(boolean present) { + doClose(); + running = true; + buttons.run(); + + // do this for the terminal window / dos prompt / etc + for (int i = 0; i < 10; i++) System.out.println(); + + // clear the console on each run, unless the user doesn't want to + //if (PdeBase.getBoolean("console.auto_clear", true)) { + //if (PdePreferences.getBoolean("console.auto_clear", true)) { + if (PdePreferences.getBoolean("console.auto_clear")) { + console.clear(); + } + + presenting = present; + if (presenting) { + // wipe everything out with a bulbous screen-covering window + presentationWindow.show(); + presentationWindow.toFront(); + } + + try { + if (!sketch.handleRun()) return; + + runtime = new PdeRuntime(sketch, this); + runtime.start(presenting ? presentLocation : appletLocation); + watcher = new RunButtonWatcher(); + + } catch (PdeException e) { + error(e); + + } catch (Exception e) { + e.printStackTrace(); + } + //sketch.cleanup(); // where does this go? + } + + class RunButtonWatcher implements Runnable { + Thread thread; + + public RunButtonWatcher() { + thread = new Thread(this, "run button watcher"); + thread.setPriority(Thread.MIN_PRIORITY); + thread.start(); + } + + public void run() { + while (Thread.currentThread() == thread) { + if (runtime == null) { + stop(); + + } else { + if (runtime.applet != null) { + if (runtime.applet.finished) { + stop(); + } + //buttons.running(!runtime.applet.finished); + + } else if (runtime.process != null) { + //buttons.running(true); // ?? + try { + runtime.process.waitFor(); + stop(); + } catch (InterruptedException ie) { + + } + } else { + stop(); + } + } + try { + Thread.sleep(250); + } catch (InterruptedException e) { } + //System.out.println("still inside runner thread"); + } + } + + public void stop() { + buttons.running(false); + thread = null; + } + } + + + public void handleStop() { // called by menu or buttons + if (presenting) { + doClose(); + } else { + doStop(); + } + } + + + /** + * Stop the applet but don't kill its window. + */ + public void doStop() { + if (runtime != null) runtime.stop(); + if (watcher != null) watcher.stop(); + message(EMPTY); + + // the buttons are sometimes still null during the constructor + // is this still true? are people still hitting this error? + /*if (buttons != null)*/ buttons.clear(); + + running = false; + } + + + /** + * Stop the applet and kill its window. When running in presentation + * mode, this will always be called instead of doStop(). + */ + public void doClose() { + if (presenting) { + presentationWindow.hide(); + + } else { + try { + // the window will also be null the process was running + // externally. so don't even try setting if window is null + // since PdeRuntime will set the appletLocation when an + // external process is in use. + 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) { + runtime.close(); // kills the window + runtime = null; // will this help? + } + } catch (Exception e) { } + //buttons.clear(); // done by doStop + + sketch.cleanup(); + + // [toxi 030903] + // focus the PDE again after quitting presentation mode + toFront(); + } + + + /** + * Check to see if there have been changes. If so, prompt user + * whether or not to save first. If the user cancels, just ignore. + * Otherwise, one of the other methods will handle calling + * checkModified2() which will get on with business. + */ + protected void checkModified(int checkModifiedMode) { + this.checkModifiedMode = checkModifiedMode; + + if (!sketch.modified) { + checkModified2(); + return; + } + + String prompt = "Save changes to " + sketch.name + "? "; + + if (checkModifiedMode != HANDLE_QUIT) { + // if the user is not quitting, then use the nicer + // dialog that's actually inside the p5 window. + status.prompt(prompt); + + } else { + // if the user selected quit, then this has to be done with + // a JOptionPane instead of internally in the editor. + // TODO this is actually just a bug to be fixed. + + // macosx java kills the app even though cancel might get hit + // so the cancel button is (temporarily) left off + // this may be treated differently in macosx java 1.4, + // but 1.4 isn't currently stable enough to use. + + // turns out windows has the same problem (sometimes) + // disable cancel for now until a fix can be found. + + Object[] options = { "Yes", "No" }; + int result = JOptionPane.showOptionDialog(this, + prompt, + "Quit", + JOptionPane.YES_NO_OPTION, + JOptionPane.QUESTION_MESSAGE, + null, + options, + options[0]); + + if (result == JOptionPane.YES_OPTION) { + handleSave(); + checkModified2(); + + } else if (result == JOptionPane.NO_OPTION) { + checkModified2(); // though this may just quit + + } else if (result == JOptionPane.CANCEL_OPTION) { + // ignored + } + } + } + + + /** + * Called by PdeEditorStatus to complete the job. + */ + public void checkModified2() { + switch (checkModifiedMode) { + case HANDLE_NEW: handleNew2(false); break; + case HANDLE_OPEN: handleOpen2(handleOpenPath); break; + case HANDLE_QUIT: handleQuit2(); break; + } + checkModifiedMode = 0; + } + + + /** + * 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); + } + + + /** + * User selected "New Library", this will act just like handleNew + * but internally set a flag that the new guy is a library, + * meaning that a "library" subfolder will be added. + */ + public void handleNewLibrary() { + doStop(); + handleNewShift = false; + handleNewLibrary = true; + checkModified(HANDLE_NEW); + } + + + /** + * Does all the plumbing to create a new project + * then calls handleOpen to load it up. + * @param startup true if the app is starting (auto-create a sketch) + */ + protected void handleNew2(boolean startup) { + try { + String pdePath = + sketchbook.handleNew(startup, handleNewShift, handleNewLibrary); + if (pdePath != null) handleOpen2(pdePath); + + } catch (IOException e) { + // not sure why this would happen, but since there's no way to + // recover (outside of creating another new setkch, which might + // just cause more trouble), then they've gotta quit. + PdeBase.showError("Problem creating a new sketch", + "An error occurred while creating\n" + + "a new sketch. Processing must now quit.", e); + } + } + + + /** + * 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; + } + doStop(); + handleOpenPath = path; + checkModified(HANDLE_OPEN); + } + + + /** + * Second stage of open, occurs after having checked to + * see if the modifications (if any) to the previous sketch + * need to be saved. + */ + protected void handleOpen2(String path) { + try { + // check to make sure that this .pde file is + // in a folder of the same name + File file = new File(path); + File parentFile = new File(file.getParent()); + String parentName = parentFile.getName(); + String pdeName = parentName + ".pde"; + File altFile = new File(file.getParent(), pdeName); + + //System.out.println("path = " + file.getParent()); + //System.out.println("name = " + file.getName()); + //System.out.println("pname = " + parentName); + + if (pdeName.equals(file.getName())) { + // no beef with this guy + + } else if (altFile.exists()) { + // user selected a .java from the same sketch, + // but open the .pde instead + path = altFile.getAbsolutePath(); + //System.out.println("found alt file in same folder"); + + } else if (!path.endsWith(".pde")) { + PdeBase.showWarning("Bad file selected", + "Processing can only open its own sketches\n" + + "and other files ending in .pde", null); + return; + + } else { + String properParent = + file.getName().substring(0, file.getName().length() - 4); + + Object[] options = { "OK", "Cancel" }; + String prompt = + "The file \"" + file.getName() + "\" needs to be inside\n" + + "a sketch folder named \"" + properParent + "\".\n" + + "Create this folder, move the file, and continue?"; + + int result = JOptionPane.showOptionDialog(this, + prompt, + "Moving", + JOptionPane.YES_NO_OPTION, + JOptionPane.QUESTION_MESSAGE, + null, + options, + options[0]); + + if (result == JOptionPane.YES_OPTION) { + // create properly named folder + File properFolder = new File(file.getParent(), properParent); + if (properFolder.exists()) { + PdeBase.showWarning("Error", + "A folder named \"" + properParent + "\" " + + "already exists. Can't open sketch.", null); + return; + } + if (!properFolder.mkdirs()) { + throw new IOException("Couldn't create sketch folder"); + } + // copy the sketch inside + File properPdeFile = new File(properFolder, file.getName()); + File origPdeFile = new File(path); + PdeBase.copyFile(origPdeFile, properPdeFile); + + // remove the original file, so user doesn't get confused + origPdeFile.delete(); + + // update with the new path + path = properPdeFile.getAbsolutePath(); + + } else if (result == JOptionPane.NO_OPTION) { + return; + } + } + + sketch = new PdeSketch(this, path); + // TODO re-enable this once export application works +//// mobile: exportAppItem removed from mobile + //exportAppItem.setEnabled(false && !sketch.isLibrary()); + buttons.disableRun(sketch.isLibrary()); + header.rebuild(); + if (PdePreferences.getBoolean("console.auto_clear")) { + console.clear(); + } + + } catch (Exception e) { + error(e); + } + } + + + // there is no handleSave1 since there's never a need to prompt + public void handleSave() { + message("Saving..."); + try { + if (sketch.save()) { + message("Done Saving."); + } else { + message(EMPTY); + } + // rebuild sketch menu in case a save-as was forced + sketchbook.rebuildMenus(); + + } catch (Exception e) { + // show the error as a message in the window + error(e); + + // zero out the current action, + // so that checkModified2 will just do nothing + checkModifiedMode = 0; + // this is used when another operation calls a save + } + buttons.clear(); + } + + + public void handleSaveAs() { + doStop(); + + 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(); + } + + + /** + * Handles calling the export() function on sketch, and + * queues all the gui status stuff that comes along with it. + * + * Made synchronized to (hopefully) avoid problems of people + * hitting export twice, quickly, and horking things up. + */ + synchronized public void handleExport() { + String what = sketch.isLibrary() ? "Applet" : "Library"; + message("Exporting " + what + "..."); + try { + boolean success = sketch.isLibrary() ? + sketch.exportLibrary() : sketch.exportApplet(); + if (success) { + message("Done exporting."); + } else { + // error message will already be visible + } + } catch (Exception e) { + message("Error during export."); + e.printStackTrace(); + } + buttons.clear(); + } + + + synchronized public void handleExportApp() { + message("Exporting application..."); + try { + if (sketch.exportApplication()) { + message("Done exporting."); + } else { + // error message will already be visible + } + } catch (Exception e) { + message("Error during export."); + e.printStackTrace(); + } + buttons.clear(); + } + + + /** + * Quit, but first ask user if it's ok. Also store preferences + * to disk just in case they want to quit. Final exit() happens + * in PdeEditor since it has the callback from PdeEditorStatus. + */ + public void handleQuit() { + // stop isn't sufficient with external vm & quit + // instead use doClose() which will kill the external vm + //doStop(); + doClose(); + + //if (!checkModified()) return; + checkModified(HANDLE_QUIT); + //System.out.println("exiting doquit"); + } + + + /** + * Actually do the quit action. + */ + protected void handleQuit2() { + storePreferences(); + preferences.save(); + + sketchbook.clean(); + + //System.out.println("exiting here"); + System.exit(0); + } + + + // an improved algorithm that would still avoid a full state machine + // 1. build an array of strings for the lines + // 2. first remove everything between /* and */ (relentless) + // 3. next remove anything inside two sets of " " + // but not if escaped with a \ + // these can't extend beyond a line, so that works well + // (this will save from "http://blahblah" showing up as a comment) + // 4. remove from // to the end of a line everywhere + // 5. run through remaining text to do indents + // using hokey brace-counting algorithm + // 6. also add indents for switch statements + // case blah: { } (colons at end of line isn't a good way) + // maybe /case \w+\:/ + public void handleBeautify() { + String prog = textarea.getText(); + + // TODO re-enable history + //history.record(prog, PdeHistory.BEAUTIFY); + + int tabSize = PdePreferences.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 = textarea.getSelectionEnd(); + + // replace with new bootiful text + 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 + textarea.select(selectionEnd, selectionEnd); + + //setSketchModified(true); + //sketch.setCurrentModified(true); + sketch.setModified(); + buttons.clear(); + } + + + // TODO iron out bugs with this code under + // different platforms, especially macintosh + public void highlightLine(int lnum) { + if (lnum < 0) { + textarea.select(0, 0); + return; + } + //System.out.println(lnum); + String s = textarea.getText(); + int len = s.length(); + int st = -1; + int ii = 0; + int end = -1; + int lc = 0; + if (lnum == 0) st = 0; + for (int i = 0; i < len; i++) { + ii++; + //if ((s.charAt(i) == '\n') || (s.charAt(i) == '\r')) { + boolean newline = false; + if (s.charAt(i) == '\r') { + if ((i != len-1) && (s.charAt(i+1) == '\n')) { + i++; //ii--; + } + lc++; + newline = true; + } else if (s.charAt(i) == '\n') { + lc++; + newline = true; + } + if (newline) { + if (lc == lnum) + //st = i+1; + st = ii; + else if (lc == lnum+1) { + //end = i; + end = ii; + break; + } + } + } + if (end == -1) end = len; + + // sometimes KJC claims that the line it found an error in is + // the last line in the file + 1. Just highlight the last line + // in this case. [dmose] + if (st == -1) st = len; + + textarea.select(st, end); + } + + + // ................................................................... + + + public void error(Exception e) { + status.error(e.getMessage()); + e.printStackTrace(); + } + + + public void error(PdeException e) { + if (e.file >= 0) sketch.setCurrent(e.file); + if (e.line >= 0) highlightLine(e.line); + + status.error(e.getMessage()); + buttons.clearRun(); + } + + + /* + 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); + } + */ + + + // ................................................................... + + + /** + * Returns the edit popup menu. + */ + class TextAreaPopup extends JPopupMenu { + //protected ReferenceKeys referenceItems = new ReferenceKeys(); + String currentDir = System.getProperty("user.dir"); + String referenceFile = null; + + JMenuItem cutItem, copyItem; + JMenuItem referenceItem; + + + public TextAreaPopup() { + JMenuItem item; + + cutItem = new JMenuItem("Cut"); + cutItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + textarea.cut(); + } + }); + this.add(cutItem); + + copyItem = new JMenuItem("Copy"); + copyItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + textarea.copy(); + } + }); + this.add(copyItem); + + item = new JMenuItem("Paste"); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + textarea.paste(); + } + }); + this.add(item); + + item = new JMenuItem("Select All"); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + textarea.selectAll(); + } + }); + this.add(item); + + this.addSeparator(); + + referenceItem = new JMenuItem("Find in Reference"); + referenceItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + PdeBase.showReference(referenceFile); + } + }); + this.add(referenceItem); + } + + // if no text is selected, disable copy and cut menu items + public void show(Component component, int x, int y) { + if (textarea.isSelectionActive()) { + cutItem.setEnabled(true); + copyItem.setEnabled(true); + + referenceFile = PdeKeywords.getReference(textarea.getSelectedText()); + if (referenceFile != null) { + referenceItem.setEnabled(true); + } + } else { + cutItem.setEnabled(false); + copyItem.setEnabled(false); + referenceItem.setEnabled(false); + } + super.show(component, x, y); + } + } + +//// mobile: exports MIDlet and executes emulator + public void handleRunEmulator() { + doClose(); + running = true; + buttons.run(); + + // do this for the terminal window / dos prompt / etc + for (int i = 0; i < 10; i++) System.out.println(); + + // clear the console on each run, unless the user doesn't want to + //if (PdeBase.getBoolean("console.auto_clear", true)) { + //if (PdePreferences.getBoolean("console.auto_clear", true)) { + if (PdePreferences.getBoolean("console.auto_clear")) { + console.clear(); + } + + try { + if (!sketch.exportMIDlet()) return; + + runtime = new PdeEmulator(sketch, this); + runtime.start(null); + watcher = new RunButtonWatcher(); + + } catch (Exception e) { + e.printStackTrace(); + } + } + +//// mobile: exports MIDlet + synchronized public void handleExportMIDlet() { + message("Exporting MIDlet..."); + try { + if (sketch.exportMIDlet()) { + message("Done exporting."); + File midletDir = new File(sketch.folder, "midlet"); + PdeBase.openFolder(midletDir); + } else { + // error message will already be visible + } + } catch (Exception e) { + message("Error during export."); + e.printStackTrace(); + } + buttons.clear(); + } +} + diff --git a/app/PdeEditorButtons.java b/app/PdeEditorButtons.java new file mode 100755 index 000000000..62af26684 --- /dev/null +++ b/app/PdeEditorButtons.java @@ -0,0 +1,417 @@ +/* -*- mode: jde; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + PdeEditorButtons - run/stop/etc buttons for the ide + Part of the Processing project - http://processing.org + + Copyright (c) 2004 Ben Fry and the Processing project. + + The original rendition of this code was written by Ben Fry and + Copyright (c) 2001-03 Massachusetts Institute of Technology + + 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 +*/ + +import java.awt.*; +import java.awt.event.*; +import java.awt.font.*; +import java.awt.geom.*; +import javax.swing.*; +import javax.swing.event.*; + + +public class PdeEditorButtons extends JComponent implements MouseInputListener { + + static final String title[] = { + "", "run", "stop", "new", "open", "save", "export" + }; + + static final int BUTTON_COUNT = title.length; + static final int BUTTON_WIDTH = PdePreferences.GRID_SIZE; + static final int BUTTON_HEIGHT = PdePreferences.GRID_SIZE; + + static final int NOTHING = 0; + static final int RUN = 1; + static final int STOP = 2; + + static final int NEW = 3; + static final int OPEN = 4; + static final int SAVE = 5; + static final int EXPORT = 6; + + static final int INACTIVE = 0; + static final int ROLLOVER = 1; + static final int ACTIVE = 2; + + PdeEditor editor; + boolean disableRun; + //Label status; + + Image offscreen; + int width, height; + + Color bgcolor; + + Image buttons; + Image inactive[]; + Image rollover[]; + Image active[]; + int currentRollover; + int currentSelection; + + JPopupMenu popup; + + int buttonCount; + int state[] = new int[BUTTON_COUNT]; + Image stateImage[]; + int which[]; // mapping indices to implementation + + int x1, x2; + int y1[], y2[]; + + String status; + Font statusFont; + Color statusColor; + int statusY; + + + public PdeEditorButtons(PdeEditor editor) { + this.editor = editor; + buttons = PdeBase.getImage("buttons.gif", this); + + buttonCount = 0; + which = new int[BUTTON_COUNT]; + + which[buttonCount++] = NOTHING; + which[buttonCount++] = RUN; + which[buttonCount++] = STOP; + which[buttonCount++] = NEW; + which[buttonCount++] = OPEN; + which[buttonCount++] = SAVE; + which[buttonCount++] = EXPORT; + + currentRollover = -1; + + bgcolor = PdePreferences.getColor("buttons.bgcolor"); + + status = ""; + + //setLayout(null); + //status = new JLabel(); + statusFont = PdePreferences.getFont("buttons.status.font"); + statusColor = PdePreferences.getColor("buttons.status.color"); + //add(status); + + //status.setBounds(-5, BUTTON_COUNT * BUTTON_HEIGHT, + // BUTTON_WIDTH + 15, BUTTON_HEIGHT); + //status.setAlignment(Label.CENTER); + statusY = (BUTTON_COUNT + 1) * BUTTON_HEIGHT; + + addMouseListener(this); + addMouseMotionListener(this); + } + + + /* + public void update() { + paint(this.getGraphics()); + } + + public void update(Graphics g) { + paint(g); + } + */ + + //public void paintComponent(Graphics g) { + //super.paintComponent(g); + //} + + + public void paintComponent(Graphics screen) { + if (inactive == null) { + inactive = new Image[BUTTON_COUNT]; + rollover = new Image[BUTTON_COUNT]; + active = new Image[BUTTON_COUNT]; + + //state = new int[BUTTON_COUNT]; + + for (int i = 0; i < BUTTON_COUNT; i++) { + inactive[i] = createImage(BUTTON_WIDTH, BUTTON_HEIGHT); + Graphics g = inactive[i].getGraphics(); + g.drawImage(buttons, -(i*BUTTON_WIDTH), -2*BUTTON_HEIGHT, null); + + rollover[i] = createImage(BUTTON_WIDTH, BUTTON_HEIGHT); + g = rollover[i].getGraphics(); + g.drawImage(buttons, -(i*BUTTON_WIDTH), -1*BUTTON_HEIGHT, null); + + active[i] = createImage(BUTTON_WIDTH, BUTTON_HEIGHT); + g = active[i].getGraphics(); + g.drawImage(buttons, -(i*BUTTON_WIDTH), -0*BUTTON_HEIGHT, null); + } + + state = new int[buttonCount]; + stateImage = new Image[buttonCount]; + for (int i = 0; i < buttonCount; i++) { + setState(i, INACTIVE, false); + } + } + Dimension size = size(); + if ((offscreen == null) || + (size.width != width) || (size.height != height)) { + offscreen = createImage(size.width, size.height); + width = size.width; + height = size.height; + + x1 = 0; + x2 = BUTTON_WIDTH; + + y1 = new int[buttonCount]; + y2 = new int[buttonCount]; + + int offsetY = 0; + for (int i = 0; i < buttonCount; i++) { + y1[i] = offsetY; + y2[i] = offsetY + BUTTON_HEIGHT; + offsetY = y2[i]; + } + } + Graphics g = offscreen.getGraphics(); + g.setColor(bgcolor); //getBackground()); + g.fillRect(0, 0, width, height); + + for (int i = 0; i < buttonCount; i++) { + //g.drawImage(stateImage[i], x1[i], y1, null); + g.drawImage(stateImage[i], x1, y1[i], null); + } + + g.setColor(statusColor); + g.setFont(statusFont); + + // if i ever find the guy who wrote the java2d api, + // i will hurt him. or just laugh in his face. or pity him. + Graphics2D g2 = (Graphics2D) g; + FontRenderContext frc = g2.getFontRenderContext(); + float statusW = (float) statusFont.getStringBounds(status, frc).getWidth(); + float statusX = (getSize().width - statusW) / 2; + + //int statusWidth = g.getFontMetrics().stringWidth(status); + //int statusX = (getSize().width - statusWidth) / 2; + + g2.drawString(status, statusX, statusY); + screen.drawImage(offscreen, 0, 0, null); + } + + + public void mouseMoved(MouseEvent e) { + // mouse events before paint(); + if (state == null) return; + + if (state[OPEN] != INACTIVE) { + // avoid flicker, since there will probably be an update event + setState(OPEN, INACTIVE, false); + } + //System.out.println(e); + //mouseMove(e); + handleMouse(e.getX(), e.getY()); + } + + + public void mouseDragged(MouseEvent e) { } + + + public void handleMouse(int x, int y) { + if (currentRollover != -1) { + if ((y > y1[currentRollover]) && (x > x1) && + (y < y2[currentRollover]) && (x < x2)) { + return; + + } else { + setState(currentRollover, INACTIVE, true); + messageClear(title[currentRollover]); + currentRollover = -1; + } + } + int sel = findSelection(x, y); + if (sel == -1) return; + + if (state[sel] != ACTIVE) { + if (!(disableRun && ((sel == RUN) || (sel == STOP)))) { + setState(sel, ROLLOVER, true); + currentRollover = sel; + } + } + } + + + private int findSelection(int x, int y) { + // if app loads slowly and cursor is near the buttons + // when it comes up, the app may not have time to load + if ((y1 == null) || (y2 == null)) return -1; + + for (int i = 0; i < buttonCount; i++) { + if ((x > x1) && (y > y1[i]) && + (x < x2) && (y < y2[i])) { + //if ((x > x1[i]) && (y > y1) && + //(x < x2[i]) && (y < y2)) { + return i; + } + } + return -1; + } + + + private void setState(int slot, int newState, boolean updateAfter) { + //if (inactive == null) return; + state[slot] = newState; + switch (newState) { + case INACTIVE: + stateImage[slot] = inactive[which[slot]]; + break; + case ACTIVE: + stateImage[slot] = active[which[slot]]; + break; + case ROLLOVER: + stateImage[slot] = rollover[which[slot]]; + message(title[which[slot]]); + break; + } + if (updateAfter) repaint(); // changed for swing from update(); + } + + + public void mouseEntered(MouseEvent e) { + //mouseMove(e); + handleMouse(e.getX(), e.getY()); + } + + + public void mouseExited(MouseEvent e) { + if (state[OPEN] != INACTIVE) { + setState(OPEN, INACTIVE, true); + } + + // kludge + //for (int i = 0; i < BUTTON_COUNT; i++) { + //messageClear(title[i]); + //} + status = ""; + //mouseMove(e); + handleMouse(e.getX(), e.getY()); + } + + int wasDown = -1; + + + public void mousePressed(MouseEvent e) { + int x = e.getX(); + 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); + } + + if (currentSelection == OPEN) { + if (popup == null) { + //popup = new JPopupMenu(); + popup = editor.sketchbook.getPopupMenu(); + add(popup); + } + //editor.sketchbook.rebuildPopup(popup); + popup.show(this, x, y); + } + } + + + public void mouseClicked(MouseEvent e) { } + + + public void mouseReleased(MouseEvent e) { + switch (currentSelection) { + case RUN: + if (!disableRun) { + editor.handleRunEmulator();//(e.isShiftDown()); + } + break; + + case STOP: + if (!disableRun) { + setState(RUN, INACTIVE, true); + editor.handleStop(); + } + break; + + case OPEN: setState(OPEN, INACTIVE, true); break; + case NEW: editor.handleNew(e.isShiftDown()); break; + case SAVE: editor.handleSave(); break; + case EXPORT: editor.handleExport(); break; + } + currentSelection = -1; + } + + + public void disableRun(boolean what) { + disableRun = what; + } + + + public void clear() { // (int button) { + if (inactive == null) return; + + // skip the run button, do the others + for (int i = 1; i < buttonCount; i++) { + setState(i, INACTIVE, false); + } + repaint(); // changed for swing from update(); + } + + + 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; + } + + public void messageClear(String msg) { + //if (status.getText().equals(msg + " ")) status.setText(PdeEditor.EMPTY); + if (status.equals(msg)) status = ""; + } + + + public Dimension getPreferredSize() { + return new Dimension(BUTTON_WIDTH, (BUTTON_COUNT + 1)*BUTTON_HEIGHT); + } +} diff --git a/app/PdeEmulator.java b/app/PdeEmulator.java new file mode 100755 index 000000000..23819b35f --- /dev/null +++ b/app/PdeEmulator.java @@ -0,0 +1,48 @@ +import java.awt.Point; +import java.io.*; + +/** + * + * @author Francis Li + */ +public class PdeEmulator extends PdeRuntime { + + /** Creates a new instance of PdeEmulator */ + public PdeEmulator(PdeSketch sketch, PdeEditor editor) { + super(sketch, editor); + } + + public void start(Point windowLocation) throws PdeException { + try{ + String wtkBinPath = PdePreferences.get("wtk.path") + File.separator + "bin"; + + StringBuffer command = new StringBuffer(); + command.append(wtkBinPath); + command.append(File.separator); + command.append("emulator.exe -Xdescriptor:\""); + command.append(sketch.folder.getPath()); + command.append(File.separator); + command.append("midlet"); + command.append(File.separator); + command.append(sketch.name); + command.append(".jad\""); + + process = Runtime.getRuntime().exec(command.toString(), null, new File(wtkBinPath)); + processInput = new SystemOutSiphon(process.getInputStream()); + processError = new PdeMessageSiphon(process.getErrorStream(), this); + processOutput = process.getOutputStream(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public void stop() { + } + + public void close() { + } + + public void message(String s) { + System.err.println(s); + } +} diff --git a/app/PdePreprocessor.java b/app/PdePreprocessor.java new file mode 100755 index 000000000..6ba7a11df --- /dev/null +++ b/app/PdePreprocessor.java @@ -0,0 +1,419 @@ +/* -*- mode: jde; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + PdePreprocessor - wrapper for default ANTLR-generated parser + Part of the Processing project - http://processing.org + + Except where noted, code is written by Ben Fry and + Copyright (c) 2001-03 Massachusetts Institute of Technology + + ANTLR-generated parser and several supporting classes written + by Dan Mosedale via funding from the Interaction Institute IVREA. + + 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 +*/ + +import processing.core.*; + +import java.io.*; + +import antlr.*; +import antlr.collections.*; +import antlr.collections.impl.*; + +import com.oroinc.text.regex.*; + + +public class PdePreprocessor { + + static final int JDK11 = 0; + static final int JDK13 = 1; + static final int JDK14 = 2; + + static String defaultImports[][] = new String[3][]; + + // these ones have the .* at the end, since a class name + // might be at the end instead of .* whcih would make trouble + // other classes using this can lop of the . and anything after + // it to produce a package name consistently. + String extraImports[]; + + // imports just from the code folder, treated differently + // than the others, since the imports are auto-generated. + String codeFolderImports[]; + + static final int STATIC = 0; // formerly BEGINNER + static final int ACTIVE = 1; // formerly INTERMEDIATE + static final int JAVA = 2; // formerly ADVANCED + // static to make it easier for the antlr preproc to get at it + static int programType = -1; + + Reader programReader; + String buildPath; + + // used for calling the ASTFactory to get the root node + private static final int ROOT_ID = 0; + + private String baseClass; + private String[] baseImports; + + public void setBaseClass(String baseClass) { + this.baseClass = baseClass; + } + + public void setBaseImports(String[] baseImports) { + this.baseImports = baseImports; + } + + + /** + * These may change in-between (if the prefs panel adds this option) + * so grab them here on construction. + */ + public PdePreprocessor() { + defaultImports[JDK11] = + PApplet.split(PdePreferences.get("preproc.imports.jdk11"), ','); + defaultImports[JDK13] = + PApplet.split(PdePreferences.get("preproc.imports.jdk13"), ','); + defaultImports[JDK14] = + PApplet.split(PdePreferences.get("preproc.imports.jdk14"), ','); + } + + + /** + * Used by PdeEmitter.dumpHiddenTokens() + */ + public static TokenStreamCopyingHiddenTokenFilter filter; + + + /** + * preprocesses a pde file and write out a java file + * @return the classname of the exported Java + */ + //public String write(String program, String buildPath, String name, + // String extraImports[]) throws java.lang.Exception { + public String write(String program, String buildPath, String name, String codeFolderPackages[]) + throws java.lang.Exception { + // if the program ends with no CR or LF an OutOfMemoryError will happen. + // not gonna track down the bug now, so here's a hack for it: + if ((program.length() > 0) && + program.charAt(program.length()-1) != '\n') { + program += "\n"; + } + + if (PdePreferences.getBoolean("preproc.substitute_unicode")) { + // check for non-ascii chars (these will be/must be in unicode format) + char p[] = program.toCharArray(); + int unicodeCount = 0; + for (int i = 0; i < p.length; i++) { + if (p[i] > 127) unicodeCount++; + } + // if non-ascii chars are in there, convert to unicode escapes + if (unicodeCount != 0) { + // add unicodeCount * 5.. replacing each unicode char + // with six digit uXXXX sequence (xxxx is in hex) + // (except for nbsp chars which will be a replaced with a space) + int index = 0; + char p2[] = new char[p.length + unicodeCount*5]; + for (int i = 0; i < p.length; i++) { + if (p[i] < 128) { + p2[index++] = p[i]; + + } else if (p[i] == 160) { // unicode for non-breaking space + p2[index++] = ' '; + + } else { + int c = p[i]; + p2[index++] = '\\'; + p2[index++] = 'u'; + char str[] = Integer.toHexString(c).toCharArray(); + // add leading zeros, so that the length is 4 + //for (int i = 0; i < 4 - str.length; i++) p2[index++] = '0'; + for (int m = 0; m < 4 - str.length; m++) p2[index++] = '0'; + System.arraycopy(str, 0, p2, index, str.length); + index += str.length; + } + } + program = new String(p2, 0, index); + } + } + + // if this guy has his own imports, need to remove them + // just in case it's not an advanced mode sketch + PatternMatcher matcher = new Perl5Matcher(); + PatternCompiler compiler = new Perl5Compiler(); + //String mess = "^\\s*(import\\s+\\S+\\s*;)"; + String mess = "^\\s*(import\\s+)(\\S+)(\\s*;)"; + java.util.Vector imports = new java.util.Vector(); + + Pattern pattern = null; + try { + pattern = compiler.compile(mess); + } catch (MalformedPatternException e) { + e.printStackTrace(); + return null; + } + + do { + PatternMatcherInput input = new PatternMatcherInput(program); + if (!matcher.contains(input, pattern)) break; + + MatchResult result = matcher.getMatch(); + String piece1 = result.group(1).toString(); + String piece2 = result.group(2).toString(); // the package name + String piece3 = result.group(3).toString(); + String piece = piece1 + piece2 + piece3; + int len = piece.length(); + + //imports.add(piece); + imports.add(piece2); + int idx = program.indexOf(piece); + // just remove altogether? + program = program.substring(0, idx) + program.substring(idx + len); + + //System.out.println("removing " + piece); + + } while (true); + + int importsCount = imports.size(); + extraImports = new String[importsCount]; + imports.copyInto(extraImports); + + + /* + if (codeFolderPackages != null) { + extraImports = new String[importsCount + codeFolderPackages.length]; + imports.copyInto(extraImports); + for (int i = 0; i < codeFolderPackages.length; i++) { + extraImports[importsCount + i] = codeFolderPackages[i] + ".*"; + } + codeFolderImports = null; + } + */ + + if (codeFolderPackages != null) { + codeFolderImports = new String[codeFolderPackages.length]; + for (int i = 0; i < codeFolderPackages.length; i++) { + codeFolderImports[i] = codeFolderPackages[i] + ".*"; + } + } else { + codeFolderImports = null; + } + + // + + // do this after the program gets re-combobulated + this.programReader = new StringReader(program); + this.buildPath = buildPath; + + // create a lexer with the stream reader, and tell it to handle + // hidden tokens (eg whitespace, comments) since we want to pass these + // through so that the line numbers when the compiler reports errors + // match those that will be highlighted in the PDE IDE + // + PdeLexer lexer = new PdeLexer(programReader); + lexer.setTokenObjectClass("antlr.CommonHiddenStreamToken"); + + // create the filter for hidden tokens and specify which tokens to + // hide and which to copy to the hidden text + // + filter = new TokenStreamCopyingHiddenTokenFilter(lexer); + filter.hide(PdeRecognizer.SL_COMMENT); + filter.hide(PdeRecognizer.ML_COMMENT); + filter.hide(PdeRecognizer.WS); + filter.copy(PdeRecognizer.SEMI); + filter.copy(PdeRecognizer.LPAREN); + filter.copy(PdeRecognizer.RPAREN); + filter.copy(PdeRecognizer.LCURLY); + filter.copy(PdeRecognizer.RCURLY); + filter.copy(PdeRecognizer.COMMA); + filter.copy(PdeRecognizer.RBRACK); + filter.copy(PdeRecognizer.LBRACK); + filter.copy(PdeRecognizer.COLON); + + // create a parser and set what sort of AST should be generated + // + PdeRecognizer parser = new PdeRecognizer(filter); + + // use our extended AST class + // + parser.setASTNodeClass("antlr.ExtendedCommonASTWithHiddenTokens"); + + // start parsing at the compilationUnit non-terminal + // + parser.pdeProgram(); + + // set up the AST for traversal by PdeEmitter + // + ASTFactory factory = new ASTFactory(); + AST parserAST = parser.getAST(); + AST rootNode = factory.create(ROOT_ID, "AST ROOT"); + rootNode.setFirstChild(parserAST); + + // unclear if this actually works, but it's worth a shot + // + ((CommonAST)parserAST).setVerboseStringConversion( + true, parser.getTokenNames()); + + // if this is an advanced program, the classname is already defined. + // + if (programType == JAVA) { + name = getFirstClassName(parserAST); + } + + // if 'null' was passed in for the name, but this isn't + // a 'java' mode class, then there's a problem, so punt. + // + if (name == null) return null; + + // output the code + // + PdeEmitter emitter = new PdeEmitter(); + File streamFile = new File(buildPath, name + ".java"); + PrintStream stream = new PrintStream(new FileOutputStream(streamFile)); + + //writeHeader(stream, extraImports, name); + writeHeader(stream, name); + + emitter.setOut(stream); + emitter.print(rootNode); + + writeFooter(stream); + stream.close(); + + // if desired, serialize the parse tree to an XML file. can + // be viewed usefully with Mozilla or IE + + if (PdePreferences.getBoolean("preproc.output_parse_tree")) { + + stream = new PrintStream(new FileOutputStream("parseTree.xml")); + stream.println(""); + stream.println(""); + OutputStreamWriter writer = new OutputStreamWriter(stream); + if (parserAST != null) { + ((CommonAST)parserAST).xmlSerialize(writer); + } + writer.flush(); + stream.println(""); + writer.close(); + } + + return name; + } + + + /** + * Write any required header material (eg imports, class decl stuff) + * + * @param out PrintStream to write it to. + * @param exporting Is this being exported from PDE? + * @param name Name of the class being created. + */ + void writeHeader(PrintStream out, String className) { + + // must include processing.core + out.print("import processing.core.*; "); + + // emit emports that are needed for classes from the code folder + if (extraImports != null) { + for (int i = 0; i < extraImports.length; i++) { + out.print("import " + extraImports[i] + "; "); + } + } + + if (codeFolderImports != null) { + for (int i = 0; i < codeFolderImports.length; i++) { + out.print("import " + codeFolderImports[i] + "; "); + } + } + + // emit standard imports (read from pde.properties) + // for each language level that's being used. + String jdkVersionStr = PdePreferences.get("preproc.jdk_version"); + + int jdkVersion = JDK11; // default + if (jdkVersionStr.equals("1.3")) { jdkVersion = JDK13; }; + if (jdkVersionStr.equals("1.4")) { jdkVersion = JDK14; }; + + if (baseImports != null) { + int length = baseImports.length; + for (int i = 0; i < length; i++) { + out.print("import " + baseImports[i] + ";"); + } + } else { + for (int i = 0; i <= jdkVersion; i++) { + for (int j = 0; j < defaultImports[i].length; j++) { + out.print("import " + defaultImports[i][j] + ".*; "); + } + } + } + + if (programType < JAVA) { + // open the class definition + if (baseClass != null) { + out.print("public class " + className + " extends " + baseClass + "{"); + } else { + out.print("public class " + className + " extends PApplet {"); + } + + if (programType == STATIC) { + // now that size() and background() can go inside of draw() + // actually, use setup(), because when running externally + // the applet size needs to be set before the window is drawn, + // meaning that after setup() things need to be ducky. + //out.print("public void draw() {"); + out.print("public void setup() {"); + } + } + } + + /** + * Write any necessary closing text. + * + * @param out PrintStream to write it to. + */ + void writeFooter(PrintStream out) { + + if (programType == STATIC) { + // close off draw() definition + out.print("noLoop(); "); + out.print("}"); + } + + if (programType < JAVA) { + // close off the class definition + out.print("}"); + } + } + + + static String advClassName = ""; + + /** + * Find the first CLASS_DEF node in the tree, and return the name of the + * class in question. + * + * XXXdmose right now, we're using a little hack to the grammar to get + * this info. In fact, we should be descending the AST passed in. + */ + String getFirstClassName(AST ast) { + + String t = advClassName; + advClassName = ""; + + return t; + } + +} diff --git a/app/PdePreverifier.java b/app/PdePreverifier.java new file mode 100755 index 000000000..36f678fae --- /dev/null +++ b/app/PdePreverifier.java @@ -0,0 +1,55 @@ +import java.io.*; + +public class PdePreverifier implements PdeMessageConsumer { + + public PdePreverifier() { + } + + public boolean preverify(File source, File output) { + String wtkPath = PdePreferences.get("wtk.path"); + String wtkBinPath = wtkPath + File.separator + "bin" + File.separator; + String wtkLibPath = wtkPath + File.separator + "lib" + File.separator; + + StringBuffer command = new StringBuffer(); + command.append(wtkBinPath); + command.append("preverify.exe -target CLDC1.0 -classpath "); + command.append(wtkLibPath); + command.append("cldcapi10.jar;"); + command.append(wtkLibPath); + command.append("midpapi10.jar;lib"); + command.append(File.separator); + command.append("mobile.jar"); + command.append(" -d \""); + command.append(output.getPath()); + command.append("\" \""); + command.append(source.getPath()); + command.append("\""); + //System.out.println(command.toString()); + try { + Process p = Runtime.getRuntime().exec(command.toString()); + boolean running = true; + int result = -1; + while (running) { + try { + result = p.waitFor(); + new PdeMessageSiphon(p.getInputStream(), this); + new PdeMessageSiphon(p.getErrorStream(), this); + + running = false; + } catch (InterruptedException ie) { + ie.printStackTrace (); + } + } + //System.out.println("Preverify complete!"); + return (result == 0); + } catch (Exception e) { + e.printStackTrace (); + } + + return false; + } + + public void message(String s) { + System.err.println(s); + } +} diff --git a/app/PdeSketch.java b/app/PdeSketch.java new file mode 100755 index 000000000..0f09c3207 --- /dev/null +++ b/app/PdeSketch.java @@ -0,0 +1,2034 @@ +/* -*- mode: jde; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + PdeSketch - stores information about files in the current sketch + Part of the Processing project - http://processing.org + + Except where noted, code is written by Ben Fry + Copyright (c) 2001-04 Massachusetts Institute of Technology + + 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 +*/ + +import processing.core.*; + +import java.awt.FileDialog; +import java.io.*; +import java.util.*; +import java.util.jar.*; +import java.util.zip.*; + +import javax.swing.JOptionPane; + +import com.oroinc.text.regex.*; + + +public class PdeSketch { + static String TEMP_BUILD_PATH = "lib" + File.separator + "build"; + static File tempBuildFolder; + + PdeEditor editor; + + // name of sketch, which is the name of main file + // (without .pde or .java extension) + String name; + + // name of 'main' file, used by load(), such as sketch_04040.pde + String mainFilename; + //String path; // path to 'main' file for this sketch + + // true if any of the files have been modified + boolean modified; + + boolean library; // true if it's a library + + public File folder; //sketchFolder; + File dataFolder; + File codeFolder; + + static final int PDE = 0; + static final int JAVA = 1; + + PdeCode current; + int codeCount; + PdeCode code[]; + + int hiddenCount; + PdeCode hidden[]; + + // all these set each time build() is called + String mainClassName; + String classPath; + String libraryPath; + boolean externalRuntime; + Vector importedLibraries; // vec of File objects + + /** + * path is location of the main .pde file, because this is also + * simplest to use when opening the file from the finder/explorer. + */ + public PdeSketch(PdeEditor editor, String path) throws IOException { + this.editor = editor; + + File mainFile = new File(path); + //System.out.println("main file is " + mainFile); + + mainFilename = mainFile.getName(); + //System.out.println("main file is " + mainFilename); + + // get the name of the sketch by chopping .pde or .java + // off of the main file name + if (mainFilename.endsWith(".pde")) { + name = mainFilename.substring(0, mainFilename.length() - 4); + } else if (mainFilename.endsWith(".java")) { + name = mainFilename.substring(0, mainFilename.length() - 5); + } + + // lib/build must exist when the application is started + // it is added to the CLASSPATH by default, but if it doesn't + // exist when the application is started, then java will remove + // the entry from the CLASSPATH, causing PdeRuntime to fail. + // + tempBuildFolder = new File(TEMP_BUILD_PATH); + if (!tempBuildFolder.exists()) { + tempBuildFolder.mkdirs(); + PdeBase.showError("Required folder missing", + "A required folder was missing from \n" + + "from your installation of Processing.\n" + + "It has now been replaced, please restart \n" + + "the application to complete the repair.", null); + } + + folder = new File(new File(path).getParent()); + //System.out.println("sketch dir is " + folder); + + codeFolder = new File(folder, "code"); + dataFolder = new File(folder, "data"); + + File libraryFolder = new File(folder, "library"); + if (libraryFolder.exists()) { + library = true; + } + + load(); + } + + + /** + * Build the list of files. + * + * Generally this is only done once, rather than + * each time a change is made, because otherwise it gets to be + * a nightmare to keep track of what files went where, because + * not all the data will be saved to disk. + * + * The exception is when an external editor is in use, + * in which case the load happens each time "run" is hit. + */ + public void load() { + // get list of files in the sketch folder + String list[] = folder.list(); + + for (int i = 0; i < list.length; i++) { + if (list[i].endsWith(".pde")) codeCount++; + else if (list[i].endsWith(".java")) codeCount++; + else if (list[i].endsWith(".pde.x")) hiddenCount++; + else if (list[i].endsWith(".java.x")) hiddenCount++; + } + + code = new PdeCode[codeCount]; + hidden = new PdeCode[hiddenCount]; + + int codeCounter = 0; + int hiddenCounter = 0; + + for (int i = 0; i < list.length; i++) { + if (list[i].endsWith(".pde")) { + code[codeCounter++] = + new PdeCode(list[i].substring(0, list[i].length() - 4), + new File(folder, list[i]), + PDE); + + } else if (list[i].endsWith(".java")) { + code[codeCounter++] = + new PdeCode(list[i].substring(0, list[i].length() - 5), + new File(folder, list[i]), + JAVA); + + } else if (list[i].endsWith(".pde.x")) { + hidden[hiddenCounter++] = + new PdeCode(list[i].substring(0, list[i].length() - 6), + new File(folder, list[i]), + PDE); + + } else if (list[i].endsWith(".java.x")) { + hidden[hiddenCounter++] = + new PdeCode(list[i].substring(0, list[i].length() - 7), + new File(folder, list[i]), + JAVA); + } + } + //System.out.println("code count 2 is " + codeCount); + + // remove any entries that didn't load properly + int index = 0; + while (index < codeCount) { + if (code[index].program == null) { + //hide(index); // although will this file be hidable? + for (int i = index+1; i < codeCount; i++) { + code[i-1] = code[i]; + } + codeCount--; + + } else { + index++; + } + } + //System.out.println("code count 3 is " + codeCount); + + // move the main class to the first tab + // start at 1, if it's at zero, don't bother + //System.out.println("looking for " + mainFilename); + for (int i = 1; i < codeCount; i++) { + if (code[i].file.getName().equals(mainFilename)) { + //System.out.println("found main code at slot " + i); + PdeCode temp = code[0]; + code[0] = code[i]; + code[i] = temp; + break; + } + } + + // sort the entries at the top + sortCode(); + + // set the main file to be the current tab + //current = code[0]; + setCurrent(0); + } + + + protected void insertCode(PdeCode newCode) { + // add file to the code/codeCount list, resort the list + if (codeCount == code.length) { + PdeCode temp[] = new PdeCode[codeCount+1]; + System.arraycopy(code, 0, temp, 0, codeCount); + code = temp; + } + code[codeCount++] = newCode; + } + + + protected void sortCode() { + // cheap-ass sort of the rest of the files + // it's a dumb, slow sort, but there shouldn't be more than ~5 files + for (int i = 1; i < codeCount; i++) { + int who = i; + for (int j = i + 1; j < codeCount; j++) { + if (code[j].name.compareTo(code[who].name) < 0) { + who = j; // this guy is earlier in the alphabet + } + } + if (who != i) { // swap with someone if changes made + PdeCode temp = code[who]; + code[who] = code[i]; + code[i] = temp; + } + } + } + + boolean renamingCode; + + + public void newCode() { + //System.out.println("new code"); + // ask for name of new file + // maybe just popup a text area? + renamingCode = false; + editor.status.edit("Name for new file:", ""); + } + + + public void renameCode() { + // don't allow rename of the main code + if (current == code[0]) return; + // TODO maybe gray out the menu on setCurrent(0) + + // ask for new name of file (internal to window) + // TODO maybe just popup a text area? + renamingCode = true; + editor.status.edit("New name for file:", current.name); + } + + + /** + * This is called upon return from entering a new file name. + * (that is, from either newCode or renameCode after the prompt) + * This code is almost identical for both the newCode and renameCode + * cases, so they're kept merged except for right in the middle + * where they diverge. + */ + public void nameCode(String newName) { + // if renaming to the same thing as before, just ignore. + // also ignoring case here, because i don't want to write + // a bunch of special stuff for each platform + // (osx is case insensitive but preserving, windows insensitive, + // *nix is sensitive and preserving.. argh) + if (renamingCode && newName.equalsIgnoreCase(current.name)) { + // exit quietly for the 'rename' case. + // if it's a 'new' then an error will occur down below + return; + } + + String newFilename = null; + int newFlavor = 0; + + // add .pde to file if it has no extension + if (newName.endsWith(".pde")) { + newFilename = newName; + newName = newName.substring(0, newName.length() - 4); + newFlavor = PDE; + + } else if (newName.endsWith(".java")) { + newFilename = newName; + newName = newName.substring(0, newName.length() - 5); + newFlavor = JAVA; + + } else { + newFilename = newName + ".pde"; + newFlavor = PDE; + } + + // dots are allowed for the .pde and .java, but not in general + // so make sure the user didn't name things poo.time.pde + // or something like that (nothing against poo time) + if (newName.indexOf('.') != -1) { + newName = PdeSketchbook.sanitizedName(newName); + newFilename = newName + ((newFlavor == PDE) ? ".pde" : ".java"); + } + + // create the new file, new PdeCode object and load it + File newFile = new File(folder, newFilename); + if (newFile.exists()) { // yay! users will try anything + PdeBase.showMessage("Nope", + "A file named \"" + newFile + "\" already exists\n" + + "in \"" + folder.getAbsolutePath() + "\""); + return; + } + + if (renamingCode) { + if (!current.file.renameTo(newFile)) { + PdeBase.showWarning("Error", + "Could not rename \"" + current.file.getName() + + "\" to \"" + newFile.getName() + "\"", null); + return; + } + current.file = newFile; + current.name = newName; + current.flavor = newFlavor; + + } else { // creating a new file + try { + newFile.createNewFile(); // TODO returns a boolean + } catch (IOException e) { + PdeBase.showWarning("Error", + "Could not create the file \"" + newFile + "\"\n" + + "in \"" + folder.getAbsolutePath() + "\"", e); + return; + } + PdeCode newCode = new PdeCode(newName, newFile, newFlavor); + insertCode(newCode); + } + + // sort the entries + sortCode(); + + // set the new guy as current + setCurrent(newName); + + // update the tabs + editor.header.repaint(); + } + + + /** + * Remove a piece of code from the sketch and from the disk. + */ + public void deleteCode() { + // don't allow delete of the main code + // TODO maybe gray out the menu on setCurrent(0) + if (current == code[0]) { + PdeBase.showMessage("Can't do that", + "You cannot delete the main " + + ".pde file from a sketch\n"); + return; + } + + // confirm deletion with user, yes/no + Object[] options = { "OK", "Cancel" }; + String prompt = + "Are you sure you want to delete \"" + current.name + "\"?"; + int result = JOptionPane.showOptionDialog(editor, + prompt, + "Delete", + JOptionPane.YES_NO_OPTION, + JOptionPane.QUESTION_MESSAGE, + null, + options, + options[0]); + if (result == JOptionPane.YES_OPTION) { + // delete the file + if (!current.file.delete()) { + PdeBase.showMessage("Couldn't do it", + "Could not delete \"" + current.name + "\"."); + return; + } + + // remove code from the list + removeCode(current); + + // just set current tab to the main tab + setCurrent(0); + + // update the tabs + editor.header.repaint(); + } + } + + + protected void removeCode(PdeCode which) { + // remove it from the internal list of files + // resort internal list of files + for (int i = 0; i < codeCount; i++) { + if (code[i] == which) { + for (int j = i; j < codeCount-1; j++) { + code[j] = code[j+1]; + } + codeCount--; + return; + } + } + System.err.println("removeCode: internal error.. could not find code"); + } + + + public void hideCode() { + // don't allow hide of the main code + // TODO maybe gray out the menu on setCurrent(0) + if (current == code[0]) { + PdeBase.showMessage("Can't do that", + "You cannot hide the main " + + ".pde file from a sketch\n"); + return; + } + + // rename the file + File newFile = new File(current.file.getAbsolutePath() + ".x"); + if (!current.file.renameTo(newFile)) { + PdeBase.showWarning("Error", + "Could not hide " + + "\"" + current.file.getName() + "\".", null); + return; + } + current.file = newFile; + + // move it to the hidden list + if (hiddenCount == hidden.length) { + PdeCode temp[] = new PdeCode[hiddenCount+1]; + System.arraycopy(hidden, 0, temp, 0, hiddenCount); + hidden = temp; + } + hidden[hiddenCount++] = current; + + // remove it from the main list + removeCode(current); + + // update the tabs + setCurrent(0); + editor.header.repaint(); + } + + + public void unhideCode(String what) { + //System.out.println("unhide " + e); + int unhideIndex = -1; + for (int i = 0; i < hiddenCount; i++) { + if (hidden[i].name.equals(what)) { + unhideIndex = i; + + // remove from the 'hidden' list + for (int j = i; j < hiddenCount-1; j++) { + hidden[j] = hidden[j+1]; + } + hiddenCount--; + break; + } + } + if (unhideIndex == -1) { + System.err.println("internal error: could find " + what + " to unhide."); + return; + } + PdeCode unhideCode = hidden[unhideIndex]; + if (!unhideCode.file.exists()) { + PdeBase.showMessage("Can't unhide", + "The file \"" + what + "\" no longer exists."); + //System.out.println(unhideCode.file); + return; + } + String unhidePath = unhideCode.file.getAbsolutePath(); + File unhideFile = + new File(unhidePath.substring(0, unhidePath.length() - 2)); + + if (!unhideCode.file.renameTo(unhideFile)) { + PdeBase.showMessage("Can't unhide", + "The file \"" + what + "\" could not be" + + "renamed and unhidden."); + return; + } + unhideCode.file = unhideFile; + insertCode(unhideCode); + sortCode(); + setCurrent(unhideCode.name); + editor.header.repaint(); + } + + + /** + * Return true if this sketch is a library. + */ + public boolean isLibrary() { + return library; + } + + + /** + * Sets the modified value for the code in the frontmost tab. + */ + public void setModified() { + current.modified = true; + calcModified(); + } + + + public void calcModified() { + modified = false; + for (int i = 0; i < codeCount; i++) { + if (code[i].modified) { + modified = true; + break; + } + } + editor.header.repaint(); + } + + + /** + * Save all code in the current sketch. + */ + public boolean save() throws IOException { + // first get the contents of the editor text area + if (current.modified) { + current.program = editor.getText(); + } + + // see if actually modified + if (!modified) return false; + + // check if the files are read-only. + // if so, need to first do a "save as". + if (isReadOnly()) { + PdeBase.showMessage("Sketch is read-only", + "Some files are marked \"read-only\", so you'll\n" + + "need to re-save this sketch to another location."); + // if the user cancels, give up on the save() + if (!saveAs()) return false; + } + + for (int i = 0; i < codeCount; i++) { + if (code[i].modified) code[i].save(); + } + calcModified(); + return true; + } + + + public void saveCurrent() throws IOException { + current.save(); + calcModified(); + } + + + /** + * handles 'save as' for a sketch.. essentially duplicates + * the current sketch folder to a new location, and then calls + * 'save'. (needs to take the current state of the open files + * and save them to the new folder.. but not save over the old + * versions for the old sketch..) + * + * also removes the previously-generated .class and .jar files, + * because they can cause trouble. + */ + public boolean saveAs() throws IOException { + // get new name for folder + FileDialog fd = new FileDialog(editor, //new Frame(), + "Save sketch folder as...", + FileDialog.SAVE); + // always default to the sketchbook folder.. + //fd.setDirectory(PdePreferences.get("sketchbook.path")); + fd.setDirectory(folder.getParent()); + fd.setFile(folder.getName()); + //System.out.println("setting to " + folder.getParent()); + + // TODO or maybe this should default to the + // parent dir of the old folder? + + fd.show(); + String newParentDir = fd.getDirectory(); + String newName = fd.getFile(); + + // user cancelled selection + if (newName == null) return false; + newName = PdeSketchbook.sanitizeName(newName); + + // new sketch folder + File newFolder = new File(newParentDir, newName); + + // make sure the paths aren't the same + if (newFolder.equals(folder)) { + PdeBase.showWarning("You can't fool me", + "The new sketch name and location are the same\n" + + "as the old. I ain't not doin nuthin'.", null); + return false; + } + + // check to see if the user is trying to save this sketch + // inside the same sketch + try { + String newPath = newFolder.getCanonicalPath() + File.separator; + String oldPath = folder.getCanonicalPath() + File.separator; + //System.out.println(newPath); + //System.out.println(oldPath); + + if (newPath.indexOf(oldPath) == 0) { + PdeBase.showWarning("How very Borges of you", + "You cannot save the sketch into a folder\n" + + "inside itself. This would go on forever.", null); + return false; + } + } catch (IOException e) { } + + // copy the entire contents of the sketch folder + PdeBase.copyDir(folder, newFolder); + + // change the references to the dir location in PdeCode files + for (int i = 0; i < codeCount; i++) { + code[i].file = new File(newFolder, code[i].file.getName()); + } + for (int i = 0; i < hiddenCount; i++) { + hidden[i].file = new File(newFolder, hidden[i].file.getName()); + } + + // remove the old sketch file from the new dir + code[0].file.delete(); + // name for the new main .pde file + code[0].file = new File(newFolder, newName + ".pde"); + code[0].name = newName; + // write the contents to the renamed file + // (this may be resaved if the code is modified) + code[0].save(); + + // change the other paths + String oldName = name; + name = newName; + File oldFolder = folder; + folder = newFolder; + dataFolder = new File(folder, "data"); + codeFolder = new File(folder, "code"); + + // remove the 'applet', 'application', 'library' folders + // from the copied version. + // otherwise their .class and .jar files can cause conflicts. + PdeBase.removeDir(new File(folder, "applet")); + PdeBase.removeDir(new File(folder, "application")); + PdeBase.removeDir(new File(folder, "library")); + + // do a "save" + // this will take care of the unsaved changes in each of the tabs + save(); + + // get the changes into the sketchbook menu + //sketchbook.rebuildMenu(); + // done inside PdeEditor instead + + // update the tabs for the name change + editor.header.repaint(); + + // let PdeEditor know that the save was successful + return true; + } + + + /** + * Prompt the user for a new file to the sketch. + * This could be .class or .jar files for the code folder, + * .pde or .java files for the project, + * or .dll, .jnilib, or .so files for the code folder + */ + public void addFile() { + // get a dialog, select a file to add to the sketch + String prompt = + "Select an image or other data file to copy to your sketch"; + //FileDialog fd = new FileDialog(new Frame(), prompt, FileDialog.LOAD); + FileDialog fd = new FileDialog(editor, prompt, FileDialog.LOAD); + fd.show(); + + String directory = fd.getDirectory(); + String filename = fd.getFile(); + if (filename == null) return; + + // copy the file into the folder. if people would rather + // it move instead of copy, they can do it by hand + File sourceFile = new File(directory, filename); + + File destFile = null; + boolean addingCode = false; + + // if the file appears to be code related, drop it + // into the code folder, instead of the data folder + if (filename.toLowerCase().endsWith(".class") || + filename.toLowerCase().endsWith(".jar") || + filename.toLowerCase().endsWith(".dll") || + filename.toLowerCase().endsWith(".jnilib") || + filename.toLowerCase().endsWith(".so")) { + //File codeFolder = new File(this.folder, "code"); + if (!codeFolder.exists()) codeFolder.mkdirs(); + destFile = new File(codeFolder, filename); + + } else if (filename.toLowerCase().endsWith(".pde") || + filename.toLowerCase().endsWith(".java")) { + destFile = new File(this.folder, filename); + addingCode = true; + + } else { + //File dataFolder = new File(this.folder, "data"); + if (!dataFolder.exists()) dataFolder.mkdirs(); + destFile = new File(dataFolder, filename); + } + + // make sure they aren't the same file + if (!addingCode && sourceFile.equals(destFile)) { + PdeBase.showWarning("You can't fool me", + "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; + } + + // in case the user is "adding" the code in an attempt + // to update the sketch's tabs + if (!sourceFile.equals(destFile)) { + try { + PdeBase.copyFile(sourceFile, destFile); + } catch (IOException e) { + PdeBase.showWarning("Error adding file", + "Could not add '" + filename + + "' to the sketch.", e); + } + } + + // make the tabs update after this guy is added + if (addingCode) { + String newName = destFile.getName(); + int newFlavor = -1; + if (newName.toLowerCase().endsWith(".pde")) { + newName = newName.substring(0, newName.length() - 4); + newFlavor = PDE; + } else { + newName = newName.substring(0, newName.length() - 5); + newFlavor = JAVA; + } + + // see also "nameCode" for identical situation + PdeCode newCode = new PdeCode(newName, destFile, newFlavor); + insertCode(newCode); + sortCode(); + setCurrent(newName); + editor.header.repaint(); + } + } + + + public void addLibrary(String jarPath) { + String list[] = PdeCompiler.packageListFromClassPath(jarPath); + + // import statements into the main sketch file (code[0]) + // if the current code is a .java file, insert into current + if (current.flavor == PDE) { + setCurrent(0); + } + // could also scan the text in the file to see if each import + // statement is already in there, but if the user has the import + // commented out, then this will be a problem. + StringBuffer buffer = new StringBuffer(); + for (int i = 0; i < list.length; i++) { + buffer.append("import "); + buffer.append(list[i]); + buffer.append(".*;\n"); + } + buffer.append('\n'); + buffer.append(editor.getText()); + editor.setText(buffer.toString(), false); + setModified(); + } + + + /** + * Change what file is currently being edited. + * 1. store the String for the text of the current file. + * 2. retrieve the String for the text of the new file. + * 3. change the text that's visible in the text area + */ + public void setCurrent(int which) { + // get the text currently being edited + //program[current] = editor.getText(); + if (current != null) { + current.program = editor.getText(); + } + + current = code[which]; + + // set to the text for this file + // 'true' means to wipe out the undo buffer + // (so they don't undo back to the other file.. whups!) + editor.setText(current.program, true); + + // and i'll personally make a note of the change + //current = which; + + editor.header.rebuild(); + } + + + /** + * Internal helper function to set the current tab + * based on a name (used by codeNew and codeRename). + */ + protected void setCurrent(String findName) { + for (int i = 0; i < codeCount; i++) { + if (findName.equals(code[i].name)) { + setCurrent(i); + return; + } + } + } + + + /** + * Cleanup temporary files used during a build/run. + */ + protected void cleanup() { + // if the java runtime is holding onto any files in the build dir, we + // won't be able to delete them, so we need to force a gc here + // + System.gc(); + + // note that we can't remove the builddir itself, otherwise + // the next time we start up, internal runs using PdeRuntime won't + // work because the build dir won't exist at startup, so the classloader + // will ignore the fact that that dir is in the CLASSPATH in run.sh + // + //File dirObject = new File(TEMP_BUILD_PATH); + //PdeBase.removeDescendants(dirObject); + PdeBase.removeDescendants(tempBuildFolder); + } + + + /** + * Preprocess, Compile, and Run the current code. + * This is not Runnable.run(), but a handler for the run() command. + * + * There are three main parts to this process: + * + * (0. if not java, then use another 'engine'.. i.e. python) + * + * 1. do the p5 language preprocessing + * this creates a working .java file in a specific location + * better yet, just takes a chunk of java code and returns a + * new/better string editor can take care of saving this to a + * file location + * + * 2. compile the code from that location + * catching errors along the way + * placing it in a ready classpath, or .. ? + * + * 3. run the code + * needs to communicate location for window + * and maybe setup presentation space as well + * run externally if a code folder exists, + * or if more than one file is in the project + * + * X. afterwards, some of these steps need a cleanup function + */ + //public void run() throws PdeException { + public boolean handleRun() throws PdeException { + current.program = editor.getText(); + + // TODO record history here + //current.history.record(program, PdeHistory.RUN); + + // if an external editor is being used, need to grab the + // latest version of the code from the file. + if (PdePreferences.getBoolean("editor.external")) { + // history gets screwed by the open.. + //String historySaved = history.lastRecorded; + //handleOpen(sketch); + //history.lastRecorded = historySaved; + + // nuke previous files and settings, just get things loaded + load(); + } + + // in case there were any boogers left behind + // do this here instead of after exiting, since the exit + // can happen so many different ways.. and this will be + // better connected to the dataFolder stuff below. + cleanup(); + + // make up a temporary class name to suggest. + // name will only be used if the code is not in ADVANCED mode. + String suggestedClassName = + ("Temporary_" + String.valueOf((int) (Math.random() * 10000)) + + "_" + String.valueOf((int) (Math.random() * 10000))); + + // handle preprocessing the main file's code + mainClassName = build(TEMP_BUILD_PATH, suggestedClassName); + // externalPaths is magically set by build() + + if (!externalRuntime) { // only if not running externally already + // copy contents of data dir into lib/build + if (dataFolder.exists()) { + // just drop the files in the build folder (pre-68) + //PdeBase.copyDir(dataDir, buildDir); + // drop the files into a 'data' subfolder of the build dir + try { + PdeBase.copyDir(dataFolder, new File(tempBuildFolder, "data")); + } catch (IOException e) { + e.printStackTrace(); + throw new PdeException("Problem copying files from data folder"); + } + } + } + + // if the compilation worked, run the applet +// if (mainClassName != null) { + + /* + if (externalPaths == null) { + externalPaths = + PdeCompiler.calcClassPath(null) + File.pathSeparator + + tempBuildPath; + } else { + externalPaths = + tempBuildPath + File.pathSeparator + + PdeCompiler.calcClassPath(null) + File.pathSeparator + + externalPaths; + } + */ + + // get a useful folder name for the 'code' folder + // so that it can be included in the java.library.path + /* + String libraryPath = ""; + if (externalCode != null) { + libraryPath = externalCode.getCanonicalPath(); + } + */ + + // create a runtime object +// runtime = new PdeRuntime(this, editor); + + // if programType is ADVANCED + // or the code/ folder is not empty -> or just exists (simpler) + // then set boolean for external to true + // include path to build in front, then path for code folder + // when passing the classpath through + // actually, build will already be in there, just prepend code + + // use the runtime object to consume the errors now + // no need to bother recycling the old guy + //PdeMessageStream messageStream = new PdeMessageStream(runtime); + + // start the applet +// runtime.start(presenting ? presentLocation : appletLocation); //, + //new PrintStream(messageStream)); + + // spawn a thread to update PDE GUI state +// watcher = new RunButtonWatcher(); + +// } else { + // [dmose] throw an exception here? + // [fry] iirc the exception will have already been thrown +// cleanup(); +// } + return (mainClassName != null); + } + + + /** + * Have the contents of the currently visible tab been modified? + */ + /* + public boolean isCurrentModified() { + return modified[current]; + } + */ + + + /** + * Build all the code for this sketch. + * + * In an advanced program, the returned classname could be different, + * which is why the className is set based on the return value. + * A compilation error will burp up a PdeException. + * + * @return null if compilation failed, main class name if not + */ + protected String build(String buildPath, String suggestedClassName) + throws PdeException { + //String importPackageList[] = null; + String javaClassPath = System.getProperty("java.class.path"); + // remove quotes if any.. this is an annoying thing on windows + if (javaClassPath.startsWith("\"") && javaClassPath.endsWith("\"")) { + javaClassPath = javaClassPath.substring(1, javaClassPath.length() - 1); + } + javaClassPath = PdeSketchbook.librariesClassPath + File.pathSeparator + + javaClassPath; + + return build(buildPath, suggestedClassName, null, null, + PdeCompiler.calcBootClassPath(), javaClassPath); + } + + protected String build(String buildPath, String suggestedClassName, + String baseClass, String[] baseImports, + String bootClassPath, String additionalClassPath) + throws PdeException { + + String codeFolderPackages[] = null; + + classPath = buildPath; + if (additionalClassPath != null) { + classPath += File.pathSeparator + additionalClassPath; + } + + // figure out the contents of the code folder to see if there + // are files that need to be added to the imports + //File codeFolder = new File(folder, "code"); + if (codeFolder.exists()) { + externalRuntime = true; + classPath += File.pathSeparator + + PdeCompiler.contentsToClassPath(codeFolder); + //importPackageList = PdeCompiler.packageListFromClassPath(classPath); + libraryPath = codeFolder.getAbsolutePath(); + + // get a list of .jar files in the "code" folder + // (class files in subfolders should also be picked up) + String codeFolderClassPath = + PdeCompiler.contentsToClassPath(codeFolder); + // get list of packages found in those jars + codeFolderPackages = + PdeCompiler.packageListFromClassPath(codeFolderClassPath); + //PApplet.println(libraryPath); + //PApplet.println("packages:"); + //PApplet.printarr(codeFolderPackages); + } else { + // 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) externalRuntime = true; + } + //externalRuntime = (codeCount > 1); // may still be set true later + //importPackageList = null; + libraryPath = ""; + } + + // if 'data' folder is large, set to external runtime + if (dataFolder.exists() && + PdeBase.calcFolderSize(dataFolder) > 768 * 1024) { // if > 768k + externalRuntime = true; + } + + + // 1. concatenate all .pde files to the 'main' pde + // store line number for starting point of each code bit + + StringBuffer bigCode = new StringBuffer(code[0].program); + int bigCount = countLines(code[0].program); + + for (int i = 1; i < codeCount; i++) { + if (code[i].flavor == PDE) { + code[i].lineOffset = ++bigCount; + bigCode.append('\n'); + bigCode.append(code[i].program); + bigCount += countLines(code[i].program); + code[i].preprocName = null; // don't compile me + } + } + + + // 2. run preproc on that code using the sugg class name + // to create a single .java file and write to buildpath + + String primaryClassName = null; + + PdePreprocessor preprocessor = new PdePreprocessor(); + try { + // if (i != 0) preproc will fail if a pde file is not + // java mode, since that's required + preprocessor.setBaseClass(baseClass); + preprocessor.setBaseImports(baseImports); + + String className = + preprocessor.write(bigCode.toString(), buildPath, + suggestedClassName, codeFolderPackages); + //preprocessor.write(bigCode.toString(), buildPath, + // suggestedClassName, importPackageList); + if (className == null) { + throw new PdeException("Could not find main class"); + // this situation might be perfectly fine, + // (i.e. if the file is empty) + //System.out.println("No class found in " + code[i].name); + //System.out.println("(any code in that file will be ignored)"); + //System.out.println(); + + } else { + code[0].preprocName = className + ".java"; + } + + // store this for the compiler and the runtime + primaryClassName = className; + //System.out.println("primary class " + primaryClassName); + + // check if the 'main' file is in java mode + if ((PdePreprocessor.programType == PdePreprocessor.JAVA) || + (preprocessor.extraImports.length != 0)) { + externalRuntime = true; // we in advanced mode now, boy + } + + } catch (antlr.RecognitionException re) { + // this even returns a column + int errorFile = 0; + int errorLine = re.getLine() - 1; + for (int i = 1; i < codeCount; i++) { + if ((code[i].flavor == PDE) && + (code[i].lineOffset < errorLine)) { + errorFile = i; + } + } + errorLine -= code[errorFile].lineOffset; + + throw new PdeException(re.getMessage(), errorFile, + errorLine, re.getColumn()); + + } catch (antlr.TokenStreamRecognitionException tsre) { + // while this seems to store line and column internally, + // there doesn't seem to be a method to grab it.. + // so instead it's done using a regexp + PatternMatcher matcher = new Perl5Matcher(); + PatternCompiler compiler = new Perl5Compiler(); + // line 3:1: unexpected char: 0xA0 + String mess = "^line (\\d+):(\\d+):\\s"; + + Pattern pattern = null; + try { + pattern = compiler.compile(mess); + } catch (MalformedPatternException e) { + PdeBase.showWarning("Internal Problem", + "An internal error occurred while trying\n" + + "to compile the sketch. Please report\n" + + "this online at http://processing.org/bugs", e); + } + + PatternMatcherInput input = + new PatternMatcherInput(tsre.toString()); + if (matcher.contains(input, pattern)) { + MatchResult result = matcher.getMatch(); + + int errorLine = Integer.parseInt(result.group(1).toString()) - 1; + int errorColumn = Integer.parseInt(result.group(2).toString()); + int errorFile = 0; + for (int i = 1; i < codeCount; i++) { + if ((code[i].flavor == PDE) && + (code[i].lineOffset < errorLine)) { + errorFile = i; + } + } + errorLine -= code[errorFile].lineOffset; + + throw new PdeException(tsre.getMessage(), + errorFile, errorLine, errorColumn); + + } else { + // this is bad, defaults to the main class.. hrm. + throw new PdeException(tsre.toString(), 0, -1, -1); + } + + } catch (PdeException pe) { + // PdeExceptions are caught here and re-thrown, so that they don't + // get lost in the more general "Exception" handler below. + throw pe; + + } catch (Exception ex) { + // TODO better method for handling this? + System.err.println("Uncaught exception type:" + ex.getClass()); + ex.printStackTrace(); + throw new PdeException(ex.toString()); + } + + // grab the imports from the code just preproc'd + + importedLibraries = new Vector(); + String imports[] = preprocessor.extraImports; + for (int i = 0; i < imports.length; i++) { + // remove things up to the last dot + String entry = imports[i].substring(0, imports[i].lastIndexOf('.')); + //System.out.println("found package " + entry); + File libFolder = (File) PdeSketchbook.importToLibraryTable.get(entry); + if (libFolder == null) { + //throw new PdeException("Could not find library for " + entry); + continue; + } + //System.out.println(" found lib folder " + libFolder); + importedLibraries.add(libFolder); + + libraryPath += File.pathSeparator + libFolder.getAbsolutePath(); + } + + + // 3. then loop over the code[] and save each .java file + + for (int i = 0; i < codeCount; i++) { + if (code[i].flavor == JAVA) { + // 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 + ".java"; + try { + PdeBase.saveFile(code[i].program, new File(buildPath, filename)); + } catch (IOException e) { + e.printStackTrace(); + throw new PdeException("Problem moving " + filename + + " to the build folder"); + } + code[i].preprocName = filename; + } + } + + // compile the program. errors will happen as a PdeException + // that will bubble up to whomever called build(). + // + PdeCompiler compiler = new PdeCompiler(); + boolean success = compiler.compile(this, buildPath, bootClassPath); + //System.out.println("success = " + success + " ... " + primaryClassName); + return success ? primaryClassName : null; + } + + + protected int countLines(String what) { + char c[] = what.toCharArray(); + int count = 0; + for (int i = 0; i < c.length; i++) { + if (c[i] == '\n') count++; + } + return count; + } + + + /** + * Called by PdeEditor to handle someone having selected 'export'. + * Pops up a dialog box for export options, and then calls the + * necessary function with the parameters from the window. + * + * +-------------------------------------------------------+ + * + + + * + Export to: [ Applet (for the web) + ] [ OK ] + + * + + + * + > Advanced + + * + + + * + - - - - - - - - - - - - - - - - - - - - - - - - - - - + + * + Version: [ Java 1.1 + ] + + * + + + * + Recommended version of Java when exporting applets. + + * + - - - - - - - - - - - - - - - - - - - - - - - - - - - + + * + Version: [ Java 1.3 + ] + + * + + + * + Java 1.3 is not recommended for applets, + + * + unless you are using features that require it. + + * + Using a version of Java other than 1.1 will require + + * + your Windows users to install the Java Plug-In, + + * + and your Macintosh users to be running OS X. + + * + - - - - - - - - - - - - - - - - - - - - - - - - - - - + + * + Version: [ Java 1.4 + ] + + * + + + * + identical message as 1.3 above... + + * + + + * +-------------------------------------------------------+ + * + * +-------------------------------------------------------+ + * + + + * + Export to: [ Application + ] [ OK ] + + * + + + * + > Advanced + + * + - - - - - - - - - - - - - - - - - - - - - - - - - - - + + * + Version: [ Java 1.1 + ] + + * + + + * + Not much point to using Java 1.1 for applications. + + * + To run applications, all users will have to + + * + install Java, in which case they'll most likely + + * + have version 1.3 or later. + + * + - - - - - - - - - - - - - - - - - - - - - - - - - - - + + * + Version: [ Java 1.3 + ] + + * + + + * + Java 1.3 is the recommended setting for exporting + + * + applications. Applications will run on any Windows + + * + or Unix machine with Java installed. Mac OS X has + + * + Java installed with the operation system, so there + + * + is no additional installation will be required. + + * + + + * + - - - - - - - - - - - - - - - - - - - - - - - - - - - + + * + + + * + Platform: [ Mac OS X + ] <-- defaults to current platform + * + + + * + Exports the application as a double-clickable + + * + .app package, compatible with Mac OS X. + + * + - - - - - - - - - - - - - - - - - - - - - - - - - - - + + * + Platform: [ Windows + ] + + * + + + * + Exports the application as a double-clickable + + * + .exe and a handful of supporting files. + + * + - - - - - - - - - - - - - - - - - - - - - - - - - - - + + * + Platform: [ jar file + ] + + * + + + * + A jar file can be used on any platform that has + + * + Java installed. Simply doube-click the jar (or type + + * + "java -jar sketch.jar" at a command prompt) to run + + * + the application. It is the least fancy method for + + * + exporting. + + * + + + * +-------------------------------------------------------+ + * + + * +-------------------------------------------------------+ + * + + + * + Export to: [ Library + ] [ OK ] + + * + + + * +-------------------------------------------------------+ + */ + //public boolean export() throws Exception { + //return exportApplet(true); + //} + + + public boolean exportApplet(/*boolean replaceHtml*/) throws Exception { + boolean replaceHtml = true; + //File appletDir, String exportSketchName, File dataDir) { + //String program = textarea.getText(); + + // create the project directory + // pass null for datapath because the files shouldn't be + // copied to the build dir.. that's only for the temp stuff + File appletDir = new File(folder, "applet"); + + boolean writeHtml = true; + if (appletDir.exists()) { + File htmlFile = new File(appletDir, "index.html"); + if (htmlFile.exists() && !replaceHtml) { + writeHtml = false; + } + } else { + appletDir.mkdirs(); + } + + // build the sketch + String foundName = build(appletDir.getPath(), name); + + // (already reported) error during export, exit this function + if (foundName == null) return false; + + // if name != exportSketchName, then that's weirdness + // BUG unfortunately, that can also be a bug in the preproc :( + if (!name.equals(foundName)) { + PdeBase.showWarning("Error during export", + "Sketch name is " + name + " but the sketch\n" + + "name in the code was " + foundName, null); + return false; + } + + if (writeHtml) { + int wide = PApplet.DEFAULT_WIDTH; + int high = PApplet.DEFAULT_HEIGHT; + + //try { + PatternMatcher matcher = new Perl5Matcher(); + PatternCompiler compiler = new Perl5Compiler(); + + // this matches against any uses of the size() function, + // whether they contain numbers of variables or whatever. + // this way, no warning is shown if size() isn't actually + // used in the applet, which is the case especially for + // beginners that are cutting/pasting from the reference. + String sizing = + "[\\s\\;]size\\s*\\(\\s*(\\S+)\\s*,\\s*(\\S+)\\s*\\);"; + Pattern pattern = compiler.compile(sizing); + + // adds a space at the beginning, in case size() is the very + // first thing in the program (very common), since the regexp + // needs to check for things in front of it. + PatternMatcherInput input = + new PatternMatcherInput(" " + code[0].program); + if (matcher.contains(input, pattern)) { + MatchResult result = matcher.getMatch(); + try { + wide = Integer.parseInt(result.group(1).toString()); + high = Integer.parseInt(result.group(2).toString()); + + } catch (NumberFormatException e) { + // found a reference to size, but it didn't + // seem to contain numbers + final String message = + "The size of this applet could not automatically be\n" + + "determined from your code. You'll have to edit the\n" + + "HTML file to set the size of the applet."; + + PdeBase.showWarning("Could not find applet size", message, null); + } + } // else no size() command found + + // handle this in editor instead, rare or nonexistant + //} catch (MalformedPatternException e) { + //PdeBase.showWarning("Internal Problem", + // "An internal error occurred while trying\n" + + // "to export the sketch. Please report this.", e); + //return false; + //} + + StringBuffer sources = new StringBuffer(); + for (int i = 0; i < codeCount; i++) { + sources.append("" + + code[i].name + " "); + } + + File htmlOutputFile = new File(appletDir, "index.html"); + FileOutputStream fos = new FileOutputStream(htmlOutputFile); + PrintStream ps = new PrintStream(fos); + + // @@sketch@@, @@width@@, @@height@@, @@archive@@, @@source@@ + + InputStream is = null; + // if there is an applet.html file in the sketch folder, use that + File customHtml = new File(folder, "applet.html"); + if (customHtml.exists()) { + is = new FileInputStream(customHtml); + } + if (is == null) { + is = PdeBase.getStream("applet.html"); + } + BufferedReader reader = new BufferedReader(new InputStreamReader(is)); + + String line = null; + while ((line = reader.readLine()) != null) { + if (line.indexOf("@@") != -1) { + StringBuffer sb = new StringBuffer(line); + int index = 0; + while ((index = sb.indexOf("@@sketch@@")) != -1) { + sb.replace(index, index + "@@sketch@@".length(), + name); + } + while ((index = sb.indexOf("@@source@@")) != -1) { + sb.replace(index, index + "@@source@@".length(), + sources.toString()); + } + while ((index = sb.indexOf("@@archive@@")) != -1) { + sb.replace(index, index + "@@archive@@".length(), + name + ".jar"); + } + while ((index = sb.indexOf("@@width@@")) != -1) { + sb.replace(index, index + "@@width@@".length(), + String.valueOf(wide)); + } + while ((index = sb.indexOf("@@height@@")) != -1) { + sb.replace(index, index + "@@height@@".length(), + String.valueOf(high)); + } + line = sb.toString(); + } + ps.println(line); + } + + reader.close(); + ps.flush(); + ps.close(); + } + + // copy the source files to the target, since we like + // to encourage people to share their code + for (int i = 0; i < codeCount; i++) { + try { + PdeBase.copyFile(code[i].file, + new File(appletDir, code[i].file.getName())); + } catch (IOException e) { + + } + } + + // create new .jar file + FileOutputStream zipOutputFile = + new FileOutputStream(new File(appletDir, name + ".jar")); + ZipOutputStream zos = new ZipOutputStream(zipOutputFile); + ZipEntry entry; + + // add the contents of the code folder to the jar + // unpacks all jar files + //File codeFolder = new File(folder, "code"); + if (codeFolder.exists()) { + String includes = PdeCompiler.contentsToClassPath(codeFolder); + packClassPathIntoZipFile(includes, zos); + } + + // add contents of 'library' folders to the jar file + // if a file called 'export.txt' is in there, it contains + // a list of the files that should be exported. + // otherwise, all files are exported. + Enumeration enum = importedLibraries.elements(); + while (enum.hasMoreElements()) { + // in the list is a File object that points the + // library sketch's "library" folder + File libraryFolder = (File)enum.nextElement(); + //System.out.println("exporting files from " + libFolder); + File exportSettings = new File(libraryFolder, "export.txt"); + String exportList[] = null; + if (exportSettings.exists()) { + //exportList = PApplet.loadStrings(exportSettings); + String info[] = PApplet.loadStrings(exportSettings); + for (int i = 0; i < info.length; i++) { + if (info[i].startsWith("applet")) { + int idx = info[i].indexOf('='); // get applet= or applet = + String commas = info[i].substring(idx+1).trim(); + exportList = PApplet.split(commas, ", "); + } + } + } else { + exportList = libraryFolder.list(); + } + for (int i = 0; i < exportList.length; i++) { + if (exportList[i].equals(".") || + exportList[i].equals("..")) continue; + + exportList[i] = PApplet.trim(exportList[i]); + if (exportList[i].equals("")) continue; + + File exportFile = new File(libraryFolder, exportList[i]); + if (!exportFile.exists()) { + System.err.println("File " + exportList[i] + " does not exist"); + + } else if (exportFile.isDirectory()) { + System.err.println("Ignoring sub-folder \"" + exportList[i] + "\""); + + } else if (exportFile.getName().toLowerCase().endsWith(".zip") || + exportFile.getName().toLowerCase().endsWith(".jar")) { + packClassPathIntoZipFile(exportFile.getAbsolutePath(), zos); + + } else { // just copy the file over.. prolly a .dll or something + PdeBase.copyFile(exportFile, + new File(appletDir, exportFile.getName())); + } + } + } + + // add the appropriate bagel to the classpath + /* + String jdkVersion = PdePreferences.get("compiler.jdk_version"); + String bagelJar = "lib/export11.jar"; // default + if (jdkVersion.equals("1.3") || jdkVersion.equals("1.4")) { + bagelJar = "lib/export13.jar"; + } + */ + String bagelJar = "lib/core.jar"; + + //if (jdkVersionStr.equals("1.3")) { bagelJar = "export13.jar" }; + //if (jdkVersionStr.equals("1.4")) { bagelJar = "export14.jar" }; + packClassPathIntoZipFile(bagelJar, zos); + + /* + // add the contents of lib/export to the jar file + // these are the jdk11-only bagel classes + String exportDir = ("lib" + File.separator + + "export" + File.separator); + String bagelClasses[] = new File(exportDir).list(); + + for (int i = 0; i < bagelClasses.length; i++) { + if (!bagelClasses[i].endsWith(".class")) continue; + entry = new ZipEntry(bagelClasses[i]); + zos.putNextEntry(entry); + zos.write(PdeBase.grabFile(new File(exportDir + bagelClasses[i]))); + zos.closeEntry(); + } + */ + + // TODO these two loops are insufficient. + // should instead recursively add entire contents of build folder + // the data folder will already be a subdirectory + // and the classes may be buried in subfolders if a package name was used + + // files to include from data directory + //if ((dataDir != null) && (dataDir.exists())) { + if (dataFolder.exists()) { + String dataFiles[] = dataFolder.list(); + for (int i = 0; i < dataFiles.length; i++) { + // don't export hidden files + // skipping dot prefix removes all: . .. .DS_Store + if (dataFiles[i].charAt(0) == '.') continue; + + entry = new ZipEntry(dataFiles[i]); + zos.putNextEntry(entry); + zos.write(PdeBase.grabFile(new File(dataFolder, dataFiles[i]))); + zos.closeEntry(); + } + } + + // add the project's .class files to the jar + // just grabs everything from the build directory + // since there may be some inner classes + // (add any .class files from the applet dir, then delete them) + String classfiles[] = appletDir.list(); + for (int i = 0; i < classfiles.length; i++) { + if (classfiles[i].endsWith(".class")) { + entry = new ZipEntry(classfiles[i]); + zos.putNextEntry(entry); + zos.write(PdeBase.grabFile(new File(appletDir, classfiles[i]))); + zos.closeEntry(); + } + } + + // remove the .class files from the applet folder. if they're not + // removed, the msjvm will complain about an illegal access error, + // since the classes are outside the jar file. + for (int i = 0; i < classfiles.length; i++) { + if (classfiles[i].endsWith(".class")) { + File deadguy = new File(appletDir, classfiles[i]); + if (!deadguy.delete()) { + PdeBase.showWarning("Could not delete", + classfiles[i] + " could not \n" + + "be deleted from the applet folder. \n" + + "You'll need to remove it by hand.", null); + } + } + } + + // close up the jar file + zos.flush(); + zos.close(); + + PdeBase.openFolder(appletDir); + + //} catch (Exception e) { + //e.printStackTrace(); + //} + return true; + } + + + public boolean exportApplication() { + return true; + } + + + public boolean exportLibrary() { + return true; + } + + + /** + * Slurps up .class files from a colon (or semicolon on windows) + * separated list of paths and adds them to a ZipOutputStream. + */ + static public void packClassPathIntoZipFile(String path, + ZipOutputStream zos) + throws IOException { + String pieces[] = PApplet.split(path, File.pathSeparatorChar); + + for (int i = 0; i < pieces.length; i++) { + if (pieces[i].length() == 0) continue; + //System.out.println("checking piece " + pieces[i]); + + // is it a jar file or directory? + if (pieces[i].toLowerCase().endsWith(".jar") || + pieces[i].toLowerCase().endsWith(".zip")) { + try { + ZipFile file = new ZipFile(pieces[i]); + Enumeration entries = file.entries(); + while (entries.hasMoreElements()) { + ZipEntry entry = (ZipEntry) entries.nextElement(); + if (entry.isDirectory()) { + // actually 'continue's for all dir entries + + } else { + String name = entry.getName(); + // ignore contents of the META-INF folders + if (name.indexOf("META-INF") == 0) continue; + ZipEntry entree = new ZipEntry(name); + + zos.putNextEntry(entree); + byte buffer[] = new byte[(int) entry.getSize()]; + InputStream is = file.getInputStream(entry); + + int offset = 0; + int remaining = buffer.length; + while (remaining > 0) { + int count = is.read(buffer, offset, remaining); + offset += count; + remaining -= count; + } + + zos.write(buffer); + zos.flush(); + zos.closeEntry(); + } + } + } catch (IOException e) { + System.err.println("Error in file " + pieces[i]); + e.printStackTrace(); + } + } else { // not a .jar or .zip, prolly a directory + File dir = new File(pieces[i]); + // but must be a dir, since it's one of several paths + // just need to check if it exists + if (dir.exists()) { + packClassPathIntoZipFileRecursive(dir, null, zos); + } + } + } + } + + + /** + * Continue the process of magical exporting. This function + * can be called recursively to walk through folders looking + * for more goodies that will be added to the ZipOutputStream. + */ + static public void packClassPathIntoZipFileRecursive(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; + // ignore . .. and .DS_Store + if (files[i].charAt(0) == '.') continue; + + File sub = new File(dir, files[i]); + String nowfar = (sofar == null) ? + files[i] : (sofar + "/" + files[i]); + + if (sub.isDirectory()) { + packClassPathIntoZipFileRecursive(sub, nowfar, zos); + + } else { + // don't add .jar and .zip files, since they only work + // inside the root, and they're unpacked + if (!files[i].toLowerCase().endsWith(".jar") && + !files[i].toLowerCase().endsWith(".zip") && + files[i].charAt(0) != '.') { + ZipEntry entry = new ZipEntry(nowfar); + zos.putNextEntry(entry); + zos.write(PdeBase.grabFile(sub)); + zos.closeEntry(); + } + } + } + } + + + /** + * Returns true if this is a read-only sketch. Used for the + * examples directory, or when sketches are loaded from read-only + * volumes or folders without appropraite permissions. + */ + public boolean isReadOnly() { + String apath = folder.getAbsolutePath(); + if (apath.startsWith(PdeSketchbook.examplesPath) || + apath.startsWith(PdeSketchbook.librariesPath)) { + return true; + + // this doesn't work on directories + //} else if (!folder.canWrite()) { + } else { + // check to see if each modified code file can be written to + for (int i = 0; i < codeCount; i++) { + if (code[i].modified && !code[i].file.canWrite()) { + //System.err.println("found a read-only file " + code[i].file); + return true; + } + } + //return true; + } + return false; + } + + + /** + * Returns path to the main .pde file for this sketch. + */ + public String getMainFilePath() { + return code[0].file.getAbsolutePath(); + } + + /** Packages up sketch into an executable midlet with JAD descriptor. + */ + public boolean exportMIDlet() throws Exception { + boolean replaceJad = true; + + // create the project directory + File midletDir = new File(folder, "midlet"); + + boolean writeJad = true; + if (midletDir.exists()) { + File jadFile = new File(midletDir, name + ".jad"); + if (jadFile.exists() && !replaceJad) { + writeJad = false; + } + //// delete any existing jar file + File jarFile = new File(midletDir, name + ".jar"); + if (jarFile.exists()) { + jarFile.delete(); + } + } else { + midletDir.mkdirs(); + } + + // build the sketch + String wtkPath = PdePreferences.get("wtk.path"); + if (wtkPath == null) { + PdeBase.showWarning("Sun Wireless Toolkit (WTK) not found. " + + "Please specify the location of the WTK in your preferences.txt file. Example:\n\n" + + "wtk.path=C:\WTK22"); + } + String wtkLibPath = wtkPath + File.separator + "lib" + File.separator; + String baseClass = "PMIDlet"; + String[] baseImports = {}; + String bootClassPath = wtkLibPath + "cldcapi10.jar" + File.pathSeparator + + wtkLibPath + "midpapi10.jar"; + //// this is just to satisfy Jikes which wants java.io.Serializable and java.lang.Cloneable to be reachable... + //System.out.println(System.getProperty ("java.home")); + bootClassPath += File.pathSeparator + System.getProperty("java.home") + File.separator + "lib" + File.separator + "rt.jar"; + String additionalClassPath = "lib" + File.separator + "mobile.jar"; + String foundName = build(midletDir.getPath(), name, baseClass, baseImports, + bootClassPath, additionalClassPath); + + // (already reported) error during export, exit this function + if (foundName == null) return false; + + // if name != exportSketchName, then that's weirdness + // BUG unfortunately, that can also be a bug in the preproc :( + if (!name.equals(foundName)) { + PdeBase.showWarning("Error during export", + "Sketch name is " + name + " but the sketch\n" + + "name in the code was " + foundName, null); + return false; + } + + //// preverify class files into temporary directory + PdePreverifier preverifier = new PdePreverifier(); + File tmpDir = new File(folder, "tmp"); + if (preverifier.preverify(midletDir, tmpDir) != true) { + return false; + } + + //// generate manifest file and JAD file + File mfOutputFile = new File(tmpDir, "MANIFEST.MF"); + FileOutputStream fos = new FileOutputStream(mfOutputFile); + PrintStream ps = new PrintStream(fos); + + File jadFile = new File(midletDir, name + ".jad"); + PrintStream jadPs = new PrintStream(new FileOutputStream(jadFile)); + + InputStream is = null; + // if there is a mobile.mf file in the sketch folder, use that + File customMf = new File(folder, "mobile.mf"); + if (customMf.exists()) { + is = new FileInputStream(customMf); + } + if (is == null) { + is = PdeBase.getStream("mobile.mf"); + } + BufferedReader reader = new BufferedReader(new InputStreamReader(is)); + + String line = null; + while ((line = reader.readLine()) != null) { + if (line.indexOf("@@") != -1) { + StringBuffer sb = new StringBuffer(line); + int index = 0; + while ((index = sb.indexOf("@@sketch@@")) != -1) { + sb.replace(index, index + "@@sketch@@".length(), + name); + } + line = sb.toString(); + } + ps.println(line); + jadPs.println(line); + } + reader.close(); + ps.flush(); + ps.close(); + + // create new .jar file + File jarFile = new File(midletDir, name + ".jar"); + FileOutputStream zipOutputFile = + new FileOutputStream(jarFile); + ZipOutputStream zos = new ZipOutputStream(zipOutputFile); + ZipEntry entry; + + // add the contents of the code folder to the jar + // unpacks all jar files + //File codeFolder = new File(folder, "code"); + if (codeFolder.exists()) { + String includes = PdeCompiler.contentsToClassPath(codeFolder); + packClassPathIntoZipFile(includes, zos); + } + + // add contents of 'library' folders to the jar file + // if a file called 'export.txt' is in there, it contains + // a list of the files that should be exported. + // otherwise, all files are exported. + Enumeration enum = importedLibraries.elements(); + while (enum.hasMoreElements()) { + // in the list is a File object that points the + // library sketch's "library" folder + File libraryFolder = (File)enum.nextElement(); + //System.out.println("exporting files from " + libFolder); + File exportSettings = new File(libraryFolder, "export.txt"); + String exportList[] = null; + if (exportSettings.exists()) { + //exportList = PApplet.loadStrings(exportSettings); + String info[] = PApplet.loadStrings(exportSettings); + for (int i = 0; i < info.length; i++) { + if (info[i].startsWith("applet")) { + int idx = info[i].indexOf('='); // get applet= or applet = + String commas = info[i].substring(idx+1).trim(); + exportList = PApplet.split(commas, ", "); + } + } + } else { + exportList = libraryFolder.list(); + } + for (int i = 0; i < exportList.length; i++) { + if (exportList[i].equals(".") || + exportList[i].equals("..")) continue; + + exportList[i] = PApplet.trim(exportList[i]); + if (exportList[i].equals("")) continue; + + File exportFile = new File(libraryFolder, exportList[i]); + if (!exportFile.exists()) { + System.err.println("File " + exportList[i] + " does not exist"); + + } else if (exportFile.isDirectory()) { + System.err.println("Ignoring sub-folder \"" + exportList[i] + "\""); + + } else if (exportFile.getName().toLowerCase().endsWith(".zip") || + exportFile.getName().toLowerCase().endsWith(".jar")) { + packClassPathIntoZipFile(exportFile.getAbsolutePath(), zos); + + } else { // just copy the file over.. prolly a .dll or something + PdeBase.copyFile(exportFile, + new File(midletDir, exportFile.getName())); + } + } + } + + //// add the mobile core + packClassPathIntoZipFile(additionalClassPath, zos); + + // files to include from data directory + //if ((dataDir != null) && (dataDir.exists())) { + if (dataFolder.exists()) { + String dataFiles[] = dataFolder.list(); + for (int i = 0; i < dataFiles.length; i++) { + // don't export hidden files + // skipping dot prefix removes all: . .. .DS_Store + if (dataFiles[i].charAt(0) == '.') continue; + + entry = new ZipEntry(dataFiles[i]); + zos.putNextEntry(entry); + zos.write(PdeBase.grabFile(new File(dataFolder, dataFiles[i]))); + zos.closeEntry(); + } + } + + //// add the preverified .class files to the jar + String classfiles[] = tmpDir.list(); + for (int i = 0; i < classfiles.length; i++) { + if (classfiles[i].endsWith(".class")) { + entry = new ZipEntry(classfiles[i]); + zos.putNextEntry(entry); + zos.write(PdeBase.grabFile(new File(tmpDir, classfiles[i]))); + zos.closeEntry(); + } else if (classfiles[i].endsWith(".MF")) { + entry = new ZipEntry("META-INF/MANIFEST.MF"); + zos.putNextEntry(entry); + zos.write(PdeBase.grabFile(new File(tmpDir, classfiles[i]))); + zos.closeEntry(); + } + } + + // remove the .class files from the midlet folder + classfiles = midletDir.list(); + for (int i = 0; i < classfiles.length; i++) { + if (classfiles[i].endsWith(".class")) { + File deadguy = new File(midletDir, classfiles[i]); + if (!deadguy.delete()) { + PdeBase.showWarning("Could not delete", + classfiles[i] + " could not \n" + + "be deleted from the applet folder. \n" + + "You'll need to remove it by hand.", null); + } + } + } + + // close up the jar file + zos.flush(); + zos.close(); + + //// now we can get the size of the jar file to generate the JAD file from manifest file + jadPs.println("MIDlet-Jar-URL: " + name + ".jar"); + jadPs.println("MIDlet-Jar-Size: " + jarFile.length()); + jadPs.close(); + + //// delete temporary directory + classfiles = tmpDir.list(); + for (int i = 0; i < classfiles.length; i++) { + File deadguy = new File(tmpDir, classfiles[i]); + if (!deadguy.delete()) { + PdeBase.showWarning("Could not delete", + classfiles[i] + " could not \n" + + "be deleted from the temporary folder. \n" + + "You'll need to remove it by hand.", null); + } + } + tmpDir.delete(); + + return true; + } +} diff --git a/build/shared/lib/mobile.mf b/build/shared/lib/mobile.mf new file mode 100755 index 000000000..881681036 --- /dev/null +++ b/build/shared/lib/mobile.mf @@ -0,0 +1,6 @@ +MIDlet-1: @@sketch@@, ,@@sketch@@ +MIDlet-Name: @@sketch@@ +MIDlet-Version: 1.0.0 +MIDlet-Vendor: Processing Mobile 74alpha +MicroEdition-Profile: MIDP-1.0 +MicroEdition-Configuration: CLDC-1.0 \ No newline at end of file diff --git a/build/windows/build.xml b/build/windows/build.xml new file mode 100755 index 000000000..f9f18e75a --- /dev/null +++ b/build/windows/build.xml @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +