1 /* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ 2 3 /* 4 Part of the Processing project - http://processing.org 5 6 Copyright (c) 2004-10 Ben Fry and Casey Reas 7 Copyright (c) 2001-04 Massachusetts Institute of Technology 8 9 This program is free software; you can redistribute it and/or modify 10 it under the terms of the GNU General Public License version 2 11 as published by the Free Software Foundation. 12 13 This program is distributed in the hope that it will be useful, 14 but WITHOUT ANY WARRANTY; without even the implied warranty of 15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 GNU General Public License for more details. 17 18 You should have received a copy of the GNU General Public License 19 along with this program; if not, write to the Free Software Foundation, 20 Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 21 */ 22 23 package processing.app; 24 25 import cc.arduino.Compiler; 26 import cc.arduino.Constants; 27 import cc.arduino.UpdatableBoardsLibsFakeURLsHandler; 28 import cc.arduino.UploaderUtils; 29 import cc.arduino.contributions.*; 30 import cc.arduino.contributions.libraries.*; 31 import cc.arduino.contributions.libraries.ui.LibraryManagerUI; 32 import cc.arduino.contributions.packages.ContributedPlatform; 33 import cc.arduino.contributions.packages.ContributionInstaller; 34 import cc.arduino.contributions.packages.ContributionsIndexer; 35 import cc.arduino.contributions.packages.ui.ContributionManagerUI; 36 import cc.arduino.files.DeleteFilesOnShutdown; 37 import cc.arduino.packages.DiscoveryManager; 38 import cc.arduino.view.Event; 39 import cc.arduino.view.JMenuUtils; 40 import cc.arduino.view.SplashScreenHelper; 41 42 import org.apache.commons.compress.utils.IOUtils; 43 import org.apache.commons.lang3.StringUtils; 44 import processing.app.debug.TargetBoard; 45 import processing.app.debug.TargetPackage; 46 import processing.app.debug.TargetPlatform; 47 import processing.app.helpers.*; 48 import processing.app.helpers.filefilters.OnlyDirs; 49 import processing.app.helpers.filefilters.OnlyFilesWithExtension; 50 import processing.app.javax.swing.filechooser.FileNameExtensionFilter; 51 import processing.app.legacy.PApplet; 52 import processing.app.macosx.ThinkDifferent; 53 import processing.app.packages.LibraryList; 54 import processing.app.packages.UserLibrary; 55 import processing.app.syntax.PdeKeywords; 56 import processing.app.syntax.SketchTextAreaDefaultInputMap; 57 import processing.app.tools.MenuScroller; 58 import processing.app.tools.ZipDeflater; 59 60 import javax.swing.*; 61 import java.awt.*; 62 import java.awt.event.*; 63 import java.io.*; 64 import java.util.*; 65 import java.util.List; 66 import java.util.Timer; 67 import java.util.logging.Handler; 68 import java.util.logging.Level; 69 import java.util.logging.Logger; 70 import java.util.stream.Collectors; 71 import java.util.stream.Stream; 72 73 import static processing.app.I18n.tr; 74 75 76 /** 77 * The base class for the main processing application. 78 * Primary role of this class is for platform identification and 79 * general interaction with the system (launching URLs, loading 80 * files and images, etc) that comes from that. 81 */ 82 public class Base { 83 84 private static final int RECENT_SKETCHES_MAX_SIZE = 10; 85 86 private static boolean commandLine; 87 public static volatile Base INSTANCE; 88 89 public static Map<String, Object> FIND_DIALOG_STATE = new HashMap<>(); 90 private final ContributionInstaller contributionInstaller; 91 private final LibraryInstaller libraryInstaller; 92 private ContributionsSelfCheck contributionsSelfCheck; 93 94 // set to true after the first time the menu is built. 95 // so that the errors while building don't show up again. 96 boolean builtOnce; 97 98 // classpath for all known libraries for p5 99 // (both those in the p5/libs folder and those with lib subfolders 100 // found in the sketchbook) 101 static public String librariesClassPath; 102 103 // Location for untitled items 104 static File untitledFolder; 105 106 // p5 icon for the window 107 // static Image icon; 108 109 // int editorCount; 110 List<Editor> editors = Collections.synchronizedList(new ArrayList<Editor>()); 111 Editor activeEditor; 112 113 // these menus are shared so that the board and serial port selections 114 // are the same for all windows (since the board and serial port that are 115 // actually used are determined by the preferences, which are shared) 116 private List<JMenu> boardsCustomMenus; 117 private List<JMenuItem> programmerMenus; 118 119 private PdeKeywords pdeKeywords; 120 private final List<JMenuItem> recentSketchesMenuItems = new LinkedList<>(); 121 main(String args[])122 static public void main(String args[]) throws Exception { 123 if (!OSUtils.isWindows()) { 124 // Those properties helps enabling anti-aliasing on Linux 125 // (but not on Windows where they made things worse actually 126 // and the font rendering becomes ugly). 127 128 // Those properties must be set before initializing any 129 // graphic object, otherwise they don't have any effect. 130 System.setProperty("awt.useSystemAAFontSettings", "on"); 131 System.setProperty("swing.aatext", "true"); 132 } 133 System.setProperty("java.net.useSystemProxies", "true"); 134 135 if (OSUtils.isMacOS()) { 136 ThinkDifferent.init(); 137 } 138 139 try { 140 INSTANCE = new Base(args); 141 } catch (Throwable e) { 142 e.printStackTrace(System.err); 143 System.exit(255); 144 } 145 } 146 initLogger()147 static public void initLogger() { 148 Handler consoleHandler = new ConsoleLogger(); 149 consoleHandler.setLevel(Level.ALL); 150 consoleHandler.setFormatter(new LogFormatter("%1$tl:%1$tM:%1$tS [%4$7s] %2$s: %5$s%n")); 151 152 Logger globalLogger = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME); 153 globalLogger.setLevel(consoleHandler.getLevel()); 154 155 // Remove default 156 Handler[] handlers = globalLogger.getHandlers(); 157 for(Handler handler : handlers) { 158 globalLogger.removeHandler(handler); 159 } 160 Logger root = Logger.getLogger(""); 161 handlers = root.getHandlers(); 162 for(Handler handler : handlers) { 163 root.removeHandler(handler); 164 } 165 166 globalLogger.addHandler(consoleHandler); 167 168 Logger.getLogger("cc.arduino.packages.autocomplete").setParent(globalLogger); 169 Logger.getLogger("br.com.criativasoft.cpluslibparser").setParent(globalLogger); 170 Logger.getLogger(Base.class.getPackage().getName()).setParent(globalLogger); 171 172 } 173 isCommandLine()174 static protected boolean isCommandLine() { 175 return commandLine; 176 } 177 178 // Returns a File object for the given pathname. If the pathname 179 // is not absolute, it is interpreted relative to the current 180 // directory when starting the IDE (which is not the same as the 181 // current working directory!). absoluteFile(String path)182 static public File absoluteFile(String path) { 183 return BaseNoGui.absoluteFile(path); 184 } 185 Base(String[] args)186 public Base(String[] args) throws Exception { 187 Thread deleteFilesOnShutdownThread = new Thread(DeleteFilesOnShutdown.INSTANCE); 188 deleteFilesOnShutdownThread.setName("DeleteFilesOnShutdown"); 189 Runtime.getRuntime().addShutdownHook(deleteFilesOnShutdownThread); 190 191 BaseNoGui.initLogger(); 192 193 initLogger(); 194 195 BaseNoGui.initPlatform(); 196 197 BaseNoGui.getPlatform().init(); 198 199 BaseNoGui.initPortableFolder(); 200 201 // Look for a possible "--preferences-file" parameter and load preferences 202 BaseNoGui.initParameters(args); 203 204 CommandlineParser parser = new CommandlineParser(args); 205 parser.parseArgumentsPhase1(); 206 commandLine = !parser.isGuiMode(); 207 208 SplashScreenHelper splash; 209 if (parser.isGuiMode()) { 210 // Setup all notification widgets 211 splash = new SplashScreenHelper(SplashScreen.getSplashScreen()); 212 BaseNoGui.notifier = new GUIUserNotifier(this); 213 214 // Setup the theme coloring fun 215 Theme.init(); 216 System.setProperty("swing.aatext", PreferencesData.get("editor.antialias", "true")); 217 218 // Set the look and feel before opening the window 219 try { 220 BaseNoGui.getPlatform().setLookAndFeel(); 221 } catch (Exception e) { 222 // ignore 223 } 224 225 // Use native popups so they don't look so crappy on osx 226 JPopupMenu.setDefaultLightWeightPopupEnabled(false); 227 } else { 228 splash = new SplashScreenHelper(null); 229 } 230 231 splash.splashText(tr("Loading configuration...")); 232 233 BaseNoGui.initVersion(); 234 235 // Don't put anything above this line that might make GUI, 236 // because the platform has to be inited properly first. 237 238 // Create a location for untitled sketches 239 untitledFolder = FileUtils.createTempFolder("untitled" + new Random().nextInt(Integer.MAX_VALUE), ".tmp"); 240 DeleteFilesOnShutdown.add(untitledFolder); 241 242 BaseNoGui.checkInstallationFolder(); 243 244 // If no path is set, get the default sketchbook folder for this platform 245 if (BaseNoGui.getSketchbookPath() == null) { 246 File defaultFolder = getDefaultSketchbookFolderOrPromptForIt(); 247 if (BaseNoGui.getPortableFolder() != null) 248 PreferencesData.set("sketchbook.path", BaseNoGui.getPortableSketchbookFolder()); 249 else 250 PreferencesData.set("sketchbook.path", defaultFolder.getAbsolutePath()); 251 if (!defaultFolder.exists()) { 252 defaultFolder.mkdirs(); 253 } 254 } 255 256 splash.splashText(tr("Initializing packages...")); 257 BaseNoGui.initPackages(); 258 259 splash.splashText(tr("Preparing boards...")); 260 261 if (!isCommandLine()) { 262 rebuildBoardsMenu(); 263 rebuildProgrammerMenu(); 264 } 265 266 // Setup board-dependent variables. 267 onBoardOrPortChange(); 268 269 pdeKeywords = new PdeKeywords(); 270 pdeKeywords.reload(); 271 272 contributionInstaller = new ContributionInstaller(BaseNoGui.getPlatform(), new GPGDetachedSignatureVerifier()); 273 libraryInstaller = new LibraryInstaller(BaseNoGui.getPlatform()); 274 275 parser.parseArgumentsPhase2(); 276 277 // Save the preferences. For GUI mode, this happens in the quit 278 // handler, but for other modes we should also make sure to save 279 // them. 280 PreferencesData.save(); 281 282 if (parser.isInstallBoard()) { 283 ContributionsIndexer indexer = new ContributionsIndexer( 284 BaseNoGui.getSettingsFolder(), BaseNoGui.getHardwareFolder(), 285 BaseNoGui.getPlatform(), new GPGDetachedSignatureVerifier()); 286 ProgressListener progressListener = new ConsoleProgressListener(); 287 288 List<String> downloadedPackageIndexFiles = contributionInstaller.updateIndex(progressListener); 289 contributionInstaller.deleteUnknownFiles(downloadedPackageIndexFiles); 290 indexer.parseIndex(); 291 indexer.syncWithFilesystem(); 292 293 String[] boardToInstallParts = parser.getBoardToInstall().split(":"); 294 295 ContributedPlatform selected = null; 296 if (boardToInstallParts.length == 3) { 297 selected = indexer.getIndex().findPlatform(boardToInstallParts[0], boardToInstallParts[1], VersionHelper.valueOf(boardToInstallParts[2]).toString()); 298 } else if (boardToInstallParts.length == 2) { 299 List<ContributedPlatform> platformsByName = indexer.getIndex().findPlatforms(boardToInstallParts[0], boardToInstallParts[1]); 300 Collections.sort(platformsByName, new DownloadableContributionVersionComparator()); 301 if (!platformsByName.isEmpty()) { 302 selected = platformsByName.get(platformsByName.size() - 1); 303 } 304 } 305 if (selected == null) { 306 System.out.println(tr("Selected board is not available")); 307 System.exit(1); 308 } 309 310 ContributedPlatform installed = indexer.getInstalled(boardToInstallParts[0], boardToInstallParts[1]); 311 312 if (!selected.isReadOnly()) { 313 contributionInstaller.install(selected, progressListener); 314 } 315 316 if (installed != null && !installed.isReadOnly()) { 317 contributionInstaller.remove(installed); 318 } 319 320 System.exit(0); 321 322 } else if (parser.isInstallLibrary()) { 323 BaseNoGui.onBoardOrPortChange(); 324 325 ProgressListener progressListener = new ConsoleProgressListener(); 326 libraryInstaller.updateIndex(progressListener); 327 328 LibrariesIndexer indexer = new LibrariesIndexer(BaseNoGui.getSettingsFolder()); 329 indexer.parseIndex(); 330 indexer.setSketchbookLibrariesFolder(BaseNoGui.getSketchbookLibrariesFolder()); 331 indexer.setLibrariesFolders(BaseNoGui.getLibrariesPath()); 332 333 for (String library : parser.getLibraryToInstall().split(",")) { 334 String[] libraryToInstallParts = library.split(":"); 335 336 ContributedLibrary selected = null; 337 if (libraryToInstallParts.length == 2) { 338 selected = indexer.getIndex().find(libraryToInstallParts[0], VersionHelper.valueOf(libraryToInstallParts[1]).toString()); 339 } else if (libraryToInstallParts.length == 1) { 340 List<ContributedLibrary> librariesByName = indexer.getIndex().find(libraryToInstallParts[0]); 341 Collections.sort(librariesByName, new DownloadableContributionVersionComparator()); 342 if (!librariesByName.isEmpty()) { 343 selected = librariesByName.get(librariesByName.size() - 1); 344 } 345 } 346 if (selected == null) { 347 System.out.println(tr("Selected library is not available")); 348 System.exit(1); 349 } 350 351 ContributedLibrary installed = indexer.getIndex().getInstalled(libraryToInstallParts[0]); 352 if (selected.isReadOnly()) { 353 libraryInstaller.remove(installed, progressListener); 354 } else { 355 libraryInstaller.install(selected, installed, progressListener); 356 } 357 } 358 359 System.exit(0); 360 361 } else if (parser.isVerifyOrUploadMode()) { 362 // Set verbosity for command line build 363 PreferencesData.setBoolean("build.verbose", parser.isDoVerboseBuild()); 364 PreferencesData.setBoolean("upload.verbose", parser.isDoVerboseUpload()); 365 366 // Set preserve-temp flag 367 PreferencesData.setBoolean("runtime.preserve.temp.files", parser.isPreserveTempFiles()); 368 369 // Make sure these verbosity preferences are only for the current session 370 PreferencesData.setDoSave(false); 371 372 Sketch sketch = null; 373 String outputFile = null; 374 375 try { 376 // Build 377 splash.splashText(tr("Verifying...")); 378 379 File sketchFile = BaseNoGui.absoluteFile(parser.getFilenames().get(0)); 380 sketch = new Sketch(sketchFile); 381 382 outputFile = new Compiler(sketch).build(progress -> {}, false); 383 } catch (Exception e) { 384 // Error during build 385 System.exit(1); 386 } 387 388 if (parser.isUploadMode()) { 389 // Upload 390 splash.splashText(tr("Uploading...")); 391 392 try { 393 List<String> warnings = new ArrayList<>(); 394 UploaderUtils uploader = new UploaderUtils(); 395 boolean res = uploader.upload(sketch, null, outputFile, 396 parser.isDoUseProgrammer(), 397 parser.isNoUploadPort(), warnings); 398 for (String warning : warnings) { 399 System.out.println(tr("Warning") + ": " + warning); 400 } 401 if (!res) { 402 throw new Exception(); 403 } 404 } catch (Exception e) { 405 // Error during upload 406 System.out.flush(); 407 System.err.flush(); 408 System.err 409 .println(tr("An error occurred while uploading the sketch")); 410 System.exit(1); 411 } 412 } 413 414 // No errors exit gracefully 415 System.exit(0); 416 } else if (parser.isGuiMode()) { 417 splash.splashText(tr("Starting...")); 418 419 for (String path : parser.getFilenames()) { 420 // Correctly resolve relative paths 421 File file = absoluteFile(path); 422 423 // Fix a problem with systems that use a non-ASCII languages. Paths are 424 // being passed in with 8.3 syntax, which makes the sketch loader code 425 // unhappy, since the sketch folder naming doesn't match up correctly. 426 // http://dev.processing.org/bugs/show_bug.cgi?id=1089 427 if (OSUtils.isWindows()) { 428 try { 429 file = file.getCanonicalFile(); 430 } catch (IOException e) { 431 e.printStackTrace(); 432 } 433 } 434 435 if (!parser.isForceSavePrefs()) 436 PreferencesData.setDoSave(true); 437 if (handleOpen(file, retrieveSketchLocation(".default"), false) == null) { 438 String mess = I18n.format(tr("Failed to open sketch: \"{0}\""), path); 439 // Open failure is fatal in upload/verify mode 440 if (parser.isVerifyOrUploadMode()) 441 showError(null, mess, 2); 442 else 443 showWarning(null, mess, null); 444 } 445 } 446 447 installKeyboardInputMap(); 448 449 // Check if there were previously opened sketches to be restored 450 restoreSketches(); 451 452 // Create a new empty window (will be replaced with any files to be opened) 453 if (editors.isEmpty()) { 454 handleNew(); 455 } 456 457 new Thread(new BuiltInCoreIsNewerCheck(this)).start(); 458 459 // Check for boards which need an additional core 460 new Thread(new NewBoardListener(this)).start(); 461 462 // Check for updates 463 if (PreferencesData.getBoolean("update.check")) { 464 new UpdateCheck(this); 465 466 contributionsSelfCheck = new ContributionsSelfCheck(this, new UpdatableBoardsLibsFakeURLsHandler(this), contributionInstaller, libraryInstaller); 467 new Timer(false).schedule(contributionsSelfCheck, Constants.BOARDS_LIBS_UPDATABLE_CHECK_START_PERIOD); 468 } 469 470 } else if (parser.isNoOpMode()) { 471 // Do nothing (intended for only changing preferences) 472 System.exit(0); 473 } else if (parser.isGetPrefMode()) { 474 BaseNoGui.dumpPrefs(parser); 475 } 476 } 477 installKeyboardInputMap()478 private void installKeyboardInputMap() { 479 UIManager.put("RSyntaxTextAreaUI.inputMap", new SketchTextAreaDefaultInputMap()); 480 } 481 482 /** 483 * Post-constructor setup for the editor area. Loads the last 484 * sketch that was used (if any), and restores other Editor settings. 485 * The complement to "storePreferences", this is called when the 486 * application is first launched. 487 * 488 * @throws Exception 489 */ restoreSketches()490 protected boolean restoreSketches() throws Exception { 491 // Iterate through all sketches that were open last time p5 was running. 492 // If !windowPositionValid, then ignore the coordinates found for each. 493 494 // Save the sketch path and window placement for each open sketch 495 int count = PreferencesData.getInteger("last.sketch.count"); 496 int opened = 0; 497 for (int i = count - 1; i >= 0; i--) { 498 String path = PreferencesData.get("last.sketch" + i + ".path"); 499 if (path == null) { 500 continue; 501 } 502 if (BaseNoGui.getPortableFolder() != null && !new File(path).isAbsolute()) { 503 File absolute = new File(BaseNoGui.getPortableFolder(), path); 504 try { 505 path = absolute.getCanonicalPath(); 506 } catch (IOException e) { 507 // path unchanged. 508 } 509 } 510 int[] location = retrieveSketchLocation("" + i); 511 // If file did not exist, null will be returned for the Editor 512 if (handleOpen(new File(path), location, nextEditorLocation(), false, false) != null) { 513 opened++; 514 } 515 } 516 return (opened > 0); 517 } 518 519 520 /** 521 * Store list of sketches that are currently open. 522 * Called when the application is quitting and documents are still open. 523 */ storeSketches()524 protected void storeSketches() { 525 // Save the width and height of the screen 526 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); 527 PreferencesData.setInteger("last.screen.width", screen.width); 528 PreferencesData.setInteger("last.screen.height", screen.height); 529 530 // If there is only one sketch opened save his position as default 531 if (editors.size() == 1) { 532 storeSketchLocation(editors.get(0), ".default"); 533 } 534 535 // Save the sketch path and window placement for each open sketch 536 String untitledPath = untitledFolder.getAbsolutePath(); 537 List<Editor> reversedEditors = new LinkedList<>(editors); 538 Collections.reverse(reversedEditors); 539 int index = 0; 540 for (Editor editor : reversedEditors) { 541 Sketch sketch = editor.getSketch(); 542 String path = sketch.getMainFilePath(); 543 // Skip untitled sketches if they do not contains changes. 544 if (path.startsWith(untitledPath) && !sketch.isModified()) { 545 continue; 546 } 547 storeSketchLocation(editor, "" + index); 548 index++; 549 } 550 PreferencesData.setInteger("last.sketch.count", index); 551 } 552 storeSketchLocation(Editor editor, String index)553 private void storeSketchLocation(Editor editor, String index) { 554 String path = editor.getSketch().getMainFilePath(); 555 String loc = StringUtils.join(editor.getPlacement(), ','); 556 PreferencesData.set("last.sketch" + index + ".path", path); 557 PreferencesData.set("last.sketch" + index + ".location", loc); 558 } 559 retrieveSketchLocation(String index)560 private int[] retrieveSketchLocation(String index) { 561 if (PreferencesData.get("last.screen.height") == null) 562 return defaultEditorLocation(); 563 564 // if screen size has changed, the window coordinates no longer 565 // make sense, so don't use them unless they're identical 566 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); 567 int screenW = PreferencesData.getInteger("last.screen.width"); 568 int screenH = PreferencesData.getInteger("last.screen.height"); 569 570 if ((screen.width != screenW) || (screen.height != screenH)) 571 return defaultEditorLocation(); 572 573 String locationStr = PreferencesData 574 .get("last.sketch" + index + ".location"); 575 if (locationStr == null) 576 return defaultEditorLocation(); 577 return PApplet.parseInt(PApplet.split(locationStr, ',')); 578 } 579 storeRecentSketches(SketchController sketch)580 protected void storeRecentSketches(SketchController sketch) { 581 if (sketch.isUntitled()) { 582 return; 583 } 584 585 Set<String> sketches = new LinkedHashSet<>(); 586 sketches.add(sketch.getSketch().getMainFilePath()); 587 sketches.addAll(PreferencesData.getCollection("recent.sketches")); 588 589 PreferencesData.setCollection("recent.sketches", sketches); 590 } 591 removeRecentSketchPath(String path)592 protected void removeRecentSketchPath(String path) { 593 Collection<String> sketches = new LinkedList<>(PreferencesData.getCollection("recent.sketches")); 594 sketches.remove(path); 595 PreferencesData.setCollection("recent.sketches", sketches); 596 } 597 598 // Because of variations in native windowing systems, no guarantees about 599 // changes to the focused and active Windows can be made. Developers must 600 // never assume that this Window is the focused or active Window until this 601 // Window receives a WINDOW_GAINED_FOCUS or WINDOW_ACTIVATED event. handleActivated(Editor whichEditor)602 protected void handleActivated(Editor whichEditor) { 603 activeEditor = whichEditor; 604 activeEditor.rebuildRecentSketchesMenu(); 605 if (PreferencesData.getBoolean("editor.external")) { 606 try { 607 // If the list of files on disk changed, recreate the tabs for them 608 if (activeEditor.getSketch().reload()) 609 activeEditor.createTabs(); 610 else // Let the current tab know it was activated, so it can reload 611 activeEditor.getCurrentTab().activated(); 612 } catch (IOException e) { 613 System.err.println(e); 614 } 615 } 616 617 // set the current window to be the console that's getting output 618 EditorConsole.setCurrentEditorConsole(activeEditor.console); 619 } 620 defaultEditorLocation()621 protected int[] defaultEditorLocation() { 622 int defaultWidth = PreferencesData.getInteger("editor.window.width.default"); 623 int defaultHeight = PreferencesData.getInteger("editor.window.height.default"); 624 Rectangle screen = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration().getBounds(); 625 return new int[]{ 626 (screen.width - defaultWidth) / 2, 627 (screen.height - defaultHeight) / 2, 628 defaultWidth, defaultHeight, 0 629 }; 630 } 631 nextEditorLocation()632 protected int[] nextEditorLocation() { 633 if (activeEditor == null) { 634 // If no current active editor, use default placement 635 return defaultEditorLocation(); 636 } 637 638 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); 639 640 // With a currently active editor, open the new window 641 // using the same dimensions, but offset slightly. 642 synchronized (editors) { 643 int[] location = activeEditor.getPlacement(); 644 645 // Just in case the bounds for that window are bad 646 final int OVER = 50; 647 location[0] += OVER; 648 location[1] += OVER; 649 650 if (location[0] == OVER || location[2] == OVER 651 || location[0] + location[2] > screen.width 652 || location[1] + location[3] > screen.height) { 653 // Warp the next window to a randomish location on screen. 654 int[] l = defaultEditorLocation(); 655 l[0] *= Math.random() * 2; 656 l[1] *= Math.random() * 2; 657 return l; 658 } 659 660 return location; 661 } 662 } 663 664 665 // ................................................................. 666 667 668 boolean breakTime = false; 669 String[] months = { 670 "jan", "feb", "mar", "apr", "may", "jun", 671 "jul", "aug", "sep", "oct", "nov", "dec" 672 }; 673 createNewUntitled()674 protected File createNewUntitled() throws IOException { 675 File newbieDir = null; 676 String newbieName = null; 677 678 // In 0126, untitled sketches will begin in the temp folder, 679 // and then moved to a new location because Save will default to Save As. 680 File sketchbookDir = BaseNoGui.getSketchbookFolder(); 681 File newbieParentDir = untitledFolder; 682 683 // Use a generic name like sketch_031008a, the date plus a char 684 int index = 0; 685 //SimpleDateFormat formatter = new SimpleDateFormat("yyMMdd"); 686 //SimpleDateFormat formatter = new SimpleDateFormat("MMMdd"); 687 //String purty = formatter.format(new Date()).toLowerCase(); 688 Calendar cal = Calendar.getInstance(); 689 int day = cal.get(Calendar.DAY_OF_MONTH); // 1..31 690 int month = cal.get(Calendar.MONTH); // 0..11 691 String purty = months[month] + PApplet.nf(day, 2); 692 693 do { 694 if (index == 26*26) { 695 // In 0166, avoid running past zz by sending people outdoors. 696 if (!breakTime) { 697 showWarning(tr("Time for a Break"), 698 tr("You've reached the limit for auto naming of new sketches\n" + 699 "for the day. How about going for a walk instead?"), null); 700 breakTime = true; 701 } else { 702 showWarning(tr("Sunshine"), 703 tr("No really, time for some fresh air for you."), null); 704 } 705 return null; 706 } 707 708 int multiples = index / 26; 709 710 if(multiples > 0){ 711 newbieName = ((char) ('a' + (multiples-1))) + "" + ((char) ('a' + (index % 26))) + ""; 712 }else{ 713 newbieName = ((char) ('a' + index)) + ""; 714 } 715 newbieName = "sketch_" + purty + newbieName; 716 newbieDir = new File(newbieParentDir, newbieName); 717 index++; 718 // Make sure it's not in the temp folder *and* it's not in the sketchbook 719 } while (newbieDir.exists() || new File(sketchbookDir, newbieName).exists()); 720 721 // Make the directory for the new sketch 722 newbieDir.mkdirs(); 723 724 // Make an empty pde file 725 File newbieFile = new File(newbieDir, newbieName + ".ino"); 726 if (!newbieFile.createNewFile()) { 727 throw new IOException(); 728 } 729 FileUtils.copyFile(new File(getContentFile("examples"), "01.Basics" + File.separator + "BareMinimum" + File.separator + "BareMinimum.ino"), newbieFile); 730 return newbieFile; 731 } 732 733 734 /** 735 * Create a new untitled document in a new sketch window. 736 * 737 * @throws Exception 738 */ handleNew()739 public void handleNew() throws Exception { 740 try { 741 File file = createNewUntitled(); 742 if (file != null) { 743 handleOpen(file, true); 744 } 745 746 } catch (IOException e) { 747 if (activeEditor != null) { 748 activeEditor.statusError(e); 749 } 750 } 751 } 752 753 754 /** 755 * Prompt for a sketch to open, and open it in a new window. 756 * 757 * @throws Exception 758 */ handleOpenPrompt()759 public void handleOpenPrompt() throws Exception { 760 // get the frontmost window frame for placing file dialog 761 FileDialog fd = new FileDialog(activeEditor, tr("Open an Arduino sketch..."), FileDialog.LOAD); 762 File lastFolder = new File(PreferencesData.get("last.folder", BaseNoGui.getSketchbookFolder().getAbsolutePath())); 763 if (lastFolder.exists() && lastFolder.isFile()) { 764 lastFolder = lastFolder.getParentFile(); 765 } 766 fd.setDirectory(lastFolder.getAbsolutePath()); 767 768 // Only show .pde files as eligible bachelors 769 fd.setFilenameFilter(new FilenameFilter() { 770 public boolean accept(File dir, String name) { 771 return name.toLowerCase().endsWith(".ino") 772 || name.toLowerCase().endsWith(".pde"); 773 } 774 }); 775 776 fd.setVisible(true); 777 778 String directory = fd.getDirectory(); 779 String filename = fd.getFile(); 780 781 // User canceled selection 782 if (filename == null) return; 783 784 File inputFile = new File(directory, filename); 785 786 PreferencesData.set("last.folder", inputFile.getAbsolutePath()); 787 handleOpen(inputFile); 788 } 789 790 791 /** 792 * Open a sketch in a new window. 793 * 794 * @param file File to open 795 * @return the Editor object, so that properties (like 'untitled') 796 * can be set by the caller 797 * @throws Exception 798 */ handleOpen(File file)799 public Editor handleOpen(File file) throws Exception { 800 return handleOpen(file, false); 801 } 802 handleOpen(File file, boolean untitled)803 public Editor handleOpen(File file, boolean untitled) throws Exception { 804 return handleOpen(file, nextEditorLocation(), untitled); 805 } 806 handleOpen(File file, int[] location, boolean untitled)807 protected Editor handleOpen(File file, int[] location, boolean untitled) throws Exception { 808 return handleOpen(file, location, location, true, untitled); 809 } 810 handleOpen(File file, int[] storedLocation, int[] defaultLocation, boolean storeOpenedSketches, boolean untitled)811 protected Editor handleOpen(File file, int[] storedLocation, int[] defaultLocation, boolean storeOpenedSketches, boolean untitled) throws Exception { 812 if (!file.exists()) return null; 813 814 // Cycle through open windows to make sure that it's not already open. 815 for (Editor editor : editors) { 816 if (editor.getSketch().getPrimaryFile().getFile().equals(file)) { 817 editor.toFront(); 818 return editor; 819 } 820 } 821 822 Editor editor = new Editor(this, file, storedLocation, defaultLocation, BaseNoGui.getPlatform()); 823 824 // Make sure that the sketch actually loaded 825 if (editor.getSketchController() == null) { 826 return null; // Just walk away quietly 827 } 828 829 editor.untitled = untitled; 830 831 editors.add(editor); 832 833 if (storeOpenedSketches) { 834 // Store information on who's open and running 835 // (in case there's a crash or something that can't be recovered) 836 storeSketches(); 837 storeRecentSketches(editor.getSketchController()); 838 rebuildRecentSketchesMenuItems(); 839 PreferencesData.save(); 840 } 841 842 // now that we're ready, show the window 843 // (don't do earlier, cuz we might move it based on a window being closed) 844 SwingUtilities.invokeLater(() -> editor.setVisible(true)); 845 846 return editor; 847 } 848 rebuildRecentSketchesMenuItems()849 protected void rebuildRecentSketchesMenuItems() { 850 Set<File> recentSketches = new LinkedHashSet<File>() { 851 852 @Override 853 public boolean add(File file) { 854 if (size() >= RECENT_SKETCHES_MAX_SIZE) { 855 return false; 856 } 857 return super.add(file); 858 } 859 }; 860 861 for (String path : PreferencesData.getCollection("recent.sketches")) { 862 File file = new File(path); 863 if (file.exists()) { 864 recentSketches.add(file); 865 } 866 } 867 868 recentSketchesMenuItems.clear(); 869 for (final File recentSketch : recentSketches) { 870 JMenuItem recentSketchMenuItem = new JMenuItem(recentSketch.getParentFile().getName()); 871 recentSketchMenuItem.addActionListener(new ActionListener() { 872 @Override 873 public void actionPerformed(ActionEvent actionEvent) { 874 try { 875 handleOpen(recentSketch); 876 } catch (Exception e) { 877 e.printStackTrace(); 878 } 879 } 880 }); 881 recentSketchesMenuItems.add(recentSketchMenuItem); 882 } 883 } 884 885 886 /** 887 * Close a sketch as specified by its editor window. 888 * 889 * @param editor Editor object of the sketch to be closed. 890 * @return true if succeeded in closing, false if canceled. 891 */ handleClose(Editor editor)892 public boolean handleClose(Editor editor) { 893 // Check if modified 894 // boolean immediate = editors.size() == 1; 895 if (!editor.checkModified()) { 896 return false; 897 } 898 899 if (editors.size() == 1) { 900 storeSketches(); 901 902 // This will store the sketch count as zero 903 editors.remove(editor); 904 try { 905 Editor.serialMonitor.close(); 906 } catch (Exception e) { 907 //ignore 908 } 909 rebuildRecentSketchesMenuItems(); 910 911 // Save out the current prefs state 912 PreferencesData.save(); 913 914 // Since this wasn't an actual Quit event, call System.exit() 915 System.exit(0); 916 917 } else { 918 // More than one editor window open, 919 // proceed with closing the current window. 920 editor.setVisible(false); 921 editor.dispose(); 922 // for (int i = 0; i < editorCount; i++) { 923 // if (editor == editors[i]) { 924 // for (int j = i; j < editorCount-1; j++) { 925 // editors[j] = editors[j+1]; 926 // } 927 // editorCount--; 928 // // Set to null so that garbage collection occurs 929 // editors[editorCount] = null; 930 // } 931 // } 932 editors.remove(editor); 933 } 934 return true; 935 } 936 937 938 /** 939 * Handler for File → Quit. 940 * 941 * @return false if canceled, true otherwise. 942 */ handleQuit()943 public boolean handleQuit() { 944 // If quit is canceled, this will be replaced anyway 945 // by a later handleQuit() that is not canceled. 946 storeSketches(); 947 try { 948 Editor.serialMonitor.close(); 949 } catch (Exception e) { 950 // ignore 951 } 952 953 if (handleQuitEach()) { 954 // Save out the current prefs state 955 PreferencesData.save(); 956 957 if (!OSUtils.isMacOS()) { 958 // If this was fired from the menu or an AppleEvent (the Finder), 959 // then Mac OS X will send the terminate signal itself. 960 System.exit(0); 961 } 962 return true; 963 } 964 return false; 965 } 966 967 968 /** 969 * Attempt to close each open sketch in preparation for quitting. 970 * 971 * @return false if canceled along the way 972 */ handleQuitEach()973 protected boolean handleQuitEach() { 974 for (Editor editor : editors) { 975 if (!editor.checkModified()) { 976 return false; 977 } 978 } 979 return true; 980 } 981 982 983 // ................................................................. 984 985 986 /** 987 * Asynchronous version of menu rebuild to be used on save and rename 988 * to prevent the interface from locking up until the menus are done. 989 */ rebuildSketchbookMenus()990 public void rebuildSketchbookMenus() { 991 //System.out.println("async enter"); 992 //new Exception().printStackTrace(); 993 SwingUtilities.invokeLater(new Runnable() { 994 public void run() { 995 //System.out.println("starting rebuild"); 996 rebuildSketchbookMenu(Editor.sketchbookMenu); 997 rebuildToolbarMenu(Editor.toolbarMenu); 998 //System.out.println("done with rebuild"); 999 } 1000 }); 1001 //System.out.println("async exit"); 1002 } 1003 1004 rebuildToolbarMenu(JMenu menu)1005 protected void rebuildToolbarMenu(JMenu menu) { 1006 JMenuItem item; 1007 menu.removeAll(); 1008 1009 // Add the single "Open" item 1010 item = Editor.newJMenuItem(tr("Open..."), 'O'); 1011 item.addActionListener(new ActionListener() { 1012 public void actionPerformed(ActionEvent e) { 1013 try { 1014 handleOpenPrompt(); 1015 } catch (Exception e1) { 1016 e1.printStackTrace(); 1017 } 1018 } 1019 }); 1020 menu.add(item); 1021 menu.addSeparator(); 1022 1023 // Add a list of all sketches and subfolders 1024 boolean sketches = addSketches(menu, BaseNoGui.getSketchbookFolder()); 1025 if (sketches) menu.addSeparator(); 1026 1027 // Add each of the subfolders of examples directly to the menu 1028 boolean found = addSketches(menu, BaseNoGui.getExamplesFolder()); 1029 if (found) menu.addSeparator(); 1030 } 1031 1032 rebuildSketchbookMenu(JMenu menu)1033 protected void rebuildSketchbookMenu(JMenu menu) { 1034 menu.removeAll(); 1035 addSketches(menu, BaseNoGui.getSketchbookFolder()); 1036 1037 JMenu librariesMenu = JMenuUtils.findSubMenuWithLabel(menu, "libraries"); 1038 if (librariesMenu != null) { 1039 menu.remove(librariesMenu); 1040 } 1041 JMenu hardwareMenu = JMenuUtils.findSubMenuWithLabel(menu, "hardware"); 1042 if (hardwareMenu != null) { 1043 menu.remove(hardwareMenu); 1044 } 1045 } 1046 getSortedLibraries()1047 private List<ContributedLibrary> getSortedLibraries() { 1048 List<ContributedLibrary> installedLibraries = new LinkedList<>(BaseNoGui.librariesIndexer.getInstalledLibraries()); 1049 Collections.sort(installedLibraries, new LibraryByTypeComparator()); 1050 Collections.sort(installedLibraries, new LibraryOfSameTypeComparator()); 1051 return installedLibraries; 1052 } 1053 rebuildImportMenu(JMenu importMenu)1054 public void rebuildImportMenu(JMenu importMenu) { 1055 if (importMenu == null) 1056 return; 1057 importMenu.removeAll(); 1058 1059 JMenuItem menu = new JMenuItem(tr("Manage Libraries...")); 1060 menu.addActionListener(e -> openLibraryManager("", "")); 1061 importMenu.add(menu); 1062 importMenu.addSeparator(); 1063 1064 JMenuItem addLibraryMenuItem = new JMenuItem(tr("Add .ZIP Library...")); 1065 addLibraryMenuItem.addActionListener(new ActionListener() { 1066 public void actionPerformed(ActionEvent e) { 1067 Base.this.handleAddLibrary(); 1068 Base.this.onBoardOrPortChange(); 1069 Base.this.rebuildImportMenu(Editor.importMenu); 1070 Base.this.rebuildExamplesMenu(Editor.examplesMenu); 1071 } 1072 }); 1073 importMenu.add(addLibraryMenuItem); 1074 importMenu.addSeparator(); 1075 1076 // Split between user supplied libraries and IDE libraries 1077 TargetPlatform targetPlatform = BaseNoGui.getTargetPlatform(); 1078 1079 if (targetPlatform != null) { 1080 List<ContributedLibrary> libs = getSortedLibraries(); 1081 String lastLibType = null; 1082 for (ContributedLibrary lib : libs) { 1083 if (lastLibType == null || !lastLibType.equals(lib.getTypes().get(0))) { 1084 if (lastLibType != null) { 1085 importMenu.addSeparator(); 1086 } 1087 lastLibType = lib.getTypes().get(0); 1088 JMenuItem platformItem = new JMenuItem(I18n.format(tr("{0} libraries"), lastLibType)); 1089 platformItem.setEnabled(false); 1090 importMenu.add(platformItem); 1091 } 1092 1093 AbstractAction action = new AbstractAction(lib.getName()) { 1094 public void actionPerformed(ActionEvent event) { 1095 UserLibrary l = (UserLibrary) getValue("library"); 1096 try { 1097 activeEditor.getSketchController().importLibrary(l); 1098 } catch (IOException e) { 1099 showWarning(tr("Error"), I18n.format("Unable to list header files in {0}", l.getSrcFolder()), e); 1100 } 1101 } 1102 }; 1103 action.putValue("library", lib); 1104 1105 // Add new element at the bottom 1106 JMenuItem item = new JMenuItem(action); 1107 item.putClientProperty("library", lib); 1108 importMenu.add(item); 1109 } 1110 } 1111 } 1112 rebuildExamplesMenu(JMenu menu)1113 public void rebuildExamplesMenu(JMenu menu) { 1114 if (menu == null) { 1115 return; 1116 } 1117 1118 menu.removeAll(); 1119 1120 // Add examples from distribution "example" folder 1121 JMenuItem label = new JMenuItem(tr("Built-in Examples")); 1122 label.setEnabled(false); 1123 menu.add(label); 1124 boolean found = addSketches(menu, BaseNoGui.getExamplesFolder()); 1125 if (found) { 1126 menu.addSeparator(); 1127 } 1128 1129 // Libraries can come from 4 locations: collect info about all four 1130 File ideLibraryPath = BaseNoGui.getContentFile("libraries"); 1131 File sketchbookLibraryPath = BaseNoGui.getSketchbookLibrariesFolder(); 1132 File platformLibraryPath = null; 1133 File referencedPlatformLibraryPath = null; 1134 String boardId = null; 1135 String referencedPlatformName = null; 1136 String myArch = null; 1137 TargetPlatform targetPlatform = BaseNoGui.getTargetPlatform(); 1138 if (targetPlatform != null) { 1139 myArch = targetPlatform.getId(); 1140 boardId = BaseNoGui.getTargetBoard().getName(); 1141 platformLibraryPath = new File(targetPlatform.getFolder(), "libraries"); 1142 String core = BaseNoGui.getBoardPreferences().get("build.core", "arduino"); 1143 if (core.contains(":")) { 1144 String refcore = core.split(":")[0]; 1145 TargetPlatform referencedPlatform = BaseNoGui.getTargetPlatform(refcore, myArch); 1146 if (referencedPlatform != null) { 1147 referencedPlatformName = referencedPlatform.getPreferences().get("name"); 1148 referencedPlatformLibraryPath = new File(referencedPlatform.getFolder(), "libraries"); 1149 } 1150 } 1151 } 1152 1153 // Divide the libraries into 7 lists, corresponding to the 4 locations 1154 // with the retired IDE libs further divided into their own list, and 1155 // any incompatible sketchbook libs further divided into their own list. 1156 // The 7th list of "other" libraries should always be empty, but serves 1157 // as a safety feature to prevent any library from vanishing. 1158 LibraryList allLibraries = new LibraryList(BaseNoGui.librariesIndexer.getInstalledLibraries()); 1159 LibraryList ideLibs = new LibraryList(); 1160 LibraryList retiredIdeLibs = new LibraryList(); 1161 LibraryList platformLibs = new LibraryList(); 1162 LibraryList referencedPlatformLibs = new LibraryList(); 1163 LibraryList sketchbookLibs = new LibraryList(); 1164 LibraryList sketchbookIncompatibleLibs = new LibraryList(); 1165 LibraryList otherLibs = new LibraryList(); 1166 for (UserLibrary lib : allLibraries) { 1167 // Get the library's location - used for sorting into categories 1168 File libraryLocation = lib.getInstalledFolder().getParentFile(); 1169 // Is this library compatible? 1170 List<String> arch = lib.getArchitectures(); 1171 boolean compatible; 1172 if (myArch == null || arch == null || arch.contains("*")) { 1173 compatible = true; 1174 } else { 1175 compatible = arch.contains(myArch); 1176 } 1177 // IDE Libaries (including retired) 1178 if (libraryLocation.equals(ideLibraryPath)) { 1179 if (compatible) { 1180 // only compatible IDE libs are shown 1181 boolean retired = false; 1182 List<String> types = lib.getTypes(); 1183 if (types != null) retired = types.contains("Retired"); 1184 if (retired) { 1185 retiredIdeLibs.add(lib); 1186 } else { 1187 ideLibs.add(lib); 1188 } 1189 } 1190 // Platform Libraries 1191 } else if (libraryLocation.equals(platformLibraryPath)) { 1192 // all platform libs are assumed to be compatible 1193 platformLibs.add(lib); 1194 // Referenced Platform Libraries 1195 } else if (libraryLocation.equals(referencedPlatformLibraryPath)) { 1196 // all referenced platform libs are assumed to be compatible 1197 referencedPlatformLibs.add(lib); 1198 // Sketchbook Libraries (including incompatible) 1199 } else if (libraryLocation.equals(sketchbookLibraryPath)) { 1200 if (compatible) { 1201 // libraries promoted from sketchbook (behave as builtin) 1202 if (lib.getTypes() != null && lib.getTypes().contains("Arduino") 1203 && lib.getArchitectures().contains("*")) { 1204 ideLibs.add(lib); 1205 } else { 1206 sketchbookLibs.add(lib); 1207 } 1208 } else { 1209 sketchbookIncompatibleLibs.add(lib); 1210 } 1211 // Other libraries of unknown type (should never occur) 1212 } else { 1213 otherLibs.add(lib); 1214 } 1215 } 1216 1217 // Add examples from libraries 1218 if (!ideLibs.isEmpty()) { 1219 ideLibs.sort(); 1220 label = new JMenuItem(tr("Examples for any board")); 1221 label.setEnabled(false); 1222 menu.add(label); 1223 } 1224 for (UserLibrary lib : ideLibs) { 1225 addSketchesSubmenu(menu, lib); 1226 } 1227 1228 if (!retiredIdeLibs.isEmpty()) { 1229 retiredIdeLibs.sort(); 1230 JMenu retired = new JMenu(tr("RETIRED")); 1231 menu.add(retired); 1232 for (UserLibrary lib : retiredIdeLibs) { 1233 addSketchesSubmenu(retired, lib); 1234 } 1235 } 1236 1237 if (!platformLibs.isEmpty()) { 1238 menu.addSeparator(); 1239 platformLibs.sort(); 1240 label = new JMenuItem(I18n.format(tr("Examples for {0}"), boardId)); 1241 label.setEnabled(false); 1242 menu.add(label); 1243 for (UserLibrary lib : platformLibs) { 1244 addSketchesSubmenu(menu, lib); 1245 } 1246 } 1247 1248 if (!referencedPlatformLibs.isEmpty()) { 1249 menu.addSeparator(); 1250 referencedPlatformLibs.sort(); 1251 label = new JMenuItem(I18n.format(tr("Examples for {0}"), referencedPlatformName)); 1252 label.setEnabled(false); 1253 menu.add(label); 1254 for (UserLibrary lib : referencedPlatformLibs) { 1255 addSketchesSubmenu(menu, lib); 1256 } 1257 } 1258 1259 if (!sketchbookLibs.isEmpty()) { 1260 menu.addSeparator(); 1261 sketchbookLibs.sort(); 1262 label = new JMenuItem(tr("Examples from Custom Libraries")); 1263 label.setEnabled(false); 1264 menu.add(label); 1265 for (UserLibrary lib : sketchbookLibs) { 1266 addSketchesSubmenu(menu, lib); 1267 } 1268 } 1269 1270 if (!sketchbookIncompatibleLibs.isEmpty()) { 1271 sketchbookIncompatibleLibs.sort(); 1272 JMenu incompatible = new JMenu(tr("INCOMPATIBLE")); 1273 menu.add(incompatible); 1274 for (UserLibrary lib : sketchbookIncompatibleLibs) { 1275 addSketchesSubmenu(incompatible, lib); 1276 } 1277 } 1278 1279 if (!otherLibs.isEmpty()) { 1280 menu.addSeparator(); 1281 otherLibs.sort(); 1282 label = new JMenuItem(tr("Examples from Other Libraries")); 1283 label.setEnabled(false); 1284 menu.add(label); 1285 for (UserLibrary lib : otherLibs) { 1286 addSketchesSubmenu(menu, lib); 1287 } 1288 } 1289 } 1290 1291 private static String priorPlatformFolder; 1292 private static boolean newLibraryImported; 1293 onBoardOrPortChange()1294 public void onBoardOrPortChange() { 1295 BaseNoGui.onBoardOrPortChange(); 1296 1297 // reload keywords when package/platform changes 1298 TargetPlatform tp = BaseNoGui.getTargetPlatform(); 1299 if (tp != null) { 1300 String platformFolder = tp.getFolder().getAbsolutePath(); 1301 if (priorPlatformFolder == null || !priorPlatformFolder.equals(platformFolder) || newLibraryImported) { 1302 pdeKeywords = new PdeKeywords(); 1303 pdeKeywords.reload(); 1304 priorPlatformFolder = platformFolder; 1305 newLibraryImported = false; 1306 for (Editor editor : editors) { 1307 editor.updateKeywords(pdeKeywords); 1308 } 1309 } 1310 } 1311 1312 // Update editors status bar 1313 for (Editor editor : editors) { 1314 editor.onBoardOrPortChange(); 1315 } 1316 } 1317 openLibraryManager(final String filterText, String dropdownItem)1318 public void openLibraryManager(final String filterText, String dropdownItem) { 1319 if (contributionsSelfCheck != null) { 1320 contributionsSelfCheck.cancel(); 1321 } 1322 @SuppressWarnings("serial") 1323 LibraryManagerUI managerUI = new LibraryManagerUI(activeEditor, libraryInstaller) { 1324 @Override 1325 protected void onIndexesUpdated() throws Exception { 1326 BaseNoGui.initPackages(); 1327 rebuildBoardsMenu(); 1328 rebuildProgrammerMenu(); 1329 onBoardOrPortChange(); 1330 updateUI(); 1331 if (StringUtils.isNotEmpty(dropdownItem)) { 1332 selectDropdownItemByClassName(dropdownItem); 1333 } 1334 if (StringUtils.isNotEmpty(filterText)) { 1335 setFilterText(filterText); 1336 } 1337 } 1338 }; 1339 managerUI.setLocationRelativeTo(activeEditor); 1340 managerUI.updateUI(); 1341 managerUI.setVisible(true); 1342 // Manager dialog is modal, waits here until closed 1343 1344 //handleAddLibrary(); 1345 newLibraryImported = true; 1346 onBoardOrPortChange(); 1347 rebuildImportMenu(Editor.importMenu); 1348 rebuildExamplesMenu(Editor.examplesMenu); 1349 } 1350 openBoardsManager(final String filterText, String dropdownItem)1351 public void openBoardsManager(final String filterText, String dropdownItem) throws Exception { 1352 if (contributionsSelfCheck != null) { 1353 contributionsSelfCheck.cancel(); 1354 } 1355 @SuppressWarnings("serial") 1356 ContributionManagerUI managerUI = new ContributionManagerUI(activeEditor, contributionInstaller) { 1357 @Override 1358 protected void onIndexesUpdated() throws Exception { 1359 BaseNoGui.initPackages(); 1360 rebuildBoardsMenu(); 1361 rebuildProgrammerMenu(); 1362 updateUI(); 1363 if (StringUtils.isNotEmpty(dropdownItem)) { 1364 selectDropdownItemByClassName(dropdownItem); 1365 } 1366 if (StringUtils.isNotEmpty(filterText)) { 1367 setFilterText(filterText); 1368 } 1369 } 1370 }; 1371 managerUI.setLocationRelativeTo(activeEditor); 1372 managerUI.updateUI(); 1373 managerUI.setVisible(true); 1374 // Installer dialog is modal, waits here until closed 1375 1376 // Reload all boards (that may have been installed/updated/removed) 1377 BaseNoGui.initPackages(); 1378 rebuildBoardsMenu(); 1379 rebuildProgrammerMenu(); 1380 onBoardOrPortChange(); 1381 } 1382 rebuildBoardsMenu()1383 public void rebuildBoardsMenu() throws Exception { 1384 boardsCustomMenus = new LinkedList<>(); 1385 1386 // The first custom menu is the "Board" selection submenu 1387 JMenu boardMenu = new JMenu(tr("Board")); 1388 boardMenu.putClientProperty("removeOnWindowDeactivation", true); 1389 MenuScroller.setScrollerFor(boardMenu); 1390 1391 boardMenu.add(new JMenuItem(new AbstractAction(tr("Boards Manager...")) { 1392 public void actionPerformed(ActionEvent actionevent) { 1393 String filterText = ""; 1394 String dropdownItem = ""; 1395 if (actionevent instanceof Event) { 1396 filterText = ((Event) actionevent).getPayload().get("filterText").toString(); 1397 dropdownItem = ((Event) actionevent).getPayload().get("dropdownItem").toString(); 1398 } 1399 try { 1400 openBoardsManager(filterText, dropdownItem); 1401 } catch (Exception e) { 1402 //TODO show error 1403 e.printStackTrace(); 1404 } 1405 } 1406 })); 1407 boardsCustomMenus.add(boardMenu); 1408 1409 // If there are no platforms installed we are done 1410 if (BaseNoGui.packages.size() == 0) 1411 return; 1412 1413 // Separate "Install boards..." command from installed boards 1414 boardMenu.add(new JSeparator()); 1415 1416 // Generate custom menus for all platforms 1417 Set<String> customMenusTitles = new HashSet<>(); 1418 for (TargetPackage targetPackage : BaseNoGui.packages.values()) { 1419 for (TargetPlatform targetPlatform : targetPackage.platforms()) { 1420 customMenusTitles.addAll(targetPlatform.getCustomMenus().values()); 1421 } 1422 } 1423 for (String customMenuTitle : customMenusTitles) { 1424 JMenu customMenu = new JMenu(tr(customMenuTitle)); 1425 customMenu.putClientProperty("removeOnWindowDeactivation", true); 1426 boardsCustomMenus.add(customMenu); 1427 } 1428 1429 List<JMenuItem> menuItemsToClickAfterStartup = new LinkedList<>(); 1430 1431 ButtonGroup boardsButtonGroup = new ButtonGroup(); 1432 Map<String, ButtonGroup> buttonGroupsMap = new HashMap<>(); 1433 1434 // Cycle through all packages 1435 boolean first = true; 1436 for (TargetPackage targetPackage : BaseNoGui.packages.values()) { 1437 // For every package cycle through all platform 1438 for (TargetPlatform targetPlatform : targetPackage.platforms()) { 1439 1440 // Add a separator from the previous platform 1441 if (!first) 1442 boardMenu.add(new JSeparator()); 1443 first = false; 1444 1445 // Add a title for each platform 1446 String platformLabel = targetPlatform.getPreferences().get("name"); 1447 if (platformLabel != null && !targetPlatform.getBoards().isEmpty()) { 1448 JMenuItem menuLabel = new JMenuItem(tr(platformLabel)); 1449 menuLabel.setEnabled(false); 1450 boardMenu.add(menuLabel); 1451 } 1452 1453 // Cycle through all boards of this platform 1454 for (TargetBoard board : targetPlatform.getBoards().values()) { 1455 if (board.getPreferences().get("hide") != null) 1456 continue; 1457 JMenuItem item = createBoardMenusAndCustomMenus(boardsCustomMenus, menuItemsToClickAfterStartup, 1458 buttonGroupsMap, 1459 board, targetPlatform, targetPackage); 1460 boardMenu.add(item); 1461 boardsButtonGroup.add(item); 1462 } 1463 } 1464 } 1465 1466 if (menuItemsToClickAfterStartup.isEmpty()) { 1467 menuItemsToClickAfterStartup.add(selectFirstEnabledMenuItem(boardMenu)); 1468 } 1469 1470 for (JMenuItem menuItemToClick : menuItemsToClickAfterStartup) { 1471 menuItemToClick.setSelected(true); 1472 menuItemToClick.getAction().actionPerformed(new ActionEvent(this, -1, "")); 1473 } 1474 } 1475 createBoardMenusAndCustomMenus( final List<JMenu> boardsCustomMenus, List<JMenuItem> menuItemsToClickAfterStartup, Map<String, ButtonGroup> buttonGroupsMap, TargetBoard board, TargetPlatform targetPlatform, TargetPackage targetPackage)1476 private JRadioButtonMenuItem createBoardMenusAndCustomMenus( 1477 final List<JMenu> boardsCustomMenus, List<JMenuItem> menuItemsToClickAfterStartup, 1478 Map<String, ButtonGroup> buttonGroupsMap, 1479 TargetBoard board, TargetPlatform targetPlatform, TargetPackage targetPackage) 1480 throws Exception { 1481 String selPackage = PreferencesData.get("target_package"); 1482 String selPlatform = PreferencesData.get("target_platform"); 1483 String selBoard = PreferencesData.get("board"); 1484 1485 String boardId = board.getId(); 1486 String packageName = targetPackage.getId(); 1487 String platformName = targetPlatform.getId(); 1488 1489 // Setup a menu item for the current board 1490 @SuppressWarnings("serial") 1491 Action action = new AbstractAction(board.getName()) { 1492 public void actionPerformed(ActionEvent actionevent) { 1493 BaseNoGui.selectBoard((TargetBoard) getValue("b")); 1494 filterVisibilityOfSubsequentBoardMenus(boardsCustomMenus, (TargetBoard) getValue("b"), 1); 1495 1496 onBoardOrPortChange(); 1497 rebuildImportMenu(Editor.importMenu); 1498 rebuildExamplesMenu(Editor.examplesMenu); 1499 } 1500 }; 1501 action.putValue("b", board); 1502 1503 JRadioButtonMenuItem item = new JRadioButtonMenuItem(action); 1504 1505 if (selBoard.equals(boardId) && selPackage.equals(packageName) 1506 && selPlatform.equals(platformName)) { 1507 menuItemsToClickAfterStartup.add(item); 1508 } 1509 1510 PreferencesMap customMenus = targetPlatform.getCustomMenus(); 1511 for (final String menuId : customMenus.keySet()) { 1512 String title = customMenus.get(menuId); 1513 JMenu menu = getBoardCustomMenu(tr(title)); 1514 1515 if (board.hasMenu(menuId)) { 1516 PreferencesMap boardCustomMenu = board.getMenuLabels(menuId); 1517 for (String customMenuOption : boardCustomMenu.keySet()) { 1518 @SuppressWarnings("serial") 1519 Action subAction = new AbstractAction(tr(boardCustomMenu.get(customMenuOption))) { 1520 public void actionPerformed(ActionEvent e) { 1521 PreferencesData.set("custom_" + menuId, ((TargetBoard) getValue("board")).getId() + "_" + getValue("custom_menu_option")); 1522 onBoardOrPortChange(); 1523 } 1524 }; 1525 subAction.putValue("board", board); 1526 subAction.putValue("custom_menu_option", customMenuOption); 1527 1528 if (!buttonGroupsMap.containsKey(menuId)) { 1529 buttonGroupsMap.put(menuId, new ButtonGroup()); 1530 } 1531 1532 JRadioButtonMenuItem subItem = new JRadioButtonMenuItem(subAction); 1533 menu.add(subItem); 1534 buttonGroupsMap.get(menuId).add(subItem); 1535 1536 String selectedCustomMenuEntry = PreferencesData.get("custom_" + menuId); 1537 if (selBoard.equals(boardId) && (boardId + "_" + customMenuOption).equals(selectedCustomMenuEntry)) { 1538 menuItemsToClickAfterStartup.add(subItem); 1539 } 1540 } 1541 } 1542 } 1543 1544 return item; 1545 } 1546 filterVisibilityOfSubsequentBoardMenus(List<JMenu> boardsCustomMenus, TargetBoard board, int fromIndex)1547 private void filterVisibilityOfSubsequentBoardMenus(List<JMenu> boardsCustomMenus, TargetBoard board, 1548 int fromIndex) { 1549 for (int i = fromIndex; i < boardsCustomMenus.size(); i++) { 1550 JMenu menu = boardsCustomMenus.get(i); 1551 for (int m = 0; m < menu.getItemCount(); m++) { 1552 JMenuItem menuItem = menu.getItem(m); 1553 menuItem.setVisible(menuItem.getAction().getValue("board").equals(board)); 1554 } 1555 menu.setVisible(ifThereAreVisibleItemsOn(menu)); 1556 1557 if (menu.isVisible()) { 1558 JMenuItem visibleSelectedOrFirstMenuItem = selectVisibleSelectedOrFirstMenuItem(menu); 1559 if (!visibleSelectedOrFirstMenuItem.isSelected()) { 1560 visibleSelectedOrFirstMenuItem.setSelected(true); 1561 visibleSelectedOrFirstMenuItem.getAction().actionPerformed(null); 1562 } 1563 } 1564 } 1565 } 1566 ifThereAreVisibleItemsOn(JMenu menu)1567 private static boolean ifThereAreVisibleItemsOn(JMenu menu) { 1568 for (int i = 0; i < menu.getItemCount(); i++) { 1569 if (menu.getItem(i).isVisible()) { 1570 return true; 1571 } 1572 } 1573 return false; 1574 } 1575 getBoardCustomMenu(String label)1576 private JMenu getBoardCustomMenu(String label) throws Exception { 1577 for (JMenu menu : boardsCustomMenus) { 1578 if (label.equals(menu.getText())) { 1579 return menu; 1580 } 1581 } 1582 throw new Exception("Custom menu not found!"); 1583 } 1584 getProgrammerMenus()1585 public List<JMenuItem> getProgrammerMenus() { 1586 return programmerMenus; 1587 } 1588 selectVisibleSelectedOrFirstMenuItem(JMenu menu)1589 private static JMenuItem selectVisibleSelectedOrFirstMenuItem(JMenu menu) { 1590 JMenuItem firstVisible = null; 1591 for (int i = 0; i < menu.getItemCount(); i++) { 1592 JMenuItem item = menu.getItem(i); 1593 if (item != null && item.isVisible()) { 1594 if (item.isSelected()) { 1595 return item; 1596 } 1597 if (firstVisible == null) { 1598 firstVisible = item; 1599 } 1600 } 1601 } 1602 1603 if (firstVisible != null) { 1604 return firstVisible; 1605 } 1606 1607 throw new IllegalStateException("Menu has no enabled items"); 1608 } 1609 selectFirstEnabledMenuItem(JMenu menu)1610 private static JMenuItem selectFirstEnabledMenuItem(JMenu menu) { 1611 for (int i = 1; i < menu.getItemCount(); i++) { 1612 JMenuItem item = menu.getItem(i); 1613 if (item != null && item.isEnabled()) { 1614 return item; 1615 } 1616 } 1617 throw new IllegalStateException("Menu has no enabled items"); 1618 } 1619 rebuildProgrammerMenu()1620 public void rebuildProgrammerMenu() { 1621 programmerMenus = new LinkedList<>(); 1622 1623 ButtonGroup group = new ButtonGroup(); 1624 for (TargetPackage targetPackage : BaseNoGui.packages.values()) { 1625 for (TargetPlatform targetPlatform : targetPackage.platforms()) { 1626 for (String programmer : targetPlatform.getProgrammers().keySet()) { 1627 String id = targetPackage.getId() + ":" + programmer; 1628 1629 @SuppressWarnings("serial") 1630 AbstractAction action = new AbstractAction(targetPlatform.getProgrammer(programmer).get("name")) { 1631 public void actionPerformed(ActionEvent actionevent) { 1632 PreferencesData.set("programmer", "" + getValue("id")); 1633 } 1634 }; 1635 action.putValue("id", id); 1636 JMenuItem item = new JRadioButtonMenuItem(action); 1637 if (PreferencesData.get("programmer").equals(id)) { 1638 item.setSelected(true); 1639 } 1640 group.add(item); 1641 programmerMenus.add(item); 1642 } 1643 } 1644 } 1645 } 1646 1647 /** 1648 * Scan a folder recursively, and add any sketches found to the menu 1649 * specified. Set the openReplaces parameter to true when opening the sketch 1650 * should replace the sketch in the current window, or false when the 1651 * sketch should open in a new window. 1652 */ addSketches(JMenu menu, File folder)1653 protected boolean addSketches(JMenu menu, File folder) { 1654 if (folder == null) 1655 return false; 1656 1657 if (!folder.isDirectory()) return false; 1658 1659 File[] files = folder.listFiles(); 1660 // If a bad folder or unreadable or whatever, this will come back null 1661 if (files == null) return false; 1662 1663 // Alphabetize files, since it's not always alpha order 1664 Arrays.sort(files, new Comparator<File>() { 1665 @Override 1666 public int compare(File file, File file2) { 1667 return file.getName().compareToIgnoreCase(file2.getName()); 1668 } 1669 }); 1670 1671 boolean ifound = false; 1672 1673 for (File subfolder : files) { 1674 if (FileUtils.isSCCSOrHiddenFile(subfolder)) { 1675 continue; 1676 } 1677 1678 if (!subfolder.isDirectory()) continue; 1679 1680 if (addSketchesSubmenu(menu, subfolder.getName(), subfolder)) { 1681 ifound = true; 1682 } 1683 } 1684 1685 return ifound; 1686 } 1687 addSketchesSubmenu(JMenu menu, UserLibrary lib)1688 private boolean addSketchesSubmenu(JMenu menu, UserLibrary lib) { 1689 return addSketchesSubmenu(menu, lib.getName(), lib.getInstalledFolder()); 1690 } 1691 addSketchesSubmenu(JMenu menu, String name, File folder)1692 private boolean addSketchesSubmenu(JMenu menu, String name, File folder) { 1693 1694 ActionListener listener = new ActionListener() { 1695 public void actionPerformed(ActionEvent e) { 1696 String path = e.getActionCommand(); 1697 File file = new File(path); 1698 if (file.exists()) { 1699 try { 1700 handleOpen(file); 1701 } catch (Exception e1) { 1702 e1.printStackTrace(); 1703 } 1704 } else { 1705 showWarning(tr("Sketch Does Not Exist"), 1706 tr("The selected sketch no longer exists.\n" 1707 + "You may need to restart Arduino to update\n" 1708 + "the sketchbook menu."), null); 1709 } 1710 } 1711 }; 1712 1713 File entry = new File(folder, name + ".ino"); 1714 if (!entry.exists() && (new File(folder, name + ".pde")).exists()) 1715 entry = new File(folder, name + ".pde"); 1716 1717 // if a .pde file of the same prefix as the folder exists.. 1718 if (entry.exists()) { 1719 1720 if (!BaseNoGui.isSanitaryName(name)) { 1721 if (!builtOnce) { 1722 String complaining = I18n 1723 .format( 1724 tr("The sketch \"{0}\" cannot be used.\n" 1725 + "Sketch names must contain only basic letters and numbers\n" 1726 + "(ASCII-only with no spaces, " 1727 + "and it cannot start with a number).\n" 1728 + "To get rid of this message, remove the sketch from\n" 1729 + "{1}"), name, entry.getAbsolutePath()); 1730 showMessage(tr("Ignoring sketch with bad name"), complaining); 1731 } 1732 return false; 1733 } 1734 1735 JMenuItem item = new JMenuItem(name); 1736 item.addActionListener(listener); 1737 item.setActionCommand(entry.getAbsolutePath()); 1738 menu.add(item); 1739 return true; 1740 } 1741 1742 // don't create an extra menu level for a folder named "examples" 1743 if (folder.getName().equals("examples")) 1744 return addSketches(menu, folder); 1745 1746 // not a sketch folder, but maybe a subfolder containing sketches 1747 JMenu submenu = new JMenu(name); 1748 boolean found = addSketches(submenu, folder); 1749 if (found) { 1750 menu.add(submenu); 1751 MenuScroller.setScrollerFor(submenu); 1752 } 1753 return found; 1754 } 1755 addLibraries(JMenu menu, LibraryList libs)1756 protected void addLibraries(JMenu menu, LibraryList libs) throws IOException { 1757 1758 LibraryList list = new LibraryList(libs); 1759 list.sort(); 1760 1761 for (UserLibrary lib : list) { 1762 @SuppressWarnings("serial") 1763 AbstractAction action = new AbstractAction(lib.getName()) { 1764 public void actionPerformed(ActionEvent event) { 1765 UserLibrary l = (UserLibrary) getValue("library"); 1766 try { 1767 activeEditor.getSketchController().importLibrary(l); 1768 } catch (IOException e) { 1769 showWarning(tr("Error"), I18n.format("Unable to list header files in {0}", l.getSrcFolder()), e); 1770 } 1771 } 1772 }; 1773 action.putValue("library", lib); 1774 1775 // Add new element at the bottom 1776 JMenuItem item = new JMenuItem(action); 1777 item.putClientProperty("library", lib); 1778 menu.add(item); 1779 1780 // XXX: DAM: should recurse here so that library folders can be nested 1781 } 1782 } 1783 1784 /** 1785 * Given a folder, return a list of the header files in that folder (but not 1786 * the header files in its sub-folders, as those should be included from 1787 * within the header files at the top-level). 1788 */ headerListFromIncludePath(File path)1789 static public String[] headerListFromIncludePath(File path) throws IOException { 1790 String[] list = path.list(new OnlyFilesWithExtension(".h")); 1791 if (list == null) { 1792 throw new IOException(); 1793 } 1794 return list; 1795 } 1796 1797 /** 1798 * Show the About box. 1799 */ 1800 @SuppressWarnings("serial") handleAbout()1801 public void handleAbout() { 1802 final Image image = Theme.getLibImage("about", activeEditor, 1803 Theme.scale(475), Theme.scale(300)); 1804 final Window window = new Window(activeEditor) { 1805 public void paint(Graphics graphics) { 1806 Graphics2D g = Theme.setupGraphics2D(graphics); 1807 g.drawImage(image, 0, 0, null); 1808 1809 Font f = new Font("SansSerif", Font.PLAIN, Theme.scale(11)); 1810 g.setFont(f); 1811 g.setColor(new Color(0,151,156)); 1812 g.drawString(BaseNoGui.VERSION_NAME_LONG, Theme.scale(33), Theme.scale(20)); 1813 } 1814 }; 1815 window.addMouseListener(new MouseAdapter() { 1816 public void mousePressed(MouseEvent e) { 1817 window.dispose(); 1818 } 1819 }); 1820 int w = image.getWidth(activeEditor); 1821 int h = image.getHeight(activeEditor); 1822 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); 1823 window.setBounds((screen.width - w) / 2, (screen.height - h) / 2, w, h); 1824 window.setLocationRelativeTo(activeEditor); 1825 window.setVisible(true); 1826 } 1827 1828 1829 /** 1830 * Show the Preferences window. 1831 */ handlePrefs()1832 public void handlePrefs() { 1833 cc.arduino.view.preferences.Preferences dialog = new cc.arduino.view.preferences.Preferences(activeEditor, this); 1834 if (activeEditor != null) { 1835 dialog.setLocationRelativeTo(activeEditor); 1836 } 1837 dialog.setVisible(true); 1838 } 1839 1840 // XXX: Remove this method and make librariesIndexer non-static getLibraries()1841 static public LibraryList getLibraries() { 1842 return BaseNoGui.librariesIndexer.getInstalledLibraries(); 1843 } 1844 getBoardsCustomMenus()1845 public List<JMenu> getBoardsCustomMenus() { 1846 return boardsCustomMenus; 1847 } 1848 getDefaultSketchbookFolderOrPromptForIt()1849 public File getDefaultSketchbookFolderOrPromptForIt() { 1850 File sketchbookFolder = BaseNoGui.getDefaultSketchbookFolder(); 1851 1852 if (sketchbookFolder == null && !isCommandLine()) { 1853 sketchbookFolder = promptSketchbookLocation(); 1854 } 1855 1856 // create the folder if it doesn't exist already 1857 boolean result = true; 1858 if (!sketchbookFolder.exists()) { 1859 result = sketchbookFolder.mkdirs(); 1860 } 1861 1862 if (!result) { 1863 showError(tr("You forgot your sketchbook"), 1864 tr("Arduino cannot run because it could not\n" + 1865 "create a folder to store your sketchbook."), null); 1866 } 1867 1868 return sketchbookFolder; 1869 } 1870 1871 1872 /** 1873 * Check for a new sketchbook location. 1874 */ promptSketchbookLocation()1875 static protected File promptSketchbookLocation() { 1876 File folder = null; 1877 1878 folder = new File(System.getProperty("user.home"), "sketchbook"); 1879 if (!folder.exists()) { 1880 folder.mkdirs(); 1881 return folder; 1882 } 1883 1884 String prompt = tr("Select (or create new) folder for sketches..."); 1885 folder = selectFolder(prompt, null, null); 1886 if (folder == null) { 1887 System.exit(0); 1888 } 1889 return folder; 1890 } 1891 1892 1893 // ................................................................. 1894 1895 1896 /** 1897 * Implements the cross-platform headache of opening URLs 1898 * TODO This code should be replaced by PApplet.link(), 1899 * however that's not a static method (because it requires 1900 * an AppletContext when used as an applet), so it's mildly 1901 * trickier than just removing this method. 1902 */ openURL(String url)1903 static public void openURL(String url) { 1904 try { 1905 BaseNoGui.getPlatform().openURL(url); 1906 1907 } catch (Exception e) { 1908 showWarning(tr("Problem Opening URL"), 1909 I18n.format(tr("Could not open the URL\n{0}"), url), e); 1910 } 1911 } 1912 1913 1914 /** 1915 * Used to determine whether to disable the "Show Sketch Folder" option. 1916 * 1917 * @return true If a means of opening a folder is known to be available. 1918 */ openFolderAvailable()1919 static protected boolean openFolderAvailable() { 1920 return BaseNoGui.getPlatform().openFolderAvailable(); 1921 } 1922 1923 1924 /** 1925 * Implements the other cross-platform headache of opening 1926 * a folder in the machine's native file browser. 1927 */ openFolder(File file)1928 static public void openFolder(File file) { 1929 try { 1930 BaseNoGui.getPlatform().openFolder(file); 1931 1932 } catch (Exception e) { 1933 showWarning(tr("Problem Opening Folder"), 1934 I18n.format(tr("Could not open the folder\n{0}"), file.getAbsolutePath()), e); 1935 } 1936 } 1937 1938 1939 // ................................................................. 1940 1941 selectFolder(String prompt, File folder, Component parent)1942 static public File selectFolder(String prompt, File folder, Component parent) { 1943 JFileChooser fc = new JFileChooser(); 1944 fc.setDialogTitle(prompt); 1945 if (folder != null) { 1946 fc.setSelectedFile(folder); 1947 } 1948 fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); 1949 1950 int returned = fc.showOpenDialog(parent); 1951 if (returned == JFileChooser.APPROVE_OPTION) { 1952 return fc.getSelectedFile(); 1953 } 1954 return null; 1955 } 1956 1957 1958 // ................................................................. 1959 1960 1961 /** 1962 * Give this Frame an icon. 1963 */ setIcon(Frame frame)1964 static public void setIcon(Frame frame) { 1965 if (OSUtils.isMacOS()) { 1966 return; 1967 } 1968 1969 List<Image> icons = Stream 1970 .of("16", "24", "32", "48", "64", "72", "96", "128", "256") 1971 .map(res -> "/lib/icons/" + res + "x" + res + "/apps/arduino.png") 1972 .map(path -> BaseNoGui.getContentFile(path).getAbsolutePath()) 1973 .map(absPath -> Toolkit.getDefaultToolkit().createImage(absPath)) 1974 .collect(Collectors.toList()); 1975 frame.setIconImages(icons); 1976 } 1977 1978 1979 /** 1980 * Registers key events for a Ctrl-W and ESC with an ActionListener 1981 * that will take care of disposing the window. 1982 */ registerWindowCloseKeys(JRootPane root, ActionListener disposer)1983 static public void registerWindowCloseKeys(JRootPane root, 1984 ActionListener disposer) { 1985 KeyStroke stroke = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0); 1986 root.registerKeyboardAction(disposer, stroke, 1987 JComponent.WHEN_IN_FOCUSED_WINDOW); 1988 1989 int modifiers = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(); 1990 stroke = KeyStroke.getKeyStroke('W', modifiers); 1991 root.registerKeyboardAction(disposer, stroke, 1992 JComponent.WHEN_IN_FOCUSED_WINDOW); 1993 } 1994 1995 1996 // ................................................................. 1997 1998 showReference(String filename)1999 static public void showReference(String filename) { 2000 showReference("reference/www.arduino.cc/en", filename); 2001 } 2002 showReference(String prefix, String filename)2003 static public void showReference(String prefix, String filename) { 2004 File referenceFolder = getContentFile(prefix); 2005 File referenceFile = new File(referenceFolder, filename); 2006 if (!referenceFile.exists()) 2007 referenceFile = new File(referenceFolder, filename + ".html"); 2008 2009 if(referenceFile.exists()){ 2010 openURL(referenceFile.getAbsolutePath()); 2011 }else{ 2012 showWarning(tr("Problem Opening URL"), I18n.format(tr("Could not open the URL\n{0}"), referenceFile), null); 2013 } 2014 } 2015 showEdisonGettingStarted()2016 public static void showEdisonGettingStarted() { 2017 showReference("reference/Edison_help_files", "ArduinoIDE_guide_edison"); 2018 } 2019 showArduinoGettingStarted()2020 static public void showArduinoGettingStarted() { 2021 if (OSUtils.isMacOS()) { 2022 showReference("Guide/MacOSX"); 2023 } else if (OSUtils.isWindows()) { 2024 showReference("Guide/Windows"); 2025 } else { 2026 openURL("http://www.arduino.cc/playground/Learning/Linux"); 2027 } 2028 } 2029 showReference()2030 static public void showReference() { 2031 showReference("Reference/HomePage"); 2032 } 2033 2034 showEnvironment()2035 static public void showEnvironment() { 2036 showReference("Guide/Environment"); 2037 } 2038 2039 showTroubleshooting()2040 static public void showTroubleshooting() { 2041 showReference("Guide/Troubleshooting"); 2042 } 2043 2044 showFAQ()2045 static public void showFAQ() { 2046 showReference("Main/FAQ"); 2047 } 2048 2049 2050 // ................................................................. 2051 2052 2053 /** 2054 * "No cookie for you" type messages. Nothing fatal or all that 2055 * much of a bummer, but something to notify the user about. 2056 */ showMessage(String title, String message)2057 static public void showMessage(String title, String message) { 2058 BaseNoGui.showMessage(title, message); 2059 } 2060 2061 2062 /** 2063 * Non-fatal error message with optional stack trace side dish. 2064 */ showWarning(String title, String message, Exception e)2065 static public void showWarning(String title, String message, Exception e) { 2066 BaseNoGui.showWarning(title, message, e); 2067 } 2068 2069 showError(String title, String message, Throwable e)2070 static public void showError(String title, String message, Throwable e) { 2071 showError(title, message, e, 1); 2072 } 2073 showError(String title, String message, int exit_code)2074 static public void showError(String title, String message, int exit_code) { 2075 showError(title, message, null, exit_code); 2076 } 2077 2078 /** 2079 * Show an error message that's actually fatal to the program. 2080 * This is an error that can't be recovered. Use showWarning() 2081 * for errors that allow P5 to continue running. 2082 */ showError(String title, String message, Throwable e, int exit_code)2083 static public void showError(String title, String message, Throwable e, int exit_code) { 2084 BaseNoGui.showError(title, message, e, exit_code); 2085 } 2086 2087 getContentFile(String name)2088 static public File getContentFile(String name) { 2089 return BaseNoGui.getContentFile(name); 2090 } 2091 2092 2093 // ................................................................... 2094 2095 2096 /** 2097 * Get the number of lines in a file by counting the number of newline 2098 * characters inside a String (and adding 1). 2099 */ countLines(String what)2100 static public int countLines(String what) { 2101 return BaseNoGui.countLines(what); 2102 } 2103 2104 2105 /** 2106 * Same as PApplet.loadBytes(), however never does gzip decoding. 2107 */ loadBytesRaw(File file)2108 static public byte[] loadBytesRaw(File file) throws IOException { 2109 int size = (int) file.length(); 2110 FileInputStream input = null; 2111 try { 2112 input = new FileInputStream(file); 2113 byte buffer[] = new byte[size]; 2114 int offset = 0; 2115 int bytesRead; 2116 while ((bytesRead = input.read(buffer, offset, size - offset)) != -1) { 2117 offset += bytesRead; 2118 if (bytesRead == 0) break; 2119 } 2120 return buffer; 2121 } finally { 2122 IOUtils.closeQuietly(input); 2123 } 2124 } 2125 2126 2127 /** 2128 * Read from a file with a bunch of attribute/value pairs 2129 * that are separated by = and ignore comments with #. 2130 */ readSettings(File inputFile)2131 static public HashMap<String, String> readSettings(File inputFile) { 2132 HashMap<String, String> outgoing = new HashMap<>(); 2133 if (!inputFile.exists()) return outgoing; // return empty hash 2134 2135 String lines[] = PApplet.loadStrings(inputFile); 2136 for (int i = 0; i < lines.length; i++) { 2137 int hash = lines[i].indexOf('#'); 2138 String line = (hash == -1) ? 2139 lines[i].trim() : lines[i].substring(0, hash).trim(); 2140 if (line.length() == 0) continue; 2141 2142 int equals = line.indexOf('='); 2143 if (equals == -1) { 2144 System.err.println("ignoring illegal line in " + inputFile); 2145 System.err.println(" " + line); 2146 continue; 2147 } 2148 String attr = line.substring(0, equals).trim(); 2149 String valu = line.substring(equals + 1).trim(); 2150 outgoing.put(attr, valu); 2151 } 2152 return outgoing; 2153 } 2154 2155 copyFile(File sourceFile, File targetFile)2156 static public void copyFile(File sourceFile, 2157 File targetFile) throws IOException { 2158 InputStream from = null; 2159 OutputStream to = null; 2160 try { 2161 from = new BufferedInputStream(new FileInputStream(sourceFile)); 2162 to = new BufferedOutputStream(new FileOutputStream(targetFile)); 2163 byte[] buffer = new byte[16 * 1024]; 2164 int bytesRead; 2165 while ((bytesRead = from.read(buffer)) != -1) { 2166 to.write(buffer, 0, bytesRead); 2167 } 2168 to.flush(); 2169 } finally { 2170 IOUtils.closeQuietly(from); 2171 IOUtils.closeQuietly(to); 2172 } 2173 2174 targetFile.setLastModified(sourceFile.lastModified()); 2175 } 2176 2177 2178 /** 2179 * Grab the contents of a file as a string. 2180 */ loadFile(File file)2181 static public String loadFile(File file) throws IOException { 2182 return BaseNoGui.loadFile(file); 2183 } 2184 2185 2186 /** 2187 * Spew the contents of a String object out to a file. 2188 */ saveFile(String str, File file)2189 static public void saveFile(String str, File file) throws IOException { 2190 BaseNoGui.saveFile(str, file); 2191 } 2192 2193 2194 /** 2195 * Calculate the size of the contents of a folder. 2196 * Used to determine whether sketches are empty or not. 2197 * Note that the function calls itself recursively. 2198 */ calcFolderSize(File folder)2199 static public int calcFolderSize(File folder) { 2200 int size = 0; 2201 2202 String files[] = folder.list(); 2203 // null if folder doesn't exist, happens when deleting sketch 2204 if (files == null) return -1; 2205 2206 for (int i = 0; i < files.length; i++) { 2207 if (files[i].equals(".") || (files[i].equals("..")) || 2208 files[i].equals(".DS_Store")) continue; 2209 File fella = new File(folder, files[i]); 2210 if (fella.isDirectory()) { 2211 size += calcFolderSize(fella); 2212 } else { 2213 size += (int) fella.length(); 2214 } 2215 } 2216 return size; 2217 } 2218 handleAddLibrary()2219 public void handleAddLibrary() { 2220 JFileChooser fileChooser = new JFileChooser(System.getProperty("user.home")); 2221 fileChooser.setDialogTitle(tr("Select a zip file or a folder containing the library you'd like to add")); 2222 fileChooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES); 2223 fileChooser.setFileFilter(new FileNameExtensionFilter(tr("ZIP files or folders"), "zip")); 2224 2225 Dimension preferredSize = fileChooser.getPreferredSize(); 2226 fileChooser.setPreferredSize(new Dimension(preferredSize.width + 200, preferredSize.height + 200)); 2227 2228 int returnVal = fileChooser.showOpenDialog(activeEditor); 2229 2230 if (returnVal != JFileChooser.APPROVE_OPTION) { 2231 return; 2232 } 2233 2234 File sourceFile = fileChooser.getSelectedFile(); 2235 File tmpFolder = null; 2236 2237 try { 2238 // unpack ZIP 2239 if (!sourceFile.isDirectory()) { 2240 try { 2241 tmpFolder = FileUtils.createTempFolder(); 2242 ZipDeflater zipDeflater = new ZipDeflater(sourceFile, tmpFolder); 2243 zipDeflater.deflate(); 2244 File[] foldersInTmpFolder = tmpFolder.listFiles(new OnlyDirs()); 2245 if (foldersInTmpFolder.length != 1) { 2246 throw new IOException(tr("Zip doesn't contain a library")); 2247 } 2248 sourceFile = foldersInTmpFolder[0]; 2249 } catch (IOException e) { 2250 activeEditor.statusError(e); 2251 return; 2252 } 2253 } 2254 2255 File libFolder = sourceFile; 2256 if (FileUtils.isSubDirectory(new File(PreferencesData.get("sketchbook.path")), libFolder)) { 2257 activeEditor.statusError(tr("A subfolder of your sketchbook is not a valid library")); 2258 return; 2259 } 2260 2261 if (FileUtils.isSubDirectory(libFolder, new File(PreferencesData.get("sketchbook.path")))) { 2262 activeEditor.statusError(tr("You can't import a folder that contains your sketchbook")); 2263 return; 2264 } 2265 2266 String libName = libFolder.getName(); 2267 if (!BaseNoGui.isSanitaryName(libName)) { 2268 String mess = I18n.format(tr("The library \"{0}\" cannot be used.\n" 2269 + "Library names must contain only basic letters and numbers.\n" 2270 + "(ASCII only and no spaces, and it cannot start with a number)"), 2271 libName); 2272 activeEditor.statusError(mess); 2273 return; 2274 } 2275 2276 String[] headers; 2277 if (new File(libFolder, "library.properties").exists()) { 2278 headers = BaseNoGui.headerListFromIncludePath(UserLibrary.create(libFolder).getSrcFolder()); 2279 } else { 2280 headers = BaseNoGui.headerListFromIncludePath(libFolder); 2281 } 2282 if (headers.length == 0) { 2283 activeEditor.statusError(tr("Specified folder/zip file does not contain a valid library")); 2284 return; 2285 } 2286 2287 // copy folder 2288 File destinationFolder = new File(BaseNoGui.getSketchbookLibrariesFolder(), sourceFile.getName()); 2289 if (!destinationFolder.mkdir()) { 2290 activeEditor.statusError(I18n.format(tr("A library named {0} already exists"), sourceFile.getName())); 2291 return; 2292 } 2293 try { 2294 FileUtils.copy(sourceFile, destinationFolder); 2295 } catch (IOException e) { 2296 activeEditor.statusError(e); 2297 return; 2298 } 2299 activeEditor.statusNotice(tr("Library added to your libraries. Check \"Include library\" menu")); 2300 } catch (IOException e) { 2301 // FIXME error when importing. ignoring :( 2302 } finally { 2303 // delete zip created temp folder, if exists 2304 newLibraryImported = true; 2305 FileUtils.recursiveDelete(tmpFolder); 2306 } 2307 } 2308 getDiscoveryManager()2309 public static DiscoveryManager getDiscoveryManager() { 2310 return BaseNoGui.getDiscoveryManager(); 2311 } 2312 getActiveEditor()2313 public Editor getActiveEditor() { 2314 return activeEditor; 2315 } 2316 hasActiveEditor()2317 public boolean hasActiveEditor() { 2318 return activeEditor != null; 2319 } 2320 getEditors()2321 public List<Editor> getEditors() { 2322 return new LinkedList<>(editors); 2323 } 2324 getPdeKeywords()2325 public PdeKeywords getPdeKeywords() { 2326 return pdeKeywords; 2327 } 2328 getRecentSketchesMenuItems()2329 public List<JMenuItem> getRecentSketchesMenuItems() { 2330 return recentSketchesMenuItems; 2331 } 2332 2333 } 2334