/* -*- 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 cc.arduino.packages.BoardPort; import cc.arduino.packages.MonitorFactory; import cc.arduino.packages.Uploader; import cc.arduino.packages.uploaders.SerialUploader; import cc.arduino.view.GoToLineNumber; import cc.arduino.view.StubMenuListener; import cc.arduino.view.findreplace.FindReplace; import cc.arduino.CompilerProgressListener; import com.jcraft.jsch.JSchException; import jssc.SerialPortException; import processing.app.debug.RunnerException; import processing.app.forms.PasswordAuthorizationDialog; import processing.app.helpers.DocumentTextChangeListener; import processing.app.helpers.Keys; import processing.app.helpers.OSUtils; import processing.app.helpers.PreferencesMapException; import processing.app.helpers.StringReplacer; import processing.app.legacy.PApplet; import processing.app.syntax.PdeKeywords; import processing.app.syntax.SketchTextArea; import processing.app.tools.MenuScroller; import processing.app.tools.Tool; import javax.swing.*; import javax.swing.event.*; import javax.swing.text.BadLocationException; import java.awt.*; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.Transferable; import java.awt.event.*; import java.awt.print.PageFormat; import java.awt.print.PrinterException; import java.awt.print.PrinterJob; import java.io.File; import java.io.FileFilter; import java.io.FilenameFilter; import java.io.IOException; import java.net.ConnectException; import java.net.URL; import java.net.URLClassLoader; import java.util.*; import java.util.List; import java.util.function.Predicate; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import java.util.ArrayList; import static processing.app.I18n.tr; import static processing.app.Theme.scale; /** * Main editor panel for the Processing Development Environment. */ @SuppressWarnings("serial") public class Editor extends JFrame implements RunnerListener { public static final int MAX_TIME_AWAITING_FOR_RESUMING_SERIAL_MONITOR = 10000; final Platform platform; private JMenu recentSketchesMenu; private JMenu programmersMenu; private final Box upper; private ArrayList tabs = new ArrayList<>(); private int currentTabIndex = -1; private static class ShouldSaveIfModified implements Predicate { @Override public boolean test(SketchController controller) { return PreferencesData.getBoolean("editor.save_on_verify") && controller.getSketch().isModified() && !controller.isReadOnly( BaseNoGui.librariesIndexer .getInstalledLibraries(), BaseNoGui.getExamplesPath()); } } private static class ShouldSaveReadOnly implements Predicate { @Override public boolean test(SketchController sketch) { return sketch.isReadOnly(BaseNoGui.librariesIndexer.getInstalledLibraries(), BaseNoGui.getExamplesPath()); } } private final static List BOARD_PROTOCOLS_ORDER = Arrays.asList("serial", "network"); private final static List BOARD_PROTOCOLS_ORDER_TRANSLATIONS = Arrays.asList(tr("Serial ports"), tr("Network ports")); final Base base; // otherwise, if the window is resized with the message label // set to blank, it's preferredSize() will be fukered private static final String EMPTY = " " + " " + " "; /** Command on Mac OS X, Ctrl on Windows and Linux */ private 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; private PageFormat pageFormat; // file, sketch, and tools menus for re-inserting items private JMenu fileMenu; private JMenu toolsMenu; private int numTools = 0; public boolean avoidMultipleOperations = false; private final 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; private static JMenu portMenu; static volatile AbstractMonitor serialMonitor; static AbstractMonitor serialPlotter; final EditorHeader header; EditorStatus status; EditorConsole console; private JSplitPane splitPane; // currently opened program SketchController sketchController; Sketch sketch; EditorLineStatus lineStatus; //JEditorPane editorPane; /** Contains all EditorTabs, of which only one will be visible */ private JPanel codePanel; //Runner runtime; private JMenuItem saveMenuItem; private JMenuItem saveAsMenuItem; //boolean presenting; private boolean uploading; // undo fellers private JMenuItem undoItem; private JMenuItem redoItem; private FindReplace find; Runnable runHandler; Runnable presentHandler; private Runnable runAndSaveHandler; private Runnable presentAndSaveHandler; Runnable exportHandler; private Runnable exportAppHandler; private Runnable timeoutUploadHandler; public Editor(Base ibase, File file, int[] storedLocation, int[] defaultLocation, Platform platform) throws Exception { super("Arduino"); this.base = ibase; this.platform = platform; 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) { base.handleActivated(Editor.this); } // added for 1.0.5 // http://dev.processing.org/bugs/show_bug.cgi?id=1260 public void windowDeactivated(WindowEvent e) { fileMenu.remove(sketchbookMenu); fileMenu.remove(examplesMenu); List toolsMenuItemsToRemove = new LinkedList<>(); for (Component menuItem : toolsMenu.getMenuComponents()) { if (menuItem instanceof JComponent) { Object removeOnWindowDeactivation = ((JComponent) menuItem).getClientProperty("removeOnWindowDeactivation"); if (removeOnWindowDeactivation != null && Boolean.valueOf(removeOnWindowDeactivation.toString())) { toolsMenuItemsToRemove.add(menuItem); } } } for (Component menuItem : toolsMenuItemsToRemove) { toolsMenu.remove(menuItem); } toolsMenu.remove(portMenu); } }); //PdeKeywords keywords = new PdeKeywords(); //sketchbook = new Sketchbook(this); buildMenuBar(); // For rev 0120, placing things inside a JPanel Container contentPain = getContentPane(); contentPain.setLayout(new BorderLayout()); JPanel pane = new JPanel(); pane.setLayout(new BorderLayout()); contentPain.add(pane, BorderLayout.CENTER); Box box = Box.createVerticalBox(); 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); // assemble console panel, consisting of status area and the console itself JPanel consolePanel = new JPanel(); consolePanel.setLayout(new BorderLayout()); status = new EditorStatus(this); consolePanel.add(status, BorderLayout.NORTH); console = new EditorConsole(); console.setName("console"); // windows puts an ugly border on this guy console.setBorder(null); consolePanel.add(console, BorderLayout.CENTER); lineStatus = new EditorLineStatus(); consolePanel.add(lineStatus, BorderLayout.SOUTH); codePanel = new JPanel(new BorderLayout()); upper.add(codePanel); splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, upper, consolePanel); // 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); // By default, the split pane binds Ctrl-Tab and Ctrl-Shift-Tab for changing // focus. Since we do not use that, but want to use these shortcuts for // switching tabs, remove the bindings from the split pane. This allows the // events to bubble up and be handled by the EditorHeader. Keys.killBinding(splitPane, Keys.ctrl(KeyEvent.VK_TAB)); Keys.killBinding(splitPane, Keys.ctrlShift(KeyEvent.VK_TAB)); splitPane.setDividerSize(scale(splitPane.getDividerSize())); // the following changed from 600, 400 for netbooks // http://code.google.com/p/arduino/issues/detail?id=52 splitPane.setMinimumSize(scale(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); pane.add(box); pane.setTransferHandler(new FileDropHandler()); // Set the minimum size for the editor window setMinimumSize(scale(new Dimension( PreferencesData.getInteger("editor.window.width.min"), PreferencesData.getInteger("editor.window.height.min")))); // Bring back the general options for the editor applyPreferences(); // Finish preparing Editor (formerly found in Base) pack(); // Set the window bounds and the divider location before setting it visible setPlacement(storedLocation, defaultLocation); // Open the document that was passed in boolean loaded = handleOpenInternal(file); if (!loaded) sketchController = null; } /** * 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. */ private 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)) { List list = (List) transferable.getTransferData(DataFlavor.javaFileListFlavor); for (File file : list) { if (sketchController.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 (String piece : pieces) { if (piece.startsWith("#")) continue; String path = null; if (piece.startsWith("file:///")) { path = piece.substring(7); } else if (piece.startsWith("file:/")) { path = piece.substring(5); } if (sketchController.addFile(new File(path))) { successful++; } } } } catch (Exception e) { e.printStackTrace(); return false; } if (successful == 0) { statusError(tr("No files were added to the sketch.")); } else if (successful == 1) { statusNotice(tr("One file added to the sketch.")); } else { statusNotice( I18n.format(tr("{0} files added to the sketch."), successful)); } return true; } } private void setPlacement(int[] storedLocation, int[] defaultLocation) { if (storedLocation.length > 5 && storedLocation[5] != 0) { setExtendedState(storedLocation[5]); setPlacement(defaultLocation); } else { setPlacement(storedLocation); } } private 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[6]; // 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(); location[5] = getExtendedState() & MAXIMIZED_BOTH; return location; } // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . /** * 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() { boolean external = PreferencesData.getBoolean("editor.external"); saveMenuItem.setEnabled(!external); saveAsMenuItem.setEnabled(!external); for (EditorTab tab: tabs) tab.applyPreferences(); } // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . private void buildMenuBar() { JMenuBar menubar = new JMenuBar(); final JMenu fileMenu = buildFileMenu(); fileMenu.addMenuListener(new StubMenuListener() { @Override public void menuSelected(MenuEvent e) { List components = Arrays.asList(fileMenu.getComponents()); if (!components.contains(sketchbookMenu)) { fileMenu.insert(sketchbookMenu, 3); } if (!components.contains(sketchbookMenu)) { fileMenu.insert(examplesMenu, 4); } fileMenu.revalidate(); validate(); } }); menubar.add(fileMenu); menubar.add(buildEditMenu()); final JMenu sketchMenu = new JMenu(tr("Sketch")); sketchMenu.setMnemonic(KeyEvent.VK_S); sketchMenu.addMenuListener(new StubMenuListener() { @Override public void menuSelected(MenuEvent e) { buildSketchMenu(sketchMenu); sketchMenu.revalidate(); validate(); } }); buildSketchMenu(sketchMenu); menubar.add(sketchMenu); final JMenu toolsMenu = buildToolsMenu(); toolsMenu.addMenuListener(new StubMenuListener() { @Override public void menuSelected(MenuEvent e) { List components = Arrays.asList(toolsMenu.getComponents()); int offset = 0; for (JMenu menu : base.getBoardsCustomMenus()) { if (!components.contains(menu)) { toolsMenu.insert(menu, numTools + offset); offset++; } } if (!components.contains(portMenu)) { toolsMenu.insert(portMenu, numTools + offset); } programmersMenu.removeAll(); base.getProgrammerMenus().forEach(programmersMenu::add); toolsMenu.revalidate(); validate(); } }); menubar.add(toolsMenu); menubar.add(buildHelpMenu()); setJMenuBar(menubar); } private JMenu buildFileMenu() { JMenuItem item; fileMenu = new JMenu(tr("File")); fileMenu.setMnemonic(KeyEvent.VK_F); item = newJMenuItem(tr("New"), 'N'); item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { try { base.handleNew(); } catch (Exception e1) { e1.printStackTrace(); } } }); fileMenu.add(item); item = Editor.newJMenuItem(tr("Open..."), 'O'); item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { try { base.handleOpenPrompt(); } catch (Exception e1) { e1.printStackTrace(); } } }); fileMenu.add(item); base.rebuildRecentSketchesMenuItems(); recentSketchesMenu = new JMenu(tr("Open Recent")); SwingUtilities.invokeLater(new Runnable() { @Override public void run() { rebuildRecentSketchesMenu(); } }); fileMenu.add(recentSketchesMenu); if (sketchbookMenu == null) { sketchbookMenu = new JMenu(tr("Sketchbook")); MenuScroller.setScrollerFor(sketchbookMenu); base.rebuildSketchbookMenu(sketchbookMenu); } fileMenu.add(sketchbookMenu); if (examplesMenu == null) { examplesMenu = new JMenu(tr("Examples")); MenuScroller.setScrollerFor(examplesMenu); base.rebuildExamplesMenu(examplesMenu); } fileMenu.add(examplesMenu); item = Editor.newJMenuItem(tr("Close"), 'W'); item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { base.handleClose(Editor.this); } }); fileMenu.add(item); saveMenuItem = newJMenuItem(tr("Save"), 'S'); saveMenuItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { handleSave(false); } }); fileMenu.add(saveMenuItem); saveAsMenuItem = newJMenuItemShift(tr("Save As..."), 'S'); saveAsMenuItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { handleSaveAs(); } }); fileMenu.add(saveAsMenuItem); fileMenu.addSeparator(); item = newJMenuItemShift(tr("Page Setup"), 'P'); item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { handlePageSetup(); } }); fileMenu.add(item); item = newJMenuItem(tr("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 (!OSUtils.hasMacOSStyleMenus()) { fileMenu.addSeparator(); item = newJMenuItem(tr("Preferences"), ','); item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { base.handlePrefs(); } }); fileMenu.add(item); fileMenu.addSeparator(); item = newJMenuItem(tr("Quit"), 'Q'); item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { base.handleQuit(); } }); fileMenu.add(item); } return fileMenu; } public void rebuildRecentSketchesMenu() { recentSketchesMenu.removeAll(); for (JMenuItem recentSketchMenuItem : base.getRecentSketchesMenuItems()) { recentSketchesMenu.add(recentSketchMenuItem); } } private void buildSketchMenu(JMenu sketchMenu) { sketchMenu.removeAll(); JMenuItem item = newJMenuItem(tr("Verify/Compile"), 'R'); item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { handleRun(false, Editor.this.presentHandler, Editor.this.runHandler); } }); sketchMenu.add(item); item = newJMenuItem(tr("Upload"), 'U'); item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { handleExport(false); } }); sketchMenu.add(item); item = newJMenuItemShift(tr("Upload Using Programmer"), 'U'); item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { handleExport(true); } }); sketchMenu.add(item); item = newJMenuItemAlt(tr("Export compiled Binary"), 'S'); item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { if (new ShouldSaveReadOnly().test(sketchController) && !handleSave(true)) { System.out.println(tr("Export canceled, changes must first be saved.")); return; } handleRun(false, new ShouldSaveReadOnly(), Editor.this.presentAndSaveHandler, Editor.this.runAndSaveHandler); } }); sketchMenu.add(item); // item = new JMenuItem("Stop"); // item.addActionListener(new ActionListener() { // public void actionPerformed(ActionEvent e) { // handleStop(); // } // }); // sketchMenu.add(item); sketchMenu.addSeparator(); item = newJMenuItem(tr("Show Sketch Folder"), 'K'); item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { Base.openFolder(sketch.getFolder()); } }); sketchMenu.add(item); item.setEnabled(Base.openFolderAvailable()); if (importMenu == null) { importMenu = new JMenu(tr("Include Library")); MenuScroller.setScrollerFor(importMenu); base.rebuildImportMenu(importMenu); } sketchMenu.add(importMenu); item = new JMenuItem(tr("Add File...")); item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { sketchController.handleAddFile(); } }); sketchMenu.add(item); } private JMenu buildToolsMenu() { toolsMenu = new JMenu(tr("Tools")); toolsMenu.setMnemonic(KeyEvent.VK_T); addInternalTools(toolsMenu); JMenuItem item = newJMenuItemShift(tr("Serial Monitor"), 'M'); item.addActionListener(e -> handleSerial()); toolsMenu.add(item); item = newJMenuItemShift(tr("Serial Plotter"), 'L'); item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { handlePlotter(); } }); toolsMenu.add(item); addTools(toolsMenu, BaseNoGui.getToolsFolder()); File sketchbookTools = new File(BaseNoGui.getSketchbookFolder(), "tools"); addTools(toolsMenu, sketchbookTools); toolsMenu.addSeparator(); numTools = toolsMenu.getItemCount(); // XXX: DAM: these should probably be implemented using the Tools plugin // API, if possible (i.e. if it supports custom actions, etc.) base.getBoardsCustomMenus().stream().forEach(toolsMenu::add); if (portMenu == null) portMenu = new JMenu(tr("Port")); populatePortMenu(); toolsMenu.add(portMenu); MenuScroller.setScrollerFor(portMenu); item = new JMenuItem(tr("Get Board Info")); item.addActionListener(e -> handleBoardInfo()); toolsMenu.add(item); toolsMenu.addSeparator(); base.rebuildProgrammerMenu(); programmersMenu = new JMenu(tr("Programmer")); base.getProgrammerMenus().stream().forEach(programmersMenu::add); toolsMenu.add(programmersMenu); item = new JMenuItem(tr("Burn Bootloader")); item.addActionListener(e -> handleBurnBootloader()); toolsMenu.add(item); toolsMenu.addMenuListener(new StubMenuListener() { public void menuSelected(MenuEvent e) { //System.out.println("Tools menu selected."); populatePortMenu(); for (Component c : toolsMenu.getMenuComponents()) { if ((c instanceof JMenu) && c.isVisible()) { JMenu menu = (JMenu)c; String name = menu.getText(); if (name == null) continue; String basename = name; int index = name.indexOf(':'); if (index > 0) basename = name.substring(0, index); String sel = null; int count = menu.getItemCount(); for (int i=0; i < count; i++) { JMenuItem item = menu.getItem(i); if (item != null && item.isSelected()) { sel = item.getText(); if (sel != null) break; } } if (sel == null) { if (!name.equals(basename)) menu.setText(basename); } else { if (sel.length() > 50) sel = sel.substring(0, 50) + "..."; String newname = basename + ": \"" + sel + "\""; if (!name.equals(newname)) menu.setText(newname); } } } } }); return toolsMenu; } private void addTools(JMenu menu, File sourceFolder) { if (sourceFolder == null) return; Map toolItems = new HashMap<>(); File[] folders = sourceFolder.listFiles(new FileFilter() { public boolean accept(File folder) { if (folder.isDirectory()) { //System.out.println("checking " + folder); File subfolder = new File(folder, "tool"); return subfolder.exists(); } return false; } }); if (folders == null || folders.length == 0) { return; } for (File folder : folders) { File toolDirectory = new File(folder, "tool"); try { // add dir to classpath for .classes //urlList.add(toolDirectory.toURL()); // add .jar files to classpath File[] archives = toolDirectory.listFiles(new FilenameFilter() { public boolean accept(File dir, String name) { return (name.toLowerCase().endsWith(".jar") || name.toLowerCase().endsWith(".zip")); } }); URL[] urlList = new URL[archives.length]; for (int j = 0; j < urlList.length; j++) { urlList[j] = archives[j].toURI().toURL(); } URLClassLoader loader = new URLClassLoader(urlList); String className = null; for (File archive : archives) { className = findClassInZipFile(folder.getName(), archive); if (className != null) break; } /* // Alternatively, could use manifest files with special attributes: // http://java.sun.com/j2se/1.3/docs/guide/jar/jar.html // Example code for loading from a manifest file: // http://forums.sun.com/thread.jspa?messageID=3791501 File infoFile = new File(toolDirectory, "tool.txt"); if (!infoFile.exists()) continue; String[] info = PApplet.loadStrings(infoFile); //Main-Class: org.poo.shoe.AwesomerTool //String className = folders[i].getName(); String className = null; for (int k = 0; k < info.length; k++) { if (info[k].startsWith(";")) continue; String[] pieces = PApplet.splitTokens(info[k], ": "); if (pieces.length == 2) { if (pieces[0].equals("Main-Class")) { className = pieces[1]; } } } */ // If no class name found, just move on. if (className == null) continue; Class toolClass = Class.forName(className, true, loader); final Tool tool = (Tool) toolClass.newInstance(); tool.init(Editor.this); String title = tool.getMenuTitle(); JMenuItem item = new JMenuItem(title); item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { SwingUtilities.invokeLater(tool); //new Thread(tool).start(); } }); //menu.add(item); toolItems.put(title, item); } catch (Exception e) { e.printStackTrace(); } } ArrayList toolList = new ArrayList<>(toolItems.keySet()); if (toolList.size() == 0) return; menu.addSeparator(); Collections.sort(toolList); for (String title : toolList) { menu.add(toolItems.get(title)); } } private String findClassInZipFile(String base, File file) { // Class file to search for String classFileName = "/" + base + ".class"; ZipFile zipFile = null; try { zipFile = new ZipFile(file); Enumeration entries = zipFile.entries(); while (entries.hasMoreElements()) { ZipEntry entry = (ZipEntry) entries.nextElement(); if (!entry.isDirectory()) { String name = entry.getName(); //System.out.println("entry: " + name); if (name.endsWith(classFileName)) { //int slash = name.lastIndexOf('/'); //String packageName = (slash == -1) ? "" : name.substring(0, slash); // Remove .class and convert slashes to periods. return name.substring(0, name.length() - 6).replace('/', '.'); } } } } catch (IOException e) { //System.err.println("Ignoring " + filename + " (" + e.getMessage() + ")"); e.printStackTrace(); } finally { if (zipFile != null) { try { zipFile.close(); } catch (IOException e) { // noop } } } return null; } public void updateKeywords(PdeKeywords keywords) { for (EditorTab tab : tabs) tab.updateKeywords(keywords); } JMenuItem createToolMenuItem(String className) { try { Class toolClass = Class.forName(className); final Tool tool = (Tool) toolClass.newInstance(); JMenuItem item = new JMenuItem(tool.getMenuTitle()); tool.init(Editor.this); item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { SwingUtilities.invokeLater(tool); } }); return item; } catch (Exception e) { e.printStackTrace(); return null; } } private void addInternalTools(JMenu menu) { JMenuItem item; item = createToolMenuItem("cc.arduino.packages.formatter.AStyle"); if (item == null) { throw new NullPointerException("Tool cc.arduino.packages.formatter.AStyle unavailable"); } item.setName("menuToolsAutoFormat"); int modifiers = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(); item.setAccelerator(KeyStroke.getKeyStroke('T', modifiers)); menu.add(item); //menu.add(createToolMenuItem("processing.app.tools.CreateFont")); //menu.add(createToolMenuItem("processing.app.tools.ColorSelector")); menu.add(createToolMenuItem("processing.app.tools.Archiver")); menu.add(createToolMenuItem("processing.app.tools.FixEncoding")); } class SerialMenuListener implements ActionListener { private final String serialPort; public SerialMenuListener(String serialPort) { this.serialPort = serialPort; } public void actionPerformed(ActionEvent e) { selectSerialPort(serialPort); base.onBoardOrPortChange(); } } private void selectSerialPort(String name) { if(portMenu == null) { System.out.println(tr("serialMenu is null")); return; } if (name == null) { System.out.println(tr("name is null")); return; } JCheckBoxMenuItem selection = null; for (int i = 0; i < portMenu.getItemCount(); i++) { JMenuItem menuItem = portMenu.getItem(i); if (!(menuItem instanceof JCheckBoxMenuItem)) { continue; } JCheckBoxMenuItem checkBoxMenuItem = ((JCheckBoxMenuItem) menuItem); checkBoxMenuItem.setState(false); if (name.equals(checkBoxMenuItem.getText())) selection = checkBoxMenuItem; } if (selection != null) selection.setState(true); //System.out.println(item.getLabel()); BaseNoGui.selectSerialPort(name); if (serialMonitor != null) { try { serialMonitor.close(); serialMonitor.setVisible(false); } catch (Exception e) { // ignore } } if (serialPlotter != null) { try { serialPlotter.close(); serialPlotter.setVisible(false); } catch (Exception e) { // ignore } } onBoardOrPortChange(); base.onBoardOrPortChange(); //System.out.println("set to " + get("serial.port")); } private void populatePortMenu() { portMenu.removeAll(); String selectedPort = PreferencesData.get("serial.port"); List ports = Base.getDiscoveryManager().discovery(); ports = platform.filterPorts(ports, PreferencesData.getBoolean("serial.ports.showall")); Collections.sort(ports, new Comparator() { @Override public int compare(BoardPort o1, BoardPort o2) { return BOARD_PROTOCOLS_ORDER.indexOf(o1.getProtocol()) - BOARD_PROTOCOLS_ORDER.indexOf(o2.getProtocol()); } }); String lastProtocol = null; String lastProtocolTranslated; for (BoardPort port : ports) { if (lastProtocol == null || !port.getProtocol().equals(lastProtocol)) { if (lastProtocol != null) { portMenu.addSeparator(); } lastProtocol = port.getProtocol(); if (BOARD_PROTOCOLS_ORDER.indexOf(port.getProtocol()) != -1) { lastProtocolTranslated = BOARD_PROTOCOLS_ORDER_TRANSLATIONS.get(BOARD_PROTOCOLS_ORDER.indexOf(port.getProtocol())); } else { lastProtocolTranslated = port.getProtocol(); } JMenuItem lastProtocolMenuItem = new JMenuItem(tr(lastProtocolTranslated)); lastProtocolMenuItem.setEnabled(false); portMenu.add(lastProtocolMenuItem); } String address = port.getAddress(); String label = port.getLabel(); JCheckBoxMenuItem item = new JCheckBoxMenuItem(label, address.equals(selectedPort)); item.addActionListener(new SerialMenuListener(address)); portMenu.add(item); } portMenu.setEnabled(portMenu.getMenuComponentCount() > 0); } private JMenu buildHelpMenu() { // To deal with a Mac OS X 10.5 bug, add an extra space after the name // so that the OS doesn't try to insert its slow help menu. JMenu menu = new JMenu(tr("Help")); menu.setMnemonic(KeyEvent.VK_H); JMenuItem item; /* // testing internal web server to serve up docs from a zip file item = new JMenuItem("Web Server Test"); item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { //WebServer ws = new WebServer(); SwingUtilities.invokeLater(new Runnable() { public void run() { try { int port = WebServer.launch("/Users/fry/coconut/processing/build/shared/reference.zip"); Base.openURL("http://127.0.0.1:" + port + "/reference/setup_.html"); } catch (IOException e1) { e1.printStackTrace(); } } }); } }); menu.add(item); */ /* item = new JMenuItem("Browser Test"); item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { //Base.openURL("http://processing.org/learning/gettingstarted/"); //JFrame browserFrame = new JFrame("Browser"); BrowserStartup bs = new BrowserStartup("jar:file:/Users/fry/coconut/processing/build/shared/reference.zip!/reference/setup_.html"); bs.initUI(); bs.launch(); } }); menu.add(item); */ item = new JMenuItem(tr("Getting Started")); item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { Base.showArduinoGettingStarted(); } }); menu.add(item); item = new JMenuItem(tr("Environment")); item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { Base.showEnvironment(); } }); menu.add(item); item = new JMenuItem(tr("Troubleshooting")); item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { Base.showTroubleshooting(); } }); menu.add(item); item = new JMenuItem(tr("Reference")); item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { Base.showReference(); } }); menu.add(item); menu.addSeparator(); item = new JMenuItem(tr("Galileo Help")); item.setEnabled(false); menu.add(item); item = new JMenuItem(tr("Getting Started")); item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { Base.showReference("reference/Galileo_help_files", "ArduinoIDE_guide_galileo"); } }); menu.add(item); item = new JMenuItem(tr("Troubleshooting")); item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { Base.showReference("reference/Galileo_help_files", "Guide_Troubleshooting_Galileo"); } }); menu.add(item); menu.addSeparator(); item = new JMenuItem(tr("Edison Help")); item.setEnabled(false); menu.add(item); item = new JMenuItem(tr("Getting Started")); item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { Base.showReference("reference/Edison_help_files", "ArduinoIDE_guide_edison"); } }); menu.add(item); item = new JMenuItem(tr("Troubleshooting")); item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { Base.showReference("reference/Edison_help_files", "Guide_Troubleshooting_Edison"); } }); menu.add(item); menu.addSeparator(); item = newJMenuItemShift(tr("Find in Reference"), 'F'); item.addActionListener(this::handleFindReference); menu.add(item); item = new JMenuItem(tr("Frequently Asked Questions")); item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { Base.showFAQ(); } }); menu.add(item); item = new JMenuItem(tr("Visit Arduino.cc")); item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { Base.openURL(tr("http://www.arduino.cc/")); } }); menu.add(item); // macosx already has its own about menu if (!OSUtils.hasMacOSStyleMenus()) { menu.addSeparator(); item = new JMenuItem(tr("About Arduino")); item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { base.handleAbout(); } }); menu.add(item); } return menu; } private JMenu buildEditMenu() { JMenu menu = new JMenu(tr("Edit")); menu.setName("menuEdit"); menu.setMnemonic(KeyEvent.VK_E); undoItem = newJMenuItem(tr("Undo"), 'Z'); undoItem.setName("menuEditUndo"); undoItem.addActionListener(e -> getCurrentTab().handleUndo()); menu.add(undoItem); if (!OSUtils.isMacOS()) { redoItem = newJMenuItem(tr("Redo"), 'Y'); } else { redoItem = newJMenuItemShift(tr("Redo"), 'Z'); } redoItem.setName("menuEditRedo"); redoItem.addActionListener(e -> getCurrentTab().handleRedo()); menu.add(redoItem); menu.addSeparator(); JMenuItem cutItem = newJMenuItem(tr("Cut"), 'X'); cutItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { getCurrentTab().handleCut(); } }); menu.add(cutItem); JMenuItem copyItem = newJMenuItem(tr("Copy"), 'C'); copyItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { getCurrentTab().getTextArea().copy(); } }); menu.add(copyItem); JMenuItem copyForumItem = newJMenuItemShift(tr("Copy for Forum"), 'C'); copyForumItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { getCurrentTab().handleDiscourseCopy(); } }); menu.add(copyForumItem); JMenuItem copyHTMLItem = newJMenuItemAlt(tr("Copy as HTML"), 'C'); copyHTMLItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { getCurrentTab().handleHTMLCopy(); } }); menu.add(copyHTMLItem); JMenuItem pasteItem = newJMenuItem(tr("Paste"), 'V'); pasteItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { getCurrentTab().handlePaste(); } }); menu.add(pasteItem); JMenuItem selectAllItem = newJMenuItem(tr("Select All"), 'A'); selectAllItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { getCurrentTab().handleSelectAll(); } }); menu.add(selectAllItem); JMenuItem gotoLine = newJMenuItem(tr("Go to line..."), 'L'); gotoLine.addActionListener(e -> { GoToLineNumber goToLineNumber = new GoToLineNumber(Editor.this); goToLineNumber.setLocationRelativeTo(Editor.this); goToLineNumber.setVisible(true); }); menu.add(gotoLine); menu.addSeparator(); JMenuItem commentItem = newJMenuItem(tr("Comment/Uncomment"), PreferencesData.get("editor.keys.shortcut_comment", "/").charAt(0)); commentItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { getCurrentTab().handleCommentUncomment(); } }); menu.add(commentItem); JMenuItem increaseIndentItem = new JMenuItem(tr("Increase Indent")); increaseIndentItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0)); increaseIndentItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { getCurrentTab().handleIndentOutdent(true); } }); menu.add(increaseIndentItem); JMenuItem decreseIndentItem = new JMenuItem(tr("Decrease Indent")); decreseIndentItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, InputEvent.SHIFT_MASK)); decreseIndentItem.setName("menuDecreaseIndent"); decreseIndentItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { getCurrentTab().handleIndentOutdent(false); } }); menu.add(decreseIndentItem); menu.addSeparator(); JMenuItem increaseFontSizeItem = newJMenuItem(tr("Increase Font Size"), '+'); increaseFontSizeItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { base.handleFontSizeChange(1); } }); menu.add(increaseFontSizeItem); JMenuItem decreaseFontSizeItem = newJMenuItem(tr("Decrease Font Size"), '-'); decreaseFontSizeItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { base.handleFontSizeChange(-1); } }); menu.add(decreaseFontSizeItem); menu.addSeparator(); JMenuItem findItem = newJMenuItem(tr("Find..."), 'F'); findItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { if (find == null) { find = new FindReplace(Editor.this, Base.FIND_DIALOG_STATE); } if (!OSUtils.isMacOS()) { find.setFindText(getCurrentTab().getSelectedText()); } find.setLocationRelativeTo(Editor.this); find.setVisible(true); } }); menu.add(findItem); JMenuItem findNextItem = newJMenuItem(tr("Find Next"), 'G'); findNextItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { if (find != null) { find.findNext(); } } }); menu.add(findNextItem); JMenuItem findPreviousItem = newJMenuItemShift(tr("Find Previous"), 'G'); findPreviousItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { if (find != null) { find.findPrevious(); } } }); menu.add(findPreviousItem); if (OSUtils.isMacOS()) { JMenuItem useSelectionForFindItem = newJMenuItem(tr("Use Selection For Find"), 'E'); useSelectionForFindItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { if (find == null) { find = new FindReplace(Editor.this, Base.FIND_DIALOG_STATE); } find.setFindText(getCurrentTab().getSelectedText()); } }); menu.add(useSelectionForFindItem); } menu.addMenuListener(new MenuListener() { @Override public void menuSelected(MenuEvent e) { boolean enabled = getCurrentTab().getSelectedText() != null; cutItem.setEnabled(enabled); copyItem.setEnabled(enabled); } @Override public void menuDeselected(MenuEvent e) {} @Override public void menuCanceled(MenuEvent e) {} }); return menu; } /** * A software engineer, somewhere, needs to have his abstraction * taken away. In some countries they jail or beat people for writing * the sort of API that would require a five line helper function * just to set the command key for a menu item. */ static public JMenuItem newJMenuItem(String title, int what) { JMenuItem menuItem = new JMenuItem(title); menuItem.setAccelerator(KeyStroke.getKeyStroke(what, SHORTCUT_KEY_MASK)); return menuItem; } /** * Like newJMenuItem() but adds shift as a modifier for the key command. */ static public JMenuItem newJMenuItemShift(String title, int what) { JMenuItem menuItem = new JMenuItem(title); menuItem.setAccelerator(KeyStroke.getKeyStroke(what, SHORTCUT_KEY_MASK | ActionEvent.SHIFT_MASK)); return menuItem; } /** * Same as newJMenuItem(), but adds the ALT (on Linux and Windows) * or OPTION (on Mac OS X) key as a modifier. */ private static JMenuItem newJMenuItemAlt(String title, int what) { JMenuItem menuItem = new JMenuItem(title); menuItem.setAccelerator(KeyStroke.getKeyStroke(what, SHORTCUT_ALT_KEY_MASK)); return menuItem; } // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . protected void updateUndoRedoState() { SketchTextArea textArea = getCurrentTab().getTextArea(); undoItem.setEnabled(textArea.canUndo()); redoItem.setEnabled(textArea.canRedo()); } // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . // these will be done in a more generic way soon, more like: // setHandler("action name", Runnable); // but for the time being, working out the kinks of how many things to // abstract from the editor in this fashion. private void resetHandlers() { runHandler = new BuildHandler(); presentHandler = new BuildHandler(true); runAndSaveHandler = new BuildHandler(false, true); presentAndSaveHandler = new BuildHandler(true, true); exportHandler = new DefaultExportHandler(); exportAppHandler = new DefaultExportAppHandler(); timeoutUploadHandler = new TimeoutUploadHandler(); } // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . /** * Gets the current sketch controller. */ public SketchController getSketchController() { return sketchController; } /** * Gets the current sketch. */ public Sketch getSketch() { return sketch; } /** * Gets the currently displaying tab. */ public EditorTab getCurrentTab() { return tabs.get(currentTabIndex); } /** * Gets the index of the currently displaying tab. */ public int getCurrentTabIndex() { return currentTabIndex; } /** * Returns an (unmodifiable) list of currently opened tabs. */ public List getTabs() { return Collections.unmodifiableList(tabs); } // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . /** * Change the currently displayed tab. * Note that the GUI might not update immediately, since this needs * to run in the Event dispatch thread. * @param index The index of the tab to select */ public void selectTab(final int index) { currentTabIndex = index; updateUndoRedoState(); updateTitle(); header.rebuild(); getCurrentTab().activated(); // This must be run in the GUI thread SwingUtilities.invokeLater(() -> { codePanel.removeAll(); codePanel.add(tabs.get(index), BorderLayout.CENTER); tabs.get(index).requestFocusInWindow(); // get the caret blinking // For some reason, these are needed. Revalidate says it should be // automatically called when components are added or removed, but without // it, the component switched to is not displayed. repaint() is needed to // clear the entire text area of any previous text. codePanel.revalidate(); codePanel.repaint(); }); } public void selectNextTab() { selectTab((currentTabIndex + 1) % tabs.size()); } public void selectPrevTab() { selectTab((currentTabIndex - 1 + tabs.size()) % tabs.size()); } public EditorTab findTab(final SketchFile file) { return tabs.get(findTabIndex(file)); } /** * Finds the index of the tab showing the given file. Matches the file against * EditorTab.getSketchFile() using ==. * * @returns The index of the tab for the given file, or -1 if no such tab was * found. */ public int findTabIndex(final SketchFile file) { for (int i = 0; i < tabs.size(); ++i) { if (tabs.get(i).getSketchFile() == file) return i; } return -1; } /** * Finds the index of the tab showing the given file. Matches the file against * EditorTab.getSketchFile().getFile() using equals. * * @returns The index of the tab for the given file, or -1 if no such tab was * found. */ public int findTabIndex(final File file) { for (int i = 0; i < tabs.size(); ++i) { if (tabs.get(i).getSketchFile().getFile().equals(file)) return i; } return -1; } /** * Create tabs for each of the current sketch's files, removing any existing * tabs. */ public void createTabs() { tabs.clear(); currentTabIndex = -1; tabs.ensureCapacity(sketch.getCodeCount()); for (SketchFile file : sketch.getFiles()) { try { addTab(file, null); } catch(IOException e) { // TODO: Improve / move error handling System.err.println(e); } } selectTab(0); } /** * Reorders tabs as per current sketch's files order */ public void reorderTabs() { Collections.sort(tabs, (x, y) -> Sketch.CODE_DOCS_COMPARATOR.compare(x.getSketchFile(), y.getSketchFile())); } /** * Add a new tab. * * @param file * The file to show in the tab. * @param contents * The contents to show in the tab, or null to load the contents from * the given file. * @throws IOException */ protected void addTab(SketchFile file, String contents) throws IOException { EditorTab tab = new EditorTab(this, file, contents); tab.getTextArea().getDocument() .addDocumentListener(new DocumentTextChangeListener( () -> updateUndoRedoState())); tabs.add(tab); reorderTabs(); } protected void removeTab(SketchFile file) throws IOException { int index = findTabIndex(file); tabs.remove(index); } // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . void handleFindReference(ActionEvent e) { String text = getCurrentTab().getCurrentKeyword(); String referenceFile = base.getPdeKeywords().getReference(text); if (referenceFile == null) { statusNotice(I18n.format(tr("No reference available for \"{0}\""), text)); } else { if (referenceFile.startsWith("Serial_")) { Base.showReference("Serial/" + referenceFile.substring("Serial_".length())); } else { Base.showReference("Reference/" + referenceFile); } } } // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . /** * Implements Sketch → Run. * @param verbose Set true to run with verbose output. * @param verboseHandler * @param nonVerboseHandler */ public void handleRun(final boolean verbose, Runnable verboseHandler, Runnable nonVerboseHandler) { handleRun(verbose, new ShouldSaveIfModified(), verboseHandler, nonVerboseHandler); } private void handleRun(final boolean verbose, Predicate shouldSavePredicate, Runnable verboseHandler, Runnable nonVerboseHandler) { if (shouldSavePredicate.test(sketchController)) { handleSave(true); } toolbar.activateRun(); status.progress(tr("Compiling sketch...")); // do this to advance/clear 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 (PreferencesData.getBoolean("console.auto_clear")) { console.clear(); } // Cannot use invokeLater() here, otherwise it gets // placed on the event thread and causes a hang--bad idea all around. new Thread(verbose ? verboseHandler : nonVerboseHandler).start(); } class BuildHandler implements Runnable { private final boolean verbose; private final boolean saveHex; public BuildHandler() { this(false); } public BuildHandler(boolean verbose) { this(verbose, false); } public BuildHandler(boolean verbose, boolean saveHex) { this.verbose = verbose; this.saveHex = saveHex; } @Override public void run() { try { removeAllLineHighlights(); sketchController.build(verbose, saveHex); statusNotice(tr("Done compiling.")); } catch (PreferencesMapException e) { statusError(I18n.format( tr("Error while compiling: missing '{0}' configuration parameter"), e.getMessage())); } catch (Exception e) { status.unprogress(); statusError(e); } status.unprogress(); toolbar.deactivateRun(); avoidMultipleOperations = false; } } public void removeAllLineHighlights() { for (EditorTab tab : tabs) tab.getTextArea().removeAllLineHighlights(); } public void addLineHighlight(int line) throws BadLocationException { getCurrentTab().getTextArea().addLineHighlight(line, new Color(1, 0, 0, 0.2f)); getCurrentTab().getTextArea().setCaretPosition(getCurrentTab().getTextArea().getLineStartOffset(line)); } /** * Implements Sketch → Stop, or pressing Stop on the toolbar. */ private void handleStop() { // called by menu or buttons // toolbar.activate(EditorToolbar.STOP); toolbar.deactivateRun(); // toolbar.deactivate(EditorToolbar.STOP); // focus the PDE again after quitting presentation mode [toxi 030903] toFront(); } /** * Check if the sketch is modified and ask user to save changes. * @return false if canceling the close/quit operation */ protected boolean checkModified() { if (!sketch.isModified()) return true; // As of Processing 1.0.10, this always happens immediately. // http://dev.processing.org/bugs/show_bug.cgi?id=1456 toFront(); String prompt = I18n.format(tr("Save changes to \"{0}\"? "), sketch.getName()); if (!OSUtils.hasMacOSStyleMenus()) { int result = JOptionPane.showConfirmDialog(this, prompt, tr("Close"), JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE); switch (result) { case JOptionPane.YES_OPTION: return handleSave(true); case JOptionPane.NO_OPTION: return true; // ok to continue case JOptionPane.CANCEL_OPTION: case JOptionPane.CLOSED_OPTION: // Escape key pressed return false; default: throw new IllegalStateException(); } } else { // This code is disabled unless Java 1.5 is being used on Mac OS X // because of a Java bug that prevents the initial value of the // dialog from being set properly (at least on my MacBook Pro). // The bug causes the "Don't Save" option to be the highlighted, // blinking, default. This sucks. But I'll tell you what doesn't // suck--workarounds for the Mac and Apple's snobby attitude about it! // I think it's nifty that they treat their developers like dirt. JOptionPane pane = new JOptionPane(tr(" " + " " + "Do you want to save changes to this sketch
" + " before closing?
" + "

If you don't save, your changes will be lost."), JOptionPane.QUESTION_MESSAGE); String[] options = new String[] { tr("Save"), tr("Cancel"), tr("Don't Save") }; pane.setOptions(options); // highlight the safest option ala apple hig pane.setInitialValue(options[0]); 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 { return result == options[2]; } } } /** * 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(File sketchFile) { // check to make sure that this .pde file is // in a folder of the same name String fileName = sketchFile.getName(); File file = Sketch.checkSketchFile(sketchFile); if (file == null) { if (!fileName.endsWith(".ino") && !fileName.endsWith(".pde")) { Base.showWarning(tr("Bad file selected"), tr("Arduino 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 = {tr("OK"), tr("Cancel")}; String prompt = I18n.format(tr("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, tr("Moving"), JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, options, options[0]); if (result != JOptionPane.YES_OPTION) { return false; } // create properly named folder File properFolder = new File(sketchFile.getParent(), properParent); if (properFolder.exists()) { Base.showWarning(tr("Error"), I18n.format(tr("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(tr("Error"), tr("Could not create the sketch folder."), null); return false; } // copy the sketch inside File properPdeFile = new File(properFolder, sketchFile.getName()); try { Base.copyFile(sketchFile, properPdeFile); } catch (IOException e) { Base.showWarning(tr("Error"), tr("Could not copy to a proper location."), e); return false; } // remove the original file, so user doesn't get confused sketchFile.delete(); // update with the new path file = properPdeFile; } } try { sketch = new Sketch(file); } catch (IOException e) { Base.showWarning(tr("Error"), tr("Could not create the sketch."), e); return false; } sketchController = new SketchController(this, sketch); createTabs(); // Disable untitled setting from previous document, if any untitled = false; // opening was successful return true; } private void updateTitle() { if (sketchController == null) { return; } SketchFile current = getCurrentTab().getSketchFile(); String customFormat = PreferencesData.get("editor.custom_title_format"); if (customFormat != null && !customFormat.trim().isEmpty()) { Map titleMap = new HashMap(); titleMap.put("file", current.getFileName()); String path = sketch.getFolder().getAbsolutePath(); titleMap.put("folder", path); titleMap.put("path", path); titleMap.put("project", sketch.getName()); titleMap.put("version", BaseNoGui.VERSION_NAME_LONG); setTitle(StringReplacer.replaceFromMapping(customFormat, titleMap)); } else { if (current.isPrimary()) { setTitle(I18n.format(tr("{0} | Arduino {1}"), sketch.getName(), BaseNoGui.VERSION_NAME_LONG)); } else { setTitle(I18n.format(tr("{0} - {1} | Arduino {2}"), sketch.getName(), current.getFileName(), BaseNoGui.VERSION_NAME_LONG)); } } } /** * 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 removeAllLineHighlights(); 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; } private boolean handleSave2() { toolbar.activateSave(); statusNotice(tr("Saving...")); boolean saved = false; try { boolean wasReadOnly = sketchController.isReadOnly(BaseNoGui.librariesIndexer.getInstalledLibraries(), BaseNoGui.getExamplesPath()); String previousMainFilePath = sketch.getMainFilePath(); saved = sketchController.save(); if (saved) { statusNotice(tr("Done Saving.")); if (wasReadOnly) { base.removeRecentSketchPath(previousMainFilePath); } base.storeRecentSketches(sketchController); base.rebuildRecentSketchesMenuItems(); } 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.deactivateSave(); return saved; } public boolean handleSaveAs() { //stopRunner(); // formerly from 0135 handleStop(); toolbar.activateSave(); //SwingUtilities.invokeLater(new Runnable() { //public void run() { statusNotice(tr("Saving...")); try { if (sketchController.saveAs()) { base.storeRecentSketches(sketchController); base.rebuildRecentSketchesMenuItems(); statusNotice(tr("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(tr("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.deactivateSave(); // Update editor window title in case of "Save as..." updateTitle(); header.rebuild(); } return true; } private boolean serialPrompt() { int count = portMenu.getItemCount(); Object[] names = new Object[count]; for (int i = 0; i < count; i++) { names[i] = portMenu.getItem(i).getText(); } String result = (String) JOptionPane.showInputDialog(this, I18n.format( tr("Serial port {0} not found.\n" + "Retry the upload with another serial port?"), PreferencesData.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 (PreferencesData.getBoolean("editor.save_on_verify")) { if (sketch.isModified() && !sketchController.isReadOnly( BaseNoGui.librariesIndexer .getInstalledLibraries(), BaseNoGui.getExamplesPath())) { handleSave(true); } } toolbar.activateExport(); console.clear(); status.progress(tr("Uploading to I/O Board...")); avoidMultipleOperations = true; new Thread(timeoutUploadHandler).start(); new Thread(usingProgrammer ? exportAppHandler : exportHandler).start(); } // DAM: in Arduino, this is upload class DefaultExportHandler implements Runnable { public void run() { try { removeAllLineHighlights(); if (serialMonitor != null) { serialMonitor.suspend(); } if (serialPlotter != null) { serialPlotter.suspend(); } uploading = true; boolean success = sketchController.exportApplet(false); if (success) { statusNotice(tr("Done uploading.")); } } catch (SerialNotFoundException e) { if (portMenu.getItemCount() == 0) statusError(e); else if (serialPrompt()) run(); else statusNotice(tr("Upload canceled.")); } catch (PreferencesMapException e) { statusError(I18n.format( tr("Error while uploading: missing '{0}' configuration parameter"), e.getMessage())); } catch (RunnerException e) { //statusError("Error during upload."); //e.printStackTrace(); status.unprogress(); statusError(e); } catch (Exception e) { e.printStackTrace(); } finally { populatePortMenu(); avoidMultipleOperations = false; } status.unprogress(); uploading = false; //toolbar.clear(); toolbar.deactivateExport(); resumeOrCloseSerialMonitor(); resumeOrCloseSerialPlotter(); base.onBoardOrPortChange(); } } private void resumeOrCloseSerialMonitor() { // Return the serial monitor window to its initial state if (serialMonitor != null) { BoardPort boardPort = BaseNoGui.getDiscoveryManager().find(PreferencesData.get("serial.port")); long sleptFor = 0; while (boardPort == null && sleptFor < MAX_TIME_AWAITING_FOR_RESUMING_SERIAL_MONITOR) { try { Thread.sleep(100); sleptFor += 100; boardPort = BaseNoGui.getDiscoveryManager().find(PreferencesData.get("serial.port")); } catch (InterruptedException e) { // noop } } try { if (serialMonitor != null) { serialMonitor.resume(boardPort); if (boardPort == null) { serialMonitor.close(); handleSerial(); } else { serialMonitor.resume(boardPort); } } } catch (Exception e) { statusError(e); } } } private void resumeOrCloseSerialPlotter() { // Return the serial plotter window to its initial state if (serialPlotter != null) { BoardPort boardPort = BaseNoGui.getDiscoveryManager().find(PreferencesData.get("serial.port")); try { if (serialPlotter != null) serialPlotter.resume(boardPort); if (boardPort == null) { serialPlotter.close(); handlePlotter(); } else { serialPlotter.resume(boardPort); } } catch (Exception e) { statusError(e); } } } // DAM: in Arduino, this is upload (with verbose output) class DefaultExportAppHandler implements Runnable { public void run() { try { if (serialMonitor != null) { serialMonitor.suspend(); } if (serialPlotter != null) { serialPlotter.suspend(); } uploading = true; boolean success = sketchController.exportApplet(true); if (success) { statusNotice(tr("Done uploading.")); } } catch (SerialNotFoundException e) { if (portMenu.getItemCount() == 0) statusError(e); else if (serialPrompt()) run(); else statusNotice(tr("Upload canceled.")); } catch (PreferencesMapException e) { statusError(I18n.format( tr("Error while uploading: missing '{0}' configuration parameter"), e.getMessage())); } catch (RunnerException e) { //statusError("Error during upload."); //e.printStackTrace(); status.unprogress(); statusError(e); } catch (Exception e) { e.printStackTrace(); } finally { avoidMultipleOperations = false; populatePortMenu(); } status.unprogress(); uploading = false; //toolbar.clear(); toolbar.deactivateExport(); resumeOrCloseSerialMonitor(); resumeOrCloseSerialPlotter(); base.onBoardOrPortChange(); } } class TimeoutUploadHandler implements Runnable { public void run() { try { //10 seconds, than reactivate upload functionality and let the programmer pid being killed Thread.sleep(1000 * 10); if (uploading) { avoidMultipleOperations = false; } } catch (InterruptedException e) { // noop } } } public void handleSerial() { if(serialPlotter != null) { if(serialPlotter.isClosed()) { serialPlotter = null; } else { statusError(tr("Serial monitor not available while plotter is open")); return; } } if (serialMonitor != null) { // The serial monitor already exists if (serialMonitor.isClosed()) { serialMonitor.dispose(); // If it's closed, clear the refrence to the existing // monitor and create a new one serialMonitor = null; } else { // If it's not closed, give it the focus try { serialMonitor.toFront(); serialMonitor.requestFocus(); return; } catch (Exception e) { // noop } } } BoardPort port = Base.getDiscoveryManager().find(PreferencesData.get("serial.port")); if (port == null) { statusError(I18n.format(tr("Board at {0} is not available"), PreferencesData.get("serial.port"))); return; } serialMonitor = new MonitorFactory().newMonitor(port); if (serialMonitor == null) { String board = port.getPrefs().get("board"); String boardName = BaseNoGui.getPlatform().resolveDeviceByBoardID(BaseNoGui.packages, board); statusError(I18n.format(tr("Serial monitor is not supported on network ports such as {0} for the {1} in this release"), PreferencesData.get("serial.port"), boardName)); return; } Base.setIcon(serialMonitor); // If currently uploading, disable the monitor (it will be later // enabled when done uploading) if (uploading || avoidMultipleOperations) { try { serialMonitor.suspend(); } catch (Exception e) { statusError(e); } } boolean success = false; do { if (serialMonitor.requiresAuthorization() && !PreferencesData.has(serialMonitor.getAuthorizationKey())) { PasswordAuthorizationDialog dialog = new PasswordAuthorizationDialog(this, tr("Type board password to access its console")); dialog.setLocationRelativeTo(this); dialog.setVisible(true); if (dialog.isCancelled()) { statusNotice(tr("Unable to open serial monitor")); return; } PreferencesData.set(serialMonitor.getAuthorizationKey(), dialog.getPassword()); } try { serialMonitor.setVisible(true); if (!avoidMultipleOperations) { serialMonitor.open(); } success = true; } catch (ConnectException e) { statusError(tr("Unable to connect: is the sketch using the bridge?")); } catch (JSchException e) { statusError(tr("Unable to connect: wrong password?")); } catch (SerialException e) { String errorMessage = e.getMessage(); if (e.getCause() != null && e.getCause() instanceof SerialPortException) { errorMessage += " (" + ((SerialPortException) e.getCause()).getExceptionType() + ")"; } statusError(errorMessage); try { serialMonitor.close(); } catch (Exception e1) { // noop } } catch (Exception e) { statusError(e); } finally { if (serialMonitor.requiresAuthorization() && !success) { PreferencesData.remove(serialMonitor.getAuthorizationKey()); } } } while (serialMonitor.requiresAuthorization() && !success); } public void handlePlotter() { if(serialMonitor != null) { if(serialMonitor.isClosed()) { serialMonitor = null; } else { statusError(tr("Plotter not available while serial monitor is open")); return; } } if (serialPlotter != null) { // The serial plotter already exists if (serialPlotter.isClosed()) { // If it's closed, clear the refrence to the existing // plotter and create a new one serialPlotter = null; } else { // If it's not closed, give it the focus try { serialPlotter.toFront(); serialPlotter.requestFocus(); return; } catch (Exception e) { // noop } } } BoardPort port = Base.getDiscoveryManager().find(PreferencesData.get("serial.port")); if (port == null) { statusError(I18n.format(tr("Board at {0} is not available"), PreferencesData.get("serial.port"))); return; } serialPlotter = new SerialPlotter(port); Base.setIcon(serialPlotter); // If currently uploading, disable the plotter (it will be later // enabled when done uploading) if (uploading) { try { serialPlotter.suspend(); } catch (Exception e) { statusError(e); } } boolean success = false; do { if (serialPlotter.requiresAuthorization() && !PreferencesData.has(serialPlotter.getAuthorizationKey())) { PasswordAuthorizationDialog dialog = new PasswordAuthorizationDialog(this, tr("Type board password to access its console")); dialog.setLocationRelativeTo(this); dialog.setVisible(true); if (dialog.isCancelled()) { statusNotice(tr("Unable to open serial plotter")); return; } PreferencesData.set(serialPlotter.getAuthorizationKey(), dialog.getPassword()); } try { serialPlotter.open(); serialPlotter.setVisible(true); success = true; } catch (ConnectException e) { statusError(tr("Unable to connect: is the sketch using the bridge?")); } catch (JSchException e) { statusError(tr("Unable to connect: wrong password?")); } catch (SerialException e) { String errorMessage = e.getMessage(); if (e.getCause() != null && e.getCause() instanceof SerialPortException) { errorMessage += " (" + ((SerialPortException) e.getCause()).getExceptionType() + ")"; } statusError(errorMessage); } catch (Exception e) { statusError(e); } finally { if (serialPlotter.requiresAuthorization() && !success) { PreferencesData.remove(serialPlotter.getAuthorizationKey()); } } } while (serialPlotter.requiresAuthorization() && !success); } private void handleBurnBootloader() { console.clear(); statusNotice(tr("Burning bootloader to I/O Board (this may take a minute)...")); new Thread(() -> { try { Uploader uploader = new SerialUploader(); if (uploader.burnBootloader()) { SwingUtilities.invokeLater(() -> statusNotice(tr("Done burning bootloader."))); } else { SwingUtilities.invokeLater(() -> statusError(tr("Error while burning bootloader."))); // error message will already be visible } } catch (PreferencesMapException e) { SwingUtilities.invokeLater(() -> { statusError(I18n.format( tr("Error while burning bootloader: missing '{0}' configuration parameter"), e.getMessage())); }); } catch (RunnerException e) { SwingUtilities.invokeLater(() -> statusError(e.getMessage())); } catch (Exception e) { SwingUtilities.invokeLater(() -> statusError(tr("Error while burning bootloader."))); e.printStackTrace(); } }).start(); } private void handleBoardInfo() { console.clear(); String selectedPort = PreferencesData.get("serial.port"); List ports = Base.getDiscoveryManager().discovery(); String label = ""; String vid = ""; String pid = ""; String iserial = ""; String protocol = ""; boolean found = false; for (BoardPort port : ports) { if (port.getAddress().equals(selectedPort)) { label = port.getBoardName(); vid = port.getVID(); pid = port.getPID(); iserial = port.getISerial(); protocol = port.getProtocol(); found = true; break; } } if (!found) { statusNotice(tr("Please select a port to obtain board info")); return; } if (protocol.equals("network")) { statusNotice(tr("Network port, can't obtain info")); return; } if (vid == null || vid.equals("") || vid.equals("0000")) { statusNotice(tr("Native serial port, can't obtain info")); return; } if (iserial == null || iserial.equals("")) { iserial = tr("Upload any sketch to obtain it"); } if (label == null) { label = tr("Unknown board"); } String infos = I18n.format("BN: {0}\nVID: {1}\nPID: {2}\nSN: {3}", label, vid, pid, iserial); JTextArea textArea = new JTextArea(infos); JOptionPane.showMessageDialog(this, textArea, tr("Board Info"), JOptionPane.PLAIN_MESSAGE); } /** * Handler for File → Page Setup. */ private void handlePageSetup() { PrinterJob printerJob = PrinterJob.getPrinterJob(); if (pageFormat == null) { pageFormat = printerJob.defaultPage(); } pageFormat = printerJob.pageDialog(pageFormat); } /** * Handler for File → Print. */ private void handlePrint() { statusNotice(tr("Printing...")); //printerJob = null; PrinterJob printerJob = PrinterJob.getPrinterJob(); if (pageFormat != null) { //System.out.println("setting page format " + pageFormat); printerJob.setPrintable(getCurrentTab().getTextArea(), pageFormat); } else { printerJob.setPrintable(getCurrentTab().getTextArea()); } // set the name of the job to the code name printerJob.setJobName(getCurrentTab().getSketchFile().getPrettyName()); if (printerJob.printDialog()) { try { printerJob.print(); statusNotice(tr("Done printing.")); } catch (PrinterException pe) { statusError(tr("Error while printing.")); pe.printStackTrace(); } } else { statusNotice(tr("Printing canceled.")); } //printerJob = null; // clear this out? } // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . /** * Show an error int the status bar. */ public void statusError(String what) { System.err.println(what); status.error(what); //new Exception("deactivating RUN").printStackTrace(); toolbar.deactivateRun(); } /** * 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.hasCodeFile()) { selectTab(findTabIndex(re.getCodeFile())); } if (re.hasCodeLine()) { int line = re.getCodeLine(); // subtract one from the end so that the \n ain't included if (line >= getCurrentTab().getTextArea().getLineCount()) { // The error is at the end of this current chunk of code, // so the last line needs to be selected. line = getCurrentTab().getTextArea().getLineCount() - 1; if (getCurrentTab().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 >= getCurrentTab().getTextArea().getLineCount()) { System.err.println(I18n.format(tr("Bad error line: {0}"), line)); } else { try { addLineHighlight(line); } catch (BadLocationException e1) { e1.printStackTrace(); } } } } // 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. */ private void statusEmpty() { statusNotice(EMPTY); } // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . protected void onBoardOrPortChange() { Map boardPreferences = BaseNoGui.getBoardPreferences(); if (boardPreferences != null) lineStatus.setBoardName(boardPreferences.get("name")); else lineStatus.setBoardName("-"); lineStatus.setSerialPort(PreferencesData.get("serial.port")); lineStatus.repaint(); } public void addCompilerProgressListener(CompilerProgressListener listener){ this.status.addCompilerProgressListener(listener); } }