/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ /* Part of the Processing project - http://processing.org Copyright (c) 2004-09 Ben Fry and Casey Reas 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 version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package processing.app; import processing.app.debug.*; import processing.app.syntax.*; import processing.app.tools.*; import processing.core.*; import static processing.app.I18n._; import java.awt.*; import java.awt.datatransfer.*; import java.awt.event.*; import java.awt.print.*; import java.io.*; import java.net.*; import java.util.*; import java.util.zip.*; import javax.swing.*; import javax.swing.event.*; import javax.swing.text.*; import javax.swing.undo.*; import gnu.io.*; /** * Main editor panel for the Processing Development Environment. */ @SuppressWarnings("serial") public class Editor extends JFrame implements RunnerListener { Base base; // otherwise, if the window is resized with the message label // set to blank, it's preferredSize() will be fukered static protected final String EMPTY = " " + " " + " "; /** Command on Mac OS X, Ctrl on Windows and Linux */ static final int SHORTCUT_KEY_MASK = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(); /** Command-W on Mac OS X, Ctrl-W on Windows and Linux */ static final KeyStroke WINDOW_CLOSE_KEYSTROKE = KeyStroke.getKeyStroke('W', SHORTCUT_KEY_MASK); /** Command-Option on Mac OS X, Ctrl-Alt on Windows and Linux */ static final int SHORTCUT_ALT_KEY_MASK = ActionEvent.ALT_MASK | Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(); /** * true if this file has not yet been given a name by the user */ boolean untitled; PageFormat pageFormat; PrinterJob printerJob; // file, sketch, and tools menus for re-inserting items JMenu fileMenu; JMenu sketchMenu; JMenu toolsMenu; int numTools = 0; EditorToolbar toolbar; // these menus are shared so that they needn't be rebuilt for all windows // each time a sketch is created, renamed, or moved. static JMenu toolbarMenu; static JMenu sketchbookMenu; static JMenu examplesMenu; static JMenu importMenu; // these menus are shared so that the board and serial port selections // are the same for all windows (since the board and serial port that are // actually used are determined by the preferences, which are shared) static JMenu boardsMenu; static JMenu serialMenu; static SerialMenuListener serialMenuListener; static SerialMonitor serialMonitor; EditorHeader header; EditorStatus status; EditorConsole console; JSplitPane splitPane; JPanel consolePanel; JLabel lineNumberComponent; // currently opened program Sketch sketch; EditorLineStatus lineStatus; //JEditorPane editorPane; JEditTextArea textarea; EditorListener listener; // runtime information and window placement Point sketchWindowLocation; //Runner runtime; JMenuItem exportAppItem; JMenuItem saveMenuItem; JMenuItem saveAsMenuItem; boolean running; //boolean presenting; boolean uploading; // undo fellers JMenuItem undoItem, redoItem; protected UndoAction undoAction; protected RedoAction redoAction; UndoManager undo; // used internally, and only briefly CompoundEdit compoundEdit; FindReplace find; Runnable runHandler; Runnable presentHandler; Runnable stopHandler; Runnable exportHandler; Runnable exportAppHandler; public Editor(Base ibase, String path, int[] location) { super("Arduino"); this.base = ibase; Base.setIcon(this); // Install default actions for Run, Present, etc. resetHandlers(); // add listener to handle window close box hit event addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { base.handleClose(Editor.this); } }); // don't close the window when clicked, the app will take care // of that via the handleQuitInternal() methods // http://dev.processing.org/bugs/show_bug.cgi?id=440 setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); // When bringing a window to front, let the Base know addWindowListener(new WindowAdapter() { public void windowActivated(WindowEvent e) { // System.err.println("activate"); // not coming through base.handleActivated(Editor.this); // re-add the sub-menus that are shared by all windows fileMenu.insert(sketchbookMenu, 2); fileMenu.insert(examplesMenu, 3); sketchMenu.insert(importMenu, 4); toolsMenu.insert(boardsMenu, numTools); toolsMenu.insert(serialMenu, numTools + 1); } // added for 1.0.5 // http://dev.processing.org/bugs/show_bug.cgi?id=1260 public void windowDeactivated(WindowEvent e) { // System.err.println("deactivate"); // not coming through fileMenu.remove(sketchbookMenu); fileMenu.remove(examplesMenu); sketchMenu.remove(importMenu); toolsMenu.remove(boardsMenu); toolsMenu.remove(serialMenu); } }); //PdeKeywords keywords = new PdeKeywords(); //sketchbook = new Sketchbook(this); if (serialMonitor == null) { serialMonitor = new SerialMonitor(Preferences.get("serial.port")); serialMonitor.setIconImage(getIconImage()); } buildMenuBar(); // For rev 0120, placing things inside a JPanel Container contentPain = getContentPane(); contentPain.setLayout(new BorderLayout()); JPanel pain = new JPanel(); pain.setLayout(new BorderLayout()); contentPain.add(pain, BorderLayout.CENTER); Box box = Box.createVerticalBox(); Box upper = Box.createVerticalBox(); if (toolbarMenu == null) { toolbarMenu = new JMenu(); base.rebuildToolbarMenu(toolbarMenu); } toolbar = new EditorToolbar(this, toolbarMenu); upper.add(toolbar); header = new EditorHeader(this); upper.add(header); textarea = new JEditTextArea(new PdeTextAreaDefaults()); textarea.setRightClickPopup(new TextAreaPopup()); textarea.setHorizontalOffset(6); // assemble console panel, consisting of status area and the console itself consolePanel = new JPanel(); consolePanel.setLayout(new BorderLayout()); status = new EditorStatus(this); consolePanel.add(status, BorderLayout.NORTH); console = new EditorConsole(this); // windows puts an ugly border on this guy console.setBorder(null); consolePanel.add(console, BorderLayout.CENTER); lineStatus = new EditorLineStatus(textarea); consolePanel.add(lineStatus, BorderLayout.SOUTH); upper.add(textarea); splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, upper, consolePanel); splitPane.setOneTouchExpandable(true); // repaint child panes while resizing splitPane.setContinuousLayout(true); // if window increases in size, give all of increase to // the textarea in the uppper pane splitPane.setResizeWeight(1D); // to fix ugliness.. normally macosx java 1.3 puts an // ugly white border around this object, so turn it off. splitPane.setBorder(null); // the default size on windows is too small and kinda ugly int dividerSize = Preferences.getInteger("editor.divider.size"); if (dividerSize != 0) { splitPane.setDividerSize(dividerSize); } // the following changed from 600, 400 for netbooks // http://code.google.com/p/arduino/issues/detail?id=52 splitPane.setMinimumSize(new Dimension(600, 100)); box.add(splitPane); // hopefully these are no longer needed w/ swing // (har har har.. that was wishful thinking) listener = new EditorListener(this, textarea); pain.add(box); // get shift down/up events so we can show the alt version of toolbar buttons textarea.addKeyListener(toolbar); pain.setTransferHandler(new FileDropHandler()); // System.out.println("t1"); // Finish preparing Editor (formerly found in Base) pack(); // System.out.println("t2"); // Set the window bounds and the divider location before setting it visible setPlacement(location); // Set the minimum size for the editor window setMinimumSize(new Dimension(Preferences.getInteger("editor.window.width.min"), Preferences.getInteger("editor.window.height.min"))); // System.out.println("t3"); // Bring back the general options for the editor applyPreferences(); // System.out.println("t4"); // Open the document that was passed in boolean loaded = handleOpenInternal(path); if (!loaded) sketch = null; // System.out.println("t5"); // All set, now show the window //setVisible(true); } /** * Handles files dragged & dropped from the desktop and into the editor * window. Dragging files into the editor window is the same as using * "Sketch → Add File" for each file. */ class FileDropHandler extends TransferHandler { public boolean canImport(JComponent dest, DataFlavor[] flavors) { return true; } @SuppressWarnings("unchecked") public boolean importData(JComponent src, Transferable transferable) { int successful = 0; try { DataFlavor uriListFlavor = new DataFlavor("text/uri-list;class=java.lang.String"); if (transferable.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) { java.util.List list = (java.util.List) transferable.getTransferData(DataFlavor.javaFileListFlavor); for (int i = 0; i < list.size(); i++) { File file = (File) list.get(i); if (sketch.addFile(file)) { successful++; } } } else if (transferable.isDataFlavorSupported(uriListFlavor)) { // Some platforms (Mac OS X and Linux, when this began) preferred // this method of moving files. String data = (String)transferable.getTransferData(uriListFlavor); String[] pieces = PApplet.splitTokens(data, "\r\n"); for (int i = 0; i < pieces.length; i++) { if (pieces[i].startsWith("#")) continue; String path = null; if (pieces[i].startsWith("file:///")) { path = pieces[i].substring(7); } else if (pieces[i].startsWith("file:/")) { path = pieces[i].substring(5); } if (sketch.addFile(new File(path))) { successful++; } } } } catch (Exception e) { e.printStackTrace(); return false; } if (successful == 0) { statusError(_("No files were added to the sketch.")); } else if (successful == 1) { statusNotice(_("One file added to the sketch.")); } else { statusNotice( I18n.format(_("{0} files added to the sketch."), successful)); } return true; } } protected void setPlacement(int[] location) { setBounds(location[0], location[1], location[2], location[3]); if (location[4] != 0) { splitPane.setDividerLocation(location[4]); } } protected int[] getPlacement() { int[] location = new int[5]; // Get the dimensions of the Frame Rectangle bounds = getBounds(); location[0] = bounds.x; location[1] = bounds.y; location[2] = bounds.width; location[3] = bounds.height; // Get the current placement of the divider location[4] = splitPane.getDividerLocation(); return location; } /** * Hack for #@#)$(* Mac OS X 10.2. *
* This appears to only be required on OS X 10.2, and is not * even being called on later versions of OS X or Windows. */ // public Dimension getMinimumSize() { // //System.out.println("getting minimum size"); // return new Dimension(500, 550); // } // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . /** * 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. */ protected void applyPreferences() { // apply the setting for 'use external editor' boolean external = Preferences.getBoolean("editor.external"); textarea.setEditable(!external); saveMenuItem.setEnabled(!external); saveAsMenuItem.setEnabled(!external); textarea.setDisplayLineNumbers(Preferences.getBoolean("editor.linenumbers")); TextAreaPainter painter = textarea.getPainter(); if (external) { // disable line highlight and turn off the caret when disabling Color color = Theme.getColor("editor.external.bgcolor"); painter.setBackground(color); painter.setLineHighlightEnabled(false); textarea.setCaretVisible(false); } else { Color color = Theme.getColor("editor.bgcolor"); painter.setBackground(color); boolean highlight = Preferences.getBoolean("editor.linehighlight"); painter.setLineHighlightEnabled(highlight); textarea.setCaretVisible(true); } // apply changes to the font size for the editor //TextAreaPainter painter = textarea.getPainter(); painter.setFont(Preferences.getFont("editor.font")); //Font font = painter.getFont(); //textarea.getPainter().setFont(new Font("Courier", Font.PLAIN, 36)); // in case tab expansion stuff has changed listener.applyPreferences(); // in case moved to a new location // For 0125, changing to async version (to be implemented later) //sketchbook.rebuildMenus(); // For 0126, moved into Base, which will notify all editors. //base.rebuildMenusAsync(); } // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . protected void buildMenuBar() { JMenuBar menubar = new JMenuBar(); menubar = new JMenuBar(); menubar.add(buildFileMenu()); menubar.add(buildEditMenu()); menubar.add(buildSketchMenu()); menubar.add(buildToolsMenu()); menubar.add(buildHelpMenu()); setJMenuBar(menubar); } protected JMenu buildFileMenu() { JMenuItem item; fileMenu = new JMenu(_("File")); item = newJMenuItem(_("New"), 'N'); item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { base.handleNew(); } }); fileMenu.add(item); item = Editor.newJMenuItem(_("Open..."), 'O'); item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { base.handleOpenPrompt(); } }); fileMenu.add(item); if (sketchbookMenu == null) { sketchbookMenu = new JMenu(_("Sketchbook")); base.rebuildSketchbookMenu(sketchbookMenu); } fileMenu.add(sketchbookMenu); if (examplesMenu == null) { examplesMenu = new JMenu(_("Examples")); base.rebuildExamplesMenu(examplesMenu); } fileMenu.add(examplesMenu); item = Editor.newJMenuItem(_("Close"), 'W'); item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { base.handleClose(Editor.this); } }); fileMenu.add(item); saveMenuItem = newJMenuItem(_("Save"), 'S'); saveMenuItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { handleSave(false); } }); fileMenu.add(saveMenuItem); saveAsMenuItem = newJMenuItemShift(_("Save As..."), 'S'); saveAsMenuItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { handleSaveAs(); } }); fileMenu.add(saveAsMenuItem); item = newJMenuItem(_("Upload"), 'U'); item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { handleExport(false); } }); fileMenu.add(item); item = newJMenuItemShift(_("Upload Using Programmer"), 'U'); item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { handleExport(true); } }); fileMenu.add(item); fileMenu.addSeparator(); item = newJMenuItemShift(_("Page Setup"), 'P'); item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { handlePageSetup(); } }); fileMenu.add(item); item = newJMenuItem(_("Print"), 'P'); item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { handlePrint(); } }); fileMenu.add(item); // macosx already has its own preferences and quit menu if (!Base.isMacOS()) { fileMenu.addSeparator(); item = newJMenuItem(_("Preferences"), ','); item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { base.handlePrefs(); } }); fileMenu.add(item); fileMenu.addSeparator(); item = newJMenuItem(_("Quit"), 'Q'); item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { base.handleQuit(); } }); fileMenu.add(item); } return fileMenu; } protected JMenu buildSketchMenu() { JMenuItem item; sketchMenu = new JMenu(_("Sketch")); item = newJMenuItem(_("Verify / Compile"), 'R'); item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { handleRun(false); } }); sketchMenu.add(item); // item = newJMenuItemShift("Verify / Compile (verbose)", 'R'); // item.addActionListener(new ActionListener() { // public void actionPerformed(ActionEvent e) { // handleRun(true); // } // }); // sketchMenu.add(item); // item = new JMenuItem("Stop"); // item.addActionListener(new ActionListener() { // public void actionPerformed(ActionEvent e) { // handleStop(); // } // }); // sketchMenu.add(item); sketchMenu.addSeparator(); if (importMenu == null) { importMenu = new JMenu(_("Import Library...")); base.rebuildImportMenu(importMenu, this); } sketchMenu.add(importMenu); item = newJMenuItem(_("Show Sketch Folder"), 'K'); item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { Base.openFolder(sketch.getFolder()); } }); sketchMenu.add(item); item.setEnabled(Base.openFolderAvailable()); item = new JMenuItem(_("Add File...")); item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { sketch.handleAddFile(); } }); sketchMenu.add(item); return sketchMenu; } protected JMenu buildToolsMenu() { toolsMenu = new JMenu(_("Tools")); JMenu menu = toolsMenu; JMenuItem item; addInternalTools(menu); item = newJMenuItemShift(_("Serial Monitor"), 'M'); item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { handleSerial(); } }); menu.add(item); addTools(menu, Base.getToolsFolder()); File sketchbookTools = new File(Base.getSketchbookFolder(), "tools"); addTools(menu, sketchbookTools); menu.addSeparator(); numTools = menu.getItemCount(); // XXX: DAM: these should probably be implemented using the Tools plugin // API, if possible (i.e. if it supports custom actions, etc.) if (boardsMenu == null) { boardsMenu = new JMenu(_("Board")); base.rebuildBoardsMenu(boardsMenu, this); } menu.add(boardsMenu); if (serialMenuListener == null) serialMenuListener = new SerialMenuListener(); if (serialMenu == null) serialMenu = new JMenu(_("Serial Port")); populateSerialMenu(); menu.add(serialMenu); menu.addSeparator(); JMenu programmerMenu = new JMenu(_("Programmer")); base.rebuildProgrammerMenu(programmerMenu); menu.add(programmerMenu); item = new JMenuItem(_("Burn Bootloader")); item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { handleBurnBootloader(); } }); menu.add(item); menu.addMenuListener(new MenuListener() { public void menuCanceled(MenuEvent e) {} public void menuDeselected(MenuEvent e) {} public void menuSelected(MenuEvent e) { //System.out.println("Tools menu selected."); populateSerialMenu(); } }); return menu; } protected void addTools(JMenu menu, File sourceFolder) { HashMapIf you don't save, your changes will be lost."), JOptionPane.QUESTION_MESSAGE); String[] options = new String[] { _("Save"), _("Cancel"), _("Don't Save") }; pane.setOptions(options); // highlight the safest option ala apple hig pane.setInitialValue(options[0]); // on macosx, setting the destructive property places this option // away from the others at the lefthand side pane.putClientProperty("Quaqua.OptionPane.destructiveOption", new Integer(2)); JDialog dialog = pane.createDialog(this, null); dialog.setVisible(true); Object result = pane.getValue(); if (result == options[0]) { // save (and close/quit) return handleSave(true); } else if (result == options[2]) { // don't save (still close/quit) return true; } else { // cancel? return false; } } } /** * Open a sketch from a particular path, but don't check to save changes. * Used by Sketch.saveAs() to re-open a sketch after the "Save As" */ protected void handleOpenUnchecked(String path, int codeIndex, int selStart, int selStop, int scrollPos) { internalCloseRunner(); handleOpenInternal(path); // Replacing a document that may be untitled. If this is an actual // untitled document, then editor.untitled will be set by Base. untitled = false; sketch.setCurrentCode(codeIndex); textarea.select(selStart, selStop); textarea.setScrollPosition(scrollPos); } /** * Second stage of open, occurs after having checked to see if the * modifications (if any) to the previous sketch need to be saved. */ protected boolean handleOpenInternal(String path) { // check to make sure that this .pde file is // in a folder of the same name File file = new File(path); String fileName = file.getName(); File parent = file.getParentFile(); String parentName = parent.getName(); String pdeName = parentName + ".pde"; File altPdeFile = new File(parent, pdeName); String inoName = parentName + ".ino"; File altInoFile = new File(parent, pdeName); if (pdeName.equals(fileName) || inoName.equals(fileName)) { // no beef with this guy } else if (altPdeFile.exists()) { // user selected a .java from the same sketch, but open the .pde instead path = altPdeFile.getAbsolutePath(); } else if (altInoFile.exists()) { path = altInoFile.getAbsolutePath(); } else if (!path.endsWith(".ino") && !path.endsWith(".pde")) { Base.showWarning(_("Bad file selected"), _("Processing can only open its own sketches\n" + "and other files ending in .ino or .pde"), null); return false; } else { String properParent = fileName.substring(0, fileName.length() - 4); Object[] options = { _("OK"), _("Cancel") }; String prompt = I18n.format( _("The file \"{0}\" needs to be inside\n" + "a sketch folder named \"{1}\".\n" + "Create this folder, move the file, and continue?"), fileName, properParent ); int result = JOptionPane.showOptionDialog(this, prompt, _("Moving"), JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, options, options[0]); if (result == JOptionPane.YES_OPTION) { // create properly named folder File properFolder = new File(file.getParent(), properParent); if (properFolder.exists()) { Base.showWarning(_("Error"), I18n.format( _("A folder named \"{0}\" already exists. " + "Can't open sketch."), properParent ), null); return false; } if (!properFolder.mkdirs()) { //throw new IOException("Couldn't create sketch folder"); Base.showWarning(_("Error"), _("Could not create the sketch folder."), null); return false; } // copy the sketch inside File properPdeFile = new File(properFolder, file.getName()); File origPdeFile = new File(path); try { Base.copyFile(origPdeFile, properPdeFile); } catch (IOException e) { Base.showWarning(_("Error"), _("Could not copy to a proper location."), e); return false; } // remove the original file, so user doesn't get confused origPdeFile.delete(); // update with the new path path = properPdeFile.getAbsolutePath(); } else if (result == JOptionPane.NO_OPTION) { return false; } } try { sketch = new Sketch(this, path); } catch (IOException e) { Base.showWarning(_("Error"), _("Could not create the sketch."), e); return false; } header.rebuild(); // Set the title of the window to "sketch_070752a - Processing 0126" setTitle( I18n.format( _("{0} | Arduino {1}"), sketch.getName(), Base.VERSION_NAME ) ); // Disable untitled setting from previous document, if any untitled = false; // Store information on who's open and running // (in case there's a crash or something that can't be recovered) base.storeSketches(); Preferences.save(); // opening was successful return true; // } catch (Exception e) { // e.printStackTrace(); // statusError(e); // return false; // } } /** * Actually handle the save command. If 'immediately' is set to false, * this will happen in another thread so that the message area * will update and the save button will stay highlighted while the * save is happening. If 'immediately' is true, then it will happen * immediately. This is used during a quit, because invokeLater() * won't run properly while a quit is happening. This fixes * Bug 276. */ public boolean handleSave(boolean immediately) { //stopRunner(); handleStop(); // 0136 if (untitled) { return handleSaveAs(); // need to get the name, user might also cancel here } else if (immediately) { return handleSave2(); } else { SwingUtilities.invokeLater(new Runnable() { public void run() { handleSave2(); } }); } return true; } protected boolean handleSave2() { toolbar.activate(EditorToolbar.SAVE); statusNotice(_("Saving...")); boolean saved = false; try { saved = sketch.save(); if (saved) statusNotice(_("Done Saving.")); else statusEmpty(); // rebuild sketch menu in case a save-as was forced // Disabling this for 0125, instead rebuild the menu inside // the Save As method of the Sketch object, since that's the // only one who knows whether something was renamed. //sketchbook.rebuildMenus(); //sketchbook.rebuildMenusAsync(); } catch (Exception e) { // show the error as a message in the window statusError(e); // zero out the current action, // so that checkModified2 will just do nothing //checkModifiedMode = 0; // this is used when another operation calls a save } //toolbar.clear(); toolbar.deactivate(EditorToolbar.SAVE); return saved; } public boolean handleSaveAs() { //stopRunner(); // formerly from 0135 handleStop(); toolbar.activate(EditorToolbar.SAVE); //SwingUtilities.invokeLater(new Runnable() { //public void run() { statusNotice(_("Saving...")); try { if (sketch.saveAs()) { statusNotice(_("Done Saving.")); // Disabling this for 0125, instead rebuild the menu inside // the Save As method of the Sketch object, since that's the // only one who knows whether something was renamed. //sketchbook.rebuildMenusAsync(); } else { statusNotice(_("Save Canceled.")); return false; } } catch (Exception e) { // show the error as a message in the window statusError(e); } finally { // make sure the toolbar button deactivates toolbar.deactivate(EditorToolbar.SAVE); } return true; } public boolean serialPrompt() { int count = serialMenu.getItemCount(); Object[] names = new Object[count]; for (int i = 0; i < count; i++) { names[i] = ((JCheckBoxMenuItem)serialMenu.getItem(i)).getText(); } String result = (String) JOptionPane.showInputDialog(this, I18n.format( _("Serial port {0} not found.\n" + "Retry the upload with another serial port?"), Preferences.get("serial.port") ), "Serial port not found", JOptionPane.PLAIN_MESSAGE, null, names, 0); if (result == null) return false; selectSerialPort(result); base.onBoardOrPortChange(); return true; } /** * Called by Sketch → Export. * 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. */ /** * 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(final boolean usingProgrammer) { //if (!handleExportCheckModified()) return; toolbar.activate(EditorToolbar.EXPORT); console.clear(); status.progress(_("Uploading to I/O Board...")); new Thread(usingProgrammer ? exportAppHandler : exportHandler).start(); } // DAM: in Arduino, this is upload class DefaultExportHandler implements Runnable { public void run() { try { serialMonitor.closeSerialPort(); serialMonitor.setVisible(false); uploading = true; boolean success = sketch.exportApplet(false); if (success) { statusNotice(_("Done uploading.")); } else { // error message will already be visible } } catch (SerialNotFoundException e) { populateSerialMenu(); if (serialMenu.getItemCount() == 0) statusError(e); else if (serialPrompt()) run(); else statusNotice(_("Upload canceled.")); } catch (RunnerException e) { //statusError("Error during upload."); //e.printStackTrace(); status.unprogress(); statusError(e); } catch (Exception e) { e.printStackTrace(); } status.unprogress(); uploading = false; //toolbar.clear(); toolbar.deactivate(EditorToolbar.EXPORT); } } // DAM: in Arduino, this is upload (with verbose output) class DefaultExportAppHandler implements Runnable { public void run() { try { serialMonitor.closeSerialPort(); serialMonitor.setVisible(false); uploading = true; boolean success = sketch.exportApplet(true); if (success) { statusNotice(_("Done uploading.")); } else { // error message will already be visible } } catch (SerialNotFoundException e) { populateSerialMenu(); if (serialMenu.getItemCount() == 0) statusError(e); else if (serialPrompt()) run(); else statusNotice(_("Upload canceled.")); } catch (RunnerException e) { //statusError("Error during upload."); //e.printStackTrace(); status.unprogress(); statusError(e); } catch (Exception e) { e.printStackTrace(); } status.unprogress(); uploading = false; //toolbar.clear(); toolbar.deactivate(EditorToolbar.EXPORT); } } /** * Checks to see if the sketch has been modified, and if so, * asks the user to save the sketch or cancel the export. * This prevents issues where an incomplete version of the sketch * would be exported, and is a fix for * Bug 157 */ protected boolean handleExportCheckModified() { if (!sketch.isModified()) return true; Object[] options = { _("OK"), _("Cancel") }; int result = JOptionPane.showOptionDialog(this, _("Save changes before export?"), _("Save"), JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, null, options, options[0]); if (result == JOptionPane.OK_OPTION) { handleSave(true); } else { // why it's not CANCEL_OPTION is beyond me (at least on the mac) // but f-- it.. let's get this shite done.. //} else if (result == JOptionPane.CANCEL_OPTION) { statusNotice(_("Export canceled, changes must first be saved.")); //toolbar.clear(); return false; } return true; } public void handleSerial() { if (uploading) return; try { serialMonitor.openSerialPort(); serialMonitor.setVisible(true); } catch (SerialException e) { statusError(e); } } protected void handleBurnBootloader() { console.clear(); statusNotice(_("Burning bootloader to I/O Board (this may take a minute)...")); SwingUtilities.invokeLater(new Runnable() { public void run() { try { Uploader uploader = new AvrdudeUploader(); if (uploader.burnBootloader()) { statusNotice(_("Done burning bootloader.")); } else { statusError(_("Error while burning bootloader.")); // error message will already be visible } } catch (RunnerException e) { statusError(_("Error while burning bootloader.")); e.printStackTrace(); //statusError(e); } catch (Exception e) { statusError(_("Error while burning bootloader.")); e.printStackTrace(); } }}); } /** * Handler for File → Page Setup. */ public void handlePageSetup() { //printerJob = null; if (printerJob == null) { printerJob = PrinterJob.getPrinterJob(); } if (pageFormat == null) { pageFormat = printerJob.defaultPage(); } pageFormat = printerJob.pageDialog(pageFormat); //System.out.println("page format is " + pageFormat); } /** * Handler for File → Print. */ public void handlePrint() { statusNotice(_("Printing...")); //printerJob = null; if (printerJob == null) { printerJob = PrinterJob.getPrinterJob(); } if (pageFormat != null) { //System.out.println("setting page format " + pageFormat); printerJob.setPrintable(textarea.getPainter(), pageFormat); } else { printerJob.setPrintable(textarea.getPainter()); } // set the name of the job to the code name printerJob.setJobName(sketch.getCurrentCode().getPrettyName()); if (printerJob.printDialog()) { try { printerJob.print(); statusNotice(_("Done printing.")); } catch (PrinterException pe) { statusError(_("Error while printing.")); pe.printStackTrace(); } } else { statusNotice(_("Printing canceled.")); } //printerJob = null; // clear this out? } // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . /** * Show an error int the status bar. */ public void statusError(String what) { status.error(what); //new Exception("deactivating RUN").printStackTrace(); toolbar.deactivate(EditorToolbar.RUN); } /** * Show an exception in the editor status bar. */ public void statusError(Exception e) { e.printStackTrace(); // if (e == null) { // System.err.println("Editor.statusError() was passed a null exception."); // return; // } if (e instanceof RunnerException) { RunnerException re = (RunnerException) e; if (re.hasCodeIndex()) { sketch.setCurrentCode(re.getCodeIndex()); } if (re.hasCodeLine()) { int line = re.getCodeLine(); // subtract one from the end so that the \n ain't included if (line >= textarea.getLineCount()) { // The error is at the end of this current chunk of code, // so the last line needs to be selected. line = textarea.getLineCount() - 1; if (textarea.getLineText(line).length() == 0) { // The last line may be zero length, meaning nothing to select. // If so, back up one more line. line--; } } if (line < 0 || line >= textarea.getLineCount()) { System.err.println(I18n.format(_("Bad error line: {0}"), line)); } else { textarea.select(textarea.getLineStartOffset(line), textarea.getLineStopOffset(line) - 1); } } } // Since this will catch all Exception types, spend some time figuring // out which kind and try to give a better error message to the user. String mess = e.getMessage(); if (mess != null) { String javaLang = "java.lang."; if (mess.indexOf(javaLang) == 0) { mess = mess.substring(javaLang.length()); } String rxString = "RuntimeException: "; if (mess.indexOf(rxString) == 0) { mess = mess.substring(rxString.length()); } statusError(mess); } // e.printStackTrace(); } /** * Show a notice message in the editor status bar. */ public void statusNotice(String msg) { status.notice(msg); } /** * Clear the status area. */ public void statusEmpty() { statusNotice(EMPTY); } // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . protected void onBoardOrPortChange() { Map