.
diff --git a/app/lib/jssc.LICENSE.LGPL.txt b/app/lib/jssc.LICENSE.LGPL.txt
new file mode 100644
index 00000000000..65c5ca88a67
--- /dev/null
+++ b/app/lib/jssc.LICENSE.LGPL.txt
@@ -0,0 +1,165 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+
+ This version of the GNU Lesser General Public License incorporates
+the terms and conditions of version 3 of the GNU General Public
+License, supplemented by the additional permissions listed below.
+
+ 0. Additional Definitions.
+
+ As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
+General Public License.
+
+ "The Library" refers to a covered work governed by this License,
+other than an Application or a Combined Work as defined below.
+
+ An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+ A "Combined Work" is a work produced by combining or linking an
+Application with the Library. The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+ The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+ The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+ 1. Exception to Section 3 of the GNU GPL.
+
+ You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+ 2. Conveying Modified Versions.
+
+ If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+ a) under this License, provided that you make a good faith effort to
+ ensure that, in the event an Application does not supply the
+ function or data, the facility still operates, and performs
+ whatever part of its purpose remains meaningful, or
+
+ b) under the GNU GPL, with none of the additional permissions of
+ this License applicable to that copy.
+
+ 3. Object Code Incorporating Material from Library Header Files.
+
+ The object code form of an Application may incorporate material from
+a header file that is part of the Library. You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+ a) Give prominent notice with each copy of the object code that the
+ Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the object code with a copy of the GNU GPL and this license
+ document.
+
+ 4. Combined Works.
+
+ You may convey a Combined Work under terms of your choice that,
+taken together, effectively do not restrict modification of the
+portions of the Library contained in the Combined Work and reverse
+engineering for debugging such modifications, if you also do each of
+the following:
+
+ a) Give prominent notice with each copy of the Combined Work that
+ the Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the Combined Work with a copy of the GNU GPL and this license
+ document.
+
+ c) For a Combined Work that displays copyright notices during
+ execution, include the copyright notice for the Library among
+ these notices, as well as a reference directing the user to the
+ copies of the GNU GPL and this license document.
+
+ d) Do one of the following:
+
+ 0) Convey the Minimal Corresponding Source under the terms of this
+ License, and the Corresponding Application Code in a form
+ suitable for, and under terms that permit, the user to
+ recombine or relink the Application with a modified version of
+ the Linked Version to produce a modified Combined Work, in the
+ manner specified by section 6 of the GNU GPL for conveying
+ Corresponding Source.
+
+ 1) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (a) uses at run time
+ a copy of the Library already present on the user's computer
+ system, and (b) will operate properly with a modified version
+ of the Library that is interface-compatible with the Linked
+ Version.
+
+ e) Provide Installation Information, but only if you would otherwise
+ be required to provide such information under section 6 of the
+ GNU GPL, and only to the extent that such information is
+ necessary to install and execute a modified version of the
+ Combined Work produced by recombining or relinking the
+ Application with a modified version of the Linked Version. (If
+ you use option 4d0, the Installation Information must accompany
+ the Minimal Corresponding Source and Corresponding Application
+ Code. If you use option 4d1, you must provide the Installation
+ Information in the manner specified by section 6 of the GNU GPL
+ for conveying Corresponding Source.)
+
+ 5. Combined Libraries.
+
+ You may place library facilities that are a work based on the
+Library side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+ a) Accompany the combined library with a copy of the same work based
+ on the Library, uncombined with any other library facilities,
+ conveyed under the terms of this License.
+
+ b) Give prominent notice with the combined library that part of it
+ is a work based on the Library, and explaining where to find the
+ accompanying uncombined form of the same work.
+
+ 6. Revised Versions of the GNU Lesser General Public License.
+
+ The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser General Public License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Library as you received it specifies that a certain numbered version
+of the GNU Lesser General Public License "or any later version"
+applies to it, you have the option of following the terms and
+conditions either of that published version or of any later version
+published by the Free Software Foundation. If the Library as you
+received it does not specify a version number of the GNU Lesser
+General Public License, you may choose any version of the GNU Lesser
+General Public License ever published by the Free Software Foundation.
+
+ If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.
diff --git a/app/lib/jtouchbar-1.0.0.jar b/app/lib/jtouchbar-1.0.0.jar
new file mode 100644
index 00000000000..2c473bec881
Binary files /dev/null and b/app/lib/jtouchbar-1.0.0.jar differ
diff --git a/app/lib/jtouchbar.LICENSE.MIT.txt b/app/lib/jtouchbar.LICENSE.MIT.txt
new file mode 100644
index 00000000000..f4fa99707a0
--- /dev/null
+++ b/app/lib/jtouchbar.LICENSE.MIT.txt
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2018 thizzer.com
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
\ No newline at end of file
diff --git a/app/lib/rsyntaxtextarea-3.0.3-SNAPSHOT.jar b/app/lib/rsyntaxtextarea-3.0.3-SNAPSHOT.jar
new file mode 100644
index 00000000000..e1844494aca
Binary files /dev/null and b/app/lib/rsyntaxtextarea-3.0.3-SNAPSHOT.jar differ
diff --git a/app/lib/slf4j-api-1.7.22.jar b/app/lib/slf4j-api-1.7.22.jar
new file mode 100644
index 00000000000..ddf3dc951be
Binary files /dev/null and b/app/lib/slf4j-api-1.7.22.jar differ
diff --git a/app/lib/slf4j-simple-1.7.22.jar b/app/lib/slf4j-simple-1.7.22.jar
new file mode 100644
index 00000000000..13ee902df0e
Binary files /dev/null and b/app/lib/slf4j-simple-1.7.22.jar differ
diff --git a/app/lib/xml-apis-1.3.04.jar b/app/lib/xml-apis-1.3.04.jar
new file mode 100644
index 00000000000..d42c0ea6cfd
Binary files /dev/null and b/app/lib/xml-apis-1.3.04.jar differ
diff --git a/app/lib/xml-apis-ext-1.3.04.jar b/app/lib/xml-apis-ext-1.3.04.jar
new file mode 100644
index 00000000000..a7869d68aac
Binary files /dev/null and b/app/lib/xml-apis-ext-1.3.04.jar differ
diff --git a/app/lib/xmlgraphics-commons-2.0.jar b/app/lib/xmlgraphics-commons-2.0.jar
new file mode 100644
index 00000000000..a168e4ff42b
Binary files /dev/null and b/app/lib/xmlgraphics-commons-2.0.jar differ
diff --git a/app/src/cc/arduino/ConsoleOutputStream.java b/app/src/cc/arduino/ConsoleOutputStream.java
new file mode 100644
index 00000000000..6334f11e7e2
--- /dev/null
+++ b/app/src/cc/arduino/ConsoleOutputStream.java
@@ -0,0 +1,113 @@
+/*
+ * This file is part of Arduino.
+ *
+ * Arduino is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * As a special exception, you may use this file as part of a free software
+ * library without restriction. Specifically, if other files instantiate
+ * templates or use macros or inline functions from this file, or you compile
+ * this file and link it with other files to produce an executable, this
+ * file does not by itself cause the resulting executable to be covered by
+ * the GNU General Public License. This exception does not however
+ * invalidate any other reasons why the executable file might be covered by
+ * the GNU General Public License.
+ *
+ * Copyright 2015 Arduino LLC (http://www.arduino.cc/)
+ *
+ * Original version of this file courtesy of Rob Camick
+ *
+ * https://tips4java.wordpress.com/2008/11/08/message-console/
+ *
+ * About page at https://tips4java.wordpress.com/about/ says something
+ * like MIT
+ */
+
+package cc.arduino;
+
+import processing.app.EditorConsole;
+
+import javax.swing.*;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.SimpleAttributeSet;
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+
+/*
+ * Class to intercept output from a PrintStream and add it to a Document.
+ * The output can optionally be redirected to a different PrintStream.
+ * The text displayed in the Document can be color coded to indicate
+ * the output source.
+ */
+public class ConsoleOutputStream extends ByteArrayOutputStream {
+
+ private SimpleAttributeSet attributes;
+ private final PrintStream printStream;
+ private final Timer timer;
+
+ private volatile EditorConsole editorConsole;
+ private volatile boolean newLinePrinted;
+
+ public ConsoleOutputStream(SimpleAttributeSet attributes, PrintStream printStream) {
+ this.attributes = attributes;
+ this.printStream = printStream;
+ this.newLinePrinted = false;
+
+ this.timer = new Timer(100, (e) -> {
+ if (editorConsole != null && newLinePrinted) {
+ editorConsole.scrollDown();
+ newLinePrinted = false;
+ }
+ });
+ timer.setRepeats(false);
+ }
+
+ public void setAttibutes(SimpleAttributeSet attributes) {
+ this.attributes = attributes;
+ }
+
+ public void setCurrentEditorConsole(EditorConsole console) {
+ this.editorConsole = console;
+ }
+
+ public synchronized void flush() {
+ String text = toString();
+
+ if (text.length() == 0) {
+ return;
+ }
+
+ printStream.print(text);
+ printInConsole(text);
+
+ reset();
+ }
+
+ private void printInConsole(String text) {
+ newLinePrinted = newLinePrinted || text.contains("\n");
+ if (editorConsole != null) {
+ SwingUtilities.invokeLater(() -> {
+ try {
+ editorConsole.insertString(text, attributes);
+ } catch (BadLocationException ble) {
+ //ignore
+ }
+ });
+
+ if (!timer.isRunning()) {
+ timer.restart();
+ }
+ }
+ }
+}
diff --git a/app/src/cc/arduino/UpdatableBoardsLibsFakeURLsHandler.java b/app/src/cc/arduino/UpdatableBoardsLibsFakeURLsHandler.java
new file mode 100644
index 00000000000..77694d925d3
--- /dev/null
+++ b/app/src/cc/arduino/UpdatableBoardsLibsFakeURLsHandler.java
@@ -0,0 +1,78 @@
+/*
+ * This file is part of Arduino.
+ *
+ * Arduino is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * As a special exception, you may use this file as part of a free software
+ * library without restriction. Specifically, if other files instantiate
+ * templates or use macros or inline functions from this file, or you compile
+ * this file and link it with other files to produce an executable, this
+ * file does not by itself cause the resulting executable to be covered by
+ * the GNU General Public License. This exception does not however
+ * invalidate any other reasons why the executable file might be covered by
+ * the GNU General Public License.
+ *
+ * Copyright 2015 Arduino LLC (http://www.arduino.cc/)
+ */
+
+package cc.arduino;
+
+import processing.app.Base;
+
+import javax.swing.event.HyperlinkEvent;
+import javax.swing.event.HyperlinkListener;
+import java.net.URL;
+
+public class UpdatableBoardsLibsFakeURLsHandler implements HyperlinkListener {
+
+ private static final String BOARDSMANAGER = "boardsmanager";
+ private static final String LIBRARYMANAGER = "librarymanager";
+
+ private final Base base;
+
+ public UpdatableBoardsLibsFakeURLsHandler(Base base) {
+ this.base = base;
+ }
+
+ @Override
+ public void hyperlinkUpdate(HyperlinkEvent event) {
+ if (event.getEventType() != HyperlinkEvent.EventType.ACTIVATED) {
+ return;
+ }
+
+ URL url = event.getURL();
+ openBoardLibManager(url);
+ }
+
+ public void openBoardLibManager(URL url) {
+ if (BOARDSMANAGER.equals(url.getHost())) {
+ try {
+ base.openBoardsManager(url.getRef() == null ? "": url.getRef() , url.getPath() == null ? "" : url.getPath().replace("/", ""));
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return;
+ }
+
+ if (LIBRARYMANAGER.equals(url.getHost())) {
+ base.openLibraryManager(url.getRef() == null ? "": url.getRef() , url.getPath() == null ? "" : url.getPath().replace("/", ""));
+ return;
+ }
+
+ throw new IllegalArgumentException(url.getHost() + " is invalid");
+
+ }
+
+}
diff --git a/app/src/cc/arduino/contributions/BuiltInCoreIsNewerCheck.java b/app/src/cc/arduino/contributions/BuiltInCoreIsNewerCheck.java
new file mode 100644
index 00000000000..d28d735e35f
--- /dev/null
+++ b/app/src/cc/arduino/contributions/BuiltInCoreIsNewerCheck.java
@@ -0,0 +1,107 @@
+/*
+ * This file is part of Arduino.
+ *
+ * Copyright 2015 Arduino LLC (http://www.arduino.cc/)
+ *
+ * Arduino is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * As a special exception, you may use this file as part of a free software
+ * library without restriction. Specifically, if other files instantiate
+ * templates or use macros or inline functions from this file, or you compile
+ * this file and link it with other files to produce an executable, this
+ * file does not by itself cause the resulting executable to be covered by
+ * the GNU General Public License. This exception does not however
+ * invalidate any other reasons why the executable file might be covered by
+ * the GNU General Public License.
+ */
+
+package cc.arduino.contributions;
+
+import cc.arduino.contributions.packages.ContributedPlatform;
+import processing.app.Base;
+import processing.app.BaseNoGui;
+import processing.app.I18n;
+import processing.app.PreferencesData;
+
+import javax.swing.*;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+import static processing.app.I18n.tr;
+
+public class BuiltInCoreIsNewerCheck implements Runnable {
+
+ private final Base base;
+
+ public BuiltInCoreIsNewerCheck(Base base) {
+ this.base = base;
+ }
+
+ @Override
+ public void run() {
+ try {
+ builtInPackageIsNewerCheck();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private void builtInPackageIsNewerCheck() throws InterruptedException {
+ if (PreferencesData.getInteger("builtin_platform_is_newer", -1) >= BaseNoGui.REVISION) {
+ return;
+ }
+
+ List contributedPlatforms = BaseNoGui.indexer
+ .getPackages().stream() //
+ .map(pack -> pack.getPlatforms()) //
+ .flatMap(platfs -> platfs.stream()) //
+ .collect(Collectors.toList());
+
+ Optional mayInstalledBuiltIn = contributedPlatforms
+ .stream() //
+ .filter(p -> p.isInstalled()) //
+ .filter(p -> p.isBuiltIn()) //
+ .findFirst();
+ if (!mayInstalledBuiltIn.isPresent()) {
+ return;
+ }
+ final ContributedPlatform installedBuiltIn = mayInstalledBuiltIn.get();
+
+ ContributedPlatform installedNotBuiltIn = BaseNoGui.indexer.getInstalled(installedBuiltIn.getParentPackage().getName(), installedBuiltIn.getArchitecture());
+ if (installedNotBuiltIn == null) {
+ return;
+ }
+
+ while (!base.hasActiveEditor()) {
+ Thread.sleep(100);
+ }
+
+ if (VersionComparator.greaterThan(installedBuiltIn.getParsedVersion(), installedNotBuiltIn.getParsedVersion())) {
+ SwingUtilities.invokeLater(() -> {
+ PreferencesData.setInteger("builtin_platform_is_newer", BaseNoGui.REVISION);
+ assert base.hasActiveEditor();
+ int chosenOption = JOptionPane.showConfirmDialog(base.getActiveEditor(), I18n.format(tr("The IDE includes an updated {0} package, but you're using an older one.\nDo you want to upgrade {0}?"), installedBuiltIn.getName()), I18n.format(tr("A newer {0} package is available"), installedBuiltIn.getName()), JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
+ if (chosenOption == JOptionPane.YES_OPTION) {
+ try {
+ base.openBoardsManager(installedBuiltIn.getName(), "");
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ });
+ }
+ }
+}
diff --git a/app/src/cc/arduino/contributions/ContributionsSelfCheck.java b/app/src/cc/arduino/contributions/ContributionsSelfCheck.java
new file mode 100644
index 00000000000..50e5e8617ea
--- /dev/null
+++ b/app/src/cc/arduino/contributions/ContributionsSelfCheck.java
@@ -0,0 +1,221 @@
+/*
+ * This file is part of Arduino.
+ *
+ * Copyright 2015 Arduino LLC (http://www.arduino.cc/)
+ *
+ * Arduino is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * As a special exception, you may use this file as part of a free software
+ * library without restriction. Specifically, if other files instantiate
+ * templates or use macros or inline functions from this file, or you compile
+ * this file and link it with other files to produce an executable, this
+ * file does not by itself cause the resulting executable to be covered by
+ * the GNU General Public License. This exception does not however
+ * invalidate any other reasons why the executable file might be covered by
+ * the GNU General Public License.
+ */
+
+package cc.arduino.contributions;
+
+import cc.arduino.UpdatableBoardsLibsFakeURLsHandler;
+import cc.arduino.contributions.libraries.LibraryInstaller;
+import cc.arduino.contributions.libraries.filters.UpdatableLibraryPredicate;
+import cc.arduino.contributions.packages.ContributionInstaller;
+import cc.arduino.contributions.packages.filters.UpdatablePlatformPredicate;
+import cc.arduino.view.NotificationPopup;
+import processing.app.*;
+
+import javax.swing.*;
+import javax.swing.event.HyperlinkListener;
+
+import java.awt.event.WindowEvent;
+import java.awt.event.WindowFocusListener;
+import java.net.URL;
+import java.util.TimerTask;
+
+import static processing.app.I18n.tr;
+
+public class ContributionsSelfCheck extends TimerTask implements NotificationPopup.OptionalButtonCallbacks {
+
+ private final Base base;
+ private final HyperlinkListener hyperlinkListener;
+ private final ContributionInstaller contributionInstaller;
+ private final LibraryInstaller libraryInstaller;
+ private final ProgressListener progressListener;
+ private final String boardsManagerURL = "http://boardsmanager/DropdownUpdatableCoresItem";
+ private final String libraryManagerURL = "http://librarymanager/DropdownUpdatableLibrariesItem";
+
+ private volatile boolean cancelled;
+ private volatile NotificationPopup notificationPopup;
+
+ public ContributionsSelfCheck(Base base, HyperlinkListener hyperlinkListener, ContributionInstaller contributionInstaller, LibraryInstaller libraryInstaller) {
+ this.base = base;
+ this.hyperlinkListener = hyperlinkListener;
+ this.contributionInstaller = contributionInstaller;
+ this.libraryInstaller = libraryInstaller;
+ this.progressListener = new NoopProgressListener();
+ this.cancelled = false;
+ }
+
+ @Override
+ public void run() {
+ updateContributionIndex();
+ updateLibrariesIndex();
+
+ boolean updatablePlatforms = checkForUpdatablePlatforms();
+
+ boolean updatableLibraries = checkForUpdatableLibraries();
+
+ if (!updatableLibraries && !updatablePlatforms) {
+ return;
+ }
+
+ boolean setAccessible = PreferencesData.getBoolean("ide.accessible");
+ final String text;
+ final String button1Name;
+ final String button2Name;
+ String openAnchorBoards = "";
+ String closeAnchorBoards = "";
+ String openAnchorLibraries = "";
+ String closeAnchorLibraries = "";
+
+ // if accessibility mode and board updates are available set the button name and clear the anchors
+ if(setAccessible && updatablePlatforms) {
+ button1Name = tr("Boards");
+ openAnchorBoards = "";
+ closeAnchorBoards = "";
+ }
+ else { // when not accessibility mode or no boards to update no button is needed
+ button1Name = null;
+ }
+
+ // if accessibility mode and libraries updates are available set the button name and clear the anchors
+ if (setAccessible && updatableLibraries) {
+ button2Name = tr("Libraries");
+ openAnchorLibraries = "";
+ closeAnchorLibraries = "";
+ }
+ else { // when not accessibility mode or no libraries to update no button is needed
+ button2Name = null;
+ }
+
+ if (updatableLibraries && !updatablePlatforms) {
+ text = I18n.format(tr("Updates available for some of your {0}libraries{1}"), openAnchorLibraries, closeAnchorLibraries);
+ } else if (!updatableLibraries && updatablePlatforms) {
+ text = I18n.format(tr("Updates available for some of your {0}boards{1}"), openAnchorBoards, closeAnchorBoards);
+ } else {
+ text = I18n.format(tr("Updates available for some of your {0}boards{1} and {2}libraries{3}"), openAnchorBoards, closeAnchorBoards, openAnchorLibraries, closeAnchorLibraries);
+ }
+
+ if (cancelled) {
+ return;
+ }
+
+ SwingUtilities.invokeLater(() -> {
+ Editor ed = base.getActiveEditor();
+ boolean accessibleIde = PreferencesData.getBoolean("ide.accessible");
+ if (accessibleIde) {
+ notificationPopup = new NotificationPopup(ed, hyperlinkListener, text, false, this, button1Name, button2Name);
+ }
+ else { // if not accessible view leave it the same
+ notificationPopup = new NotificationPopup(ed, hyperlinkListener, text);
+ }
+ if (ed.isFocused()) {
+ notificationPopup.begin();
+ return;
+ }
+
+ // If the IDE is not focused wait until it is focused again to
+ // display the notification, this avoids the annoying side effect
+ // to "steal" the focus from another application.
+ WindowFocusListener wfl = new WindowFocusListener() {
+ @Override
+ public void windowLostFocus(WindowEvent evt) {
+ }
+
+ @Override
+ public void windowGainedFocus(WindowEvent evt) {
+ notificationPopup.begin();
+ for (Editor e : base.getEditors())
+ e.removeWindowFocusListener(this);
+ }
+ };
+ for (Editor e : base.getEditors())
+ e.addWindowFocusListener(wfl);
+ });
+ }
+
+ private void goToManager(String link) {
+ try {
+ ((UpdatableBoardsLibsFakeURLsHandler) hyperlinkListener)
+ .openBoardLibManager(new URL(link));
+ } catch (Exception e) {
+ System.err.println("Error while attempting to open board manager: "
+ + e.getMessage());
+ }
+ }
+
+ // callback for boards button
+ public void onOptionalButton1Callback() {
+ goToManager(boardsManagerURL);
+ }
+
+ // callback for libraries button
+ public void onOptionalButton2Callback() {
+ goToManager(libraryManagerURL);
+ }
+
+ static boolean checkForUpdatablePlatforms() {
+ return BaseNoGui.indexer.getPackages().stream()
+ .flatMap(pack -> pack.getPlatforms().stream())
+ .anyMatch(new UpdatablePlatformPredicate());
+ }
+
+ static boolean checkForUpdatableLibraries() {
+ return BaseNoGui.librariesIndexer.getIndex().getLibraries().stream()
+ .anyMatch(new UpdatableLibraryPredicate());
+ }
+
+ @Override
+ public boolean cancel() {
+ cancelled = true;
+ if (notificationPopup != null) {
+ notificationPopup.close();
+ }
+ return super.cancel();
+ }
+
+ private void updateLibrariesIndex() {
+ if (cancelled) {
+ return;
+ }
+ try {
+ libraryInstaller.updateIndex(progressListener);
+ } catch (Exception e) {
+ // ignore
+ }
+ }
+
+ private void updateContributionIndex() {
+ if (cancelled) {
+ return;
+ }
+ try {
+ contributionInstaller.updateIndex(progressListener);
+ } catch (Exception e) {
+ // ignore
+ }
+ }
+}
diff --git a/app/src/cc/arduino/contributions/libraries/LibraryOfSameTypeComparator.java b/app/src/cc/arduino/contributions/libraries/LibraryOfSameTypeComparator.java
new file mode 100644
index 00000000000..74bd3767518
--- /dev/null
+++ b/app/src/cc/arduino/contributions/libraries/LibraryOfSameTypeComparator.java
@@ -0,0 +1,55 @@
+/*
+ * This file is part of Arduino.
+ *
+ * Copyright 2015 Arduino LLC (http://www.arduino.cc/)
+ *
+ * Arduino is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * As a special exception, you may use this file as part of a free software
+ * library without restriction. Specifically, if other files instantiate
+ * templates or use macros or inline functions from this file, or you compile
+ * this file and link it with other files to produce an executable, this
+ * file does not by itself cause the resulting executable to be covered by
+ * the GNU General Public License. This exception does not however
+ * invalidate any other reasons why the executable file might be covered by
+ * the GNU General Public License.
+ */
+
+package cc.arduino.contributions.libraries;
+
+import java.util.Comparator;
+
+import processing.app.packages.UserLibrary;
+
+public class LibraryOfSameTypeComparator implements Comparator {
+
+ @Override
+ public int compare(UserLibrary o1, UserLibrary o2) {
+ if (o1.getTypes().isEmpty() && o2.getTypes().isEmpty()) {
+ return 0;
+ }
+ if (o1.getTypes().isEmpty()) {
+ return 1;
+ }
+ if (o2.getTypes().isEmpty()) {
+ return -1;
+ }
+ if (!o1.getTypes().get(0).equals(o2.getTypes().get(0))) {
+ return o1.getTypes().get(0).compareTo(o2.getTypes().get(0));
+ }
+ return o1.getName().compareTo(o2.getName());
+ }
+
+}
diff --git a/app/src/cc/arduino/contributions/libraries/LibraryTypeComparator.java b/app/src/cc/arduino/contributions/libraries/LibraryTypeComparator.java
new file mode 100644
index 00000000000..ba53403acf4
--- /dev/null
+++ b/app/src/cc/arduino/contributions/libraries/LibraryTypeComparator.java
@@ -0,0 +1,60 @@
+/*
+ * This file is part of Arduino.
+ *
+ * Copyright 2015 Arduino LLC (http://www.arduino.cc/)
+ *
+ * Arduino is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * As a special exception, you may use this file as part of a free software
+ * library without restriction. Specifically, if other files instantiate
+ * templates or use macros or inline functions from this file, or you compile
+ * this file and link it with other files to produce an executable, this
+ * file does not by itself cause the resulting executable to be covered by
+ * the GNU General Public License. This exception does not however
+ * invalidate any other reasons why the executable file might be covered by
+ * the GNU General Public License.
+ */
+
+package cc.arduino.contributions.libraries;
+
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+
+public class LibraryTypeComparator implements Comparator {
+
+ private final List types;
+
+ public LibraryTypeComparator() {
+ this("Arduino", "Partner", "Recommended", "Contributed");
+ }
+
+ public LibraryTypeComparator(String... types) {
+ this.types = Arrays.asList(types);
+ }
+
+ @Override
+ public int compare(String o1, String o2) {
+ if (types.contains(o1) && types.contains(o2)) {
+ return types.indexOf(o1) - types.indexOf(o2);
+ } else if (types.contains(o1)) {
+ return -1;
+ } else if (types.contains(o2)) {
+ return 1;
+ }
+ return o1.compareTo(o2);
+ }
+
+}
diff --git a/app/src/cc/arduino/contributions/libraries/filters/UpdatableLibraryPredicate.java b/app/src/cc/arduino/contributions/libraries/filters/UpdatableLibraryPredicate.java
new file mode 100644
index 00000000000..e96f1759423
--- /dev/null
+++ b/app/src/cc/arduino/contributions/libraries/filters/UpdatableLibraryPredicate.java
@@ -0,0 +1,62 @@
+/*
+ * This file is part of Arduino.
+ *
+ * Copyright 2015 Arduino LLC (http://www.arduino.cc/)
+ *
+ * Arduino is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * As a special exception, you may use this file as part of a free software
+ * library without restriction. Specifically, if other files instantiate
+ * templates or use macros or inline functions from this file, or you compile
+ * this file and link it with other files to produce an executable, this
+ * file does not by itself cause the resulting executable to be covered by
+ * the GNU General Public License. This exception does not however
+ * invalidate any other reasons why the executable file might be covered by
+ * the GNU General Public License.
+ */
+
+package cc.arduino.contributions.libraries.filters;
+
+import java.util.List;
+import java.util.function.Predicate;
+
+import cc.arduino.contributions.VersionComparator;
+import cc.arduino.contributions.libraries.ContributedLibrary;
+import cc.arduino.contributions.libraries.LibrariesIndexer;
+import processing.app.BaseNoGui;
+
+public class UpdatableLibraryPredicate implements Predicate {
+
+ LibrariesIndexer librariesIndexer;
+
+ public UpdatableLibraryPredicate() {
+ librariesIndexer = BaseNoGui.librariesIndexer;
+ }
+
+ public UpdatableLibraryPredicate(LibrariesIndexer indexer) {
+ librariesIndexer = indexer;
+ }
+
+ @Override
+ public boolean test(ContributedLibrary lib) {
+ if (!lib.isLibraryInstalled()) {
+ return false;
+ }
+ String libraryName = lib.getName();
+ List libraries = librariesIndexer.getIndex().find(libraryName);
+ ContributedLibrary latest = libraries.stream().reduce(VersionComparator::max).get();
+ return !latest.isLibraryInstalled();
+ }
+}
diff --git a/app/src/cc/arduino/contributions/libraries/ui/ContributedLibraryReleasesComparator.java b/app/src/cc/arduino/contributions/libraries/ui/ContributedLibraryReleasesComparator.java
new file mode 100644
index 00000000000..11436b2ccfb
--- /dev/null
+++ b/app/src/cc/arduino/contributions/libraries/ui/ContributedLibraryReleasesComparator.java
@@ -0,0 +1,73 @@
+/*
+ * This file is part of Arduino.
+ *
+ * Copyright 2015 Arduino LLC (http://www.arduino.cc/)
+ *
+ * Arduino is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * As a special exception, you may use this file as part of a free software
+ * library without restriction. Specifically, if other files instantiate
+ * templates or use macros or inline functions from this file, or you compile
+ * this file and link it with other files to produce an executable, this
+ * file does not by itself cause the resulting executable to be covered by
+ * the GNU General Public License. This exception does not however
+ * invalidate any other reasons why the executable file might be covered by
+ * the GNU General Public License.
+ */
+
+package cc.arduino.contributions.libraries.ui;
+
+import cc.arduino.contributions.libraries.ContributedLibrary;
+import cc.arduino.contributions.libraries.ContributedLibraryReleases;
+
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+
+public class ContributedLibraryReleasesComparator implements Comparator {
+
+ private final String firstType;
+
+ public ContributedLibraryReleasesComparator(String firstType) {
+ this.firstType = firstType;
+ }
+
+ @Override
+ public int compare(ContributedLibraryReleases o1, ContributedLibraryReleases o2) {
+ ContributedLibrary lib1 = o1.getLatest();
+ ContributedLibrary lib2 = o2.getLatest();
+
+ List types1 = lib1.getTypes();
+ List types2 = lib2.getTypes();
+ if (types1 == null) types1 = Arrays.asList();
+ if (types2 == null) types2 = Arrays.asList();
+
+ if (lib1.getTypes().contains(firstType) && lib2.getTypes().contains(firstType)) {
+ return compareName(lib1, lib2);
+ }
+ if (lib1.getTypes().contains(firstType)) {
+ return -1;
+ }
+ if (lib2.getTypes().contains(firstType)) {
+ return 1;
+ }
+ return compareName(lib1, lib2);
+ }
+
+ private int compareName(ContributedLibrary lib1, ContributedLibrary lib2) {
+ return lib1.getName().compareToIgnoreCase(lib2.getName());
+ }
+
+}
diff --git a/app/src/cc/arduino/contributions/libraries/ui/ContributedLibraryTableCellEditor.java b/app/src/cc/arduino/contributions/libraries/ui/ContributedLibraryTableCellEditor.java
new file mode 100644
index 00000000000..7c2ecff383f
--- /dev/null
+++ b/app/src/cc/arduino/contributions/libraries/ui/ContributedLibraryTableCellEditor.java
@@ -0,0 +1,151 @@
+/*
+ * This file is part of Arduino.
+ *
+ * Copyright 2015 Arduino LLC (http://www.arduino.cc/)
+ *
+ * Arduino is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * As a special exception, you may use this file as part of a free software
+ * library without restriction. Specifically, if other files instantiate
+ * templates or use macros or inline functions from this file, or you compile
+ * this file and link it with other files to produce an executable, this
+ * file does not by itself cause the resulting executable to be covered by
+ * the GNU General Public License. This exception does not however
+ * invalidate any other reasons why the executable file might be covered by
+ * the GNU General Public License.
+ */
+
+package cc.arduino.contributions.libraries.ui;
+
+import static processing.app.I18n.tr;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Optional;
+
+import javax.swing.JComboBox;
+import javax.swing.JTable;
+
+import cc.arduino.contributions.DownloadableContributionVersionComparator;
+import cc.arduino.contributions.VersionComparator;
+import cc.arduino.contributions.libraries.ContributedLibrary;
+import cc.arduino.contributions.libraries.ContributedLibraryReleases;
+import cc.arduino.contributions.ui.InstallerTableCell;
+import cc.arduino.utils.ReverseComparator;
+
+@SuppressWarnings("serial")
+public class ContributedLibraryTableCellEditor extends InstallerTableCell {
+
+ private ContributedLibraryReleases editorValue;
+ private ContributedLibraryTableCellJPanel editorCell;
+
+ @Override
+ public Object getCellEditorValue() {
+ return editorValue;
+ }
+
+ @Override
+ public Component getTableCellEditorComponent(JTable table, Object value,
+ boolean isSelected, int row,
+ int column) {
+ editorValue = (ContributedLibraryReleases) value;
+
+ editorCell = new ContributedLibraryTableCellJPanel(table, value, true);
+ editorCell.installButton
+ .addActionListener(e -> onInstall(editorValue.getSelected(),
+ editorValue.getInstalled()));
+ editorCell.downgradeButton.addActionListener(e -> {
+ JComboBox chooser = editorCell.downgradeChooser;
+ ContributedLibrary lib = (ContributedLibrary) chooser.getSelectedItem();
+ onInstall(lib, editorValue.getInstalled());
+ });
+ editorCell.versionToInstallChooser.addActionListener(e -> {
+ editorValue.select((ContributedLibrary) editorCell.versionToInstallChooser.getSelectedItem());
+ if (editorCell.versionToInstallChooser.getSelectedIndex() != 0) {
+ InstallerTableCell.dropdownSelected(true);
+ }
+ });
+
+ setEnabled(true);
+
+ final Optional mayInstalled = editorValue.getInstalled();
+
+ List releases = editorValue.getReleases();
+ List notInstalled = new LinkedList<>(releases);
+ if (mayInstalled.isPresent()) {
+ notInstalled.remove(editorValue.getInstalled().get());
+ }
+
+ Collections.sort(notInstalled, new ReverseComparator<>(
+ new DownloadableContributionVersionComparator()));
+
+ editorCell.downgradeChooser.removeAllItems();
+ editorCell.downgradeChooser.addItem(tr("Select version"));
+
+ final List notInstalledPrevious = new LinkedList<>();
+ final List notInstalledNewer = new LinkedList<>();
+
+ notInstalled.stream().forEach(input -> {
+ if (!mayInstalled.isPresent()
+ || VersionComparator.greaterThan(mayInstalled.get(), input)) {
+ notInstalledPrevious.add(input);
+ } else {
+ notInstalledNewer.add(input);
+ }
+ });
+ notInstalledNewer.forEach(editorCell.downgradeChooser::addItem);
+ notInstalledPrevious.forEach(editorCell.downgradeChooser::addItem);
+
+ editorCell.downgradeChooser
+ .setVisible(mayInstalled.isPresent()
+ && (!notInstalledPrevious.isEmpty()
+ || notInstalledNewer.size() > 1));
+ editorCell.downgradeButton
+ .setVisible(mayInstalled.isPresent()
+ && (!notInstalledPrevious.isEmpty()
+ || notInstalledNewer.size() > 1));
+
+ editorCell.versionToInstallChooser.removeAllItems();
+ notInstalled.forEach(editorCell.versionToInstallChooser::addItem);
+ editorCell.versionToInstallChooser
+ .setVisible(!mayInstalled.isPresent() && notInstalled.size() > 1);
+
+ editorCell.setForeground(Color.BLACK);
+ editorCell.setBackground(new Color(218, 227, 227)); // #dae3e3
+ return editorCell;
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ editorCell.setButtonsVisible(enabled);
+ }
+
+ public void setStatus(String status) {
+ editorCell.statusLabel.setText(status);
+ }
+
+ protected void onRemove(ContributedLibrary selected) {
+ // Empty
+ }
+
+ protected void onInstall(ContributedLibrary selected,
+ Optional mayInstalled) {
+ // Empty
+ }
+
+}
diff --git a/app/src/cc/arduino/contributions/libraries/ui/ContributedLibraryTableCellJPanel.java b/app/src/cc/arduino/contributions/libraries/ui/ContributedLibraryTableCellJPanel.java
new file mode 100644
index 00000000000..a5bb940babc
--- /dev/null
+++ b/app/src/cc/arduino/contributions/libraries/ui/ContributedLibraryTableCellJPanel.java
@@ -0,0 +1,281 @@
+package cc.arduino.contributions.libraries.ui;
+
+import static processing.app.I18n.format;
+import static processing.app.I18n.tr;
+
+import java.awt.*;
+import java.util.Optional;
+
+import javax.swing.*;
+import javax.swing.border.EmptyBorder;
+import javax.swing.border.TitledBorder;
+import javax.swing.event.HyperlinkEvent;
+import javax.swing.text.Document;
+import javax.swing.text.html.HTMLDocument;
+import javax.swing.text.html.StyleSheet;
+
+import cc.arduino.contributions.DownloadableContributionVersionComparator;
+import cc.arduino.contributions.libraries.ContributedLibrary;
+import cc.arduino.contributions.libraries.ContributedLibraryReleases;
+import cc.arduino.contributions.ui.InstallerTableCell;
+import processing.app.Base;
+import processing.app.PreferencesData;
+import processing.app.Theme;
+
+public class ContributedLibraryTableCellJPanel extends JPanel {
+
+ final JButton moreInfoButton;
+ final JButton installButton;
+ final Component installButtonPlaceholder;
+ final JComboBox downgradeChooser;
+ final JComboBox versionToInstallChooser;
+ final JButton downgradeButton;
+ final JPanel buttonsPanel;
+ final JPanel inactiveButtonsPanel;
+ final JLabel statusLabel;
+ final JTextPane description;
+ final TitledBorder titledBorder;
+ private final String moreInfoLbl = tr("More info");
+
+ public ContributedLibraryTableCellJPanel(JTable parentTable, Object value,
+ boolean isSelected) {
+ super();
+ setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
+
+ // Actual title set below
+ titledBorder = BorderFactory.createTitledBorder("");
+ titledBorder.setTitleFont(getFont().deriveFont(Font.BOLD));
+ setBorder(titledBorder);
+
+ moreInfoButton = new JButton(moreInfoLbl);
+ moreInfoButton.setVisible(false);
+ installButton = new JButton(tr("Install"));
+ int width = installButton.getPreferredSize().width;
+ installButtonPlaceholder = Box.createRigidArea(new Dimension(width, 1));
+
+ downgradeButton = new JButton(tr("Install"));
+
+ downgradeChooser = new JComboBox();
+ downgradeChooser.addItem("-");
+ downgradeChooser.setMaximumSize(new Dimension((int)downgradeChooser.getPreferredSize().getWidth() + 50, (int)downgradeChooser.getPreferredSize().getHeight()));
+ downgradeChooser.setMinimumSize(new Dimension((int)downgradeChooser.getPreferredSize().getWidth() + 50, (int)downgradeChooser.getPreferredSize().getHeight()));
+ downgradeChooser.addActionListener(e -> {
+ Object selectVersionItem = downgradeChooser.getItemAt(0);
+ boolean disableDowngrade = (downgradeChooser.getSelectedItem() == selectVersionItem);
+ downgradeButton.setEnabled(!disableDowngrade);
+ if (!disableDowngrade) {
+ InstallerTableCell.dropdownSelected(true);
+ }
+ });
+
+ versionToInstallChooser = new JComboBox();
+ versionToInstallChooser.addItem("-");
+ versionToInstallChooser
+ .setMaximumSize(new Dimension((int)versionToInstallChooser.getPreferredSize().getWidth() + 50, (int)versionToInstallChooser.getPreferredSize().getHeight()));
+ versionToInstallChooser
+ .setMinimumSize(new Dimension((int)versionToInstallChooser.getPreferredSize().getWidth() + 50, (int)versionToInstallChooser.getPreferredSize().getHeight()));
+
+ description = makeNewDescription();
+ add(description);
+
+ buttonsPanel = new JPanel();
+ buttonsPanel.setLayout(new BoxLayout(buttonsPanel, BoxLayout.X_AXIS));
+ buttonsPanel.setOpaque(false);
+
+ buttonsPanel.add(Box.createHorizontalStrut(7));
+ if (PreferencesData.getBoolean("ide.accessible")) {
+ buttonsPanel.add(moreInfoButton);
+ buttonsPanel.add(Box.createHorizontalStrut(5));
+ buttonsPanel.add(Box.createHorizontalStrut(15));
+ }
+ buttonsPanel.add(downgradeChooser);
+ buttonsPanel.add(Box.createHorizontalStrut(5));
+ buttonsPanel.add(downgradeButton);
+
+ buttonsPanel.add(Box.createHorizontalGlue());
+
+ buttonsPanel.add(versionToInstallChooser);
+ buttonsPanel.add(Box.createHorizontalStrut(5));
+ buttonsPanel.add(installButton);
+ buttonsPanel.add(Box.createHorizontalStrut(5));
+ buttonsPanel.add(Box.createHorizontalStrut(15));
+
+ add(buttonsPanel);
+
+ inactiveButtonsPanel = new JPanel();
+ inactiveButtonsPanel
+ .setLayout(new BoxLayout(inactiveButtonsPanel, BoxLayout.X_AXIS));
+ inactiveButtonsPanel.setOpaque(false);
+
+ int height = installButton.getMinimumSize().height;
+ inactiveButtonsPanel.add(Box.createVerticalStrut(height));
+ inactiveButtonsPanel.add(Box.createGlue());
+
+ statusLabel = new JLabel(" ");
+ inactiveButtonsPanel.add(statusLabel);
+ inactiveButtonsPanel.add(Box.createHorizontalStrut(15));
+
+ add(inactiveButtonsPanel);
+
+ add(Box.createVerticalStrut(15));
+
+ ContributedLibraryReleases releases = (ContributedLibraryReleases) value;
+
+ // FIXME: happens on macosx, don't know why
+ if (releases == null)
+ return;
+
+ ContributedLibrary selected = releases.getSelected();
+ titledBorder.setTitle(selected.getName());
+ Optional mayInstalled = releases.getInstalled();
+
+ boolean installable, upgradable;
+ if (!mayInstalled.isPresent()) {
+ installable = true;
+ upgradable = false;
+ } else {
+ installable = false;
+ upgradable = new DownloadableContributionVersionComparator()
+ .compare(selected, mayInstalled.get()) > 0;
+ }
+ if (installable) {
+ installButton.setText(tr("Install"));
+ }
+ if (upgradable) {
+ installButton.setText(tr("Update"));
+ }
+ installButton.setVisible(installable || upgradable);
+ installButtonPlaceholder.setVisible(!(installable || upgradable));
+
+ String name = selected.getName();
+ // String author = selected.getAuthor();
+ String maintainer = selected.getMaintainer();
+ final String website = selected.getWebsite();
+ String sentence = selected.getSentence();
+ String paragraph = selected.getParagraph();
+ // String availableVer = selectedLib.getVersion();
+ // String url = selected.getUrl();
+
+ String midcolor = isSelected ? "#000000" : "#888888";
+
+ String desc = "";
+
+ // Library name...
+// desc += format("{0}", name);
+ if (mayInstalled.isPresent() && mayInstalled.get().isIDEBuiltIn()) {
+ desc += " Built-In ";
+ }
+
+ // ...author...
+ desc += format("", midcolor);
+ if (maintainer != null && !maintainer.isEmpty()) {
+ desc += format(" by {0}", maintainer);
+ }
+
+ // ...version.
+ if (mayInstalled.isPresent()) {
+ String installedVer = mayInstalled.get().getParsedVersion();
+ if (installedVer == null) {
+ desc += " " + tr("Version unknown");
+ } else {
+ desc += " " + format(tr("Version {0}"), installedVer);
+ }
+ }
+ desc += "";
+
+ if (mayInstalled.isPresent()) {
+ desc += " INSTALLED";
+ }
+
+ desc += "
";
+
+ // Description
+ if (sentence != null) {
+ desc += format("{0} ", sentence);
+ if (paragraph != null && !paragraph.isEmpty())
+ desc += format("{0}", paragraph);
+ desc += "
";
+ }
+ if (maintainer != null && !maintainer.isEmpty()) {
+ desc = setButtonOrLink(moreInfoButton, desc, moreInfoLbl, website);
+ }
+
+ desc += "";
+ description.setText(desc);
+ // copy description to accessibility context for screen readers to use
+ description.getAccessibleContext().setAccessibleDescription(desc);
+
+ // for modelToView to work, the text area has to be sized. It doesn't
+ // matter if it's visible or not.
+
+ // See:
+ // http://stackoverflow.com/questions/3081210/how-to-set-jtextarea-to-have-height-that-matches-the-size-of-a-text-it-contains
+ InstallerTableCell
+ .setJTextPaneDimensionToFitContainedText(description,
+ parentTable.getBounds().width);
+ }
+
+ // same function as in ContributedPlatformTableCellJPanel - is there a utils file this can move to?
+ private String setButtonOrLink(JButton button, String desc, String label, String url) {
+ boolean accessibleIDE = PreferencesData.getBoolean("ide.accessible");
+ String retString = desc;
+
+ if (accessibleIDE) {
+ button.setVisible(true);
+ button.addActionListener(e -> {
+ Base.openURL(url);
+ });
+ }
+ else {
+ // if not accessible IDE, keep link the same EXCEPT that now the link text is translated!
+ retString += format("{1}
", url, label);
+ }
+
+ return retString;
+ }
+
+ // TODO Make this a method of Theme
+ private JTextPane makeNewDescription() {
+ JTextPane description = new JTextPane();
+ description.setInheritsPopupMenu(true);
+ Insets margin = description.getMargin();
+ margin.bottom = 0;
+ description.setMargin(margin);
+ description.setContentType("text/html");
+ Document doc = description.getDocument();
+ if (doc instanceof HTMLDocument) {
+ HTMLDocument html = (HTMLDocument) doc;
+ StyleSheet s = html.getStyleSheet();
+ s.addRule("body { margin: 0; padding: 0;"
+ + "font-family: Verdana, Geneva, Arial, Helvetica, sans-serif;"
+ + "color: black;"
+ + "font-size: " + 10 * Theme.getScale() / 100 + "; }");
+ }
+ description.setOpaque(false);
+ description.setBorder(new EmptyBorder(4, 7, 7, 7));
+ description.setHighlighter(null);
+ description.setEditable(false);
+ description.addHyperlinkListener(e -> {
+ if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
+ Base.openURL(e.getDescription());
+ }
+ });
+ // description.addKeyListener(new DelegatingKeyListener(parentTable));
+ return description;
+ }
+
+ public void setButtonsVisible(boolean enabled) {
+ installButton.setEnabled(enabled);
+ buttonsPanel.setVisible(enabled);
+ inactiveButtonsPanel.setVisible(!enabled);
+ }
+
+ public void setForeground(Color c) {
+ super.setForeground(c);
+ // The description is not opaque, so copy our foreground color to it.
+ if (description != null)
+ description.setForeground(c);
+ if (titledBorder != null)
+ titledBorder.setTitleColor(c);
+ }
+}
diff --git a/app/src/cc/arduino/contributions/libraries/ui/ContributedLibraryTableCellRenderer.java b/app/src/cc/arduino/contributions/libraries/ui/ContributedLibraryTableCellRenderer.java
new file mode 100644
index 00000000000..d107f90208a
--- /dev/null
+++ b/app/src/cc/arduino/contributions/libraries/ui/ContributedLibraryTableCellRenderer.java
@@ -0,0 +1,64 @@
+/*
+ * This file is part of Arduino.
+ *
+ * Copyright 2015 Arduino LLC (http://www.arduino.cc/)
+ *
+ * Arduino is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * As a special exception, you may use this file as part of a free software
+ * library without restriction. Specifically, if other files instantiate
+ * templates or use macros or inline functions from this file, or you compile
+ * this file and link it with other files to produce an executable, this
+ * file does not by itself cause the resulting executable to be covered by
+ * the GNU General Public License. This exception does not however
+ * invalidate any other reasons why the executable file might be covered by
+ * the GNU General Public License.
+ */
+
+package cc.arduino.contributions.libraries.ui;
+
+import java.awt.Color;
+import java.awt.Component;
+
+import javax.swing.JTable;
+import javax.swing.table.TableCellRenderer;
+
+@SuppressWarnings("serial")
+public class ContributedLibraryTableCellRenderer implements TableCellRenderer {
+
+ public Component getTableCellRendererComponent(JTable table, Object value,
+ boolean isSelected,
+ boolean hasFocus, int row,
+ int column) {
+ ContributedLibraryTableCellJPanel cell = new ContributedLibraryTableCellJPanel(table,
+ value, isSelected);
+ cell.setButtonsVisible(false);
+
+ cell.setForeground(Color.BLACK);
+ if (row % 2 == 0) {
+ cell.setBackground(new Color(236, 241, 241)); // #ecf1f1
+ } else {
+ cell.setBackground(new Color(255, 255, 255));
+ }
+
+ int height = new Double(cell.getPreferredSize().getHeight()).intValue();
+ if (table.getRowHeight(row) < height) {
+ table.setRowHeight(row, height);
+ }
+
+ return cell;
+ }
+
+}
diff --git a/app/src/cc/arduino/contributions/libraries/ui/DropdownAllLibraries.java b/app/src/cc/arduino/contributions/libraries/ui/DropdownAllLibraries.java
new file mode 100644
index 00000000000..ce50aca1432
--- /dev/null
+++ b/app/src/cc/arduino/contributions/libraries/ui/DropdownAllLibraries.java
@@ -0,0 +1,50 @@
+/*
+ * This file is part of Arduino.
+ *
+ * Copyright 2015 Arduino LLC (http://www.arduino.cc/)
+ *
+ * Arduino is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * As a special exception, you may use this file as part of a free software
+ * library without restriction. Specifically, if other files instantiate
+ * templates or use macros or inline functions from this file, or you compile
+ * this file and link it with other files to produce an executable, this
+ * file does not by itself cause the resulting executable to be covered by
+ * the GNU General Public License. This exception does not however
+ * invalidate any other reasons why the executable file might be covered by
+ * the GNU General Public License.
+ */
+
+package cc.arduino.contributions.libraries.ui;
+
+import cc.arduino.contributions.libraries.ContributedLibraryReleases;
+import cc.arduino.contributions.ui.DropdownItem;
+
+import java.util.function.Predicate;
+
+import static processing.app.I18n.tr;
+
+public class DropdownAllLibraries implements DropdownItem {
+
+ public String toString() {
+ return tr("All");
+ }
+
+ @Override
+ public Predicate getFilterPredicate() {
+ return x -> true;
+ }
+
+}
diff --git a/app/src/cc/arduino/contributions/libraries/ui/DropdownInstalledLibraryItem.java b/app/src/cc/arduino/contributions/libraries/ui/DropdownInstalledLibraryItem.java
new file mode 100644
index 00000000000..e5b42e3b37a
--- /dev/null
+++ b/app/src/cc/arduino/contributions/libraries/ui/DropdownInstalledLibraryItem.java
@@ -0,0 +1,55 @@
+/*
+ * This file is part of Arduino.
+ *
+ * Copyright 2015 Arduino LLC (http://www.arduino.cc/)
+ *
+ * Arduino is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * As a special exception, you may use this file as part of a free software
+ * library without restriction. Specifically, if other files instantiate
+ * templates or use macros or inline functions from this file, or you compile
+ * this file and link it with other files to produce an executable, this
+ * file does not by itself cause the resulting executable to be covered by
+ * the GNU General Public License. This exception does not however
+ * invalidate any other reasons why the executable file might be covered by
+ * the GNU General Public License.
+ */
+
+package cc.arduino.contributions.libraries.ui;
+
+import static processing.app.I18n.tr;
+
+import java.util.function.Predicate;
+
+import cc.arduino.contributions.libraries.ContributedLibraryReleases;
+import cc.arduino.contributions.ui.DropdownItem;
+
+public class DropdownInstalledLibraryItem implements DropdownItem {
+
+ public String toString() {
+ return tr("Installed");
+ }
+
+ @Override
+ public Predicate getFilterPredicate() {
+ return new Predicate() {
+ @Override
+ public boolean test(ContributedLibraryReleases t) {
+ return t.getInstalled().isPresent();
+ }
+ };
+ }
+
+}
diff --git a/app/src/cc/arduino/contributions/libraries/ui/DropdownLibraryOfCategoryItem.java b/app/src/cc/arduino/contributions/libraries/ui/DropdownLibraryOfCategoryItem.java
new file mode 100644
index 00000000000..0d07b3ccf03
--- /dev/null
+++ b/app/src/cc/arduino/contributions/libraries/ui/DropdownLibraryOfCategoryItem.java
@@ -0,0 +1,63 @@
+/*
+ * This file is part of Arduino.
+ *
+ * Copyright 2015 Arduino LLC (http://www.arduino.cc/)
+ *
+ * Arduino is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * As a special exception, you may use this file as part of a free software
+ * library without restriction. Specifically, if other files instantiate
+ * templates or use macros or inline functions from this file, or you compile
+ * this file and link it with other files to produce an executable, this
+ * file does not by itself cause the resulting executable to be covered by
+ * the GNU General Public License. This exception does not however
+ * invalidate any other reasons why the executable file might be covered by
+ * the GNU General Public License.
+ */
+
+package cc.arduino.contributions.libraries.ui;
+
+import cc.arduino.contributions.libraries.ContributedLibrary;
+import cc.arduino.contributions.libraries.ContributedLibraryReleases;
+import cc.arduino.contributions.ui.DropdownItem;
+
+import java.util.function.Predicate;
+
+import static processing.app.I18n.tr;
+
+public class DropdownLibraryOfCategoryItem implements DropdownItem {
+
+ private final String category;
+
+ public DropdownLibraryOfCategoryItem(String category) {
+ this.category = category;
+ }
+
+ public String toString() {
+ return tr(category);
+ }
+
+ @Override
+ public Predicate getFilterPredicate() {
+ return new Predicate() {
+ @Override
+ public boolean test(ContributedLibraryReleases rel) {
+ ContributedLibrary lib = rel.getLatest();
+ return category.equals(lib.getCategory());
+ }
+ };
+ }
+
+}
diff --git a/app/src/cc/arduino/contributions/libraries/ui/DropdownLibraryOfTypeItem.java b/app/src/cc/arduino/contributions/libraries/ui/DropdownLibraryOfTypeItem.java
new file mode 100644
index 00000000000..28f44a01894
--- /dev/null
+++ b/app/src/cc/arduino/contributions/libraries/ui/DropdownLibraryOfTypeItem.java
@@ -0,0 +1,63 @@
+/*
+ * This file is part of Arduino.
+ *
+ * Copyright 2015 Arduino LLC (http://www.arduino.cc/)
+ *
+ * Arduino is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * As a special exception, you may use this file as part of a free software
+ * library without restriction. Specifically, if other files instantiate
+ * templates or use macros or inline functions from this file, or you compile
+ * this file and link it with other files to produce an executable, this
+ * file does not by itself cause the resulting executable to be covered by
+ * the GNU General Public License. This exception does not however
+ * invalidate any other reasons why the executable file might be covered by
+ * the GNU General Public License.
+ */
+
+package cc.arduino.contributions.libraries.ui;
+
+import cc.arduino.contributions.libraries.ContributedLibraryReleases;
+import cc.arduino.contributions.ui.DropdownItem;
+
+import java.util.List;
+import java.util.function.Predicate;
+
+import static processing.app.I18n.tr;
+
+public class DropdownLibraryOfTypeItem implements DropdownItem {
+
+ private final String type;
+
+ public DropdownLibraryOfTypeItem(String type) {
+ this.type = type;
+ }
+
+ public String toString() {
+ return tr(type);
+ }
+
+ @Override
+ public Predicate getFilterPredicate() {
+ return new Predicate() {
+ @Override
+ public boolean test(ContributedLibraryReleases lib) {
+ List types = lib.getLatest().getTypes();
+ return types != null && types.contains(type);
+ }
+ };
+ }
+
+}
diff --git a/app/src/cc/arduino/contributions/libraries/ui/DropdownUpdatableLibrariesItem.java b/app/src/cc/arduino/contributions/libraries/ui/DropdownUpdatableLibrariesItem.java
new file mode 100644
index 00000000000..2c75498f822
--- /dev/null
+++ b/app/src/cc/arduino/contributions/libraries/ui/DropdownUpdatableLibrariesItem.java
@@ -0,0 +1,62 @@
+/*
+ * This file is part of Arduino.
+ *
+ * Copyright 2015 Arduino LLC (http://www.arduino.cc/)
+ *
+ * Arduino is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * As a special exception, you may use this file as part of a free software
+ * library without restriction. Specifically, if other files instantiate
+ * templates or use macros or inline functions from this file, or you compile
+ * this file and link it with other files to produce an executable, this
+ * file does not by itself cause the resulting executable to be covered by
+ * the GNU General Public License. This exception does not however
+ * invalidate any other reasons why the executable file might be covered by
+ * the GNU General Public License.
+ */
+
+package cc.arduino.contributions.libraries.ui;
+
+import cc.arduino.contributions.libraries.ContributedLibrary;
+import cc.arduino.contributions.libraries.ContributedLibraryReleases;
+import cc.arduino.contributions.ui.DropdownItem;
+
+import java.util.Optional;
+import java.util.function.Predicate;
+
+import static processing.app.I18n.tr;
+
+public class DropdownUpdatableLibrariesItem implements DropdownItem {
+
+ @Override
+ public Predicate getFilterPredicate() {
+ return new Predicate() {
+ @Override
+ public boolean test(ContributedLibraryReleases lib) {
+ Optional mayInstalled = lib.getInstalled();
+ if (!mayInstalled.isPresent()) {
+ return false;
+ }
+ return !lib.getLatest().equals(mayInstalled.get());
+ }
+ };
+ }
+
+ @Override
+ public String toString() {
+ return tr("Updatable");
+ }
+
+}
diff --git a/app/src/cc/arduino/contributions/libraries/ui/LibrariesIndexTableModel.java b/app/src/cc/arduino/contributions/libraries/ui/LibrariesIndexTableModel.java
new file mode 100644
index 00000000000..ceed4562f8d
--- /dev/null
+++ b/app/src/cc/arduino/contributions/libraries/ui/LibrariesIndexTableModel.java
@@ -0,0 +1,212 @@
+/*
+ * This file is part of Arduino.
+ *
+ * Copyright 2015 Arduino LLC (http://www.arduino.cc/)
+ *
+ * Arduino is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * As a special exception, you may use this file as part of a free software
+ * library without restriction. Specifically, if other files instantiate
+ * templates or use macros or inline functions from this file, or you compile
+ * this file and link it with other files to produce an executable, this
+ * file does not by itself cause the resulting executable to be covered by
+ * the GNU General Public License. This exception does not however
+ * invalidate any other reasons why the executable file might be covered by
+ * the GNU General Public License.
+ */
+
+package cc.arduino.contributions.libraries.ui;
+
+import cc.arduino.contributions.libraries.ContributedLibrary;
+import cc.arduino.contributions.libraries.ContributedLibraryReleases;
+import cc.arduino.contributions.packages.ContributedPlatform;
+import cc.arduino.contributions.ui.FilteredAbstractTableModel;
+import processing.app.BaseNoGui;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Predicate;
+
+@SuppressWarnings("serial")
+public class LibrariesIndexTableModel
+ extends FilteredAbstractTableModel {
+
+ private final List contributions = new ArrayList<>();
+
+ private final String[] columnNames = { "Description" };
+
+ private final Class>[] columnTypes = { ContributedPlatform.class };
+
+ Predicate selectedCategoryFilter = null;
+ String selectedFilters[] = null;
+
+ public void updateIndexFilter(String filters[],
+ Predicate additionalFilter) {
+ selectedCategoryFilter = additionalFilter;
+ selectedFilters = filters;
+ update();
+ }
+
+ /**
+ * Check if string contains all the substrings in set. The
+ * compare is case insensitive.
+ *
+ * @param string
+ * @param filters
+ * @return true if all the strings in set are contained in
+ * string.
+ */
+ private boolean stringContainsAll(String string, String filters[]) {
+ if (string == null) {
+ return false;
+ }
+
+ if (filters == null) {
+ return true;
+ }
+
+ for (String filter : filters) {
+ if (!string.toLowerCase().contains(filter.toLowerCase())) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ @Override
+ public int getColumnCount() {
+ return columnNames.length;
+ }
+
+ @Override
+ public int getRowCount() {
+ return contributions.size();
+ }
+
+ @Override
+ public String getColumnName(int column) {
+ return columnNames[column];
+ }
+
+ @Override
+ public Class> getColumnClass(int colum) {
+ return columnTypes[colum];
+ }
+
+ @Override
+ public void setValueAt(Object value, int row, int col) {
+ fireTableCellUpdated(row, col);
+ }
+
+ @Override
+ public Object getValueAt(int row, int col) {
+ if (row >= contributions.size()) {
+ return null;
+ }
+ ContributedLibraryReleases contribution = contributions.get(row);
+ return contribution;// .getSelected();
+ }
+
+ @Override
+ public boolean isCellEditable(int row, int col) {
+ return true;
+ }
+
+ public ContributedLibraryReleases getReleases(int row) {
+ return contributions.get(row);
+ }
+
+ public ContributedLibrary getSelectedRelease(int row) {
+ return contributions.get(row).getSelected();
+ }
+
+ public void update() {
+ updateContributions();
+ fireTableDataChanged();
+ }
+
+ private boolean filterCondition(ContributedLibraryReleases lib) {
+ if (selectedCategoryFilter != null && !selectedCategoryFilter.test(lib)) {
+ return false;
+ }
+
+ ContributedLibrary latest = lib.getLatest();
+ String compoundTargetSearchText = latest.getName() + " "
+ + latest.getParagraph() + " "
+ + latest.getSentence();
+ if (latest.getProvidesIncludes() != null) {
+ compoundTargetSearchText += " " + latest.getProvidesIncludes();
+ }
+ if (!stringContainsAll(compoundTargetSearchText, selectedFilters)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public void updateLibrary(ContributedLibrary lib) {
+ // Find the row interested in the change
+ int row = -1;
+ for (ContributedLibraryReleases releases : contributions) {
+ if (releases.shouldContain(lib))
+ row = contributions.indexOf(releases);
+ }
+
+ updateContributions();
+
+ // If the library is found in the list send update event
+ // or insert event on the specific row...
+ for (ContributedLibraryReleases releases : contributions) {
+ if (releases.shouldContain(lib)) {
+ if (row == -1) {
+ row = contributions.indexOf(releases);
+ fireTableRowsInserted(row, row);
+ } else {
+ fireTableRowsUpdated(row, row);
+ }
+ return;
+ }
+ }
+ // ...otherwise send a row deleted event
+ fireTableRowsDeleted(row, row);
+ }
+
+ private List rebuildContributionsFromIndex() {
+ List res = new ArrayList<>();
+ BaseNoGui.librariesIndexer.getIndex().getLibraries(). //
+ forEach(lib -> {
+ for (ContributedLibraryReleases contribution : res) {
+ if (!contribution.shouldContain(lib))
+ continue;
+ contribution.add(lib);
+ return;
+ }
+
+ res.add(new ContributedLibraryReleases(lib));
+ });
+ return res;
+ }
+
+ private void updateContributions() {
+ List all = rebuildContributionsFromIndex();
+ contributions.clear();
+ all.stream().filter(this::filterCondition).forEach(contributions::add);
+ Collections.sort(contributions,
+ new ContributedLibraryReleasesComparator("Arduino"));
+ }
+
+}
diff --git a/app/src/cc/arduino/contributions/libraries/ui/LibraryManagerUI.java b/app/src/cc/arduino/contributions/libraries/ui/LibraryManagerUI.java
new file mode 100644
index 00000000000..69ab10006c9
--- /dev/null
+++ b/app/src/cc/arduino/contributions/libraries/ui/LibraryManagerUI.java
@@ -0,0 +1,294 @@
+/*
+ * This file is part of Arduino.
+ *
+ * Copyright 2015 Arduino LLC (http://www.arduino.cc/)
+ *
+ * Arduino is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * As a special exception, you may use this file as part of a free software
+ * library without restriction. Specifically, if other files instantiate
+ * templates or use macros or inline functions from this file, or you compile
+ * this file and link it with other files to produce an executable, this
+ * file does not by itself cause the resulting executable to be covered by
+ * the GNU General Public License. This exception does not however
+ * invalidate any other reasons why the executable file might be covered by
+ * the GNU General Public License.
+ */
+
+package cc.arduino.contributions.libraries.ui;
+
+import static processing.app.I18n.tr;
+
+import java.awt.Dialog;
+import java.awt.Frame;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Optional;
+
+import javax.swing.Box;
+import javax.swing.JComboBox;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.table.TableCellRenderer;
+
+import cc.arduino.contributions.libraries.ContributedLibrary;
+import cc.arduino.contributions.libraries.ContributedLibraryReleases;
+import cc.arduino.contributions.libraries.LibraryInstaller;
+import cc.arduino.contributions.libraries.LibraryTypeComparator;
+import cc.arduino.contributions.libraries.ui.MultiLibraryInstallDialog.Result;
+import cc.arduino.contributions.ui.DropdownItem;
+import cc.arduino.contributions.ui.FilteredAbstractTableModel;
+import cc.arduino.contributions.ui.InstallerJDialog;
+import cc.arduino.contributions.ui.InstallerJDialogUncaughtExceptionHandler;
+import cc.arduino.contributions.ui.InstallerTableCell;
+import cc.arduino.utils.Progress;
+import processing.app.BaseNoGui;
+
+@SuppressWarnings("serial")
+public class LibraryManagerUI extends InstallerJDialog {
+
+ private final JComboBox typeChooser;
+ private final LibraryInstaller installer;
+
+ @Override
+ protected FilteredAbstractTableModel createContribModel() {
+ return new LibrariesIndexTableModel();
+ }
+
+ private LibrariesIndexTableModel getContribModel() {
+ return (LibrariesIndexTableModel) contribModel;
+ }
+
+ @Override
+ protected TableCellRenderer createCellRenderer() {
+ return new ContributedLibraryTableCellRenderer();
+ }
+
+ @Override
+ protected InstallerTableCell createCellEditor() {
+ return new ContributedLibraryTableCellEditor() {
+ @Override
+ protected void onInstall(ContributedLibrary selectedLibrary, Optional mayInstalledLibrary) {
+ if (mayInstalledLibrary.isPresent() && selectedLibrary.isIDEBuiltIn()) {
+ onRemovePressed(mayInstalledLibrary.get());
+ } else {
+ onInstallPressed(selectedLibrary);
+ }
+ }
+
+ @Override
+ protected void onRemove(ContributedLibrary library) {
+ onRemovePressed(library);
+ }
+ };
+ }
+
+ public LibraryManagerUI(Frame parent, LibraryInstaller installer) {
+ super(parent, tr("Library Manager"), Dialog.ModalityType.APPLICATION_MODAL, tr("Unable to reach Arduino.cc due to possible network issues."));
+ this.installer = installer;
+
+ filtersContainer.add(new JLabel(tr("Topic")), 1);
+ filtersContainer.remove(2);
+
+ typeChooser = new JComboBox();
+ typeChooser.setMaximumRowCount(20);
+ typeChooser.setEnabled(false);
+
+ filtersContainer.add(Box.createHorizontalStrut(5), 0);
+ filtersContainer.add(new JLabel(tr("Type")), 1);
+ filtersContainer.add(Box.createHorizontalStrut(5), 2);
+ filtersContainer.add(typeChooser, 3);
+ }
+
+ protected final ActionListener typeChooserActionListener = new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent event) {
+ DropdownItem selected = (DropdownItem) typeChooser.getSelectedItem();
+ previousRowAtPoint = -1;
+ if (selected != null && extraFilter != selected.getFilterPredicate()) {
+ extraFilter = selected.getFilterPredicate();
+ if (contribTable.getCellEditor() != null) {
+ contribTable.getCellEditor().stopCellEditing();
+ }
+ updateIndexFilter(filters, categoryFilter.and(extraFilter));
+ }
+ }
+ };
+
+ private Collection oldCategories = new ArrayList<>();
+ private Collection oldTypes = new ArrayList<>();
+
+ public void updateUI() {
+ // Check if categories or types have changed
+ Collection categories = BaseNoGui.librariesIndexer.getIndex().getCategories();
+ List types = new LinkedList<>(BaseNoGui.librariesIndexer.getIndex().getTypes());
+ Collections.sort(types, new LibraryTypeComparator());
+
+ if (categories.equals(oldCategories) && types.equals(oldTypes)) {
+ return;
+ }
+ oldCategories = categories;
+ oldTypes = types;
+
+ // Load categories
+ categoryFilter = x -> true;
+ categoryChooser.removeActionListener(categoryChooserActionListener);
+ categoryChooser.removeAllItems();
+ categoryChooser.addItem(new DropdownAllLibraries());
+ for (String category : categories) {
+ categoryChooser.addItem(new DropdownLibraryOfCategoryItem(category));
+ }
+ categoryChooser.setEnabled(categoryChooser.getItemCount() > 1);
+ categoryChooser.addActionListener(categoryChooserActionListener);
+ categoryChooser.setSelectedIndex(0);
+
+ // Load types
+ extraFilter = x -> true;
+ typeChooser.removeActionListener(typeChooserActionListener);
+ typeChooser.removeAllItems();
+ typeChooser.addItem(new DropdownAllLibraries());
+ typeChooser.addItem(new DropdownUpdatableLibrariesItem());
+ typeChooser.addItem(new DropdownInstalledLibraryItem());
+ for (String type : types) {
+ typeChooser.addItem(new DropdownLibraryOfTypeItem(type));
+ }
+ typeChooser.setEnabled(typeChooser.getItemCount() > 1);
+ typeChooser.addActionListener(typeChooserActionListener);
+ typeChooser.setSelectedIndex(0);
+
+ filterField.setEnabled(contribModel.getRowCount() > 0);
+ }
+
+ public void selectDropdownItemByClassName(String dropdownItem) {
+ selectDropdownItemByClassName(typeChooser, dropdownItem);
+ }
+
+ public void setProgress(Progress progress) {
+ progressBar.setValue(progress);
+ }
+
+ private Thread installerThread = null;
+
+ @Override
+ protected void onCancelPressed() {
+ super.onUpdatePressed();
+ if (installerThread != null) {
+ installerThread.interrupt();
+ }
+ }
+
+ @Override
+ protected void onUpdatePressed() {
+ super.onUpdatePressed();
+ installerThread = new Thread(() -> {
+ try {
+ setProgressVisible(true, "");
+ installer.updateIndex(this::setProgress);
+ onIndexesUpdated();
+ if (contribTable.getCellEditor() != null) {
+ contribTable.getCellEditor().stopCellEditing();
+ }
+ getContribModel().update();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ } finally {
+ setProgressVisible(false, "");
+ }
+ });
+ installerThread.setName("LibraryManager Update Thread");
+ installerThread.setUncaughtExceptionHandler(new InstallerJDialogUncaughtExceptionHandler(this, noConnectionErrorMessage));
+ installerThread.start();
+ }
+
+ public void onInstallPressed(final ContributedLibrary lib) {
+ List deps = BaseNoGui.librariesIndexer.getIndex().resolveDependeciesOf(lib);
+ boolean depsInstalled = deps.stream().allMatch(l -> l.getInstalledLibrary().isPresent() || l.getName().equals(lib.getName()));
+ Result installDeps;
+ if (!depsInstalled) {
+ MultiLibraryInstallDialog dialog;
+ dialog = new MultiLibraryInstallDialog(this, lib, deps);
+ dialog.setLocationRelativeTo(this);
+ dialog.setVisible(true);
+ installDeps = dialog.getInstallDepsResult();
+ if (installDeps == Result.CANCEL)
+ return;
+ } else {
+ installDeps = Result.NONE;
+ }
+ clearErrorMessage();
+ installerThread = new Thread(() -> {
+ try {
+ setProgressVisible(true, tr("Installing..."));
+ if (installDeps == Result.ALL) {
+ installer.install(deps, this::setProgress);
+ } else {
+ installer.install(lib, this::setProgress);
+ }
+ onIndexesUpdated();
+ if (contribTable.getCellEditor() != null) {
+ contribTable.getCellEditor().stopCellEditing();
+ }
+ getContribModel().update();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ } finally {
+ setProgressVisible(false, "");
+ }
+ });
+ installerThread.setName("LibraryManager Installer Thread");
+ installerThread.setUncaughtExceptionHandler(new InstallerJDialogUncaughtExceptionHandler(this, noConnectionErrorMessage));
+ installerThread.start();
+ }
+
+ public void onRemovePressed(final ContributedLibrary lib) {
+ boolean managedByIndex = BaseNoGui.librariesIndexer.getIndex().getLibraries().contains(lib);
+
+ if (!managedByIndex) {
+ int chosenOption = JOptionPane.showConfirmDialog(this, tr("This library is not listed on Library Manager. You won't be able to reinstall it from here.\nAre you sure you want to delete it?"), tr("Please confirm library deletion"), JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
+ if (chosenOption != JOptionPane.YES_OPTION) {
+ return;
+ }
+ }
+
+ clearErrorMessage();
+ installerThread = new Thread(() -> {
+ try {
+ setProgressVisible(true, tr("Removing..."));
+ installer.remove(lib, this::setProgress);
+ onIndexesUpdated();
+ if (contribTable.getCellEditor() != null) {
+ contribTable.getCellEditor().stopCellEditing();
+ }
+ getContribModel().update();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ } finally {
+ setProgressVisible(false, "");
+ }
+ });
+ installerThread.setName("LibraryManager Remove Thread");
+ installerThread.setUncaughtExceptionHandler(new InstallerJDialogUncaughtExceptionHandler(this, noConnectionErrorMessage));
+ installerThread.start();
+ }
+
+ protected void onIndexesUpdated() throws Exception {
+ // Empty
+ }
+}
\ No newline at end of file
diff --git a/app/src/cc/arduino/contributions/libraries/ui/MultiLibraryInstallDialog.java b/app/src/cc/arduino/contributions/libraries/ui/MultiLibraryInstallDialog.java
new file mode 100644
index 00000000000..75f7703f430
--- /dev/null
+++ b/app/src/cc/arduino/contributions/libraries/ui/MultiLibraryInstallDialog.java
@@ -0,0 +1,177 @@
+/*
+ * This file is part of Arduino.
+ *
+ * Copyright 2017 Arduino LLC (http://www.arduino.cc/)
+ *
+ * Arduino is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * As a special exception, you may use this file as part of a free software
+ * library without restriction. Specifically, if other files instantiate
+ * templates or use macros or inline functions from this file, or you compile
+ * this file and link it with other files to produce an executable, this
+ * file does not by itself cause the resulting executable to be covered by
+ * the GNU General Public License. This exception does not however
+ * invalidate any other reasons why the executable file might be covered by
+ * the GNU General Public License.
+ */
+
+package cc.arduino.contributions.libraries.ui;
+
+import static processing.app.I18n.format;
+import static processing.app.I18n.tr;
+
+import java.awt.BorderLayout;
+import java.awt.Container;
+import java.awt.Insets;
+import java.awt.Window;
+import java.awt.event.WindowEvent;
+import java.util.List;
+
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JPanel;
+import javax.swing.JTextPane;
+import javax.swing.WindowConstants;
+import javax.swing.border.EmptyBorder;
+import javax.swing.text.Document;
+import javax.swing.text.html.HTMLDocument;
+import javax.swing.text.html.StyleSheet;
+
+import cc.arduino.contributions.libraries.ContributedLibrary;
+import cc.arduino.contributions.libraries.UnavailableContributedLibrary;
+import processing.app.Base;
+import processing.app.Theme;
+
+public class MultiLibraryInstallDialog extends JDialog {
+
+ enum Result {
+ ALL, NONE, CANCEL
+ }
+
+ private Result result = Result.CANCEL;
+
+ public MultiLibraryInstallDialog(Window parent, ContributedLibrary lib,
+ List dependencies) {
+ super(parent, format(tr("Dependencies for library {0}:{1}"), lib.getName(),
+ lib.getParsedVersion()),
+ ModalityType.APPLICATION_MODAL);
+ Container pane = getContentPane();
+ pane.setLayout(new BorderLayout());
+
+ pane.add(Box.createHorizontalStrut(10), BorderLayout.WEST);
+ pane.add(Box.createHorizontalStrut(10), BorderLayout.EAST);
+
+ {
+ JButton cancel = new JButton(tr("Cancel"));
+ cancel.addActionListener(ev -> {
+ result = Result.CANCEL;
+ setVisible(false);
+ });
+
+ JButton all = new JButton(tr("Install all"));
+ all.addActionListener(ev -> {
+ result = Result.ALL;
+ setVisible(false);
+ });
+
+ JButton none = new JButton(format(tr("Install '{0}' only"), lib.getName()));
+ none.addActionListener(ev -> {
+ result = Result.NONE;
+ setVisible(false);
+ });
+
+ Box buttonsBox = Box.createHorizontalBox();
+ buttonsBox.add(all);
+ buttonsBox.add(Box.createHorizontalStrut(5));
+ buttonsBox.add(none);
+ buttonsBox.add(Box.createHorizontalStrut(5));
+ buttonsBox.add(cancel);
+
+ JPanel buttonsPanel = new JPanel();
+ buttonsPanel.setBorder(new EmptyBorder(7, 10, 7, 10));
+ buttonsPanel.setLayout(new BoxLayout(buttonsPanel, BoxLayout.Y_AXIS));
+ buttonsPanel.add(buttonsBox);
+
+ pane.add(buttonsPanel, BorderLayout.SOUTH);
+ }
+
+ {
+ String libName = format("{0}:{1}", lib.getName(),
+ lib.getParsedVersion());
+ String desc = format(tr("The library {0} needs some other library
dependencies currently not installed:"),
+ libName);
+ desc += "
";
+ for (ContributedLibrary l : dependencies) {
+ if (l.getName().equals(lib.getName()))
+ continue;
+ if (l.getInstalledLibrary().isPresent())
+ continue;
+ if (l instanceof UnavailableContributedLibrary)
+ continue;
+ desc += format("- {0}
", l.getName());
+ }
+ desc += "
";
+ desc += tr("Would you like to install also all the missing dependencies?");
+
+ JTextPane textArea = makeNewDescription();
+ textArea.setContentType("text/html");
+ textArea.setText(desc);
+
+ JPanel libsList = new JPanel();
+ libsList.setLayout(new BoxLayout(libsList, BoxLayout.Y_AXIS));
+ libsList.add(textArea);
+ libsList.setBorder(new EmptyBorder(7, 7, 7, 7));
+ pane.add(libsList, BorderLayout.NORTH);
+ }
+
+ pack();
+ setResizable(false);
+ setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
+
+ WindowEvent closing = new WindowEvent(this, WindowEvent.WINDOW_CLOSING);
+ Base.registerWindowCloseKeys(getRootPane(), e -> dispatchEvent(closing));
+ }
+
+ // TODO Make this a method of Theme
+ private JTextPane makeNewDescription() {
+ JTextPane description = new JTextPane();
+ description.setInheritsPopupMenu(true);
+ Insets margin = description.getMargin();
+ margin.bottom = 0;
+ description.setMargin(margin);
+ description.setContentType("text/html");
+ Document doc = description.getDocument();
+ if (doc instanceof HTMLDocument) {
+ HTMLDocument html = (HTMLDocument) doc;
+ StyleSheet s = html.getStyleSheet();
+ s.addRule("body { margin: 0; padding: 0;"
+ + "font-family: Verdana, Geneva, Arial, Helvetica, sans-serif;"
+ + "color: black;" + "font-size: " + 15 * Theme.getScale() / 100
+ + "; }");
+ }
+ description.setOpaque(false);
+ description.setBorder(new EmptyBorder(4, 7, 7, 7));
+ description.setHighlighter(null);
+ description.setEditable(false);
+ add(description, 0);
+ return description;
+ }
+
+ public Result getInstallDepsResult() {
+ return result;
+ }
+}
diff --git a/app/src/cc/arduino/contributions/packages/filters/CategoryPredicate.java b/app/src/cc/arduino/contributions/packages/filters/CategoryPredicate.java
new file mode 100644
index 00000000000..c756e014f1b
--- /dev/null
+++ b/app/src/cc/arduino/contributions/packages/filters/CategoryPredicate.java
@@ -0,0 +1,49 @@
+/*
+ * This file is part of Arduino.
+ *
+ * Copyright 2015 Arduino LLC (http://www.arduino.cc/)
+ *
+ * Arduino is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * As a special exception, you may use this file as part of a free software
+ * library without restriction. Specifically, if other files instantiate
+ * templates or use macros or inline functions from this file, or you compile
+ * this file and link it with other files to produce an executable, this
+ * file does not by itself cause the resulting executable to be covered by
+ * the GNU General Public License. This exception does not however
+ * invalidate any other reasons why the executable file might be covered by
+ * the GNU General Public License.
+ */
+
+package cc.arduino.contributions.packages.filters;
+
+import cc.arduino.contributions.packages.ContributedPlatform;
+
+import java.util.function.Predicate;
+
+public class CategoryPredicate implements Predicate {
+
+ private final String category;
+
+ public CategoryPredicate(String category) {
+ this.category = category;
+ }
+
+ @Override
+ public boolean test(ContributedPlatform input) {
+ return input.getCategory() != null && category.equals(input.getCategory());
+ }
+
+}
diff --git a/app/src/cc/arduino/contributions/packages/filters/UpdatablePlatformPredicate.java b/app/src/cc/arduino/contributions/packages/filters/UpdatablePlatformPredicate.java
new file mode 100644
index 00000000000..019b5118eee
--- /dev/null
+++ b/app/src/cc/arduino/contributions/packages/filters/UpdatablePlatformPredicate.java
@@ -0,0 +1,56 @@
+/*
+ * This file is part of Arduino.
+ *
+ * Copyright 2015 Arduino LLC (http://www.arduino.cc/)
+ *
+ * Arduino is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * As a special exception, you may use this file as part of a free software
+ * library without restriction. Specifically, if other files instantiate
+ * templates or use macros or inline functions from this file, or you compile
+ * this file and link it with other files to produce an executable, this
+ * file does not by itself cause the resulting executable to be covered by
+ * the GNU General Public License. This exception does not however
+ * invalidate any other reasons why the executable file might be covered by
+ * the GNU General Public License.
+ */
+
+package cc.arduino.contributions.packages.filters;
+
+import cc.arduino.contributions.VersionComparator;
+import cc.arduino.contributions.packages.ContributedPlatform;
+import processing.app.BaseNoGui;
+
+import java.util.List;
+import java.util.function.Predicate;
+
+public class UpdatablePlatformPredicate implements Predicate {
+
+ @Override
+ public boolean test(ContributedPlatform contributedPlatform) {
+ String packageName = contributedPlatform.getParentPackage().getName();
+ String architecture = contributedPlatform.getArchitecture();
+
+ ContributedPlatform installed = BaseNoGui.indexer.getInstalled(packageName, architecture);
+ if (installed == null) {
+ return false;
+ }
+
+ List platforms = BaseNoGui.indexer.getIndex().findPlatforms(packageName, architecture);
+ return platforms.stream()
+ .filter(platform -> VersionComparator.greaterThan(platform.getParsedVersion(), installed.getParsedVersion()))
+ .count() > 0;
+ }
+}
diff --git a/app/src/cc/arduino/contributions/packages/ui/ContributedPlatformReleases.java b/app/src/cc/arduino/contributions/packages/ui/ContributedPlatformReleases.java
new file mode 100644
index 00000000000..fc516512d44
--- /dev/null
+++ b/app/src/cc/arduino/contributions/packages/ui/ContributedPlatformReleases.java
@@ -0,0 +1,108 @@
+/*
+ * This file is part of Arduino.
+ *
+ * Copyright 2015 Arduino LLC (http://www.arduino.cc/)
+ *
+ * Arduino is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * As a special exception, you may use this file as part of a free software
+ * library without restriction. Specifically, if other files instantiate
+ * templates or use macros or inline functions from this file, or you compile
+ * this file and link it with other files to produce an executable, this
+ * file does not by itself cause the resulting executable to be covered by
+ * the GNU General Public License. This exception does not however
+ * invalidate any other reasons why the executable file might be covered by
+ * the GNU General Public License.
+ */
+
+package cc.arduino.contributions.packages.ui;
+
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import cc.arduino.contributions.packages.ContributedPackage;
+import cc.arduino.contributions.packages.ContributedPlatform;
+
+public class ContributedPlatformReleases {
+
+ public final ContributedPackage packager;
+ public final String arch;
+ public final List releases;
+ public final List versions;
+ public ContributedPlatform selected = null;
+ public boolean deprecated;
+
+ public ContributedPlatformReleases(ContributedPlatform platform) {
+ packager = platform.getParentPackage();
+ arch = platform.getArchitecture();
+ releases = new LinkedList<>();
+ versions = new LinkedList<>();
+ deprecated = platform.isDeprecated();
+ add(platform);
+ }
+
+ public boolean shouldContain(ContributedPlatform platform) {
+ if (platform.getParentPackage() != packager)
+ return false;
+ return platform.getArchitecture().equals(arch);
+ }
+
+ public void add(ContributedPlatform platform) {
+ releases.add(platform);
+ String version = platform.getParsedVersion();
+ if (version != null) {
+ versions.add(version);
+ }
+ ContributedPlatform latest = getLatest();
+ selected = latest;
+ deprecated = latest.isDeprecated();
+ }
+
+ public ContributedPlatform getInstalled() {
+ List installedReleases = releases.stream()
+ .filter(p -> p.isInstalled()) //
+ .collect(Collectors.toList());
+ Collections.sort(installedReleases, ContributedPlatform.BUILTIN_AS_LAST);
+
+ if (installedReleases.isEmpty()) {
+ return null;
+ }
+
+ return installedReleases.get(0);
+ }
+
+ public ContributedPlatform getLatest() {
+ return ContributionIndexTableModel.getLatestOf(releases);
+ }
+
+ public ContributedPlatform getSelected() {
+ return selected;
+ }
+
+ public boolean isDeprecated() {
+ return deprecated;
+ }
+
+ public void select(ContributedPlatform value) {
+ for (ContributedPlatform plat : releases) {
+ if (plat == value) {
+ selected = plat;
+ return;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/cc/arduino/contributions/packages/ui/ContributedPlatformTableCellEditor.java b/app/src/cc/arduino/contributions/packages/ui/ContributedPlatformTableCellEditor.java
new file mode 100644
index 00000000000..7fe221fa340
--- /dev/null
+++ b/app/src/cc/arduino/contributions/packages/ui/ContributedPlatformTableCellEditor.java
@@ -0,0 +1,156 @@
+/*
+ * This file is part of Arduino.
+ *
+ * Copyright 2015 Arduino LLC (http://www.arduino.cc/)
+ *
+ * Arduino is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * As a special exception, you may use this file as part of a free software
+ * library without restriction. Specifically, if other files instantiate
+ * templates or use macros or inline functions from this file, or you compile
+ * this file and link it with other files to produce an executable, this
+ * file does not by itself cause the resulting executable to be covered by
+ * the GNU General Public License. This exception does not however
+ * invalidate any other reasons why the executable file might be covered by
+ * the GNU General Public License.
+ */
+
+package cc.arduino.contributions.packages.ui;
+
+import static processing.app.I18n.tr;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import javax.swing.JTable;
+
+import cc.arduino.contributions.DownloadableContributionVersionComparator;
+import cc.arduino.contributions.VersionComparator;
+import cc.arduino.contributions.packages.ContributedPlatform;
+import cc.arduino.contributions.ui.InstallerTableCell;
+import cc.arduino.utils.ReverseComparator;
+
+@SuppressWarnings("serial")
+public class ContributedPlatformTableCellEditor extends InstallerTableCell {
+
+ private ContributedPlatformTableCellJPanel cell;
+ private ContributedPlatformReleases value;
+
+ @Override
+ public Object getCellEditorValue() {
+ return value;
+ }
+
+ @Override
+ public Component getTableCellEditorComponent(JTable table, Object _value,
+ boolean isSelected, int row,
+ int column) {
+ value = (ContributedPlatformReleases) _value;
+
+ cell = new ContributedPlatformTableCellJPanel();
+ cell.installButton.addActionListener(e -> onInstall(value.getSelected(),
+ value.getInstalled()));
+ cell.removeButton.addActionListener(e -> onRemove(value.getInstalled()));
+ cell.downgradeButton.addActionListener(e -> {
+ ContributedPlatform selected = (ContributedPlatform) cell.downgradeChooser
+ .getSelectedItem();
+ onInstall(selected, value.getInstalled());
+ });
+ cell.versionToInstallChooser.addActionListener(e -> {
+ value.select((ContributedPlatform) cell.versionToInstallChooser.getSelectedItem());
+ if (cell.versionToInstallChooser.getSelectedIndex() != 0) {
+ InstallerTableCell.dropdownSelected(true);
+ }
+ });
+
+ setEnabled(true);
+
+ final ContributedPlatform installed = value.getInstalled();
+
+ List releases = new LinkedList<>(value.releases);
+ List uninstalledReleases = releases.stream() //
+ .filter(p -> !p.isInstalled()) //
+ .collect(Collectors.toList());
+
+ List installedBuiltIn = releases.stream() //
+ .filter(p -> p.isInstalled()) //
+ .filter(p -> p.isBuiltIn()) //
+ .collect(Collectors.toList());
+
+ if (installed != null && !installedBuiltIn.contains(installed)) {
+ uninstalledReleases.addAll(installedBuiltIn);
+ }
+
+ Collections.sort(uninstalledReleases, new ReverseComparator<>(
+ new DownloadableContributionVersionComparator()));
+
+ cell.downgradeChooser.removeAllItems();
+ cell.downgradeChooser.addItem(tr("Select version"));
+
+ final List uninstalledPreviousReleases = new LinkedList<>();
+ final List uninstalledNewerReleases = new LinkedList<>();
+
+ uninstalledReleases.stream().forEach(input -> {
+ if (installed == null
+ || VersionComparator.greaterThan(installed.getParsedVersion(),
+ input.getParsedVersion())) {
+ uninstalledPreviousReleases.add(input);
+ } else {
+ uninstalledNewerReleases.add(input);
+ }
+ });
+ uninstalledNewerReleases.forEach(cell.downgradeChooser::addItem);
+ uninstalledPreviousReleases.forEach(cell.downgradeChooser::addItem);
+
+ boolean downgradeVisible = installed != null
+ && (!uninstalledPreviousReleases.isEmpty()
+ || uninstalledNewerReleases.size() > 1);
+ cell.downgradeChooser.setVisible(downgradeVisible);
+ cell.downgradeButton.setVisible(downgradeVisible);
+
+ cell.versionToInstallChooser.removeAllItems();
+ uninstalledReleases.forEach(cell.versionToInstallChooser::addItem);
+ cell.versionToInstallChooser
+ .setVisible(installed == null && uninstalledReleases.size() > 1);
+
+ cell.update(table, _value, !installedBuiltIn.isEmpty());
+ cell.setForeground(Color.BLACK);
+ cell.setBackground(new Color(218, 227, 227)); // #dae3e3
+ return cell;
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ cell.setButtonsVisible(enabled);
+ }
+
+ public void setStatus(String status) {
+ cell.statusLabel.setText(status);
+ }
+
+ protected void onRemove(ContributedPlatform contributedPlatform) {
+ // Empty
+ }
+
+ protected void onInstall(ContributedPlatform contributedPlatform,
+ ContributedPlatform installed) {
+ // Empty
+ }
+
+}
diff --git a/app/src/cc/arduino/contributions/packages/ui/ContributedPlatformTableCellJPanel.java b/app/src/cc/arduino/contributions/packages/ui/ContributedPlatformTableCellJPanel.java
new file mode 100644
index 00000000000..19961c1c97f
--- /dev/null
+++ b/app/src/cc/arduino/contributions/packages/ui/ContributedPlatformTableCellJPanel.java
@@ -0,0 +1,324 @@
+/*
+ * This file is part of Arduino.
+ *
+ * Copyright 2015 Arduino LLC (http://www.arduino.cc/)
+ *
+ * Arduino is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * As a special exception, you may use this file as part of a free software
+ * library without restriction. Specifically, if other files instantiate
+ * templates or use macros or inline functions from this file, or you compile
+ * this file and link it with other files to produce an executable, this
+ * file does not by itself cause the resulting executable to be covered by
+ * the GNU General Public License. This exception does not however
+ * invalidate any other reasons why the executable file might be covered by
+ * the GNU General Public License.
+ */
+
+package cc.arduino.contributions.packages.ui;
+
+import static processing.app.I18n.format;
+import static processing.app.I18n.tr;
+
+import java.awt.*;
+
+import javax.swing.*;
+import javax.swing.border.EmptyBorder;
+import javax.swing.border.TitledBorder;
+import javax.swing.event.HyperlinkEvent;
+import javax.swing.text.Document;
+import javax.swing.text.html.HTMLDocument;
+import javax.swing.text.html.StyleSheet;
+
+import cc.arduino.contributions.DownloadableContributionVersionComparator;
+import cc.arduino.contributions.packages.ContributedBoard;
+import cc.arduino.contributions.packages.ContributedHelp;
+import cc.arduino.contributions.packages.ContributedPlatform;
+import cc.arduino.contributions.ui.InstallerTableCell;
+import processing.app.Base;
+import processing.app.PreferencesData;
+import processing.app.Theme;
+
+@SuppressWarnings("serial")
+public class ContributedPlatformTableCellJPanel extends JPanel {
+
+ final JButton moreInfoButton;
+ final JButton onlineHelpButton;
+ final JButton installButton;
+ final JButton removeButton;
+ final Component removeButtonPlaceholder;
+ final Component installButtonPlaceholder;
+ final JComboBox downgradeChooser;
+ final JComboBox versionToInstallChooser;
+ final JButton downgradeButton;
+ final JPanel buttonsPanel;
+ final JPanel inactiveButtonsPanel;
+ final JLabel statusLabel;
+ final JTextPane description;
+ final TitledBorder titledBorder;
+ private final String moreInfoLbl = tr("More Info");
+ private final String onlineHelpLbl = tr("Online Help");
+
+ public ContributedPlatformTableCellJPanel() {
+ super();
+ setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
+
+ // Actual title set by update()
+ titledBorder = BorderFactory.createTitledBorder("");
+ titledBorder.setTitleFont(getFont().deriveFont(Font.BOLD));
+ setBorder(titledBorder);
+
+ {
+ installButton = new JButton(tr("Install"));
+ moreInfoButton = new JButton(moreInfoLbl);
+ moreInfoButton.setVisible(false);
+ onlineHelpButton = new JButton(onlineHelpLbl);
+ onlineHelpButton.setVisible(false);
+ int width = installButton.getPreferredSize().width;
+ installButtonPlaceholder = Box.createRigidArea(new Dimension(width, 1));
+ }
+
+ {
+ removeButton = new JButton(tr("Remove"));
+ int width = removeButton.getPreferredSize().width;
+ removeButtonPlaceholder = Box.createRigidArea(new Dimension(width, 1));
+ }
+
+ downgradeButton = new JButton(tr("Install"));
+
+ downgradeChooser = new JComboBox();
+ downgradeChooser.addItem("-");
+ downgradeChooser.setMaximumSize(downgradeChooser.getPreferredSize());
+ downgradeChooser.addItemListener(e -> {
+ Object selectVersionItem = downgradeChooser.getItemAt(0);
+ boolean disableDowngrade = (e.getItem() == selectVersionItem);
+ downgradeButton.setEnabled(!disableDowngrade);
+ if (!disableDowngrade) {
+ InstallerTableCell.dropdownSelected(true);
+ }
+ });
+
+ versionToInstallChooser = new JComboBox();
+ versionToInstallChooser.addItem("-");
+ versionToInstallChooser
+ .setMaximumSize(versionToInstallChooser.getPreferredSize());
+
+ description = makeNewDescription();
+ add(description);
+
+ buttonsPanel = new JPanel();
+ buttonsPanel.setLayout(new BoxLayout(buttonsPanel, BoxLayout.X_AXIS));
+ buttonsPanel.setOpaque(false);
+
+ buttonsPanel.add(Box.createHorizontalStrut(7));
+ if (PreferencesData.getBoolean("ide.accessible")) { // only add the buttons if needed
+ buttonsPanel.add(onlineHelpButton);
+ buttonsPanel.add(Box.createHorizontalStrut(5));
+ buttonsPanel.add(moreInfoButton);
+ buttonsPanel.add(Box.createHorizontalStrut(5));
+ buttonsPanel.add(Box.createHorizontalStrut(15));
+ }
+ buttonsPanel.add(downgradeChooser);
+ buttonsPanel.add(Box.createHorizontalStrut(5));
+ buttonsPanel.add(downgradeButton);
+
+ buttonsPanel.add(Box.createHorizontalGlue());
+
+ buttonsPanel.add(versionToInstallChooser);
+ buttonsPanel.add(Box.createHorizontalStrut(5));
+ buttonsPanel.add(installButton);
+ buttonsPanel.add(Box.createHorizontalStrut(5));
+ buttonsPanel.add(removeButton);
+ buttonsPanel.add(Box.createHorizontalStrut(5));
+ buttonsPanel.add(Box.createHorizontalStrut(15));
+
+ add(buttonsPanel);
+
+ inactiveButtonsPanel = new JPanel();
+ inactiveButtonsPanel
+ .setLayout(new BoxLayout(inactiveButtonsPanel, BoxLayout.X_AXIS));
+ inactiveButtonsPanel.setOpaque(false);
+
+ int height = installButton.getMinimumSize().height;
+ inactiveButtonsPanel.add(Box.createVerticalStrut(height));
+ inactiveButtonsPanel.add(Box.createGlue());
+
+ statusLabel = new JLabel(" ");
+ inactiveButtonsPanel.add(statusLabel);
+ inactiveButtonsPanel.add(Box.createHorizontalStrut(15));
+
+ add(inactiveButtonsPanel);
+
+ add(Box.createVerticalStrut(15));
+ }
+
+ // same function as in ContributedLibraryTableCellJPanel - is there a utils file this can move to?
+ private String setButtonOrLink(JButton button, String desc, String label, String url) {
+ boolean accessibleIDE = PreferencesData.getBoolean("ide.accessible");
+ String retString = desc;
+
+ if (accessibleIDE) {
+ button.setVisible(true);
+ button.addActionListener(e -> {
+ Base.openURL(url);
+ });
+ }
+ else {
+ // if not accessible IDE, keep link the same EXCEPT that now the link text is translated!
+ retString += " " + format("{1}
", url, label);
+ }
+
+ return retString;
+ }
+
+ void update(JTable parentTable, Object value, boolean hasBuiltInRelease) {
+ ContributedPlatformReleases releases = (ContributedPlatformReleases) value;
+
+ // FIXME: happens on macosx, don't know why
+ if (releases == null) {
+ return;
+ }
+
+ ContributedPlatform selected = releases.getSelected();
+ titledBorder.setTitle(selected.getName());
+ ContributedPlatform installed = releases.getInstalled();
+
+ boolean removable, installable, upgradable;
+ if (installed == null) {
+ installable = true;
+ removable = false;
+ upgradable = false;
+ } else {
+ installable = false;
+ removable = !installed.isBuiltIn() && !hasBuiltInRelease;
+ upgradable = new DownloadableContributionVersionComparator()
+ .compare(selected, installed) > 0;
+ }
+ if (installable) {
+ installButton.setText(tr("Install"));
+ }
+ if (upgradable) {
+ installButton.setText(tr("Update"));
+ }
+ installButton.setVisible(installable || upgradable);
+ installButtonPlaceholder.setVisible(!(installable || upgradable));
+ removeButton.setVisible(removable);
+ removeButtonPlaceholder.setVisible(!removable);
+
+ String desc = "";
+// desc += "" + selected.getName() + "";
+ if (installed != null && installed.isBuiltIn()) {
+ desc += " Built-In ";
+ }
+
+ String author = selected.getParentPackage().getMaintainer();
+ if (author != null && !author.isEmpty()) {
+ desc += " " + format("by {0}", author);
+ }
+ if (installed != null) {
+ desc += " "
+ + format(tr("version {0}"), installed.getParsedVersion())
+ + " INSTALLED";
+ }
+ if (releases.isDeprecated()) {
+ desc += " DEPRECATED";
+ }
+ desc += "
";
+
+ desc += tr("Boards included in this package:") + "
";
+ for (ContributedBoard board : selected.getBoards()) {
+ desc += board.getName() + ", ";
+ }
+ if (desc.lastIndexOf(',') != -1) {
+ desc = desc.substring(0, desc.lastIndexOf(',')) + ".
";
+ }
+
+ ContributedHelp help = null;
+ if (selected.getHelp() != null) {
+ help = selected.getHelp();
+ } else if (selected.getParentPackage().getHelp() != null) {
+ help = selected.getParentPackage().getHelp();
+ }
+
+ if (help != null) {
+ String url = help.getOnline();
+ if (url != null && !url.isEmpty()) {
+ desc = setButtonOrLink(onlineHelpButton, desc, onlineHelpLbl, url);
+ }
+ }
+
+ String url = selected.getParentPackage().getWebsiteURL();
+ if (url != null && !url.isEmpty()) {
+ desc = setButtonOrLink(moreInfoButton, desc, moreInfoLbl, url);
+ }
+
+ desc += "";
+ description.setText(desc);
+ // copy description to accessibility context for screen readers to use
+ description.getAccessibleContext().setAccessibleDescription(desc);
+
+ // for modelToView to work, the text area has to be sized. It doesn't
+ // matter if it's visible or not.
+
+ // See:
+ // http://stackoverflow.com/questions/3081210/how-to-set-jtextarea-to-have-height-that-matches-the-size-of-a-text-it-contains
+ int width = parentTable.getBounds().width;
+ InstallerTableCell.setJTextPaneDimensionToFitContainedText(description,
+ width);
+ }
+
+ private JTextPane makeNewDescription() {
+ JTextPane description = new JTextPane();
+ description.setInheritsPopupMenu(true);
+ Insets margin = description.getMargin();
+ margin.bottom = 0;
+ description.setMargin(margin);
+ description.setContentType("text/html");
+ Document doc = description.getDocument();
+ if (doc instanceof HTMLDocument) {
+ HTMLDocument html = (HTMLDocument) doc;
+ StyleSheet s = html.getStyleSheet();
+ s.addRule("body { margin: 0; padding: 0;"
+ + "font-family: Verdana, Geneva, Arial, Helvetica, sans-serif;"
+ + "font-size: " + 10 * Theme.getScale() / 100 + "; }");
+ }
+ description.setOpaque(false);
+ description.setBorder(new EmptyBorder(4, 7, 7, 7));
+ description.setHighlighter(null);
+ description.setEditable(false);
+ description.addHyperlinkListener(e -> {
+ if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
+ Base.openURL(e.getDescription());
+ }
+ });
+ return description;
+ }
+
+ public void setButtonsVisible(boolean enabled) {
+ installButton.setEnabled(enabled);
+ removeButton.setEnabled(enabled);
+ buttonsPanel.setVisible(enabled);
+ inactiveButtonsPanel.setVisible(!enabled);
+ }
+
+ public void setForeground(Color c) {
+ super.setForeground(c);
+ // The description is not opaque, so copy our foreground color to it.
+ if (description != null)
+ description.setForeground(c);
+ if (titledBorder != null)
+ titledBorder.setTitleColor(c);
+ }
+}
diff --git a/app/src/cc/arduino/contributions/packages/ui/ContributedPlatformTableCellRenderer.java b/app/src/cc/arduino/contributions/packages/ui/ContributedPlatformTableCellRenderer.java
new file mode 100644
index 00000000000..b6f6aae015c
--- /dev/null
+++ b/app/src/cc/arduino/contributions/packages/ui/ContributedPlatformTableCellRenderer.java
@@ -0,0 +1,64 @@
+/*
+ * This file is part of Arduino.
+ *
+ * Copyright 2015 Arduino LLC (http://www.arduino.cc/)
+ *
+ * Arduino is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * As a special exception, you may use this file as part of a free software
+ * library without restriction. Specifically, if other files instantiate
+ * templates or use macros or inline functions from this file, or you compile
+ * this file and link it with other files to produce an executable, this
+ * file does not by itself cause the resulting executable to be covered by
+ * the GNU General Public License. This exception does not however
+ * invalidate any other reasons why the executable file might be covered by
+ * the GNU General Public License.
+ */
+
+package cc.arduino.contributions.packages.ui;
+
+import java.awt.Color;
+import java.awt.Component;
+
+import javax.swing.JTable;
+import javax.swing.table.TableCellRenderer;
+
+@SuppressWarnings("serial")
+public class ContributedPlatformTableCellRenderer implements TableCellRenderer {
+
+ public Component getTableCellRendererComponent(JTable table, Object value,
+ boolean isSelected,
+ boolean hasFocus, int row,
+ int column) {
+ ContributedPlatformTableCellJPanel cell = new ContributedPlatformTableCellJPanel();
+ cell.setButtonsVisible(false);
+ cell.update(table, value, false);
+
+ cell.setForeground(Color.BLACK);
+ if (row % 2 == 0) {
+ cell.setBackground(new Color(236, 241, 241)); // #ecf1f1
+ } else {
+ cell.setBackground(new Color(255, 255, 255));
+ }
+
+ int height = new Double(cell.getPreferredSize().getHeight()).intValue();
+ if (table.getRowHeight(row) < height) {
+ table.setRowHeight(row, height);
+ }
+
+ return cell;
+ }
+
+}
diff --git a/app/src/cc/arduino/contributions/packages/ui/ContributionIndexTableModel.java b/app/src/cc/arduino/contributions/packages/ui/ContributionIndexTableModel.java
new file mode 100644
index 00000000000..2c9939849bb
--- /dev/null
+++ b/app/src/cc/arduino/contributions/packages/ui/ContributionIndexTableModel.java
@@ -0,0 +1,187 @@
+/*
+ * This file is part of Arduino.
+ *
+ * Copyright 2015 Arduino LLC (http://www.arduino.cc/)
+ *
+ * Arduino is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * As a special exception, you may use this file as part of a free software
+ * library without restriction. Specifically, if other files instantiate
+ * templates or use macros or inline functions from this file, or you compile
+ * this file and link it with other files to produce an executable, this
+ * file does not by itself cause the resulting executable to be covered by
+ * the GNU General Public License. This exception does not however
+ * invalidate any other reasons why the executable file might be covered by
+ * the GNU General Public License.
+ */
+
+package cc.arduino.contributions.packages.ui;
+
+import cc.arduino.contributions.packages.ContributedBoard;
+import cc.arduino.contributions.packages.ContributedPackage;
+import cc.arduino.contributions.packages.ContributedPlatform;
+import cc.arduino.contributions.ui.FilteredAbstractTableModel;
+import processing.app.BaseNoGui;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+@SuppressWarnings("serial")
+public class ContributionIndexTableModel
+ extends FilteredAbstractTableModel {
+
+ private final List contributions = new ArrayList<>();
+ private final String[] columnNames = { "Description" };
+ private final Class>[] columnTypes = { ContributedPlatform.class };
+ private Predicate filter;
+ private String[] filters;
+
+ public void updateIndexFilter(String[] filters,
+ Predicate filter) {
+ this.filter = filter;
+ this.filters = filters;
+ updateContributions();
+ }
+
+ private void updateContributions() {
+ contributions.clear();
+
+ // Generate ContributedPlatformReleases from all platform releases
+ for (ContributedPackage pack : BaseNoGui.indexer.getPackages()) {
+ for (ContributedPlatform platform : pack.getPlatforms()) {
+ addContribution(platform);
+ }
+ }
+
+ // Filter ContributedPlatformReleases based on search terms
+ contributions.removeIf(releases -> {
+ for (ContributedPlatform platform : releases.releases) {
+ String compoundTargetSearchText = platform.getName() + "\n"
+ + platform.getBoards().stream()
+ .map(ContributedBoard::getName)
+ .collect(Collectors.joining(" "));
+ if (!filter.test(platform)) {
+ continue;
+ }
+ if (!stringContainsAll(compoundTargetSearchText, filters))
+ continue;
+ return false;
+ }
+ return true;
+ });
+
+ // Sort ContributedPlatformReleases and put deprecated platforms to the bottom
+ Collections.sort(contributions, (x,y)-> {
+ if (x.isDeprecated() != y.isDeprecated()) {
+ return x.isDeprecated() ? 1 : -1;
+ }
+ ContributedPlatform x1 = x.getLatest();
+ ContributedPlatform y1 = y.getLatest();
+ int category = (x1.getCategory().equals("Arduino") ? -1 : 0) + (y1.getCategory().equals("Arduino") ? 1 : 0);
+ if (category != 0) {
+ return category;
+ }
+ return x1.getName().compareToIgnoreCase(y1.getName());
+ });
+
+ fireTableDataChanged();
+ }
+
+ /**
+ * Check if string contains all the substrings in set. The
+ * compare is case insensitive.
+ *
+ * @param string
+ * @param set
+ * @return true if all the strings in set are contained in
+ * string.
+ */
+ private boolean stringContainsAll(String string, String set[]) {
+ if (set == null)
+ return true;
+ for (String s : set) {
+ if (!string.toLowerCase().contains(s.toLowerCase()))
+ return false;
+ }
+ return true;
+ }
+
+ private void addContribution(ContributedPlatform platform) {
+ for (ContributedPlatformReleases contribution : contributions) {
+ if (!contribution.shouldContain(platform)) {
+ continue;
+ }
+ contribution.add(platform);
+ return;
+ }
+ contributions.add(new ContributedPlatformReleases(platform));
+ }
+
+ @Override
+ public int getColumnCount() {
+ return columnNames.length;
+ }
+
+ @Override
+ public int getRowCount() {
+ return contributions.size();
+ }
+
+ @Override
+ public String getColumnName(int column) {
+ return columnNames[column];
+ }
+
+ @Override
+ public Class> getColumnClass(int colum) {
+ return columnTypes[colum];
+ }
+
+ @Override
+ public void setValueAt(Object value, int row, int col) {
+ fireTableCellUpdated(row, col);
+ }
+
+ @Override
+ public Object getValueAt(int row, int col) {
+ if (row >= contributions.size()) {
+ return null;
+ }
+ ContributedPlatformReleases contribution = contributions.get(row);
+ return contribution;// .getSelected();
+ }
+
+ @Override
+ public boolean isCellEditable(int row, int col) {
+ return true;
+ }
+
+ public ContributedPlatformReleases getReleases(int row) {
+ return contributions.get(row);
+ }
+
+ public ContributedPlatform getSelectedRelease(int row) {
+ return contributions.get(row).getSelected();
+ }
+
+ public void update() {
+ updateContributions();
+ fireTableDataChanged();
+ }
+
+}
diff --git a/app/src/cc/arduino/contributions/packages/ui/ContributionManagerUI.java b/app/src/cc/arduino/contributions/packages/ui/ContributionManagerUI.java
new file mode 100644
index 00000000000..c00e91e9d13
--- /dev/null
+++ b/app/src/cc/arduino/contributions/packages/ui/ContributionManagerUI.java
@@ -0,0 +1,242 @@
+/*
+ * This file is part of Arduino.
+ *
+ * Copyright 2015 Arduino LLC (http://www.arduino.cc/)
+ *
+ * Arduino is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * As a special exception, you may use this file as part of a free software
+ * library without restriction. Specifically, if other files instantiate
+ * templates or use macros or inline functions from this file, or you compile
+ * this file and link it with other files to produce an executable, this
+ * file does not by itself cause the resulting executable to be covered by
+ * the GNU General Public License. This exception does not however
+ * invalidate any other reasons why the executable file might be covered by
+ * the GNU General Public License.
+ */
+
+package cc.arduino.contributions.packages.ui;
+
+import cc.arduino.contributions.packages.ContributedPlatform;
+import cc.arduino.contributions.packages.ContributionInstaller;
+import cc.arduino.contributions.ui.*;
+import cc.arduino.utils.Progress;
+import processing.app.BaseNoGui;
+import processing.app.I18n;
+
+import javax.swing.*;
+import javax.swing.table.TableCellRenderer;
+
+import java.awt.*;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+
+import static processing.app.I18n.tr;
+
+@SuppressWarnings("serial")
+public class ContributionManagerUI extends InstallerJDialog {
+
+ private final ContributionInstaller installer;
+
+ @Override
+ protected FilteredAbstractTableModel createContribModel() {
+ return new ContributionIndexTableModel();
+ }
+
+ private ContributionIndexTableModel getContribModel() {
+ return (ContributionIndexTableModel) contribModel;
+ }
+
+ @Override
+ protected TableCellRenderer createCellRenderer() {
+ return new ContributedPlatformTableCellRenderer();
+ }
+
+ @Override
+ protected InstallerTableCell createCellEditor() {
+ return new ContributedPlatformTableCellEditor() {
+ @Override
+ protected void onInstall(ContributedPlatform selected,
+ ContributedPlatform installed) {
+ if (selected.isBuiltIn()) {
+ onRemovePressed(installed, false);
+ } else {
+ onInstallPressed(selected, installed);
+ }
+ }
+
+ @Override
+ protected void onRemove(ContributedPlatform installedPlatform) {
+ onRemovePressed(installedPlatform, true);
+ }
+ };
+ }
+
+ public ContributionManagerUI(Frame parent, ContributionInstaller installer) {
+ super(parent, tr("Boards Manager"), Dialog.ModalityType.APPLICATION_MODAL,
+ tr("Unable to reach Arduino.cc due to possible network issues."));
+ this.installer = installer;
+ }
+
+ private Collection oldCategories = new ArrayList<>();
+
+ public void updateUI() {
+ // Check if categories have changed
+ Collection categories = BaseNoGui.indexer.getCategories();
+ if (categories.equals(oldCategories)) {
+ return;
+ }
+ oldCategories = categories;
+
+ categoryChooser.removeActionListener(categoryChooserActionListener);
+ // Enable categories combo only if there are two or more choices
+ filterField.setEnabled(getContribModel().getRowCount() > 0);
+ categoryFilter = x -> true;
+ categoryChooser.removeAllItems();
+ categoryChooser.addItem(new DropdownAllCoresItem());
+ categoryChooser.addItem(new DropdownUpdatableCoresItem());
+ for (String s : categories) {
+ categoryChooser.addItem(new DropdownCoreOfCategoryItem(s));
+ }
+ categoryChooser.addActionListener(categoryChooserActionListener);
+ categoryChooser.setSelectedIndex(0);
+ }
+
+ public void setProgress(Progress progress) {
+ progressBar.setValue(progress);
+ }
+
+ /*
+ * Installer methods follows
+ */
+
+ private Thread installerThread = null;
+
+ @Override
+ public void onCancelPressed() {
+ super.onCancelPressed();
+ if (installerThread != null) {
+ installerThread.interrupt();
+ }
+ }
+
+ @Override
+ public void onUpdatePressed() {
+ super.onUpdatePressed();
+ installerThread = new Thread(() -> {
+ try {
+ setProgressVisible(true, "");
+ installer.updateIndex(this::setProgress);
+ onIndexesUpdated();
+ if (contribTable.getCellEditor() != null) {
+ contribTable.getCellEditor().stopCellEditing();
+ }
+ getContribModel().update();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ } finally {
+ setProgressVisible(false, "");
+ }
+ });
+ installerThread.setName("ContributionManager Update Thread");
+ installerThread
+ .setUncaughtExceptionHandler(new InstallerJDialogUncaughtExceptionHandler(
+ this, noConnectionErrorMessage));
+ installerThread.start();
+ }
+
+ public void onInstallPressed(final ContributedPlatform platformToInstall,
+ final ContributedPlatform platformToRemove) {
+ clearErrorMessage();
+ installerThread = new Thread(() -> {
+ List errors = new LinkedList<>();
+ try {
+ setProgressVisible(true, tr("Installing..."));
+ if (platformToRemove != null && !platformToRemove.isBuiltIn()) {
+ errors.addAll(installer.remove(platformToRemove));
+ }
+ errors.addAll(installer.install(platformToInstall, this::setProgress));
+ onIndexesUpdated();
+ if (contribTable.getCellEditor() != null) {
+ contribTable.getCellEditor().stopCellEditing();
+ }
+ getContribModel().update();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ } finally {
+ setProgressVisible(false, "");
+ if (!errors.isEmpty()) {
+ setErrorMessage(errors.get(0));
+ }
+ }
+ });
+ installerThread.setName("ContributionManager Install Thread");
+ installerThread
+ .setUncaughtExceptionHandler(new InstallerJDialogUncaughtExceptionHandler(
+ this, noConnectionErrorMessage));
+ installerThread.start();
+ }
+
+ public void onRemovePressed(final ContributedPlatform platform,
+ boolean showWarning) {
+ clearErrorMessage();
+
+ if (showWarning) {
+ int chosenOption = JOptionPane
+ .showConfirmDialog(this,
+ I18n.format(tr("Do you want to remove {0}?\nIf you do so you won't be able to use {0} any more."),
+ platform.getName()),
+ tr("Please confirm boards deletion"),
+ JOptionPane.YES_NO_OPTION,
+ JOptionPane.QUESTION_MESSAGE);
+ if (chosenOption != JOptionPane.YES_OPTION) {
+ return;
+ }
+ }
+
+ installerThread = new Thread(() -> {
+ try {
+ setProgressVisible(true, tr("Removing..."));
+ installer.remove(platform);
+ onIndexesUpdated();
+ if (contribTable.getCellEditor() != null) {
+ contribTable.getCellEditor().stopCellEditing();
+ }
+ getContribModel().update();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ } finally {
+ setProgressVisible(false, "");
+ }
+ });
+ installerThread.setName("ContributionManager Remove Thread");
+ installerThread
+ .setUncaughtExceptionHandler(new InstallerJDialogUncaughtExceptionHandler(
+ this, noConnectionErrorMessage));
+ installerThread.start();
+ }
+
+ /**
+ * Callback invoked when indexes are updated
+ *
+ * @throws Exception
+ */
+ protected void onIndexesUpdated() throws Exception {
+ // Empty
+ }
+
+}
diff --git a/app/src/cc/arduino/contributions/packages/ui/DropdownAllCoresItem.java b/app/src/cc/arduino/contributions/packages/ui/DropdownAllCoresItem.java
new file mode 100644
index 00000000000..15a7bf531d8
--- /dev/null
+++ b/app/src/cc/arduino/contributions/packages/ui/DropdownAllCoresItem.java
@@ -0,0 +1,50 @@
+/*
+ * This file is part of Arduino.
+ *
+ * Copyright 2015 Arduino LLC (http://www.arduino.cc/)
+ *
+ * Arduino is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * As a special exception, you may use this file as part of a free software
+ * library without restriction. Specifically, if other files instantiate
+ * templates or use macros or inline functions from this file, or you compile
+ * this file and link it with other files to produce an executable, this
+ * file does not by itself cause the resulting executable to be covered by
+ * the GNU General Public License. This exception does not however
+ * invalidate any other reasons why the executable file might be covered by
+ * the GNU General Public License.
+ */
+
+package cc.arduino.contributions.packages.ui;
+
+import cc.arduino.contributions.packages.ContributedPlatform;
+import cc.arduino.contributions.ui.DropdownItem;
+
+import java.util.function.Predicate;
+
+import static processing.app.I18n.tr;
+
+public class DropdownAllCoresItem implements DropdownItem {
+
+ public String toString() {
+ return tr("All");
+ }
+
+ @Override
+ public Predicate getFilterPredicate() {
+ return x -> true;
+ }
+
+}
diff --git a/app/src/cc/arduino/contributions/packages/ui/DropdownCoreOfCategoryItem.java b/app/src/cc/arduino/contributions/packages/ui/DropdownCoreOfCategoryItem.java
new file mode 100644
index 00000000000..6de03b587b3
--- /dev/null
+++ b/app/src/cc/arduino/contributions/packages/ui/DropdownCoreOfCategoryItem.java
@@ -0,0 +1,57 @@
+/*
+ * This file is part of Arduino.
+ *
+ * Copyright 2015 Arduino LLC (http://www.arduino.cc/)
+ *
+ * Arduino is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * As a special exception, you may use this file as part of a free software
+ * library without restriction. Specifically, if other files instantiate
+ * templates or use macros or inline functions from this file, or you compile
+ * this file and link it with other files to produce an executable, this
+ * file does not by itself cause the resulting executable to be covered by
+ * the GNU General Public License. This exception does not however
+ * invalidate any other reasons why the executable file might be covered by
+ * the GNU General Public License.
+ */
+
+package cc.arduino.contributions.packages.ui;
+
+import cc.arduino.contributions.packages.ContributedPlatform;
+import cc.arduino.contributions.packages.filters.CategoryPredicate;
+import cc.arduino.contributions.ui.DropdownItem;
+
+import java.util.function.Predicate;
+
+import static processing.app.I18n.tr;
+
+public class DropdownCoreOfCategoryItem implements DropdownItem {
+
+ private final String category;
+
+ public DropdownCoreOfCategoryItem(String category) {
+ this.category = category;
+ }
+
+ public String toString() {
+ return tr(category);
+ }
+
+ @Override
+ public Predicate getFilterPredicate() {
+ return new CategoryPredicate(category);
+ }
+
+}
diff --git a/app/src/cc/arduino/contributions/packages/ui/DropdownUpdatableCoresItem.java b/app/src/cc/arduino/contributions/packages/ui/DropdownUpdatableCoresItem.java
new file mode 100644
index 00000000000..7f704b388b5
--- /dev/null
+++ b/app/src/cc/arduino/contributions/packages/ui/DropdownUpdatableCoresItem.java
@@ -0,0 +1,52 @@
+/*
+ * This file is part of Arduino.
+ *
+ * Copyright 2015 Arduino LLC (http://www.arduino.cc/)
+ *
+ * Arduino is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * As a special exception, you may use this file as part of a free software
+ * library without restriction. Specifically, if other files instantiate
+ * templates or use macros or inline functions from this file, or you compile
+ * this file and link it with other files to produce an executable, this
+ * file does not by itself cause the resulting executable to be covered by
+ * the GNU General Public License. This exception does not however
+ * invalidate any other reasons why the executable file might be covered by
+ * the GNU General Public License.
+ */
+
+package cc.arduino.contributions.packages.ui;
+
+import cc.arduino.contributions.packages.ContributedPlatform;
+import cc.arduino.contributions.packages.filters.UpdatablePlatformPredicate;
+import cc.arduino.contributions.ui.DropdownItem;
+
+import java.util.function.Predicate;
+
+import static processing.app.I18n.tr;
+
+public class DropdownUpdatableCoresItem implements DropdownItem {
+
+ @Override
+ public Predicate getFilterPredicate() {
+ return new UpdatablePlatformPredicate();
+ }
+
+ @Override
+ public String toString() {
+ return tr("Updatable");
+ }
+
+}
diff --git a/app/src/cc/arduino/contributions/ui/DropdownItem.java b/app/src/cc/arduino/contributions/ui/DropdownItem.java
new file mode 100644
index 00000000000..2d18e2fdcb4
--- /dev/null
+++ b/app/src/cc/arduino/contributions/ui/DropdownItem.java
@@ -0,0 +1,38 @@
+/*
+ * This file is part of Arduino.
+ *
+ * Copyright 2015 Arduino LLC (http://www.arduino.cc/)
+ *
+ * Arduino is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * As a special exception, you may use this file as part of a free software
+ * library without restriction. Specifically, if other files instantiate
+ * templates or use macros or inline functions from this file, or you compile
+ * this file and link it with other files to produce an executable, this
+ * file does not by itself cause the resulting executable to be covered by
+ * the GNU General Public License. This exception does not however
+ * invalidate any other reasons why the executable file might be covered by
+ * the GNU General Public License.
+ */
+
+package cc.arduino.contributions.ui;
+
+import java.util.function.Predicate;
+
+public interface DropdownItem {
+
+ Predicate getFilterPredicate();
+
+}
diff --git a/app/src/cc/arduino/contributions/ui/FilterJTextField.java b/app/src/cc/arduino/contributions/ui/FilterJTextField.java
new file mode 100644
index 00000000000..83aeba45430
--- /dev/null
+++ b/app/src/cc/arduino/contributions/ui/FilterJTextField.java
@@ -0,0 +1,149 @@
+/*
+ * This file is part of Arduino.
+ *
+ * Copyright 2015 Arduino LLC (http://www.arduino.cc/)
+ *
+ * Arduino is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * As a special exception, you may use this file as part of a free software
+ * library without restriction. Specifically, if other files instantiate
+ * templates or use macros or inline functions from this file, or you compile
+ * this file and link it with other files to produce an executable, this
+ * file does not by itself cause the resulting executable to be covered by
+ * the GNU General Public License. This exception does not however
+ * invalidate any other reasons why the executable file might be covered by
+ * the GNU General Public License.
+ */
+
+package cc.arduino.contributions.ui;
+
+import javax.swing.*;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+import java.awt.*;
+import java.awt.event.FocusEvent;
+import java.awt.event.FocusListener;
+
+@SuppressWarnings("serial")
+public class FilterJTextField extends JTextField {
+ private final String filterHint;
+
+ private boolean showingHint;
+ private Timer timer;
+
+ public FilterJTextField(String hint) {
+ super(hint);
+ filterHint = hint;
+
+ showingHint = true;
+ updateStyle();
+ timer = new Timer(1000, e -> {
+ applyFilter();
+ timer.stop();
+ });
+
+ addFocusListener(new FocusListener() {
+ public void focusLost(FocusEvent focusEvent) {
+ if (getText().isEmpty()) {
+ showingHint = true;
+ }
+ updateStyle();
+ }
+
+ public void focusGained(FocusEvent focusEvent) {
+ if (showingHint) {
+ showingHint = false;
+ setText("");
+ }
+ updateStyle();
+ }
+ });
+
+ getDocument().addDocumentListener(new DocumentListener() {
+ public void removeUpdate(DocumentEvent e) {
+ spawnTimer();
+ }
+
+ public void insertUpdate(DocumentEvent e) {
+ spawnTimer();
+ }
+
+ public void changedUpdate(DocumentEvent e) {
+
+ }
+ });
+
+ addActionListener(e -> {
+ if (timer.isRunning()) {
+ timer.stop();
+ }
+ applyFilter();
+ });
+ }
+
+ private void spawnTimer() {
+ if (timer.isRunning()) {
+ timer.stop();
+ }
+ timer.start();
+ }
+
+ public void applyFilter() {
+ String[] filteredText = new String[0];
+ if (!showingHint) {
+ String filter = getText().toLowerCase();
+
+ // Replace anything but 0-9, a-z, or : with a space
+ filter = filter.replaceAll("[^\\x30-\\x39^\\x61-\\x7a^\\x3a]", " ");
+
+ filteredText = filter.split(" ");
+ }
+ onFilter(filteredText);
+ }
+
+ protected void onFilter(String[] strings) {
+ // Empty
+ }
+
+ private void updateStyle() {
+ if (showingHint) {
+ setText(filterHint);
+ setForeground(Color.gray);
+ setFont(getFont().deriveFont(Font.ITALIC));
+ } else {
+ setForeground(UIManager.getColor("TextField.foreground"));
+ setFont(getFont().deriveFont(Font.PLAIN));
+ }
+ }
+
+ @Override
+ public void paste() {
+
+ // Same precondition check as JTextComponent#paste().
+ if (!isEditable() || !isEnabled()) {
+ return;
+ }
+
+ // Disable hint to prevent the focus handler from clearing the pasted text.
+ if (showingHint) {
+ showingHint = false;
+ setText("");
+ updateStyle();
+ }
+
+ // Perform the paste.
+ super.paste();
+ }
+}
diff --git a/app/src/cc/arduino/contributions/ui/FilteredAbstractTableModel.java b/app/src/cc/arduino/contributions/ui/FilteredAbstractTableModel.java
new file mode 100644
index 00000000000..348561c31bf
--- /dev/null
+++ b/app/src/cc/arduino/contributions/ui/FilteredAbstractTableModel.java
@@ -0,0 +1,57 @@
+/*
+ * This file is part of Arduino.
+ *
+ * Copyright 2015 Arduino LLC (http://www.arduino.cc/)
+ *
+ * Arduino is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * As a special exception, you may use this file as part of a free software
+ * library without restriction. Specifically, if other files instantiate
+ * templates or use macros or inline functions from this file, or you compile
+ * this file and link it with other files to produce an executable, this
+ * file does not by itself cause the resulting executable to be covered by
+ * the GNU General Public License. This exception does not however
+ * invalidate any other reasons why the executable file might be covered by
+ * the GNU General Public License.
+ */
+
+package cc.arduino.contributions.ui;
+
+import cc.arduino.contributions.DownloadableContribution;
+import cc.arduino.contributions.VersionComparator;
+
+import javax.swing.table.AbstractTableModel;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.function.Predicate;
+
+public abstract class FilteredAbstractTableModel extends AbstractTableModel {
+
+ abstract public void updateIndexFilter(String[] filters, Predicate additionalFilter);
+
+ public static T getLatestOf(List contribs) {
+ contribs = new LinkedList<>(contribs);
+ final VersionComparator versionComparator = new VersionComparator();
+ Collections.sort(contribs, (contrib1, contrib2) -> versionComparator.compare(contrib1.getParsedVersion(), contrib2.getParsedVersion()));
+
+ if (contribs.isEmpty()) {
+ return null;
+ }
+
+ return contribs.get(contribs.size() - 1);
+ }
+
+}
diff --git a/app/src/cc/arduino/contributions/ui/InstallerJDialog.java b/app/src/cc/arduino/contributions/ui/InstallerJDialog.java
new file mode 100644
index 00000000000..8abff8f3454
--- /dev/null
+++ b/app/src/cc/arduino/contributions/ui/InstallerJDialog.java
@@ -0,0 +1,381 @@
+/*
+ * This file is part of Arduino.
+ *
+ * Copyright 2015 Arduino LLC (http://www.arduino.cc/)
+ *
+ * Arduino is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * As a special exception, you may use this file as part of a free software
+ * library without restriction. Specifically, if other files instantiate
+ * templates or use macros or inline functions from this file, or you compile
+ * this file and link it with other files to produce an executable, this
+ * file does not by itself cause the resulting executable to be covered by
+ * the GNU General Public License. This exception does not however
+ * invalidate any other reasons why the executable file might be covered by
+ * the GNU General Public License.
+ */
+
+package cc.arduino.contributions.ui;
+
+import static processing.app.I18n.tr;
+import static processing.app.Theme.scale;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.Frame;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.FocusEvent;
+import java.awt.event.FocusListener;
+import java.awt.event.KeyEvent;
+import java.awt.event.WindowEvent;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseMotionListener;
+import java.awt.event.WindowAdapter;
+import java.util.function.Predicate;
+
+import javax.swing.Action;
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+import javax.swing.JScrollPane;
+import javax.swing.JTable;
+import javax.swing.ListSelectionModel;
+import javax.swing.ScrollPaneConstants;
+import javax.swing.SwingUtilities;
+import javax.swing.WindowConstants;
+import javax.swing.border.EmptyBorder;
+import javax.swing.table.TableCellRenderer;
+import javax.swing.table.TableColumn;
+import javax.swing.table.TableColumnModel;
+import javax.swing.text.DefaultEditorKit;
+
+import cc.arduino.contributions.ui.listeners.AbstractKeyListener;
+import processing.app.Base;
+
+public abstract class InstallerJDialog extends JDialog {
+
+ // Toolbar on top of the window:
+ // - Categories drop-down menu
+ protected final JComboBox categoryChooser;
+ // - Search text-field
+ protected final FilterJTextField filterField;
+ protected final JPanel filtersContainer;
+ // Currently selected category and filters
+ protected Predicate extraFilter = x -> true;
+ protected Predicate categoryFilter;
+ protected String[] filters;
+ protected final String noConnectionErrorMessage;
+
+ // Real contribution table
+ protected JTable contribTable;
+ // Model behind the table
+ protected final FilteredAbstractTableModel contribModel;
+ private final JButton closeButton;
+ private final JButton dismissErrorMessageButton;
+
+ protected int previousRowAtPoint = -1;
+
+ abstract protected FilteredAbstractTableModel createContribModel();
+
+ abstract protected TableCellRenderer createCellRenderer();
+
+ abstract protected InstallerTableCell createCellEditor();
+
+ // Bottom:
+ // - Progress bar
+ protected final ProgressJProgressBar progressBar;
+ private final Box progressBox;
+ private final Box errorMessageBox;
+ private final JLabel errorMessage;
+
+ public InstallerJDialog(Frame parent, String title, ModalityType applicationModal, String noConnectionErrorMessage) {
+ super(parent, title, applicationModal);
+ this.noConnectionErrorMessage = noConnectionErrorMessage;
+
+ setResizable(true);
+
+ Container pane = getContentPane();
+ pane.setLayout(new BorderLayout());
+
+ {
+ categoryChooser = new JComboBox();
+ categoryChooser.setMaximumRowCount(20);
+ categoryChooser.setEnabled(false);
+
+ filterField = new FilterJTextField(tr("Filter your search...")) {
+ @Override
+ protected void onFilter(String[] _filters) {
+ previousRowAtPoint = -1;
+ filters = _filters;
+ if (contribTable.getCellEditor() != null) {
+ contribTable.getCellEditor().stopCellEditing();
+ }
+ updateIndexFilter(filters, categoryFilter);
+ }
+ };
+ filterField.getAccessibleContext().setAccessibleDescription(tr("Search Filter"));
+
+ // Add cut/copy/paste contextual menu to the search filter input field.
+ JPopupMenu menu = new JPopupMenu();
+
+ Action cut = new DefaultEditorKit.CutAction();
+ cut.putValue(Action.NAME, tr("Cut"));
+ menu.add(cut);
+
+ Action copy = new DefaultEditorKit.CopyAction();
+ copy.putValue(Action.NAME, tr("Copy"));
+ menu.add(copy);
+
+ Action paste = new DefaultEditorKit.PasteAction();
+ paste.putValue(Action.NAME, tr("Paste"));
+ menu.add(paste);
+
+ filterField.setComponentPopupMenu(menu);
+
+ // Focus the filter field when the window opens.
+ addWindowListener(new WindowAdapter() {
+ @Override
+ public void windowOpened(WindowEvent e) {
+ filterField.requestFocus();
+ }
+ });
+
+ filtersContainer = new JPanel();
+ filtersContainer.setLayout(new BoxLayout(filtersContainer, BoxLayout.X_AXIS));
+ filtersContainer.add(Box.createHorizontalStrut(5));
+ filtersContainer.add(new JLabel(tr("Type")));
+ filtersContainer.add(Box.createHorizontalStrut(5));
+ filtersContainer.add(categoryChooser);
+ filtersContainer.add(Box.createHorizontalStrut(5));
+ filtersContainer.add(filterField);
+ filtersContainer.setBorder(new EmptyBorder(7, 7, 7, 7));
+ pane.add(filtersContainer, BorderLayout.NORTH);
+ }
+
+ contribModel = createContribModel();
+ contribTable = new JTable(contribModel);
+ contribTable.setTableHeader(null);
+ contribTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+ contribTable.setColumnSelectionAllowed(false);
+ contribTable.setDragEnabled(false);
+ contribTable.setIntercellSpacing(new Dimension(0, 1));
+ contribTable.setShowVerticalLines(false);
+ contribTable.addKeyListener(new AbstractKeyListener() {
+
+ @Override
+ public void keyReleased(KeyEvent keyEvent) {
+ if (keyEvent.getKeyCode() != KeyEvent.VK_DOWN && keyEvent.getKeyCode() != KeyEvent.VK_UP) {
+ return;
+ }
+
+ if (!contribTable.isEnabled()) {
+ return;
+ }
+
+ contribTable.editCellAt(contribTable.getSelectedRow(), contribTable.getSelectedColumn());
+ }
+ });
+
+ contribTable.addMouseMotionListener(new MouseMotionListener() {
+
+ public void mouseDragged(MouseEvent e) {}
+
+ public void mouseMoved(MouseEvent e) {
+ // avoid firing edits events until the mouse changes cell or the user is back on the cell after selecting a dropdown
+ int rowAtPoint = contribTable.rowAtPoint(e.getPoint());
+ if (!InstallerTableCell.isDropdownSelected() && rowAtPoint != previousRowAtPoint) {
+ contribTable.editCellAt(rowAtPoint, 0);
+ previousRowAtPoint = rowAtPoint;
+ InstallerTableCell.dropdownSelected(false);
+ }
+ if (InstallerTableCell.isDropdownSelected() && rowAtPoint == previousRowAtPoint) {
+ // back to the original cell, can drop dropdown selector lock
+ InstallerTableCell.dropdownSelected(false);
+ }
+ }
+ });
+
+ {
+ TableColumnModel tcm = contribTable.getColumnModel();
+ TableColumn col = tcm.getColumn(0);
+ col.setCellRenderer(createCellRenderer());
+ col.setCellEditor(createCellEditor());
+ col.setResizable(true);
+ }
+
+ {
+ JScrollPane scrollPane = new JScrollPane();
+ scrollPane.setViewportView(contribTable);
+ scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
+ scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
+ scrollPane.getVerticalScrollBar().setUnitIncrement(7);
+ pane.add(scrollPane, BorderLayout.CENTER);
+ }
+
+ pane.add(Box.createHorizontalStrut(10), BorderLayout.WEST);
+ pane.add(Box.createHorizontalStrut(10), BorderLayout.EAST);
+
+ progressBar = new ProgressJProgressBar();
+ progressBar.setStringPainted(true);
+ progressBar.setString(" ");
+ progressBar.setVisible(true);
+
+ errorMessage = new JLabel("");
+ errorMessage.setForeground(Color.RED);
+
+ {
+ JButton cancelButton = new JButton(tr("Cancel"));
+ cancelButton.addActionListener(arg0 -> onCancelPressed());
+
+ progressBox = Box.createHorizontalBox();
+ progressBox.add(progressBar);
+ progressBox.add(Box.createHorizontalStrut(5));
+ progressBox.add(cancelButton);
+
+ dismissErrorMessageButton = new JButton(tr("OK"));
+ dismissErrorMessageButton.addActionListener(arg0 -> {
+ clearErrorMessage();
+ setErrorMessageVisible(false);
+ });
+
+ closeButton = new JButton(tr("Close"));
+ closeButton.addActionListener(arg0 -> InstallerJDialog.this.dispatchEvent(new WindowEvent(InstallerJDialog.this, WindowEvent.WINDOW_CLOSING)));
+
+ errorMessageBox = Box.createHorizontalBox();
+ errorMessageBox.add(Box.createHorizontalGlue());
+ errorMessageBox.add(errorMessage);
+ errorMessageBox.add(Box.createHorizontalGlue());
+ errorMessageBox.add(dismissErrorMessageButton);
+ errorMessageBox.add(closeButton);
+ errorMessageBox.setVisible(false);
+ }
+
+ {
+ JPanel progressPanel = new JPanel();
+ progressPanel.setBorder(new EmptyBorder(7, 10, 7, 10));
+ progressPanel.setLayout(new BoxLayout(progressPanel, BoxLayout.Y_AXIS));
+ progressPanel.add(progressBox);
+ progressPanel.add(errorMessageBox);
+ pane.add(progressPanel, BorderLayout.SOUTH);
+ }
+ setProgressVisible(false, "");
+
+ setMinimumSize(scale(new Dimension(800, 450)));
+
+ setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
+
+ Base.registerWindowCloseKeys(getRootPane(), e -> InstallerJDialog.this.dispatchEvent(new WindowEvent(InstallerJDialog.this, WindowEvent.WINDOW_CLOSING)));
+
+ SwingUtilities.invokeLater(InstallerJDialog.this::onUpdatePressed);
+ }
+
+ public void updateIndexFilter(String[] filters, Predicate additionalFilter) {
+ contribModel.updateIndexFilter(filters, additionalFilter);
+ }
+
+ public void setErrorMessage(String message) {
+ errorMessage.setText("" + message + "");
+ setErrorMessageVisible(true);
+ }
+
+ public void clearErrorMessage() {
+ errorMessage.setText("");
+ setErrorMessageVisible(false);
+ }
+
+ public void setProgressVisible(boolean visible, String status) {
+ if (visible) {
+ setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
+ } else {
+ setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
+ }
+ errorMessageBox.setVisible(!visible);
+ progressBox.setVisible(visible);
+
+ filterField.setEnabled(!visible);
+ categoryChooser.setEnabled(!visible);
+ contribTable.setEnabled(!visible);
+ if (contribTable.getCellEditor() != null) {
+ ((InstallerTableCell) contribTable.getCellEditor()).setEnabled(!visible);
+ ((InstallerTableCell) contribTable.getCellEditor()).setStatus(status);
+ }
+ }
+
+ private void setErrorMessageVisible(boolean visible) {
+ errorMessage.setVisible(visible);
+ dismissErrorMessageButton.setVisible(visible);
+ closeButton.setVisible(!visible);
+ errorMessageBox.setVisible(true);
+ }
+
+ protected final ActionListener categoryChooserActionListener = new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent event) {
+ DropdownItem selected = (DropdownItem) categoryChooser.getSelectedItem();
+ previousRowAtPoint = -1;
+ if (selected != null && categoryFilter != selected.getFilterPredicate()) {
+ categoryFilter = selected.getFilterPredicate();
+ if (contribTable.getCellEditor() != null) {
+ contribTable.getCellEditor().stopCellEditing();
+ }
+ updateIndexFilter(filters, categoryFilter.and(extraFilter));
+ }
+ }
+ };
+
+ public void setFilterText(String filterText) {
+ for (FocusListener listener : filterField.getFocusListeners()) {
+ listener.focusGained(new FocusEvent(filterField, FocusEvent.FOCUS_GAINED));
+ }
+ filterField.setText(filterText);
+ filterField.applyFilter();
+ }
+
+ public void selectDropdownItemByClassName(String dropdownItem) {
+ selectDropdownItemByClassName(categoryChooser, dropdownItem);
+ }
+
+ public void selectDropdownItemByClassName(JComboBox combo, String dropdownItem) {
+ for (int i = 0; i < combo.getItemCount(); i++) {
+ if (dropdownItem.equals(combo.getItemAt(i).getClass().getSimpleName())) {
+ combo.setSelectedIndex(i);
+ return;
+ }
+ }
+ }
+
+ /**
+ * Action performed when the Cancel button is pressed.
+ */
+ protected void onCancelPressed() {
+ clearErrorMessage();
+ }
+
+ /**
+ * Action performed when the "Update List" button is pressed.
+ */
+ protected void onUpdatePressed() {
+ clearErrorMessage();
+ }
+
+}
diff --git a/app/src/cc/arduino/contributions/ui/InstallerJDialogUncaughtExceptionHandler.java b/app/src/cc/arduino/contributions/ui/InstallerJDialogUncaughtExceptionHandler.java
new file mode 100644
index 00000000000..df0067b9036
--- /dev/null
+++ b/app/src/cc/arduino/contributions/ui/InstallerJDialogUncaughtExceptionHandler.java
@@ -0,0 +1,60 @@
+/*
+ * This file is part of Arduino.
+ *
+ * Copyright 2015 Arduino LLC (http://www.arduino.cc/)
+ *
+ * Arduino is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * As a special exception, you may use this file as part of a free software
+ * library without restriction. Specifically, if other files instantiate
+ * templates or use macros or inline functions from this file, or you compile
+ * this file and link it with other files to produce an executable, this
+ * file does not by itself cause the resulting executable to be covered by
+ * the GNU General Public License. This exception does not however
+ * invalidate any other reasons why the executable file might be covered by
+ * the GNU General Public License.
+ */
+
+package cc.arduino.contributions.ui;
+
+import javax.swing.*;
+
+import static processing.app.I18n.tr;
+
+public class InstallerJDialogUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
+
+ private final InstallerJDialog parent;
+ private final String connectionErrorMessage;
+
+ public InstallerJDialogUncaughtExceptionHandler(InstallerJDialog parent, String connectionErrorMessage) {
+ this.parent = parent;
+ this.connectionErrorMessage = connectionErrorMessage;
+ }
+
+ @Override
+ public void uncaughtException(Thread t, final Throwable e) {
+ String errorMessage = tr(e.getMessage().substring(e.getMessage().indexOf(":") + 1));
+ if (errorMessage.startsWith("Error downloading")) {
+ errorMessage = connectionErrorMessage;
+ }
+ final String finalErrorMessage = errorMessage;
+ SwingUtilities.invokeLater(() -> {
+ System.err.println(finalErrorMessage);
+ e.printStackTrace();
+ });
+ parent.setErrorMessage(finalErrorMessage);
+ }
+
+}
diff --git a/app/src/cc/arduino/contributions/ui/InstallerTableCell.java b/app/src/cc/arduino/contributions/ui/InstallerTableCell.java
new file mode 100644
index 00000000000..3f94d3e0d79
--- /dev/null
+++ b/app/src/cc/arduino/contributions/ui/InstallerTableCell.java
@@ -0,0 +1,72 @@
+/*
+ * This file is part of Arduino.
+ *
+ * Copyright 2015 Arduino LLC (http://www.arduino.cc/)
+ *
+ * Arduino is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * As a special exception, you may use this file as part of a free software
+ * library without restriction. Specifically, if other files instantiate
+ * templates or use macros or inline functions from this file, or you compile
+ * this file and link it with other files to produce an executable, this
+ * file does not by itself cause the resulting executable to be covered by
+ * the GNU General Public License. This exception does not however
+ * invalidate any other reasons why the executable file might be covered by
+ * the GNU General Public License.
+ */
+
+package cc.arduino.contributions.ui;
+
+import java.awt.Dimension;
+import java.awt.Rectangle;
+
+import javax.swing.AbstractCellEditor;
+import javax.swing.JTextPane;
+import javax.swing.table.TableCellEditor;
+import javax.swing.text.BadLocationException;
+
+public abstract class InstallerTableCell extends AbstractCellEditor implements TableCellEditor {
+
+ abstract public void setEnabled(boolean b);
+
+ private static boolean dropdownSelected = false;
+
+ public static boolean isDropdownSelected( ) {
+ return dropdownSelected;
+ }
+
+ public static void dropdownSelected(boolean b) {
+ dropdownSelected = b;
+ }
+
+ abstract public void setStatus(String s);
+
+ public static void setJTextPaneDimensionToFitContainedText(JTextPane jTextPane, int width) {
+ Dimension minimumDimension = new Dimension(width, 10);
+ jTextPane.setPreferredSize(minimumDimension);
+ jTextPane.setSize(minimumDimension);
+
+ try {
+ Rectangle r = jTextPane.modelToView(jTextPane.getDocument().getLength());
+ //r.height += jTextPane.modelToView(0).y; // add margins
+ Dimension d = new Dimension(minimumDimension.width, r.y + r.height);
+ jTextPane.setPreferredSize(d);
+ } catch (BadLocationException e) {
+ throw new RuntimeException(e);
+ }
+
+ }
+
+}
diff --git a/app/src/cc/arduino/contributions/ui/ProgressJProgressBar.java b/app/src/cc/arduino/contributions/ui/ProgressJProgressBar.java
new file mode 100644
index 00000000000..7c946e4993e
--- /dev/null
+++ b/app/src/cc/arduino/contributions/ui/ProgressJProgressBar.java
@@ -0,0 +1,50 @@
+/*
+ * This file is part of Arduino.
+ *
+ * Copyright 2015 Arduino LLC (http://www.arduino.cc/)
+ *
+ * Arduino is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * As a special exception, you may use this file as part of a free software
+ * library without restriction. Specifically, if other files instantiate
+ * templates or use macros or inline functions from this file, or you compile
+ * this file and link it with other files to produce an executable, this
+ * file does not by itself cause the resulting executable to be covered by
+ * the GNU General Public License. This exception does not however
+ * invalidate any other reasons why the executable file might be covered by
+ * the GNU General Public License.
+ */
+
+package cc.arduino.contributions.ui;
+
+import cc.arduino.utils.Progress;
+
+import javax.swing.*;
+
+@SuppressWarnings("serial")
+public class ProgressJProgressBar extends JProgressBar {
+
+ public void setValue(Progress p) {
+ setValue((int) p.getProgress());
+ if (p.getStatus() != null) {
+ setString(p.getStatus());
+ // copy status to accessibility context for screen readers to use
+ getAccessibleContext().setAccessibleDescription(p.getStatus());
+ // make status focusable so screen readers can get to it
+ setFocusable(true);
+ }
+ }
+
+}
diff --git a/app/src/cc/arduino/contributions/ui/listeners/AbstractKeyListener.java b/app/src/cc/arduino/contributions/ui/listeners/AbstractKeyListener.java
new file mode 100644
index 00000000000..3562a89a814
--- /dev/null
+++ b/app/src/cc/arduino/contributions/ui/listeners/AbstractKeyListener.java
@@ -0,0 +1,52 @@
+/*
+ * This file is part of Arduino.
+ *
+ * Copyright 2015 Arduino LLC (http://www.arduino.cc/)
+ *
+ * Arduino is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * As a special exception, you may use this file as part of a free software
+ * library without restriction. Specifically, if other files instantiate
+ * templates or use macros or inline functions from this file, or you compile
+ * this file and link it with other files to produce an executable, this
+ * file does not by itself cause the resulting executable to be covered by
+ * the GNU General Public License. This exception does not however
+ * invalidate any other reasons why the executable file might be covered by
+ * the GNU General Public License.
+ */
+
+package cc.arduino.contributions.ui.listeners;
+
+import java.awt.event.KeyEvent;
+import java.awt.event.KeyListener;
+
+public abstract class AbstractKeyListener implements KeyListener {
+
+ @Override
+ public void keyTyped(KeyEvent keyEvent) {
+
+ }
+
+ @Override
+ public void keyPressed(KeyEvent keyEvent) {
+
+ }
+
+ @Override
+ public void keyReleased(KeyEvent keyEvent) {
+
+ }
+
+}
diff --git a/app/src/cc/arduino/contributions/ui/listeners/DelegatingKeyListener.java b/app/src/cc/arduino/contributions/ui/listeners/DelegatingKeyListener.java
new file mode 100644
index 00000000000..161b67eb1f8
--- /dev/null
+++ b/app/src/cc/arduino/contributions/ui/listeners/DelegatingKeyListener.java
@@ -0,0 +1,76 @@
+/*
+ * This file is part of Arduino.
+ *
+ * Copyright 2015 Arduino LLC (http://www.arduino.cc/)
+ *
+ * Arduino is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * As a special exception, you may use this file as part of a free software
+ * library without restriction. Specifically, if other files instantiate
+ * templates or use macros or inline functions from this file, or you compile
+ * this file and link it with other files to produce an executable, this
+ * file does not by itself cause the resulting executable to be covered by
+ * the GNU General Public License. This exception does not however
+ * invalidate any other reasons why the executable file might be covered by
+ * the GNU General Public License.
+ */
+
+package cc.arduino.contributions.ui.listeners;
+
+import java.awt.*;
+import java.awt.event.KeyEvent;
+import java.awt.event.KeyListener;
+
+public class DelegatingKeyListener implements KeyListener {
+
+ private final Component delegate;
+
+ public DelegatingKeyListener(Component delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public void keyTyped(final KeyEvent keyEvent) {
+ if (delegate.getKeyListeners() == null) {
+ return;
+ }
+
+ for (KeyListener listener : delegate.getKeyListeners()) {
+ listener.keyTyped(keyEvent);
+ }
+ }
+
+ @Override
+ public void keyPressed(KeyEvent keyEvent) {
+ if (delegate.getKeyListeners() == null) {
+ return;
+ }
+
+ for (KeyListener listener : delegate.getKeyListeners()) {
+ listener.keyPressed(keyEvent);
+ }
+ }
+
+ @Override
+ public void keyReleased(KeyEvent keyEvent) {
+ if (delegate.getKeyListeners() == null) {
+ return;
+ }
+
+ for (KeyListener listener : delegate.getKeyListeners()) {
+ listener.keyReleased(keyEvent);
+ }
+ }
+}
diff --git a/app/src/cc/arduino/packages/MonitorFactory.java b/app/src/cc/arduino/packages/MonitorFactory.java
new file mode 100644
index 00000000000..3be7723b586
--- /dev/null
+++ b/app/src/cc/arduino/packages/MonitorFactory.java
@@ -0,0 +1,52 @@
+/*
+ * This file is part of Arduino.
+ *
+ * Arduino is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * As a special exception, you may use this file as part of a free software
+ * library without restriction. Specifically, if other files instantiate
+ * templates or use macros or inline functions from this file, or you compile
+ * this file and link it with other files to produce an executable, this
+ * file does not by itself cause the resulting executable to be covered by
+ * the GNU General Public License. This exception does not however
+ * invalidate any other reasons why the executable file might be covered by
+ * the GNU General Public License.
+ *
+ * Copyright 2015 Arduino LLC (http://www.arduino.cc/)
+ */
+
+package cc.arduino.packages;
+
+import processing.app.AbstractMonitor;
+import processing.app.NetworkMonitor;
+import processing.app.SerialMonitor;
+
+public class MonitorFactory {
+
+ public AbstractMonitor newMonitor(BoardPort port) {
+ if ("network".equals(port.getProtocol())) {
+ if ("yes".equals(port.getPrefs().get("ssh_upload"))) {
+ // the board is SSH capable
+ return new NetworkMonitor(port);
+ } else {
+ // SSH not supported, no monitor support
+ return null;
+ }
+ }
+
+ return new SerialMonitor(port);
+ }
+
+}
diff --git a/app/src/cc/arduino/packages/formatter/AStyle.java b/app/src/cc/arduino/packages/formatter/AStyle.java
new file mode 100644
index 00000000000..70b6717ff66
--- /dev/null
+++ b/app/src/cc/arduino/packages/formatter/AStyle.java
@@ -0,0 +1,98 @@
+/*
+ * This file is part of Arduino.
+ *
+ * Arduino is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * As a special exception, you may use this file as part of a free software
+ * library without restriction. Specifically, if other files instantiate
+ * templates or use macros or inline functions from this file, or you compile
+ * this file and link it with other files to produce an executable, this
+ * file does not by itself cause the resulting executable to be covered by
+ * the GNU General Public License. This exception does not however
+ * invalidate any other reasons why the executable file might be covered by
+ * the GNU General Public License.
+ *
+ * Copyright 2015 Arduino LLC (http://www.arduino.cc/)
+ */
+
+package cc.arduino.packages.formatter;
+
+import processing.app.Base;
+import processing.app.BaseNoGui;
+import processing.app.Editor;
+import processing.app.helpers.FileUtils;
+import processing.app.tools.Tool;
+
+import java.io.File;
+import java.io.IOException;
+
+import static processing.app.I18n.tr;
+
+public class AStyle implements Tool {
+
+ private static final String FORMATTER_CONF = "formatter.conf";
+
+ private final AStyleInterface aStyleInterface;
+ private final String formatterConfiguration;
+ private Editor editor;
+
+ public AStyle() {
+ this.aStyleInterface = new AStyleInterface();
+ File customFormatterConf = BaseNoGui.getSettingsFile(FORMATTER_CONF);
+ File defaultFormatterConf = new File(Base.getContentFile("lib"), FORMATTER_CONF);
+
+ File formatterConf;
+ if (customFormatterConf.exists()) {
+ formatterConf = customFormatterConf;
+ } else {
+ formatterConf = defaultFormatterConf;
+ }
+ String formatterConfiguration = "";
+
+ try {
+ formatterConfiguration = FileUtils.readFileToString(formatterConf);
+ } catch (IOException e) {
+ // ignored
+ }
+ this.formatterConfiguration = formatterConfiguration;
+ }
+
+ @Override
+ public void init(Editor editor) {
+ this.editor = editor;
+ }
+
+ @Override
+ public void run() {
+ String originalText = editor.getCurrentTab().getText();
+ String formattedText = aStyleInterface.AStyleMain(originalText, formatterConfiguration);
+
+ if (formattedText.equals(originalText)) {
+ editor.statusNotice(tr("No changes necessary for Auto Format."));
+ return;
+ }
+
+ editor.getCurrentTab().setText(formattedText);
+
+ // mark as finished
+ editor.statusNotice(tr("Auto Format finished."));
+ }
+
+ @Override
+ public String getMenuTitle() {
+ return tr("Auto Format");
+ }
+
+}
diff --git a/app/src/cc/arduino/packages/formatter/AStyleInterface.java b/app/src/cc/arduino/packages/formatter/AStyleInterface.java
new file mode 100644
index 00000000000..4224bf164e7
--- /dev/null
+++ b/app/src/cc/arduino/packages/formatter/AStyleInterface.java
@@ -0,0 +1,90 @@
+/*
+ * This file is part of Arduino.
+ *
+ * Arduino is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * As a special exception, you may use this file as part of a free software
+ * library without restriction. Specifically, if other files instantiate
+ * templates or use macros or inline functions from this file, or you compile
+ * this file and link it with other files to produce an executable, this
+ * file does not by itself cause the resulting executable to be covered by
+ * the GNU General Public License. This exception does not however
+ * invalidate any other reasons why the executable file might be covered by
+ * the GNU General Public License.
+ *
+ * Copyright 2015 Arduino LLC (http://www.arduino.cc/)
+ */
+
+package cc.arduino.packages.formatter;
+
+import processing.app.Base;
+import processing.app.helpers.OSUtils;
+
+import java.io.File;
+
+public class AStyleInterface {
+
+ static {
+ if (OSUtils.isWindows()) {
+ loadLib(Base.getContentFile(System.mapLibraryName("msvcp100")));
+ loadLib(Base.getContentFile(System.mapLibraryName("msvcr100")));
+ }
+ loadLib(new File(Base.getContentFile("lib"), System.mapLibraryName("astylej")));
+ }
+
+ private static void loadLib(File lib) {
+ try {
+ System.load(lib.getAbsolutePath());
+ } catch (UnsatisfiedLinkError e) {
+ e.printStackTrace();
+ System.out.println(e.getMessage());
+ System.out.println("Cannot load native library " + lib.getAbsolutePath());
+ System.out.println("The program has terminated!");
+ System.exit(1);
+ }
+ }
+
+ /**
+ * Calls the AStyleMain function in Artistic Style.
+ *
+ * @param textIn A string containing the source code to be formatted.
+ * @param options A string of options to Artistic Style.
+ * @return A String containing the formatted source from Artistic Style.
+ */
+ public native String AStyleMain(String textIn, String options);
+
+ /**
+ * Calls the AStyleGetVersion function in Artistic Style.
+ *
+ * @return A String containing the version number of Artistic Style.
+ */
+ public native String AStyleGetVersion();
+
+ /**
+ * Error handler for messages from Artistic Style.
+ * This method is called only if there are errors when AStyleMain is called.
+ * This is for debugging and there should be no errors when the calling
+ * parameters are correct.
+ * Changing the method name requires changing Artistic Style.
+ * Signature: (ILjava/lang/String;)V.
+ *
+ * @param errorNumber The error number from Artistic Style.
+ * @param errorMessage The error message from Artistic Style.
+ */
+ private void ErrorHandler(int errorNumber, String errorMessage) {
+ System.out.println("AStyle error " + String.valueOf(errorNumber) + " - " + errorMessage);
+ }
+
+}
diff --git a/app/src/cc/arduino/view/Event.java b/app/src/cc/arduino/view/Event.java
new file mode 100644
index 00000000000..86bac75045f
--- /dev/null
+++ b/app/src/cc/arduino/view/Event.java
@@ -0,0 +1,54 @@
+/*
+ * This file is part of Arduino.
+ *
+ * Arduino is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * As a special exception, you may use this file as part of a free software
+ * library without restriction. Specifically, if other files instantiate
+ * templates or use macros or inline functions from this file, or you compile
+ * this file and link it with other files to produce an executable, this
+ * file does not by itself cause the resulting executable to be covered by
+ * the GNU General Public License. This exception does not however
+ * invalidate any other reasons why the executable file might be covered by
+ * the GNU General Public License.
+ *
+ * Copyright 2015 Arduino LLC (http://www.arduino.cc/)
+ */
+
+package cc.arduino.view;
+
+import java.awt.event.ActionEvent;
+import java.util.HashMap;
+import java.util.Map;
+
+public class Event extends ActionEvent {
+
+ private final Map payload;
+
+ public Event(Object source, int id, String command) {
+ super(source, id, command);
+ this.payload = new HashMap<>();
+ }
+
+ public Map getPayload() {
+ return payload;
+ }
+
+ @Override
+ public String toString() {
+ return super.toString() + "\n" + payload.toString();
+ }
+
+}
diff --git a/app/src/cc/arduino/view/EventListener.java b/app/src/cc/arduino/view/EventListener.java
new file mode 100644
index 00000000000..9f73a6ff7cc
--- /dev/null
+++ b/app/src/cc/arduino/view/EventListener.java
@@ -0,0 +1,36 @@
+/*
+ * This file is part of Arduino.
+ *
+ * Arduino is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * As a special exception, you may use this file as part of a free software
+ * library without restriction. Specifically, if other files instantiate
+ * templates or use macros or inline functions from this file, or you compile
+ * this file and link it with other files to produce an executable, this
+ * file does not by itself cause the resulting executable to be covered by
+ * the GNU General Public License. This exception does not however
+ * invalidate any other reasons why the executable file might be covered by
+ * the GNU General Public License.
+ *
+ * Copyright 2015 Arduino LLC (http://www.arduino.cc/)
+ */
+
+package cc.arduino.view;
+
+public interface EventListener {
+
+ void eventHappened(Event event);
+
+}
diff --git a/app/src/cc/arduino/view/GoToLineNumber.form b/app/src/cc/arduino/view/GoToLineNumber.form
new file mode 100644
index 00000000000..fd3089dd02e
--- /dev/null
+++ b/app/src/cc/arduino/view/GoToLineNumber.form
@@ -0,0 +1,114 @@
+
+
+
diff --git a/app/src/cc/arduino/view/GoToLineNumber.java b/app/src/cc/arduino/view/GoToLineNumber.java
new file mode 100644
index 00000000000..475b0bbe502
--- /dev/null
+++ b/app/src/cc/arduino/view/GoToLineNumber.java
@@ -0,0 +1,148 @@
+/*
+ * This file is part of Arduino.
+ *
+ * Arduino is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * As a special exception, you may use this file as part of a free software
+ * library without restriction. Specifically, if other files instantiate
+ * templates or use macros or inline functions from this file, or you compile
+ * this file and link it with other files to produce an executable, this
+ * file does not by itself cause the resulting executable to be covered by
+ * the GNU General Public License. This exception does not however
+ * invalidate any other reasons why the executable file might be covered by
+ * the GNU General Public License.
+ *
+ * Copyright 2015 Arduino LLC (http://www.arduino.cc/)
+ */
+
+package cc.arduino.view;
+
+import processing.app.Base;
+import processing.app.Editor;
+
+import java.awt.event.WindowEvent;
+
+import static processing.app.I18n.tr;
+
+public class GoToLineNumber extends javax.swing.JDialog {
+
+ private final Editor editor;
+
+ public GoToLineNumber(Editor editor) {
+ super(editor);
+ this.editor = editor;
+ initComponents();
+
+ Base.registerWindowCloseKeys(getRootPane(), this::cancelActionPerformed);
+ }
+
+ /**
+ * This method is called from within the constructor to initialize the form.
+ * WARNING: Do NOT modify this code. The content of this method is always
+ * regenerated by the Form Editor.
+ */
+ @SuppressWarnings("unchecked")
+ // //GEN-BEGIN:initComponents
+ private void initComponents() {
+
+ javax.swing.JLabel jLabel1 = new javax.swing.JLabel();
+ lineNumber = new javax.swing.JTextField();
+ javax.swing.JButton cancel = new javax.swing.JButton();
+ javax.swing.JButton ok = new javax.swing.JButton();
+
+ setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
+ setTitle(tr("Go to line"));
+ setModal(true);
+ setResizable(false);
+
+ jLabel1.setText(tr("Line number:"));
+
+ lineNumber.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ lineNumberActionPerformed(evt);
+ }
+ });
+
+ cancel.setText(tr("Cancel"));
+ cancel.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ cancelActionPerformed(evt);
+ }
+ });
+
+ ok.setText(tr("OK"));
+ ok.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ okActionPerformed(evt);
+ }
+ });
+
+ javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
+ getContentPane().setLayout(layout);
+ layout.setHorizontalGroup(
+ layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(layout.createSequentialGroup()
+ .addContainerGap()
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(layout.createSequentialGroup()
+ .addComponent(jLabel1)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(lineNumber, javax.swing.GroupLayout.DEFAULT_SIZE, 203, Short.MAX_VALUE))
+ .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
+ .addGap(0, 0, Short.MAX_VALUE)
+ .addComponent(ok)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(cancel)))
+ .addContainerGap())
+ );
+ layout.setVerticalGroup(
+ layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(layout.createSequentialGroup()
+ .addContainerGap()
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+ .addComponent(jLabel1)
+ .addComponent(lineNumber, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+ .addComponent(cancel)
+ .addComponent(ok))
+ .addContainerGap())
+ );
+
+ pack();
+ }// //GEN-END:initComponents
+
+ private void okActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_okActionPerformed
+ try {
+ int line = Integer.parseInt(lineNumber.getText());
+ editor.getCurrentTab().goToLine(line);
+ cancelActionPerformed(evt);
+ } catch (Exception e) {
+ // ignore
+ }
+ }//GEN-LAST:event_okActionPerformed
+
+ private void cancelActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cancelActionPerformed
+ dispatchEvent(new WindowEvent(this, WindowEvent.WINDOW_CLOSING));
+ }//GEN-LAST:event_cancelActionPerformed
+
+ private void lineNumberActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_lineNumberActionPerformed
+ okActionPerformed(evt);
+ }//GEN-LAST:event_lineNumberActionPerformed
+
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ private javax.swing.JTextField lineNumber;
+ // End of variables declaration//GEN-END:variables
+}
diff --git a/app/src/cc/arduino/view/JMenuUtils.java b/app/src/cc/arduino/view/JMenuUtils.java
new file mode 100644
index 00000000000..de64e41a14c
--- /dev/null
+++ b/app/src/cc/arduino/view/JMenuUtils.java
@@ -0,0 +1,46 @@
+/*
+ * This file is part of Arduino.
+ *
+ * Arduino is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * As a special exception, you may use this file as part of a free software
+ * library without restriction. Specifically, if other files instantiate
+ * templates or use macros or inline functions from this file, or you compile
+ * this file and link it with other files to produce an executable, this
+ * file does not by itself cause the resulting executable to be covered by
+ * the GNU General Public License. This exception does not however
+ * invalidate any other reasons why the executable file might be covered by
+ * the GNU General Public License.
+ *
+ * Copyright 2015 Arduino LLC (http://www.arduino.cc/)
+ */
+
+package cc.arduino.view;
+
+import javax.swing.*;
+import java.awt.*;
+
+public class JMenuUtils {
+
+ public static JMenu findSubMenuWithLabel(JMenu menu, String text) {
+ for (Component submenu : menu.getMenuComponents()) {
+ if (submenu instanceof JMenu && text.equals(((JMenu) submenu).getText())) {
+ return (JMenu) submenu;
+ }
+ }
+ return null;
+ }
+
+}
diff --git a/app/src/cc/arduino/view/NotificationPopup.java b/app/src/cc/arduino/view/NotificationPopup.java
new file mode 100644
index 00000000000..2de079c8525
--- /dev/null
+++ b/app/src/cc/arduino/view/NotificationPopup.java
@@ -0,0 +1,274 @@
+/*
+ * This file is part of Arduino.
+ *
+ * Arduino is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * As a special exception, you may use this file as part of a free software
+ * library without restriction. Specifically, if other files instantiate
+ * templates or use macros or inline functions from this file, or you compile
+ * this file and link it with other files to produce an executable, this
+ * file does not by itself cause the resulting executable to be covered by
+ * the GNU General Public License. This exception does not however
+ * invalidate any other reasons why the executable file might be covered by
+ * the GNU General Public License.
+ *
+ * Copyright 2015 Arduino LLC (http://www.arduino.cc/)
+ */
+
+package cc.arduino.view;
+
+import static processing.app.Theme.scale;
+
+import java.awt.Color;
+import java.awt.FlowLayout;
+import java.awt.Frame;
+import java.awt.Image;
+import java.awt.Point;
+import java.awt.event.*;
+import java.util.Timer;
+import java.util.TimerTask;
+
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JEditorPane;
+import javax.swing.JLabel;
+import javax.swing.WindowConstants;
+import javax.swing.border.LineBorder;
+import javax.swing.event.HyperlinkListener;
+
+import cc.arduino.Constants;
+import processing.app.PreferencesData;
+import processing.app.Theme;
+
+import java.awt.event.KeyEvent;
+
+import static processing.app.I18n.tr;
+
+public class NotificationPopup extends JDialog {
+ private Timer autoCloseTimer = new Timer(false);
+ private boolean autoClose = true;
+ private OptionalButtonCallbacks optionalButtonCallbacks;
+
+ public interface OptionalButtonCallbacks {
+ void onOptionalButton1Callback();
+ void onOptionalButton2Callback();
+ }
+
+ public NotificationPopup(Frame parent, HyperlinkListener hyperlinkListener,
+ String message) {
+ this(parent, hyperlinkListener, message, true, null, null, null);
+ }
+
+ public NotificationPopup(Frame parent, HyperlinkListener hyperlinkListener,
+ String message, boolean _autoClose) {
+ this(parent, hyperlinkListener, message, _autoClose, null, null, null);
+ }
+
+ public NotificationPopup(Frame parent, HyperlinkListener hyperlinkListener,
+ String message, boolean _autoClose, OptionalButtonCallbacks listener, String button1Name, String button2Name) {
+ super(parent, false);
+
+ if (!PreferencesData.getBoolean("ide.accessible")) {
+ // often auto-close is too fast for users of screen readers, so don't allow it.
+ autoClose = _autoClose;
+ }
+ else {
+ autoClose = false;
+ }
+ optionalButtonCallbacks = listener;
+
+ setLayout(new FlowLayout());
+ setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
+ setUndecorated(true);
+ setResizable(false);
+
+ Image arduino = Theme.getLibImage("arduino", this, scale(40), scale(40));
+ JLabel arduinoIcon = new JLabel(new ImageIcon(arduino));
+ add(arduinoIcon);
+
+ JEditorPane text = new JEditorPane();
+ text.setBorder(new LineBorder(new Color(0, 0, 0), 0, true));
+ text.setContentType("text/html"); // NOI18N
+ text.setOpaque(false);
+ text.setEditable(false);
+ text.setText(" " + message + " ");
+ text.addHyperlinkListener(hyperlinkListener);
+ add(text);
+
+ if (button1Name != null) {
+ JButton optionalButton1 = new JButton(tr(button1Name));
+ MouseAdapter button1Action = new MouseAdapter() {
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ if (optionalButtonCallbacks != null) {
+ optionalButtonCallbacks.onOptionalButton1Callback();
+ }
+ }
+ };
+ optionalButton1.addMouseListener(button1Action);
+
+ KeyListener button1Key = new KeyListener() {
+ // Ignore when the key is typed - only act once the key is released
+ public void keyTyped(KeyEvent e) {
+ // do nothing here, wait until the key is released
+ }
+
+ // Ignore when the key is pressed - only act once the key is released
+ public void keyPressed(KeyEvent e) {
+ // do nothing here, wait until the key is released
+ }
+
+ public void keyReleased(KeyEvent e) {
+ int key = e.getKeyCode();
+ if ((key == KeyEvent.VK_ENTER) || (key == KeyEvent.VK_SPACE)) {
+ optionalButtonCallbacks.onOptionalButton1Callback();
+ }
+ }
+ };
+ optionalButton1.addKeyListener(button1Key);
+ add(optionalButton1);
+ }
+
+ if (button2Name != null) {
+ JButton optionalButton2 = new JButton(tr(button2Name));
+ MouseAdapter button2Action = new MouseAdapter() {
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ if (optionalButtonCallbacks != null) {
+ optionalButtonCallbacks.onOptionalButton2Callback();
+ }
+ }
+ };
+ optionalButton2.addMouseListener(button2Action);
+
+ KeyListener button2Key = new KeyListener() {
+ // Ignore when the key is typed - only act once the key is released
+ public void keyTyped(KeyEvent e) {
+ // do nothing here, wait until the key is released
+ }
+
+ // Ignore when the key is pressed - only act once the key is released
+ public void keyPressed(KeyEvent e) {
+ // do nothing here, wait until the key is released
+ }
+
+ public void keyReleased(KeyEvent e) {
+ int key = e.getKeyCode();
+ if ((key == KeyEvent.VK_ENTER) || (key == KeyEvent.VK_SPACE)) {
+ optionalButtonCallbacks.onOptionalButton2Callback();
+ }
+ }
+ };
+ optionalButton2.addKeyListener(button2Key);
+ add(optionalButton2);
+ }
+
+ Image close = Theme.getThemeImage("close", this, scale(22), scale(22));
+ JButton closeButton = new JButton(new ImageIcon(close));
+ closeButton.setBorder(null);
+ closeButton.setBorderPainted(false);
+ closeButton.setHideActionText(true);
+ closeButton.setOpaque(false);
+ closeButton.setBackground(new Color(0, 0, 0, 0));
+ closeButton.getAccessibleContext().setAccessibleDescription(tr("Close"));
+ KeyListener closeKey = new KeyListener() {
+ // Ignore when the key is typed - only act once the key is released
+ public void keyTyped(KeyEvent e) {
+ // do nothing here, wait until the key is released
+ }
+
+ // Ignore when the key is pressed - only act once the key is released
+ public void keyPressed(KeyEvent e) {
+ // do nothing here, wait until the key is released
+ }
+
+ public void keyReleased(KeyEvent e) {
+ int key = e.getKeyCode();
+ if ((key == KeyEvent.VK_ENTER) || (key == KeyEvent.VK_SPACE)) {
+ close();
+ }
+ }
+ };
+ closeButton.addKeyListener(closeKey);
+ add(closeButton);
+
+ MouseAdapter closeOnClick = new MouseAdapter() {
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ close();
+ }
+ };
+ addMouseListener(closeOnClick);
+ text.addMouseListener(closeOnClick);
+ arduinoIcon.addMouseListener(closeOnClick);
+ closeButton.addMouseListener(closeOnClick);
+
+ pack();
+
+ updateLocation(parent);
+ ComponentAdapter parentMovedListener = new ComponentAdapter() {
+ @Override
+ public void componentMoved(ComponentEvent e) {
+ updateLocation(parent);
+ }
+
+ @Override
+ public void componentResized(ComponentEvent e) {
+ updateLocation(parent);
+ }
+ };
+ parent.addComponentListener(parentMovedListener);
+ addWindowListener(new WindowAdapter() {
+ @Override
+ public void windowClosed(WindowEvent e) {
+ parent.removeComponentListener(parentMovedListener);
+ }
+ });
+ }
+
+ private void updateLocation(Frame parent) {
+ Point parentLocation = parent.getLocation();
+
+ int parentX = Double.valueOf(parentLocation.getX()).intValue();
+ int parentY = Double.valueOf(parentLocation.getY()).intValue();
+ setLocation(parentX, parentY + parent.getHeight() - getHeight());
+ }
+
+ public void close() {
+ if (autoClose) {
+ autoCloseTimer.cancel();
+ }
+ setModal(false);
+ dispatchEvent(new WindowEvent(this, WindowEvent.WINDOW_CLOSING));
+ }
+
+ public void begin() {
+ if (autoClose) {
+ autoCloseTimer.schedule(new TimerTask() {
+ @Override
+ public void run() {
+ close();
+ }
+ }, Constants.NOTIFICATION_POPUP_AUTOCLOSE_DELAY);
+ }
+ setVisible(true);
+ if (PreferencesData.getBoolean("ide.accessible")) {
+ requestFocus();
+ setModal(true);
+ }
+ }
+}
diff --git a/app/src/cc/arduino/view/SplashScreenHelper.java b/app/src/cc/arduino/view/SplashScreenHelper.java
new file mode 100644
index 00000000000..108c1c8b2f6
--- /dev/null
+++ b/app/src/cc/arduino/view/SplashScreenHelper.java
@@ -0,0 +1,123 @@
+/*
+ * This file is part of Arduino.
+ *
+ * Code inspired by this tutorial http://wiki.netbeans.org/Splash_Screen_Beginner_Tutorial. License says "You may modify and use it as you wish."
+ *
+ * Copyright 2015 Arduino LLC (http://www.arduino.cc/)
+ *
+ * Arduino is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * As a special exception, you may use this file as part of a free software
+ * library without restriction. Specifically, if other files instantiate
+ * templates or use macros or inline functions from this file, or you compile
+ * this file and link it with other files to produce an executable, this
+ * file does not by itself cause the resulting executable to be covered by
+ * the GNU General Public License. This exception does not however
+ * invalidate any other reasons why the executable file might be covered by
+ * the GNU General Public License.
+ */
+
+package cc.arduino.view;
+
+import java.awt.*;
+import java.awt.geom.Rectangle2D;
+import java.util.Map;
+
+import processing.app.Theme;
+
+public class SplashScreenHelper {
+
+ private static final int X_OFFSET = 0;
+ private static final int Y_OFFSET = 300;
+ private static final int TEXTAREA_HEIGHT = 30;
+ private static final int TEXTAREA_WIDTH = 475;
+
+ private final Map desktopHints;
+ private final SplashScreen splash;
+ private Rectangle2D.Double splashTextArea;
+ private Graphics2D splashGraphics;
+
+ public SplashScreenHelper(SplashScreen splash) {
+ this.splash = splash;
+ if (splash != null) {
+ Toolkit tk = Toolkit.getDefaultToolkit();
+ desktopHints = (Map) tk.getDesktopProperty("awt.font.desktophints");
+ } else {
+ desktopHints = null;
+ }
+ }
+
+ public void splashText(String text) {
+ if (splash == null) {
+ printText(text);
+ return;
+ }
+
+ if (!splash.isVisible()) {
+ return;
+ }
+
+ if (splashTextArea == null) {
+ prepareTextAreaAndGraphics();
+ }
+
+ eraseLastStatusText();
+
+ drawText(text);
+
+ ensureTextIsDiplayed();
+ }
+
+ private void ensureTextIsDiplayed() {
+ synchronized (SplashScreen.class) {
+ if (splash.isVisible()) {
+ splash.update();
+ }
+ }
+ }
+
+ private void drawText(String str) {
+ splashGraphics.setPaint(Color.BLACK);
+ FontMetrics metrics = splashGraphics.getFontMetrics();
+ splashGraphics.drawString(str, (int) splashTextArea.getX() + 10, (int) splashTextArea.getY() + (TEXTAREA_HEIGHT - metrics.getHeight()) + 5);
+ }
+
+ private void eraseLastStatusText() {
+ splashGraphics.setPaint(new Color(229, 229, 229));
+ splashGraphics.fill(splashTextArea);
+ }
+
+ private void prepareTextAreaAndGraphics() {
+ splashTextArea = new Rectangle2D.Double(X_OFFSET, Y_OFFSET, TEXTAREA_WIDTH, TEXTAREA_HEIGHT);
+
+ splashGraphics = Theme.setupGraphics2D(splash.createGraphics());
+
+ if (desktopHints != null) {
+ splashGraphics.addRenderingHints(desktopHints);
+ }
+ }
+
+ public void close() {
+ if (splash == null) {
+ return;
+ }
+ splash.close();
+ }
+
+ private void printText(String str) {
+ System.err.println(str);
+ }
+
+}
diff --git a/app/src/cc/arduino/view/StubMenuListener.java b/app/src/cc/arduino/view/StubMenuListener.java
new file mode 100644
index 00000000000..dd9654b8295
--- /dev/null
+++ b/app/src/cc/arduino/view/StubMenuListener.java
@@ -0,0 +1,49 @@
+/*
+ * This file is part of Arduino.
+ *
+ * Copyright 2015 Arduino LLC (http://www.arduino.cc/)
+ *
+ * Arduino is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * As a special exception, you may use this file as part of a free software
+ * library without restriction. Specifically, if other files instantiate
+ * templates or use macros or inline functions from this file, or you compile
+ * this file and link it with other files to produce an executable, this
+ * file does not by itself cause the resulting executable to be covered by
+ * the GNU General Public License. This exception does not however
+ * invalidate any other reasons why the executable file might be covered by
+ * the GNU General Public License.
+ */
+
+package cc.arduino.view;
+
+import javax.swing.event.MenuEvent;
+import javax.swing.event.MenuListener;
+
+public class StubMenuListener implements MenuListener {
+
+ @Override
+ public void menuSelected(MenuEvent e) {
+ }
+
+ @Override
+ public void menuDeselected(MenuEvent e) {
+ }
+
+ @Override
+ public void menuCanceled(MenuEvent e) {
+ }
+
+}
diff --git a/app/src/cc/arduino/view/findreplace/FindReplace.form b/app/src/cc/arduino/view/findreplace/FindReplace.form
new file mode 100644
index 00000000000..1b4fcc5ad80
--- /dev/null
+++ b/app/src/cc/arduino/view/findreplace/FindReplace.form
@@ -0,0 +1,197 @@
+
+
+
diff --git a/app/src/cc/arduino/view/findreplace/FindReplace.java b/app/src/cc/arduino/view/findreplace/FindReplace.java
new file mode 100644
index 00000000000..03e7b10947d
--- /dev/null
+++ b/app/src/cc/arduino/view/findreplace/FindReplace.java
@@ -0,0 +1,472 @@
+/*
+ * This file is part of Arduino.
+ *
+ * Copyright 2015 Arduino LLC (http://www.arduino.cc/)
+ *
+ * Arduino is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * As a special exception, you may use this file as part of a free software
+ * library without restriction. Specifically, if other files instantiate
+ * templates or use macros or inline functions from this file, or you compile
+ * this file and link it with other files to produce an executable, this
+ * file does not by itself cause the resulting executable to be covered by
+ * the GNU General Public License. This exception does not however
+ * invalidate any other reasons why the executable file might be covered by
+ * the GNU General Public License.
+ */
+
+package cc.arduino.view.findreplace;
+
+import processing.app.Base;
+import processing.app.Editor;
+import processing.app.EditorTab;
+import processing.app.helpers.OSUtils;
+
+import java.awt.*;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import javax.swing.JPopupMenu;
+import javax.swing.Action;
+import javax.swing.text.DefaultEditorKit;
+import java.util.HashMap;
+import java.util.Map;
+
+import static processing.app.I18n.tr;
+
+public class FindReplace extends javax.swing.JFrame {
+
+ private static final String FIND_TEXT = "findText";
+ private static final String REPLACE_TEXT = "replaceText";
+ private static final String IGNORE_CASE = "ignoreCase";
+ private static final String SEARCH_ALL_FILES = "searchAllFiles";
+ private static final String WRAP_AROUND = "wrapAround";
+
+ private final Editor editor;
+
+ public FindReplace(Editor editor, Map state) {
+ this.editor = editor;
+
+ initComponents();
+
+ if (OSUtils.isMacOS()) {
+ buttonsContainer.removeAll();
+ buttonsContainer.add(replaceAllButton);
+ buttonsContainer.add(replaceButton);
+ buttonsContainer.add(replaceFindButton);
+ buttonsContainer.add(previousButton);
+ buttonsContainer.add(findButton);
+ }
+
+ Base.registerWindowCloseKeys(getRootPane(), e -> {
+ setVisible(false);
+ Base.FIND_DIALOG_STATE = findDialogState();
+ });
+
+ Base.setIcon(this);
+
+ addWindowListener(new WindowAdapter() {
+ public void windowActivated(WindowEvent e) {
+ findField.requestFocusInWindow();
+ findField.selectAll();
+ }
+ });
+
+ restoreFindDialogState(state);
+ }
+
+ @Override
+ public void setVisible(boolean b) {
+ getRootPane().setDefaultButton(findButton);
+
+ super.setVisible(b);
+ }
+
+ private Map findDialogState() {
+ Map state = new HashMap<>();
+ state.put(FIND_TEXT, findField.getText());
+ state.put(REPLACE_TEXT, replaceField.getText());
+ state.put(IGNORE_CASE, ignoreCaseBox.isSelected());
+ state.put(WRAP_AROUND, wrapAroundBox.isSelected());
+ state.put(SEARCH_ALL_FILES, searchAllFilesBox.isSelected());
+ return state;
+ }
+
+ private void restoreFindDialogState(Map state) {
+ if (state.containsKey(FIND_TEXT)) {
+ findField.setText((String) state.get(FIND_TEXT));
+ }
+ if (state.containsKey(REPLACE_TEXT)) {
+ replaceField.setText((String) state.get(REPLACE_TEXT));
+ }
+ if (state.containsKey(IGNORE_CASE)) {
+ ignoreCaseBox.setSelected((Boolean) state.get(IGNORE_CASE));
+ }
+ if (state.containsKey(SEARCH_ALL_FILES)) {
+ searchAllFilesBox.setSelected((Boolean) state.get(SEARCH_ALL_FILES));
+ }
+ if (state.containsKey(WRAP_AROUND)) {
+ wrapAroundBox.setSelected((Boolean) state.get(WRAP_AROUND));
+ }
+ }
+
+ /**
+ * This method is called from within the constructor to initialize the form.
+ * WARNING: Do NOT modify this code. The content of this method is always
+ * regenerated by the Form Editor.
+ */
+ @SuppressWarnings("unchecked")
+ // //GEN-BEGIN:initComponents
+ private void initComponents() {
+
+ javax.swing.JLabel findLabel = new javax.swing.JLabel();
+ findField = new javax.swing.JTextField();
+ javax.swing.JLabel replaceLabel = new javax.swing.JLabel();
+ replaceField = new javax.swing.JTextField();
+ ignoreCaseBox = new javax.swing.JCheckBox();
+ wrapAroundBox = new javax.swing.JCheckBox();
+ searchAllFilesBox = new javax.swing.JCheckBox();
+ buttonsContainer = new javax.swing.JPanel();
+ findButton = new javax.swing.JButton();
+ previousButton = new javax.swing.JButton();
+ replaceFindButton = new javax.swing.JButton();
+ replaceButton = new javax.swing.JButton();
+ replaceAllButton = new javax.swing.JButton();
+
+ setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
+ setTitle(tr("Find"));
+ setResizable(false);
+
+ findLabel.setText(tr("Find:"));
+
+ findField.setColumns(20);
+
+ replaceLabel.setText(tr("Replace with:"));
+
+ replaceField.setColumns(20);
+
+ ignoreCaseBox.setSelected(true);
+ ignoreCaseBox.setText(tr("Ignore Case"));
+
+ wrapAroundBox.setSelected(true);
+ wrapAroundBox.setText(tr("Wrap Around"));
+
+ searchAllFilesBox.setText(tr("Search all Sketch Tabs"));
+
+ JPopupMenu menu = new JPopupMenu();
+ Action cut = new DefaultEditorKit.CutAction();
+ cut.putValue(Action.NAME, tr("Cut"));
+ menu.add( cut );
+
+ Action copy = new DefaultEditorKit.CopyAction();
+ copy.putValue(Action.NAME, tr("Copy"));
+ menu.add( copy );
+
+ Action paste = new DefaultEditorKit.PasteAction();
+ paste.putValue(Action.NAME, tr("Paste"));
+ menu.add( paste );
+
+ findField.setComponentPopupMenu( menu );
+ replaceField.setComponentPopupMenu( menu );
+
+ findButton.setText(tr("Find"));
+ findButton.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ findButtonActionPerformed(evt);
+ }
+ });
+ buttonsContainer.add(findButton);
+
+ previousButton.setText(tr("Previous"));
+ previousButton.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ previousButtonActionPerformed(evt);
+ }
+ });
+ buttonsContainer.add(previousButton);
+
+ replaceFindButton.setText(tr("Replace & Find"));
+ replaceFindButton.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ replaceFindButtonActionPerformed(evt);
+ }
+ });
+ buttonsContainer.add(replaceFindButton);
+
+ replaceButton.setText(tr("Replace"));
+ replaceButton.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ replaceButtonActionPerformed(evt);
+ }
+ });
+ buttonsContainer.add(replaceButton);
+
+ replaceAllButton.setText(tr("Replace All"));
+ replaceAllButton.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ replaceAllButtonActionPerformed(evt);
+ }
+ });
+ buttonsContainer.add(replaceAllButton);
+
+ javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
+ getContentPane().setLayout(layout);
+ layout.setHorizontalGroup(
+ layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(layout.createSequentialGroup()
+ .addContainerGap()
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(layout.createSequentialGroup()
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
+ .addComponent(replaceLabel)
+ .addComponent(findLabel))
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(findField)
+ .addComponent(replaceField)
+ .addGroup(layout.createSequentialGroup()
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(searchAllFilesBox)
+ .addComponent(wrapAroundBox)
+ .addComponent(ignoreCaseBox))
+ .addGap(0, 0, Short.MAX_VALUE))))
+ .addComponent(buttonsContainer, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
+ .addContainerGap())
+ );
+ layout.setVerticalGroup(
+ layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(layout.createSequentialGroup()
+ .addContainerGap()
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+ .addComponent(findLabel)
+ .addComponent(findField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+ .addComponent(replaceLabel)
+ .addComponent(replaceField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(ignoreCaseBox)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(wrapAroundBox)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(searchAllFilesBox)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(buttonsContainer, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
+ );
+
+ pack();
+ }// //GEN-END:initComponents
+
+ private void findButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_findButtonActionPerformed
+ findNext();
+ }//GEN-LAST:event_findButtonActionPerformed
+
+ private void previousButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_previousButtonActionPerformed
+ findPrevious();
+ }//GEN-LAST:event_previousButtonActionPerformed
+
+ private void replaceFindButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_replaceFindButtonActionPerformed
+ replaceAndFindNext();
+ }//GEN-LAST:event_replaceFindButtonActionPerformed
+
+ private void replaceButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_replaceButtonActionPerformed
+ replace();
+ }//GEN-LAST:event_replaceButtonActionPerformed
+
+ private void replaceAllButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_replaceAllButtonActionPerformed
+ replaceAll();
+ }//GEN-LAST:event_replaceAllButtonActionPerformed
+
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ private javax.swing.JPanel buttonsContainer;
+ private javax.swing.JButton findButton;
+ private javax.swing.JTextField findField;
+ private javax.swing.JCheckBox ignoreCaseBox;
+ private javax.swing.JButton previousButton;
+ private javax.swing.JButton replaceAllButton;
+ private javax.swing.JButton replaceButton;
+ private javax.swing.JTextField replaceField;
+ private javax.swing.JButton replaceFindButton;
+ private javax.swing.JCheckBox searchAllFilesBox;
+ private javax.swing.JCheckBox wrapAroundBox;
+ // End of variables declaration//GEN-END:variables
+
+ private boolean find(boolean wrap, boolean backwards, boolean searchTabs, int originTab) {
+ String search = findField.getText();
+
+ if (search.length() == 0) {
+ return false;
+ }
+
+ String text = editor.getCurrentTab().getText();
+
+ if (ignoreCaseBox.isSelected()) {
+ search = search.toLowerCase();
+ text = text.toLowerCase();
+ }
+
+ int nextIndex;
+ if (!backwards) {
+ // int selectionStart = editor.textarea.getSelectionStart();
+ int selectionEnd = editor.getCurrentTab().getSelectionStop();
+
+ nextIndex = text.indexOf(search, selectionEnd);
+ } else {
+ // int selectionStart = editor.textarea.getSelectionStart();
+ int selectionStart = editor.getCurrentTab().getSelectionStart() - 1;
+
+ if (selectionStart >= 0) {
+ nextIndex = text.lastIndexOf(search, selectionStart);
+ } else {
+ nextIndex = -1;
+ }
+ }
+
+ if (nextIndex == -1) {
+ // Nothing found on this tab: Search other tabs if required
+ if (searchTabs) {
+ int numTabs = editor.getTabs().size();
+ if (numTabs > 1) {
+ int realCurrentTab = editor.getCurrentTabIndex();
+
+ if (originTab != realCurrentTab) {
+ if (originTab < 0) {
+ originTab = realCurrentTab;
+ }
+
+ if (!wrap) {
+ if ((!backwards && realCurrentTab + 1 >= numTabs)
+ || (backwards && realCurrentTab - 1 < 0)) {
+ return false; // Can't continue without wrap
+ }
+ }
+
+ if (backwards) {
+ editor.selectPrevTab();
+ this.setVisible(true);
+ int l = editor.getCurrentTab().getText().length() - 1;
+ editor.getCurrentTab().setSelection(l, l);
+ } else {
+ editor.selectNextTab();
+ this.setVisible(true);
+ editor.getCurrentTab().setSelection(0, 0);
+ }
+
+ return find(wrap, backwards, true, originTab);
+ }
+ }
+ }
+
+ if (wrap) {
+ nextIndex = backwards ? text.lastIndexOf(search) : text.indexOf(search, 0);
+ }
+ }
+
+ if (nextIndex != -1) {
+ EditorTab currentTab = editor.getCurrentTab();
+ currentTab.getTextArea().getFoldManager().ensureOffsetNotInClosedFold(nextIndex);
+ currentTab.setSelection(nextIndex, nextIndex + search.length());
+ currentTab.getTextArea().getCaret().setSelectionVisible(true);
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Replace the current selection with whatever's in the replacement text
+ * field.
+ */
+ private void replace() {
+ if (findField.getText().length() == 0) {
+ return;
+ }
+
+ int newpos = editor.getCurrentTab().getSelectionStart() - findField.getText().length();
+ if (newpos < 0) {
+ newpos = 0;
+ }
+ editor.getCurrentTab().setSelection(newpos, newpos);
+
+ boolean foundAtLeastOne = false;
+
+ if (find(false, false, searchAllFilesBox.isSelected(), -1)) {
+ foundAtLeastOne = true;
+ editor.getCurrentTab().setSelectedText(replaceField.getText());
+ }
+
+ if (!foundAtLeastOne) {
+ Toolkit.getDefaultToolkit().beep();
+ }
+
+ }
+
+ /**
+ * Replace the current selection with whatever's in the replacement text
+ * field, and then find the next match
+ */
+ private void replaceAndFindNext() {
+ replace();
+ findNext();
+ }
+
+ /**
+ * Replace everything that matches by doing find and replace alternately until
+ * nothing more found.
+ */
+ private void replaceAll() {
+ if (findField.getText().length() == 0) {
+ return;
+ }
+
+ if (searchAllFilesBox.isSelected()) {
+ editor.selectTab(0); // select the first tab
+ }
+
+ editor.getCurrentTab().setSelection(0, 0); // move to the beginning
+
+ boolean foundAtLeastOne = false;
+ while (true) {
+ if (find(false, false, searchAllFilesBox.isSelected(), -1)) {
+ foundAtLeastOne = true;
+ editor.getCurrentTab().setSelectedText(replaceField.getText());
+ } else {
+ break;
+ }
+ }
+ if (!foundAtLeastOne) {
+ Toolkit.getDefaultToolkit().beep();
+ }
+ }
+
+ public void findNext() {
+ if (!find(wrapAroundBox.isSelected(), false, searchAllFilesBox.isSelected(), -1)) {
+ Toolkit.getDefaultToolkit().beep();
+ }
+ }
+
+ public void findPrevious() {
+ if (!find(wrapAroundBox.isSelected(), true, searchAllFilesBox.isSelected(), -1)) {
+ Toolkit.getDefaultToolkit().beep();
+ }
+ }
+
+ public void setFindText(String text) {
+ if (text == null) {
+ return;
+ }
+ findField.setText(text);
+ }
+}
diff --git a/app/src/cc/arduino/view/preferences/AdditionalBoardsManagerURLTextArea.form b/app/src/cc/arduino/view/preferences/AdditionalBoardsManagerURLTextArea.form
new file mode 100644
index 00000000000..4ffbee63d9d
--- /dev/null
+++ b/app/src/cc/arduino/view/preferences/AdditionalBoardsManagerURLTextArea.form
@@ -0,0 +1,151 @@
+
+
+
diff --git a/app/src/cc/arduino/view/preferences/AdditionalBoardsManagerURLTextArea.java b/app/src/cc/arduino/view/preferences/AdditionalBoardsManagerURLTextArea.java
new file mode 100644
index 00000000000..d5bc9cc0e52
--- /dev/null
+++ b/app/src/cc/arduino/view/preferences/AdditionalBoardsManagerURLTextArea.java
@@ -0,0 +1,197 @@
+/*
+ * This file is part of Arduino.
+ *
+ * Copyright 2015 Arduino LLC (http://www.arduino.cc/)
+ *
+ * Arduino is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * As a special exception, you may use this file as part of a free software
+ * library without restriction. Specifically, if other files instantiate
+ * templates or use macros or inline functions from this file, or you compile
+ * this file and link it with other files to produce an executable, this
+ * file does not by itself cause the resulting executable to be covered by
+ * the GNU General Public License. This exception does not however
+ * invalidate any other reasons why the executable file might be covered by
+ * the GNU General Public License.
+ */
+
+package cc.arduino.view.preferences;
+
+import processing.app.Base;
+
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.WindowEvent;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.stream.Collectors;
+
+import static processing.app.I18n.tr;
+
+public class AdditionalBoardsManagerURLTextArea extends javax.swing.JDialog {
+
+ private ActionListener onOkListener;
+
+ public AdditionalBoardsManagerURLTextArea(Window parent) {
+ super(parent);
+ initComponents();
+ setLocationRelativeTo(parent);
+
+ Base.registerWindowCloseKeys(getRootPane(), this::cancelActionPerformed);
+ }
+
+ /**
+ * This method is called from within the constructor to initialize the form.
+ * WARNING: Do NOT modify this code. The content of this method is always
+ * regenerated by the Form Editor.
+ */
+ @SuppressWarnings("unchecked")
+ // //GEN-BEGIN:initComponents
+ private void initComponents() {
+
+ javax.swing.JScrollPane jScrollPane1 = new javax.swing.JScrollPane();
+ javax.swing.JButton cancel = new javax.swing.JButton();
+ javax.swing.JButton ok = new javax.swing.JButton();
+ javax.swing.JLabel jLabel1 = new javax.swing.JLabel();
+ unofficialListURLLabel = new javax.swing.JLabel();
+
+ setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
+ setTitle(tr("Additional Boards Manager URLs"));
+ setModal(true);
+ setModalExclusionType(java.awt.Dialog.ModalExclusionType.APPLICATION_EXCLUDE);
+
+ additionalBoardsManagerURLs.setColumns(20);
+ additionalBoardsManagerURLs.setRows(5);
+ additionalBoardsManagerURLs.setName(""); // NOI18N
+ jScrollPane1.setViewportView(additionalBoardsManagerURLs);
+
+ cancel.setText(tr("Cancel"));
+ cancel.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ cancelActionPerformed(evt);
+ }
+ });
+
+ ok.setText(tr("OK"));
+ ok.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ okActionPerformed(evt);
+ }
+ });
+
+ jLabel1.setText(tr("Enter additional URLs, one for each row"));
+
+ unofficialListURLLabel.setText(tr("Click for a list of unofficial boards support URLs"));
+ unofficialListURLLabel.setCursor(new java.awt.Cursor(java.awt.Cursor.HAND_CURSOR));
+ unofficialListURLLabel.addMouseListener(new java.awt.event.MouseAdapter() {
+ public void mouseClicked(java.awt.event.MouseEvent evt) {
+ unofficialListURLLabelMouseClicked(evt);
+ }
+
+ public void mouseExited(java.awt.event.MouseEvent evt) {
+ unofficialListURLLabelMouseExited(evt);
+ }
+
+ public void mouseEntered(java.awt.event.MouseEvent evt) {
+ unofficialListURLLabelMouseEntered(evt);
+ }
+ });
+
+ javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
+ getContentPane().setLayout(layout);
+ layout.setHorizontalGroup(
+ layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(layout.createSequentialGroup()
+ .addContainerGap()
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 538, Short.MAX_VALUE)
+ .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
+ .addGap(0, 0, Short.MAX_VALUE)
+ .addComponent(ok)
+ .addGap(7, 7, 7)
+ .addComponent(cancel))
+ .addGroup(layout.createSequentialGroup()
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(unofficialListURLLabel)
+ .addComponent(jLabel1))
+ .addGap(0, 0, Short.MAX_VALUE)))
+ .addContainerGap())
+ );
+ layout.setVerticalGroup(
+ layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(layout.createSequentialGroup()
+ .addContainerGap()
+ .addComponent(jLabel1)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(jScrollPane1)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(unofficialListURLLabel)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+ .addComponent(ok)
+ .addComponent(cancel))
+ .addContainerGap())
+ );
+
+ pack();
+ }// //GEN-END:initComponents
+
+ private void cancelActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cancelActionPerformed
+ dispatchEvent(new WindowEvent(this, WindowEvent.WINDOW_CLOSING));
+ }//GEN-LAST:event_cancelActionPerformed
+
+ private void okActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_okActionPerformed
+ ActionEvent actionEvent = new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "");
+ onOkListener.actionPerformed(actionEvent);
+ cancelActionPerformed(evt);
+ }//GEN-LAST:event_okActionPerformed
+
+ private void unofficialListURLLabelMouseEntered(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_unofficialListURLLabelMouseEntered
+ unofficialListURLLabel.setForeground(new Color(0, 0, 140));
+ }//GEN-LAST:event_unofficialListURLLabelMouseEntered
+
+ private void unofficialListURLLabelMouseExited(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_unofficialListURLLabelMouseExited
+ unofficialListURLLabel.setForeground(new Color(76, 76, 76));
+ }//GEN-LAST:event_unofficialListURLLabelMouseExited
+
+ private void unofficialListURLLabelMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_unofficialListURLLabelMouseClicked
+ Base.openURL("https://github.com/arduino/Arduino/wiki/Unofficial-list-of-3rd-party-boards-support-urls");
+ }//GEN-LAST:event_unofficialListURLLabelMouseClicked
+
+ public void setText(String text) {
+ Collection urls = splitAndTrim(text, ",");
+ additionalBoardsManagerURLs.setText(urls.stream().filter(s -> s != null).collect(Collectors.joining("\n")));
+ }
+
+ private Collection splitAndTrim(String text, String separator) {
+ Collection urls = Arrays.asList(text.split(separator));
+ return urls.stream().map(String::trim).filter(url -> !url.isEmpty()).collect(Collectors.toList());
+ }
+
+ public String getText() {
+ Collection urls = splitAndTrim(additionalBoardsManagerURLs.getText(), "\n");
+ return urls.stream().filter(s -> s != null).collect(Collectors.joining(","));
+ }
+
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ private final javax.swing.JTextArea additionalBoardsManagerURLs = new javax.swing.JTextArea();
+ private javax.swing.JLabel unofficialListURLLabel;
+ // End of variables declaration//GEN-END:variables
+
+ public void onOk(ActionListener listener) {
+ this.onOkListener = listener;
+ }
+}
diff --git a/app/src/cc/arduino/view/preferences/Preferences.form b/app/src/cc/arduino/view/preferences/Preferences.form
new file mode 100644
index 00000000000..797e9d57ded
--- /dev/null
+++ b/app/src/cc/arduino/view/preferences/Preferences.form
@@ -0,0 +1,873 @@
+
+
+
diff --git a/app/src/cc/arduino/view/preferences/Preferences.java b/app/src/cc/arduino/view/preferences/Preferences.java
new file mode 100644
index 00000000000..005d2f83e54
--- /dev/null
+++ b/app/src/cc/arduino/view/preferences/Preferences.java
@@ -0,0 +1,978 @@
+/*
+ * This file is part of Arduino.
+ *
+ * Copyright 2015 Arduino LLC (http://www.arduino.cc/)
+ *
+ * Arduino is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * As a special exception, you may use this file as part of a free software
+ * library without restriction. Specifically, if other files instantiate
+ * templates or use macros or inline functions from this file, or you compile
+ * this file and link it with other files to produce an executable, this
+ * file does not by itself cause the resulting executable to be covered by
+ * the GNU General Public License. This exception does not however
+ * invalidate any other reasons why the executable file might be covered by
+ * the GNU General Public License.
+ */
+
+package cc.arduino.view.preferences;
+
+import cc.arduino.Constants;
+import cc.arduino.i18n.Language;
+import cc.arduino.i18n.Languages;
+import processing.app.Base;
+import processing.app.BaseNoGui;
+import processing.app.Editor;
+import processing.app.I18n;
+import processing.app.PreferencesData;
+import processing.app.Theme;
+import processing.app.Theme.ZippedTheme;
+import processing.app.helpers.FileUtils;
+import processing.app.legacy.PApplet;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.ItemEvent;
+import java.awt.event.WindowEvent;
+import java.io.File;
+import java.util.Collection;
+import java.util.LinkedList;
+
+import static processing.app.I18n.tr;
+
+public class Preferences extends javax.swing.JDialog {
+
+ private final WarningItem[] warningItems;
+ private final Base base;
+
+ private static class WarningItem {
+ private final String value;
+ private final String translation;
+
+ public WarningItem(String value, String translation) {
+ this.value = value;
+ this.translation = translation;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ @Override
+ public String toString() {
+ return translation;
+ }
+ }
+
+ public Preferences(Window parent, Base base) {
+ super(parent);
+ this.base = base;
+
+ this.warningItems = new WarningItem[]{
+ new WarningItem("none", tr("None")),
+ new WarningItem("default", tr("Default")),
+ new WarningItem("more", tr("More")),
+ new WarningItem("all", tr("All"))
+ };
+
+ initComponents();
+
+ Base.registerWindowCloseKeys(getRootPane(), this::cancelButtonActionPerformed);
+
+ showPreferencesData();
+ }
+
+ /**
+ * This method is called from within the constructor to initialize the form.
+ * WARNING: Do NOT modify this code. The content of this method is always
+ * regenerated by the Form Editor.
+ */
+ @SuppressWarnings("unchecked")
+ // //GEN-BEGIN:initComponents
+ private void initComponents() {
+
+ proxyTypeButtonGroup = new javax.swing.ButtonGroup();
+ manualProxyTypeButtonGroup = new javax.swing.ButtonGroup();
+ javax.swing.JPanel jPanel2 = new javax.swing.JPanel();
+ javax.swing.JTabbedPane jTabbedPane1 = new javax.swing.JTabbedPane();
+ jPanel1 = new javax.swing.JPanel();
+ sketchbookLocationLabel = new javax.swing.JLabel();
+ sketchbookLocationField = new javax.swing.JTextField();
+ browseButton = new javax.swing.JButton();
+ comboLanguageLabel = new javax.swing.JLabel();
+ comboLanguage = new JComboBox(Languages.languages);
+ requiresRestartLabel = new javax.swing.JLabel();
+ fontSizeLabel = new javax.swing.JLabel();
+ fontSizeField = new javax.swing.JTextField();
+ showVerboseLabel = new javax.swing.JLabel();
+ verboseCompilationBox = new javax.swing.JCheckBox();
+ verboseUploadBox = new javax.swing.JCheckBox();
+ comboWarningsLabel = new javax.swing.JLabel();
+ comboWarnings = new JComboBox(warningItems);
+ additionalBoardsManagerLabel = new javax.swing.JLabel();
+ additionalBoardsManagerField = new javax.swing.JTextField();
+ extendedAdditionalUrlFieldWindow = new javax.swing.JButton();
+ morePreferencesLabel = new javax.swing.JLabel();
+ preferencesFileLabel = new javax.swing.JLabel();
+ arduinoNotRunningLabel = new javax.swing.JLabel();
+ checkboxesContainer = new javax.swing.JPanel();
+ displayLineNumbersBox = new javax.swing.JCheckBox();
+ enableCodeFoldingBox = new javax.swing.JCheckBox();
+ verifyUploadBox = new javax.swing.JCheckBox();
+ externalEditorBox = new javax.swing.JCheckBox();
+ checkUpdatesBox = new javax.swing.JCheckBox();
+ saveVerifyUploadBox = new javax.swing.JCheckBox();
+ accessibleIDEBox = new javax.swing.JCheckBox();
+ jLabel1 = new javax.swing.JLabel();
+ jLabel2 = new javax.swing.JLabel();
+ scaleSpinner = new javax.swing.JSpinner();
+ autoScaleCheckBox = new javax.swing.JCheckBox();
+ jLabel3 = new javax.swing.JLabel();
+ javax.swing.JPanel jPanel4 = new javax.swing.JPanel();
+ noProxy = new javax.swing.JRadioButton();
+ autoProxy = new javax.swing.JRadioButton();
+ manualProxy = new javax.swing.JRadioButton();
+ autoProxyUsePAC = new javax.swing.JCheckBox();
+ autoProxyPACURL = new javax.swing.JTextField();
+ manualProxyHTTP = new javax.swing.JRadioButton();
+ manualProxySOCKS = new javax.swing.JRadioButton();
+ manualProxyHostNameLabel = new javax.swing.JLabel();
+ manualProxyPortLabel = new javax.swing.JLabel();
+ manualProxyHostName = new javax.swing.JTextField();
+ manualProxyPort = new javax.swing.JTextField();
+ manualProxyUsernameLabel = new javax.swing.JLabel();
+ manualProxyUsername = new javax.swing.JTextField();
+ manualProxyPasswordLabel = new javax.swing.JLabel();
+ manualProxyPassword = new javax.swing.JPasswordField();
+ autoProxyUsernameLabel = new javax.swing.JLabel();
+ autoProxyUsername = new javax.swing.JTextField();
+ autoProxyPassword = new javax.swing.JPasswordField();
+ autoProxyPasswordLabel = new javax.swing.JLabel();
+ comboThemeLabel = new javax.swing.JLabel();
+ comboTheme = new JComboBox();
+ requiresRestartLabel2 = new javax.swing.JLabel();
+ javax.swing.JPanel jPanel3 = new javax.swing.JPanel();
+ javax.swing.JButton okButton = new javax.swing.JButton();
+ javax.swing.JButton cancelButton = new javax.swing.JButton();
+
+ setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
+ setTitle(tr("Preferences"));
+ setModal(true);
+ setResizable(false);
+
+ jPanel2.setLayout(new javax.swing.BoxLayout(jPanel2, javax.swing.BoxLayout.Y_AXIS));
+
+ jTabbedPane1.setFocusable(false);
+ jTabbedPane1.setRequestFocusEnabled(false);
+
+ sketchbookLocationLabel.setText(tr("Sketchbook location:"));
+ sketchbookLocationLabel.setLabelFor(sketchbookLocationField);
+
+ sketchbookLocationField.setColumns(40);
+
+ browseButton.setText(I18n.PROMPT_BROWSE);
+ browseButton.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ browseButtonActionPerformed(evt);
+ }
+ });
+
+ comboLanguageLabel.setText(tr("Editor language: "));
+
+ requiresRestartLabel.setText(tr(" (requires restart of Arduino)"));
+
+ comboLanguage.getAccessibleContext().setAccessibleName("Editor language (requires restart of Arduino)");
+
+ fontSizeLabel.setText(tr("Editor font size: "));
+ fontSizeLabel.setLabelFor(fontSizeField);
+
+ fontSizeField.setColumns(4);
+
+ showVerboseLabel.setText(tr("Show verbose output during: "));
+
+ verboseCompilationBox.setText(tr("compilation "));
+ verboseCompilationBox.getAccessibleContext().setAccessibleName("Show verbose output during compilation");
+
+ verboseUploadBox.setText(tr("upload"));
+ verboseUploadBox.getAccessibleContext().setAccessibleName("Show verbose output during upload");
+
+ comboWarningsLabel.setText(tr("Compiler warnings: "));
+ comboWarningsLabel.setLabelFor(comboWarnings);
+
+ additionalBoardsManagerLabel.setText(tr("Additional Boards Manager URLs: "));
+ additionalBoardsManagerLabel.setToolTipText(tr("Enter a comma separated list of urls"));
+ additionalBoardsManagerLabel.setLabelFor(additionalBoardsManagerField);
+
+ additionalBoardsManagerField.setToolTipText(tr("Enter a comma separated list of urls"));
+
+ extendedAdditionalUrlFieldWindow.setIcon(new ImageIcon(Theme.getThemeImage("newwindow", this, Theme.scale(16), Theme.scale(14))));
+ extendedAdditionalUrlFieldWindow.setMargin(new java.awt.Insets(1, 1, 1, 1));
+ extendedAdditionalUrlFieldWindow.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ extendedAdditionalUrlFieldWindowActionPerformed(evt);
+ }
+ });
+ extendedAdditionalUrlFieldWindow.getAccessibleContext().setAccessibleName("New Window");
+
+ morePreferencesLabel.setForeground(Color.GRAY);
+ morePreferencesLabel.setText(tr("More preferences can be edited directly in the file"));
+
+ preferencesFileLabel.setText(PreferencesData.getPreferencesFile().getAbsolutePath());
+ preferencesFileLabel.setCursor(new java.awt.Cursor(java.awt.Cursor.HAND_CURSOR));
+ preferencesFileLabel.addMouseListener(new java.awt.event.MouseAdapter() {
+ public void mousePressed(java.awt.event.MouseEvent evt) {
+ preferencesFileLabelMousePressed(evt);
+ }
+ public void mouseExited(java.awt.event.MouseEvent evt) {
+ preferencesFileLabelMouseExited(evt);
+ }
+ public void mouseEntered(java.awt.event.MouseEvent evt) {
+ preferencesFileLabelMouseEntered(evt);
+ }
+ });
+ preferencesFileLabel.setFocusable(true);
+
+ arduinoNotRunningLabel.setForeground(Color.GRAY);
+ arduinoNotRunningLabel.setText(tr("(edit only when Arduino is not running)"));
+
+ checkboxesContainer.setLayout(new GridLayout(0,2));
+
+ displayLineNumbersBox.setText(tr("Display line numbers"));
+ checkboxesContainer.add(displayLineNumbersBox);
+
+ enableCodeFoldingBox.setText(tr("Enable Code Folding"));
+ checkboxesContainer.add(enableCodeFoldingBox);
+
+ verifyUploadBox.setText(tr("Verify code after upload"));
+ checkboxesContainer.add(verifyUploadBox);
+
+ externalEditorBox.setText(tr("Use external editor"));
+ externalEditorBox.addItemListener(ev -> {
+ if (ev.getStateChange() == ItemEvent.SELECTED) {
+ for (Editor e : base.getEditors()) {
+ if (e.getSketch().isModified()) {
+ String msg = tr("You have unsaved changes!\nYou must save all your sketches to enable this option.");
+ JOptionPane.showMessageDialog(null, msg,
+ tr("Can't enable external editor"),
+ JOptionPane.INFORMATION_MESSAGE);
+ externalEditorBox.setSelected(false);
+ return;
+ }
+ }
+ }
+ });
+
+ checkboxesContainer.add(externalEditorBox);
+
+ checkUpdatesBox.setText(tr("Check for updates on startup"));
+ checkboxesContainer.add(checkUpdatesBox);
+
+ saveVerifyUploadBox.setText(tr("Save when verifying or uploading"));
+ checkboxesContainer.add(saveVerifyUploadBox);
+
+ accessibleIDEBox.setText(tr("Use accessibility features"));
+ checkboxesContainer.add(accessibleIDEBox);
+
+ jLabel1.setText(tr("Interface scale:"));
+
+ jLabel2.setText(tr(" (requires restart of Arduino)"));
+
+ scaleSpinner.setModel(new javax.swing.SpinnerNumberModel(100, 100, 400, 5));
+ scaleSpinner.setEnabled(false);
+ scaleSpinner.getAccessibleContext().setAccessibleName("Interface scale (requires restart of Arduino)");
+
+ autoScaleCheckBox.setSelected(true);
+ autoScaleCheckBox.setText(tr("Automatic"));
+ autoScaleCheckBox.addItemListener(new java.awt.event.ItemListener() {
+ public void itemStateChanged(java.awt.event.ItemEvent evt) {
+ autoScaleCheckBoxItemStateChanged(evt);
+ }
+ });
+ autoScaleCheckBox.getAccessibleContext().setAccessibleName("Automatic interface scale (requires restart of Arduino");
+
+ jLabel3.setText("%");
+
+ comboThemeLabel.setText(tr("Theme: "));
+
+ comboTheme.getAccessibleContext().setAccessibleName("Theme (requires restart of Arduino)");
+
+ requiresRestartLabel2.setText(tr(" (requires restart of Arduino)"));
+
+ javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1);
+ jPanel1.setLayout(jPanel1Layout);
+ jPanel1Layout.setHorizontalGroup(
+ jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(jPanel1Layout.createSequentialGroup()
+ .addContainerGap()
+ .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(jPanel1Layout.createSequentialGroup()
+ .addComponent(sketchbookLocationField, javax.swing.GroupLayout.DEFAULT_SIZE, 400, Short.MAX_VALUE)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(browseButton))
+ .addComponent(checkboxesContainer, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+ .addGroup(jPanel1Layout.createSequentialGroup()
+ .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(jPanel1Layout.createSequentialGroup()
+ .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(jLabel1)
+ .addComponent(comboWarningsLabel))
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(comboWarnings, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addGroup(jPanel1Layout.createSequentialGroup()
+ .addComponent(autoScaleCheckBox)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(scaleSpinner, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addGap(0, 0, 0)
+ .addComponent(jLabel3)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(jLabel2))))
+ .addGroup(jPanel1Layout.createSequentialGroup()
+ .addComponent(showVerboseLabel)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(verboseCompilationBox)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(verboseUploadBox))
+ .addGroup(jPanel1Layout.createSequentialGroup()
+ .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(comboLanguageLabel)
+ .addComponent(fontSizeLabel)
+ .addComponent(comboThemeLabel))
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(jPanel1Layout.createSequentialGroup()
+ .addComponent(comboTheme, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(requiresRestartLabel2))
+ .addComponent(fontSizeField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addGroup(jPanel1Layout.createSequentialGroup()
+ .addComponent(comboLanguage, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(requiresRestartLabel))))
+ .addComponent(arduinoNotRunningLabel)
+ .addComponent(morePreferencesLabel)
+ .addComponent(preferencesFileLabel)
+ .addComponent(sketchbookLocationLabel)
+ .addGroup(jPanel1Layout.createSequentialGroup()
+ .addComponent(additionalBoardsManagerLabel)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(additionalBoardsManagerField, javax.swing.GroupLayout.PREFERRED_SIZE, 500, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(extendedAdditionalUrlFieldWindow)))
+ .addGap(0, 0, Short.MAX_VALUE)))
+ .addContainerGap())
+ );
+
+ jPanel1Layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {comboLanguageLabel, comboWarningsLabel, fontSizeLabel, jLabel1, showVerboseLabel, comboThemeLabel});
+
+ jPanel1Layout.setVerticalGroup(
+ jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel1Layout.createSequentialGroup()
+ .addContainerGap()
+ .addComponent(sketchbookLocationLabel)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+ .addComponent(sketchbookLocationField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addComponent(browseButton))
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+ .addComponent(comboLanguageLabel)
+ .addComponent(comboLanguage, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addComponent(requiresRestartLabel))
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+ .addComponent(fontSizeLabel)
+ .addComponent(fontSizeField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+ .addComponent(jLabel1)
+ .addComponent(jLabel2)
+ .addComponent(scaleSpinner, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addComponent(autoScaleCheckBox)
+ .addComponent(jLabel3))
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+ .addComponent(comboThemeLabel)
+ .addComponent(comboTheme, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addComponent(requiresRestartLabel2))
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+ .addComponent(showVerboseLabel)
+ .addComponent(verboseCompilationBox)
+ .addComponent(verboseUploadBox))
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+ .addComponent(comboWarningsLabel)
+ .addComponent(comboWarnings, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(checkboxesContainer, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+ .addComponent(additionalBoardsManagerLabel)
+ .addComponent(additionalBoardsManagerField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
+ .addComponent(extendedAdditionalUrlFieldWindow))
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(morePreferencesLabel)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(preferencesFileLabel)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(arduinoNotRunningLabel)
+ .addContainerGap())
+ );
+
+ jTabbedPane1.addTab(tr("Settings"), jPanel1);
+
+ proxyTypeButtonGroup.add(noProxy);
+ noProxy.setText(tr("No proxy"));
+ noProxy.setActionCommand(Constants.PROXY_TYPE_NONE);
+
+ proxyTypeButtonGroup.add(autoProxy);
+ autoProxy.setText(tr("Auto-detect proxy settings"));
+ autoProxy.setActionCommand(Constants.PROXY_TYPE_AUTO);
+ autoProxy.addItemListener(new java.awt.event.ItemListener() {
+ public void itemStateChanged(java.awt.event.ItemEvent evt) {
+ autoProxyItemStateChanged(evt);
+ }
+ });
+
+ proxyTypeButtonGroup.add(manualProxy);
+ manualProxy.setText(tr("Manual proxy configuration"));
+ manualProxy.setActionCommand(Constants.PROXY_TYPE_MANUAL);
+ manualProxy.addItemListener(new java.awt.event.ItemListener() {
+ public void itemStateChanged(java.awt.event.ItemEvent evt) {
+ manualProxyItemStateChanged(evt);
+ }
+ });
+
+ autoProxyUsePAC.setText(tr("Automatic proxy configuration URL:"));
+ autoProxyUsePAC.addItemListener(new java.awt.event.ItemListener() {
+ public void itemStateChanged(java.awt.event.ItemEvent evt) {
+ autoProxyUsePACItemStateChanged(evt);
+ }
+ });
+
+ manualProxyTypeButtonGroup.add(manualProxyHTTP);
+ manualProxyHTTP.setText("HTTP");
+ manualProxyHTTP.setActionCommand(Constants.PROXY_MANUAL_TYPE_HTTP);
+
+ manualProxyTypeButtonGroup.add(manualProxySOCKS);
+ manualProxySOCKS.setText("SOCKS");
+ manualProxySOCKS.setActionCommand(Constants.PROXY_MANUAL_TYPE_SOCKS);
+
+ manualProxyHostNameLabel.setText(tr("Host name:"));
+
+ manualProxyPortLabel.setText(tr("Port number:"));
+
+ manualProxyUsernameLabel.setText(tr("Username:"));
+
+ manualProxyPasswordLabel.setText(tr("Password:"));
+
+ manualProxyPassword.setToolTipText("");
+
+ autoProxyUsernameLabel.setText(tr("Username:"));
+
+ autoProxyPassword.setToolTipText("");
+
+ autoProxyPasswordLabel.setText(tr("Password:"));
+
+ javax.swing.GroupLayout jPanel4Layout = new javax.swing.GroupLayout(jPanel4);
+ jPanel4.setLayout(jPanel4Layout);
+ jPanel4Layout.setHorizontalGroup(
+ jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(jPanel4Layout.createSequentialGroup()
+ .addContainerGap()
+ .addGroup(jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(jPanel4Layout.createSequentialGroup()
+ .addGap(12, 12, 12)
+ .addGroup(jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(autoProxyUsePAC)
+ .addGroup(jPanel4Layout.createSequentialGroup()
+ .addGap(12, 12, 12)
+ .addGroup(jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(autoProxyUsernameLabel)
+ .addComponent(autoProxyPasswordLabel))))
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addGroup(jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(autoProxyPACURL)
+ .addGroup(jPanel4Layout.createSequentialGroup()
+ .addGroup(jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(autoProxyUsername, javax.swing.GroupLayout.PREFERRED_SIZE, 178, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addComponent(autoProxyPassword, javax.swing.GroupLayout.PREFERRED_SIZE, 180, javax.swing.GroupLayout.PREFERRED_SIZE))
+ .addGap(0, 0, Short.MAX_VALUE))))
+ .addGroup(jPanel4Layout.createSequentialGroup()
+ .addGroup(jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(noProxy)
+ .addComponent(autoProxy)
+ .addComponent(manualProxy)
+ .addGroup(jPanel4Layout.createSequentialGroup()
+ .addGap(12, 12, 12)
+ .addGroup(jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(jPanel4Layout.createSequentialGroup()
+ .addComponent(manualProxyHTTP)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(manualProxySOCKS))
+ .addGroup(jPanel4Layout.createSequentialGroup()
+ .addGroup(jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(manualProxyHostNameLabel)
+ .addComponent(manualProxyPortLabel)
+ .addComponent(manualProxyUsernameLabel)
+ .addComponent(manualProxyPasswordLabel))
+ .addGap(18, 18, 18)
+ .addGroup(jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(manualProxyHostName, javax.swing.GroupLayout.PREFERRED_SIZE, 541, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addComponent(manualProxyPort, javax.swing.GroupLayout.PREFERRED_SIZE, 74, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addGroup(jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false)
+ .addComponent(manualProxyPassword, javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(manualProxyUsername, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.PREFERRED_SIZE, 180, javax.swing.GroupLayout.PREFERRED_SIZE)))))))
+ .addGap(0, 0, Short.MAX_VALUE)))
+ .addContainerGap())
+ );
+ jPanel4Layout.setVerticalGroup(
+ jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(jPanel4Layout.createSequentialGroup()
+ .addContainerGap()
+ .addComponent(noProxy)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(autoProxy)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addGroup(jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+ .addComponent(autoProxyUsePAC)
+ .addComponent(autoProxyPACURL, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addGroup(jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+ .addComponent(autoProxyUsername, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addComponent(autoProxyUsernameLabel))
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addGroup(jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+ .addComponent(autoProxyPasswordLabel)
+ .addComponent(autoProxyPassword, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(manualProxy)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addGroup(jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+ .addComponent(manualProxyHTTP)
+ .addComponent(manualProxySOCKS))
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addGroup(jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+ .addComponent(manualProxyHostNameLabel)
+ .addComponent(manualProxyHostName, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addGroup(jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+ .addComponent(manualProxyPort, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addComponent(manualProxyPortLabel))
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addGroup(jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+ .addComponent(manualProxyUsername, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addComponent(manualProxyUsernameLabel))
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addGroup(jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+ .addComponent(manualProxyPassword, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addComponent(manualProxyPasswordLabel))
+ .addContainerGap(50, Short.MAX_VALUE))
+ );
+
+ jTabbedPane1.addTab(tr("Network"), jPanel4);
+
+ jPanel2.add(jTabbedPane1);
+
+ okButton.setText(I18n.PROMPT_OK);
+ okButton.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ okButtonActionPerformed(evt);
+ }
+ });
+
+ cancelButton.setText(I18n.PROMPT_CANCEL);
+ cancelButton.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ cancelButtonActionPerformed(evt);
+ }
+ });
+
+ javax.swing.GroupLayout jPanel3Layout = new javax.swing.GroupLayout(jPanel3);
+ jPanel3.setLayout(jPanel3Layout);
+ jPanel3Layout.setHorizontalGroup(
+ jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel3Layout.createSequentialGroup()
+ .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
+ .addComponent(okButton)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(cancelButton)
+ .addContainerGap())
+ );
+ jPanel3Layout.setVerticalGroup(
+ jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel3Layout.createSequentialGroup()
+ .addGroup(jPanel3Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+ .addComponent(okButton)
+ .addComponent(cancelButton)))
+ );
+
+ jPanel2.add(jPanel3);
+
+ javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
+ getContentPane().setLayout(layout);
+ layout.setHorizontalGroup(
+ layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGap(0, 800, Short.MAX_VALUE)
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(jPanel2, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
+ );
+ layout.setVerticalGroup(
+ layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGap(0, 400, Short.MAX_VALUE)
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(jPanel2, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
+ );
+
+ pack();
+ }// //GEN-END:initComponents
+
+ private void cancelButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cancelButtonActionPerformed
+ dispatchEvent(new WindowEvent(this, WindowEvent.WINDOW_CLOSING));
+ }//GEN-LAST:event_cancelButtonActionPerformed
+
+ private void okButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_okButtonActionPerformed
+ java.util.List errors = validateData();
+ if (!errors.isEmpty()) {
+ Base.showWarning(tr("Error"), errors.get(0), null);
+ return;
+ }
+
+ savePreferencesData();
+ base.getEditors().forEach(processing.app.Editor::applyPreferences);
+ cancelButtonActionPerformed(evt);
+ }//GEN-LAST:event_okButtonActionPerformed
+
+ private void autoProxyItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_autoProxyItemStateChanged
+ disableAllProxyFields();
+ autoProxyFieldsSetEnabled(autoProxy.isSelected());
+ }//GEN-LAST:event_autoProxyItemStateChanged
+
+ private void manualProxyItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_manualProxyItemStateChanged
+ disableAllProxyFields();
+ manualProxyFieldsSetEnabled(manualProxy.isSelected());
+ }//GEN-LAST:event_manualProxyItemStateChanged
+
+ private void autoProxyUsePACItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_autoProxyUsePACItemStateChanged
+ autoProxyPACFieldsSetEnabled(autoProxyUsePAC.isSelected());
+ }//GEN-LAST:event_autoProxyUsePACItemStateChanged
+
+ private void preferencesFileLabelMouseEntered(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_preferencesFileLabelMouseEntered
+ preferencesFileLabel.setForeground(new Color(0, 0, 140));
+ }//GEN-LAST:event_preferencesFileLabelMouseEntered
+
+ private void preferencesFileLabelMouseExited(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_preferencesFileLabelMouseExited
+ preferencesFileLabel.setForeground(new Color(76, 76, 76));
+ }//GEN-LAST:event_preferencesFileLabelMouseExited
+
+ private void preferencesFileLabelMousePressed(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_preferencesFileLabelMousePressed
+ Base.openFolder(PreferencesData.getPreferencesFile().getParentFile());
+ }//GEN-LAST:event_preferencesFileLabelMousePressed
+
+ private void extendedAdditionalUrlFieldWindowActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_extendedAdditionalUrlFieldWindowActionPerformed
+ final AdditionalBoardsManagerURLTextArea additionalBoardsManagerURLTextArea = new AdditionalBoardsManagerURLTextArea(this);
+ additionalBoardsManagerURLTextArea.setText(additionalBoardsManagerField.getText());
+ additionalBoardsManagerURLTextArea.onOk(e -> additionalBoardsManagerField.setText(additionalBoardsManagerURLTextArea.getText()));
+ additionalBoardsManagerURLTextArea.setVisible(true);
+ }//GEN-LAST:event_extendedAdditionalUrlFieldWindowActionPerformed
+
+ private void browseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_browseButtonActionPerformed
+ File dflt = new File(sketchbookLocationField.getText());
+ File file = Base.selectFolder(tr("Select new sketchbook location"), dflt, this);
+ if (file != null) {
+ String path = file.getAbsolutePath();
+ sketchbookLocationField.setText(path);
+ }
+ }//GEN-LAST:event_browseButtonActionPerformed
+
+ private void autoScaleCheckBoxItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_autoScaleCheckBoxItemStateChanged
+ scaleSpinner.setEnabled(!autoScaleCheckBox.isSelected());
+ }//GEN-LAST:event_autoScaleCheckBoxItemStateChanged
+
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ private javax.swing.JTextField additionalBoardsManagerField;
+ private javax.swing.JLabel additionalBoardsManagerLabel;
+ private javax.swing.JLabel arduinoNotRunningLabel;
+ private javax.swing.JRadioButton autoProxy;
+ private javax.swing.JTextField autoProxyPACURL;
+ private javax.swing.JPasswordField autoProxyPassword;
+ private javax.swing.JLabel autoProxyPasswordLabel;
+ private javax.swing.JCheckBox autoProxyUsePAC;
+ private javax.swing.JTextField autoProxyUsername;
+ private javax.swing.JLabel autoProxyUsernameLabel;
+ private javax.swing.JCheckBox autoScaleCheckBox;
+ private javax.swing.JButton browseButton;
+ private javax.swing.JCheckBox checkUpdatesBox;
+ private javax.swing.JCheckBox accessibleIDEBox;
+ private javax.swing.JPanel checkboxesContainer;
+ private javax.swing.JComboBox comboLanguage;
+ private javax.swing.JLabel comboLanguageLabel;
+ private javax.swing.JComboBox comboWarnings;
+ private javax.swing.JLabel comboWarningsLabel;
+ private javax.swing.JCheckBox displayLineNumbersBox;
+ private javax.swing.JCheckBox enableCodeFoldingBox;
+ private javax.swing.JButton extendedAdditionalUrlFieldWindow;
+ private javax.swing.JCheckBox externalEditorBox;
+ private javax.swing.JTextField fontSizeField;
+ private javax.swing.JLabel fontSizeLabel;
+ private javax.swing.JLabel jLabel1;
+ private javax.swing.JLabel jLabel2;
+ private javax.swing.JLabel jLabel3;
+ private javax.swing.JPanel jPanel1;
+ private javax.swing.JRadioButton manualProxy;
+ private javax.swing.JRadioButton manualProxyHTTP;
+ private javax.swing.JTextField manualProxyHostName;
+ private javax.swing.JLabel manualProxyHostNameLabel;
+ private javax.swing.JPasswordField manualProxyPassword;
+ private javax.swing.JLabel manualProxyPasswordLabel;
+ private javax.swing.JTextField manualProxyPort;
+ private javax.swing.JLabel manualProxyPortLabel;
+ private javax.swing.JRadioButton manualProxySOCKS;
+ private javax.swing.ButtonGroup manualProxyTypeButtonGroup;
+ private javax.swing.JTextField manualProxyUsername;
+ private javax.swing.JLabel manualProxyUsernameLabel;
+ private javax.swing.JLabel morePreferencesLabel;
+ private javax.swing.JRadioButton noProxy;
+ private javax.swing.JLabel preferencesFileLabel;
+ private javax.swing.ButtonGroup proxyTypeButtonGroup;
+ private javax.swing.JLabel requiresRestartLabel;
+ private javax.swing.JCheckBox saveVerifyUploadBox;
+ private javax.swing.JSpinner scaleSpinner;
+ private javax.swing.JLabel showVerboseLabel;
+ private javax.swing.JTextField sketchbookLocationField;
+ private javax.swing.JLabel sketchbookLocationLabel;
+ private javax.swing.JCheckBox verboseCompilationBox;
+ private javax.swing.JCheckBox verboseUploadBox;
+ private javax.swing.JCheckBox verifyUploadBox;
+ private javax.swing.JComboBox comboTheme;
+ private javax.swing.JLabel comboThemeLabel;
+ private javax.swing.JLabel requiresRestartLabel2;
+ // End of variables declaration//GEN-END:variables
+
+ private java.util.List validateData() {
+ java.util.List errors = new LinkedList<>();
+ if (FileUtils.isSubDirectory(new File(sketchbookLocationField.getText()), new File(PreferencesData.get("runtime.ide.path")))) {
+ errors.add(tr("The specified sketchbook folder contains your copy of the IDE.\nPlease choose a different folder for your sketchbook."));
+ }
+ return errors;
+ }
+
+ private void savePreferencesData() {
+ String oldPath = PreferencesData.get("sketchbook.path");
+ String newPath = sketchbookLocationField.getText();
+ if (newPath.isEmpty()) {
+ if (BaseNoGui.getPortableFolder() == null) {
+ newPath = base.getDefaultSketchbookFolderOrPromptForIt().toString();
+ } else {
+ newPath = BaseNoGui.getPortableSketchbookFolder();
+ }
+ }
+ if (!newPath.equals(oldPath)) {
+ base.rebuildSketchbookMenus();
+ PreferencesData.set("sketchbook.path", newPath);
+ }
+
+ Language newLanguage = (Language) comboLanguage.getSelectedItem();
+ PreferencesData.set("editor.languages.current", newLanguage.getIsoCode());
+
+ if (comboTheme.getSelectedIndex() == 0) {
+ PreferencesData.set("theme.file", "");
+ } else {
+ PreferencesData.set("theme.file", ((ZippedTheme) comboTheme.getSelectedItem()).getKey());
+ }
+
+ String newSizeText = fontSizeField.getText();
+ try {
+ int newSize = Integer.parseInt(newSizeText.trim());
+ String pieces[] = PApplet.split(PreferencesData.get("editor.font"), ',');
+ pieces[2] = String.valueOf(newSize);
+ PreferencesData.set("editor.font", PApplet.join(pieces, ','));
+
+ } catch (Exception e) {
+ System.err.println(I18n.format(tr("ignoring invalid font size {0}"), newSizeText));
+ }
+
+ if (autoScaleCheckBox.isSelected()) {
+ PreferencesData.set("gui.scale", "auto");
+ } else {
+ PreferencesData.set("gui.scale", scaleSpinner.getValue().toString());
+ }
+
+ // put each of the settings into the table
+ PreferencesData.setBoolean("build.verbose", verboseCompilationBox.isSelected());
+ PreferencesData.setBoolean("upload.verbose", verboseUploadBox.isSelected());
+
+ WarningItem warningItem = (WarningItem) comboWarnings.getSelectedItem();
+ PreferencesData.set("compiler.warning_level", warningItem.getValue());
+
+ PreferencesData.setBoolean("editor.linenumbers", displayLineNumbersBox.isSelected());
+
+ PreferencesData.setBoolean("editor.code_folding", enableCodeFoldingBox.isSelected());
+
+ PreferencesData.setBoolean("upload.verify", verifyUploadBox.isSelected());
+
+ PreferencesData.setBoolean("editor.save_on_verify", saveVerifyUploadBox.isSelected());
+
+ PreferencesData.setBoolean("editor.external", externalEditorBox.isSelected());
+
+ PreferencesData.setBoolean("update.check", checkUpdatesBox.isSelected());
+
+ PreferencesData.setBoolean("ide.accessible", accessibleIDEBox.isSelected());
+
+ PreferencesData.set("boardsmanager.additional.urls", additionalBoardsManagerField.getText().replace("\r\n", "\n").replace("\r", "\n").replace("\n", ","));
+
+ PreferencesData.set(Constants.PREF_PROXY_TYPE, proxyTypeButtonGroup.getSelection().getActionCommand());
+ PreferencesData.set(Constants.PREF_PROXY_PAC_URL, autoProxyUsePAC.isSelected() ? autoProxyPACURL.getText() : "");
+ PreferencesData.set(Constants.PREF_PROXY_MANUAL_TYPE, manualProxyTypeButtonGroup.getSelection().getActionCommand());
+ PreferencesData.set(Constants.PREF_PROXY_MANUAL_HOSTNAME, manualProxyHostName.getText());
+ PreferencesData.set(Constants.PREF_PROXY_MANUAL_PORT, manualProxyPort.getText());
+ if (PreferencesData.get(Constants.PREF_PROXY_TYPE).equals(Constants.PROXY_TYPE_MANUAL)) {
+ PreferencesData.set(Constants.PREF_PROXY_USERNAME, manualProxyUsername.getText());
+ PreferencesData.set(Constants.PREF_PROXY_PASSWORD, String.valueOf(manualProxyPassword.getPassword()));
+ }
+ if (PreferencesData.get(Constants.PREF_PROXY_TYPE).equals(Constants.PROXY_TYPE_AUTO)) {
+ PreferencesData.set(Constants.PREF_PROXY_USERNAME, autoProxyUsername.getText());
+ PreferencesData.set(Constants.PREF_PROXY_PASSWORD, String.valueOf(autoProxyPassword.getPassword()));
+ }
+ }
+
+ private void showPreferencesData() {
+ sketchbookLocationField.setText(PreferencesData.get("sketchbook.path"));
+
+ String currentLanguageISOCode = PreferencesData.get("editor.languages.current");
+ for (Language language : Languages.languages) {
+ if (language.getIsoCode().equals(currentLanguageISOCode)) {
+ comboLanguage.setSelectedItem(language);
+ }
+ }
+
+ String selectedTheme = PreferencesData.get("theme.file", "");
+ Collection availablethemes = Theme.getAvailablethemes();
+ comboTheme.addItem(tr("Default theme"));
+ for (ZippedTheme theme : availablethemes) {
+ comboTheme.addItem(theme);
+ if (theme.getKey().equals(selectedTheme)) {
+ comboTheme.setSelectedItem(theme);
+ }
+ }
+
+ Font editorFont = PreferencesData.getFont("editor.font");
+ fontSizeField.setText(String.valueOf(editorFont.getSize()));
+
+ try {
+ int scale = PreferencesData.getInteger("gui.scale", -1);
+ if (scale != -1) {
+ autoScaleCheckBox.setSelected(false);
+ scaleSpinner.setValue(scale);
+ }
+ } catch (NumberFormatException ignore) {
+ // In any case defaults to "auto"
+ }
+
+ verboseCompilationBox.setSelected(PreferencesData.getBoolean("build.verbose"));
+ verboseUploadBox.setSelected(PreferencesData.getBoolean("upload.verbose"));
+
+ String currentWarningLevel = PreferencesData.get("compiler.warning_level", "none");
+ for (WarningItem item : warningItems) {
+ if (currentWarningLevel.equals(item.getValue())) {
+ comboWarnings.setSelectedItem(item);
+ }
+ }
+
+ displayLineNumbersBox.setSelected(PreferencesData.getBoolean("editor.linenumbers"));
+
+ enableCodeFoldingBox.setSelected(PreferencesData.getBoolean("editor.code_folding"));
+
+ verifyUploadBox.setSelected(PreferencesData.getBoolean("upload.verify"));
+
+ externalEditorBox.setSelected(PreferencesData.getBoolean("editor.external"));
+
+ if (PreferencesData.get("compiler.cache_core") == null) {
+ PreferencesData.setBoolean("compiler.cache_core", true);
+ }
+
+ checkUpdatesBox.setSelected(PreferencesData.getBoolean("update.check"));
+
+ if (PreferencesData.get("editor.update_extension") == null) {
+ PreferencesData.setBoolean("editor.update_extension", true);
+ }
+
+ accessibleIDEBox.setSelected(PreferencesData.getBoolean("ide.accessible"));
+
+ saveVerifyUploadBox.setSelected(PreferencesData.getBoolean("editor.save_on_verify"));
+
+ additionalBoardsManagerField.setText(PreferencesData.get("boardsmanager.additional.urls"));
+
+ disableAllProxyFields();
+ String proxyType = PreferencesData.get(Constants.PREF_PROXY_TYPE, Constants.PROXY_TYPE_AUTO);
+
+ if (Constants.PROXY_TYPE_NONE.equals(proxyType)) {
+ noProxy.setSelected(true);
+ } else if (Constants.PROXY_TYPE_AUTO.equals(proxyType)) {
+ autoProxy.setSelected(true);
+ autoProxyFieldsSetEnabled(true);
+ if (!PreferencesData.get(Constants.PREF_PROXY_PAC_URL, "").isEmpty()) {
+ autoProxyUsePAC.setSelected(true);
+ autoProxyPACURL.setText(PreferencesData.get(Constants.PREF_PROXY_PAC_URL));
+ autoProxyUsername.setText(PreferencesData.get(Constants.PREF_PROXY_USERNAME));
+ autoProxyPassword.setText(PreferencesData.get(Constants.PREF_PROXY_PASSWORD));
+ }
+ } else {
+ manualProxy.setSelected(true);
+ manualProxyFieldsSetEnabled(true);
+ manualProxyHostName.setText(PreferencesData.get(Constants.PREF_PROXY_MANUAL_HOSTNAME));
+ manualProxyPort.setText(PreferencesData.get(Constants.PREF_PROXY_MANUAL_PORT));
+ manualProxyUsername.setText(PreferencesData.get(Constants.PREF_PROXY_USERNAME));
+ manualProxyPassword.setText(PreferencesData.get(Constants.PREF_PROXY_PASSWORD));
+ }
+
+ String selectedManualProxyType = PreferencesData.get(Constants.PREF_PROXY_MANUAL_TYPE, Constants.PROXY_MANUAL_TYPE_HTTP);
+ manualProxyHTTP.setSelected(Constants.PROXY_MANUAL_TYPE_HTTP.equals(selectedManualProxyType));
+ manualProxySOCKS.setSelected(Constants.PROXY_MANUAL_TYPE_SOCKS.equals(selectedManualProxyType));
+ }
+
+ private void manualProxyFieldsSetEnabled(boolean enabled) {
+ manualProxySOCKS.setEnabled(enabled);
+ manualProxyHTTP.setEnabled(enabled);
+ manualProxyHostNameLabel.setEnabled(enabled);
+ manualProxyHostName.setEnabled(enabled);
+ manualProxyPortLabel.setEnabled(enabled);
+ manualProxyPort.setEnabled(enabled);
+ manualProxyUsernameLabel.setEnabled(enabled);
+ manualProxyUsername.setEnabled(enabled);
+ manualProxyPasswordLabel.setEnabled(enabled);
+ manualProxyPassword.setEnabled(enabled);
+ }
+
+ private void autoProxyFieldsSetEnabled(boolean enabled) {
+ autoProxyUsePAC.setEnabled(enabled);
+ autoProxyPACFieldsSetEnabled(enabled && autoProxyUsePAC.isSelected());
+ }
+
+ private void autoProxyPACFieldsSetEnabled(boolean enabled) {
+ autoProxyPACURL.setEnabled(enabled);
+ autoProxyUsername.setEnabled(enabled);
+ autoProxyUsernameLabel.setEnabled(enabled);
+ autoProxyPassword.setEnabled(enabled);
+ autoProxyPasswordLabel.setEnabled(enabled);
+ }
+
+ private void disableAllProxyFields() {
+ autoProxyFieldsSetEnabled(false);
+ manualProxyFieldsSetEnabled(false);
+ }
+}
diff --git a/app/src/processing/app/AbstractMonitor.java b/app/src/processing/app/AbstractMonitor.java
new file mode 100644
index 00000000000..b6ba0d7652e
--- /dev/null
+++ b/app/src/processing/app/AbstractMonitor.java
@@ -0,0 +1,214 @@
+package processing.app;
+
+import cc.arduino.packages.BoardPort;
+import processing.app.legacy.PApplet;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+
+@SuppressWarnings("serial")
+public abstract class AbstractMonitor extends JFrame implements ActionListener {
+
+ private boolean closed;
+
+ private StringBuffer updateBuffer;
+ private Timer updateTimer;
+ private Timer portExistsTimer;
+
+ private BoardPort boardPort;
+
+ protected String[] serialRateStrings = {"300", "1200", "2400", "4800", "9600", "19200", "38400", "57600", "74880", "115200", "230400", "250000", "500000", "1000000", "2000000"};
+
+ public AbstractMonitor(BoardPort boardPort) {
+ super(boardPort.getLabel());
+ this.boardPort = boardPort;
+
+ addWindowListener(new WindowAdapter() {
+ @Override
+ public void windowClosing(WindowEvent event) {
+ try {
+ closed = true;
+ close();
+ } catch (Exception e) {
+ // ignore
+ }
+ }
+ });
+
+ // obvious, no?
+ KeyStroke wc = Editor.WINDOW_CLOSE_KEYSTROKE;
+ getRootPane().getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(wc, "close");
+ getRootPane().getActionMap().put("close", (new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent event) {
+ try {
+ close();
+ } catch (Exception e) {
+ // ignore
+ }
+ setVisible(false);
+ }
+ }));
+
+
+ onCreateWindow(getContentPane());
+
+ this.setMinimumSize(new Dimension(getContentPane().getMinimumSize().width, this.getPreferredSize().height));
+
+ pack();
+
+ Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
+ String locationStr = PreferencesData.get("last.serial.location");
+ if (locationStr != null) {
+ int[] location = PApplet.parseInt(PApplet.split(locationStr, ','));
+ if (location[0] + location[2] <= screen.width && location[1] + location[3] <= screen.height) {
+ setPlacement(location);
+ }
+ }
+
+ updateBuffer = new StringBuffer(1048576);
+ updateTimer = new Timer(33, this); // redraw serial monitor at 30 Hz
+ updateTimer.start();
+
+ ActionListener portExists = new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent ae) {
+ try {
+ if (Base.getDiscoveryManager().find(boardPort.getAddress()) == null) {
+ if (!closed) {
+ suspend();
+ }
+ } else {
+ if (closed && !Editor.isUploading()) {
+ resume(boardPort);
+ }
+ }
+ } catch (Exception e) {}
+ }
+ };
+
+ portExistsTimer = new Timer(1000, portExists); // check if the port is still there every second
+ portExistsTimer.start();
+
+ closed = false;
+ }
+
+ protected abstract void onCreateWindow(Container mainPane);
+
+ public void enableWindow(boolean enable) {
+ onEnableWindow(enable);
+ }
+
+ protected abstract void onEnableWindow(boolean enable);
+
+ // Puts the window in suspend state, closing the serial port
+ // to allow other entity (the programmer) to use it
+ public void suspend() throws Exception {
+ enableWindow(false);
+
+ close();
+ }
+
+ public void dispose() {
+ super.dispose();
+ portExistsTimer.stop();
+ }
+
+ public void resume(BoardPort boardPort) throws Exception {
+ setBoardPort(boardPort);
+
+ // Enable the window
+ enableWindow(true);
+
+ // If the window is visible, try to open the serial port
+ if (!isVisible()) {
+ return;
+ }
+
+ open();
+ }
+
+ protected void setPlacement(int[] location) {
+ setBounds(location[0], location[1], location[2], location[3]);
+ }
+
+ protected int[] getPlacement() {
+ int[] location = new int[4];
+
+ // 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;
+
+ return location;
+ }
+
+ public abstract void message(final String s);
+
+ public boolean requiresAuthorization() {
+ return false;
+ }
+
+ public String getAuthorizationKey() {
+ return null;
+ }
+
+ public boolean isClosed() {
+ return closed;
+ }
+
+ public void open() throws Exception {
+ closed = false;
+ }
+
+ public void close() throws Exception {
+ closed = true;
+ }
+
+ public BoardPort getBoardPort() {
+ return boardPort;
+ }
+
+ public void setBoardPort(BoardPort boardPort) {
+ if (boardPort == null) {
+ return;
+ }
+ setTitle(boardPort.getLabel());
+ this.boardPort = boardPort;
+ }
+
+ public synchronized void addToUpdateBuffer(char buff[], int n) {
+ updateBuffer.append(buff, 0, n);
+ }
+
+ private synchronized String consumeUpdateBuffer() {
+ String s = updateBuffer.toString();
+ updateBuffer.setLength(0);
+ return s;
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ String s = consumeUpdateBuffer();
+ if (s.isEmpty()) {
+ return;
+ } else {
+ message(s);
+ }
+ }
+
+ /**
+ * 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() {
+ // Empty.
+ };
+}
diff --git a/app/src/processing/app/AbstractTextMonitor.java b/app/src/processing/app/AbstractTextMonitor.java
new file mode 100644
index 00000000000..00eabb20649
--- /dev/null
+++ b/app/src/processing/app/AbstractTextMonitor.java
@@ -0,0 +1,265 @@
+package processing.app;
+
+import static processing.app.I18n.tr;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyListener;
+import java.awt.event.MouseWheelListener;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.StringTokenizer;
+
+import javax.swing.Action;
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+import javax.swing.JScrollPane;
+import javax.swing.JTextField;
+import javax.swing.SwingUtilities;
+import javax.swing.border.EmptyBorder;
+import javax.swing.text.DefaultCaret;
+import javax.swing.text.DefaultEditorKit;
+
+import cc.arduino.packages.BoardPort;
+
+@SuppressWarnings("serial")
+public abstract class AbstractTextMonitor extends AbstractMonitor {
+
+ protected JLabel noLineEndingAlert;
+ protected TextAreaFIFO textArea;
+ protected JScrollPane scrollPane;
+ protected JTextField textField;
+ protected JButton sendButton;
+ protected JButton clearButton;
+ protected JCheckBox autoscrollBox;
+ protected JCheckBox addTimeStampBox;
+ protected JComboBox lineEndings;
+ protected JComboBox serialRates;
+
+ public AbstractTextMonitor(BoardPort boardPort) {
+ super(boardPort);
+ }
+
+ @Override
+ public synchronized void addMouseWheelListener(MouseWheelListener l) {
+ super.addMouseWheelListener(l);
+ textArea.addMouseWheelListener(l);
+ }
+
+ @Override
+ public synchronized void addKeyListener(KeyListener l) {
+ super.addKeyListener(l);
+ textArea.addKeyListener(l);
+ textField.addKeyListener(l);
+ }
+
+ @Override
+ protected void onCreateWindow(Container mainPane) {
+
+ mainPane.setLayout(new BorderLayout());
+
+ textArea = new TextAreaFIFO(8_000_000);
+ textArea.setRows(16);
+ textArea.setColumns(40);
+ textArea.setEditable(false);
+
+ // don't automatically update the caret. that way we can manually decide
+ // whether or not to do so based on the autoscroll checkbox.
+ ((DefaultCaret) textArea.getCaret()).setUpdatePolicy(DefaultCaret.NEVER_UPDATE);
+
+ scrollPane = new JScrollPane(textArea);
+
+ mainPane.add(scrollPane, BorderLayout.CENTER);
+
+ JPanel upperPane = new JPanel();
+ upperPane.setLayout(new BoxLayout(upperPane, BoxLayout.X_AXIS));
+ upperPane.setBorder(new EmptyBorder(4, 4, 4, 4));
+
+ textField = new JTextField(40);
+ // textField is selected every time the window is focused
+ addWindowFocusListener(new WindowAdapter() {
+ @Override
+ public void windowGainedFocus(WindowEvent e) {
+ textField.requestFocusInWindow();
+ }
+ });
+
+ // Add cut/copy/paste contextual menu to the text input field.
+ JPopupMenu menu = new JPopupMenu();
+
+ Action cut = new DefaultEditorKit.CutAction();
+ cut.putValue(Action.NAME, tr("Cut"));
+ menu.add(cut);
+
+ Action copy = new DefaultEditorKit.CopyAction();
+ copy.putValue(Action.NAME, tr("Copy"));
+ menu.add(copy);
+
+ Action paste = new DefaultEditorKit.PasteAction();
+ paste.putValue(Action.NAME, tr("Paste"));
+ menu.add(paste);
+
+ textField.setComponentPopupMenu(menu);
+
+ sendButton = new JButton(tr("Send"));
+ clearButton = new JButton(tr("Clear output"));
+
+ upperPane.add(textField);
+ upperPane.add(Box.createRigidArea(new Dimension(4, 0)));
+ upperPane.add(sendButton);
+
+ mainPane.add(upperPane, BorderLayout.NORTH);
+
+ final JPanel pane = new JPanel();
+ pane.setLayout(new BoxLayout(pane, BoxLayout.X_AXIS));
+ pane.setBorder(new EmptyBorder(4, 4, 4, 4));
+
+ autoscrollBox = new JCheckBox(tr("Autoscroll"), true);
+ addTimeStampBox = new JCheckBox(tr("Show timestamp"), false);
+
+ noLineEndingAlert = new JLabel(I18n.format(tr("You've pressed {0} but nothing was sent. Should you select a line ending?"), tr("Send")));
+ noLineEndingAlert.setToolTipText(noLineEndingAlert.getText());
+ noLineEndingAlert.setForeground(pane.getBackground());
+ Dimension minimumSize = new Dimension(noLineEndingAlert.getMinimumSize());
+ minimumSize.setSize(minimumSize.getWidth() / 3, minimumSize.getHeight());
+ noLineEndingAlert.setMinimumSize(minimumSize);
+
+ lineEndings = new JComboBox<>(new String[]{tr("No line ending"), tr("Newline"), tr("Carriage return"), tr("Both NL & CR")});
+ lineEndings.addActionListener((ActionEvent event) -> {
+ PreferencesData.setInteger("serial.line_ending", lineEndings.getSelectedIndex());
+ noLineEndingAlert.setForeground(pane.getBackground());
+ });
+ addTimeStampBox.addActionListener((ActionEvent event) ->
+ PreferencesData.setBoolean("serial.show_timestamp", addTimeStampBox.isSelected()));
+
+ lineEndings.setMaximumSize(lineEndings.getMinimumSize());
+
+ serialRates = new JComboBox<>();
+ for (String rate : serialRateStrings) {
+ serialRates.addItem(rate + " " + tr("baud"));
+ }
+
+ serialRates.setMaximumSize(serialRates.getMinimumSize());
+
+ pane.add(autoscrollBox);
+ pane.add(addTimeStampBox);
+ pane.add(Box.createHorizontalGlue());
+ pane.add(noLineEndingAlert);
+ pane.add(Box.createRigidArea(new Dimension(8, 0)));
+ pane.add(lineEndings);
+ pane.add(Box.createRigidArea(new Dimension(8, 0)));
+ pane.add(serialRates);
+ pane.add(Box.createRigidArea(new Dimension(8, 0)));
+ pane.add(clearButton);
+
+ applyPreferences();
+
+ mainPane.add(pane, BorderLayout.SOUTH);
+ }
+
+ @Override
+ protected void onEnableWindow(boolean enable) {
+ // never actually disable textArea, so people can
+ // still select & copy text, even when the port
+ // is closed or disconnected
+ textArea.setEnabled(true);
+ if (enable) {
+ // setting these to null for system default
+ // gives a wrong gray background on Windows
+ // so assume black text on white background
+ textArea.setForeground(Color.BLACK);
+ textArea.setBackground(Color.WHITE);
+ } else {
+ // In theory, UIManager.getDefaults() should
+ // give us the system's colors for disabled
+ // windows. But it doesn't seem to work. :(
+ textArea.setForeground(new Color(64, 64, 64));
+ textArea.setBackground(new Color(238, 238, 238));
+ }
+ textArea.invalidate();
+ scrollPane.setEnabled(enable);
+ textField.setEnabled(enable);
+ sendButton.setEnabled(enable);
+ }
+
+ public void onSendCommand(ActionListener listener) {
+ textField.addActionListener(listener);
+ sendButton.addActionListener(listener);
+ }
+
+ public void onClearCommand(ActionListener listener) {
+ clearButton.addActionListener(listener);
+ }
+
+ public void onSerialRateChange(ActionListener listener) {
+ serialRates.addActionListener(listener);
+ }
+
+ @Override
+ public void message(String msg) {
+ SwingUtilities.invokeLater(() -> updateTextArea(msg));
+ }
+
+ private static final String LINE_SEPARATOR = "\n";
+ private boolean isStartingLine = true;
+
+ protected void updateTextArea(String msg) {
+ if (addTimeStampBox.isSelected()) {
+ textArea.append(addTimestamps(msg));
+ } else {
+ textArea.append(msg);
+ }
+ if (autoscrollBox.isSelected()) {
+ textArea.setCaretPosition(textArea.getDocument().getLength());
+ }
+ }
+
+ @Override
+ public void applyPreferences() {
+
+ // Apply font.
+ Font consoleFont = Theme.getFont("console.font");
+ Font editorFont = PreferencesData.getFont("editor.font");
+ textArea.setFont(Theme.scale(new Font(
+ consoleFont.getName(), consoleFont.getStyle(), editorFont.getSize())));
+
+ // Apply line endings.
+ if (PreferencesData.get("serial.line_ending") != null) {
+ lineEndings.setSelectedIndex(PreferencesData.getInteger("serial.line_ending"));
+ }
+
+ // Apply timestamp visibility.
+ if (PreferencesData.get("serial.show_timestamp") != null) {
+ addTimeStampBox.setSelected(PreferencesData.getBoolean("serial.show_timestamp"));
+ }
+ }
+
+ private String addTimestamps(String text) {
+ String now = new SimpleDateFormat("HH:mm:ss.SSS -> ").format(new Date());
+ final StringBuilder sb = new StringBuilder(text.length() + now.length());
+ StringTokenizer tokenizer = new StringTokenizer(text, LINE_SEPARATOR, true);
+ while (tokenizer.hasMoreTokens()) {
+ if (isStartingLine) {
+ sb.append(now);
+ }
+ String token = tokenizer.nextToken();
+ sb.append(token);
+ // tokenizer returns "\n" as a single token
+ isStartingLine = token.equals(LINE_SEPARATOR);
+ }
+ return sb.toString();
+ }
+}
diff --git a/app/src/processing/app/Base.java b/app/src/processing/app/Base.java
index 0dba54f300a..cdac3059f6d 100644
--- a/app/src/processing/app/Base.java
+++ b/app/src/processing/app/Base.java
@@ -22,16 +22,61 @@
package processing.app;
+import cc.arduino.Compiler;
+import cc.arduino.Constants;
+import cc.arduino.UpdatableBoardsLibsFakeURLsHandler;
+import cc.arduino.UploaderUtils;
+import cc.arduino.contributions.*;
+import cc.arduino.contributions.libraries.ContributedLibrary;
+import cc.arduino.contributions.libraries.LibrariesIndexer;
+import cc.arduino.contributions.libraries.LibraryInstaller;
+import cc.arduino.contributions.libraries.LibraryOfSameTypeComparator;
+import cc.arduino.contributions.libraries.ui.LibraryManagerUI;
+import cc.arduino.contributions.packages.ContributedPlatform;
+import cc.arduino.contributions.packages.ContributionInstaller;
+import cc.arduino.contributions.packages.ContributionsIndexer;
+import cc.arduino.contributions.packages.ui.ContributionManagerUI;
+import cc.arduino.files.DeleteFilesOnShutdown;
+import cc.arduino.packages.DiscoveryManager;
+import cc.arduino.packages.Uploader;
+import cc.arduino.view.Event;
+import cc.arduino.view.JMenuUtils;
+import cc.arduino.view.SplashScreenHelper;
+import com.github.zafarkhaja.semver.Version;
+import org.apache.commons.compress.utils.IOUtils;
+import org.apache.commons.lang3.StringUtils;
+import processing.app.debug.TargetBoard;
+import processing.app.debug.TargetPackage;
+import processing.app.debug.TargetPlatform;
+import processing.app.helpers.*;
+import processing.app.helpers.filefilters.OnlyDirs;
+import processing.app.helpers.filefilters.OnlyFilesWithExtension;
+import processing.app.javax.swing.filechooser.FileNameExtensionFilter;
+import processing.app.legacy.PApplet;
+import processing.app.macosx.ThinkDifferent;
+import processing.app.packages.LibraryList;
+import processing.app.packages.UserLibrary;
+import processing.app.packages.UserLibraryFolder.Location;
+import processing.app.syntax.PdeKeywords;
+import processing.app.syntax.SketchTextAreaDefaultInputMap;
+import processing.app.tools.MenuScroller;
+import processing.app.tools.ZipDeflater;
+
+import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
+import java.util.List;
+import java.util.Timer;
import java.util.*;
+import java.util.logging.Handler;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
-import javax.swing.*;
-
-import processing.app.debug.Compiler;
-import processing.app.debug.Target;
-import processing.core.*;
+import static processing.app.I18n.format;
+import static processing.app.I18n.tr;
/**
@@ -41,55 +86,25 @@
* files and images, etc) that comes from that.
*/
public class Base {
- public static final int REVISION = 22;
- /** This might be replaced by main() if there's a lib/version.txt file. */
- static String VERSION_NAME = "0022";
- /** Set true if this a proper release rather than a numbered revision. */
- static public boolean RELEASE = false;
- static HashMap platformNames = new HashMap();
- static {
- platformNames.put(PConstants.WINDOWS, "windows");
- platformNames.put(PConstants.MACOSX, "macosx");
- platformNames.put(PConstants.LINUX, "linux");
- }
-
- static HashMap platformIndices = new HashMap();
- static {
- platformIndices.put("windows", PConstants.WINDOWS);
- platformIndices.put("macosx", PConstants.MACOSX);
- platformIndices.put("linux", PConstants.LINUX);
- }
- static Platform platform;
+ private static final int RECENT_SKETCHES_MAX_SIZE = 10;
- static private boolean commandLine;
+ private static boolean commandLine;
+ public static volatile Base INSTANCE;
- // A single instance of the preferences window
- Preferences preferencesFrame;
+ public static Map FIND_DIALOG_STATE = new HashMap<>();
+ private final ContributionInstaller contributionInstaller;
+ private final LibraryInstaller libraryInstaller;
+ private ContributionsSelfCheck contributionsSelfCheck;
// set to true after the first time the menu is built.
// so that the errors while building don't show up again.
boolean builtOnce;
- static File buildFolder;
-
- // these are static because they're used by Sketch
- static private File examplesFolder;
- static private File librariesFolder;
- static private File toolsFolder;
- static private File hardwareFolder;
-
- static HashSet libraries;
-
- // maps imported packages to their library folder
- static HashMap importToLibraryTable;
-
// classpath for all known libraries for p5
// (both those in the p5/libs folder and those with lib subfolders
// found in the sketchbook)
static public String librariesClassPath;
-
- static public HashMap targetsTable;
// Location for untitled items
static File untitledFolder;
@@ -97,340 +112,538 @@ public class Base {
// p5 icon for the window
// static Image icon;
-// int editorCount;
-// Editor[] editors;
- java.util.List editors =
- Collections.synchronizedList(new ArrayList());
-// ArrayList editors = Collections.synchronizedList(new ArrayList());
+ // int editorCount;
+ List editors = Collections.synchronizedList(new ArrayList());
Editor activeEditor;
+ // these menus are shared so that the board and serial port selections
+ // are the same for all windows (since the board and serial port that are
+ // actually used are determined by the preferences, which are shared)
+ private List boardsCustomMenus;
+ private List programmerMenus;
- static public void main(String args[]) {
- try {
- File versionFile = getContentFile("lib/version.txt");
- if (versionFile.exists()) {
- String version = PApplet.loadStrings(versionFile)[0];
- if (!version.equals(VERSION_NAME)) {
- VERSION_NAME = version;
- RELEASE = true;
- }
- }
- } catch (Exception e) {
- e.printStackTrace();
+ private PdeKeywords pdeKeywords;
+ private final List recentSketchesMenuItems = new LinkedList<>();
+
+ static public void main(String args[]) throws Exception {
+ if (!OSUtils.isWindows()) {
+ // Those properties helps enabling anti-aliasing on Linux
+ // (but not on Windows where they made things worse actually
+ // and the font rendering becomes ugly).
+
+ // Those properties must be set before initializing any
+ // graphic object, otherwise they don't have any effect.
+ System.setProperty("awt.useSystemAAFontSettings", "on");
+ System.setProperty("swing.aatext", "true");
}
+ System.setProperty("java.net.useSystemProxies", "true");
-// if (System.getProperty("mrj.version") != null) {
-// //String jv = System.getProperty("java.version");
-// String ov = System.getProperty("os.version");
-// if (ov.startsWith("10.5")) {
-// System.setProperty("apple.laf.useScreenMenuBar", "true");
-// }
-// }
+ if (OSUtils.isMacOS()) {
+ System.setProperty("apple.laf.useScreenMenuBar",
+ String.valueOf(!System.getProperty("os.version").startsWith("10.13")
+ || isMacOsAboutMenuItemPresent()));
- /*
- commandLine = false;
- if (args.length >= 2) {
- if (args[0].startsWith("--")) {
- commandLine = true;
- }
+ ThinkDifferent.init();
}
- if (PApplet.javaVersion < 1.5f) {
- //System.err.println("no way man");
- Base.showError("Need to install Java 1.5",
- "This version of Processing requires \n" +
- "Java 1.5 or later to run properly.\n" +
- "Please visit java.com to upgrade.", null);
+ try {
+ INSTANCE = new Base(args);
+ } catch (Throwable e) {
+ e.printStackTrace(System.err);
+ System.exit(255);
}
- */
+ }
- initPlatform();
+ @SuppressWarnings("deprecation")
+ public static boolean isMacOsAboutMenuItemPresent() {
+ return com.apple.eawt.Application.getApplication().isAboutMenuItemPresent();
+ }
-// // Set the look and feel before opening the window
-// try {
-// platform.setLookAndFeel();
-// } catch (Exception e) {
-// System.err.println("Non-fatal error while setting the Look & Feel.");
-// System.err.println("The error message follows, however Processing should run fine.");
-// System.err.println(e.getMessage());
-// //e.printStackTrace();
-// }
+ static public void initLogger() {
+ Handler consoleHandler = new ConsoleLogger();
+ consoleHandler.setLevel(Level.ALL);
+ consoleHandler.setFormatter(new LogFormatter("%1$tl:%1$tM:%1$tS [%4$7s] %2$s: %5$s%n"));
- // Use native popups so they don't look so crappy on osx
- JPopupMenu.setDefaultLightWeightPopupEnabled(false);
+ Logger globalLogger = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME);
+ globalLogger.setLevel(consoleHandler.getLevel());
- // Don't put anything above this line that might make GUI,
- // because the platform has to be inited properly first.
+ // Remove default
+ Handler[] handlers = globalLogger.getHandlers();
+ for(Handler handler : handlers) {
+ globalLogger.removeHandler(handler);
+ }
+ Logger root = Logger.getLogger("");
+ handlers = root.getHandlers();
+ for(Handler handler : handlers) {
+ root.removeHandler(handler);
+ }
- // Make sure a full JDK is installed
- //initRequirements();
+ globalLogger.addHandler(consoleHandler);
- // run static initialization that grabs all the prefs
- Preferences.init(null);
+ Logger.getLogger("cc.arduino.packages.autocomplete").setParent(globalLogger);
+ Logger.getLogger("br.com.criativasoft.cpluslibparser").setParent(globalLogger);
+ Logger.getLogger(Base.class.getPackage().getName()).setParent(globalLogger);
- // setup the theme coloring fun
- Theme.init();
+ }
- // Set the look and feel before opening the window
- try {
- platform.setLookAndFeel();
- } catch (Exception e) {
- String mess = e.getMessage();
- if (mess.indexOf("ch.randelshofer.quaqua.QuaquaLookAndFeel") == -1) {
- System.err.println("Non-fatal error while setting the Look & Feel.");
- System.err.println("The error message follows, however Arduino should run fine.");
- System.err.println(mess);
+ static protected boolean isCommandLine() {
+ return commandLine;
+ }
+
+ // Returns a File object for the given pathname. If the pathname
+ // is not absolute, it is interpreted relative to the current
+ // directory when starting the IDE (which is not the same as the
+ // current working directory!).
+ static public File absoluteFile(String path) {
+ return BaseNoGui.absoluteFile(path);
+ }
+
+ public Base(String[] args) throws Exception {
+ Thread deleteFilesOnShutdownThread = new Thread(DeleteFilesOnShutdown.INSTANCE);
+ deleteFilesOnShutdownThread.setName("DeleteFilesOnShutdown");
+ Runtime.getRuntime().addShutdownHook(deleteFilesOnShutdownThread);
+
+ BaseNoGui.initLogger();
+
+ initLogger();
+
+ BaseNoGui.initPlatform();
+
+ BaseNoGui.getPlatform().init();
+
+ BaseNoGui.initPortableFolder();
+
+ // Look for a possible "--preferences-file" parameter and load preferences
+ BaseNoGui.initParameters(args);
+
+ CommandlineParser parser = new CommandlineParser(args);
+ parser.parseArgumentsPhase1();
+ commandLine = !parser.isGuiMode();
+
+ BaseNoGui.checkInstallationFolder();
+
+ // If no path is set, get the default sketchbook folder for this platform
+ if (BaseNoGui.getSketchbookPath() == null) {
+ File defaultFolder = getDefaultSketchbookFolderOrPromptForIt();
+ if (BaseNoGui.getPortableFolder() != null)
+ PreferencesData.set("sketchbook.path", BaseNoGui.getPortableSketchbookFolder());
+ else
+ PreferencesData.set("sketchbook.path", defaultFolder.getAbsolutePath());
+ if (!defaultFolder.exists()) {
+ defaultFolder.mkdirs();
}
}
- // Create a location for untitled sketches
- untitledFolder = createTempFolder("untitled");
- untitledFolder.deleteOnExit();
+ SplashScreenHelper splash;
+ if (parser.isGuiMode()) {
+ // Setup all notification widgets
+ splash = new SplashScreenHelper(SplashScreen.getSplashScreen());
+ BaseNoGui.notifier = new GUIUserNotifier(this);
- new Base(args);
- }
+ // Setup the theme coloring fun
+ Theme.init();
+ System.setProperty("swing.aatext", PreferencesData.get("editor.antialias", "true"));
+
+ // Set the look and feel before opening the window
+ try {
+ BaseNoGui.getPlatform().setLookAndFeel();
+ } catch (Exception e) {
+ // ignore
+ }
+ // Use native popups so they don't look so crappy on osx
+ JPopupMenu.setDefaultLightWeightPopupEnabled(false);
+ } else {
+ splash = new SplashScreenHelper(null);
+ }
- static protected void setCommandLine() {
- commandLine = true;
- }
+ splash.splashText(tr("Loading configuration..."));
+ BaseNoGui.initVersion();
- static protected boolean isCommandLine() {
- return commandLine;
- }
+ // Don't put anything above this line that might make GUI,
+ // because the platform has to be inited properly first.
+
+ // Create a location for untitled sketches
+ untitledFolder = FileUtils.createTempFolder("untitled" + new Random().nextInt(Integer.MAX_VALUE), ".tmp");
+ DeleteFilesOnShutdown.add(untitledFolder);
+ splash.splashText(tr("Initializing packages..."));
+ BaseNoGui.initPackages();
- static protected void initPlatform() {
- try {
- Class> platformClass = Class.forName("processing.app.Platform");
- if (Base.isMacOS()) {
- platformClass = Class.forName("processing.app.macosx.Platform");
- } else if (Base.isWindows()) {
- platformClass = Class.forName("processing.app.windows.Platform");
- } else if (Base.isLinux()) {
- platformClass = Class.forName("processing.app.linux.Platform");
- }
- platform = (Platform) platformClass.newInstance();
- } catch (Exception e) {
- Base.showError("Problem Setting the Platform",
- "An unknown error occurred while trying to load\n" +
- "platform-specific code for your machine.", e);
+ parser.getUploadPort().ifPresent(BaseNoGui::selectSerialPort);
+
+ splash.splashText(tr("Preparing boards..."));
+
+ if (!isCommandLine()) {
+ rebuildBoardsMenu();
+ rebuildProgrammerMenu();
+ } else {
+ TargetBoard lastSelectedBoard = BaseNoGui.getTargetBoard();
+ if (lastSelectedBoard != null)
+ BaseNoGui.selectBoard(lastSelectedBoard);
}
- }
+ // Setup board-dependent variables.
+ onBoardOrPortChange();
- static protected void initRequirements() {
- try {
- Class.forName("com.sun.jdi.VirtualMachine");
- } catch (ClassNotFoundException cnfe) {
- Base.showPlatforms();
- Base.showError("Please install JDK 1.5 or later",
- "Arduino requires a full JDK (not just a JRE)\n" +
- "to run. Please install JDK 1.5 or later.\n" +
- "More information can be found in the reference.", cnfe);
+ pdeKeywords = new PdeKeywords();
+ pdeKeywords.reload();
+
+ final GPGDetachedSignatureVerifier gpgDetachedSignatureVerifier = new GPGDetachedSignatureVerifier();
+ contributionInstaller = new ContributionInstaller(BaseNoGui.getPlatform(), gpgDetachedSignatureVerifier);
+ libraryInstaller = new LibraryInstaller(BaseNoGui.getPlatform(), gpgDetachedSignatureVerifier);
+
+ parser.parseArgumentsPhase2();
+
+ // Save the preferences. For GUI mode, this happens in the quit
+ // handler, but for other modes we should also make sure to save
+ // them.
+ if (parser.isForceSavePrefs()) {
+ PreferencesData.save();
}
- }
+ if (parser.isInstallBoard()) {
+ ContributionsIndexer indexer = new ContributionsIndexer(
+ BaseNoGui.getSettingsFolder(), BaseNoGui.getHardwareFolder(),
+ BaseNoGui.getPlatform(), gpgDetachedSignatureVerifier);
+ ProgressListener progressListener = new ConsoleProgressListener();
- public Base(String[] args) {
- platform.init(this);
+ contributionInstaller.updateIndex(progressListener);
+ indexer.parseIndex();
+ indexer.syncWithFilesystem();
- // Get paths for the libraries and examples in the Processing folder
- //String workingDirectory = System.getProperty("user.dir");
- examplesFolder = getContentFile("examples");
- librariesFolder = getContentFile("libraries");
- toolsFolder = getContentFile("tools");
+ String[] boardToInstallParts = parser.getBoardToInstall().split(":");
- // Get the sketchbook path, and make sure it's set properly
- String sketchbookPath = Preferences.get("sketchbook.path");
+ ContributedPlatform selected = null;
+ if (boardToInstallParts.length == 3) {
+ Optional version = VersionHelper.valueOf(boardToInstallParts[2]);
+ if (!version.isPresent()) {
+ System.out.println(format(tr("Invalid version {0}"), boardToInstallParts[2]));
+ System.exit(1);
+ }
+ selected = indexer.getIndex().findPlatform(boardToInstallParts[0], boardToInstallParts[1], version.get().toString());
+ } else if (boardToInstallParts.length == 2) {
+ List platformsByName = indexer.getIndex().findPlatforms(boardToInstallParts[0], boardToInstallParts[1]);
+ Collections.sort(platformsByName, new DownloadableContributionVersionComparator());
+ if (!platformsByName.isEmpty()) {
+ selected = platformsByName.get(platformsByName.size() - 1);
+ }
+ }
+ if (selected == null) {
+ System.out.println(tr("Selected board is not available"));
+ System.exit(1);
+ }
- // If a value is at least set, first check to see if the folder exists.
- // If it doesn't, warn the user that the sketchbook folder is being reset.
- if (sketchbookPath != null) {
- File skechbookFolder = new File(sketchbookPath);
- if (!skechbookFolder.exists()) {
- Base.showWarning("Sketchbook folder disappeared",
- "The sketchbook folder no longer exists.\n" +
- "Arduino will switch to the default sketchbook\n" +
- "location, and create a new sketchbook folder if\n" +
- "necessary. Arduino will then stop talking about\n" +
- "himself in the third person.", null);
- sketchbookPath = null;
+ ContributedPlatform installed = indexer.getInstalled(boardToInstallParts[0], boardToInstallParts[1]);
+
+ if (!selected.isBuiltIn()) {
+ contributionInstaller.install(selected, progressListener);
}
- }
- // If no path is set, get the default sketchbook folder for this platform
- if (sketchbookPath == null) {
- File defaultFolder = getDefaultSketchbookFolder();
- Preferences.set("sketchbook.path", defaultFolder.getAbsolutePath());
- if (!defaultFolder.exists()) {
- defaultFolder.mkdirs();
+ if (installed != null && !installed.isBuiltIn()) {
+ contributionInstaller.remove(installed);
+ }
+
+ System.exit(0);
+
+ } else if (parser.isInstallLibrary()) {
+ BaseNoGui.onBoardOrPortChange();
+
+ ProgressListener progressListener = new ConsoleProgressListener();
+ libraryInstaller.updateIndex(progressListener);
+
+ LibrariesIndexer indexer = new LibrariesIndexer(BaseNoGui.getSettingsFolder());
+ indexer.parseIndex();
+ indexer.setLibrariesFolders(BaseNoGui.getLibrariesFolders());
+ indexer.rescanLibraries();
+
+ for (String library : parser.getLibraryToInstall().split(",")) {
+ String[] libraryToInstallParts = library.split(":");
+
+ ContributedLibrary selected = null;
+ if (libraryToInstallParts.length == 2) {
+ Optional version = VersionHelper.valueOf(libraryToInstallParts[1]);
+ if (!version.isPresent()) {
+ System.out.println(format(tr("Invalid version {0}"), libraryToInstallParts[1]));
+ System.exit(1);
+ }
+ selected = indexer.getIndex().find(libraryToInstallParts[0], version.get().toString());
+ } else if (libraryToInstallParts.length == 1) {
+ List librariesByName = indexer.getIndex().find(libraryToInstallParts[0]);
+ Collections.sort(librariesByName, new DownloadableContributionVersionComparator());
+ if (!librariesByName.isEmpty()) {
+ selected = librariesByName.get(librariesByName.size() - 1);
+ }
+ }
+ if (selected == null) {
+ System.out.println(tr("Selected library is not available"));
+ System.exit(1);
+ }
+
+ Optional mayInstalled = indexer.getIndex().getInstalled(libraryToInstallParts[0]);
+ if (mayInstalled.isPresent() && selected.isIDEBuiltIn()) {
+ System.out.println(tr(I18n
+ .format("Library {0} is available as built-in in the IDE.\nRemoving the other version {1} installed in the sketchbook...",
+ library, mayInstalled.get().getParsedVersion())));
+ libraryInstaller.remove(mayInstalled.get(), progressListener);
+ } else {
+ libraryInstaller.install(selected, progressListener);
+ }
+ }
+
+ System.exit(0);
+
+ } else if (parser.isVerifyOrUploadMode()) {
+ // Set verbosity for command line build
+ PreferencesData.setBoolean("build.verbose", parser.isDoVerboseBuild());
+ PreferencesData.setBoolean("upload.verbose", parser.isDoVerboseUpload());
+
+ // Set preserve-temp flag
+ PreferencesData.setBoolean("runtime.preserve.temp.files", parser.isPreserveTempFiles());
+
+ // Make sure these verbosity preferences are only for the current session
+ PreferencesData.setDoSave(false);
+
+ Sketch sketch = null;
+ String outputFile = null;
+
+ try {
+ // Build
+ splash.splashText(tr("Verifying..."));
+
+ File sketchFile = BaseNoGui.absoluteFile(parser.getFilenames().get(0));
+ sketch = new Sketch(sketchFile);
+
+ outputFile = new Compiler(sketch).build(progress -> {}, false);
+ } catch (Exception e) {
+ // Error during build
+ e.printStackTrace();
+ System.exit(1);
}
- }
-
- targetsTable = new HashMap();
- loadHardware(getHardwareFolder());
- loadHardware(getSketchbookHardwareFolder());
- // Check if there were previously opened sketches to be restored
- boolean opened = restoreSketches();
+ if (parser.isUploadMode()) {
+ // Upload
+ splash.splashText(tr("Uploading..."));
- // Check if any files were passed in on the command line
- for (int i = 0; i < args.length; i++) {
- String path = args[i];
- // Fix a problem with systems that use a non-ASCII languages. Paths are
- // being passed in with 8.3 syntax, which makes the sketch loader code
- // unhappy, since the sketch folder naming doesn't match up correctly.
- // http://dev.processing.org/bugs/show_bug.cgi?id=1089
- if (isWindows()) {
try {
- File file = new File(args[i]);
- path = file.getCanonicalPath();
- } catch (IOException e) {
- e.printStackTrace();
+ List warnings = new ArrayList<>();
+ UploaderUtils uploader = new UploaderUtils();
+ boolean res = uploader.upload(sketch, null, outputFile,
+ parser.isDoUseProgrammer(),
+ parser.isNoUploadPort(), warnings);
+ for (String warning : warnings) {
+ System.out.println(tr("Warning") + ": " + warning);
+ }
+ if (!res) {
+ throw new Exception();
+ }
+ } catch (Exception e) {
+ // Error during upload
+ System.out.flush();
+ System.err.flush();
+ System.err
+ .println(tr("An error occurred while uploading the sketch"));
+ System.exit(1);
}
}
- if (handleOpen(path) != null) {
- opened = true;
+
+ // No errors exit gracefully
+ System.exit(0);
+ } else if (parser.isGuiMode()) {
+ splash.splashText(tr("Starting..."));
+
+ for (String path : parser.getFilenames()) {
+ // Correctly resolve relative paths
+ File file = absoluteFile(path);
+
+ // Fix a problem with systems that use a non-ASCII languages. Paths are
+ // being passed in with 8.3 syntax, which makes the sketch loader code
+ // unhappy, since the sketch folder naming doesn't match up correctly.
+ // http://dev.processing.org/bugs/show_bug.cgi?id=1089
+ if (OSUtils.isWindows()) {
+ try {
+ file = file.getCanonicalFile();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ if (!parser.isForceSavePrefs())
+ PreferencesData.setDoSave(true);
+ if (handleOpen(file, retrieveSketchLocation(".default"), false) == null) {
+ String mess = format(tr("Failed to open sketch: \"{0}\""), path);
+ // Open failure is fatal in upload/verify mode
+ if (parser.isVerifyOrUploadMode())
+ showError(null, mess, 2);
+ else
+ showWarning(null, mess, null);
+ }
}
- }
- // Create a new empty window (will be replaced with any files to be opened)
- if (!opened) {
- handleNew();
- }
+ installKeyboardInputMap();
+
+ // Check if there were previously opened sketches to be restored
+ restoreSketches();
+
+ // Create a new empty window (will be replaced with any files to be opened)
+ if (editors.isEmpty()) {
+ handleNew();
+ }
- // check for updates
- if (Preferences.getBoolean("update.check")) {
- new UpdateCheck(this);
+ new Thread(new BuiltInCoreIsNewerCheck(this)).start();
+
+ // Check for boards which need an additional core
+ new Thread(new NewBoardListener(this)).start();
+
+ // Check for updates
+ if (PreferencesData.getBoolean("update.check")) {
+ new UpdateCheck(this);
+
+ contributionsSelfCheck = new ContributionsSelfCheck(this, new UpdatableBoardsLibsFakeURLsHandler(this), contributionInstaller, libraryInstaller);
+ new Timer(false).schedule(contributionsSelfCheck, Constants.BOARDS_LIBS_UPDATABLE_CHECK_START_PERIOD);
+ }
+
+ } else if (parser.isNoOpMode()) {
+ // Do nothing (intended for only changing preferences)
+ System.exit(0);
+ } else if (parser.isGetPrefMode()) {
+ BaseNoGui.dumpPrefs(parser);
+ } else if (parser.isVersionMode()) {
+ System.out.println("Arduino: " + BaseNoGui.VERSION_NAME_LONG);
+ System.exit(0);
}
}
+ private void installKeyboardInputMap() {
+ UIManager.put("RSyntaxTextAreaUI.inputMap", new SketchTextAreaDefaultInputMap());
+ }
/**
* Post-constructor setup for the editor area. Loads the last
* sketch that was used (if any), and restores other Editor settings.
* The complement to "storePreferences", this is called when the
* application is first launched.
+ *
+ * @throws Exception
*/
- protected boolean restoreSketches() {
- // figure out window placement
-
- Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
- boolean windowPositionValid = true;
-
- if (Preferences.get("last.screen.height") != null) {
- // if screen size has changed, the window coordinates no longer
- // make sense, so don't use them unless they're identical
- int screenW = Preferences.getInteger("last.screen.width");
- int screenH = Preferences.getInteger("last.screen.height");
-
- if ((screen.width != screenW) || (screen.height != screenH)) {
- windowPositionValid = false;
- }
- /*
- int windowX = Preferences.getInteger("last.window.x");
- int windowY = Preferences.getInteger("last.window.y");
- if ((windowX < 0) || (windowY < 0) ||
- (windowX > screenW) || (windowY > screenH)) {
- windowPositionValid = false;
- }
- */
- } else {
- windowPositionValid = false;
- }
-
+ protected boolean restoreSketches() throws Exception {
// Iterate through all sketches that were open last time p5 was running.
// If !windowPositionValid, then ignore the coordinates found for each.
// Save the sketch path and window placement for each open sketch
- int count = Preferences.getInteger("last.sketch.count");
+ int count = PreferencesData.getInteger("last.sketch.count");
int opened = 0;
- for (int i = 0; i < count; i++) {
- String path = Preferences.get("last.sketch" + i + ".path");
- int[] location;
- if (windowPositionValid) {
- String locationStr = Preferences.get("last.sketch" + i + ".location");
- location = PApplet.parseInt(PApplet.split(locationStr, ','));
- } else {
- location = nextEditorLocation();
+ for (int i = count - 1; i >= 0; i--) {
+ String path = PreferencesData.get("last.sketch" + i + ".path");
+ if (path == null) {
+ continue;
+ }
+ if (BaseNoGui.getPortableFolder() != null && !new File(path).isAbsolute()) {
+ File absolute = new File(BaseNoGui.getPortableFolder(), path);
+ try {
+ path = absolute.getCanonicalPath();
+ } catch (IOException e) {
+ // path unchanged.
+ }
}
+ int[] location = retrieveSketchLocation("" + i);
// If file did not exist, null will be returned for the Editor
- if (handleOpen(path, location) != null) {
+ if (handleOpen(new File(path), location, nextEditorLocation(), false, false) != null) {
opened++;
}
}
return (opened > 0);
}
+ /**
+ * Store screen dimensions on last close
+ */
+ protected void storeScreenDimensions() {
+ // Save the width and height of the screen
+ Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
+ PreferencesData.setInteger("last.screen.width", screen.width);
+ PreferencesData.setInteger("last.screen.height", screen.height);
+ }
/**
* Store list of sketches that are currently open.
* Called when the application is quitting and documents are still open.
*/
protected void storeSketches() {
- // Save the width and height of the screen
- Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
- Preferences.setInteger("last.screen.width", screen.width);
- Preferences.setInteger("last.screen.height", screen.height);
- String untitledPath = untitledFolder.getAbsolutePath();
+ // If there is only one sketch opened save his position as default
+ if (editors.size() == 1) {
+ storeSketchLocation(editors.get(0), ".default");
+ }
// Save the sketch path and window placement for each open sketch
+ String untitledPath = untitledFolder.getAbsolutePath();
+ List reversedEditors = new LinkedList<>(editors);
+ Collections.reverse(reversedEditors);
int index = 0;
- for (Editor editor : editors) {
- String path = editor.getSketch().getMainFilePath();
- // In case of a crash, save untitled sketches if they contain changes.
- // (Added this for release 0158, may not be a good idea.)
- if (path.startsWith(untitledPath) &&
- !editor.getSketch().isModified()) {
+ for (Editor editor : reversedEditors) {
+ Sketch sketch = editor.getSketch();
+ String path = sketch.getMainFilePath();
+ // Skip untitled sketches if they do not contains changes.
+ if (path.startsWith(untitledPath) && !sketch.isModified()) {
continue;
}
- Preferences.set("last.sketch" + index + ".path", path);
-
- int[] location = editor.getPlacement();
- String locationStr = PApplet.join(PApplet.str(location), ",");
- Preferences.set("last.sketch" + index + ".location", locationStr);
+ storeSketchLocation(editor, "" + index);
index++;
}
- Preferences.setInteger("last.sketch.count", index);
+ PreferencesData.setInteger("last.sketch.count", index);
}
-
- // If a sketch is untitled on quit, may need to store the new name
- // rather than the location from the temp folder.
- protected void storeSketchPath(Editor editor, int index) {
+ private void storeSketchLocation(Editor editor, String index) {
String path = editor.getSketch().getMainFilePath();
- String untitledPath = untitledFolder.getAbsolutePath();
- if (path.startsWith(untitledPath)) {
- path = "";
- }
- Preferences.set("last.sketch" + index + ".path", path);
+ String loc = StringUtils.join(editor.getPlacement(), ',');
+ PreferencesData.set("last.sketch" + index + ".path", path);
+ PreferencesData.set("last.sketch" + index + ".location", loc);
}
+ private int[] retrieveSketchLocation(String index) {
+ if (PreferencesData.get("last.screen.height") == null)
+ return defaultEditorLocation();
- /*
- public void storeSketch(Editor editor) {
- int index = -1;
- for (int i = 0; i < editorCount; i++) {
- if (editors[i] == editor) {
- index = i;
- break;
- }
- }
- if (index == -1) {
- System.err.println("Problem storing sketch " + editor.sketch.name);
- } else {
- String path = editor.sketch.getMainFilePath();
- Preferences.set("last.sketch" + index + ".path", path);
- }
+ // if screen size has changed, the window coordinates no longer
+ // make sense, so don't use them unless they're identical
+ Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
+ int screenW = PreferencesData.getInteger("last.screen.width");
+ int screenH = PreferencesData.getInteger("last.screen.height");
+
+ if ((screen.width != screenW) || (screen.height != screenH))
+ return defaultEditorLocation();
+
+ String locationStr = PreferencesData
+ .get("last.sketch" + index + ".location");
+ if (locationStr == null)
+ return defaultEditorLocation();
+
+ int location[] = PApplet.parseInt(PApplet.split(locationStr, ','));
+ if (location[0] > screen.width || location[1] > screen.height)
+ return defaultEditorLocation();
+
+ return location;
}
- */
+ protected void storeRecentSketches(SketchController sketch) {
+ if (sketch.isUntitled()) {
+ return;
+ }
- // .................................................................
+ Set sketches = new LinkedHashSet<>();
+ sketches.add(sketch.getSketch().getMainFilePath());
+ sketches.addAll(PreferencesData.getCollection("recent.sketches"));
+
+ PreferencesData.setCollection("recent.sketches", sketches);
+ }
+ protected void removeRecentSketchPath(String path) {
+ Collection sketches = new LinkedList<>(PreferencesData.getCollection("recent.sketches"));
+ sketches.remove(path);
+ PreferencesData.setCollection("recent.sketches", sketches);
+ }
// Because of variations in native windowing systems, no guarantees about
// changes to the focused and active Windows can be made. Developers must
@@ -438,52 +651,60 @@ public void storeSketch(Editor editor) {
// Window receives a WINDOW_GAINED_FOCUS or WINDOW_ACTIVATED event.
protected void handleActivated(Editor whichEditor) {
activeEditor = whichEditor;
-
- // set the current window to be the console that's getting output
- EditorConsole.setEditor(activeEditor);
+ activeEditor.rebuildRecentSketchesMenu();
+ if (PreferencesData.getBoolean("editor.external")) {
+ try {
+ // If the list of files on disk changed, recreate the tabs for them
+ if (activeEditor.getSketch().reload())
+ activeEditor.createTabs();
+ else // Let the current tab know it was activated, so it can reload
+ activeEditor.getCurrentTab().activated();
+ } catch (IOException e) {
+ System.err.println(e);
+ }
+ }
}
+ protected int[] defaultEditorLocation() {
+ int defaultWidth = PreferencesData.getInteger("editor.window.width.default");
+ int defaultHeight = PreferencesData.getInteger("editor.window.height.default");
+ Rectangle screen = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration().getBounds();
+ return new int[]{
+ (screen.width - defaultWidth) / 2,
+ (screen.height - defaultHeight) / 2,
+ defaultWidth, defaultHeight, 0
+ };
+ }
protected int[] nextEditorLocation() {
- Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
- int defaultWidth = Preferences.getInteger("editor.window.width.default");
- int defaultHeight = Preferences.getInteger("editor.window.height.default");
-
if (activeEditor == null) {
// If no current active editor, use default placement
- return new int[] {
- (screen.width - defaultWidth) / 2,
- (screen.height - defaultHeight) / 2,
- defaultWidth, defaultHeight, 0
- };
+ return defaultEditorLocation();
+ }
- } else {
- // With a currently active editor, open the new window
- // using the same dimensions, but offset slightly.
- synchronized (editors) {
- final int OVER = 50;
- // In release 0160, don't
- //location = activeEditor.getPlacement();
- Editor lastOpened = editors.get(editors.size() - 1);
- int[] location = lastOpened.getPlacement();
- // Just in case the bounds for that window are bad
- location[0] += OVER;
- location[1] += OVER;
-
- if (location[0] == OVER ||
- location[2] == OVER ||
- location[0] + location[2] > screen.width ||
- location[1] + location[3] > screen.height) {
- // Warp the next window to a randomish location on screen.
- return new int[] {
- (int) (Math.random() * (screen.width - defaultWidth)),
- (int) (Math.random() * (screen.height - defaultHeight)),
- defaultWidth, defaultHeight, 0
- };
- }
+ Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
- return location;
+ // With a currently active editor, open the new window
+ // using the same dimensions, but offset slightly.
+ synchronized (editors) {
+ int[] location = activeEditor.getPlacement();
+
+ // Just in case the bounds for that window are bad
+ final int OVER = 50;
+ location[0] += OVER;
+ location[1] += OVER;
+
+ if (location[0] == OVER || location[2] == OVER
+ || location[0] + location[2] > screen.width
+ || location[1] + location[3] > screen.height) {
+ // Warp the next window to a randomish location on screen.
+ int[] l = defaultEditorLocation();
+ l[0] *= Math.random() * 2;
+ l[1] *= Math.random() * 2;
+ return l;
}
+
+ return location;
}
}
@@ -493,23 +714,17 @@ protected int[] nextEditorLocation() {
boolean breakTime = false;
String[] months = {
- "jan", "feb", "mar", "apr", "may", "jun",
- "jul", "aug", "sep", "oct", "nov", "dec"
+ "jan", "feb", "mar", "apr", "may", "jun",
+ "jul", "aug", "sep", "oct", "nov", "dec"
};
- /**
- * Handle creating a sketch folder, return its base .pde file
- * or null if the operation was canceled.
- * @param shift whether shift is pressed, which will invert prompt setting
- * @param noPrompt disable prompt, no matter the setting
- */
- protected String createNewUntitled() throws IOException {
+ protected File createNewUntitled() throws IOException {
File newbieDir = null;
String newbieName = null;
// In 0126, untitled sketches will begin in the temp folder,
// and then moved to a new location because Save will default to Save As.
- File sketchbookDir = getSketchbookFolder();
+ File sketchbookDir = BaseNoGui.getSketchbookFolder();
File newbieParentDir = untitledFolder;
// Use a generic name like sketch_031008a, the date plus a char
@@ -521,21 +736,30 @@ protected String createNewUntitled() throws IOException {
int day = cal.get(Calendar.DAY_OF_MONTH); // 1..31
int month = cal.get(Calendar.MONTH); // 0..11
String purty = months[month] + PApplet.nf(day, 2);
+
do {
- if (index == 26) {
- // In 0159, avoid running past z by sending people outdoors.
+ if (index == 26*26) {
+ // In 0166, avoid running past zz by sending people outdoors.
if (!breakTime) {
- Base.showWarning("Time for a Break",
- "You've reached the limit for auto naming of new sketches\n" +
- "for the day. How about going for a walk instead?", null);
+ showWarning(tr("Time for a Break"),
+ tr("You've reached the limit for auto naming of new sketches\n" +
+ "for the day. How about going for a walk instead?"), null);
breakTime = true;
} else {
- Base.showWarning("Sunshine",
- "No really, time for some fresh air for you.", null);
+ showWarning(tr("Sunshine"),
+ tr("No really, time for some fresh air for you."), null);
}
return null;
}
- newbieName = "sketch_" + purty + ((char) ('a' + index));
+
+ int multiples = index / 26;
+
+ if(multiples > 0){
+ newbieName = ((char) ('a' + (multiples-1))) + "" + ((char) ('a' + (index % 26))) + "";
+ }else{
+ newbieName = ((char) ('a' + index)) + "";
+ }
+ newbieName = "sketch_" + purty + newbieName;
newbieDir = new File(newbieParentDir, newbieName);
index++;
// Make sure it's not in the temp folder *and* it's not in the sketchbook
@@ -545,21 +769,38 @@ protected String createNewUntitled() throws IOException {
newbieDir.mkdirs();
// Make an empty pde file
- File newbieFile = new File(newbieDir, newbieName + ".pde");
- new FileOutputStream(newbieFile); // create the file
- return newbieFile.getAbsolutePath();
+ File newbieFile = new File(newbieDir, newbieName + ".ino");
+ if (!newbieFile.createNewFile()) {
+ throw new IOException();
+ }
+
+ // Initialize the pde file with the BareMinimum sketch.
+ // Apply user-defined tab settings.
+ String sketch = FileUtils.readFileToString(
+ new File(getContentFile("examples"), "01.Basics" + File.separator
+ + "BareMinimum" + File.separator + "BareMinimum.ino"));
+ String currentTab = " ";
+ String newTab = (PreferencesData.getBoolean("editor.tabs.expand")
+ ? StringUtils.repeat(" ",
+ PreferencesData.getInteger("editor.tabs.size"))
+ : "\t");
+ sketch = sketch.replaceAll(
+ "(?<=(^|\n)(" + currentTab + "){0,50})" + currentTab, newTab);
+ FileUtils.writeStringToFile(newbieFile, sketch);
+ return newbieFile;
}
/**
* Create a new untitled document in a new sketch window.
+ *
+ * @throws Exception
*/
- public void handleNew() {
+ public void handleNew() throws Exception {
try {
- String path = createNewUntitled();
- if (path != null) {
- Editor editor = handleOpen(path);
- editor.untitled = true;
+ File file = createNewUntitled();
+ if (file != null) {
+ handleOpen(file, true);
}
} catch (IOException e) {
@@ -570,76 +811,27 @@ public void handleNew() {
}
- /**
- * Replace the sketch in the current window with a new untitled document.
- */
- public void handleNewReplace() {
- if (!activeEditor.checkModified()) {
- return; // sketch was modified, and user canceled
- }
- // Close the running window, avoid window boogers with multiple sketches
- activeEditor.internalCloseRunner();
-
- // Actually replace things
- handleNewReplaceImpl();
- }
-
-
- protected void handleNewReplaceImpl() {
- try {
- String path = createNewUntitled();
- if (path != null) {
- activeEditor.handleOpenInternal(path);
- activeEditor.untitled = true;
- }
-// return true;
-
- } catch (IOException e) {
- activeEditor.statusError(e);
-// return false;
- }
- }
-
-
- /**
- * Open a sketch, replacing the sketch in the current window.
- * @param path Location of the primary pde file for the sketch.
- */
- public void handleOpenReplace(String path) {
- if (!activeEditor.checkModified()) {
- return; // sketch was modified, and user canceled
- }
- // Close the running window, avoid window boogers with multiple sketches
- activeEditor.internalCloseRunner();
-
- boolean loaded = activeEditor.handleOpenInternal(path);
- if (!loaded) {
- // replace the document without checking if that's ok
- handleNewReplaceImpl();
- }
- }
-
-
/**
* Prompt for a sketch to open, and open it in a new window.
+ *
+ * @throws Exception
*/
- public void handleOpenPrompt() {
+ public void handleOpenPrompt() throws Exception {
// get the frontmost window frame for placing file dialog
- FileDialog fd = new FileDialog(activeEditor,
- "Open an Arduino sketch...",
- FileDialog.LOAD);
- // This was annoying people, so disabled it in 0125.
- //fd.setDirectory(Preferences.get("sketchbook.path"));
- //fd.setDirectory(getSketchbookPath());
+ FileDialog fd = new FileDialog(activeEditor, tr("Open an Arduino sketch..."), FileDialog.LOAD);
+ File lastFolder = new File(PreferencesData.get("last.folder", BaseNoGui.getSketchbookFolder().getAbsolutePath()));
+ if (lastFolder.exists() && lastFolder.isFile()) {
+ lastFolder = lastFolder.getParentFile();
+ }
+ fd.setDirectory(lastFolder.getAbsolutePath());
// Only show .pde files as eligible bachelors
fd.setFilenameFilter(new FilenameFilter() {
- public boolean accept(File dir, String name) {
- // TODO this doesn't seem to ever be used. AWESOME.
- //System.out.println("check filter on " + dir + " " + name);
- return name.toLowerCase().endsWith(".pde");
- }
- });
+ public boolean accept(File dir, String name) {
+ return name.toLowerCase().endsWith(".ino")
+ || name.toLowerCase().endsWith(".pde");
+ }
+ });
fd.setVisible(true);
@@ -650,168 +842,131 @@ public boolean accept(File dir, String name) {
if (filename == null) return;
File inputFile = new File(directory, filename);
- handleOpen(inputFile.getAbsolutePath());
+
+ PreferencesData.set("last.folder", inputFile.getAbsolutePath());
+ handleOpen(inputFile);
}
/**
* Open a sketch in a new window.
- * @param path Path to the pde file for the sketch in question
+ *
+ * @param file File to open
* @return the Editor object, so that properties (like 'untitled')
- * can be set by the caller
+ * can be set by the caller
+ * @throws Exception
*/
- public Editor handleOpen(String path) {
- return handleOpen(path, nextEditorLocation());
+ public Editor handleOpen(File file) throws Exception {
+ return handleOpen(file, false);
}
+ public Editor handleOpen(File file, boolean untitled) throws Exception {
+ return handleOpen(file, nextEditorLocation(), untitled);
+ }
- protected Editor handleOpen(String path, int[] location) {
-// System.err.println("entering handleOpen " + path);
+ protected Editor handleOpen(File file, int[] location, boolean untitled) throws Exception {
+ return handleOpen(file, location, location, true, untitled);
+ }
- File file = new File(path);
+ protected Editor handleOpen(File file, int[] storedLocation, int[] defaultLocation, boolean storeOpenedSketches, boolean untitled) throws Exception {
if (!file.exists()) return null;
-// System.err.println(" editors: " + editors);
// Cycle through open windows to make sure that it's not already open.
for (Editor editor : editors) {
- if (editor.getSketch().getMainFilePath().equals(path)) {
+ if (editor.getSketch().getPrimaryFile().getFile().equals(file)) {
editor.toFront();
-// System.err.println(" handleOpen: already opened");
return editor;
}
}
- // If the active editor window is an untitled, and un-modified document,
- // just replace it with the file that's being opened.
-// if (activeEditor != null) {
-// Sketch activeSketch = activeEditor.sketch;
-// if (activeSketch.isUntitled() && !activeSketch.isModified()) {
-// // if it's an untitled, unmodified document, it can be replaced.
-// // except in cases where a second blank window is being opened.
-// if (!path.startsWith(untitledFolder.getAbsolutePath())) {
-// activeEditor.handleOpenUnchecked(path, 0, 0, 0, 0);
-// return activeEditor;
-// }
-// }
-// }
-
-// System.err.println(" creating new editor");
- Editor editor = new Editor(this, path, location);
-// Editor editor = null;
-// try {
-// editor = new Editor(this, path, location);
-// } catch (Exception e) {
-// e.printStackTrace();
-// System.err.flush();
-// System.out.flush();
-// System.exit(1);
-// }
-// System.err.println(" done creating new editor");
-// EditorConsole.systemErr.println(" done creating new editor");
+ Editor editor = new Editor(this, file, storedLocation, defaultLocation, BaseNoGui.getPlatform());
// Make sure that the sketch actually loaded
- if (editor.getSketch() == null) {
-// System.err.println("sketch was null, getting out of handleOpen");
+ if (editor.getSketchController() == null) {
return null; // Just walk away quietly
}
-// if (editors == null) {
-// editors = new Editor[5];
-// }
-// if (editorCount == editors.length) {
-// editors = (Editor[]) PApplet.expand(editors);
-// }
-// editors[editorCount++] = editor;
+ editor.untitled = untitled;
+
editors.add(editor);
-// if (markedForClose != null) {
-// Point p = markedForClose.getLocation();
-// handleClose(markedForClose, false);
-// // open the new window in
-// editor.setLocation(p);
-// }
+ if (storeOpenedSketches) {
+ // Store information on who's open and running
+ // (in case there's a crash or something that can't be recovered)
+ storeSketches();
+ storeRecentSketches(editor.getSketchController());
+ rebuildRecentSketchesMenuItems();
+ PreferencesData.save();
+ }
// now that we're ready, show the window
// (don't do earlier, cuz we might move it based on a window being closed)
- editor.setVisible(true);
-
-// System.err.println("exiting handleOpen");
+ SwingUtilities.invokeLater(() -> editor.setVisible(true));
return editor;
}
+ protected void rebuildRecentSketchesMenuItems() {
+ Set recentSketches = new LinkedHashSet() {
+
+ @Override
+ public boolean add(File file) {
+ if (size() >= RECENT_SKETCHES_MAX_SIZE) {
+ return false;
+ }
+ return super.add(file);
+ }
+ };
+
+ for (String path : PreferencesData.getCollection("recent.sketches")) {
+ File file = new File(path);
+ if (file.exists()) {
+ recentSketches.add(file);
+ }
+ }
+
+ recentSketchesMenuItems.clear();
+ for (final File recentSketch : recentSketches) {
+ JMenuItem recentSketchMenuItem = new JMenuItem(recentSketch.getParentFile().getName());
+ recentSketchMenuItem.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent actionEvent) {
+ try {
+ handleOpen(recentSketch);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ });
+ recentSketchesMenuItems.add(recentSketchMenuItem);
+ }
+ }
+
/**
* Close a sketch as specified by its editor window.
+ *
* @param editor Editor object of the sketch to be closed.
* @return true if succeeded in closing, false if canceled.
*/
public boolean handleClose(Editor editor) {
- // Check if modified
-// boolean immediate = editors.size() == 1;
- if (!editor.checkModified()) {
- return false;
- }
-
- // Close the running window, avoid window boogers with multiple sketches
- editor.internalCloseRunner();
if (editors.size() == 1) {
- // For 0158, when closing the last window /and/ it was already an
- // untitled sketch, just give up and let the user quit.
-// if (Preferences.getBoolean("sketchbook.closing_last_window_quits") ||
-// (editor.untitled && !editor.getSketch().isModified())) {
- if (Base.isMacOS()) {
- Object[] options = { "OK", "Cancel" };
- String prompt =
- " " +
- " " +
- "Are you sure you want to Quit?" +
- "Closing the last open sketch will quit Arduino.";
-
- int result = JOptionPane.showOptionDialog(editor,
- prompt,
- "Quit",
- JOptionPane.YES_NO_OPTION,
- JOptionPane.QUESTION_MESSAGE,
- null,
- options,
- options[0]);
- if (result == JOptionPane.NO_OPTION ||
- result == JOptionPane.CLOSED_OPTION) {
- return false;
- }
+ if (!handleQuit()) {
+ return false;
}
-
- // This will store the sketch count as zero
+ // Everything called after handleQuit will only affect OSX
+ editor.setVisible(false);
editors.remove(editor);
- Editor.serialMonitor.closeSerialPort();
- storeSketches();
-
- // Save out the current prefs state
- Preferences.save();
-
- // Since this wasn't an actual Quit event, call System.exit()
- System.exit(0);
-
} else {
// More than one editor window open,
// proceed with closing the current window.
+ // Check if modified
+ if (!editor.checkModified()) {
+ return false;
+ }
editor.setVisible(false);
editor.dispose();
-// for (int i = 0; i < editorCount; i++) {
-// if (editor == editors[i]) {
-// for (int j = i; j < editorCount-1; j++) {
-// editors[j] = editors[j+1];
-// }
-// editorCount--;
-// // Set to null so that garbage collection occurs
-// editors[editorCount] = null;
-// }
-// }
editors.remove(editor);
}
return true;
@@ -820,23 +975,33 @@ public boolean handleClose(Editor editor) {
/**
* Handler for File → Quit.
+ *
* @return false if canceled, true otherwise.
*/
public boolean handleQuit() {
// If quit is canceled, this will be replaced anyway
// by a later handleQuit() that is not canceled.
+ storeScreenDimensions();
storeSketches();
- Editor.serialMonitor.closeSerialPort();
+ try {
+ Editor.serialMonitor.close();
+ } catch (Exception e) {
+ // ignore
+ }
+
+ // kill uploader (if still alive)
+ UploaderUtils uploaderInstance = new UploaderUtils();
+ Uploader uploader = uploaderInstance.getUploaderByPreferences(false);
+ if (uploader != null && Uploader.programmerPid != null && Uploader.programmerPid.isAlive()) {
+ // kill the stuck programmer
+ Uploader.programmerPid.destroyForcibly();
+ }
if (handleQuitEach()) {
- // make sure running sketches close before quitting
- for (Editor editor : editors) {
- editor.internalCloseRunner();
- }
// Save out the current prefs state
- Preferences.save();
+ PreferencesData.save();
- if (!Base.isMacOS()) {
+ if (!OSUtils.isMacOS()) {
// If this was fired from the menu or an AppleEvent (the Finder),
// then Mac OS X will send the terminate signal itself.
System.exit(0);
@@ -849,17 +1014,12 @@ public boolean handleQuit() {
/**
* Attempt to close each open sketch in preparation for quitting.
+ *
* @return false if canceled along the way
*/
protected boolean handleQuitEach() {
- int index = 0;
for (Editor editor : editors) {
- if (editor.checkModified()) {
- // Update to the new/final sketch path for this fella
- storeSketchPath(editor, index);
- index++;
-
- } else {
+ if (!editor.checkModified()) {
return false;
}
}
@@ -874,7 +1034,7 @@ protected boolean handleQuitEach() {
* Asynchronous version of menu rebuild to be used on save and rename
* to prevent the interface from locking up until the menus are done.
*/
- protected void rebuildSketchbookMenus() {
+ public void rebuildSketchbookMenus() {
//System.out.println("async enter");
//new Exception().printStackTrace();
SwingUtilities.invokeLater(new Runnable() {
@@ -893,676 +1053,972 @@ protected void rebuildToolbarMenu(JMenu menu) {
JMenuItem item;
menu.removeAll();
- //System.out.println("rebuilding toolbar menu");
// Add the single "Open" item
- item = Editor.newJMenuItem("Open...", 'O');
+ item = Editor.newJMenuItem(tr("Open..."), 'O');
item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
+ public void actionPerformed(ActionEvent e) {
+ try {
handleOpenPrompt();
+ } catch (Exception e1) {
+ e1.printStackTrace();
}
- });
+ }
+ });
menu.add(item);
menu.addSeparator();
// Add a list of all sketches and subfolders
- try {
- boolean sketches = addSketches(menu, getSketchbookFolder(), true);
- //boolean sketches = addSketches(menu, getSketchbookFolder());
- if (sketches) menu.addSeparator();
- } catch (IOException e) {
- e.printStackTrace();
- }
+ boolean sketches = addSketches(menu, BaseNoGui.getSketchbookFolder());
+ if (sketches) menu.addSeparator();
- //System.out.println("rebuilding examples menu");
// Add each of the subfolders of examples directly to the menu
- try {
- boolean found = addSketches(menu, examplesFolder, true);
- if (found) menu.addSeparator();
- found = addSketches(menu, getSketchbookLibrariesFolder(), true);
- if (found) menu.addSeparator();
- addSketches(menu, librariesFolder, true);
- } catch (IOException e) {
- e.printStackTrace();
- }
+ boolean found = addSketches(menu, BaseNoGui.getExamplesFolder());
+ if (found) menu.addSeparator();
}
protected void rebuildSketchbookMenu(JMenu menu) {
- //System.out.println("rebuilding sketchbook menu");
- //new Exception().printStackTrace();
- try {
- menu.removeAll();
- addSketches(menu, getSketchbookFolder(), false);
- //addSketches(menu, getSketchbookFolder());
- } catch (IOException e) {
- e.printStackTrace();
+ menu.removeAll();
+ addSketches(menu, BaseNoGui.getSketchbookFolder());
+
+ JMenu librariesMenu = JMenuUtils.findSubMenuWithLabel(menu, "libraries");
+ if (librariesMenu != null) {
+ menu.remove(librariesMenu);
+ }
+ JMenu hardwareMenu = JMenuUtils.findSubMenuWithLabel(menu, "hardware");
+ if (hardwareMenu != null) {
+ menu.remove(hardwareMenu);
}
}
+ private LibraryList getSortedLibraries() {
+ LibraryList installedLibraries = BaseNoGui.librariesIndexer.getInstalledLibraries();
+ Collections.sort(installedLibraries, new LibraryOfSameTypeComparator());
+ return installedLibraries;
+ }
public void rebuildImportMenu(JMenu importMenu) {
- //System.out.println("rebuilding import menu");
+ if (importMenu == null)
+ return;
importMenu.removeAll();
- // reset the set of libraries
- libraries = new HashSet();
+ JMenuItem menu = new JMenuItem(tr("Manage Libraries..."));
+ // Ctrl+Shift+I on Windows and Linux, Command+Shift+I on macOS
+ menu.setAccelerator(KeyStroke.getKeyStroke('I',
+ Toolkit.getDefaultToolkit().getMenuShortcutKeyMask() |
+ ActionEvent.SHIFT_MASK));
+ menu.addActionListener(e -> openLibraryManager("", ""));
+ importMenu.add(menu);
+ importMenu.addSeparator();
+
+ JMenuItem addLibraryMenuItem = new JMenuItem(tr("Add .ZIP Library..."));
+ addLibraryMenuItem.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ Base.this.handleAddLibrary();
+ BaseNoGui.librariesIndexer.rescanLibraries();
+ Base.this.onBoardOrPortChange();
+ Base.this.rebuildImportMenu(Editor.importMenu);
+ Base.this.rebuildExamplesMenu(Editor.examplesMenu);
+ }
+ });
+ importMenu.add(addLibraryMenuItem);
+ importMenu.addSeparator();
+
+ // Split between user supplied libraries and IDE libraries
+ TargetPlatform targetPlatform = BaseNoGui.getTargetPlatform();
+
+ if (targetPlatform != null) {
+ LibraryList libs = getSortedLibraries();
+ String lastLibType = null;
+ for (UserLibrary lib : libs) {
+ String libType = lib.getTypes().get(0);
+ if (!libType.equals(lastLibType)) {
+ if (lastLibType != null) {
+ importMenu.addSeparator();
+ }
+ lastLibType = libType;
+ JMenuItem platformItem = new JMenuItem(format(tr("{0} libraries"), tr(lastLibType)));
+ platformItem.setEnabled(false);
+ importMenu.add(platformItem);
+ }
- // reset the table mapping imports to libraries
- importToLibraryTable = new HashMap();
+ AbstractAction action = new AbstractAction(lib.getName()) {
+ public void actionPerformed(ActionEvent event) {
+ UserLibrary l = (UserLibrary) getValue("library");
+ try {
+ activeEditor.getSketchController().importLibrary(l);
+ } catch (IOException e) {
+ showWarning(tr("Error"), format("Unable to list header files in {0}", l.getSrcFolder()), e);
+ }
+ }
+ };
+ action.putValue("library", lib);
- // Add from the "libraries" subfolder in the Processing directory
- try {
- addLibraries(importMenu, librariesFolder);
- } catch (IOException e) {
- e.printStackTrace();
- }
- // Add libraries found in the sketchbook folder
- int separatorIndex = importMenu.getItemCount();
- try {
- File sketchbookLibraries = getSketchbookLibrariesFolder();
- boolean found = addLibraries(importMenu, sketchbookLibraries);
- if (found) {
- JMenuItem contrib = new JMenuItem("Contributed");
- contrib.setEnabled(false);
- importMenu.insert(contrib, separatorIndex);
- importMenu.insertSeparator(separatorIndex);
+ // Add new element at the bottom
+ JMenuItem item = new JMenuItem(action);
+ item.putClientProperty("library", lib);
+ importMenu.add(item);
}
- } catch (IOException e) {
- e.printStackTrace();
}
}
-
public void rebuildExamplesMenu(JMenu menu) {
- //System.out.println("rebuilding examples menu");
- try {
- menu.removeAll();
- boolean found = addSketches(menu, examplesFolder, false);
- if (found) menu.addSeparator();
- found = addSketches(menu, getSketchbookLibrariesFolder(), false);
- if (found) menu.addSeparator();
- addSketches(menu, librariesFolder, false);
- } catch (IOException e) {
- e.printStackTrace();
+ if (menu == null) {
+ return;
}
- }
-
-
- public void rebuildBoardsMenu(JMenu menu) {
- //System.out.println("rebuilding boards menu");
- menu.removeAll();
- ButtonGroup group = new ButtonGroup();
- for (Target target : targetsTable.values()) {
- for (String board : target.getBoards().keySet()) {
- AbstractAction action =
- new AbstractAction(target.getBoards().get(board).get("name")) {
- public void actionPerformed(ActionEvent actionevent) {
- //System.out.println("Switching to " + target + ":" + board);
- Preferences.set("target", (String) getValue("target"));
- Preferences.set("board", (String) getValue("board"));
- }
- };
- action.putValue("target", target.getName());
- action.putValue("board", board);
- JMenuItem item = new JRadioButtonMenuItem(action);
- if (target.getName().equals(Preferences.get("target")) &&
- board.equals(Preferences.get("board"))) {
- item.setSelected(true);
+
+ menu.removeAll();
+
+ // Add examples from distribution "example" folder
+ JMenuItem label = new JMenuItem(tr("Built-in Examples"));
+ label.setEnabled(false);
+ menu.add(label);
+ boolean found = addSketches(menu, BaseNoGui.getExamplesFolder());
+ if (found) {
+ menu.addSeparator();
+ }
+
+ // Libraries can come from 4 locations: collect info about all four
+ String boardId = null;
+ String referencedPlatformName = null;
+ String myArch = null;
+ TargetPlatform targetPlatform = BaseNoGui.getTargetPlatform();
+ if (targetPlatform != null) {
+ myArch = targetPlatform.getId();
+ boardId = BaseNoGui.getTargetBoard().getName();
+ String core = BaseNoGui.getBoardPreferences().get("build.core", "arduino");
+ if (core.contains(":")) {
+ String refcore = core.split(":")[0];
+ TargetPlatform referencedPlatform = BaseNoGui.getTargetPlatform(refcore, myArch);
+ if (referencedPlatform != null) {
+ referencedPlatformName = referencedPlatform.getPreferences().get("name");
}
- group.add(item);
- menu.add(item);
- }
- }
- }
-
-
- public void rebuildBurnBootloaderMenu(JMenu menu) {
- //System.out.println("rebuilding burn bootloader menu");
- menu.removeAll();
- for (Target target : targetsTable.values()) {
- for (String programmer : target.getProgrammers().keySet()) {
- AbstractAction action =
- new AbstractAction(
- "w/ " + target.getProgrammers().get(programmer).get("name")) {
- public void actionPerformed(ActionEvent actionevent) {
- activeEditor.handleBurnBootloader((String) getValue("target"),
- (String) getValue("programmer"));
- }
- };
- action.putValue("target", target.getName());
- action.putValue("programmer", programmer);
- JMenuItem item = new JMenuItem(action);
- menu.add(item);
}
}
- }
-
-
- /**
- * Scan a folder recursively, and add any sketches found to the menu
- * specified. Set the openReplaces parameter to true when opening the sketch
- * should replace the sketch in the current window, or false when the
- * sketch should open in a new window.
- */
- protected boolean addSketches(JMenu menu, File folder,
- final boolean replaceExisting) throws IOException {
- // skip .DS_Store files, etc (this shouldn't actually be necessary)
- if (!folder.isDirectory()) return false;
-
- String[] list = folder.list();
- // If a bad folder or unreadable or whatever, this will come back null
- if (list == null) return false;
-
- // Alphabetize list, since it's not always alpha order
- Arrays.sort(list, String.CASE_INSENSITIVE_ORDER);
- //processing.core.PApplet.println("adding sketches " + folder.getAbsolutePath());
- //PApplet.println(list);
- ActionListener listener = new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- String path = e.getActionCommand();
- if (new File(path).exists()) {
- boolean replace = replaceExisting;
- if ((e.getModifiers() & ActionEvent.SHIFT_MASK) != 0) {
- replace = !replace;
- }
- if (replace) {
- handleOpenReplace(path);
- } else {
- handleOpen(path);
- }
+ // Divide the libraries into 7 lists, corresponding to the 4 locations
+ // with the retired IDE libs further divided into their own list, and
+ // any incompatible sketchbook libs further divided into their own list.
+ // The 7th list of "other" libraries should always be empty, but serves
+ // as a safety feature to prevent any library from vanishing.
+ LibraryList allLibraries = BaseNoGui.librariesIndexer.getInstalledLibraries();
+ LibraryList ideLibs = new LibraryList();
+ LibraryList retiredIdeLibs = new LibraryList();
+ LibraryList platformLibs = new LibraryList();
+ LibraryList referencedPlatformLibs = new LibraryList();
+ LibraryList sketchbookLibs = new LibraryList();
+ LibraryList sketchbookIncompatibleLibs = new LibraryList();
+ LibraryList otherLibs = new LibraryList();
+ for (UserLibrary lib : allLibraries) {
+ // Get the library's location - used for sorting into categories
+ Location location = lib.getLocation();
+ // Is this library compatible?
+ List arch = lib.getArchitectures();
+ boolean compatible;
+ if (myArch == null || arch == null || arch.contains("*")) {
+ compatible = true;
+ } else {
+ compatible = arch.contains(myArch);
+ }
+ // IDE Libaries (including retired)
+ if (location == Location.IDE_BUILTIN) {
+ if (compatible) {
+ // only compatible IDE libs are shown
+ if (lib.getTypes().contains("Retired")) {
+ retiredIdeLibs.add(lib);
} else {
- showWarning("Sketch Does Not Exist",
- "The selected sketch no longer exists.\n" +
- "You may need to restart Arduino to update\n" +
- "the sketchbook menu.", null);
+ ideLibs.add(lib);
}
}
- };
- // offers no speed improvement
- //menu.addActionListener(listener);
-
- boolean ifound = false;
-
- for (int i = 0; i < list.length; i++) {
- if ((list[i].charAt(0) == '.') ||
- list[i].equals("CVS")) continue;
-
- File subfolder = new File(folder, list[i]);
- if (!subfolder.isDirectory()) continue;
-
- File entry = new File(subfolder, list[i] + ".pde");
- // if a .pde file of the same prefix as the folder exists..
- if (entry.exists()) {
- //String sanityCheck = sanitizedName(list[i]);
- //if (!sanityCheck.equals(list[i])) {
- if (!Sketch.isSanitaryName(list[i])) {
- if (!builtOnce) {
- String complaining =
- "The sketch \"" + list[i] + "\" cannot be used.\n" +
- "Sketch names must contain only basic letters and numbers\n" +
- "(ASCII-only with no spaces, " +
- "and it cannot start with a number).\n" +
- "To get rid of this message, remove the sketch from\n" +
- entry.getAbsolutePath();
- Base.showMessage("Ignoring sketch with bad name", complaining);
+ // Platform Libraries
+ } else if (location == Location.CORE) {
+ // all platform libs are assumed to be compatible
+ platformLibs.add(lib);
+ // Referenced Platform Libraries
+ } else if (location == Location.REFERENCED_CORE) {
+ // all referenced platform libs are assumed to be compatible
+ referencedPlatformLibs.add(lib);
+ // Sketchbook Libraries (including incompatible)
+ } else if (location == Location.SKETCHBOOK) {
+ if (compatible) {
+ // libraries promoted from sketchbook (behave as builtin)
+ if (!lib.getTypes().isEmpty() && lib.getTypes().contains("Arduino")
+ && lib.getArchitectures().contains("*")) {
+ ideLibs.add(lib);
+ } else {
+ sketchbookLibs.add(lib);
}
- continue;
- }
-
- JMenuItem item = new JMenuItem(list[i]);
- item.addActionListener(listener);
- item.setActionCommand(entry.getAbsolutePath());
- menu.add(item);
- ifound = true;
-
- } else {
- // don't create an extra menu level for a folder named "examples"
- if (subfolder.getName().equals("examples")) {
- boolean found = addSketches(menu, subfolder, replaceExisting);
- if (found) ifound = true;
} else {
- // not a sketch folder, but maybe a subfolder containing sketches
- JMenu submenu = new JMenu(list[i]);
- // needs to be separate var
- // otherwise would set ifound to false
- boolean found = addSketches(submenu, subfolder, replaceExisting);
- //boolean found = addSketches(submenu, subfolder); //, false);
- if (found) {
- menu.add(submenu);
- ifound = true;
+ sketchbookIncompatibleLibs.add(lib);
}
+ // Other libraries of unknown type (should never occur)
+ } else {
+ otherLibs.add(lib);
}
}
- }
- return ifound; // actually ignored, but..
- }
+ // Add examples from libraries
+ if (!ideLibs.isEmpty()) {
+ ideLibs.sort();
+ label = new JMenuItem(tr("Examples for any board"));
+ label.setEnabled(false);
+ menu.add(label);
+ }
+ for (UserLibrary lib : ideLibs) {
+ addSketchesSubmenu(menu, lib);
+ }
- protected boolean addLibraries(JMenu menu, File folder) throws IOException {
- if (!folder.isDirectory()) return false;
+ if (!retiredIdeLibs.isEmpty()) {
+ retiredIdeLibs.sort();
+ JMenu retired = new JMenu(tr("RETIRED"));
+ menu.add(retired);
+ for (UserLibrary lib : retiredIdeLibs) {
+ addSketchesSubmenu(retired, lib);
+ }
+ }
- String list[] = folder.list(new FilenameFilter() {
- public boolean accept(File dir, String name) {
- // skip .DS_Store files, .svn folders, etc
- if (name.charAt(0) == '.') return false;
- if (name.equals("CVS")) return false;
- return (new File(dir, name).isDirectory());
+ if (!platformLibs.isEmpty()) {
+ menu.addSeparator();
+ platformLibs.sort();
+ label = new JMenuItem(format(tr("Examples for {0}"), boardId));
+ label.setEnabled(false);
+ menu.add(label);
+ for (UserLibrary lib : platformLibs) {
+ addSketchesSubmenu(menu, lib);
}
- });
- // if a bad folder or something like that, this might come back null
- if (list == null) return false;
+ }
- // alphabetize list, since it's not always alpha order
- // replaced hella slow bubble sort with this feller for 0093
- Arrays.sort(list, String.CASE_INSENSITIVE_ORDER);
+ if (!referencedPlatformLibs.isEmpty()) {
+ menu.addSeparator();
+ referencedPlatformLibs.sort();
+ label = new JMenuItem(format(tr("Examples for {0}"), referencedPlatformName));
+ label.setEnabled(false);
+ menu.add(label);
+ for (UserLibrary lib : referencedPlatformLibs) {
+ addSketchesSubmenu(menu, lib);
+ }
+ }
- ActionListener listener = new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- activeEditor.getSketch().importLibrary(e.getActionCommand());
- }
- };
+ if (!sketchbookLibs.isEmpty()) {
+ menu.addSeparator();
+ sketchbookLibs.sort();
+ label = new JMenuItem(tr("Examples from Custom Libraries"));
+ label.setEnabled(false);
+ menu.add(label);
+ for (UserLibrary lib : sketchbookLibs) {
+ addSketchesSubmenu(menu, lib);
+ }
+ }
- boolean ifound = false;
+ if (!sketchbookIncompatibleLibs.isEmpty()) {
+ sketchbookIncompatibleLibs.sort();
+ JMenu incompatible = new JMenu(tr("INCOMPATIBLE"));
+ MenuScroller.setScrollerFor(incompatible);
+ menu.add(incompatible);
+ for (UserLibrary lib : sketchbookIncompatibleLibs) {
+ addSketchesSubmenu(incompatible, lib);
+ }
+ }
- for (String potentialName : list) {
- File subfolder = new File(folder, potentialName);
-// File libraryFolder = new File(subfolder, "library");
-// File libraryJar = new File(libraryFolder, potentialName + ".jar");
-// // If a .jar file of the same prefix as the folder exists
-// // inside the 'library' subfolder of the sketch
-// if (libraryJar.exists()) {
- String sanityCheck = Sketch.sanitizeName(potentialName);
- if (!sanityCheck.equals(potentialName)) {
- String mess =
- "The library \"" + potentialName + "\" cannot be used.\n" +
- "Library names must contain only basic letters and numbers.\n" +
- "(ASCII only and no spaces, and it cannot start with a number)";
- Base.showMessage("Ignoring bad library name", mess);
- continue;
- }
+ if (!otherLibs.isEmpty()) {
+ menu.addSeparator();
+ otherLibs.sort();
+ label = new JMenuItem(tr("Examples from Other Libraries"));
+ label.setEnabled(false);
+ menu.add(label);
+ for (UserLibrary lib : otherLibs) {
+ addSketchesSubmenu(menu, lib);
+ }
+ }
+ }
- String libraryName = potentialName;
-// // get the path for all .jar files in this code folder
-// String libraryClassPath =
-// Compiler.contentsToClassPath(libraryFolder);
-// // grab all jars and classes from this folder,
-// // and append them to the library classpath
-// librariesClassPath +=
-// File.pathSeparatorChar + libraryClassPath;
-// // need to associate each import with a library folder
-// String packages[] =
-// Compiler.packageListFromClassPath(libraryClassPath);
- libraries.add(subfolder);
- String packages[] =
- Compiler.headerListFromIncludePath(subfolder.getAbsolutePath());
- for (String pkg : packages) {
- importToLibraryTable.put(pkg, subfolder);
- }
+ private static String priorPlatformFolder;
+ private static boolean newLibraryImported;
- JMenuItem item = new JMenuItem(libraryName);
- item.addActionListener(listener);
- item.setActionCommand(subfolder.getAbsolutePath());
- menu.add(item);
- ifound = true;
+ public void onBoardOrPortChange() {
+ BaseNoGui.onBoardOrPortChange();
-// XXX: DAM: should recurse here so that library folders can be nested
-// } else { // not a library, but is still a folder, so recurse
-// JMenu submenu = new JMenu(libraryName);
-// // needs to be separate var, otherwise would set ifound to false
-// boolean found = addLibraries(submenu, subfolder);
-// if (found) {
-// menu.add(submenu);
-// ifound = true;
-// }
-// }
- }
- return ifound;
- }
-
-
- protected void loadHardware(File folder) {
- if (!folder.isDirectory()) return;
-
- String list[] = folder.list(new FilenameFilter() {
- public boolean accept(File dir, String name) {
- // skip .DS_Store files, .svn folders, etc
- if (name.charAt(0) == '.') return false;
- if (name.equals("CVS")) return false;
- return (new File(dir, name).isDirectory());
+ // reload keywords when package/platform changes
+ TargetPlatform tp = BaseNoGui.getTargetPlatform();
+ if (tp != null) {
+ String platformFolder = tp.getFolder().getAbsolutePath();
+ if (priorPlatformFolder == null || !priorPlatformFolder.equals(platformFolder) || newLibraryImported) {
+ pdeKeywords = new PdeKeywords();
+ pdeKeywords.reload();
+ priorPlatformFolder = platformFolder;
+ newLibraryImported = false;
+ for (Editor editor : editors) {
+ editor.updateKeywords(pdeKeywords);
+ }
}
- });
- // if a bad folder or something like that, this might come back null
- if (list == null) return;
+ }
- // alphabetize list, since it's not always alpha order
- // replaced hella slow bubble sort with this feller for 0093
- Arrays.sort(list, String.CASE_INSENSITIVE_ORDER);
-
- for (String target : list) {
- File subfolder = new File(folder, target);
- targetsTable.put(target, new Target(target, subfolder));
+ // Update editors status bar
+ for (Editor editor : editors) {
+ editor.onBoardOrPortChange();
}
}
+ public void openLibraryManager(final String filterText, String dropdownItem) {
+ if (contributionsSelfCheck != null) {
+ contributionsSelfCheck.cancel();
+ }
+ @SuppressWarnings("serial")
+ LibraryManagerUI managerUI = new LibraryManagerUI(activeEditor, libraryInstaller) {
+ @Override
+ protected void onIndexesUpdated() throws Exception {
+ BaseNoGui.initPackages();
+ rebuildBoardsMenu();
+ rebuildProgrammerMenu();
+ onBoardOrPortChange();
+ updateUI();
+ if (StringUtils.isNotEmpty(dropdownItem)) {
+ selectDropdownItemByClassName(dropdownItem);
+ }
+ if (StringUtils.isNotEmpty(filterText)) {
+ setFilterText(filterText);
+ }
+ }
+ };
+ managerUI.setLocationRelativeTo(activeEditor);
+ managerUI.updateUI();
+ managerUI.setVisible(true);
+ // Manager dialog is modal, waits here until closed
+
+ //handleAddLibrary();
+ newLibraryImported = true;
+ onBoardOrPortChange();
+ rebuildImportMenu(Editor.importMenu);
+ rebuildExamplesMenu(Editor.examplesMenu);
+ }
+
+ public void openBoardsManager(final String filterText, String dropdownItem) throws Exception {
+ if (contributionsSelfCheck != null) {
+ contributionsSelfCheck.cancel();
+ }
+ @SuppressWarnings("serial")
+ ContributionManagerUI managerUI = new ContributionManagerUI(activeEditor, contributionInstaller) {
+ @Override
+ protected void onIndexesUpdated() throws Exception {
+ BaseNoGui.initPackages();
+ rebuildBoardsMenu();
+ rebuildProgrammerMenu();
+ updateUI();
+ if (StringUtils.isNotEmpty(dropdownItem)) {
+ selectDropdownItemByClassName(dropdownItem);
+ }
+ if (StringUtils.isNotEmpty(filterText)) {
+ setFilterText(filterText);
+ }
+ }
+ };
+ managerUI.setLocationRelativeTo(activeEditor);
+ managerUI.updateUI();
+ managerUI.setVisible(true);
+ // Installer dialog is modal, waits here until closed
+
+ // Reload all boards (that may have been installed/updated/removed)
+ BaseNoGui.initPackages();
+ rebuildBoardsMenu();
+ rebuildProgrammerMenu();
+ onBoardOrPortChange();
+ }
+
+ public void rebuildBoardsMenu() throws Exception {
+ boardsCustomMenus = new LinkedList<>();
+
+ // The first custom menu is the "Board" selection submenu
+ JMenu boardMenu = new JMenu(tr("Board"));
+ boardMenu.putClientProperty("removeOnWindowDeactivation", true);
+ MenuScroller.setScrollerFor(boardMenu).setTopFixedCount(1);
+
+ boardMenu.add(new JMenuItem(new AbstractAction(tr("Boards Manager...")) {
+ public void actionPerformed(ActionEvent actionevent) {
+ String filterText = "";
+ String dropdownItem = "";
+ if (actionevent instanceof Event) {
+ Event e = ((Event) actionevent);
+ filterText = e.getPayload().get("filterText").toString();
+ dropdownItem = e.getPayload().get("dropdownItem").toString();
+ }
+ try {
+ openBoardsManager(filterText, dropdownItem);
+ } catch (Exception e) {
+ //TODO show error
+ e.printStackTrace();
+ }
+ }
+ }));
+ boardsCustomMenus.add(boardMenu);
+
+ // If there are no platforms installed we are done
+ if (BaseNoGui.packages.size() == 0)
+ return;
+
+ // Separate "Install boards..." command from installed boards
+ boardMenu.add(new JSeparator());
+
+ // Generate custom menus for all platforms
+ for (TargetPackage targetPackage : BaseNoGui.packages.values()) {
+ for (TargetPlatform targetPlatform : targetPackage.platforms()) {
+ for (String customMenuTitle : targetPlatform.getCustomMenus().values()) {
+ JMenu customMenu = new JMenu(tr(customMenuTitle));
+ customMenu.putClientProperty("platform", getPlatformUniqueId(targetPlatform));
+ customMenu.putClientProperty("removeOnWindowDeactivation", true);
+ boardsCustomMenus.add(customMenu);
+ MenuScroller.setScrollerFor(customMenu);
+ }
+ }
+ }
- // .................................................................
-
+ List menuItemsToClickAfterStartup = new LinkedList<>();
- /**
- * Show the About box.
- */
- public void handleAbout() {
- final Image image = Base.getLibImage("about.jpg", activeEditor);
- final Window window = new Window(activeEditor) {
- public void paint(Graphics g) {
- g.drawImage(image, 0, 0, null);
+ ButtonGroup boardsButtonGroup = new ButtonGroup();
+ Map buttonGroupsMap = new HashMap<>();
- Graphics2D g2 = (Graphics2D) g;
- g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
- RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
+ List platformMenus = new ArrayList<>();
- g.setFont(new Font("SansSerif", Font.PLAIN, 11));
- g.setColor(Color.white);
- g.drawString(Base.VERSION_NAME, 50, 30);
- }
- };
- window.addMouseListener(new MouseAdapter() {
- public void mousePressed(MouseEvent e) {
- window.dispose();
- }
- });
- int w = image.getWidth(activeEditor);
- int h = image.getHeight(activeEditor);
- Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
- window.setBounds((screen.width-w)/2, (screen.height-h)/2, w, h);
- window.setVisible(true);
- }
+ // Cycle through all packages
+ for (TargetPackage targetPackage : BaseNoGui.packages.values()) {
+ // For every package cycle through all platform
+ for (TargetPlatform targetPlatform : targetPackage.platforms()) {
+ // Add a title for each platform
+ String platformLabel = targetPlatform.getPreferences().get("name");
+ if (platformLabel == null)
+ platformLabel = targetPackage.getId() + "-" + targetPlatform.getId();
- /**
- * Show the Preferences window.
- */
- public void handlePrefs() {
- if (preferencesFrame == null) preferencesFrame = new Preferences();
- preferencesFrame.showFrame(activeEditor);
- }
+ // add an hint that this core lives in sketchbook
+ if (targetPlatform.isInSketchbook())
+ platformLabel += " (in sketchbook)";
+ JMenu platformBoardsMenu = new JMenu(platformLabel);
+ MenuScroller.setScrollerFor(platformBoardsMenu);
+ platformMenus.add(platformBoardsMenu);
- // ...................................................................
+ // Cycle through all boards of this platform
+ for (TargetBoard board : targetPlatform.getBoards().values()) {
+ if (board.getPreferences().get("hide") != null)
+ continue;
+ JMenuItem item = createBoardMenusAndCustomMenus(boardsCustomMenus, menuItemsToClickAfterStartup,
+ buttonGroupsMap,
+ board, targetPlatform, targetPackage);
+ platformBoardsMenu.add(item);
+ boardsButtonGroup.add(item);
+ }
+ }
+ }
+ platformMenus.sort((x,y) -> x.getText().compareToIgnoreCase(y.getText()));
- /**
- * Get list of platform constants.
- */
-// static public int[] getPlatforms() {
-// return platforms;
-// }
+ JMenuItem firstBoardItem = null;
+ if (platformMenus.size() == 1) {
+ // When just one platform exists, add the board items directly,
+ // rather than using a submenu
+ for (Component boardItem : platformMenus.get(0).getMenuComponents()) {
+ boardMenu.add(boardItem);
+ if (firstBoardItem == null)
+ firstBoardItem = (JMenuItem)boardItem;
+ }
+ } else {
+ // For multiple platforms, use submenus
+ for (JMenu platformMenu : platformMenus) {
+ if (firstBoardItem == null && platformMenu.getItemCount() > 0)
+ firstBoardItem = platformMenu.getItem(0);
+ boardMenu.add(platformMenu);
+ }
+ }
+ if (firstBoardItem == null) {
+ throw new IllegalStateException("No available boards");
+ }
-// static public int getPlatform() {
-// String osname = System.getProperty("os.name");
-//
-// if (osname.indexOf("Mac") != -1) {
-// return PConstants.MACOSX;
-//
-// } else if (osname.indexOf("Windows") != -1) {
-// return PConstants.WINDOWS;
-//
-// } else if (osname.equals("Linux")) { // true for the ibm vm
-// return PConstants.LINUX;
-//
-// } else {
-// return PConstants.OTHER;
-// }
-// }
+ // If there is no current board yet (first startup, or selected
+ // board no longer defined), select first available board.
+ if (menuItemsToClickAfterStartup.isEmpty()) {
+ menuItemsToClickAfterStartup.add(firstBoardItem);
+ }
+ for (JMenuItem menuItemToClick : menuItemsToClickAfterStartup) {
+ menuItemToClick.setSelected(true);
+ menuItemToClick.getAction().actionPerformed(new ActionEvent(this, -1, ""));
+ }
+ }
- static public Platform getPlatform() {
- return platform;
+ private String getPlatformUniqueId(TargetPlatform platform) {
+ return platform.getId() + "_" + platform.getFolder();
}
+ private JRadioButtonMenuItem createBoardMenusAndCustomMenus(
+ final List boardsCustomMenus, List menuItemsToClickAfterStartup,
+ Map buttonGroupsMap,
+ TargetBoard board, TargetPlatform targetPlatform, TargetPackage targetPackage)
+ throws Exception {
+ String selPackage = PreferencesData.get("target_package");
+ String selPlatform = PreferencesData.get("target_platform");
+ String selBoard = PreferencesData.get("board");
- static public String getPlatformName() {
- String osname = System.getProperty("os.name");
+ String boardId = board.getId();
+ String packageName = targetPackage.getId();
+ String platformName = targetPlatform.getId();
- if (osname.indexOf("Mac") != -1) {
- return "macosx";
+ // Setup a menu item for the current board
+ @SuppressWarnings("serial")
+ Action action = new AbstractAction(board.getName()) {
+ public void actionPerformed(ActionEvent actionevent) {
+ BaseNoGui.selectBoard((TargetBoard) getValue("b"));
+ filterVisibilityOfSubsequentBoardMenus(boardsCustomMenus, (TargetBoard) getValue("b"), 1);
- } else if (osname.indexOf("Windows") != -1) {
- return "windows";
+ onBoardOrPortChange();
+ rebuildImportMenu(Editor.importMenu);
+ rebuildExamplesMenu(Editor.examplesMenu);
+ rebuildProgrammerMenu();
+ }
+ };
+ action.putValue("b", board);
- } else if (osname.equals("Linux")) { // true for the ibm vm
- return "linux";
+ JRadioButtonMenuItem item = new JRadioButtonMenuItem(action);
- } else {
- return "other";
+ if (selBoard.equals(boardId) && selPackage.equals(packageName)
+ && selPlatform.equals(platformName)) {
+ menuItemsToClickAfterStartup.add(item);
}
- }
+ PreferencesMap customMenus = targetPlatform.getCustomMenus();
+ for (final String menuId : customMenus.keySet()) {
+ String title = customMenus.get(menuId);
+ JMenu menu = getBoardCustomMenu(tr(title), getPlatformUniqueId(targetPlatform));
- /**
- * Map a platform constant to its name.
- * @param which PConstants.WINDOWS, PConstants.MACOSX, PConstants.LINUX
- * @return one of "windows", "macosx", or "linux"
- */
- static public String getPlatformName(int which) {
- return platformNames.get(which);
- }
+ if (board.hasMenu(menuId)) {
+ PreferencesMap boardCustomMenu = board.getMenuLabels(menuId);
+ for (String customMenuOption : boardCustomMenu.keySet()) {
+ @SuppressWarnings("serial")
+ Action subAction = new AbstractAction(tr(boardCustomMenu.get(customMenuOption))) {
+ public void actionPerformed(ActionEvent e) {
+ PreferencesData.set("custom_" + menuId, ((List) getValue("board")).get(0).getId() + "_" + getValue("custom_menu_option"));
+ onBoardOrPortChange();
+ }
+ };
+ List boards = (List) subAction.getValue("board");
+ if (boards == null) {
+ boards = new ArrayList<>();
+ }
+ boards.add(board);
+ subAction.putValue("board", boards);
+ subAction.putValue("custom_menu_option", customMenuOption);
+ if (!buttonGroupsMap.containsKey(menuId)) {
+ buttonGroupsMap.put(menuId, new ButtonGroup());
+ }
- static public int getPlatformIndex(String what) {
- Integer entry = platformIndices.get(what);
- return (entry == null) ? -1 : entry.intValue();
- }
+ JRadioButtonMenuItem subItem = new JRadioButtonMenuItem(subAction);
+ menu.add(subItem);
+ buttonGroupsMap.get(menuId).add(subItem);
+ String selectedCustomMenuEntry = PreferencesData.get("custom_" + menuId);
+ if (selBoard.equals(boardId) && (boardId + "_" + customMenuOption).equals(selectedCustomMenuEntry)) {
+ menuItemsToClickAfterStartup.add(subItem);
+ }
+ }
+ }
+ }
- // These were changed to no longer rely on PApplet and PConstants because
- // of conflicts that could happen with older versions of core.jar, where
- // the MACOSX constant would instead read as the LINUX constant.
+ return item;
+ }
+ private void filterVisibilityOfSubsequentBoardMenus(List boardsCustomMenus, TargetBoard board,
+ int fromIndex) {
+ for (int i = fromIndex; i < boardsCustomMenus.size(); i++) {
+ JMenu menu = boardsCustomMenus.get(i);
+ for (int m = 0; m < menu.getItemCount(); m++) {
+ JMenuItem menuItem = menu.getItem(m);
+ for (TargetBoard t_board : (List)menuItem.getAction().getValue("board")) {
+ menuItem.setVisible(t_board.equals(board));
+ }
+ }
+ menu.setVisible(ifThereAreVisibleItemsOn(menu));
- /**
- * returns true if Processing is running on a Mac OS X machine.
- */
- static public boolean isMacOS() {
- //return PApplet.platform == PConstants.MACOSX;
- return System.getProperty("os.name").indexOf("Mac") != -1;
+ if (menu.isVisible()) {
+ JMenuItem visibleSelectedOrFirstMenuItem = selectVisibleSelectedOrFirstMenuItem(menu);
+ if (!visibleSelectedOrFirstMenuItem.isSelected()) {
+ visibleSelectedOrFirstMenuItem.setSelected(true);
+ visibleSelectedOrFirstMenuItem.getAction().actionPerformed(null);
+ }
+ }
+ }
}
-
- /**
- * returns true if running on windows.
- */
- static public boolean isWindows() {
- //return PApplet.platform == PConstants.WINDOWS;
- return System.getProperty("os.name").indexOf("Windows") != -1;
+ private static boolean ifThereAreVisibleItemsOn(JMenu menu) {
+ for (int i = 0; i < menu.getItemCount(); i++) {
+ if (menu.getItem(i).isVisible()) {
+ return true;
+ }
+ }
+ return false;
}
+ private JMenu getBoardCustomMenu(String label, String platformUniqueId) throws Exception {
+ for (JMenu menu : boardsCustomMenus) {
+ if (label.equals(menu.getText()) && menu.getClientProperty("platform").equals(platformUniqueId)) {
+ return menu;
+ }
+ }
+ throw new Exception("Custom menu not found!");
+ }
- /**
- * true if running on linux.
- */
- static public boolean isLinux() {
- //return PApplet.platform == PConstants.LINUX;
- return System.getProperty("os.name").indexOf("Linux") != -1;
+ public List getProgrammerMenus() {
+ return programmerMenus;
}
+ private static JMenuItem selectVisibleSelectedOrFirstMenuItem(JMenu menu) {
+ JMenuItem firstVisible = null;
+ for (int i = 0; i < menu.getItemCount(); i++) {
+ JMenuItem item = menu.getItem(i);
+ if (item != null && item.isVisible()) {
+ if (item.isSelected()) {
+ return item;
+ }
+ if (firstVisible == null) {
+ firstVisible = item;
+ }
+ }
+ }
- // .................................................................
+ if (firstVisible != null) {
+ return firstVisible;
+ }
+ throw new IllegalStateException("Menu has no enabled items");
+ }
- static public File getSettingsFolder() {
- File settingsFolder = null;
+ public void rebuildProgrammerMenu() {
+ programmerMenus = new LinkedList<>();
+ ButtonGroup group = new ButtonGroup();
- String preferencesPath = Preferences.get("settings.path");
- if (preferencesPath != null) {
- settingsFolder = new File(preferencesPath);
+ TargetBoard board = BaseNoGui.getTargetBoard();
+ if (board != null) {
+ TargetPlatform boardPlatform = board.getContainerPlatform();
+ TargetPlatform corePlatform = null;
- } else {
- try {
- settingsFolder = platform.getSettingsFolder();
- } catch (Exception e) {
- showError("Problem getting data folder",
- "Error getting the Arduino data folder.", e);
+ String core = board.getPreferences().get("build.core");
+ if (core != null && core.contains(":")) {
+ String[] split = core.split(":", 2);
+ corePlatform = BaseNoGui.getCurrentTargetPlatformFromPackage(split[0]);
}
+
+ addProgrammersForPlatform(boardPlatform, programmerMenus, group);
+ if (corePlatform != null)
+ addProgrammersForPlatform(corePlatform, programmerMenus, group);
}
- // create the folder if it doesn't exist already
- if (!settingsFolder.exists()) {
- if (!settingsFolder.mkdirs()) {
- showError("Settings issues",
- "Arduino cannot run because it could not\n" +
- "create a folder to store your settings.", null);
- }
+ if (programmerMenus.isEmpty()) {
+ JMenuItem item = new JMenuItem(tr("No programmers available for this board"));
+ item.setEnabled(false);
+ programmerMenus.add(item);
}
- return settingsFolder;
}
+ public void addProgrammersForPlatform(TargetPlatform platform, List menus, ButtonGroup group) {
+ for (String programmer : platform.getProgrammers().keySet()) {
+ String id = platform.getContainerPackage().getId() + ":" + programmer;
+
+ @SuppressWarnings("serial")
+ AbstractAction action = new AbstractAction(platform.getProgrammer(programmer).get("name")) {
+ public void actionPerformed(ActionEvent actionevent) {
+ PreferencesData.set("programmer", "" + getValue("id"));
+ }
+ };
+ action.putValue("id", id);
+ JMenuItem item = new JRadioButtonMenuItem(action);
+ if (PreferencesData.get("programmer").equals(id)) {
+ item.setSelected(true);
+ }
+ group.add(item);
+ menus.add(item);
+ }
+ }
/**
- * Convenience method to get a File object for the specified filename inside
- * the settings folder.
- * For now, only used by Preferences to get the preferences.txt file.
- * @param filename A file inside the settings folder.
- * @return filename wrapped as a File object inside the settings folder
+ * Scan a folder recursively, and add any sketches found to the menu
+ * specified. Set the openReplaces parameter to true when opening the sketch
+ * should replace the sketch in the current window, or false when the
+ * sketch should open in a new window.
*/
- static public File getSettingsFile(String filename) {
- return new File(getSettingsFolder(), filename);
- }
+ protected boolean addSketches(JMenu menu, File folder) {
+ if (folder == null)
+ return false;
+ if (!folder.isDirectory()) return false;
- static public File getBuildFolder() {
- if (buildFolder == null) {
- String buildPath = Preferences.get("build.path");
- if (buildPath != null) {
- buildFolder = new File(buildPath);
+ File[] files = folder.listFiles();
+ // If a bad folder or unreadable or whatever, this will come back null
+ if (files == null) return false;
- } else {
- //File folder = new File(getTempFolder(), "build");
- //if (!folder.exists()) folder.mkdirs();
- buildFolder = createTempFolder("build");
- buildFolder.deleteOnExit();
+ // Alphabetize files, since it's not always alpha order
+ Arrays.sort(files, new Comparator() {
+ @Override
+ public int compare(File file, File file2) {
+ return file.getName().compareToIgnoreCase(file2.getName());
+ }
+ });
+
+ boolean ifound = false;
+ for (File subfolder : files) {
+ if (!FileUtils.isSCCSOrHiddenFile(subfolder) && subfolder.isDirectory()
+ && addSketchesSubmenu(menu, subfolder.getName(), subfolder)) {
+ ifound = true;
}
}
- return buildFolder;
+ return ifound;
}
+ private boolean addSketchesSubmenu(JMenu menu, UserLibrary lib) {
+ return addSketchesSubmenu(menu, lib.getName(), lib.getInstalledFolder());
+ }
- /**
- * Get the path to the platform's temporary folder, by creating
- * a temporary temporary file and getting its parent folder.
- *
- * Modified for revision 0094 to actually make the folder randomized
- * to avoid conflicts in multi-user environments. (Bug 177)
- */
- static public File createTempFolder(String name) {
- try {
- File folder = File.createTempFile(name, null);
- //String tempPath = ignored.getParent();
- //return new File(tempPath);
- folder.delete();
- folder.mkdirs();
- return folder;
+ private boolean addSketchesSubmenu(JMenu menu, String name, File folder) {
+
+ ActionListener listener = new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ String path = e.getActionCommand();
+ File file = new File(path);
+ if (file.exists()) {
+ try {
+ handleOpen(file);
+ } catch (Exception e1) {
+ e1.printStackTrace();
+ }
+ } else {
+ showWarning(tr("Sketch Does Not Exist"),
+ tr("The selected sketch no longer exists.\n"
+ + "You may need to restart Arduino to update\n"
+ + "the sketchbook menu."), null);
+ }
+ }
+ };
+
+ File entry = new File(folder, name + ".ino");
+ if (!entry.exists() && (new File(folder, name + ".pde")).exists())
+ entry = new File(folder, name + ".pde");
+
+ // if a .pde file of the same prefix as the folder exists..
+ if (entry.exists()) {
+
+ if (!BaseNoGui.isSanitaryName(name)) {
+ if (!builtOnce) {
+ String complaining = I18n
+ .format(
+ tr("The sketch \"{0}\" cannot be used.\n"
+ + "Sketch names must contain only basic letters and numbers\n"
+ + "(ASCII-only with no spaces, "
+ + "and it cannot start with a number).\n"
+ + "To get rid of this message, remove the sketch from\n"
+ + "{1}"), name, entry.getAbsolutePath());
+ showMessage(tr("Ignoring sketch with bad name"), complaining);
+ }
+ return false;
+ }
- } catch (Exception e) {
- e.printStackTrace();
+ JMenuItem item = new JMenuItem(name);
+ item.addActionListener(listener);
+ item.setActionCommand(entry.getAbsolutePath());
+ menu.add(item);
+ return true;
}
- return null;
- }
+ // don't create an extra menu level for a folder named "examples"
+ if (folder.getName().equals("examples"))
+ return addSketches(menu, folder);
- static public Set getLibraries() {
- return libraries;
+ // not a sketch folder, but maybe a subfolder containing sketches
+ JMenu submenu = new JMenu(name);
+ boolean found = addSketches(submenu, folder);
+ if (found) {
+ menu.add(submenu);
+ MenuScroller.setScrollerFor(submenu);
+ }
+ return found;
}
+ protected void addLibraries(JMenu menu, LibraryList libs) throws IOException {
- static public String getExamplesPath() {
- return examplesFolder.getAbsolutePath();
- }
-
+ LibraryList list = new LibraryList(libs);
+ list.sort();
- static public String getLibrariesPath() {
- return librariesFolder.getAbsolutePath();
- }
+ for (UserLibrary lib : list) {
+ @SuppressWarnings("serial")
+ AbstractAction action = new AbstractAction(lib.getName()) {
+ public void actionPerformed(ActionEvent event) {
+ UserLibrary l = (UserLibrary) getValue("library");
+ try {
+ activeEditor.getSketchController().importLibrary(l);
+ } catch (IOException e) {
+ showWarning(tr("Error"), format("Unable to list header files in {0}", l.getSrcFolder()), e);
+ }
+ }
+ };
+ action.putValue("library", lib);
+ // Add new element at the bottom
+ JMenuItem item = new JMenuItem(action);
+ item.putClientProperty("library", lib);
+ menu.add(item);
- static public File getToolsFolder() {
- return toolsFolder;
+ // XXX: DAM: should recurse here so that library folders can be nested
+ }
}
+ /**
+ * Given a folder, return a list of the header files in that folder (but not
+ * the header files in its sub-folders, as those should be included from
+ * within the header files at the top-level).
+ */
+ static public String[] headerListFromIncludePath(File path) throws IOException {
+ String[] list = path.list(new OnlyFilesWithExtension(".h"));
+ if (list == null) {
+ throw new IOException();
+ }
+ return list;
+ }
- static public String getToolsPath() {
- return toolsFolder.getAbsolutePath();
+ /**
+ * Show the About box.
+ */
+ @SuppressWarnings("serial")
+ public void handleAbout() {
+ final Image image = Theme.getLibImage("about", activeEditor,
+ Theme.scale(475), Theme.scale(300));
+ final Window window = new Window(activeEditor) {
+ public void paint(Graphics graphics) {
+ Graphics2D g = Theme.setupGraphics2D(graphics);
+ g.drawImage(image, 0, 0, null);
+
+ Font f = new Font("SansSerif", Font.PLAIN, Theme.scale(11));
+ g.setFont(f);
+ g.setColor(new Color(0,151,156));
+ g.drawString(BaseNoGui.VERSION_NAME_LONG, Theme.scale(33), Theme.scale(20));
+ }
+ };
+ window.addMouseListener(new MouseAdapter() {
+ public void mousePressed(MouseEvent e) {
+ window.dispose();
+ }
+ });
+ int w = image.getWidth(activeEditor);
+ int h = image.getHeight(activeEditor);
+ Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
+ window.setBounds((screen.width - w) / 2, (screen.height - h) / 2, w, h);
+ window.setLocationRelativeTo(activeEditor);
+ window.setVisible(true);
}
- static public File getHardwareFolder() {
- // calculate on the fly because it's needed by Preferences.init() to find
- // the boards.txt and programmers.txt preferences files (which happens
- // before the other folders / paths get cached).
- return getContentFile("hardware");
- }
-
-
- static public String getHardwarePath() {
- return getHardwareFolder().getAbsolutePath();
- }
-
-
- static public String getAvrBasePath() {
- if(Base.isLinux()) {
- return ""; // avr tools are installed system-wide and in the path
- } else {
- return getHardwarePath() + File.separator + "tools" +
- File.separator + "avr" + File.separator + "bin" + File.separator;
- }
- }
-
-
- static public Target getTarget() {
- return Base.targetsTable.get(Preferences.get("target"));
- }
-
-
- static public Map getBoardPreferences() {
- Target target = getTarget();
- if (target == null) return new LinkedHashMap();
- Map map = target.getBoards();
- if (map == null) return new LinkedHashMap();
- map = (Map) map.get(Preferences.get("board"));
- if (map == null) return new LinkedHashMap();
- return map;
+ /**
+ * Show the Preferences window.
+ */
+ public void handlePrefs() {
+ cc.arduino.view.preferences.Preferences dialog = new cc.arduino.view.preferences.Preferences(activeEditor, this);
+ if (activeEditor != null) {
+ dialog.setLocationRelativeTo(activeEditor);
+ }
+ dialog.setVisible(true);
}
-
- static public File getSketchbookFolder() {
- return new File(Preferences.get("sketchbook.path"));
+ /**
+ * Adjust font size
+ */
+ public void handleFontSizeChange(int change) {
+ String pieces[] = PreferencesData.get("editor.font").split(",");
+ try {
+ int newSize = Integer.parseInt(pieces[2]) + change;
+ if (newSize < 4)
+ newSize = 4;
+ pieces[2] = String.valueOf(newSize);
+ } catch (NumberFormatException e) {
+ // ignore
+ return;
+ }
+ PreferencesData.set("editor.font", StringUtils.join(pieces, ','));
+ getEditors().forEach(Editor::applyPreferences);
}
-
- static public File getSketchbookLibrariesFolder() {
- return new File(getSketchbookFolder(), "libraries");
+ /**
+ * Adds a {@link MouseWheelListener} and {@link KeyListener} to the given
+ * component that will make "CTRL scroll" and "CTRL +/-"
+ * (with optional SHIFT for +) increase/decrease the editor text size.
+ * This method is equivalent to calling
+ * {@link #addEditorFontResizeMouseWheelListener(Component)} and
+ * {@link #addEditorFontResizeKeyListener(Component)} on the given component.
+ * Note that this also affects components that use the editor font settings.
+ * @param comp - The component to add the listener to.
+ */
+ public void addEditorFontResizeListeners(Component comp) {
+ addEditorFontResizeMouseWheelListener(comp);
+ addEditorFontResizeKeyListener(comp);
}
-
- static public String getSketchbookLibrariesPath() {
- return getSketchbookLibrariesFolder().getAbsolutePath();
+ /**
+ * Adds a {@link MouseWheelListener} to the given component that will
+ * make "CTRL scroll" increase/decrease the editor text size.
+ * When CTRL is not pressed while scrolling, mouse wheel events are passed
+ * on to the parent of the given component.
+ * Note that this also affects components that use the editor font settings.
+ * @param comp - The component to add the listener to.
+ */
+ public void addEditorFontResizeMouseWheelListener(Component comp) {
+ comp.addMouseWheelListener(e -> {
+ if (e.isControlDown()) {
+ if (e.getWheelRotation() < 0) {
+ this.handleFontSizeChange(1);
+ } else {
+ this.handleFontSizeChange(-1);
+ }
+ } else {
+ if (e.getComponent() != null && e.getComponent().getParent() != null) {
+ e.getComponent().getParent().dispatchEvent(e);
+ }
+ }
+ });
}
-
-
- static public File getSketchbookHardwareFolder() {
- return new File(getSketchbookFolder(), "hardware");
+
+ /**
+ * Adds a {@link KeyListener} to the given component that will make "CTRL +/-"
+ * (with optional SHIFT for +) increase/decrease the editor text size.
+ * Note that this also affects components that use the editor font settings.
+ * @param comp - The component to add the listener to.
+ */
+ public void addEditorFontResizeKeyListener(Component comp) {
+ comp.addKeyListener(new KeyAdapter() {
+ @Override
+ public void keyPressed(KeyEvent e) {
+ if (e.getModifiersEx() == KeyEvent.CTRL_DOWN_MASK
+ || e.getModifiersEx() == (KeyEvent.CTRL_DOWN_MASK
+ | KeyEvent.SHIFT_DOWN_MASK)) {
+ switch (e.getKeyCode()) {
+ case KeyEvent.VK_PLUS:
+ case KeyEvent.VK_EQUALS:
+ Base.this.handleFontSizeChange(1);
+ break;
+ case KeyEvent.VK_MINUS:
+ if (!e.isShiftDown()) {
+ Base.this.handleFontSizeChange(-1);
+ }
+ break;
+ default:
+ }
+ }
+ }
+ });
}
+ public List getBoardsCustomMenus() {
+ return boardsCustomMenus;
+ }
- protected File getDefaultSketchbookFolder() {
- File sketchbookFolder = null;
- try {
- sketchbookFolder = platform.getDefaultSketchbookFolder();
- } catch (Exception e) { }
+ public File getDefaultSketchbookFolderOrPromptForIt() {
+ File sketchbookFolder = BaseNoGui.getDefaultSketchbookFolder();
- if (sketchbookFolder == null) {
+ if (sketchbookFolder == null && !isCommandLine()) {
sketchbookFolder = promptSketchbookLocation();
}
@@ -1573,9 +2029,9 @@ protected File getDefaultSketchbookFolder() {
}
if (!result) {
- showError("You forgot your sketchbook",
- "Arduino cannot run because it could not\n" +
- "create a folder to store your sketchbook.", null);
+ showError(tr("You forgot your sketchbook"),
+ tr("Arduino cannot run because it could not\n" +
+ "create a folder to store your sketchbook."), null);
}
return sketchbookFolder;
@@ -1594,8 +2050,8 @@ static protected File promptSketchbookLocation() {
return folder;
}
- String prompt = "Select (or create new) folder for sketches...";
- folder = Base.selectFolder(prompt, null, null);
+ String prompt = tr("Select (or create new) folder for sketches...");
+ folder = selectFolder(prompt, null, null);
if (folder == null) {
System.exit(0);
}
@@ -1615,21 +2071,21 @@ static protected File promptSketchbookLocation() {
*/
static public void openURL(String url) {
try {
- platform.openURL(url);
-
+ BaseNoGui.getPlatform().openURL(url);
} catch (Exception e) {
- showWarning("Problem Opening URL",
- "Could not open the URL\n" + url, e);
+ showWarning(tr("Problem Opening URL"),
+ format(tr("Could not open the URL\n{0}"), url), e);
}
}
/**
* Used to determine whether to disable the "Show Sketch Folder" option.
+ *
* @return true If a means of opening a folder is known to be available.
*/
static protected boolean openFolderAvailable() {
- return platform.openFolderAvailable();
+ return BaseNoGui.getPlatform().openFolderAvailable();
}
@@ -1639,11 +2095,10 @@ static protected boolean openFolderAvailable() {
*/
static public void openFolder(File file) {
try {
- platform.openFolder(file);
-
+ BaseNoGui.getPlatform().openFolder(file);
} catch (Exception e) {
- showWarning("Problem Opening Folder",
- "Could not open the folder\n" + file.getAbsolutePath(), e);
+ showWarning(tr("Problem Opening Folder"),
+ format(tr("Could not open the folder\n{0}"), file.getAbsolutePath()), e);
}
}
@@ -1651,43 +2106,17 @@ static public void openFolder(File file) {
// .................................................................
- /**
- * Prompt for a fodler and return it as a File object (or null).
- * Implementation for choosing directories that handles both the
- * Mac OS X hack to allow the native AWT file dialog, or uses
- * the JFileChooser on other platforms. Mac AWT trick obtained from
- * this post
- * on the OS X Java dev archive which explains the cryptic note in
- * Apple's Java 1.4 release docs about the special System property.
- */
- static public File selectFolder(String prompt, File folder, Frame frame) {
- if (Base.isMacOS()) {
- if (frame == null) frame = new Frame(); //.pack();
- FileDialog fd = new FileDialog(frame, prompt, FileDialog.LOAD);
- if (folder != null) {
- fd.setDirectory(folder.getParent());
- //fd.setFile(folder.getName());
- }
- System.setProperty("apple.awt.fileDialogForDirectories", "true");
- fd.setVisible(true);
- System.setProperty("apple.awt.fileDialogForDirectories", "false");
- if (fd.getFile() == null) {
- return null;
- }
- return new File(fd.getDirectory(), fd.getFile());
-
- } else {
- JFileChooser fc = new JFileChooser();
- fc.setDialogTitle(prompt);
- if (folder != null) {
- fc.setSelectedFile(folder);
- }
- fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
+ static public File selectFolder(String prompt, File folder, Component parent) {
+ JFileChooser fc = new JFileChooser();
+ fc.setDialogTitle(prompt);
+ if (folder != null) {
+ fc.setSelectedFile(folder);
+ }
+ fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
- int returned = fc.showOpenDialog(new JDialog());
- if (returned == JFileChooser.APPROVE_OPTION) {
- return fc.getSelectedFile();
- }
+ int returned = fc.showOpenDialog(parent);
+ if (returned == JFileChooser.APPROVE_OPTION) {
+ return fc.getSelectedFile();
}
return null;
}
@@ -1697,32 +2126,22 @@ static public File selectFolder(String prompt, File folder, Frame frame) {
/**
- * Give this Frame a Processing icon.
+ * Give this Frame an icon.
*/
static public void setIcon(Frame frame) {
- Image image = Toolkit.getDefaultToolkit().createImage(PApplet.ICON_IMAGE);
- frame.setIconImage(image);
- }
-
-
- // someone needs to be slapped
- //static KeyStroke closeWindowKeyStroke;
-
- /**
- * Return true if the key event was a Ctrl-W or an ESC,
- * both indicators to close the window.
- * Use as part of a keyPressed() event handler for frames.
- */
- /*
- static public boolean isCloseWindowEvent(KeyEvent e) {
- if (closeWindowKeyStroke == null) {
- int modifiers = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
- closeWindowKeyStroke = KeyStroke.getKeyStroke('W', modifiers);
+ if (OSUtils.isMacOS()) {
+ return;
}
- return ((e.getKeyCode() == KeyEvent.VK_ESCAPE) ||
- KeyStroke.getKeyStrokeForEvent(e).equals(closeWindowKeyStroke));
+
+ List icons = Stream
+ .of("16", "24", "32", "48", "64", "72", "96", "128", "256")
+ .map(res -> "/lib/icons/" + res + "x" + res + "/apps/arduino.png")
+ .map(path -> BaseNoGui.getContentFile(path).getAbsolutePath())
+ .map(absPath -> Toolkit.getDefaultToolkit().createImage(absPath))
+ .collect(Collectors.toList());
+ frame.setIconImages(icons);
}
- */
+
/**
* Registers key events for a Ctrl-W and ESC with an ActionListener
@@ -1732,59 +2151,15 @@ static public void registerWindowCloseKeys(JRootPane root,
ActionListener disposer) {
KeyStroke stroke = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0);
root.registerKeyboardAction(disposer, stroke,
- JComponent.WHEN_IN_FOCUSED_WINDOW);
+ JComponent.WHEN_IN_FOCUSED_WINDOW);
int modifiers = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
stroke = KeyStroke.getKeyStroke('W', modifiers);
root.registerKeyboardAction(disposer, stroke,
- JComponent.WHEN_IN_FOCUSED_WINDOW);
- }
-
-
- // .................................................................
-
-
- static public void showReference(String filename) {
- File referenceFolder = Base.getContentFile("reference");
- File referenceFile = new File(referenceFolder, filename);
- openURL(referenceFile.getAbsolutePath());
- }
-
- static public void showGettingStarted() {
- if (Base.isMacOS()) {
- Base.showReference("Guide_MacOSX.html");
- } else if (Base.isWindows()) {
- Base.showReference("Guide_Windows.html");
- } else {
- Base.openURL("http://www.arduino.cc/playground/Learning/Linux");
- }
- }
-
- static public void showReference() {
- showReference("index.html");
- }
-
-
- static public void showEnvironment() {
- showReference("Guide_Environment.html");
- }
-
-
- static public void showPlatforms() {
- showReference("environment" + File.separator + "platforms.html");
- }
-
-
- static public void showTroubleshooting() {
- showReference("Guide_Troubleshooting.html");
+ JComponent.WHEN_IN_FOCUSED_WINDOW);
}
- static public void showFAQ() {
- showReference("FAQ.html");
- }
-
-
// .................................................................
@@ -1793,15 +2168,7 @@ static public void showFAQ() {
* much of a bummer, but something to notify the user about.
*/
static public void showMessage(String title, String message) {
- if (title == null) title = "Message";
-
- if (commandLine) {
- System.out.println(title + ": " + message);
-
- } else {
- JOptionPane.showMessageDialog(new Frame(), message, title,
- JOptionPane.INFORMATION_MESSAGE);
- }
+ BaseNoGui.showMessage(title, message);
}
@@ -1809,250 +2176,30 @@ static public void showMessage(String title, String message) {
* Non-fatal error message with optional stack trace side dish.
*/
static public void showWarning(String title, String message, Exception e) {
- if (title == null) title = "Warning";
+ BaseNoGui.showWarning(title, message, e);
+ }
- if (commandLine) {
- System.out.println(title + ": " + message);
- } else {
- JOptionPane.showMessageDialog(new Frame(), message, title,
- JOptionPane.WARNING_MESSAGE);
- }
- if (e != null) e.printStackTrace();
+ static public void showError(String title, String message, Throwable e) {
+ showError(title, message, e, 1);
}
+ static public void showError(String title, String message, int exit_code) {
+ showError(title, message, null, exit_code);
+ }
/**
* Show an error message that's actually fatal to the program.
* This is an error that can't be recovered. Use showWarning()
* for errors that allow P5 to continue running.
*/
- static public void showError(String title, String message, Throwable e) {
- if (title == null) title = "Error";
-
- if (commandLine) {
- System.err.println(title + ": " + message);
-
- } else {
- JOptionPane.showMessageDialog(new Frame(), message, title,
- JOptionPane.ERROR_MESSAGE);
- }
- if (e != null) e.printStackTrace();
- System.exit(1);
- }
-
-
- // ...................................................................
-
-
-
- // incomplete
- static public int showYesNoCancelQuestion(Editor editor, String title,
- String primary, String secondary) {
- if (!Base.isMacOS()) {
- int result =
- JOptionPane.showConfirmDialog(null, primary + "\n" + secondary, title,
- JOptionPane.YES_NO_CANCEL_OPTION,
- JOptionPane.QUESTION_MESSAGE);
- return result;
-// if (result == JOptionPane.YES_OPTION) {
-//
-// } else if (result == JOptionPane.NO_OPTION) {
-// return true; // ok to continue
-//
-// } else if (result == JOptionPane.CANCEL_OPTION) {
-// return false;
-//
-// } else {
-// throw new IllegalStateException();
-// }
-
- } else {
- // Pane formatting adapted from the Quaqua guide
- // http://www.randelshofer.ch/quaqua/guide/joptionpane.html
- JOptionPane pane =
- new JOptionPane(" " +
- " " +
- "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[] {
- "Save", "Cancel", "Don't Save"
- };
- pane.setOptions(options);
-
- // highlight the safest option ala apple hig
- pane.setInitialValue(options[0]);
-
- // on macosx, setting the destructive property places this option
- // away from the others at the lefthand side
- pane.putClientProperty("Quaqua.OptionPane.destructiveOption",
- new Integer(2));
-
- JDialog dialog = pane.createDialog(editor, null);
- dialog.setVisible(true);
-
- Object result = pane.getValue();
- if (result == options[0]) {
- return JOptionPane.YES_OPTION;
- } else if (result == options[1]) {
- return JOptionPane.CANCEL_OPTION;
- } else if (result == options[2]) {
- return JOptionPane.NO_OPTION;
- } else {
- return JOptionPane.CLOSED_OPTION;
- }
- }
- }
-
-
-//if (result == JOptionPane.YES_OPTION) {
- //
-// } else if (result == JOptionPane.NO_OPTION) {
-// return true; // ok to continue
- //
-// } else if (result == JOptionPane.CANCEL_OPTION) {
-// return false;
- //
-// } else {
-// throw new IllegalStateException();
-// }
-
- static public int showYesNoQuestion(Frame editor, String title,
- String primary, String secondary) {
- if (!Base.isMacOS()) {
- return JOptionPane.showConfirmDialog(editor,
- "
" +
- "" + primary + "" +
- "
" + secondary, title,
- JOptionPane.YES_NO_OPTION,
- JOptionPane.QUESTION_MESSAGE);
- } else {
- // Pane formatting adapted from the Quaqua guide
- // http://www.randelshofer.ch/quaqua/guide/joptionpane.html
- JOptionPane pane =
- new JOptionPane(" " +
- " " +
- "" + primary + "" +
- "" + secondary + "
",
- JOptionPane.QUESTION_MESSAGE);
-
- String[] options = new String[] {
- "Yes", "No"
- };
- pane.setOptions(options);
-
- // highlight the safest option ala apple hig
- pane.setInitialValue(options[0]);
-
- JDialog dialog = pane.createDialog(editor, null);
- dialog.setVisible(true);
-
- Object result = pane.getValue();
- if (result == options[0]) {
- return JOptionPane.YES_OPTION;
- } else if (result == options[1]) {
- return JOptionPane.NO_OPTION;
- } else {
- return JOptionPane.CLOSED_OPTION;
- }
- }
+ static public void showError(String title, String message, Throwable e, int exit_code) {
+ BaseNoGui.showError(title, message, e, exit_code);
}
- /**
- * Retrieve a path to something in the Processing folder. Eventually this
- * may refer to the Contents subfolder of Processing.app, if we bundle things
- * up as a single .app file with no additional folders.
- */
-// static public String getContentsPath(String filename) {
-// String basePath = System.getProperty("user.dir");
-// /*
-// // do this later, when moving to .app package
-// if (PApplet.platform == PConstants.MACOSX) {
-// basePath = System.getProperty("processing.contents");
-// }
-// */
-// return basePath + File.separator + filename;
-// }
-
-
- /**
- * Get a path for something in the Processing lib folder.
- */
- /*
- static public String getLibContentsPath(String filename) {
- String libPath = getContentsPath("lib/" + filename);
- File libDir = new File(libPath);
- if (libDir.exists()) {
- return libPath;
- }
-// was looking into making this run from Eclipse, but still too much mess
-// libPath = getContents("build/shared/lib/" + what);
-// libDir = new File(libPath);
-// if (libDir.exists()) {
-// return libPath;
-// }
- return null;
- }
- */
-
static public File getContentFile(String name) {
- String path = System.getProperty("user.dir");
-
- // Get a path to somewhere inside the .app folder
- if (Base.isMacOS()) {
-// javaroot
-// $JAVAROOT
- String javaroot = System.getProperty("javaroot");
- if (javaroot != null) {
- path = javaroot;
- }
- }
- File working = new File(path);
- return new File(working, name);
- }
-
-
- /**
- * Get an image associated with the current color theme.
- */
- static public Image getThemeImage(String name, Component who) {
- return getLibImage("theme/" + name, who);
- }
-
-
- /**
- * Return an Image object from inside the Processing lib folder.
- */
- static public Image getLibImage(String name, Component who) {
- Image image = null;
- Toolkit tk = Toolkit.getDefaultToolkit();
-
- File imageLocation = new File(getContentFile("lib"), name);
- image = tk.getImage(imageLocation.getAbsolutePath());
- MediaTracker tracker = new MediaTracker(who);
- tracker.addImage(image, 0);
- try {
- tracker.waitForAll();
- } catch (InterruptedException e) { }
- return image;
- }
-
-
- /**
- * Return an InputStream for a file inside the Processing lib folder.
- */
- static public InputStream getLibStream(String filename) throws IOException {
- return new FileInputStream(new File(getContentFile("lib"), filename));
+ return BaseNoGui.getContentFile(name);
}
@@ -2064,11 +2211,7 @@ static public InputStream getLibStream(String filename) throws IOException {
* characters inside a String (and adding 1).
*/
static public int countLines(String what) {
- int count = 1;
- for (char c : what.toCharArray()) {
- if (c == '\n') count++;
- }
- return count;
+ return BaseNoGui.countLines(what);
}
@@ -2077,34 +2220,36 @@ static public int countLines(String what) {
*/
static public byte[] loadBytesRaw(File file) throws IOException {
int size = (int) file.length();
- FileInputStream input = new FileInputStream(file);
- byte buffer[] = new byte[size];
- int offset = 0;
- int bytesRead;
- while ((bytesRead = input.read(buffer, offset, size-offset)) != -1) {
- offset += bytesRead;
- if (bytesRead == 0) break;
+ FileInputStream input = null;
+ try {
+ input = new FileInputStream(file);
+ byte buffer[] = new byte[size];
+ int offset = 0;
+ int bytesRead;
+ while ((bytesRead = input.read(buffer, offset, size - offset)) != -1) {
+ offset += bytesRead;
+ if (bytesRead == 0) break;
+ }
+ return buffer;
+ } finally {
+ IOUtils.closeQuietly(input);
}
- input.close(); // weren't properly being closed
- input = null;
- return buffer;
}
-
/**
* Read from a file with a bunch of attribute/value pairs
* that are separated by = and ignore comments with #.
*/
- static public HashMap readSettings(File inputFile) {
- HashMap outgoing = new HashMap();
+ static public HashMap readSettings(File inputFile) {
+ HashMap outgoing = new HashMap<>();
if (!inputFile.exists()) return outgoing; // return empty hash
String lines[] = PApplet.loadStrings(inputFile);
for (int i = 0; i < lines.length; i++) {
int hash = lines[i].indexOf('#');
String line = (hash == -1) ?
- lines[i].trim() : lines[i].substring(0, hash).trim();
+ lines[i].trim() : lines[i].substring(0, hash).trim();
if (line.length() == 0) continue;
int equals = line.indexOf('=');
@@ -2123,20 +2268,21 @@ static public HashMap readSettings(File inputFile) {
static public void copyFile(File sourceFile,
File targetFile) throws IOException {
- InputStream from =
- new BufferedInputStream(new FileInputStream(sourceFile));
- OutputStream to =
- new BufferedOutputStream(new FileOutputStream(targetFile));
- byte[] buffer = new byte[16 * 1024];
- int bytesRead;
- while ((bytesRead = from.read(buffer)) != -1) {
- to.write(buffer, 0, bytesRead);
- }
- to.flush();
- from.close(); // ??
- from = null;
- to.close(); // ??
- to = null;
+ InputStream from = null;
+ OutputStream to = null;
+ try {
+ from = new BufferedInputStream(new FileInputStream(sourceFile));
+ to = new BufferedOutputStream(new FileOutputStream(targetFile));
+ byte[] buffer = new byte[16 * 1024];
+ int bytesRead;
+ while ((bytesRead = from.read(buffer)) != -1) {
+ to.write(buffer, 0, bytesRead);
+ }
+ to.flush();
+ } finally {
+ IOUtils.closeQuietly(from);
+ IOUtils.closeQuietly(to);
+ }
targetFile.setLastModified(sourceFile.lastModified());
}
@@ -2146,97 +2292,15 @@ static public void copyFile(File sourceFile,
* Grab the contents of a file as a string.
*/
static public String loadFile(File file) throws IOException {
- String[] contents = PApplet.loadStrings(file);
- if (contents == null) return null;
- return PApplet.join(contents, "\n");
- }
+ return BaseNoGui.loadFile(file);
+ }
/**
* Spew the contents of a String object out to a file.
*/
static public void saveFile(String str, File file) throws IOException {
- File temp = File.createTempFile(file.getName(), null, file.getParentFile());
- PApplet.saveStrings(temp, new String[] { str });
- if (file.exists()) {
- boolean result = file.delete();
- if (!result) {
- throw new IOException("Could not remove old version of " +
- file.getAbsolutePath());
- }
- }
- boolean result = temp.renameTo(file);
- if (!result) {
- throw new IOException("Could not replace " +
- file.getAbsolutePath());
- }
- }
-
-
- /**
- * Copy a folder from one place to another. This ignores all dot files and
- * folders found in the source directory, to avoid copying silly .DS_Store
- * files and potentially troublesome .svn folders.
- */
- static public void copyDir(File sourceDir,
- File targetDir) throws IOException {
- targetDir.mkdirs();
- String files[] = sourceDir.list();
- for (int i = 0; i < files.length; i++) {
- // Ignore dot files (.DS_Store), dot folders (.svn) while copying
- if (files[i].charAt(0) == '.') continue;
- //if (files[i].equals(".") || files[i].equals("..")) continue;
- File source = new File(sourceDir, files[i]);
- File target = new File(targetDir, files[i]);
- if (source.isDirectory()) {
- //target.mkdirs();
- copyDir(source, target);
- target.setLastModified(source.lastModified());
- } else {
- copyFile(source, target);
- }
- }
- }
-
-
- /**
- * Remove all files in a directory and the directory itself.
- */
- static public void removeDir(File dir) {
- if (dir.exists()) {
- removeDescendants(dir);
- if (!dir.delete()) {
- System.err.println("Could not delete " + dir);
- }
- }
- }
-
-
- /**
- * Recursively remove all files within a directory,
- * used with removeDir(), or when the contents of a dir
- * should be removed, but not the directory itself.
- * (i.e. when cleaning temp files from lib/build)
- */
- static public void removeDescendants(File dir) {
- if (!dir.exists()) return;
-
- String files[] = dir.list();
- for (int i = 0; i < files.length; i++) {
- if (files[i].equals(".") || files[i].equals("..")) continue;
- File dead = new File(dir, files[i]);
- if (!dead.isDirectory()) {
- if (!Preferences.getBoolean("compiler.save_build_files")) {
- if (!dead.delete()) {
- // temporarily disabled
- System.err.println("Could not delete " + dead);
- }
- }
- } else {
- removeDir(dead);
- //dead.delete();
- }
- }
+ BaseNoGui.saveFile(str, file);
}
@@ -2254,7 +2318,7 @@ static public int calcFolderSize(File folder) {
for (int i = 0; i < files.length; i++) {
if (files[i].equals(".") || (files[i].equals("..")) ||
- files[i].equals(".DS_Store")) continue;
+ files[i].equals(".DS_Store")) continue;
File fella = new File(folder, files[i]);
if (fella.isDirectory()) {
size += calcFolderSize(fella);
@@ -2265,45 +2329,120 @@ static public int calcFolderSize(File folder) {
return size;
}
+ public void handleAddLibrary() {
+ JFileChooser fileChooser = new JFileChooser(System.getProperty("user.home"));
+ fileChooser.setDialogTitle(tr("Select a zip file or a folder containing the library you'd like to add"));
+ fileChooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
+ fileChooser.setFileFilter(new FileNameExtensionFilter(tr("ZIP files or folders"), "zip"));
- /**
- * Recursively creates a list of all files within the specified folder,
- * and returns a list of their relative paths.
- * Ignores any files/folders prefixed with a dot.
- */
- static public String[] listFiles(String path, boolean relative) {
- return listFiles(new File(path), relative);
- }
+ Dimension preferredSize = fileChooser.getPreferredSize();
+ fileChooser.setPreferredSize(new Dimension(preferredSize.width + 200, preferredSize.height + 200));
+ int returnVal = fileChooser.showOpenDialog(activeEditor);
- static public String[] listFiles(File folder, boolean relative) {
- String path = folder.getAbsolutePath();
- Vector vector = new Vector();
- listFiles(relative ? (path + File.separator) : "", path, vector);
- String outgoing[] = new String[vector.size()];
- vector.copyInto(outgoing);
- return outgoing;
- }
+ if (returnVal != JFileChooser.APPROVE_OPTION) {
+ return;
+ }
+
+ File sourceFile = fileChooser.getSelectedFile();
+ File tmpFolder = null;
+
+ try {
+ // unpack ZIP
+ if (!sourceFile.isDirectory()) {
+ try {
+ tmpFolder = FileUtils.createTempFolder();
+ ZipDeflater zipDeflater = new ZipDeflater(sourceFile, tmpFolder);
+ zipDeflater.deflate();
+ File[] foldersInTmpFolder = tmpFolder.listFiles(new OnlyDirs());
+ if (foldersInTmpFolder.length != 1) {
+ throw new IOException(tr("Zip doesn't contain a library"));
+ }
+ sourceFile = foldersInTmpFolder[0];
+ } catch (IOException e) {
+ activeEditor.statusError(e);
+ return;
+ }
+ }
+
+ File libFolder = sourceFile;
+ if (FileUtils.isSubDirectory(new File(PreferencesData.get("sketchbook.path")), libFolder)) {
+ activeEditor.statusError(tr("A subfolder of your sketchbook is not a valid library"));
+ return;
+ }
+ if (FileUtils.isSubDirectory(libFolder, new File(PreferencesData.get("sketchbook.path")))) {
+ activeEditor.statusError(tr("You can't import a folder that contains your sketchbook"));
+ return;
+ }
- static protected void listFiles(String basePath,
- String path, Vector vector) {
- File folder = new File(path);
- String list[] = folder.list();
- if (list == null) return;
+ String libName = libFolder.getName();
+ if (!BaseNoGui.isSanitaryName(libName)) {
+ String mess = format(tr("The library \"{0}\" cannot be used.\n"
+ + "Library names must contain only basic letters and numbers.\n"
+ + "(ASCII only and no spaces, and it cannot start with a number)"),
+ libName);
+ activeEditor.statusError(mess);
+ return;
+ }
- for (int i = 0; i < list.length; i++) {
- if (list[i].charAt(0) == '.') continue;
+ String[] headers;
+ File libProp = new File(libFolder, "library.properties");
+ File srcFolder = new File(libFolder, "src");
+ if (libProp.exists() && srcFolder.isDirectory()) {
+ headers = BaseNoGui.headerListFromIncludePath(srcFolder);
+ } else {
+ headers = BaseNoGui.headerListFromIncludePath(libFolder);
+ }
+ if (headers.length == 0) {
+ activeEditor.statusError(tr("Specified folder/zip file does not contain a valid library"));
+ return;
+ }
- File file = new File(path, list[i]);
- String newPath = file.getAbsolutePath();
- if (newPath.startsWith(basePath)) {
- newPath = newPath.substring(basePath.length());
+ // copy folder
+ File destinationFolder = new File(BaseNoGui.getSketchbookLibrariesFolder().folder, sourceFile.getName());
+ if (!destinationFolder.mkdir()) {
+ activeEditor.statusError(format(tr("A library named {0} already exists"), sourceFile.getName()));
+ return;
}
- vector.add(newPath);
- if (file.isDirectory()) {
- listFiles(basePath, newPath, vector);
+ try {
+ FileUtils.copy(sourceFile, destinationFolder);
+ } catch (IOException e) {
+ activeEditor.statusError(e);
+ return;
}
+ activeEditor.statusNotice(tr("Library added to your libraries. Check \"Include library\" menu"));
+ } catch (IOException e) {
+ // FIXME error when importing. ignoring :(
+ } finally {
+ // delete zip created temp folder, if exists
+ newLibraryImported = true;
+ FileUtils.recursiveDelete(tmpFolder);
}
}
+
+ public static DiscoveryManager getDiscoveryManager() {
+ return BaseNoGui.getDiscoveryManager();
+ }
+
+ public Editor getActiveEditor() {
+ return activeEditor;
+ }
+
+ public boolean hasActiveEditor() {
+ return activeEditor != null;
+ }
+
+ public List getEditors() {
+ return new LinkedList<>(editors);
+ }
+
+ public PdeKeywords getPdeKeywords() {
+ return pdeKeywords;
+ }
+
+ public List getRecentSketchesMenuItems() {
+ return recentSketchesMenuItems;
+ }
+
}
diff --git a/app/src/processing/app/CommandHistory.java b/app/src/processing/app/CommandHistory.java
new file mode 100644
index 00000000000..cae3c2fc498
--- /dev/null
+++ b/app/src/processing/app/CommandHistory.java
@@ -0,0 +1,167 @@
+package processing.app;
+
+import java.util.LinkedList;
+import java.util.ListIterator;
+
+/**
+ * Keeps track of command history in console-like applications.
+ * @author P.J.S. Kools
+ */
+public class CommandHistory {
+
+ private final LinkedList commandHistory = new LinkedList();
+ private final int maxHistorySize;
+ private ListIterator iterator = null;
+ private boolean iteratorAsc;
+
+ /**
+ * Create a new {@link CommandHistory}.
+ * @param maxHistorySize - The max command history size.
+ */
+ public CommandHistory(int maxHistorySize) {
+ this.maxHistorySize = (maxHistorySize < 0 ? 0 : maxHistorySize);
+ this.commandHistory.addLast(""); // Current command placeholder.
+ }
+
+ /**
+ * Adds the given command to the history and resets the history traversal
+ * position to the latest command. If the latest command in the history is
+ * equal to the given command, it will not be added to the history.
+ * If the max history size is exceeded, the oldest command will be removed
+ * from the history.
+ * @param command - The command to add.
+ */
+ public void addCommand(String command) {
+ if (this.maxHistorySize == 0) {
+ return;
+ }
+
+ // Remove 'current' command.
+ this.commandHistory.removeLast();
+
+ // Add new command if it differs from the latest command.
+ if (this.commandHistory.isEmpty()
+ || !this.commandHistory.getLast().equals(command)) {
+
+ // Remove oldest command if max history size is exceeded.
+ if (this.commandHistory.size() >= this.maxHistorySize) {
+ this.commandHistory.removeFirst();
+ }
+
+ // Add new command and reset 'current' command.
+ this.commandHistory.addLast(command);
+ }
+
+ // Re-add 'current' command and reset command iterator.
+ this.commandHistory.addLast(""); // Current command placeholder.
+ this.iterator = null;
+ }
+
+ /**
+ * Gets whether a next (more recent) command is available in the history.
+ * @return {@code true} if a next command is available,
+ * returns {@code false} otherwise.
+ */
+ public boolean hasNextCommand() {
+ if (this.iterator == null) {
+ return false;
+ }
+ if (!this.iteratorAsc) {
+ this.iterator.next(); // Current command, ascending.
+ this.iteratorAsc = true;
+ }
+ return this.iterator.hasNext();
+ }
+
+ /**
+ * Gets the next (more recent) command from the history.
+ * @return The next command or {@code null} if no next command is available.
+ */
+ public String getNextCommand() {
+
+ // Return null if there is no next command available.
+ if (!this.hasNextCommand()) {
+ return null;
+ }
+
+ // Get next command.
+ String next = this.iterator.next();
+
+ // Reset 'current' command when at the end of the list.
+ if (this.iterator.nextIndex() == this.commandHistory.size()) {
+ this.iterator.set(""); // Reset 'current' command.
+ }
+ return next;
+ }
+
+ /**
+ * Gets whether a previous (older) command is available in the history.
+ * @return {@code true} if a previous command is available,
+ * returns {@code false} otherwise.
+ */
+ public boolean hasPreviousCommand() {
+ if (this.iterator == null) {
+ return this.commandHistory.size() > 1;
+ }
+ if (this.iteratorAsc) {
+ this.iterator.previous(); // Current command, descending.
+ this.iteratorAsc = false;
+ }
+ return this.iterator.hasPrevious();
+ }
+
+ /**
+ * Gets the previous (older) command from the history.
+ * When this method is called while the most recent command in the history is
+ * selected, this will store the current command as temporary latest command
+ * so that {@link #getNextCommand()} will return it once. This temporary
+ * latest command gets reset when this case occurs again or when
+ * {@link #addCommand(String)} is invoked.
+ * @param currentCommand - The current unexecuted command.
+ * @return The previous command or {@code null} if no previous command is
+ * available.
+ */
+ public String getPreviousCommand(String currentCommand) {
+
+ // Return null if there is no previous command available.
+ if (!this.hasPreviousCommand()) {
+ return null;
+ }
+
+ // Store current unexecuted command and create iterator if not traversing.
+ if (this.iterator == null) {
+ this.iterator =
+ this.commandHistory.listIterator(this.commandHistory.size());
+ this.iterator.previous(); // Last element, descending.
+ this.iteratorAsc = false;
+ }
+
+ // Store current unexecuted command if on 'current' index.
+ if (this.iterator.nextIndex() == this.commandHistory.size() - 1) {
+ this.iterator.set(currentCommand == null ? "" : currentCommand);
+ }
+
+ // Return the previous command.
+ return this.iterator.previous();
+ }
+
+ /**
+ * Resets the history location to the most recent command.
+ * @returns The latest unexecuted command as stored by
+ * {@link #getPreviousCommand(String)} or an empty string if no such command
+ * was set.
+ */
+ public String resetHistoryLocation() {
+ this.iterator = null;
+ return this.commandHistory.set(this.commandHistory.size() - 1, "");
+ }
+
+ /**
+ * Clears the command history.
+ */
+ public void clear() {
+ this.iterator = null;
+ this.commandHistory.clear();
+ this.commandHistory.addLast(""); // Current command placeholder.
+ }
+}
diff --git a/app/src/processing/app/Commander.java.disabled b/app/src/processing/app/Commander.java.disabled
deleted file mode 100644
index 88d9cb598af..00000000000
--- a/app/src/processing/app/Commander.java.disabled
+++ /dev/null
@@ -1,297 +0,0 @@
-/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
-
-/*
- Part of the Processing project - http://processing.org
-
- Copyright (c) 2008 Ben Fry and Casey Reas
-
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software Foundation,
- Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- */
-
-package processing.app;
-
-
-import java.io.File;
-import java.io.IOException;
-import java.io.PrintStream;
-
-import processing.core.PApplet;
-
-import processing.app.debug.*;
-
-
-/**
- * Class to handle running Processing from the command line.
- *
- * --help Show the help text.
- *
- * --sketch=<name&rt; Specify the sketch folder (required)
- * --output=<name&rt; Specify the output folder (required and
- * cannot be the same as the sketch folder.)
- *
- * --preprocess Preprocess a sketch into .java files.
- * --build Preprocess and compile a sketch into .class files.
- * --run Preprocess, compile, and run a sketch.
- * --present Preprocess, compile, and run a sketch full screen.
- *
- * --export-applet Export an applet.
- * --export-application Export an application.
- * --platform Specify the platform (export to application only).
- * Should be one of 'windows', 'macosx', or 'linux'.
- *
- * --preferences=<file&rt; Specify a preferences file to use (optional).
- *
- *
- * To build the command line version, first build for your platform,
- * then cd to processing/build/cmd and type 'dist.sh'. This will create a
- * usable installation plus a zip file of the same.
- *
- * @author fry
- */
-public class Commander implements RunnerListener {
- static final String helpArg = "--help";
- static final String preprocArg = "--preprocess";
- static final String buildArg = "--build";
- static final String runArg = "--run";
- static final String presentArg = "--present";
- static final String sketchArg = "--sketch=";
- static final String outputArg = "--output=";
- static final String exportAppletArg = "--export-applet";
- static final String exportApplicationArg = "--export-application";
- static final String platformArg = "--platform=";
- static final String preferencesArg = "--preferences=";
-
- static final int HELP = -1;
- static final int PREPROCESS = 0;
- static final int BUILD = 1;
- static final int RUN = 2;
- static final int PRESENT = 3;
- static final int EXPORT_APPLET = 4;
- static final int EXPORT_APPLICATION = 5;
-
- Sketch sketch;
-
-
- static public void main(String[] args) {
- // init the platform so that prefs and other native code is ready to go
- Base.initPlatform();
- // make sure a full JDK is installed
- Base.initRequirements();
- // run static initialization that grabs all the prefs
- //Preferences.init(null);
- // launch command line handler
- new Commander(args);
- }
-
-
- public Commander(String[] args) {
- String sketchFolder = null;
- String pdePath = null; // path to the .pde file
- String outputPath = null;
- String preferencesPath = null;
- int platformIndex = PApplet.platform; // default to this platform
- int mode = HELP;
-
- for (String arg : args) {
- if (arg.length() == 0) {
- // ignore it, just the crappy shell script
-
- } else if (arg.equals(helpArg)) {
- // mode already set to HELP
-
- } else if (arg.equals(buildArg)) {
- mode = BUILD;
-
- } else if (arg.equals(runArg)) {
- mode = RUN;
-
- } else if (arg.equals(presentArg)) {
- mode = PRESENT;
-
- } else if (arg.equals(preprocArg)) {
- mode = PREPROCESS;
-
- } else if (arg.equals(exportAppletArg)) {
- mode = EXPORT_APPLET;
-
- } else if (arg.equals(exportApplicationArg)) {
- mode = EXPORT_APPLICATION;
-
- } else if (arg.startsWith(platformArg)) {
- String platformStr = arg.substring(platformArg.length());
- platformIndex = Base.getPlatformIndex(platformStr);
- if (platformIndex == -1) {
- complainAndQuit(platformStr + " should instead be " +
- "'windows', 'macosx', or 'linux'.");
- }
- } else if (arg.startsWith(sketchArg)) {
- sketchFolder = arg.substring(sketchArg.length());
- File sketchy = new File(sketchFolder);
- File pdeFile = new File(sketchy, sketchy.getName() + ".pde");
- pdePath = pdeFile.getAbsolutePath();
-
- } else if (arg.startsWith(outputArg)) {
- outputPath = arg.substring(outputArg.length());
-
- } else {
- complainAndQuit("I don't know anything about " + arg + ".");
- }
- }
-
- if ((outputPath == null) &&
- (mode == PREPROCESS || mode == BUILD ||
- mode == RUN || mode == PRESENT)) {
- complainAndQuit("An output path must be specified when using " +
- preprocArg + ", " + buildArg + ", " +
- runArg + ", or " + presentArg + ".");
- }
-
- if (mode == HELP) {
- printCommandLine(System.out);
- System.exit(0);
- }
-
- // --present --platform=windows "--sketch=/Applications/Processing 0148/examples/Basics/Arrays/Array" --output=test-build
-
- File outputFolder = new File(outputPath);
- if (!outputFolder.exists()) {
- if (!outputFolder.mkdirs()) {
- complainAndQuit("Could not create the output folder.");
- }
- }
-
- // run static initialization that grabs all the prefs
- // (also pass in a prefs path if that was specified)
- Preferences.init(preferencesPath);
-
- if (sketchFolder == null) {
- complainAndQuit("No sketch path specified.");
-
- } else if (outputPath.equals(pdePath)) {
- complainAndQuit("The sketch path and output path cannot be identical.");
-
- } else if (!pdePath.toLowerCase().endsWith(".pde")) {
- complainAndQuit("Sketch path must point to the main .pde file.");
-
- } else {
- //Sketch sketch = null;
- boolean success = false;
-
- try {
- sketch = new Sketch(null, pdePath);
- if (mode == PREPROCESS) {
- success = sketch.preprocess(outputPath) != null;
-
- } else if (mode == BUILD) {
- success = sketch.build(outputPath) != null;
-
- } else if (mode == RUN || mode == PRESENT) {
- String className = sketch.build(outputPath);
- if (className != null) {
- success = true;
- Runner runner =
- new Runner(sketch, className, mode == PRESENT, this);
- runner.launch();
-
- } else {
- success = false;
- }
-
- } else if (mode == EXPORT_APPLET) {
- if (outputPath != null) {
- success = sketch.exportApplet(outputPath);
- } else {
- String target = sketchFolder + File.separatorChar + "applet";
- success = sketch.exportApplet(target);
- }
- } else if (mode == EXPORT_APPLICATION) {
- if (outputPath != null) {
- success = sketch.exportApplication(outputPath, platformIndex);
- } else {
- //String sketchFolder =
- // pdePath.substring(0, pdePath.lastIndexOf(File.separatorChar));
- outputPath =
- sketchFolder + File.separatorChar +
- "application." + Base.getPlatformName(platformIndex);
- success = sketch.exportApplication(outputPath, platformIndex);
- }
- }
- System.exit(success ? 0 : 1);
-
- } catch (RunnerException re) {
- statusError(re);
-
- } catch (IOException e) {
- e.printStackTrace();
- System.exit(1);
- }
- }
- }
-
-
- public void statusError(String message) {
- System.err.println(message);
- }
-
-
- public void statusError(Exception exception) {
- if (exception instanceof RunnerException) {
- RunnerException re = (RunnerException) exception;
-
- // format the runner exception like emacs
- //blah.java:2:10:2:13: Syntax Error: This is a big error message
- String filename = sketch.getCode(re.getCodeIndex()).getFileName();
- int line = re.getCodeLine();
- int column = re.getCodeColumn();
- if (column == -1) column = 0;
- // TODO if column not specified, should just select the whole line.
- System.err.println(filename + ":" +
- line + ":" + column + ":" +
- line + ":" + column + ":" + " " + re.getMessage());
- } else {
- exception.printStackTrace();
- }
- }
-
-
- static void complainAndQuit(String lastWords) {
- printCommandLine(System.err);
- System.err.println(lastWords);
- System.exit(1);
- }
-
-
- static void printCommandLine(PrintStream out) {
- out.println("Processing " + Base.VERSION_NAME + " rocks the console.");
- out.println();
- out.println("--help Show this help text.");
- out.println();
- out.println("--sketch= Specify the sketch folder (required)");
- out.println("--output= Specify the output folder (required and");
- out.println(" cannot be the same as the sketch folder.)");
- out.println();
- out.println("--preprocess Preprocess a sketch into .java files.");
- out.println("--build Preprocess and compile a sketch into .class files.");
- out.println("--run Preprocess, compile, and run a sketch.");
- out.println("--present Preprocess, compile, and run a sketch full screen.");
- out.println();
- out.println("--export-applet Export an applet.");
- out.println("--export-application Export an application.");
- out.println("--platform Specify the platform (export to application only).");
- out.println(" Should be one of 'windows', 'macosx', or 'linux'.");
- out.println();
- out.println("--preferences= Specify a preferences file to use (optional).");
- }
-}
\ No newline at end of file
diff --git a/app/src/processing/app/Editor.java b/app/src/processing/app/Editor.java
index 3d8bd405bad..a307bde8fbb 100644
--- a/app/src/processing/app/Editor.java
+++ b/app/src/processing/app/Editor.java
@@ -22,43 +22,145 @@
package processing.app;
-import processing.app.debug.*;
-import processing.app.syntax.*;
-import processing.app.tools.*;
-import processing.core.*;
-
-import java.awt.*;
-import java.awt.datatransfer.*;
-import java.awt.event.*;
-import java.awt.print.*;
-import java.io.*;
-import java.net.*;
-import java.util.*;
-import java.util.zip.*;
-
-import javax.swing.*;
-import javax.swing.event.*;
-import javax.swing.text.*;
-import javax.swing.undo.*;
-
-import gnu.io.*;
+import static processing.app.I18n.tr;
+import static processing.app.Theme.scale;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.Rectangle;
+import java.awt.Toolkit;
+import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.Transferable;
+import java.awt.event.ActionEvent;
+import java.awt.event.InputEvent;
+import java.awt.event.KeyEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+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.io.UnsupportedEncodingException;
+import java.net.ConnectException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.net.URLEncoder;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Predicate;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+import javax.swing.AbstractAction;
+import javax.swing.Box;
+import javax.swing.JCheckBoxMenuItem;
+import javax.swing.JComponent;
+import javax.swing.JDialog;
+import javax.swing.JFrame;
+import javax.swing.JMenu;
+import javax.swing.JMenuBar;
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JSplitPane;
+import javax.swing.JTextArea;
+import javax.swing.KeyStroke;
+import javax.swing.SwingUtilities;
+import javax.swing.TransferHandler;
+import javax.swing.event.MenuEvent;
+import javax.swing.event.MenuListener;
+import javax.swing.text.BadLocationException;
+
+import org.fife.ui.rsyntaxtextarea.folding.FoldManager;
+
+import com.jcraft.jsch.JSchException;
+
+import cc.arduino.CompilerProgressListener;
+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 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;
/**
* Main editor panel for the Processing Development Environment.
*/
+@SuppressWarnings("serial")
public class Editor extends JFrame implements RunnerListener {
- Base base;
+ 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();
+ }
+ }
+
+ private static class CanExportInSketchFolder
+ implements Predicate {
+
+ @Override
+ public boolean test(SketchController controller) {
+ if (controller.isReadOnly()) {
+ return false;
+ }
+ if (controller.getSketch().isModified()) {
+ return PreferencesData.getBoolean("editor.save_on_verify");
+ }
+ return true;
+ }
+ }
+
+ final Base base;
// otherwise, if the window is resized with the message label
// set to blank, it's preferredSize() will be fukered
- static protected final String EMPTY =
+ private static final String EMPTY =
" " +
" " +
" ";
/** Command on Mac OS X, Ctrl on Windows and Linux */
- static final int SHORTCUT_KEY_MASK =
+ 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 =
@@ -72,17 +174,17 @@ public class Editor extends JFrame implements RunnerListener {
*/
boolean untitled;
- PageFormat pageFormat;
- PrinterJob printerJob;
+ private PageFormat pageFormat;
// file, sketch, and tools menus for re-inserting items
- JMenu fileMenu;
- JMenu sketchMenu;
- JMenu toolsMenu;
+ private JMenu fileMenu;
+ private JMenu toolsMenu;
- int numTools = 0;
+ private int numTools = 0;
- EditorToolbar toolbar;
+ static 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;
@@ -90,66 +192,56 @@ public class Editor extends JFrame implements RunnerListener {
static JMenu examplesMenu;
static JMenu importMenu;
- // these menus are shared so that the board and serial port selections
- // are the same for all windows (since the board and serial port that are
- // actually used are determined by the preferences, which are shared)
- static JMenu boardsMenu;
- static JMenu serialMenu;
+ private static JMenu portMenu;
+
+ static volatile AbstractMonitor serialMonitor;
+ static AbstractMonitor serialPlotter;
- static SerialMenuListener serialMenuListener;
- static SerialMonitor serialMonitor;
-
- EditorHeader header;
+ final EditorHeader header;
EditorStatus status;
EditorConsole console;
- JSplitPane splitPane;
- JPanel consolePanel;
-
- JLabel lineNumberComponent;
+ private JSplitPane splitPane;
// currently opened program
+ SketchController sketchController;
Sketch sketch;
EditorLineStatus lineStatus;
- JEditorPane editorPane;
-
- JEditTextArea textarea;
- EditorListener listener;
+ //JEditorPane editorPane;
+
+ /** Contains all EditorTabs, of which only one will be visible */
+ private JPanel codePanel;
- // runtime information and window placement
- Point sketchWindowLocation;
//Runner runtime;
- JMenuItem exportAppItem;
- JMenuItem saveMenuItem;
- JMenuItem saveAsMenuItem;
+ private JMenuItem saveMenuItem;
+ private JMenuItem saveAsMenuItem;
- boolean running;
//boolean presenting;
- boolean uploading;
+ static private boolean uploading;
// undo fellers
- JMenuItem undoItem, redoItem;
- protected UndoAction undoAction;
- protected RedoAction redoAction;
- UndoManager undo;
- // used internally, and only briefly
- CompoundEdit compoundEdit;
+ private JMenuItem undoItem;
+ private JMenuItem redoItem;
- FindReplace find;
+ private FindReplace find;
Runnable runHandler;
Runnable presentHandler;
- Runnable stopHandler;
- Runnable exportHandler;
- Runnable exportAppHandler;
+ private Runnable runAndSaveHandler;
+ private Runnable presentAndSaveHandler;
+ private UploadHandler uploadHandler;
+ private UploadHandler uploadUsingProgrammerHandler;
+ private Runnable timeoutUploadHandler;
+ private Map internalToolCache = new HashMap();
- public Editor(Base ibase, String path, int[] location) {
+ 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);
@@ -170,45 +262,42 @@ public void windowClosing(WindowEvent e) {
// When bringing a window to front, let the Base know
addWindowListener(new WindowAdapter() {
public void windowActivated(WindowEvent e) {
-// System.err.println("activate"); // not coming through
base.handleActivated(Editor.this);
- // re-add the sub-menus that are shared by all windows
- fileMenu.insert(sketchbookMenu, 2);
- fileMenu.insert(examplesMenu, 3);
- sketchMenu.insert(importMenu, 4);
- toolsMenu.insert(boardsMenu, numTools);
- toolsMenu.insert(serialMenu, numTools + 1);
}
// added for 1.0.5
// http://dev.processing.org/bugs/show_bug.cgi?id=1260
public void windowDeactivated(WindowEvent e) {
-// System.err.println("deactivate"); // not coming through
- fileMenu.remove(sketchbookMenu);
- fileMenu.remove(examplesMenu);
- sketchMenu.remove(importMenu);
- toolsMenu.remove(boardsMenu);
- toolsMenu.remove(serialMenu);
+ 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);
- if (serialMonitor == null)
- serialMonitor = new SerialMonitor(Preferences.get("serial.port"));
-
buildMenuBar();
// For rev 0120, placing things inside a JPanel
Container contentPain = getContentPane();
contentPain.setLayout(new BorderLayout());
- JPanel pain = new JPanel();
- pain.setLayout(new BorderLayout());
- contentPain.add(pain, BorderLayout.CENTER);
+ JPanel pane = new JPanel();
+ pane.setLayout(new BorderLayout());
+ contentPain.add(pane, BorderLayout.CENTER);
Box box = Box.createVerticalBox();
- Box upper = Box.createVerticalBox();
+ upper = Box.createVerticalBox();
if (toolbarMenu == null) {
toolbarMenu = new JMenu();
@@ -220,30 +309,27 @@ public void windowDeactivated(WindowEvent e) {
header = new EditorHeader(this);
upper.add(header);
- textarea = new JEditTextArea(new PdeTextAreaDefaults());
- textarea.setRightClickPopup(new TextAreaPopup());
- textarea.setHorizontalOffset(6);
-
// assemble console panel, consisting of status area and the console itself
- consolePanel = new JPanel();
+ JPanel consolePanel = new JPanel();
consolePanel.setLayout(new BorderLayout());
status = new EditorStatus(this);
consolePanel.add(status, BorderLayout.NORTH);
- console = new EditorConsole(this);
+ console = new EditorConsole(base);
+ console.setName("console");
// windows puts an ugly border on this guy
console.setBorder(null);
consolePanel.add(console, BorderLayout.CENTER);
- lineStatus = new EditorLineStatus(textarea);
+ lineStatus = new EditorLineStatus();
consolePanel.add(lineStatus, BorderLayout.SOUTH);
- upper.add(textarea);
- splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT,
- upper, consolePanel);
+ codePanel = new JPanel(new BorderLayout());
+ upper.add(codePanel);
+
+ splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, upper, consolePanel);
- splitPane.setOneTouchExpandable(true);
// repaint child panes while resizing
splitPane.setContinuousLayout(true);
// if window increases in size, give all of increase to
@@ -253,65 +339,47 @@ public void windowDeactivated(WindowEvent e) {
// to fix ugliness.. normally macosx java 1.3 puts an
// ugly white border around this object, so turn it off.
splitPane.setBorder(null);
-
- // the default size on windows is too small and kinda ugly
- int dividerSize = Preferences.getInteger("editor.divider.size");
- if (dividerSize != 0) {
- splitPane.setDividerSize(dividerSize);
- }
-
- splitPane.setMinimumSize(new Dimension(600, 400));
+ // 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);
- pain.add(box);
+ // listener = new EditorListener(this, textarea);
+ pane.add(box);
- // get shift down/up events so we can show the alt version of toolbar buttons
- textarea.addKeyListener(toolbar);
+ pane.setTransferHandler(new FileDropHandler());
- pain.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"))));
-// System.out.println("t1");
+ // Bring back the general options for the editor
+ applyPreferences();
// Finish preparing Editor (formerly found in Base)
pack();
-// System.out.println("t2");
-
// Set the window bounds and the divider location before setting it visible
- setPlacement(location);
-
-
- // If the window is resized too small this will resize it again to the
- // minimums. Adapted by Chris Lonnen from comments here:
- // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4320050
- // as a fix for http://dev.processing.org/bugs/show_bug.cgi?id=25
- final int minW = Preferences.getInteger("editor.window.width.min");
- final int minH = Preferences.getInteger("editor.window.height.min");
- addComponentListener(new java.awt.event.ComponentAdapter() {
- public void componentResized(ComponentEvent event) {
- setSize((getWidth() < minW) ? minW : getWidth(),
- (getHeight() < minH) ? minH : getHeight());
- }
- });
-
-// System.out.println("t3");
-
- // Bring back the general options for the editor
- applyPreferences();
-
-// System.out.println("t4");
+ setPlacement(storedLocation, defaultLocation);
// Open the document that was passed in
- boolean loaded = handleOpenInternal(path);
- if (!loaded) sketch = null;
-
-// System.out.println("t5");
+ boolean loaded = handleOpenInternal(file);
+ if (!loaded) sketchController = null;
- // All set, now show the window
- //setVisible(true);
+ // default the console output to the last opened editor
+ EditorConsole.setCurrentEditorConsole(console);
}
@@ -320,7 +388,7 @@ public void componentResized(ComponentEvent event) {
* window. Dragging files into the editor window is the same as using
* "Sketch → Add File" for each file.
*/
- class FileDropHandler extends TransferHandler {
+ private class FileDropHandler extends TransferHandler {
public boolean canImport(JComponent dest, DataFlavor[] flavors) {
return true;
}
@@ -334,11 +402,10 @@ public boolean importData(JComponent src, Transferable transferable) {
new DataFlavor("text/uri-list;class=java.lang.String");
if (transferable.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
- java.util.List list = (java.util.List)
+ List list = (List)
transferable.getTransferData(DataFlavor.javaFileListFlavor);
- for (int i = 0; i < list.size(); i++) {
- File file = (File) list.get(i);
- if (sketch.addFile(file)) {
+ for (File file : list) {
+ if (sketchController.addFile(file)) {
successful++;
}
}
@@ -347,16 +414,16 @@ public boolean importData(JComponent src, Transferable transferable) {
// this method of moving files.
String data = (String)transferable.getTransferData(uriListFlavor);
String[] pieces = PApplet.splitTokens(data, "\r\n");
- for (int i = 0; i < pieces.length; i++) {
- if (pieces[i].startsWith("#")) continue;
+ for (String piece : pieces) {
+ if (piece.startsWith("#")) continue;
String path = null;
- if (pieces[i].startsWith("file:///")) {
- path = pieces[i].substring(7);
- } else if (pieces[i].startsWith("file:/")) {
- path = pieces[i].substring(5);
+ if (piece.startsWith("file:///")) {
+ path = piece.substring(7);
+ } else if (piece.startsWith("file:/")) {
+ path = piece.substring(5);
}
- if (sketch.addFile(new File(path))) {
+ if (sketchController.addFile(new File(path))) {
successful++;
}
}
@@ -367,29 +434,36 @@ public boolean importData(JComponent src, Transferable transferable) {
}
if (successful == 0) {
- statusError("No files were added to the sketch.");
+ statusError(tr("No files were added to the sketch."));
} else if (successful == 1) {
- statusNotice("One file added to the sketch.");
+ statusNotice(tr("One file added to the sketch."));
} else {
- statusNotice(successful + " files added to the sketch.");
+ 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);
+ }
+ }
- protected void setPlacement(int[] location) {
+ 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[5];
+ int[] location = new int[6];
// Get the dimensions of the Frame
Rectangle bounds = getBounds();
@@ -400,23 +474,12 @@ protected int[] getPlacement() {
// Get the current placement of the divider
location[4] = splitPane.getDividerLocation();
+ location[5] = getExtendedState() & MAXIMIZED_BOTH;
return location;
}
- /**
- * Hack for #@#)$(* Mac OS X 10.2.
- *
- * This appears to only be required on OS X 10.2, and is not
- * even being called on later versions of OS X or Windows.
- */
-// public Dimension getMinimumSize() {
-// //System.out.println("getting minimum size");
-// return new Dimension(500, 550);
-// }
-
-
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
@@ -425,296 +488,327 @@ protected int[] getPlacement() {
* the app is just starting up, or the user just finished messing
* with things in the Preferences window.
*/
- protected void applyPreferences() {
-
- // apply the setting for 'use external editor'
- boolean external = Preferences.getBoolean("editor.external");
-
- textarea.setEditable(!external);
+ public void applyPreferences() {
+ boolean external = PreferencesData.getBoolean("editor.external");
saveMenuItem.setEnabled(!external);
saveAsMenuItem.setEnabled(!external);
-
- TextAreaPainter painter = textarea.getPainter();
- if (external) {
- // disable line highlight and turn off the caret when disabling
- Color color = Theme.getColor("editor.external.bgcolor");
- painter.setBackground(color);
- painter.setLineHighlightEnabled(false);
- textarea.setCaretVisible(false);
-
- } else {
- Color color = Theme.getColor("editor.bgcolor");
- painter.setBackground(color);
- boolean highlight = Preferences.getBoolean("editor.linehighlight");
- painter.setLineHighlightEnabled(highlight);
- textarea.setCaretVisible(true);
+ for (EditorTab tab: tabs) {
+ tab.applyPreferences();
+ }
+ console.applyPreferences();
+ if (serialMonitor != null) {
+ serialMonitor.applyPreferences();
}
-
- // apply changes to the font size for the editor
- //TextAreaPainter painter = textarea.getPainter();
- painter.setFont(Preferences.getFont("editor.font"));
- //Font font = painter.getFont();
- //textarea.getPainter().setFont(new Font("Courier", Font.PLAIN, 36));
-
- // in case tab expansion stuff has changed
- listener.applyPreferences();
-
- // in case moved to a new location
- // For 0125, changing to async version (to be implemented later)
- //sketchbook.rebuildMenus();
- // For 0126, moved into Base, which will notify all editors.
- //base.rebuildMenusAsync();
}
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
- protected void buildMenuBar() {
+ private void buildMenuBar() {
JMenuBar menubar = new JMenuBar();
- menubar = new JMenuBar();
- menubar.add(buildFileMenu());
+ final JMenu fileMenu = buildFileMenu();
+ fileMenu.addMenuListener(new StubMenuListener() {
+ @Override
+ public void menuSelected(MenuEvent e) {
+ List components = Arrays.asList(fileMenu.getMenuComponents());
+ if (!components.contains(sketchbookMenu)) {
+ fileMenu.insert(sketchbookMenu, 3);
+ }
+ if (!components.contains(examplesMenu)) {
+ fileMenu.insert(examplesMenu, 4);
+ }
+ fileMenu.revalidate();
+ validate();
+ }
+ });
+ menubar.add(fileMenu);
+
menubar.add(buildEditMenu());
- menubar.add(buildSketchMenu());
- menubar.add(buildToolsMenu());
+
+ 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.getMenuComponents());
+ 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);
}
- protected JMenu buildFileMenu() {
+ private JMenu buildFileMenu() {
JMenuItem item;
- fileMenu = new JMenu("File");
+ fileMenu = new JMenu(tr("File"));
+ fileMenu.setMnemonic(KeyEvent.VK_F);
- item = newJMenuItem("New", 'N');
- item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- base.handleNew();
- }
- });
+ item = newJMenuItem(tr("New"), 'N');
+ item.addActionListener(event -> {
+ try {
+ base.handleNew();
+ } catch (Exception e1) {
+ e1.printStackTrace();
+ }
+ });
fileMenu.add(item);
- item = Editor.newJMenuItem("Open...", 'O');
- item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- base.handleOpenPrompt();
- }
- });
+ item = Editor.newJMenuItem(tr("Open..."), 'O');
+ item.addActionListener(event -> {
+ try {
+ base.handleOpenPrompt();
+ } catch (Exception e1) {
+ e1.printStackTrace();
+ }
+ });
fileMenu.add(item);
+ base.rebuildRecentSketchesMenuItems();
+ recentSketchesMenu = new JMenu(tr("Open Recent"));
+ SwingUtilities.invokeLater(() -> rebuildRecentSketchesMenu());
+ fileMenu.add(recentSketchesMenu);
+
if (sketchbookMenu == null) {
- sketchbookMenu = new JMenu("Sketchbook");
+ sketchbookMenu = new JMenu(tr("Sketchbook"));
+ MenuScroller.setScrollerFor(sketchbookMenu);
base.rebuildSketchbookMenu(sketchbookMenu);
}
fileMenu.add(sketchbookMenu);
if (examplesMenu == null) {
- examplesMenu = new JMenu("Examples");
+ examplesMenu = new JMenu(tr("Examples"));
+ MenuScroller.setScrollerFor(examplesMenu);
base.rebuildExamplesMenu(examplesMenu);
}
fileMenu.add(examplesMenu);
- item = Editor.newJMenuItem("Close", 'W');
- item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- base.handleClose(Editor.this);
- }
- });
+ item = Editor.newJMenuItem(tr("Close"), 'W');
+ item.addActionListener(event -> base.handleClose(Editor.this));
fileMenu.add(item);
- saveMenuItem = newJMenuItem("Save", 'S');
- saveMenuItem.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- handleSave(false);
- }
- });
+ saveMenuItem = newJMenuItem(tr("Save"), 'S');
+ saveMenuItem.addActionListener(event -> handleSave(false));
fileMenu.add(saveMenuItem);
- saveAsMenuItem = newJMenuItemShift("Save As...", 'S');
- saveAsMenuItem.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- handleSaveAs();
- }
- });
+ saveAsMenuItem = newJMenuItemShift(tr("Save As..."), 'S');
+ saveAsMenuItem.addActionListener(event -> handleSaveAs());
fileMenu.add(saveAsMenuItem);
- item = newJMenuItem("Upload to I/O Board", 'U');
- item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- handleExport(false);
- }
- });
- fileMenu.add(item);
-
-// item = newJMenuItemShift("Upload to I/O Board (verbose)", 'U');
-// item.addActionListener(new ActionListener() {
-// public void actionPerformed(ActionEvent e) {
-// handleExport(true);
-// }
-// });
-// fileMenu.add(item);
-
fileMenu.addSeparator();
- item = newJMenuItemShift("Page Setup", 'P');
- item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- handlePageSetup();
- }
- });
+ item = newJMenuItemShift(tr("Page Setup"), 'P');
+ item.addActionListener(event -> handlePageSetup());
fileMenu.add(item);
- item = newJMenuItem("Print", 'P');
- item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- handlePrint();
- }
- });
+ item = newJMenuItem(tr("Print"), 'P');
+ item.addActionListener(event -> handlePrint());
fileMenu.add(item);
// macosx already has its own preferences and quit menu
- if (!Base.isMacOS()) {
+ if (!OSUtils.hasMacOSStyleMenus()) {
fileMenu.addSeparator();
- item = newJMenuItem("Preferences", ',');
- item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- base.handlePrefs();
- }
- });
+ item = newJMenuItem(tr("Preferences"), ',');
+ item.addActionListener(event -> base.handlePrefs());
fileMenu.add(item);
fileMenu.addSeparator();
- item = newJMenuItem("Quit", 'Q');
- item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- base.handleQuit();
- }
- });
+ item = newJMenuItem(tr("Quit"), 'Q');
+ item.addActionListener(event -> base.handleQuit());
fileMenu.add(item);
}
return fileMenu;
}
+ public void rebuildRecentSketchesMenu() {
+ recentSketchesMenu.removeAll();
+ for (JMenuItem recentSketchMenuItem : base.getRecentSketchesMenuItems()) {
+ recentSketchesMenu.add(recentSketchMenuItem);
+ }
+ }
- protected JMenu buildSketchMenu() {
- JMenuItem item;
- sketchMenu = new JMenu("Sketch");
+ private void buildSketchMenu(JMenu sketchMenu) {
+ sketchMenu.removeAll();
- item = newJMenuItem("Verify / Compile", 'R');
- item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- handleRun(false);
- }
- });
+ JMenuItem item = newJMenuItem(tr("Verify/Compile"), 'R');
+ item.addActionListener(event -> handleRun(false, presentHandler, runHandler));
sketchMenu.add(item);
-// item = newJMenuItemShift("Verify / Compile (verbose)", 'R');
-// item.addActionListener(new ActionListener() {
-// public void actionPerformed(ActionEvent e) {
-// handleRun(true);
-// }
-// });
-// sketchMenu.add(item);
+ item = newJMenuItem(tr("Upload"), 'U');
+ item.addActionListener(event -> handleExport(false));
+ sketchMenu.add(item);
- item = new JMenuItem("Stop");
- item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- handleStop();
- }
- });
+ item = newJMenuItemShift(tr("Upload Using Programmer"), 'U');
+ item.addActionListener(event -> handleExport(true));
+ sketchMenu.add(item);
+
+ item = newJMenuItemAlt(tr("Export compiled Binary"), 'S');
+ item.addActionListener(event -> {
+ if (!(new CanExportInSketchFolder().test(sketchController))) {
+ System.out.println(tr("Export canceled, changes must first be saved."));
+ return;
+ }
+ handleRun(false, new CanExportInSketchFolder(), presentAndSaveHandler, runAndSaveHandler);
+
+ });
sketchMenu.add(item);
+// item = new JMenuItem("Stop");
+// item.addActionListener(event -> handleStop());
+// sketchMenu.add(item);
+
sketchMenu.addSeparator();
+ item = newJMenuItem(tr("Show Sketch Folder"), 'K');
+ item.addActionListener(event -> Base.openFolder(sketch.getFolder()));
+ sketchMenu.add(item);
+ item.setEnabled(Base.openFolderAvailable());
+
if (importMenu == null) {
- importMenu = new JMenu("Import Library...");
+ importMenu = new JMenu(tr("Include Library"));
+ MenuScroller.setScrollerFor(importMenu);
base.rebuildImportMenu(importMenu);
}
sketchMenu.add(importMenu);
- item = newJMenuItem("Show Sketch Folder", 'K');
- item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- Base.openFolder(sketch.getFolder());
- }
- });
+ item = new JMenuItem(tr("Add File..."));
+ item.addActionListener(event -> sketchController.handleAddFile());
sketchMenu.add(item);
- item.setEnabled(Base.openFolderAvailable());
+ }
- item = new JMenuItem("Add File...");
- item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- sketch.handleAddFile();
- }
- });
- sketchMenu.add(item);
- return sketchMenu;
- }
+ private JMenu buildToolsMenu() {
+ toolsMenu = new JMenu(tr("Tools"));
+ toolsMenu.setMnemonic(KeyEvent.VK_T);
+ addInternalTools(toolsMenu);
- protected JMenu buildToolsMenu() {
- toolsMenu = new JMenu("Tools");
- JMenu menu = toolsMenu;
- JMenuItem item;
+ JMenuItem item = newJMenuItemShift(tr("Manage Libraries..."), 'I');
+ item.addActionListener(e -> base.openLibraryManager("", ""));
+ toolsMenu.add(item);
- addInternalTools(menu);
-
- item = newJMenuItemShift("Serial Monitor", 'M');
- item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- handleSerial();
- }
- });
- menu.add(item);
-
- addTools(menu, Base.getToolsFolder());
- File sketchbookTools = new File(Base.getSketchbookFolder(), "tools");
- addTools(menu, sketchbookTools);
+ item = newJMenuItemShift(tr("Serial Monitor"), 'M');
+ item.addActionListener(e -> handleSerial());
+ toolsMenu.add(item);
+
+ item = newJMenuItemShift(tr("Serial Plotter"), 'L');
+ item.addActionListener(e -> handlePlotter());
+ toolsMenu.add(item);
+
+ addTools(toolsMenu, BaseNoGui.getToolsFolder());
+ File sketchbookTools = new File(BaseNoGui.getSketchbookFolder(), "tools");
+ addTools(toolsMenu, sketchbookTools);
+
+ toolsMenu.addSeparator();
+
+ numTools = toolsMenu.getItemCount();
- menu.addSeparator();
-
- numTools = menu.getItemCount();
-
// XXX: DAM: these should probably be implemented using the Tools plugin
// API, if possible (i.e. if it supports custom actions, etc.)
-
- if (boardsMenu == null) {
- boardsMenu = new JMenu("Board");
- base.rebuildBoardsMenu(boardsMenu);
- }
- menu.add(boardsMenu);
-
- if (serialMenuListener == null)
- serialMenuListener = new SerialMenuListener();
- if (serialMenu == null)
- serialMenu = new JMenu("Serial Port");
- populateSerialMenu();
- menu.add(serialMenu);
-
- menu.addSeparator();
- JMenu bootloaderMenu = new JMenu("Burn Bootloader");
- base.rebuildBurnBootloaderMenu(bootloaderMenu);
- menu.add(bootloaderMenu);
-
- menu.addMenuListener(new MenuListener() {
- public void menuCanceled(MenuEvent e) {}
- public void menuDeselected(MenuEvent e) {}
+ 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"));
+ MenuScroller.setScrollerFor(programmersMenu);
+ 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 JMenuItem getSelectedItemRecursive(JMenu menu) {
+ int count = menu.getItemCount();
+ for (int i=0; i < count; i++) {
+ JMenuItem item = menu.getItem(i);
+
+ if ((item instanceof JMenu))
+ item = getSelectedItemRecursive((JMenu)item);
+
+ if (item != null && item.isSelected())
+ return item;
+ }
+ return null;
+ }
+
public void menuSelected(MenuEvent e) {
//System.out.println("Tools menu selected.");
- populateSerialMenu();
+ 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);
+
+ JMenuItem item = getSelectedItemRecursive(menu);
+ String sel = item != null ? item.getText() : null;
+ 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 menu;
+ return toolsMenu;
}
- protected void addTools(JMenu menu, File sourceFolder) {
- HashMap toolItems = new HashMap();
+ 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) {
@@ -731,8 +825,8 @@ public boolean accept(File folder) {
return;
}
- for (int i = 0; i < folders.length; i++) {
- File toolDirectory = new File(folders[i], "tool");
+ for (File folder : folders) {
+ File toolDirectory = new File(folder, "tool");
try {
// add dir to classpath for .classes
@@ -742,7 +836,7 @@ public boolean accept(File folder) {
File[] archives = toolDirectory.listFiles(new FilenameFilter() {
public boolean accept(File dir, String name) {
return (name.toLowerCase().endsWith(".jar") ||
- name.toLowerCase().endsWith(".zip"));
+ name.toLowerCase().endsWith(".zip"));
}
});
@@ -753,8 +847,8 @@ public boolean accept(File dir, String name) {
URLClassLoader loader = new URLClassLoader(urlList);
String className = null;
- for (int j = 0; j < archives.length; j++) {
- className = findClassInZipFile(folders[i].getName(), archives[j]);
+ for (File archive : archives) {
+ className = findClassInZipFile(folder.getName(), archive);
if (className != null) break;
}
@@ -791,11 +885,9 @@ public boolean accept(File dir, String name) {
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();
- }
+ item.addActionListener(event -> {
+ SwingUtilities.invokeLater(tool);
+ //new Thread(tool).start();
});
//menu.add(item);
toolItems.put(title, item);
@@ -804,23 +896,24 @@ public void actionPerformed(ActionEvent e) {
e.printStackTrace();
}
}
- ArrayList toolList = new ArrayList(toolItems.keySet());
+ ArrayList toolList = new ArrayList<>(toolItems.keySet());
if (toolList.size() == 0) return;
menu.addSeparator();
Collections.sort(toolList);
for (String title : toolList) {
- menu.add((JMenuItem) toolItems.get(title));
+ menu.add(toolItems.get(title));
}
}
- protected String findClassInZipFile(String base, File file) {
+ private String findClassInZipFile(String base, File file) {
// Class file to search for
String classFileName = "/" + base + ".class";
+ ZipFile zipFile = null;
try {
- ZipFile zipFile = new ZipFile(file);
+ zipFile = new ZipFile(file);
Enumeration> entries = zipFile.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = (ZipEntry) entries.nextElement();
@@ -840,25 +933,32 @@ protected String findClassInZipFile(String base, File file) {
} catch (IOException e) {
//System.err.println("Ignoring " + filename + " (" + e.getMessage() + ")");
e.printStackTrace();
- }
- return null;
- }
+ } 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);
+ }
- protected JMenuItem createToolMenuItem(String className) {
+ JMenuItem createToolMenuItem(String className) {
try {
- Class> toolClass = Class.forName(className);
- final Tool tool = (Tool) toolClass.newInstance();
+ final Tool tool = getOrCreateToolInstance(className);
JMenuItem item = new JMenuItem(tool.getMenuTitle());
tool.init(Editor.this);
- item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- SwingUtilities.invokeLater(tool);
- }
- });
+ item.addActionListener(event -> SwingUtilities.invokeLater(tool));
return item;
} catch (Exception e) {
@@ -867,11 +967,29 @@ public void actionPerformed(ActionEvent e) {
}
}
+ private Tool getOrCreateToolInstance(String className) {
+ Tool internalTool = internalToolCache.get(className);
+ if (internalTool == null) {
+ try {
+ Class> toolClass = Class.forName(className);
+ internalTool = (Tool) toolClass.newInstance();
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ }
+ internalToolCache.put(className, internalTool);
+ }
+ return internalTool;
+ }
- protected JMenu addInternalTools(JMenu menu) {
+ private void addInternalTools(JMenu menu) {
JMenuItem item;
- item = createToolMenuItem("processing.app.tools.AutoFormat");
+ 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);
@@ -880,226 +998,178 @@ protected JMenu addInternalTools(JMenu menu) {
//menu.add(createToolMenuItem("processing.app.tools.ColorSelector"));
menu.add(createToolMenuItem("processing.app.tools.Archiver"));
menu.add(createToolMenuItem("processing.app.tools.FixEncoding"));
-
-// // These are temporary entries while Android mode is being worked out.
-// // The mode will not be in the tools menu, and won't involve a cmd-key
-// if (!Base.RELEASE) {
-// item = createToolMenuItem("processing.app.tools.android.AndroidTool");
-// item.setAccelerator(KeyStroke.getKeyStroke('D', modifiers));
-// menu.add(item);
-// menu.add(createToolMenuItem("processing.app.tools.android.Reset"));
-// }
-
- return menu;
}
- class SerialMenuListener implements ActionListener {
- //public SerialMenuListener() { }
-
- public void actionPerformed(ActionEvent e) {
- selectSerialPort(((JCheckBoxMenuItem)e.getSource()).getText());
- }
-
- /*
- public void actionPerformed(ActionEvent e) {
- System.out.println(e.getSource());
- String name = e.getActionCommand();
- PdeBase.properties.put("serial.port", name);
- System.out.println("set to " + get("serial.port"));
- //editor.skOpen(path + File.separator + name, name);
- // need to push "serial.port" into PdeBase.properties
- }
- */
- }
-
- protected void selectSerialPort(String name) {
- if(serialMenu == null) {
- System.out.println("serialMenu is null");
+ private void selectSerialPort(String name) {
+ if(portMenu == null) {
+ System.out.println(tr("serialMenu is null"));
return;
}
if (name == null) {
- System.out.println("name is null");
+ System.out.println(tr("name is null"));
return;
}
JCheckBoxMenuItem selection = null;
- for (int i = 0; i < serialMenu.getItemCount(); i++) {
- JCheckBoxMenuItem item = ((JCheckBoxMenuItem)serialMenu.getItem(i));
- if (item == null) {
- System.out.println("name is null");
+ for (int i = 0; i < portMenu.getItemCount(); i++) {
+ JMenuItem menuItem = portMenu.getItem(i);
+ if (!(menuItem instanceof JCheckBoxMenuItem)) {
continue;
}
- item.setState(false);
- if (name.equals(item.getText())) selection = item;
+ 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());
- Preferences.set("serial.port", name);
- serialMonitor.closeSerialPort();
- serialMonitor.setVisible(false);
- serialMonitor = new SerialMonitor(Preferences.get("serial.port"));
- //System.out.println("set to " + get("serial.port"));
- }
-
- protected void populateSerialMenu() {
- // getting list of ports
-
- JMenuItem rbMenuItem;
-
- //System.out.println("Clearing serial port menu.");
-
- serialMenu.removeAll();
- boolean empty = true;
-
- try
- {
- for (Enumeration enumeration = CommPortIdentifier.getPortIdentifiers(); enumeration.hasMoreElements();)
- {
- CommPortIdentifier commportidentifier = (CommPortIdentifier)enumeration.nextElement();
- //System.out.println("Found communication port: " + commportidentifier);
- if (commportidentifier.getPortType() == CommPortIdentifier.PORT_SERIAL)
- {
- //System.out.println("Adding port to serial port menu: " + commportidentifier);
- String curr_port = commportidentifier.getName();
- rbMenuItem = new JCheckBoxMenuItem(curr_port, curr_port.equals(Preferences.get("serial.port")));
- rbMenuItem.addActionListener(serialMenuListener);
- //serialGroup.add(rbMenuItem);
- serialMenu.add(rbMenuItem);
- empty = false;
- }
+ BaseNoGui.selectSerialPort(name);
+ try {
+ boolean reopenMonitor = ((serialMonitor != null && serialMonitor.isVisible()) ||
+ serialPlotter != null && serialPlotter.isVisible());
+ if (serialMonitor != null) {
+ serialMonitor.close();
}
- if (!empty) {
- //System.out.println("enabling the serialMenu");
- serialMenu.setEnabled(true);
+ if (serialPlotter != null) {
+ serialPlotter.close();
}
+ if (reopenMonitor) {
+ handleSerial();
+ }
+ } catch (Exception e) {
+ // ignore
+ }
+
+ onBoardOrPortChange();
+ base.onBoardOrPortChange();
+
+ //System.out.println("set to " + get("serial.port"));
+ }
+
+ class BoardPortJCheckBoxMenuItem extends JCheckBoxMenuItem {
+ private BoardPort port;
+ public BoardPortJCheckBoxMenuItem(BoardPort port) {
+ super();
+ this.port = port;
+ setText(toString());
+ addActionListener(e -> {
+ selectSerialPort(port.getAddress());
+ base.onBoardOrPortChange();
+ });
}
- catch (Exception exception)
- {
- System.out.println("error retrieving port list");
- exception.printStackTrace();
+ @Override
+ public String toString() {
+ // This is required for serialPrompt()
+ String label = port.getLabel();
+ if (port.getBoardName() != null && !port.getBoardName().isEmpty()) {
+ label += " (" + port.getBoardName() + ")";
+ }
+ return label;
}
-
- if (serialMenu.getItemCount() == 0) {
- serialMenu.setEnabled(false);
+ }
+
+ private void populatePortMenu() {
+ final List PROTOCOLS_ORDER = Arrays.asList("serial", "network");
+ final List PROTOCOLS_LABELS = Arrays.asList(tr("Serial ports"), tr("Network ports"));
+
+ portMenu.removeAll();
+
+ String selectedPort = PreferencesData.get("serial.port");
+
+ List ports = Base.getDiscoveryManager().discovery();
+
+ ports = platform.filterPorts(ports, PreferencesData.getBoolean("serial.ports.showall"));
+
+ ports.stream() //
+ .filter(port -> port.getProtocolLabel() == null || port.getProtocolLabel().isEmpty())
+ .forEach(port -> {
+ int labelIdx = PROTOCOLS_ORDER.indexOf(port.getProtocol());
+ if (labelIdx != -1) {
+ port.setProtocolLabel(PROTOCOLS_LABELS.get(labelIdx));
+ } else {
+ port.setProtocolLabel(port.getProtocol());
+ }
+ });
+
+ Collections.sort(ports, (port1, port2) -> {
+ String pr1 = port1.getProtocol();
+ String pr2 = port2.getProtocol();
+ int prIdx1 = PROTOCOLS_ORDER.contains(pr1) ? PROTOCOLS_ORDER.indexOf(pr1) : 999;
+ int prIdx2 = PROTOCOLS_ORDER.contains(pr2) ? PROTOCOLS_ORDER.indexOf(pr2) : 999;
+ int r = prIdx1 - prIdx2;
+ if (r != 0)
+ return r;
+ r = port1.getProtocolLabel().compareTo(port2.getProtocolLabel());
+ if (r != 0)
+ return r;
+ return port1.getAddress().compareTo(port2.getAddress());
+ });
+
+ String lastProtocol = "";
+ String lastProtocolLabel = "";
+ for (BoardPort port : ports) {
+ if (!port.getProtocol().equals(lastProtocol) || !port.getProtocolLabel().equals(lastProtocolLabel)) {
+ if (!lastProtocol.isEmpty()) {
+ portMenu.addSeparator();
+ }
+ lastProtocol = port.getProtocol();
+ lastProtocolLabel = port.getProtocolLabel();
+ JMenuItem item = new JMenuItem(tr(lastProtocolLabel));
+ item.setEnabled(false);
+ portMenu.add(item);
+ }
+ String address = port.getAddress();
+
+ BoardPortJCheckBoxMenuItem item = new BoardPortJCheckBoxMenuItem(port);
+ item.setSelected(address.equals(selectedPort));
+ portMenu.add(item);
}
- //serialMenu.addSeparator();
- //serialMenu.add(item);
+ portMenu.setEnabled(portMenu.getMenuComponentCount() > 0);
}
- protected 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("Help ");
- JMenuItem item;
+ private JMenu buildHelpMenu() {
+ JMenu menu = new JMenu(tr("Help"));
+ menu.setMnemonic(KeyEvent.VK_H);
- /*
- // 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();
- }
- });
+ JMenuItem item = new JMenuItem(tr("Getting Started"));
+ item.addActionListener(event -> Base.openURL("https://www.arduino.cc/en/Guide"));
menu.add(item);
- */
- item = new JMenuItem("Getting Started");
- item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- Base.showGettingStarted();
- }
- });
+ item = new JMenuItem(tr("Environment"));
+ item.addActionListener(event -> Base.openURL("https://www.arduino.cc/en/Guide/Environment"));
menu.add(item);
- item = new JMenuItem("Environment");
- item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- Base.showEnvironment();
- }
- });
+ item = new JMenuItem(tr("Troubleshooting"));
+ item.addActionListener(event -> Base.openURL("https://support.arduino.cc/hc/en-us"));
menu.add(item);
- item = new JMenuItem("Troubleshooting");
- item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- Base.showTroubleshooting();
- }
- });
+ item = new JMenuItem(tr("Reference"));
+ item.addActionListener(event -> Base.openURL("https://www.arduino.cc/reference/en/"));
menu.add(item);
- item = new JMenuItem("Reference");
- item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- Base.showReference();
- }
- });
- menu.add(item);
+ menu.addSeparator();
- item = newJMenuItemShift("Find in Reference", 'F');
- item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- if (textarea.isSelectionActive()) {
- handleFindReference();
- }
- }
- });
+ item = newJMenuItemShift(tr("Find in Reference"), 'F');
+ item.addActionListener(event -> handleFindReference(getCurrentTab().getCurrentKeyword()));
menu.add(item);
- item = new JMenuItem("Frequently Asked Questions");
- item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- Base.showFAQ();
- }
- });
+ item = new JMenuItem(tr("Frequently Asked Questions"));
+ item.addActionListener(event -> Base.openURL("https://support.arduino.cc/hc/en-us"));
menu.add(item);
- item = new JMenuItem("Visit Arduino.cc");
- item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- Base.openURL("http://arduino.cc/");
- }
- });
+ item = new JMenuItem(tr("Visit Arduino.cc"));
+ item.addActionListener(event -> Base.openURL("https://www.arduino.cc/"));
menu.add(item);
// macosx already has its own about menu
- if (!Base.isMacOS()) {
+ if (!OSUtils.hasMacOSStyleMenus()) {
menu.addSeparator();
- item = new JMenuItem("About Arduino");
- item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- base.handleAbout();
- }
- });
+ item = new JMenuItem(tr("About Arduino"));
+ item.addActionListener(event -> base.handleAbout());
menu.add(item);
}
@@ -1107,133 +1177,154 @@ public void actionPerformed(ActionEvent e) {
}
- protected JMenu buildEditMenu() {
- JMenu menu = new JMenu("Edit");
- JMenuItem item;
+ private JMenu buildEditMenu() {
+ JMenu menu = new JMenu(tr("Edit"));
+ menu.setName("menuEdit");
+ menu.setMnemonic(KeyEvent.VK_E);
- undoItem = newJMenuItem("Undo", 'Z');
- undoItem.addActionListener(undoAction = new UndoAction());
+ undoItem = newJMenuItem(tr("Undo"), 'Z');
+ undoItem.setName("menuEditUndo");
+ undoItem.addActionListener(event -> getCurrentTab().handleUndo());
menu.add(undoItem);
- redoItem = newJMenuItem("Redo", 'Y');
- redoItem.addActionListener(redoAction = new RedoAction());
+ if (!OSUtils.isMacOS()) {
+ redoItem = newJMenuItem(tr("Redo"), 'Y');
+ } else {
+ redoItem = newJMenuItemShift(tr("Redo"), 'Z');
+ }
+ redoItem.setName("menuEditRedo");
+ redoItem.addActionListener(event -> getCurrentTab().handleRedo());
menu.add(redoItem);
menu.addSeparator();
- // TODO "cut" and "copy" should really only be enabled
- // if some text is currently selected
- item = newJMenuItem("Cut", 'X');
- item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- handleCut();
- }
- });
- menu.add(item);
+ JMenuItem cutItem = newJMenuItem(tr("Cut"), 'X');
+ cutItem.addActionListener(event -> getCurrentTab().handleCut());
+ menu.add(cutItem);
- item = newJMenuItem("Copy", 'C');
- item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- textarea.copy();
- }
- });
- menu.add(item);
+ JMenuItem copyItem = newJMenuItem(tr("Copy"), 'C');
+ copyItem.addActionListener(event -> getCurrentTab().getTextArea().copy());
+ menu.add(copyItem);
- item = newJMenuItemShift("Copy for Forum", 'C');
- item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
-// SwingUtilities.invokeLater(new Runnable() {
-// public void run() {
- new DiscourseFormat(Editor.this, false).show();
-// }
-// });
- }
- });
- menu.add(item);
+ JMenuItem copyForumItem = newJMenuItemShift(tr("Copy for Forum"), 'C');
+ copyForumItem.addActionListener(event -> getCurrentTab().handleDiscourseCopy());
+ menu.add(copyForumItem);
- item = newJMenuItemAlt("Copy as HTML", 'C');
- item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
-// SwingUtilities.invokeLater(new Runnable() {
-// public void run() {
- new DiscourseFormat(Editor.this, true).show();
-// }
-// });
- }
- });
- menu.add(item);
+ JMenuItem copyHTMLItem = newJMenuItemAlt(tr("Copy as HTML"), 'C');
+ copyHTMLItem.addActionListener(event -> getCurrentTab().handleHTMLCopy());
+ menu.add(copyHTMLItem);
- item = newJMenuItem("Paste", 'V');
- item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- textarea.paste();
- sketch.setModified(true);
- }
- });
- menu.add(item);
+ JMenuItem pasteItem = newJMenuItem(tr("Paste"), 'V');
+ pasteItem.addActionListener(event -> getCurrentTab().handlePaste());
+ menu.add(pasteItem);
- item = newJMenuItem("Select All", 'A');
- item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- textarea.selectAll();
- }
- });
- menu.add(item);
+ JMenuItem selectAllItem = newJMenuItem(tr("Select All"), 'A');
+ selectAllItem.addActionListener(event -> getCurrentTab().handleSelectAll());
+ menu.add(selectAllItem);
+
+ JMenuItem gotoLine = newJMenuItem(tr("Go to line..."), 'L');
+ gotoLine.addActionListener(event -> {
+ GoToLineNumber goToLineNumber = new GoToLineNumber(Editor.this);
+ goToLineNumber.setLocationRelativeTo(Editor.this);
+ goToLineNumber.setVisible(true);
+ });
+ menu.add(gotoLine);
menu.addSeparator();
- item = newJMenuItem("Comment/Uncomment", '/');
- item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- handleCommentUncomment();
- }
+ JMenuItem commentItem = newJMenuItem(tr("Comment/Uncomment"), PreferencesData.get("editor.keys.shortcut_comment", "/").charAt(0));
+ commentItem.addActionListener(event -> getCurrentTab().handleCommentUncomment());
+ menu.add(commentItem);
+
+ JMenuItem increaseIndentItem = new JMenuItem(tr("Increase Indent"));
+ increaseIndentItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0));
+ increaseIndentItem.addActionListener(event -> 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(event -> getCurrentTab().handleIndentOutdent(false));
+ menu.add(decreseIndentItem);
+
+ menu.addSeparator();
+
+ JMenuItem increaseFontSizeItem = newJMenuItem(tr("Increase Font Size"), KeyEvent.VK_PLUS);
+ increaseFontSizeItem.addActionListener(event -> base.handleFontSizeChange(1));
+ menu.add(increaseFontSizeItem);
+ // Many keyboards have '+' and '=' on the same key. Allowing "CTRL +",
+ // "CTRL SHIFT +" and "CTRL =" covers the generally expected behavior.
+ KeyStroke ctrlShiftEq = KeyStroke.getKeyStroke(KeyEvent.VK_EQUALS, SHORTCUT_KEY_MASK | ActionEvent.SHIFT_MASK);
+ menu.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(ctrlShiftEq, "IncreaseFontSize");
+ KeyStroke ctrlEq = KeyStroke.getKeyStroke(KeyEvent.VK_EQUALS, SHORTCUT_KEY_MASK);
+ menu.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(ctrlEq, "IncreaseFontSize");
+ menu.getActionMap().put("IncreaseFontSize", new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ base.handleFontSizeChange(1);
+ }
});
- menu.add(item);
- item = newJMenuItem("Increase Indent", ']');
- item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- handleIndentOutdent(true);
- }
+ JMenuItem decreaseFontSizeItem = newJMenuItem(tr("Decrease Font Size"), KeyEvent.VK_MINUS);
+ decreaseFontSizeItem.addActionListener(event -> base.handleFontSizeChange(-1));
+ menu.add(decreaseFontSizeItem);
+
+ menu.addSeparator();
+
+ JMenuItem findItem = newJMenuItem(tr("Find..."), 'F');
+ findItem.addActionListener(event -> {
+ 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(item);
+ menu.add(findItem);
- item = newJMenuItem("Decrease Indent", '[');
- item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- handleIndentOutdent(false);
- }
+ JMenuItem findNextItem = newJMenuItem(tr("Find Next"), 'G');
+ findNextItem.addActionListener(event -> {
+ if (find != null) {
+ find.findNext();
+ }
});
- menu.add(item);
+ menu.add(findNextItem);
- menu.addSeparator();
+ JMenuItem findPreviousItem = newJMenuItemShift(tr("Find Previous"), 'G');
+ findPreviousItem.addActionListener(event -> {
+ if (find != null) {
+ find.findPrevious();
+ }
+ });
+ menu.add(findPreviousItem);
- item = newJMenuItem("Find...", 'F');
- item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- if (find == null) {
- find = new FindReplace(Editor.this);
- }
- //new FindReplace(Editor.this).show();
- find.setVisible(true);
- //find.setVisible(true);
+ if (OSUtils.isMacOS()) {
+ JMenuItem useSelectionForFindItem = newJMenuItem(tr("Use Selection For Find"), 'E');
+ useSelectionForFindItem.addActionListener(event -> {
+ if (find == null) {
+ find = new FindReplace(Editor.this, Base.FIND_DIALOG_STATE);
}
+ find.setFindText(getCurrentTab().getSelectedText());
});
- menu.add(item);
+ menu.add(useSelectionForFindItem);
+ }
- // TODO find next should only be enabled after a
- // search has actually taken place
- item = newJMenuItem("Find Next", 'G');
- item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- if (find != null) {
- //find.find(true);
- //FindReplace find = new FindReplace(Editor.this); //.show();
- find.find(true);
- }
- }
- });
- menu.add(item);
+ 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;
}
@@ -1247,8 +1338,7 @@ public void actionPerformed(ActionEvent e) {
*/
static public JMenuItem newJMenuItem(String title, int what) {
JMenuItem menuItem = new JMenuItem(title);
- int modifiers = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
- menuItem.setAccelerator(KeyStroke.getKeyStroke(what, modifiers));
+ menuItem.setAccelerator(KeyStroke.getKeyStroke(what, SHORTCUT_KEY_MASK));
return menuItem;
}
@@ -1256,11 +1346,10 @@ static public JMenuItem newJMenuItem(String title, int what) {
/**
* Like newJMenuItem() but adds shift as a modifier for the key command.
*/
+ // Control + Shift + K seems to not be working on linux (Xubuntu 17.04, 2017-08-19)
static public JMenuItem newJMenuItemShift(String title, int what) {
JMenuItem menuItem = new JMenuItem(title);
- int modifiers = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
- modifiers |= ActionEvent.SHIFT_MASK;
- menuItem.setAccelerator(KeyStroke.getKeyStroke(what, modifiers));
+ menuItem.setAccelerator(KeyStroke.getKeyStroke(what, SHORTCUT_KEY_MASK | ActionEvent.SHIFT_MASK));
return menuItem;
}
@@ -1269,10 +1358,8 @@ static public JMenuItem newJMenuItemShift(String title, int what) {
* Same as newJMenuItem(), but adds the ALT (on Linux and Windows)
* or OPTION (on Mac OS X) key as a modifier.
*/
- static public JMenuItem newJMenuItemAlt(String title, int what) {
+ private static JMenuItem newJMenuItemAlt(String title, int what) {
JMenuItem menuItem = new JMenuItem(title);
- //int modifiers = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
- //menuItem.setAccelerator(KeyStroke.getKeyStroke(what, modifiers));
menuItem.setAccelerator(KeyStroke.getKeyStroke(what, SHORTCUT_ALT_KEY_MASK));
return menuItem;
}
@@ -1281,74 +1368,10 @@ static public JMenuItem newJMenuItemAlt(String title, int what) {
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
- class UndoAction extends AbstractAction {
- public UndoAction() {
- super("Undo");
- this.setEnabled(false);
- }
-
- public void actionPerformed(ActionEvent e) {
- try {
- undo.undo();
- } catch (CannotUndoException ex) {
- //System.out.println("Unable to undo: " + ex);
- //ex.printStackTrace();
- }
- updateUndoState();
- redoAction.updateRedoState();
- }
-
- protected void updateUndoState() {
- if (undo.canUndo()) {
- this.setEnabled(true);
- undoItem.setEnabled(true);
- undoItem.setText(undo.getUndoPresentationName());
- putValue(Action.NAME, undo.getUndoPresentationName());
- if (sketch != null) {
- sketch.setModified(true); // 0107
- }
- } else {
- this.setEnabled(false);
- undoItem.setEnabled(false);
- undoItem.setText("Undo");
- putValue(Action.NAME, "Undo");
- if (sketch != null) {
- sketch.setModified(false); // 0107
- }
- }
- }
- }
-
-
- class RedoAction extends AbstractAction {
- public RedoAction() {
- super("Redo");
- this.setEnabled(false);
- }
-
- public void actionPerformed(ActionEvent e) {
- try {
- undo.redo();
- } catch (CannotRedoException ex) {
- //System.out.println("Unable to redo: " + ex);
- //ex.printStackTrace();
- }
- updateRedoState();
- undoAction.updateUndoState();
- }
-
- protected void updateRedoState() {
- if (undo.canRedo()) {
- redoItem.setEnabled(true);
- redoItem.setText(undo.getRedoPresentationName());
- putValue(Action.NAME, undo.getRedoPresentationName());
- } else {
- this.setEnabled(false);
- redoItem.setEnabled(false);
- redoItem.setText("Redo");
- putValue(Action.NAME, "Redo");
- }
- }
+ protected void updateUndoRedoState() {
+ SketchTextArea textArea = getCurrentTab().getTextArea();
+ undoItem.setEnabled(textArea.canUndo());
+ redoItem.setEnabled(textArea.canRedo());
}
@@ -1361,23 +1384,16 @@ protected void updateRedoState() {
// abstract from the editor in this fashion.
- public void setHandlers(Runnable runHandler, Runnable presentHandler,
- Runnable stopHandler,
- Runnable exportHandler, Runnable exportAppHandler) {
- this.runHandler = runHandler;
- this.presentHandler = presentHandler;
- this.stopHandler = stopHandler;
- this.exportHandler = exportHandler;
- this.exportAppHandler = exportAppHandler;
- }
-
-
- public void resetHandlers() {
- runHandler = new DefaultRunHandler();
- presentHandler = new DefaultPresentHandler();
- stopHandler = new DefaultStopHandler();
- exportHandler = new DefaultExportHandler();
- exportAppHandler = new DefaultExportAppHandler();
+ private void resetHandlers() {
+ runHandler = new BuildHandler();
+ presentHandler = new BuildHandler(true);
+ runAndSaveHandler = new BuildHandler(false, true);
+ presentAndSaveHandler = new BuildHandler(true, true);
+ uploadHandler = new UploadHandler();
+ uploadHandler.setUsingProgrammer(false);
+ uploadUsingProgrammerHandler = new UploadHandler();
+ uploadUsingProgrammerHandler.setUsingProgrammer(true);
+ timeoutUploadHandler = new TimeoutUploadHandler();
}
@@ -1385,416 +1401,181 @@ public void resetHandlers() {
/**
- * Gets the current sketch object.
+ * Gets the current sketch controller.
*/
- public Sketch getSketch() {
- return sketch;
+ public SketchController getSketchController() {
+ return sketchController;
}
-
/**
- * Get the JEditTextArea object for use (not recommended). This should only
- * be used in obscure cases that really need to hack the internals of the
- * JEditTextArea. Most tools should only interface via the get/set functions
- * found in this class. This will maintain compatibility with future releases,
- * which will not use JEditTextArea.
+ * Gets the current sketch.
*/
- public JEditTextArea getTextArea() {
- return textarea;
- }
-
-
- /**
- * Get the contents of the current buffer. Used by the Sketch class.
- */
- public String getText() {
- return textarea.getText();
- }
-
-
- /**
- * Get a range of text from the current buffer.
- */
- public String getText(int start, int stop) {
- return textarea.getText(start, stop - start);
- }
-
-
- /**
- * Replace the entire contents of the front-most tab.
- */
- public void setText(String what) {
- startCompoundEdit();
- textarea.setText(what);
- stopCompoundEdit();
- }
-
-
- public void insertText(String what) {
- startCompoundEdit();
- int caret = getCaretOffset();
- setSelection(caret, caret);
- textarea.setSelectedText(what);
- stopCompoundEdit();
- }
-
-
- /**
- * Called to update the text but not switch to a different set of code
- * (which would affect the undo manager).
- */
-// public void setText2(String what, int start, int stop) {
-// beginCompoundEdit();
-// textarea.setText(what);
-// endCompoundEdit();
-//
-// // make sure that a tool isn't asking for a bad location
-// start = Math.max(0, Math.min(start, textarea.getDocumentLength()));
-// stop = Math.max(0, Math.min(start, textarea.getDocumentLength()));
-// textarea.select(start, stop);
-//
-// textarea.requestFocus(); // get the caret blinking
-// }
-
-
- public String getSelectedText() {
- return textarea.getSelectedText();
- }
-
-
- public void setSelectedText(String what) {
- textarea.setSelectedText(what);
- }
-
-
- public void setSelection(int start, int stop) {
- // make sure that a tool isn't asking for a bad location
- start = PApplet.constrain(start, 0, textarea.getDocumentLength());
- stop = PApplet.constrain(stop, 0, textarea.getDocumentLength());
-
- textarea.select(start, stop);
- }
-
-
- /**
- * Get the position (character offset) of the caret. With text selected,
- * this will be the last character actually selected, no matter the direction
- * of the selection. That is, if the user clicks and drags to select lines
- * 7 up to 4, then the caret position will be somewhere on line four.
- */
- public int getCaretOffset() {
- return textarea.getCaretPosition();
+ public Sketch getSketch() {
+ return sketch;
}
-
/**
- * True if some text is currently selected.
+ * Gets the currently displaying tab.
*/
- public boolean isSelectionActive() {
- return textarea.isSelectionActive();
+ public EditorTab getCurrentTab() {
+ return tabs.get(currentTabIndex);
}
-
/**
- * Get the beginning point of the current selection.
+ * Gets the index of the currently displaying tab.
*/
- public int getSelectionStart() {
- return textarea.getSelectionStart();
+ public int getCurrentTabIndex() {
+ return currentTabIndex;
}
-
/**
- * Get the end point of the current selection.
+ * Returns an (unmodifiable) list of currently opened tabs.
*/
- public int getSelectionStop() {
- return textarea.getSelectionStop();
+ public List getTabs() {
+ return Collections.unmodifiableList(tabs);
}
-
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
/**
- * Get text for a specified line.
+ * 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 String getLineText(int line) {
- return textarea.getLineText(line);
+ 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();
+ EditorTab selectedTab = tabs.get(index);
+ codePanel.add(selectedTab, BorderLayout.CENTER);
+ selectedTab.applyPreferences();
+ selectedTab.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();
+ });
}
-
- /**
- * Replace the text on a specified line.
- */
- public void setLineText(int line, String what) {
- startCompoundEdit();
- textarea.select(getLineStartOffset(line), getLineStopOffset(line));
- textarea.setSelectedText(what);
- stopCompoundEdit();
+ public void selectNextTab() {
+ selectTab((currentTabIndex + 1) % tabs.size());
}
-
- /**
- * Get character offset for the start of a given line of text.
- */
- public int getLineStartOffset(int line) {
- return textarea.getLineStartOffset(line);
+ public void selectPrevTab() {
+ selectTab((currentTabIndex - 1 + tabs.size()) % tabs.size());
}
-
- /**
- * Get character offset for end of a given line of text.
- */
- public int getLineStopOffset(int line) {
- return textarea.getLineStopOffset(line);
+ public EditorTab findTab(final SketchFile file) {
+ return tabs.get(findTabIndex(file));
}
-
/**
- * Get the number of lines in the currently displayed buffer.
+ * 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 getLineCount() {
- return textarea.getLineCount();
+ public int findTabIndex(final SketchFile file) {
+ for (int i = 0; i < tabs.size(); ++i) {
+ if (tabs.get(i).getSketchFile() == file)
+ return i;
+ }
+ return -1;
}
-
/**
- * Use before a manipulating text to group editing operations together as a
- * single undo. Use stopCompoundEdit() once finished.
+ * 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 void startCompoundEdit() {
- compoundEdit = new CompoundEdit();
+ 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;
}
-
/**
- * Use with startCompoundEdit() to group edit operations in a single undo.
+ * Create tabs for each of the current sketch's files, removing any existing
+ * tabs.
*/
- public void stopCompoundEdit() {
- compoundEdit.end();
- undo.addEdit(compoundEdit);
- undoAction.updateUndoState();
- redoAction.updateRedoState();
- compoundEdit = null;
- }
-
-
- public int getScrollPosition() {
- return textarea.getScrollPosition();
- }
-
-
- // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
-
-
- /**
- * Switch between tabs, this swaps out the Document object
- * that's currently being manipulated.
- */
- protected void setCode(SketchCode code) {
- SyntaxDocument document = (SyntaxDocument) code.getDocument();
-
- if (document == null) { // this document not yet inited
- document = new SyntaxDocument();
- code.setDocument(document);
-
- // turn on syntax highlighting
- document.setTokenMarker(new PdeKeywords());
-
- // insert the program text into the document object
+ public void createTabs() {
+ tabs.clear();
+ currentTabIndex = -1;
+ tabs.ensureCapacity(sketch.getCodeCount());
+ for (SketchFile file : sketch.getFiles()) {
try {
- document.insertString(0, code.getProgram(), null);
- } catch (BadLocationException bl) {
- bl.printStackTrace();
+ addTab(file, null);
+ } catch(IOException e) {
+ // TODO: Improve / move error handling
+ System.err.println(e);
}
-
- // set up this guy's own undo manager
-// code.undo = new UndoManager();
-
- // connect the undo listener to the editor
- document.addUndoableEditListener(new UndoableEditListener() {
- public void undoableEditHappened(UndoableEditEvent e) {
- if (compoundEdit != null) {
- compoundEdit.addEdit(e.getEdit());
-
- } else if (undo != null) {
- undo.addEdit(e.getEdit());
- undoAction.updateUndoState();
- redoAction.updateRedoState();
- }
- }
- });
}
-
- // update the document object that's in use
- textarea.setDocument(document,
- code.getSelectionStart(), code.getSelectionStop(),
- code.getScrollPosition());
-
- textarea.requestFocus(); // get the caret blinking
-
- this.undo = code.getUndo();
- undoAction.updateUndoState();
- redoAction.updateRedoState();
+ selectTab(0);
}
-
- // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
-
-
/**
- * Implements Edit → Cut.
+ * Reorders tabs as per current sketch's files order
*/
- public void handleCut() {
- textarea.cut();
- sketch.setModified(true);
+ public void reorderTabs() {
+ Collections.sort(tabs, (x, y) -> Sketch.CODE_DOCS_COMPARATOR.compare(x.getSketchFile(), y.getSketchFile()));
}
-
- /**
- * Implements Edit → Copy.
- */
- public void handleCopy() {
- textarea.copy();
- }
-
-
- protected void handleDiscourseCopy() {
- new DiscourseFormat(Editor.this, false).show();
- }
-
-
- protected void handleHTMLCopy() {
- new DiscourseFormat(Editor.this, true).show();
- }
-
-
- /**
- * Implements Edit → Paste.
- */
- public void handlePaste() {
- textarea.paste();
- sketch.setModified(true);
- }
-
-
/**
- * Implements Edit → Select All.
+ * 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
*/
- public void handleSelectAll() {
- textarea.selectAll();
+ 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 handleCommentUncomment() {
- startCompoundEdit();
-
- int startLine = textarea.getSelectionStartLine();
- int stopLine = textarea.getSelectionStopLine();
-
- int lastLineStart = textarea.getLineStartOffset(stopLine);
- int selectionStop = textarea.getSelectionStop();
- // If the selection ends at the beginning of the last line,
- // then don't (un)comment that line.
- if (selectionStop == lastLineStart) {
- // Though if there's no selection, don't do that
- if (textarea.isSelectionActive()) {
- stopLine--;
- }
- }
-
- // If the text is empty, ignore the user.
- // Also ensure that all lines are commented (not just the first)
- // when determining whether to comment or uncomment.
- int length = textarea.getDocumentLength();
- boolean commented = true;
- for (int i = startLine; commented && (i <= stopLine); i++) {
- int pos = textarea.getLineStartOffset(i);
- if (pos + 2 > length) {
- commented = false;
- } else {
- // Check the first two characters to see if it's already a comment.
- String begin = textarea.getText(pos, 2);
- //System.out.println("begin is '" + begin + "'");
- commented = begin.equals("//");
- }
- }
-
- for (int line = startLine; line <= stopLine; line++) {
- int location = textarea.getLineStartOffset(line);
- if (commented) {
- // remove a comment
- textarea.select(location, location+2);
- if (textarea.getSelectedText().equals("//")) {
- textarea.setSelectedText("");
- }
- } else {
- // add a comment
- textarea.select(location, location);
- textarea.setSelectedText("//");
- }
- }
- // Subtract one from the end, otherwise selects past the current line.
- // (Which causes subsequent calls to keep expanding the selection)
- textarea.select(textarea.getLineStartOffset(startLine),
- textarea.getLineStopOffset(stopLine) - 1);
- stopCompoundEdit();
+ protected void removeTab(SketchFile file) throws IOException {
+ int index = findTabIndex(file);
+ tabs.remove(index);
}
- protected void handleIndentOutdent(boolean indent) {
- int tabSize = Preferences.getInteger("editor.tabs.size");
- String tabString = Editor.EMPTY.substring(0, tabSize);
-
- startCompoundEdit();
-
- int startLine = textarea.getSelectionStartLine();
- int stopLine = textarea.getSelectionStopLine();
-
- // If the selection ends at the beginning of the last line,
- // then don't (un)comment that line.
- int lastLineStart = textarea.getLineStartOffset(stopLine);
- int selectionStop = textarea.getSelectionStop();
- if (selectionStop == lastLineStart) {
- // Though if there's no selection, don't do that
- if (textarea.isSelectionActive()) {
- stopLine--;
- }
- }
-
- for (int line = startLine; line <= stopLine; line++) {
- int location = textarea.getLineStartOffset(line);
-
- if (indent) {
- textarea.select(location, location);
- textarea.setSelectedText(tabString);
-
- } else { // outdent
- textarea.select(location, location + tabSize);
- // Don't eat code if it's not indented
- if (textarea.getSelectedText().equals(tabString)) {
- textarea.setSelectedText("");
- }
- }
- }
- // Subtract one from the end, otherwise selects past the current line.
- // (Which causes subsequent calls to keep expanding the selection)
- textarea.select(textarea.getLineStartOffset(startLine),
- textarea.getLineStopOffset(stopLine) - 1);
- stopCompoundEdit();
- }
-
-
- protected void handleFindReference() {
- String text = textarea.getSelectedText().trim();
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
- if (text.length() == 0) {
- statusNotice("First select a word to find in the reference.");
+ void handleFindReference(String text) {
+ String referenceFile = base.getPdeKeywords().getReference(text);
+ String q;
+ if (referenceFile == null) {
+ q = text;
+ } else if (referenceFile.startsWith("Serial_")) {
+ q = referenceFile.substring(7);
} else {
- String referenceFile = PdeKeywords.getReference(text);
- //System.out.println("reference file is " + referenceFile);
- if (referenceFile == null) {
- statusNotice("No reference available for \"" + text + "\"");
- } else {
- Base.showReference(referenceFile + ".html");
- }
+ q = referenceFile;
+ }
+ try {
+ Base.openURL("https://www.arduino.cc/search?tab=&q="
+ + URLEncoder.encode(q, "UTF-8"));
+ } catch (UnsupportedEncodingException e) {
+ e.printStackTrace();
}
}
@@ -1805,156 +1586,137 @@ protected void handleFindReference() {
/**
* Implements Sketch → Run.
* @param verbose Set true to run with verbose output.
+ * @param verboseHandler
+ * @param nonVerboseHandler
*/
- public void handleRun(final boolean verbose) {
- internalCloseRunner();
- running = true;
- toolbar.activate(EditorToolbar.RUN);
- statusNotice("Compiling...");
+ 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 (Preferences.getBoolean("console.auto_clear")) {
+ 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 ? presentHandler : runHandler).start();
+ new Thread(verbose ? verboseHandler : nonVerboseHandler).start();
}
- // DAM: in Arduino, this is compile
- class DefaultRunHandler implements Runnable {
- public void run() {
- try {
- sketch.prepare();
- String appletClassName = sketch.build(false);
- statusNotice("Done compiling.");
- } catch (Exception e) {
- statusError(e);
- }
+ class BuildHandler implements Runnable {
- toolbar.deactivate(EditorToolbar.RUN);
+ private final boolean verbose;
+ private final boolean saveHex;
+
+ public BuildHandler() {
+ this(false);
}
- }
- // DAM: in Arduino, this is compile (with verbose output)
- class DefaultPresentHandler implements Runnable {
- public void run() {
- try {
- sketch.prepare();
- String appletClassName = sketch.build(true);
- statusNotice("Done compiling.");
- } catch (Exception e) {
- statusError(e);
- }
+ public BuildHandler(boolean verbose) {
+ this(verbose, false);
+ }
- toolbar.deactivate(EditorToolbar.RUN);
+ public BuildHandler(boolean verbose, boolean saveHex) {
+ this.verbose = verbose;
+ this.saveHex = saveHex;
}
- }
- class DefaultStopHandler implements Runnable {
+ @Override
public void run() {
try {
- // DAM: we should try to kill the compilation or upload process here.
+ 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;
}
}
- /**
- * Set the location of the sketch run window. Used by Runner to update the
- * Editor about window drag events while the sketch is running.
- */
- public void setSketchLocation(Point p) {
- sketchWindowLocation = p;
+ public void removeAllLineHighlights() {
+ for (EditorTab tab : tabs)
+ tab.getTextArea().removeAllLineHighlights();
}
-
- /**
- * Get the last location of the sketch's run window. Used by Runner to make
- * the window show up in the same location as when it was last closed.
- */
- public Point getSketchLocation() {
- return sketchWindowLocation;
+ public void addLineHighlight(int line) throws BadLocationException {
+ SketchTextArea textArea = getCurrentTab().getTextArea();
+ FoldManager foldManager = textArea.getFoldManager();
+ if (foldManager.isLineHidden(line)) {
+ for (int i = 0; i < foldManager.getFoldCount(); i++) {
+ if (foldManager.getFold(i).containsLine(line)) {
+ foldManager.getFold(i).setCollapsed(false);
+ }
+ }
+ }
+ textArea.addLineHighlight(line, new Color(1, 0, 0, 0.2f));
+ textArea.setCaretPosition(textArea.getLineStartOffset(line));
}
/**
* Implements Sketch → Stop, or pressing Stop on the toolbar.
*/
- public void handleStop() { // called by menu or buttons
- toolbar.activate(EditorToolbar.STOP);
+ private void handleStop() { // called by menu or buttons
+// toolbar.activate(EditorToolbar.STOP);
- internalCloseRunner();
-
- toolbar.deactivate(EditorToolbar.RUN);
- toolbar.deactivate(EditorToolbar.STOP);
+ toolbar.deactivateRun();
+// toolbar.deactivate(EditorToolbar.STOP);
// focus the PDE again after quitting presentation mode [toxi 030903]
toFront();
}
-
- /**
- * Deactivate the Run button. This is called by Runner to notify that the
- * sketch has stopped running, usually in response to an error (or maybe
- * the sketch completing and exiting?) Tools should not call this function.
- * To initiate a "stop" action, call handleStop() instead.
- */
- public void internalRunnerClosed() {
- running = false;
- toolbar.deactivate(EditorToolbar.RUN);
- }
-
-
- /**
- * Handle internal shutdown of the runner.
- */
- public void internalCloseRunner() {
- running = false;
-
- if (stopHandler != null)
- try {
- stopHandler.run();
- } catch (Exception e) { }
-
- sketch.cleanup();
- }
-
-
/**
* 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;
+ 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
- String prompt = "Save changes to " + sketch.getName() + "? ";
+ toFront();
+
+ String prompt = I18n.format(tr("Save changes to \"{0}\"? "),
+ sketch.getName());
- if (!Base.isMacOS()) {
+ if (!OSUtils.hasMacOSStyleMenus()) {
int result =
- JOptionPane.showConfirmDialog(this, prompt, "Close",
+ JOptionPane.showConfirmDialog(this, prompt, tr("Close"),
JOptionPane.YES_NO_CANCEL_OPTION,
JOptionPane.QUESTION_MESSAGE);
- if (result == JOptionPane.YES_OPTION) {
- return handleSave(true);
-
- } else if (result == JOptionPane.NO_OPTION) {
- return true; // ok to continue
-
- } else if (result == JOptionPane.CANCEL_OPTION) {
- return false;
-
- } else {
- throw new IllegalStateException();
+ 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 {
@@ -1966,175 +1728,142 @@ protected boolean checkModified() {
// suck--workarounds for the Mac and Apple's snobby attitude about it!
// I think it's nifty that they treat their developers like dirt.
- // Pane formatting adapted from the quaqua guide
- // http://www.randelshofer.ch/quaqua/guide/joptionpane.html
JOptionPane pane =
- new JOptionPane(" " +
- " " +
- "Do you want to save changes to this sketch
" +
- " before closing?" +
- "If you don't save, your changes will be lost.",
+ 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[] {
- "Save", "Cancel", "Don't Save"
+ tr("Save"), tr("Cancel"), tr("Don't Save")
};
pane.setOptions(options);
// highlight the safest option ala apple hig
pane.setInitialValue(options[0]);
- // on macosx, setting the destructive property places this option
- // away from the others at the lefthand side
- pane.putClientProperty("Quaqua.OptionPane.destructiveOption",
- new Integer(2));
-
JDialog dialog = pane.createDialog(this, null);
dialog.setVisible(true);
Object result = pane.getValue();
if (result == options[0]) { // save (and close/quit)
return handleSave(true);
-
- } else if (result == options[2]) { // don't save (still close/quit)
- return true;
-
- } else { // cancel?
- return false;
+ } else {
+ return result == options[2];
}
}
}
-
- /**
- * Open a sketch from a particular path, but don't check to save changes.
- * Used by Sketch.saveAs() to re-open a sketch after the "Save As"
- */
- protected void handleOpenUnchecked(String path, int codeIndex,
- int selStart, int selStop, int scrollPos) {
- internalCloseRunner();
- handleOpenInternal(path);
- // Replacing a document that may be untitled. If this is an actual
- // untitled document, then editor.untitled will be set by Base.
- untitled = false;
-
- sketch.setCurrentCode(codeIndex);
- textarea.select(selStart, selStop);
- textarea.setScrollPosition(scrollPos);
- }
-
-
/**
* Second stage of open, occurs after having checked to see if the
* modifications (if any) to the previous sketch need to be saved.
*/
- protected boolean handleOpenInternal(String path) {
+ protected boolean handleOpenInternal(File sketchFile) {
// check to make sure that this .pde file is
// in a folder of the same name
- File file = new File(path);
- File parentFile = new File(file.getParent());
- String parentName = parentFile.getName();
- String pdeName = parentName + ".pde";
- File altFile = new File(file.getParent(), pdeName);
-
- if (pdeName.equals(file.getName())) {
- // no beef with this guy
-
- } else if (altFile.exists()) {
- // user selected a .java from the same sketch,
- // but open the .pde instead
- path = altFile.getAbsolutePath();
- //System.out.println("found alt file in same folder");
-
- } else if (!path.endsWith(".pde")) {
- Base.showWarning("Bad file selected",
- "Processing can only open its own sketches\n" +
- "and other files ending in .pde", null);
- return false;
+ 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;
+ }
- } else {
- String properParent =
- file.getName().substring(0, file.getName().length() - 4);
-
- Object[] options = { "OK", "Cancel" };
- String prompt =
- "The file \"" + file.getName() + "\" needs to be inside\n" +
- "a sketch folder named \"" + properParent + "\".\n" +
- "Create this folder, move the file, and continue?";
-
- int result = JOptionPane.showOptionDialog(this,
- prompt,
- "Moving",
- JOptionPane.YES_NO_OPTION,
- JOptionPane.QUESTION_MESSAGE,
- null,
- options,
- options[0]);
-
- if (result == JOptionPane.YES_OPTION) {
// create properly named folder
- File properFolder = new File(file.getParent(), properParent);
+ File properFolder = new File(sketchFile.getParent(), properParent);
if (properFolder.exists()) {
- Base.showWarning("Error",
- "A folder named \"" + properParent + "\" " +
- "already exists. Can't open sketch.", null);
+ 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("Error",
- "Could not create the sketch folder.", null);
+ Base.showWarning(tr("Error"), tr("Could not create the sketch folder."), null);
return false;
}
// copy the sketch inside
- File properPdeFile = new File(properFolder, file.getName());
- File origPdeFile = new File(path);
+ File properPdeFile = new File(properFolder, sketchFile.getName());
try {
- Base.copyFile(origPdeFile, properPdeFile);
+ Base.copyFile(sketchFile, properPdeFile);
} catch (IOException e) {
- Base.showWarning("Error", "Could not copy to a proper location.", 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
- origPdeFile.delete();
+ sketchFile.delete();
// update with the new path
- path = properPdeFile.getAbsolutePath();
+ file = properPdeFile;
- } else if (result == JOptionPane.NO_OPTION) {
- return false;
}
}
try {
- sketch = new Sketch(this, path);
+ sketch = new Sketch(file);
} catch (IOException e) {
- Base.showWarning("Error", "Could not create the sketch.", e);
+ Base.showWarning(tr("Error"), tr("Could not create the sketch."), e);
return false;
}
- header.rebuild();
- // Set the title of the window to "sketch_070752a - Processing 0126"
- setTitle(sketch.getName() + " | Arduino " + Base.VERSION_NAME);
+ sketchController = new SketchController(this, sketch);
+ createTabs();
+
// Disable untitled setting from previous document, if any
untitled = false;
- // Store information on who's open and running
- // (in case there's a crash or something that can't be recovered)
- base.storeSketches();
- Preferences.save();
-
// opening was successful
return true;
+ }
-// } catch (Exception e) {
-// e.printStackTrace();
-// statusError(e);
-// return false;
-// }
+ public 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));
+ }
+ }
}
@@ -2150,13 +1879,14 @@ protected boolean handleOpenInternal(String path) {
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) {
- handleSave2();
+ return handleSave2();
} else {
SwingUtilities.invokeLater(new Runnable() {
@@ -2169,12 +1899,26 @@ public void run() {
}
- protected void handleSave2() {
- toolbar.activate(EditorToolbar.SAVE);
- statusNotice("Saving...");
+ private boolean handleSave2() {
+ toolbar.activateSave();
+ statusNotice(tr("Saving..."));
+ boolean saved = false;
try {
- if (sketch.save()) {
- statusNotice("Done Saving.");
+ if (PreferencesData.getBoolean("editor.autoformat_currentfile_before_saving")) {
+ Tool formatTool = getOrCreateToolInstance("cc.arduino.packages.formatter.AStyle");
+ formatTool.run();
+ }
+
+ boolean wasReadOnly = sketchController.isReadOnly();
+ 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();
}
@@ -2195,7 +1939,8 @@ protected void handleSave2() {
// this is used when another operation calls a save
}
//toolbar.clear();
- toolbar.deactivate(EditorToolbar.SAVE);
+ toolbar.deactivateSave();
+ return saved;
}
@@ -2203,20 +1948,22 @@ public boolean handleSaveAs() {
//stopRunner(); // formerly from 0135
handleStop();
- toolbar.activate(EditorToolbar.SAVE);
+ toolbar.activateSave();
//SwingUtilities.invokeLater(new Runnable() {
//public void run() {
- statusNotice("Saving...");
+ statusNotice(tr("Saving..."));
try {
- if (sketch.saveAs()) {
- statusNotice("Done Saving.");
+ 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("Save Canceled.");
+ statusNotice(tr("Save Canceled."));
return false;
}
} catch (Exception e) {
@@ -2225,37 +1972,42 @@ public boolean handleSaveAs() {
} finally {
// make sure the toolbar button deactivates
- toolbar.deactivate(EditorToolbar.SAVE);
+ toolbar.deactivateSave();
+
+ // Update editor window title in case of "Save as..."
+ updateTitle();
+ header.rebuild();
}
return true;
}
-
-
- public boolean serialPrompt() {
- int count = serialMenu.getItemCount();
- Object[] names = new Object[count];
- for (int i = 0; i < count; i++) {
- names[i] = ((JCheckBoxMenuItem)serialMenu.getItem(i)).getText();
- }
-
- String result = (String)
- JOptionPane.showInputDialog(this,
- "Serial port " +
- Preferences.get("serial.port") +
- " not found.\n" +
- "Retry the upload with another serial port?",
- "Serial port not found",
- JOptionPane.PLAIN_MESSAGE,
- null,
- names,
- 0);
- if (result == null) return false;
- selectSerialPort(result);
+
+
+ private boolean serialPrompt() {
+ List items = new ArrayList<>();
+ for (int i = 0; i < portMenu.getItemCount(); i++) {
+ if (portMenu.getItem(i) instanceof BoardPortJCheckBoxMenuItem)
+ items.add((BoardPortJCheckBoxMenuItem) portMenu.getItem(i));
+ }
+
+ String port = PreferencesData.get("serial.port");
+ String title;
+ if (port == null || port.isEmpty()) {
+ title = tr("Serial port not selected.");
+ } else {
+ title = I18n.format(tr("Serial port {0} not found."), port);
+ }
+ String question = tr("Retry the upload with another serial port?");
+ BoardPortJCheckBoxMenuItem result = (BoardPortJCheckBoxMenuItem) JOptionPane
+ .showInputDialog(this, title + "\n" + question, title,
+ JOptionPane.PLAIN_MESSAGE, null, items.toArray(), 0);
+ if (result == null)
+ return false;
+ result.doClick();
+ base.onBoardOrPortChange();
return true;
}
-
/**
* Called by Sketch → Export.
* Handles calling the export() function on sketch, and
@@ -2271,200 +2023,481 @@ public boolean serialPrompt() {
* Made synchronized to (hopefully) avoid problems of people
* hitting export twice, quickly, and horking things up.
*/
- synchronized public void handleExport(final boolean verbose) {
- //if (!handleExportCheckModified()) return;
- toolbar.activate(EditorToolbar.EXPORT);
+ synchronized public void handleExport(final boolean usingProgrammer) {
+ if (PreferencesData.getBoolean("editor.save_on_verify")) {
+ if (sketch.isModified() && !sketchController.isReadOnly()) {
+ handleSave(true);
+ }
+ }
+ toolbar.activateExport();
console.clear();
- statusNotice("Uploading to I/O Board...");
+ status.progress(tr("Uploading to I/O Board..."));
- new Thread(verbose ? exportAppHandler : exportHandler).start();
+ avoidMultipleOperations = true;
+
+ new Thread(timeoutUploadHandler).start();
+ new Thread(usingProgrammer ? uploadUsingProgrammerHandler : uploadHandler).start();
}
- // DAM: in Arduino, this is upload
- class DefaultExportHandler implements Runnable {
- public void run() {
+ class UploadHandler implements Runnable {
+ boolean usingProgrammer = false;
+ public void setUsingProgrammer(boolean usingProgrammer) {
+ this.usingProgrammer = usingProgrammer;
+ }
+
+ public void run() {
try {
- serialMonitor.closeSerialPort();
- serialMonitor.setVisible(false);
-
uploading = true;
-
- boolean success = sketch.exportApplet(false);
+
+ removeAllLineHighlights();
+ if (serialMonitor != null) {
+ serialMonitor.suspend();
+ }
+ if (serialPlotter != null) {
+ serialPlotter.suspend();
+ }
+
+ boolean success = sketchController.exportApplet(usingProgrammer);
if (success) {
- statusNotice("Done uploading.");
- } else {
- // error message will already be visible
+ statusNotice(tr("Done uploading."));
}
} catch (SerialNotFoundException e) {
- populateSerialMenu();
- if (serialMenu.getItemCount() == 0) statusError(e);
- else if (serialPrompt()) run();
- else statusNotice("Upload canceled.");
+ if (portMenu.getItemCount() == 0) {
+ statusError(tr("Serial port not selected."));
+ } 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.deactivate(EditorToolbar.EXPORT);
+ toolbar.deactivateExport();
+
+ resumeOrCloseSerialMonitor();
+ resumeOrCloseSerialPlotter();
+ base.onBoardOrPortChange();
}
}
- // DAM: in Arduino, this is upload (with verbose output)
- class DefaultExportAppHandler implements Runnable {
- public void run() {
+ static public boolean isUploading() {
+ return uploading;
+ }
+ private void resumeOrCloseSerialMonitor() {
+ // Return the serial monitor window to its initial state
+ if (serialMonitor != null) {
try {
- serialMonitor.closeSerialPort();
- serialMonitor.setVisible(false);
-
- uploading = true;
-
- boolean success = sketch.exportApplet(true);
- if (success) {
- statusNotice("Done uploading.");
- } else {
- // error message will already be visible
+ Thread.sleep(200);
+ } catch (InterruptedException e) {
+ // noop
+ }
+ 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
}
- } catch (SerialNotFoundException e) {
- populateSerialMenu();
- if (serialMenu.getItemCount() == 0) statusError(e);
- else if (serialPrompt()) run();
- else statusNotice("Upload canceled.");
- } catch (RunnerException e) {
- //statusError("Error during upload.");
- //e.printStackTrace();
+ }
+ 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) {
- e.printStackTrace();
+ statusError(e);
}
- uploading = false;
- //toolbar.clear();
- toolbar.deactivate(EditorToolbar.EXPORT);
- }
+ }
}
- /**
- * Checks to see if the sketch has been modified, and if so,
- * asks the user to save the sketch or cancel the export.
- * This prevents issues where an incomplete version of the sketch
- * would be exported, and is a fix for
- * Bug 157
- */
- protected boolean handleExportCheckModified() {
- if (!sketch.isModified()) return true;
-
- Object[] options = { "OK", "Cancel" };
- int result = JOptionPane.showOptionDialog(this,
- "Save changes before export?",
- "Save",
- JOptionPane.OK_CANCEL_OPTION,
- JOptionPane.QUESTION_MESSAGE,
- null,
- options,
- options[0]);
-
- if (result == JOptionPane.OK_OPTION) {
- handleSave(true);
+ class TimeoutUploadHandler implements Runnable {
- } else {
- // why it's not CANCEL_OPTION is beyond me (at least on the mac)
- // but f-- it.. let's get this shite done..
- //} else if (result == JOptionPane.CANCEL_OPTION) {
- statusNotice("Export canceled, changes must first be saved.");
- //toolbar.clear();
- return false;
+ 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
+ }
}
- return true;
}
-
public void handleSerial() {
- if (uploading) return;
-
- try {
- serialMonitor.openSerialPort();
- serialMonitor.setVisible(true);
- } catch (SerialException e) {
- statusError(e);
+ 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.addEditorFontResizeListeners(serialMonitor);
+ 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 {
+ if (!avoidMultipleOperations) {
+ serialMonitor.open();
+ }
+ serialMonitor.setVisible(true);
+ success = true;
+ statusEmpty();
+ } 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() + ")";
+ }
+ serialMonitor = null;
+ statusError(errorMessage);
+ try {
+ serialMonitor.close();
+ } catch (Exception e1) {
+ // noop
+ }
+ } catch (Exception e) {
+ statusError(e);
+ } finally {
+ if (serialMonitor != null && serialMonitor.requiresAuthorization() && !success) {
+ PreferencesData.remove(serialMonitor.getAuthorizationKey());
+ }
+ }
+
+ } while (serialMonitor != null && 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;
+ }
+ }
- protected void handleBurnBootloader(final String target, final String programmer) {
- console.clear();
- statusNotice("Burning bootloader to I/O Board (this may take a minute)...");
- SwingUtilities.invokeLater(new Runnable() {
- public void run() {
+ 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.dispose();
+ serialPlotter = null;
+ }
+ else {
+ // If it's not closed, give it the focus
try {
- Uploader uploader = new AvrdudeUploader();
- if (uploader.burnBootloader(target, programmer)) {
- statusNotice("Done burning bootloader.");
- } else {
- statusError("Error while burning bootloader.");
- // error message will already be visible
- }
- } catch (RunnerException e) {
- statusError("Error while burning bootloader.");
- e.printStackTrace();
- //statusError(e);
+ serialPlotter.toFront();
+ serialPlotter.requestFocus();
+ return;
} catch (Exception e) {
- statusError("Error while burning bootloader.");
- e.printStackTrace();
+ // 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;
+ statusEmpty();
+ } 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);
+ serialPlotter = null;
+ } catch (Exception e) {
+ statusError(e);
+ } finally {
+ if (serialPlotter != null && serialPlotter.requiresAuthorization() && !success) {
+ PreferencesData.remove(serialPlotter.getAuthorizationKey());
+ }
+ }
+
+ } while (serialPlotter != null && serialPlotter.requiresAuthorization() && !success);
+
+ }
+
+ private void handleBurnBootloader() {
+ console.clear();
+ EditorConsole.setCurrentEditorConsole(this.console);
+ 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 (SerialNotFoundException e) {
+ SwingUtilities.invokeLater(() -> statusError(tr("Error while burning bootloader: please select a serial port.")));
+ } 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.getPrefs().get("vid");
+ pid = port.getPrefs().get("pid");
+ iserial = port.getPrefs().get("iserial");
+ 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.
*/
- public void handlePageSetup() {
- //printerJob = null;
- if (printerJob == null) {
- printerJob = PrinterJob.getPrinterJob();
- }
+ private void handlePageSetup() {
+ PrinterJob printerJob = PrinterJob.getPrinterJob();
if (pageFormat == null) {
pageFormat = printerJob.defaultPage();
}
pageFormat = printerJob.pageDialog(pageFormat);
- //System.out.println("page format is " + pageFormat);
}
/**
* Handler for File → Print.
*/
- public void handlePrint() {
- statusNotice("Printing...");
+ private void handlePrint() {
+ statusNotice(tr("Printing..."));
//printerJob = null;
- if (printerJob == null) {
- printerJob = PrinterJob.getPrinterJob();
- }
+ PrinterJob printerJob = PrinterJob.getPrinterJob();
if (pageFormat != null) {
//System.out.println("setting page format " + pageFormat);
- printerJob.setPrintable(textarea.getPainter(), pageFormat);
+ printerJob.setPrintable(getCurrentTab().getTextArea(), pageFormat);
} else {
- printerJob.setPrintable(textarea.getPainter());
+ printerJob.setPrintable(getCurrentTab().getTextArea());
}
// set the name of the job to the code name
- printerJob.setJobName(sketch.getCurrentCode().getPrettyName());
+ printerJob.setJobName(getCurrentTab().getSketchFile().getPrettyName());
if (printerJob.printDialog()) {
try {
printerJob.print();
- statusNotice("Done printing.");
+ statusNotice(tr("Done printing."));
} catch (PrinterException pe) {
- statusError("Error while printing.");
+ statusError(tr("Error while printing."));
pe.printStackTrace();
}
} else {
- statusNotice("Printing canceled.");
+ statusNotice(tr("Printing canceled."));
}
//printerJob = null; // clear this out?
}
@@ -2477,9 +2510,10 @@ public void handlePrint() {
* 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.deactivate(EditorToolbar.RUN);
+ toolbar.deactivateRun();
}
@@ -2495,27 +2529,30 @@ public void statusError(Exception e) {
if (e instanceof RunnerException) {
RunnerException re = (RunnerException) e;
- if (re.hasCodeIndex()) {
- sketch.setCurrentCode(re.getCodeIndex());
+ 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 >= textarea.getLineCount()) {
+ 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 = textarea.getLineCount() - 1;
- if (textarea.getLineText(line).length() == 0) {
+ 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 >= textarea.getLineCount()) {
- System.err.println("Bad error line: " + line);
+ if (line < 0 || line >= getCurrentTab().getTextArea().getLineCount()) {
+ System.err.println(I18n.format(tr("Bad error line: {0}"), line));
} else {
- textarea.select(textarea.getLineStartOffset(line),
- textarea.getLineStopOffset(line) - 1);
+ try {
+ addLineHighlight(line);
+ } catch (BadLocationException e1) {
+ e1.printStackTrace();
+ }
}
}
}
@@ -2549,133 +2586,19 @@ public void statusNotice(String msg) {
/**
* Clear the status area.
*/
- public void statusEmpty() {
+ private void statusEmpty() {
statusNotice(EMPTY);
}
-
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+ protected void onBoardOrPortChange() {
+ lineStatus.updateBoardAndPort();
+ lineStatus.repaint();
+ }
- /**
- * Returns the edit popup menu.
- */
- class TextAreaPopup extends JPopupMenu {
- //String currentDir = System.getProperty("user.dir");
- String referenceFile = null;
-
- JMenuItem cutItem;
- JMenuItem copyItem;
- JMenuItem discourseItem;
- JMenuItem referenceItem;
-
-
- public TextAreaPopup() {
- JMenuItem item;
-
- cutItem = new JMenuItem("Cut");
- cutItem.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- handleCut();
- }
- });
- this.add(cutItem);
-
- copyItem = new JMenuItem("Copy");
- copyItem.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- handleCopy();
- }
- });
- this.add(copyItem);
-
- discourseItem = new JMenuItem("Copy for Forum");
- discourseItem.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- handleDiscourseCopy();
- }
- });
- this.add(discourseItem);
-
- discourseItem = new JMenuItem("Copy as HTML");
- discourseItem.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- handleHTMLCopy();
- }
- });
- this.add(discourseItem);
-
- item = new JMenuItem("Paste");
- item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- handlePaste();
- }
- });
- this.add(item);
-
- item = new JMenuItem("Select All");
- item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- handleSelectAll();
- }
- });
- this.add(item);
-
- this.addSeparator();
-
- item = new JMenuItem("Comment/Uncomment");
- item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- handleCommentUncomment();
- }
- });
- this.add(item);
-
- item = new JMenuItem("Increase Indent");
- item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- handleIndentOutdent(true);
- }
- });
- this.add(item);
-
- item = new JMenuItem("Decrease Indent");
- item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- handleIndentOutdent(false);
- }
- });
- this.add(item);
-
- this.addSeparator();
-
- referenceItem = new JMenuItem("Find in Reference");
- referenceItem.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- handleFindReference();
- }
- });
- this.add(referenceItem);
- }
-
- // if no text is selected, disable copy and cut menu items
- public void show(Component component, int x, int y) {
- if (textarea.isSelectionActive()) {
- cutItem.setEnabled(true);
- copyItem.setEnabled(true);
- discourseItem.setEnabled(true);
-
- String sel = textarea.getSelectedText().trim();
- referenceFile = PdeKeywords.getReference(sel);
- referenceItem.setEnabled(referenceFile != null);
-
- } else {
- cutItem.setEnabled(false);
- copyItem.setEnabled(false);
- discourseItem.setEnabled(false);
- referenceItem.setEnabled(false);
- }
- super.show(component, x, y);
- }
+ public void addCompilerProgressListener(CompilerProgressListener listener){
+ this.status.addCompilerProgressListener(listener);
}
+
}
diff --git a/app/src/processing/app/EditorConsole.java b/app/src/processing/app/EditorConsole.java
index 38cf00fc2ed..3942908a1da 100644
--- a/app/src/processing/app/EditorConsole.java
+++ b/app/src/processing/app/EditorConsole.java
@@ -1,5 +1,3 @@
-/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
-
/*
Part of the Processing project - http://processing.org
@@ -23,433 +21,217 @@
package processing.app;
-import java.awt.*;
-import java.awt.event.*;
-import java.io.*;
+import cc.arduino.ConsoleOutputStream;
+
import javax.swing.*;
import javax.swing.text.*;
+import java.awt.*;
+import java.io.PrintStream;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
-import java.util.*;
-
+import static processing.app.Theme.scale;
/**
* Message console that sits below the editing area.
- *
- * Debugging this class is tricky... If it's throwing exceptions,
- * don't take over System.err, and debug while watching just System.out
- * or just write println() or whatever directly to systemOut or systemErr.
*/
public class EditorConsole extends JScrollPane {
- Editor editor;
-
- JTextPane consoleTextPane;
- BufferedStyledDocument consoleDoc;
- MutableAttributeSet stdStyle;
- MutableAttributeSet errStyle;
+ private static ConsoleOutputStream out;
+ private static ConsoleOutputStream err;
+ private int startOfLine = 0;
+ private int insertPosition = 0;
- int maxLineCount;
+ // Regex for linesplitting, see insertString for comments.
+ private static final Pattern newLinePattern = Pattern.compile("([^\r\n]*)([\r\n]*\n)?(\r+)?");
- static File errFile;
- static File outFile;
- static File tempFolder;
+ public static synchronized void setCurrentEditorConsole(EditorConsole console) {
+ if (out == null) {
+ out = new ConsoleOutputStream(console.stdOutStyle, System.out);
+ System.setOut(new PrintStream(out, true));
- // Single static instance shared because there's only one real System.out.
- // Within the input handlers, the currentConsole variable will be used to
- // echo things to the correct location.
-
- static public PrintStream systemOut;
- static public PrintStream systemErr;
-
- static PrintStream consoleOut;
- static PrintStream consoleErr;
+ err = new ConsoleOutputStream(console.stdErrStyle, System.err);
+ System.setErr(new PrintStream(err, true));
+ }
- static OutputStream stdoutFile;
- static OutputStream stderrFile;
+ out.setCurrentEditorConsole(console);
+ err.setCurrentEditorConsole(console);
+ }
- static EditorConsole currentConsole;
-
+ private final DefaultStyledDocument document;
+ private final JTextPane consoleTextPane;
- public EditorConsole(Editor editor) {
- this.editor = editor;
+ private SimpleAttributeSet stdOutStyle;
+ private SimpleAttributeSet stdErrStyle;
- maxLineCount = Preferences.getInteger("console.length");
+ public EditorConsole(Base base) {
+ document = new DefaultStyledDocument();
- consoleDoc = new BufferedStyledDocument(10000, maxLineCount);
- consoleTextPane = new JTextPane(consoleDoc);
+ consoleTextPane = new JTextPane(document);
consoleTextPane.setEditable(false);
+ DefaultCaret caret = (DefaultCaret) consoleTextPane.getCaret();
+ caret.setUpdatePolicy(DefaultCaret.NEVER_UPDATE);
+ consoleTextPane.setFocusTraversalKeysEnabled(false);
- // necessary?
- MutableAttributeSet standard = new SimpleAttributeSet();
- StyleConstants.setAlignment(standard, StyleConstants.ALIGN_LEFT);
- consoleDoc.setParagraphAttributes(0, 0, standard, true);
-
- // build styles for different types of console output
- Color bgColor = Theme.getColor("console.color");
- Color fgColorOut = Theme.getColor("console.output.color");
- Color fgColorErr = Theme.getColor("console.error.color");
- Font font = Theme.getFont("console.font");
-
- stdStyle = new SimpleAttributeSet();
- StyleConstants.setForeground(stdStyle, fgColorOut);
- StyleConstants.setBackground(stdStyle, bgColor);
- StyleConstants.setFontSize(stdStyle, font.getSize());
- StyleConstants.setFontFamily(stdStyle, font.getFamily());
- StyleConstants.setBold(stdStyle, font.isBold());
- StyleConstants.setItalic(stdStyle, font.isItalic());
-
- errStyle = new SimpleAttributeSet();
- StyleConstants.setForeground(errStyle, fgColorErr);
- StyleConstants.setBackground(errStyle, bgColor);
- StyleConstants.setFontSize(errStyle, font.getSize());
- StyleConstants.setFontFamily(errStyle, font.getFamily());
- StyleConstants.setBold(errStyle, font.isBold());
- StyleConstants.setItalic(errStyle, font.isItalic());
-
- consoleTextPane.setBackground(bgColor);
-
- // add the jtextpane to this scrollpane
- this.setViewportView(consoleTextPane);
+ Color backgroundColour = Theme.getColor("console.color");
+ consoleTextPane.setBackground(backgroundColour);
- // calculate height of a line of text in pixels
- // and size window accordingly
- FontMetrics metrics = this.getFontMetrics(font);
- int height = metrics.getAscent() + metrics.getDescent();
- int lines = Preferences.getInteger("console.lines"); //, 4);
- int sizeFudge = 6; //10; // unclear why this is necessary, but it is
- setPreferredSize(new Dimension(1024, (height * lines) + sizeFudge));
- setMinimumSize(new Dimension(1024, (height * 4) + sizeFudge));
-
- if (systemOut == null) {
- systemOut = System.out;
- systemErr = System.err;
-
- // Create a temporary folder which will have a randomized name. Has to
- // be randomized otherwise another instance of Processing (or one of its
- // sister IDEs) might collide with the file causing permissions problems.
- // The files and folders are not deleted on exit because they may be
- // needed for debugging or bug reporting.
- tempFolder = Base.createTempFolder("console");
- try {
- String outFileName = Preferences.get("console.output.file");
- if (outFileName != null) {
- outFile = new File(tempFolder, outFileName);
- stdoutFile = new FileOutputStream(outFile);
- }
+ Font consoleFont = Theme.getFont("console.font");
+ Font editorFont = PreferencesData.getFont("editor.font");
+ Font actualFont = new Font(consoleFont.getName(), consoleFont.getStyle(), scale(editorFont.getSize()));
- String errFileName = Preferences.get("console.error.file");
- if (errFileName != null) {
- errFile = new File(tempFolder, errFileName);
- stderrFile = new FileOutputStream(errFile);
- }
- } catch (IOException e) {
- Base.showWarning("Console Error",
- "A problem occurred while trying to open the\n" +
- "files used to store the console output.", e);
- }
- consoleOut = new PrintStream(new EditorConsoleStream(false));
- consoleErr = new PrintStream(new EditorConsoleStream(true));
-
- if (Preferences.getBoolean("console")) {
- try {
- System.setOut(consoleOut);
- System.setErr(consoleErr);
- } catch (Exception e) {
- e.printStackTrace(systemOut);
- }
- }
- }
+ stdOutStyle = new SimpleAttributeSet();
+ StyleConstants.setForeground(stdOutStyle, Theme.getColor("console.output.color"));
+ StyleConstants.setBackground(stdOutStyle, backgroundColour);
+ StyleConstants.setFontSize(stdOutStyle, actualFont.getSize());
+ StyleConstants.setFontFamily(stdOutStyle, actualFont.getFamily());
+ StyleConstants.setBold(stdOutStyle, actualFont.isBold());
+ StyleConstants.setItalic(stdOutStyle, actualFont.isItalic());
- // to fix ugliness.. normally macosx java 1.3 puts an
- // ugly white border around this object, so turn it off.
- if (Base.isMacOS()) {
- setBorder(null);
- }
+ consoleTextPane.setParagraphAttributes(stdOutStyle, true);
- // periodically post buffered messages to the console
- // should the interval come from the preferences file?
- new javax.swing.Timer(250, new ActionListener() {
- public void actionPerformed(ActionEvent evt) {
- // only if new text has been added
- if (consoleDoc.hasAppendage) {
- // insert the text that's been added in the meantime
- consoleDoc.insertAll();
- // always move to the end of the text as it's added
- consoleTextPane.setCaretPosition(consoleDoc.getLength());
- }
- }
- }).start();
- }
+ stdErrStyle = new SimpleAttributeSet();
+ StyleConstants.setForeground(stdErrStyle, Theme.getColor("console.error.color"));
+ StyleConstants.setBackground(stdErrStyle, backgroundColour);
+ StyleConstants.setFontSize(stdErrStyle, actualFont.getSize());
+ StyleConstants.setFontFamily(stdErrStyle, actualFont.getFamily());
+ StyleConstants.setBold(stdErrStyle, actualFont.isBold());
+ StyleConstants.setItalic(stdErrStyle, actualFont.isItalic());
-
- static public void setEditor(Editor editor) {
- currentConsole = editor.console;
- }
-
-
- /**
- * Close the streams so that the temporary files can be deleted.
- *
- * File.deleteOnExit() cannot be used because the stdout and stderr
- * files are inside a folder, and have to be deleted before the
- * folder itself is deleted, which can't be guaranteed when using
- * the deleteOnExit() method.
- */
- public void handleQuit() {
- // replace original streams to remove references to console's streams
- System.setOut(systemOut);
- System.setErr(systemErr);
-
- // close the PrintStream
- consoleOut.close();
- consoleErr.close();
-
- // also have to close the original FileOutputStream
- // otherwise it won't be shut down completely
- try {
- stdoutFile.close();
- stderrFile.close();
- } catch (IOException e) {
- e.printStackTrace(systemOut);
- }
+ JPanel noWrapPanel = new JPanel(new BorderLayout());
+ noWrapPanel.add(consoleTextPane);
- outFile.delete();
- errFile.delete();
- tempFolder.delete();
- }
+ setViewportView(noWrapPanel);
+ getVerticalScrollBar().setUnitIncrement(7);
+ // calculate height of a line of text in pixels
+ // and size window accordingly
+ FontMetrics metrics = getFontMetrics(actualFont);
+ int height = metrics.getAscent() + metrics.getDescent();
+ int lines = PreferencesData.getInteger("console.lines");
+ setPreferredSize(new Dimension(100, (height * lines)));
+ setMinimumSize(new Dimension(100, (height * lines)));
- public void write(byte b[], int offset, int length, boolean err) {
- // we could do some cross platform CR/LF mangling here before outputting
- // add text to output document
- message(new String(b, offset, length), err, false);
+ // Add font size adjustment listeners.
+ if (base != null)
+ base.addEditorFontResizeListeners(consoleTextPane);
}
-
- // added sync for 0091.. not sure if it helps or hinders
- synchronized public void message(String what, boolean err, boolean advance) {
- if (err) {
- systemErr.print(what);
- //systemErr.print("CE" + what);
- } else {
- systemOut.print(what);
- //systemOut.print("CO" + what);
- }
-
- if (advance) {
- appendText("\n", err);
- if (err) {
- systemErr.println();
- } else {
- systemOut.println();
+ public void applyPreferences() {
+
+ // Update the console text pane font from the preferences.
+ Font consoleFont = Theme.getFont("console.font");
+ Font editorFont = PreferencesData.getFont("editor.font");
+ Font actualFont = new Font(consoleFont.getName(), consoleFont.getStyle(), scale(editorFont.getSize()));
+
+ AttributeSet stdOutStyleOld = stdOutStyle.copyAttributes();
+ AttributeSet stdErrStyleOld = stdErrStyle.copyAttributes();
+ StyleConstants.setFontSize(stdOutStyle, actualFont.getSize());
+ StyleConstants.setFontSize(stdErrStyle, actualFont.getSize());
+
+ // Re-insert console text with the new preferences if there were changes.
+ // This assumes that the document has single-child paragraphs (default).
+ if (!stdOutStyle.isEqual(stdOutStyleOld) || !stdErrStyle.isEqual(stdOutStyleOld)) {
+ if (out != null)
+ out.setAttibutes(stdOutStyle);
+ if (err != null)
+ err.setAttibutes(stdErrStyle);
+
+ int start;
+ for (int end = document.getLength() - 1; end >= 0; end = start - 1) {
+ Element elem = document.getParagraphElement(end);
+ start = elem.getStartOffset();
+ AttributeSet attrs = elem.getElement(0).getAttributes();
+ AttributeSet newAttrs;
+ if (attrs.isEqual(stdErrStyleOld)) {
+ newAttrs = stdErrStyle;
+ } else if (attrs.isEqual(stdOutStyleOld)) {
+ newAttrs = stdOutStyle;
+ } else {
+ continue;
+ }
+ try {
+ String text = document.getText(start, end - start);
+ document.remove(start, end - start);
+ document.insertString(start, text, newAttrs);
+ } catch (BadLocationException e) {
+ // Should only happen when text is async removed (through clear()).
+ // Accept this case, but throw an error when text could mess up.
+ if (document.getLength() != 0) {
+ throw new Error(e);
+ }
+ }
}
}
-
- // to console display
- appendText(what, err);
- // moved down here since something is punting
}
-
- /**
- * Append a piece of text to the console.
- *
- * Swing components are NOT thread-safe, and since the MessageSiphon
- * instantiates new threads, and in those callbacks, they often print
- * output to stdout and stderr, which are wrapped by EditorConsoleStream
- * and eventually leads to EditorConsole.appendText(), which directly
- * updates the Swing text components, causing deadlock.
- *
- * Updates are buffered to the console and displayed at regular
- * intervals on Swing's event-dispatching thread. (patch by David Mellis)
- */
- synchronized private void appendText(String txt, boolean e) {
- consoleDoc.appendString(txt, e ? errStyle : stdStyle);
- }
-
-
public void clear() {
try {
- consoleDoc.remove(0, consoleDoc.getLength());
+ document.remove(0, document.getLength());
+ startOfLine = 0;
+ insertPosition = 0;
} catch (BadLocationException e) {
// ignore the error otherwise this will cause an infinite loop
// maybe not a good idea in the long run?
}
}
-
-
- // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
-
-
- private static class EditorConsoleStream extends OutputStream {
- //static EditorConsole current;
- final boolean err; // whether stderr or stdout
- final byte single[] = new byte[1];
-
- public EditorConsoleStream(boolean err) {
- this.err = err;
- }
-
- public void close() { }
-
- public void flush() { }
-
- public void write(byte b[]) { // appears never to be used
- if (currentConsole != null) {
- currentConsole.write(b, 0, b.length, err);
- } else {
- try {
- if (err) {
- systemErr.write(b);
- } else {
- systemOut.write(b);
- }
- } catch (IOException e) { } // just ignore, where would we write?
- }
-
- OutputStream echo = err ? stderrFile : stdoutFile;
- if (echo != null) {
- try {
- echo.write(b);
- echo.flush();
- } catch (IOException e) {
- e.printStackTrace();
- echo = null;
- }
- }
- }
-
- public void write(byte b[], int offset, int length) {
- if (currentConsole != null) {
- currentConsole.write(b, offset, length, err);
- } else {
- try {
- if (err) {
- systemErr.write(b);
- } else {
- systemOut.write(b);
- }
- } catch (IOException e) { } // just ignore, where would we write?
- }
-
- OutputStream echo = err ? stderrFile : stdoutFile;
- if (echo != null) {
- try {
- echo.write(b, offset, length);
- echo.flush();
- } catch (IOException e) {
- e.printStackTrace();
- echo = null;
- }
- }
- }
- public void write(int b) {
- single[0] = (byte)b;
- if (currentConsole != null) {
- currentConsole.write(single, 0, 1, err);
- } else {
- // redirect for all the extra handling above
- write(new byte[] { (byte) b }, 0, 1);
- }
-
- OutputStream echo = err ? stderrFile : stdoutFile;
- if (echo != null) {
- try {
- echo.write(b);
- echo.flush();
- } catch (IOException e) {
- e.printStackTrace();
- echo = null;
- }
- }
- }
+ public void scrollDown() {
+ getHorizontalScrollBar().setValue(0);
+ getVerticalScrollBar().setValue(getVerticalScrollBar().getMaximum());
}
-}
-
-
-// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
-
-/**
- * Buffer updates to the console and output them in batches. For info, see:
- * http://java.sun.com/products/jfc/tsc/articles/text/element_buffer and
- * http://javatechniques.com/public/java/docs/gui/jtextpane-speed-part2.html
- * appendString() is called from multiple threads, and insertAll from the
- * swing event thread, so they need to be synchronized
- */
-class BufferedStyledDocument extends DefaultStyledDocument {
- ArrayList elements = new ArrayList();
- int maxLineLength, maxLineCount;
- int currentLineLength = 0;
- boolean needLineBreak = false;
- boolean hasAppendage = false;
-
- public BufferedStyledDocument(int maxLineLength, int maxLineCount) {
- this.maxLineLength = maxLineLength;
- this.maxLineCount = maxLineCount;
+ public boolean isEmpty() {
+ return document.getLength() == 0;
}
- /** buffer a string for insertion at the end of the DefaultStyledDocument */
- public synchronized void appendString(String str, AttributeSet a) {
- // do this so that it's only updated when needed (otherwise console
- // updates every 250 ms when an app isn't even running.. see bug 180)
- hasAppendage = true;
-
- // process each line of the string
- while (str.length() > 0) {
- // newlines within an element have (almost) no effect, so we need to
- // replace them with proper paragraph breaks (start and end tags)
- if (needLineBreak || currentLineLength > maxLineLength) {
- elements.add(new ElementSpec(a, ElementSpec.EndTagType));
- elements.add(new ElementSpec(a, ElementSpec.StartTagType));
- currentLineLength = 0;
+ public void insertString(String str, SimpleAttributeSet attributes) throws BadLocationException {
+ // Separate the string into content, newlines and lone carriage
+ // returns.
+ //
+ // Doing so allows lone CRs to move the insertPosition back to the
+ // start of the line to allow overwriting the most recent line (e.g.
+ // for a progress bar). Any CR or NL that are immediately followed
+ // by another NL are bunched together for efficiency, since these
+ // can just be inserted into the document directly and still be
+ // correct.
+ //
+ // The regex is written so it will necessarily match any string
+ // completely if applied repeatedly. This is important because any
+ // part not matched would be silently dropped.
+ Matcher m = newLinePattern.matcher(str);
+
+ while (m.find()) {
+ String content = m.group(1);
+ String newlines = m.group(2);
+ String crs = m.group(3);
+
+ // Replace (or append if at end of the document) the content first
+ int replaceLength = Math.min(content.length(), document.getLength() - insertPosition);
+ document.replace(insertPosition, replaceLength, content, attributes);
+ insertPosition += content.length();
+
+ // Then insert any newlines, but always at the end of the document
+ // e.g. if insertPosition is halfway a line, do not delete
+ // anything, just add the newline(s) at the end).
+ if (newlines != null) {
+ document.insertString(document.getLength(), newlines, attributes);
+ insertPosition = document.getLength();
+ startOfLine = insertPosition;
}
- if (str.indexOf('\n') == -1) {
- elements.add(new ElementSpec(a, ElementSpec.ContentType,
- str.toCharArray(), 0, str.length()));
- currentLineLength += str.length();
- needLineBreak = false;
- str = str.substring(str.length()); // eat the string
- } else {
- elements.add(new ElementSpec(a, ElementSpec.ContentType,
- str.toCharArray(), 0, str.indexOf('\n') + 1));
- needLineBreak = true;
- str = str.substring(str.indexOf('\n') + 1); // eat the line
+ // Then, for any CRs not followed by newlines, move insertPosition
+ // to the start of the line. Note that if a newline follows before
+ // any content in the next call to insertString, it will be added
+ // at the end of the document anyway, as expected.
+ if (crs != null) {
+ insertPosition = startOfLine;
}
}
}
- /** insert the buffered strings */
- public synchronized void insertAll() {
- ElementSpec[] elementArray = new ElementSpec[elements.size()];
- elements.toArray(elementArray);
-
- try {
- // check how many lines have been used so far
- // if too many, shave off a few lines from the beginning
- Element element = super.getDefaultRootElement();
- int lineCount = element.getElementCount();
- int overage = lineCount - maxLineCount;
- if (overage > 0) {
- // if 1200 lines, and 1000 lines is max,
- // find the position of the end of the 200th line
- //systemOut.println("overage is " + overage);
- Element lineElement = element.getElement(overage);
- if (lineElement == null) return; // do nuthin
-
- int endOffset = lineElement.getEndOffset();
- // remove to the end of the 200th line
- super.remove(0, endOffset);
- }
- super.insert(super.getLength(), elementArray);
-
- } catch (BadLocationException e) {
- // ignore the error otherwise this will cause an infinite loop
- // maybe not a good idea in the long run?
- }
- elements.clear();
- hasAppendage = false;
+ public String getText() {
+ return consoleTextPane.getText();
}
+
}
diff --git a/app/src/processing/app/EditorHeader.java b/app/src/processing/app/EditorHeader.java
index d40eddacf51..c5695cf8abd 100644
--- a/app/src/processing/app/EditorHeader.java
+++ b/app/src/processing/app/EditorHeader.java
@@ -23,15 +23,24 @@
package processing.app;
+import processing.app.helpers.Keys;
+import processing.app.helpers.OSUtils;
+import processing.app.helpers.SimpleAction;
+import processing.app.tools.MenuScroller;
+import static processing.app.I18n.tr;
+
import java.awt.*;
import java.awt.event.*;
-
+import java.io.IOException;
+import java.util.List;
import javax.swing.*;
+import static processing.app.Theme.scale;
/**
* Sketch tabs at the top of the editor window.
*/
+@SuppressWarnings("serial")
public class EditorHeader extends JComponent {
static Color backgroundColor;
static Color textColor[] = new Color[2];
@@ -57,33 +66,99 @@ public class EditorHeader extends JComponent {
static final int UNSELECTED = 0;
static final int SELECTED = 1;
- static final String WHERE[] = { "left", "mid", "right", "menu" };
+ static final String WHERE[] = { "left", "mid", "right" };
static final int LEFT = 0;
static final int MIDDLE = 1;
static final int RIGHT = 2;
- static final int MENU = 3;
- static final int PIECE_WIDTH = 4;
+ static final int PIECE_WIDTH = scale(4);
+ static final int PIECE_HEIGHT = scale(33);
+ static final int TAB_HEIGHT = scale(27);
- static Image[][] pieces;
+ // value for the size bars, buttons, etc
+ // TODO: Should be a Theme value?
+ static final int GRID_SIZE = 33;
- //
+ static Image[][] pieces;
+ static Image menuButtons[];
Image offscreen;
int sizeW, sizeH;
int imageW, imageH;
+ public class Actions {
+ public final Action newTab = new SimpleAction(tr("New Tab"),
+ Keys.ctrlShift(KeyEvent.VK_N),
+ () -> editor.getSketchController().handleNewCode());
+
+ public final Action renameTab = new SimpleAction(tr("Rename"),
+ () -> editor.getSketchController().handleRenameCode());
+
+ public final Action deleteTab = new SimpleAction(tr("Delete"), () -> {
+ try {
+ editor.getSketchController().handleDeleteCode();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ });
+
+ public final Action prevTab = new SimpleAction(tr("Previous Tab"),
+ Keys.ctrlAlt(KeyEvent.VK_LEFT), () -> editor.selectPrevTab());
+
+ public final Action nextTab = new SimpleAction(tr("Next Tab"),
+ Keys.ctrlAlt(KeyEvent.VK_RIGHT), () -> editor.selectNextTab());
+
+ Actions() {
+ // Explicitly bind keybindings for the actions with accelerators above
+ // Normally, this happens automatically for any actions bound to menu
+ // items, but only for menus attached to a window, not for popup menus.
+ Keys.bind(EditorHeader.this, newTab);
+ Keys.bind(EditorHeader.this, prevTab);
+ Keys.bind(EditorHeader.this, nextTab);
+
+ // Add alternative keybindings to switch tabs
+ Keys.bind(EditorHeader.this, prevTab, Keys.ctrlShift(KeyEvent.VK_TAB));
+ Keys.bind(EditorHeader.this, nextTab, Keys.ctrl(KeyEvent.VK_TAB));
+ }
+ }
+ public Actions actions = new Actions();
+
+ /**
+ * Called whenever we, or any of our ancestors, is added to a container.
+ */
+ public void addNotify() {
+ super.addNotify();
+ /*
+ * Once we get added to a window, remove Ctrl-Tab and Ctrl-Shift-Tab from
+ * the keys used for focus traversal (so our bindings for these keys will
+ * work). All components inherit from the window eventually, so this should
+ * work whenever the focus is inside our window. Some components (notably
+ * JTextPane / JEditorPane) keep their own focus traversal keys, though, and
+ * have to be treated individually (either the same as below, or by
+ * disabling focus traversal entirely).
+ */
+ Window window = SwingUtilities.getWindowAncestor(this);
+ if (window != null) {
+ Keys.killFocusTraversalBinding(window, Keys.ctrl(KeyEvent.VK_TAB));
+ Keys.killFocusTraversalBinding(window, Keys.ctrlShift(KeyEvent.VK_TAB));
+ }
+ }
public EditorHeader(Editor eddie) {
this.editor = eddie; // weird name for listener
if (pieces == null) {
pieces = new Image[STATUS.length][WHERE.length];
+ menuButtons = new Image[STATUS.length];
for (int i = 0; i < STATUS.length; i++) {
for (int j = 0; j < WHERE.length; j++) {
- String path = "tab-" + STATUS[i] + "-" + WHERE[j] + ".gif";
- pieces[i][j] = Base.getThemeImage(path, this);
+ String path = "tab-" + STATUS[i] + "-" + WHERE[j];
+ pieces[i][j] = Theme.getThemeImage(path, this, PIECE_WIDTH,
+ PIECE_HEIGHT);
}
+ String path = "tab-" + STATUS[i] + "-menu";
+ menuButtons[i] = Theme.getThemeImage(path, this, PIECE_HEIGHT,
+ PIECE_HEIGHT);
}
}
@@ -105,10 +180,10 @@ public void mousePressed(MouseEvent e) {
popup.show(EditorHeader.this, x, y);
} else {
- Sketch sketch = editor.getSketch();
- for (int i = 0; i < sketch.getCodeCount(); i++) {
+ int numTabs = editor.getTabs().size();
+ for (int i = 0; i < numTabs; i++) {
if ((x > tabLeft[i]) && (x < tabRight[i])) {
- sketch.setCurrentCode(i);
+ editor.selectTab(i);
repaint();
}
}
@@ -121,7 +196,7 @@ public void mousePressed(MouseEvent e) {
public void paintComponent(Graphics screen) {
if (screen == null) return;
- Sketch sketch = editor.getSketch();
+ SketchController sketch = editor.getSketchController();
if (sketch == null) return; // ??
Dimension size = getSize();
@@ -147,47 +222,42 @@ public void paintComponent(Graphics screen) {
offscreen = createImage(imageW, imageH);
}
- Graphics g = offscreen.getGraphics();
+ Graphics2D g = Theme.setupGraphics2D(offscreen.getGraphics());
if (font == null) {
font = Theme.getFont("header.text.font");
}
g.setFont(font); // need to set this each time through
metrics = g.getFontMetrics();
fontAscent = metrics.getAscent();
- //}
-
- //Graphics2D g2 = (Graphics2D) g;
- //g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
- // RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
// set the background for the offscreen
g.setColor(backgroundColor);
g.fillRect(0, 0, imageW, imageH);
- int codeCount = sketch.getCodeCount();
+ List tabs = editor.getTabs();
+
+ int codeCount = tabs.size();
if ((tabLeft == null) || (tabLeft.length < codeCount)) {
tabLeft = new int[codeCount];
tabRight = new int[codeCount];
}
- int x = 6; // offset from left edge of the component
- for (int i = 0; i < sketch.getCodeCount(); i++) {
- SketchCode code = sketch.getCode(i);
-
- String codeName = sketch.hideExtension(code.getExtension()) ?
- code.getPrettyName() : code.getFileName();
+ int x = scale(6); // offset from left edge of the component
+ int i = 0;
+ for (EditorTab tab : tabs) {
+ SketchFile file = tab.getSketchFile();
+ String filename = file.getPrettyName();
// if modified, add the li'l glyph next to the name
- String text = " " + codeName + (code.isModified() ? " \u00A7" : " ");
+ String text = " " + filename + (file.isModified() ? " \u00A7" : " ");
- Graphics2D g2 = (Graphics2D) g;
int textWidth = (int)
- font.getStringBounds(text, g2.getFontRenderContext()).getWidth();
+ font.getStringBounds(text, g.getFontRenderContext()).getWidth();
int pieceCount = 2 + (textWidth / PIECE_WIDTH);
int pieceWidth = pieceCount * PIECE_WIDTH;
- int state = (code == sketch.getCurrentCode()) ? SELECTED : UNSELECTED;
+ int state = (i == editor.getCurrentTabIndex()) ? SELECTED : UNSELECTED;
g.drawImage(pieces[state][LEFT], x, 0, null);
x += PIECE_WIDTH;
@@ -201,18 +271,19 @@ public void paintComponent(Graphics screen) {
int textLeft = contentLeft + (pieceWidth - textWidth) / 2;
g.setColor(textColor[state]);
- int baseline = (sizeH + fontAscent) / 2;
- //g.drawString(sketch.code[i].name, textLeft, baseline);
+ int tabMarginTop = sizeH - TAB_HEIGHT;
+ int baseline = tabMarginTop + ((TAB_HEIGHT + fontAscent) / 2) ;
g.drawString(text, textLeft, baseline);
g.drawImage(pieces[state][RIGHT], x, 0, null);
x += PIECE_WIDTH - 1; // overlap by 1 pixel
+ i++;
}
- menuLeft = sizeW - (16 + pieces[0][MENU].getWidth(this));
+ menuLeft = sizeW - (16 + menuButtons[0].getWidth(this));
menuRight = sizeW - 16;
// draw the dropdown menu target
- g.drawImage(pieces[popup.isVisible() ? SELECTED : UNSELECTED][MENU],
+ g.drawImage(menuButtons[popup.isVisible() ? SELECTED : UNSELECTED],
menuLeft, 0, null);
screen.drawImage(offscreen, 0, 0, null);
@@ -231,166 +302,59 @@ public void rebuild() {
public void rebuildMenu() {
- //System.out.println("rebuilding");
if (menu != null) {
menu.removeAll();
} else {
menu = new JMenu();
+ MenuScroller.setScrollerFor(menu);
popup = menu.getPopupMenu();
- add(popup);
popup.setLightWeightPopupEnabled(true);
-
- /*
- popup.addPopupMenuListener(new PopupMenuListener() {
- public void popupMenuCanceled(PopupMenuEvent e) {
- // on redraw, the isVisible() will get checked.
- // actually, a repaint may be fired anyway, so this
- // may be redundant.
- repaint();
- }
-
- public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { }
- public void popupMenuWillBecomeVisible(PopupMenuEvent e) { }
- });
- */
}
JMenuItem item;
- // maybe this shouldn't have a command key anyways..
- // since we're not trying to make this a full ide..
- //item = Editor.newJMenuItem("New", 'T');
-
- /*
- item = Editor.newJMenuItem("Previous", KeyEvent.VK_PAGE_UP);
- item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- System.out.println("prev");
- }
- });
- if (editor.sketch != null) {
- item.setEnabled(editor.sketch.codeCount > 1);
- }
- menu.add(item);
-
- item = Editor.newJMenuItem("Next", KeyEvent.VK_PAGE_DOWN);
- item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- System.out.println("ext");
- }
- });
- if (editor.sketch != null) {
- item.setEnabled(editor.sketch.codeCount > 1);
- }
- menu.add(item);
-
+ menu.add(new JMenuItem(actions.newTab));
+ menu.add(new JMenuItem(actions.renameTab));
+ menu.add(new JMenuItem(actions.deleteTab));
menu.addSeparator();
- */
-
- //item = new JMenuItem("New Tab");
- item = Editor.newJMenuItemShift("New Tab", 'N');
- item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- editor.getSketch().handleNewCode();
- }
- });
- menu.add(item);
-
- item = new JMenuItem("Rename");
- item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- editor.getSketch().handleRenameCode();
- /*
- // this is already being called by nameCode(), the second stage of rename
- if (editor.sketch.current == editor.sketch.code[0]) {
- editor.sketchbook.rebuildMenus();
- }
- */
- }
- });
- menu.add(item);
-
- item = new JMenuItem("Delete");
- item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- editor.getSketch().handleDeleteCode();
- }
- });
- menu.add(item);
-
- menu.addSeparator();
-
- // KeyEvent.VK_LEFT and VK_RIGHT will make Windows beep
-
- item = new JMenuItem("Previous Tab");
- KeyStroke ctrlAltLeft =
- KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, Editor.SHORTCUT_ALT_KEY_MASK);
- item.setAccelerator(ctrlAltLeft);
- // this didn't want to work consistently
- /*
- item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- editor.sketch.prevCode();
- }
- });
- */
- menu.add(item);
-
- item = new JMenuItem("Next Tab");
- KeyStroke ctrlAltRight =
- KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, Editor.SHORTCUT_ALT_KEY_MASK);
- item.setAccelerator(ctrlAltRight);
- /*
- item.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- editor.sketch.nextCode();
- }
- });
- */
- menu.add(item);
+ menu.add(new JMenuItem(actions.prevTab));
+ menu.add(new JMenuItem(actions.nextTab));
Sketch sketch = editor.getSketch();
if (sketch != null) {
menu.addSeparator();
- ActionListener jumpListener = new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- editor.getSketch().setCurrentCode(e.getActionCommand());
- }
- };
- for (SketchCode code : sketch.getCode()) {
- item = new JMenuItem(code.isExtension(sketch.getDefaultExtension()) ?
- code.getPrettyName() : code.getFileName());
- item.setActionCommand(code.getFileName());
- item.addActionListener(jumpListener);
+ int i = 0;
+ for (EditorTab tab : editor.getTabs()) {
+ SketchFile file = tab.getSketchFile();
+ final int index = i++;
+ item = new JMenuItem(file.getPrettyName());
+ item.addActionListener((ActionEvent e) -> {
+ editor.selectTab(index);
+ });
menu.add(item);
}
}
}
- public void deselectMenu() {
- repaint();
- }
-
-
public Dimension getPreferredSize() {
return getMinimumSize();
}
public Dimension getMinimumSize() {
- if (Base.isMacOS()) {
- return new Dimension(300, Preferences.GRID_SIZE);
- }
- return new Dimension(300, Preferences.GRID_SIZE - 1);
+ Dimension size = scale(new Dimension(300, GRID_SIZE));
+ if (OSUtils.isMacOS())
+ size.height--;
+ return size;
}
public Dimension getMaximumSize() {
- if (Base.isMacOS()) {
- return new Dimension(3000, Preferences.GRID_SIZE);
- }
- return new Dimension(3000, Preferences.GRID_SIZE - 1);
+ Dimension size = scale(new Dimension(30000, GRID_SIZE));
+ if (OSUtils.isMacOS())
+ size.height--;
+ return size;
}
}
diff --git a/app/src/processing/app/EditorLineStatus.java b/app/src/processing/app/EditorLineStatus.java
index f28175ff0aa..f768b597c28 100644
--- a/app/src/processing/app/EditorLineStatus.java
+++ b/app/src/processing/app/EditorLineStatus.java
@@ -22,47 +22,51 @@
package processing.app;
-import processing.app.syntax.*;
-
import java.awt.*;
-import javax.swing.*;
+import java.awt.geom.Rectangle2D;
+import javax.swing.JComponent;
+
+import processing.app.helpers.OSUtils;
+import processing.app.helpers.PreferencesMap;
+import static processing.app.I18n.tr;
+import static processing.app.Theme.scale;
/**
* Li'l status bar fella that shows the line number.
*/
public class EditorLineStatus extends JComponent {
- JEditTextArea textarea;
+
int start = -1, stop;
Image resize;
+ private static final int RESIZE_IMAGE_SIZE = scale(20);
Color foreground;
Color background;
+ Color messageForeground;
+
Font font;
- int high;
+ int height;
String text = "";
+ String name = "";
+ String port = "";
-
- public EditorLineStatus(JEditTextArea textarea) {
- this.textarea = textarea;
- textarea.editorLineStatus = this;
-
+ public EditorLineStatus() {
background = Theme.getColor("linestatus.bgcolor");
font = Theme.getFont("linestatus.font");
foreground = Theme.getColor("linestatus.color");
- high = Theme.getInteger("linestatus.height");
+ height = Theme.getInteger("linestatus.height");
- if (Base.isMacOS()) {
- resize = Base.getThemeImage("resize.gif", this);
+ if (OSUtils.isMacOS()) {
+ resize = Theme.getThemeImage("resize", this, RESIZE_IMAGE_SIZE, RESIZE_IMAGE_SIZE);
}
//linestatus.bgcolor = #000000
//linestatus.font = SansSerif,plain,10
//linestatus.color = #FFFFFF
}
-
public void set(int newStart, int newStop) {
if ((newStart == start) && (newStop == stop)) return;
@@ -85,25 +89,49 @@ public void set(int newStart, int newStop) {
repaint();
}
-
- public void paintComponent(Graphics g) {
+ public void paintComponent(Graphics graphics) {
+ Graphics2D g = Theme.setupGraphics2D(graphics);
+ if (name.isEmpty() && port.isEmpty()) {
+ updateBoardAndPort();
+ }
g.setColor(background);
Dimension size = getSize();
g.fillRect(0, 0, size.width, size.height);
g.setFont(font);
g.setColor(foreground);
- int baseline = (high + g.getFontMetrics().getAscent()) / 2;
- g.drawString(text, 6, baseline);
+ int baseline = (size.height + g.getFontMetrics().getAscent()) / 2;
+ g.drawString(text, scale(6), baseline);
- if (Base.isMacOS()) {
- g.drawImage(resize, size.width - 20, 0, this);
+ g.setColor(messageForeground);
+
+ String statusText;
+ if (port != null && !port.isEmpty()) {
+ statusText = I18n.format(tr("{0} on {1}"), name, port);
+ } else {
+ statusText = name;
+ }
+
+ Rectangle2D bounds = g.getFontMetrics().getStringBounds(statusText, null);
+
+ g.drawString(statusText, size.width - (int) bounds.getWidth() - RESIZE_IMAGE_SIZE,
+ baseline);
+
+ if (OSUtils.isMacOS()) {
+ g.drawImage(resize, size.width - RESIZE_IMAGE_SIZE, 0, this);
}
}
+ public void setBoardName(String name) {
+ this.name = name;
+ }
+
+ public void setPort(String port) {
+ this.port = port;
+ }
public Dimension getPreferredSize() {
- return new Dimension(300, high);
+ return scale(new Dimension(300, height));
}
public Dimension getMinimumSize() {
@@ -111,6 +139,15 @@ public Dimension getMinimumSize() {
}
public Dimension getMaximumSize() {
- return new Dimension(3000, high);
+ return scale(new Dimension(3000, height));
+ }
+
+ public void updateBoardAndPort() {
+ PreferencesMap boardPreferences = BaseNoGui.getBoardPreferences();
+ if (boardPreferences != null)
+ setBoardName(boardPreferences.get("name"));
+ else
+ setBoardName("-");
+ setPort(PreferencesData.get("serial.port"));
}
}
diff --git a/app/src/processing/app/EditorListener.java b/app/src/processing/app/EditorListener.java
deleted file mode 100644
index 58c3fc9a14b..00000000000
--- a/app/src/processing/app/EditorListener.java
+++ /dev/null
@@ -1,623 +0,0 @@
-/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
-
-/*
- Part of the Processing project - http://processing.org
-
- Copyright (c) 2004-08 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 as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software Foundation,
- Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-*/
-
-package processing.app;
-
-import processing.app.syntax.*;
-
-import java.awt.*;
-import java.awt.event.*;
-
-
-/**
- * Filters key events for tab expansion/indent/etc.
- *
- * For version 0099, some changes have been made to make the indents
- * smarter. There are still issues though:
- * + indent happens when it picks up a curly brace on the previous line,
- * but not if there's a blank line between them.
- * + It also doesn't handle single indent situations where a brace
- * isn't used (i.e. an if statement or for loop that's a single line).
- * It shouldn't actually be using braces.
- * Solving these issues, however, would probably best be done by a
- * smarter parser/formatter, rather than continuing to hack this class.
- */
-public class EditorListener {
- private Editor editor;
- private JEditTextArea textarea;
-
- private boolean externalEditor;
- private boolean tabsExpand;
- private boolean tabsIndent;
- private int tabSize;
- private String tabString;
- private boolean autoIndent;
-
-// private int selectionStart, selectionEnd;
-// private int position;
-
- /** ctrl-alt on windows and linux, cmd-alt on mac os x */
- static final int CTRL_ALT = ActionEvent.ALT_MASK |
- Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
-
-
- public EditorListener(Editor editor, JEditTextArea textarea) {
- this.editor = editor;
- this.textarea = textarea;
-
- // let him know that i'm leechin'
- textarea.editorListener = this;
-
- applyPreferences();
- }
-
-
- public void applyPreferences() {
- tabsExpand = Preferences.getBoolean("editor.tabs.expand");
- //tabsIndent = Preferences.getBoolean("editor.tabs.indent");
- tabSize = Preferences.getInteger("editor.tabs.size");
- tabString = Editor.EMPTY.substring(0, tabSize);
- autoIndent = Preferences.getBoolean("editor.indent");
- externalEditor = Preferences.getBoolean("editor.external");
- }
-
-
- //public void setExternalEditor(boolean externalEditor) {
- //this.externalEditor = externalEditor;
- //}
-
-
- /**
- * Intercepts key pressed events for JEditTextArea.
- *
- * Called by JEditTextArea inside processKeyEvent(). Note that this
- * won't intercept actual characters, because those are fired on
- * keyTyped().
- * @return true if the event has been handled (to remove it from the queue)
- */
- public boolean keyPressed(KeyEvent event) {
- // don't do things if the textarea isn't editable
- if (externalEditor) return false;
-
- //deselect(); // this is for paren balancing
- char c = event.getKeyChar();
- int code = event.getKeyCode();
-
-// if (code == KeyEvent.VK_SHIFT) {
-// editor.toolbar.setShiftPressed(true);
-// }
-
- //System.out.println((int)c + " " + code + " " + event);
- //System.out.println();
-
- Sketch sketch = editor.getSketch();
-
- if ((event.getModifiers() & CTRL_ALT) == CTRL_ALT) {
- if (code == KeyEvent.VK_LEFT) {
- sketch.handlePrevCode();
- return true;
- } else if (code == KeyEvent.VK_RIGHT) {
- sketch.handleNextCode();
- return true;
- }
- }
-
- if ((event.getModifiers() & KeyEvent.META_MASK) != 0) {
- //event.consume(); // does nothing
- return false;
- }
-
- // TODO i don't like these accessors. clean em up later.
- if (!editor.getSketch().isModified()) {
- if ((code == KeyEvent.VK_BACK_SPACE) || (code == KeyEvent.VK_TAB) ||
- (code == KeyEvent.VK_ENTER) || ((c >= 32) && (c < 128))) {
- sketch.setModified(true);
- }
- }
-
- if ((code == KeyEvent.VK_UP) &&
- ((event.getModifiers() & KeyEvent.CTRL_MASK) != 0)) {
- // back up to the last empty line
- char contents[] = textarea.getText().toCharArray();
- //int origIndex = textarea.getCaretPosition() - 1;
- int caretIndex = textarea.getCaretPosition();
-
- int index = calcLineStart(caretIndex - 1, contents);
- //System.out.println("line start " + (int) contents[index]);
- index -= 2; // step over the newline
- //System.out.println((int) contents[index]);
- boolean onlySpaces = true;
- while (index > 0) {
- if (contents[index] == 10) {
- if (onlySpaces) {
- index++;
- break;
- } else {
- onlySpaces = true; // reset
- }
- } else if (contents[index] != ' ') {
- onlySpaces = false;
- }
- index--;
- }
- // if the first char, index will be -2
- if (index < 0) index = 0;
-
- if ((event.getModifiers() & KeyEvent.SHIFT_MASK) != 0) {
- textarea.setSelectionStart(caretIndex);
- textarea.setSelectionEnd(index);
- } else {
- textarea.setCaretPosition(index);
- }
- event.consume();
- return true;
-
- } else if ((code == KeyEvent.VK_DOWN) &&
- ((event.getModifiers() & KeyEvent.CTRL_MASK) != 0)) {
- char contents[] = textarea.getText().toCharArray();
- int caretIndex = textarea.getCaretPosition();
-
- int index = caretIndex;
- int lineStart = 0;
- boolean onlySpaces = false; // don't count this line
- while (index < contents.length) {
- if (contents[index] == 10) {
- if (onlySpaces) {
- index = lineStart; // this is it
- break;
- } else {
- lineStart = index + 1;
- onlySpaces = true; // reset
- }
- } else if (contents[index] != ' ') {
- onlySpaces = false;
- }
- index++;
- }
- // if the first char, index will be -2
- //if (index < 0) index = 0;
-
- //textarea.setSelectionStart(index);
- //textarea.setSelectionEnd(index);
- if ((event.getModifiers() & KeyEvent.SHIFT_MASK) != 0) {
- textarea.setSelectionStart(caretIndex);
- textarea.setSelectionEnd(index);
- } else {
- textarea.setCaretPosition(index);
- }
- event.consume();
- return true;
- }
-
-
- switch ((int) c) {
-
- case 9: // TAB
- if (textarea.isSelectionActive()) {
- boolean outdent = (event.getModifiers() & KeyEvent.SHIFT_MASK) != 0;
- editor.handleIndentOutdent(!outdent);
-
- } else if (tabsExpand) { // expand tabs
- textarea.setSelectedText(tabString);
- event.consume();
- return true;
-
- } else if (tabsIndent) {
- // this code is incomplete
-
- // if this brace is the only thing on the line, outdent
- //char contents[] = getCleanedContents();
- char contents[] = textarea.getText().toCharArray();
- // index to the character to the left of the caret
- int prevCharIndex = textarea.getCaretPosition() - 1;
-
- // now find the start of this line
- int lineStart = calcLineStart(prevCharIndex, contents);
-
- int lineEnd = lineStart;
- while ((lineEnd < contents.length - 1) &&
- (contents[lineEnd] != 10)) {
- lineEnd++;
- }
-
- // get the number of braces, to determine whether this is an indent
- int braceBalance = 0;
- int index = lineStart;
- while ((index < contents.length) &&
- (contents[index] != 10)) {
- if (contents[index] == '{') {
- braceBalance++;
- } else if (contents[index] == '}') {
- braceBalance--;
- }
- index++;
- }
-
- // if it's a starting indent, need to ignore it, so lineStart
- // will be the counting point. but if there's a closing indent,
- // then the lineEnd should be used.
- int where = (braceBalance > 0) ? lineStart : lineEnd;
- int indent = calcBraceIndent(where, contents);
- if (indent == -1) {
- // no braces to speak of, do nothing
- indent = 0;
- } else {
- indent += tabSize;
- }
-
- // and the number of spaces it has
- int spaceCount = calcSpaceCount(prevCharIndex, contents);
-
- textarea.setSelectionStart(lineStart);
- textarea.setSelectionEnd(lineStart + spaceCount);
- textarea.setSelectedText(Editor.EMPTY.substring(0, indent));
-
- event.consume();
- return true;
- }
- break;
-
- case 10: // auto-indent
- case 13:
- if (autoIndent) {
- char contents[] = textarea.getText().toCharArray();
-
- // this is the previous character
- // (i.e. when you hit return, it'll be the last character
- // just before where the newline will be inserted)
- int origIndex = textarea.getCaretPosition() - 1;
-
- // NOTE all this cursing about CRLF stuff is probably moot
- // NOTE since the switch to JEditTextArea, which seems to use
- // NOTE only LFs internally (thank god). disabling for 0099.
- // walk through the array to the current caret position,
- // and count how many weirdo windows line endings there are,
- // which would be throwing off the caret position number
- /*
- int offset = 0;
- int realIndex = origIndex;
- for (int i = 0; i < realIndex-1; i++) {
- if ((contents[i] == 13) && (contents[i+1] == 10)) {
- offset++;
- realIndex++;
- }
- }
- // back up until \r \r\n or \n.. @#($* cross platform
- //System.out.println(origIndex + " offset = " + offset);
- origIndex += offset; // ARGH!#(* WINDOWS#@($*
- */
-
- // if the previous thing is a brace (whether prev line or
- // up farther) then the correct indent is the number of spaces
- // on that line + 'indent'.
- // if the previous line is not a brace, then just use the
- // identical indentation to the previous line
-
- // calculate the amount of indent on the previous line
- // this will be used *only if the prev line is not an indent*
- int spaceCount = calcSpaceCount(origIndex, contents);
-
- // If the last character was a left curly brace, then indent.
- // For 0122, walk backwards a bit to make sure that the there
- // isn't a curly brace several spaces (or lines) back. Also
- // moved this before calculating extraCount, since it'll affect
- // that as well.
- int index2 = origIndex;
- while ((index2 >= 0) &&
- Character.isWhitespace(contents[index2])) {
- index2--;
- }
- if (index2 != -1) {
- // still won't catch a case where prev stuff is a comment
- if (contents[index2] == '{') {
- // intermediate lines be damned,
- // use the indent for this line instead
- spaceCount = calcSpaceCount(index2, contents);
- spaceCount += tabSize;
- }
- }
- //System.out.println("spaceCount should be " + spaceCount);
-
- // now before inserting this many spaces, walk forward from
- // the caret position and count the number of spaces,
- // so that the number of spaces aren't duplicated again
- int index = origIndex + 1;
- int extraCount = 0;
- while ((index < contents.length) &&
- (contents[index] == ' ')) {
- //spaceCount--;
- extraCount++;
- index++;
- }
- int braceCount = 0;
- while ((index < contents.length) && (contents[index] != '\n')) {
- if (contents[index] == '}') {
- braceCount++;
- }
- index++;
- }
-
- // hitting return on a line with spaces *after* the caret
- // can cause trouble. for 0099, was ignoring the case, but this is
- // annoying, so in 0122 we're trying to fix that.
- /*
- if (spaceCount - extraCount > 0) {
- spaceCount -= extraCount;
- }
- */
- spaceCount -= extraCount;
- //if (spaceCount < 0) spaceCount = 0;
- //System.out.println("extraCount is " + extraCount);
-
- // now, check to see if the current line contains a } and if so,
- // outdent again by indent
- //if (braceCount > 0) {
- //spaceCount -= 2;
- //}
-
- if (spaceCount < 0) {
- // for rev 0122, actually delete extra space
- //textarea.setSelectionStart(origIndex + 1);
- textarea.setSelectionEnd(textarea.getSelectionStop() - spaceCount);
- textarea.setSelectedText("\n");
- } else {
- String insertion = "\n" + Editor.EMPTY.substring(0, spaceCount);
- textarea.setSelectedText(insertion);
- }
-
- // not gonna bother handling more than one brace
- if (braceCount > 0) {
- int sel = textarea.getSelectionStart();
- // sel - tabSize will be -1 if start/end parens on the same line
- // http://dev.processing.org/bugs/show_bug.cgi?id=484
- if (sel - tabSize >= 0) {
- textarea.select(sel - tabSize, sel);
- String s = Editor.EMPTY.substring(0, tabSize);
- // if these are spaces that we can delete
- if (textarea.getSelectedText().equals(s)) {
- textarea.setSelectedText("");
- } else {
- textarea.select(sel, sel);
- }
- }
- }
- } else {
- // Enter/Return was being consumed by somehow even if false
- // was returned, so this is a band-aid to simply fire the event again.
- // http://dev.processing.org/bugs/show_bug.cgi?id=1073
- textarea.setSelectedText(String.valueOf(c));
- }
- // mark this event as already handled (all but ignored)
- event.consume();
- return true;
-
- case '}':
- if (autoIndent) {
- // first remove anything that was there (in case this multiple
- // characters are selected, so that it's not in the way of the
- // spaces for the auto-indent
- if (textarea.getSelectionStart() != textarea.getSelectionStop()) {
- textarea.setSelectedText("");
- }
-
- // if this brace is the only thing on the line, outdent
- char contents[] = textarea.getText().toCharArray();
- // index to the character to the left of the caret
- int prevCharIndex = textarea.getCaretPosition() - 1;
-
- // backup from the current caret position to the last newline,
- // checking for anything besides whitespace along the way.
- // if there's something besides whitespace, exit without
- // messing any sort of indenting.
- int index = prevCharIndex;
- boolean finished = false;
- while ((index != -1) && (!finished)) {
- if (contents[index] == 10) {
- finished = true;
- index++;
- } else if (contents[index] != ' ') {
- // don't do anything, this line has other stuff on it
- return false;
- } else {
- index--;
- }
- }
- if (!finished) return false; // brace with no start
- int lineStartIndex = index;
-
- int pairedSpaceCount = calcBraceIndent(prevCharIndex, contents); //, 1);
- if (pairedSpaceCount == -1) return false;
-
- textarea.setSelectionStart(lineStartIndex);
- textarea.setSelectedText(Editor.EMPTY.substring(0, pairedSpaceCount));
-
- // mark this event as already handled
- event.consume();
- return true;
- }
- break;
- }
- return false;
- }
-
-
-// public boolean keyReleased(KeyEvent event) {
-// if (code == KeyEvent.VK_SHIFT) {
-// editor.toolbar.setShiftPressed(false);
-// }
-// }
-
-
- public boolean keyTyped(KeyEvent event) {
- char c = event.getKeyChar();
-
- if ((event.getModifiers() & KeyEvent.CTRL_MASK) != 0) {
- // on linux, ctrl-comma (prefs) being passed through to the editor
- if (c == KeyEvent.VK_COMMA) {
- event.consume();
- return true;
- }
- }
- return false;
- }
-
-
-
- /**
- * Return the index for the first character on this line.
- */
- protected int calcLineStart(int index, char contents[]) {
- // backup from the current caret position to the last newline,
- // so that we can figure out how far this line was indented
- /*int spaceCount = 0;*/
- boolean finished = false;
- while ((index != -1) && (!finished)) {
- if ((contents[index] == 10) ||
- (contents[index] == 13)) {
- finished = true;
- //index++; // maybe ?
- } else {
- index--; // new
- }
- }
- // add one because index is either -1 (the start of the document)
- // or it's the newline character for the previous line
- return index + 1;
- }
-
-
- /**
- * Calculate the number of spaces on this line.
- */
- protected int calcSpaceCount(int index, char contents[]) {
- index = calcLineStart(index, contents);
-
- int spaceCount = 0;
- // now walk forward and figure out how many spaces there are
- while ((index < contents.length) && (index >= 0) &&
- (contents[index++] == ' ')) {
- spaceCount++;
- }
- return spaceCount;
- }
-
-
- /**
- * Walk back from 'index' until the brace that seems to be
- * the beginning of the current block, and return the number of
- * spaces found on that line.
- */
- protected int calcBraceIndent(int index, char contents[]) {
- // now that we know things are ok to be indented, walk
- // backwards to the last { to see how far its line is indented.
- // this isn't perfect cuz it'll pick up commented areas,
- // but that's not really a big deal and can be fixed when
- // this is all given a more complete (proper) solution.
- int braceDepth = 1;
- boolean finished = false;
- while ((index != -1) && (!finished)) {
- if (contents[index] == '}') {
- // aww crap, this means we're one deeper
- // and will have to find one more extra {
- braceDepth++;
- //if (braceDepth == 0) {
- //finished = true;
- //}
- index--;
- } else if (contents[index] == '{') {
- braceDepth--;
- if (braceDepth == 0) {
- finished = true;
- }
- index--;
- } else {
- index--;
- }
- }
- // never found a proper brace, be safe and don't do anything
- if (!finished) return -1;
-
- // check how many spaces on the line with the matching open brace
- //int pairedSpaceCount = calcSpaceCount(index, contents);
- //System.out.println(pairedSpaceCount);
- return calcSpaceCount(index, contents);
- }
-
-
- /**
- * Get the character array and blank out the commented areas.
- * This hasn't yet been tested, the plan was to make auto-indent
- * less gullible (it gets fooled by braces that are commented out).
- */
- protected char[] getCleanedContents() {
- char c[] = textarea.getText().toCharArray();
-
- int index = 0;
- while (index < c.length - 1) {
- if ((c[index] == '/') && (c[index+1] == '*')) {
- c[index++] = 0;
- c[index++] = 0;
- while ((index < c.length - 1) &&
- !((c[index] == '*') && (c[index+1] == '/'))) {
- c[index++] = 0;
- }
-
- } else if ((c[index] == '/') && (c[index+1] == '/')) {
- // clear out until the end of the line
- while ((index < c.length) && (c[index] != 10)) {
- c[index++] = 0;
- }
- if (index != c.length) {
- index++; // skip over the newline
- }
- }
- }
- return c;
- }
-
- /*
- protected char[] getCleanedContents() {
- char c[] = textarea.getText().toCharArray();
- boolean insideMulti; // multi-line comment
- boolean insideSingle; // single line double slash
-
- //for (int i = 0; i < c.length - 1; i++) {
- int index = 0;
- while (index < c.length - 1) {
- if (insideMulti && (c[index] == '*') && (c[index+1] == '/')) {
- insideMulti = false;
- index += 2;
- } else if ((c[index] == '/') && (c[index+1] == '*')) {
- insideMulti = true;
- index += 2;
- } else if ((c[index] == '/') && (c[index+1] == '/')) {
- // clear out until the end of the line
- while (c[index] != 10) {
- c[index++] = 0;
- }
- index++;
- }
- }
- }
- */
-}
diff --git a/app/src/processing/app/EditorStatus.java b/app/src/processing/app/EditorStatus.java
index a7035ab7c44..2dfc8cf25f9 100644
--- a/app/src/processing/app/EditorStatus.java
+++ b/app/src/processing/app/EditorStatus.java
@@ -23,168 +23,183 @@
package processing.app;
-import java.awt.*;
-import java.awt.event.*;
+import processing.app.helpers.OSUtils;
+import cc.arduino.CompilerProgressListener;
+
import javax.swing.*;
+import java.awt.*;
+import java.awt.datatransfer.Clipboard;
+import java.awt.datatransfer.StringSelection;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+import java.util.ArrayList;
+import static processing.app.I18n.tr;
+import static processing.app.Theme.scale;
/**
* Panel just below the editing area that contains status messages.
*/
-public class EditorStatus extends JPanel /*implements ActionListener*/ {
- static Color bgcolor[];
- static Color fgcolor[];
-
- static final int NOTICE = 0;
- static final int ERR = 1;
- //static final int PROMPT = 2;
- //static final int EDIT = 3;
- static final int EDIT = 2;
-
- static final int YES = 1;
- static final int NO = 2;
- static final int CANCEL = 3;
- static final int OK = 4;
-
- static final String NO_MESSAGE = "";
-
- Editor editor;
-
- int mode;
- String message;
+public class EditorStatus extends JPanel {
+
+ private static final int NOTICE = 0;
+ private static final int ERR = 1;
+ private static final int EDIT = 2;
+ private static final int PROGRESS = 5;
+ private static final String NO_MESSAGE = "";
+
+ private static final Color[] BGCOLOR;
+ private static final Color[] FGCOLOR;
+
+ static {
+ BGCOLOR = new Color[6];
+ BGCOLOR[0] = Theme.getColor("status.notice.bgcolor");
+ BGCOLOR[1] = Theme.getColor("status.error.bgcolor");
+ BGCOLOR[2] = Theme.getColor("status.edit.bgcolor");
+ BGCOLOR[3] = null;
+ BGCOLOR[4] = null;
+ BGCOLOR[5] = Theme.getColor("status.notice.bgcolor");
+
+ FGCOLOR = new Color[6];
+ FGCOLOR[0] = Theme.getColor("status.notice.fgcolor");
+ FGCOLOR[1] = Theme.getColor("status.error.fgcolor");
+ FGCOLOR[2] = Theme.getColor("status.edit.fgcolor");
+ FGCOLOR[3] = null;
+ FGCOLOR[4] = null;
+ FGCOLOR[5] = Theme.getColor("status.notice.fgcolor");
+ }
- Font font;
- FontMetrics metrics;
- int ascent;
+ // value for the size bars, buttons, etc
+ // TODO: Should be a Theme value?
+ static final int GRID_SIZE = 33;
- Image offscreen;
- int sizeW, sizeH;
- int imageW, imageH;
+ private final Editor editor;
+ private final Font font;
- //JButton yesButton;
- //JButton noButton;
- JButton cancelButton;
- JButton okButton;
- JTextField editField;
+ private int mode;
+ private String message;
- //Thread promptThread;
- int response;
+ private Image offscreen;
+ private int sizeW;
+ private int sizeH;
+ private int imageW;
+ private int imageH;
+ private JButton cancelButton;
+ private JButton okButton;
+ private JTextField editField;
+ private JProgressBar progressBar;
+ private JButton copyErrorButton;
+
+ private ArrayList compilerProgressListeners;
public EditorStatus(Editor editor) {
this.editor = editor;
- empty();
-
- if (bgcolor == null) {
- bgcolor = new Color[3]; //4];
- bgcolor[0] = Theme.getColor("status.notice.bgcolor");
- bgcolor[1] = Theme.getColor("status.error.bgcolor");
- bgcolor[2] = Theme.getColor("status.edit.bgcolor");
-
- fgcolor = new Color[3]; //4];
- fgcolor[0] = Theme.getColor("status.notice.fgcolor");
- fgcolor[1] = Theme.getColor("status.error.fgcolor");
- fgcolor[2] = Theme.getColor("status.edit.fgcolor");
- }
+ this.message = NO_MESSAGE;
+ this.mode = NOTICE;
+ this.font = Theme.getFont("status.font");
+ this.compilerProgressListeners = new ArrayList<>();
+ this.compilerProgressListeners.add(this::progressUpdate);
+ initialize();
}
-
- public void empty() {
- mode = NOTICE;
- message = NO_MESSAGE;
- //update();
+ public void clearState() {
+ changeState(NOTICE);
repaint();
}
+ private void changeState(int newMode) {
+ if (mode == newMode) {
+ return;
+ }
- public void notice(String message) {
- mode = NOTICE;
- this.message = message;
- //update();
- repaint();
- }
+ mode = newMode;
- public void unnotice(String unmessage) {
- if (message.equals(unmessage)) empty();
+ if (cancelButton.isVisible()) {
+ cancelButton.doClick();
+ }
+ cancelButton.setVisible(false);
+ okButton.setVisible(false);
+ editField.setVisible(false);
+ progressBar.setVisible(false);
+ copyErrorButton.setVisible(false);
+ message = NO_MESSAGE;
}
-
- public void error(String message) {
- mode = ERR;
+ public void notice(String message) {
+ changeState(NOTICE);
this.message = message;
+ if (copyErrorButton != null) {
+ copyErrorButton.setVisible(false);
+ }
repaint();
}
-
- /*
- public void prompt(String message) {
- mode = PROMPT;
+ public void error(String message) {
+ changeState(ERR);
this.message = message;
-
- response = 0;
- yesButton.setVisible(true);
- noButton.setVisible(true);
- cancelButton.setVisible(true);
- yesButton.requestFocus();
-
+ if (copyErrorButton != null) {
+ copyErrorButton.setVisible(true);
+ }
repaint();
}
-
- // prompt has been handled, re-hide the buttons
- public void unprompt() {
- yesButton.setVisible(false);
- noButton.setVisible(false);
- cancelButton.setVisible(false);
- empty();
- }
- */
-
-
public void edit(String message, String dflt) {
- mode = EDIT;
+ changeState(EDIT);
this.message = message;
- response = 0;
okButton.setVisible(true);
cancelButton.setVisible(true);
editField.setVisible(true);
editField.setText(dflt);
editField.selectAll();
- editField.requestFocus();
+ editField.requestFocusInWindow();
repaint();
}
- public void unedit() {
+ private void unedit() {
okButton.setVisible(false);
cancelButton.setVisible(false);
editField.setVisible(false);
- empty();
+ clearState();
+ repaint();
}
+ public void progress(String message) {
+ changeState(PROGRESS);
+ this.message = message;
+ progressBar.setIndeterminate(false);
+ progressBar.setVisible(true);
+ copyErrorButton.setVisible(false);
+ setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
+ repaint();
+ }
- /*
- public void update() {
- Graphics g = this.getGraphics();
- try {
- setBackground(bgcolor[mode]);
- } catch (NullPointerException e) { } // if not ready yet
- if (g != null) paint(g);
+ public void progressNotice(String message) {
+ this.message = message;
+ repaint();
}
- public void update(Graphics g) {
- paint(g);
+ public void unprogress() {
+ if (PreferencesData.getBoolean("editor.beep.compile")) {
+ Toolkit.getDefaultToolkit().beep();
+ }
+ if (progressBar == null) {
+ return;
+ }
+ progressBar.setVisible(false);
+ progressBar.setValue(0);
+ setCursor(null);
}
- */
+ public void progressUpdate(int value) {
+ if (progressBar == null) return;
+ progressBar.setValue(value);
+ repaint();
+ }
public void paintComponent(Graphics screen) {
- //if (screen == null) return;
- if (okButton == null) setup();
-
- //System.out.println("status.paintComponent");
-
Dimension size = getSize();
if ((size.width != sizeW) || (size.height != sizeH)) {
// component has been resized
@@ -210,222 +225,194 @@ public void paintComponent(Graphics screen) {
offscreen = createImage(imageW, imageH);
}
- Graphics g = offscreen.getGraphics();
- if (font == null) {
- font = Theme.getFont("status.font");
- //new Font("SansSerif", Font.PLAIN, 12));
- g.setFont(font);
- metrics = g.getFontMetrics();
- ascent = metrics.getAscent();
- }
-
- //setBackground(bgcolor[mode]); // does nothing
-
- g.setColor(bgcolor[mode]);
+ Graphics2D g = Theme.setupGraphics2D(offscreen.getGraphics());
+ g.setColor(BGCOLOR[mode]);
g.fillRect(0, 0, imageW, imageH);
+ g.setColor(FGCOLOR[mode]);
- g.setColor(fgcolor[mode]);
g.setFont(font); // needs to be set each time on osx
+ int ascent = g.getFontMetrics().getAscent();
+ assert message != null;
g.drawString(message, Preferences.GUI_SMALL, (sizeH + ascent) / 2);
screen.drawImage(offscreen, 0, 0, null);
}
+ private void initialize() {
+ cancelButton = new JButton(I18n.PROMPT_CANCEL);
+ okButton = new JButton(I18n.PROMPT_OK);
+
+ cancelButton.addActionListener(e -> {
+ if (mode == EDIT) {
+ unedit();
+ }
+ });
+
+ okButton.addActionListener(e -> {
+ // answering to rename/new code question
+ if (mode == EDIT) { // this if() isn't (shouldn't be?) necessary
+ String answer = editField.getText();
+ editor.getSketchController().nameCode(answer);
+ unedit();
+ }
+ });
+
+ if (OSUtils.isMacOS()) {
+ cancelButton.setBackground(BGCOLOR[EDIT]);
+ okButton.setBackground(BGCOLOR[EDIT]);
+ }
+ setLayout(null);
+
+ add(cancelButton);
+ add(okButton);
+
+ cancelButton.setVisible(false);
+ okButton.setVisible(false);
+
+ editField = new JTextField();
- protected void setup() {
- if (okButton == null) {
- cancelButton = new JButton(Preferences.PROMPT_CANCEL);
- okButton = new JButton(Preferences.PROMPT_OK);
+ editField.addKeyListener(new KeyAdapter() {
- cancelButton.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- if (mode == EDIT) {
- unedit();
- //editor.toolbar.clear();
- }
+ // Grab ESC with keyPressed, because it's not making it to keyTyped
+ public void keyPressed(KeyEvent event) {
+ if (event.getKeyChar() == KeyEvent.VK_ESCAPE) {
+ unedit();
+ event.consume();
}
- });
-
- okButton.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- // answering to rename/new code question
- if (mode == EDIT) { // this if() isn't (shouldn't be?) necessary
- String answer = editField.getText();
- editor.getSketch().nameCode(answer);
- unedit();
- }
+ }
+
+ // use keyTyped to catch when the feller is actually
+ // added to the text field. with keyTyped, as opposed to
+ // keyPressed, the keyCode will be zero, even if it's
+ // enter or backspace or whatever, so the keychar should
+ // be used instead. grr.
+ public void keyTyped(KeyEvent event) {
+ int c = event.getKeyChar();
+
+ if (c == KeyEvent.VK_ENTER) { // accept the input
+ String answer = editField.getText();
+ editor.getSketchController().nameCode(answer);
+ unedit();
+ event.consume();
+
+ // easier to test the affirmative case than the negative
+ } else if ((c == KeyEvent.VK_BACK_SPACE) ||
+ (c == KeyEvent.VK_DELETE) ||
+ (c == KeyEvent.VK_RIGHT) ||
+ (c == KeyEvent.VK_LEFT) ||
+ (c == KeyEvent.VK_UP) ||
+ (c == KeyEvent.VK_DOWN) ||
+ (c == KeyEvent.VK_HOME) ||
+ (c == KeyEvent.VK_END) ||
+ (c == KeyEvent.VK_SHIFT)) {
+ // these events are ignored
+
+ } else if (c == KeyEvent.VK_SPACE) {
+ String t = editField.getText();
+ int start = editField.getSelectionStart();
+ int end = editField.getSelectionEnd();
+ editField.setText(t.substring(0, start) + "_" +
+ t.substring(end));
+ editField.setCaretPosition(start + 1);
+ event.consume();
+
+ } else if ((c == '_') || (c == '.') || ((c >= 'A') && (c <= 'Z')) || ((c >= 'a') && (c <= 'z'))) { // allow .pde and .java
+ // these are ok, allow them through
+
+ } else if ((c >= '0') && (c <= '9')) {
+ // these are ok, allow them through
+
+ } else {
+ event.consume();
}
- });
-
- // !@#(* aqua ui #($*(( that turtle-neck wearing #(** (#$@)(
- // os9 seems to work if bg of component is set, but x still a bastard
- if (Base.isMacOS()) {
- //yesButton.setBackground(bgcolor[EDIT]);
- //noButton.setBackground(bgcolor[EDIT]);
- cancelButton.setBackground(bgcolor[EDIT]);
- okButton.setBackground(bgcolor[EDIT]);
}
- setLayout(null);
-
- /*
- yesButton.addActionListener(this);
- noButton.addActionListener(this);
- cancelButton.addActionListener(this);
- okButton.addActionListener(this);
- */
-
- //add(yesButton);
- //add(noButton);
- add(cancelButton);
- add(okButton);
-
- //yesButton.setVisible(false);
- //noButton.setVisible(false);
- cancelButton.setVisible(false);
- okButton.setVisible(false);
-
- editField = new JTextField();
- // disabling, was not in use
- //editField.addActionListener(this);
-
- //if (Base.platform != Base.MACOSX) {
- editField.addKeyListener(new KeyAdapter() {
-
- // Grab ESC with keyPressed, because it's not making it to keyTyped
- public void keyPressed(KeyEvent event) {
- if (event.getKeyChar() == KeyEvent.VK_ESCAPE) {
- unedit();
- //editor.toolbar.clear();
- event.consume();
- }
- }
-
- // use keyTyped to catch when the feller is actually
- // added to the text field. with keyTyped, as opposed to
- // keyPressed, the keyCode will be zero, even if it's
- // enter or backspace or whatever, so the keychar should
- // be used instead. grr.
- public void keyTyped(KeyEvent event) {
- //System.out.println("got event " + event);
- int c = event.getKeyChar();
-
- if (c == KeyEvent.VK_ENTER) { // accept the input
- String answer = editField.getText();
- editor.getSketch().nameCode(answer);
- unedit();
- event.consume();
-
- // easier to test the affirmative case than the negative
- } else if ((c == KeyEvent.VK_BACK_SPACE) ||
- (c == KeyEvent.VK_DELETE) ||
- (c == KeyEvent.VK_RIGHT) ||
- (c == KeyEvent.VK_LEFT) ||
- (c == KeyEvent.VK_UP) ||
- (c == KeyEvent.VK_DOWN) ||
- (c == KeyEvent.VK_HOME) ||
- (c == KeyEvent.VK_END) ||
- (c == KeyEvent.VK_SHIFT)) {
- // these events are ignored
-
- /*
- } else if (c == KeyEvent.VK_ESCAPE) {
- unedit();
- editor.toolbar.clear();
- event.consume();
- */
-
- } else if (c == KeyEvent.VK_SPACE) {
- String t = editField.getText();
- int start = editField.getSelectionStart();
- int end = editField.getSelectionEnd();
- editField.setText(t.substring(0, start) + "_" +
- t.substring(end));
- editField.setCaretPosition(start+1);
- event.consume();
-
- } else if ((c == '_') || (c == '.') || // allow .pde and .java
- ((c >= 'A') && (c <= 'Z')) ||
- ((c >= 'a') && (c <= 'z'))) {
- // these are ok, allow them through
-
- } else if ((c >= '0') && (c <= '9')) {
- // getCaretPosition == 0 means that it's the first char
- // and the field is empty.
- // getSelectionStart means that it *will be* the first
- // char, because the selection is about to be replaced
- // with whatever is typed.
- if ((editField.getCaretPosition() == 0) ||
- (editField.getSelectionStart() == 0)) {
- // number not allowed as first digit
- //System.out.println("bad number bad");
- event.consume();
- }
- } else {
- event.consume();
- //System.out.println("code is " + code + " char = " + c);
- }
- //System.out.println("code is " + code + " char = " + c);
- }
- });
- add(editField);
- editField.setVisible(false);
- }
+ });
+ add(editField);
+ editField.setVisible(false);
+
+ progressBar = new JProgressBar(JScrollBar.HORIZONTAL);
+ progressBar.setIndeterminate(false);
+ progressBar.setValue(0);
+ progressBar.setBorderPainted(true);
+ add(progressBar);
+ progressBar.setVisible(false);
+
+ copyErrorButton = new JButton(tr("Copy error messages"));
+ add(copyErrorButton);
+ copyErrorButton.setVisible(false);
+ copyErrorButton.addActionListener(e -> {
+ String message1 = "";
+ message1 += tr("Arduino: ") + BaseNoGui.VERSION_NAME_LONG + " (" + System.getProperty("os.name") + "), ";
+ message1 += tr("Board: ") + "\"" + BaseNoGui.getBoardPreferences().get("name") + "\"\n\n";
+ message1 += editor.console.getText();
+ if (!(PreferencesData.getBoolean("build.verbose"))) {
+ message1 += "\n\n";
+ message1 += tr("This report would have more information with\n" +
+ "\"Show verbose output during compilation\"\n" +
+ "option enabled in File -> Preferences.\n");
+ }
+ Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+ StringSelection data = new StringSelection(message1);
+ clipboard.setContents(data, null);
+ Clipboard unixclipboard = Toolkit.getDefaultToolkit().getSystemSelection();
+ if (unixclipboard != null) {
+ unixclipboard.setContents(data, null);
+ }
+ });
}
- protected void setButtonBounds() {
+ private void setButtonBounds() {
int top = (sizeH - Preferences.BUTTON_HEIGHT) / 2;
int eachButton = Preferences.GUI_SMALL + Preferences.BUTTON_WIDTH;
- int cancelLeft = sizeW - eachButton;
- int noLeft = cancelLeft - eachButton;
- int yesLeft = noLeft - eachButton;
+ int cancelLeft = sizeW - eachButton;
+ int noLeft = cancelLeft - eachButton;
+ int yesLeft = noLeft - eachButton;
- //yesButton.setLocation(yesLeft, top);
- //noButton.setLocation(noLeft, top);
cancelButton.setLocation(cancelLeft, top);
okButton.setLocation(noLeft, top);
+ progressBar.setLocation(noLeft, top);
- //yesButton.setSize(Preferences.BUTTON_WIDTH, Preferences.BUTTON_HEIGHT);
- //noButton.setSize(Preferences.BUTTON_WIDTH, Preferences.BUTTON_HEIGHT);
cancelButton.setSize(Preferences.BUTTON_WIDTH, Preferences.BUTTON_HEIGHT);
okButton.setSize(Preferences.BUTTON_WIDTH, Preferences.BUTTON_HEIGHT);
+ progressBar.setSize(2 * Preferences.BUTTON_WIDTH, Preferences.BUTTON_HEIGHT);
// edit field height is awkward, and very different between mac and pc,
// so use at least the preferred height for now.
- int editWidth = 2*Preferences.BUTTON_WIDTH;
+ int editWidth = 2 * Preferences.BUTTON_WIDTH;
int editHeight = editField.getPreferredSize().height;
int editTop = (1 + sizeH - editHeight) / 2; // add 1 for ceil
- editField.setBounds(yesLeft - Preferences.BUTTON_WIDTH, editTop,
- editWidth, editHeight);
- }
+ editField.setBounds(yesLeft - Preferences.BUTTON_WIDTH, editTop, editWidth, editHeight);
+ progressBar.setBounds(noLeft, editTop, editWidth, editHeight);
+ Dimension copyErrorButtonSize = copyErrorButton.getPreferredSize();
+ copyErrorButton.setLocation(sizeW - copyErrorButtonSize.width - 5, top);
+ copyErrorButton.setSize(copyErrorButtonSize.width, Preferences.BUTTON_HEIGHT);
+ }
public Dimension getPreferredSize() {
return getMinimumSize();
}
public Dimension getMinimumSize() {
- return new Dimension(300, Preferences.GRID_SIZE);
+ return scale(new Dimension(300, GRID_SIZE));
}
public Dimension getMaximumSize() {
- return new Dimension(3000, Preferences.GRID_SIZE);
+ return scale(new Dimension(3000, GRID_SIZE));
}
-
- public void actionPerformed(ActionEvent e) {
- if (e.getSource() == cancelButton) {
- if (mode == EDIT) unedit();
- //editor.toolbar.clear();
-
- } else if (e.getSource() == okButton) {
- // answering to rename/new code question
- if (mode == EDIT) { // this if() isn't (shouldn't be?) necessary
- String answer = editField.getText();
- editor.getSketch().nameCode(answer);
- unedit();
- }
- }
+ public boolean isErr() {
+ return mode == ERR;
+ }
+
+ public void addCompilerProgressListener(CompilerProgressListener listener){
+ compilerProgressListeners.add(listener);
}
+
+ public ArrayList getCompilerProgressListeners(){
+ return compilerProgressListeners;
+ }
+
}
diff --git a/app/src/processing/app/EditorTab.java b/app/src/processing/app/EditorTab.java
new file mode 100644
index 00000000000..59bfe3c77d7
--- /dev/null
+++ b/app/src/processing/app/EditorTab.java
@@ -0,0 +1,633 @@
+/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
+
+/*
+ Part of the Arduino project - http://www.arduino.cc
+
+ Copyright (c) 2015 Matthijs Kooijman
+ 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 static processing.app.I18n.tr;
+import static processing.app.Theme.scale;
+
+import java.awt.BorderLayout;
+import java.awt.Font;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.FocusEvent;
+import java.awt.event.FocusListener;
+import java.io.IOException;
+
+import javax.swing.Action;
+import javax.swing.BorderFactory;
+import javax.swing.JMenuItem;
+import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+import javax.swing.ToolTipManager;
+import javax.swing.border.MatteBorder;
+import javax.swing.event.PopupMenuEvent;
+import javax.swing.event.PopupMenuListener;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.Element;
+import javax.swing.text.PlainDocument;
+import javax.swing.text.DefaultCaret;
+import javax.swing.text.Document;
+
+import org.apache.commons.lang3.StringUtils;
+import org.fife.ui.rsyntaxtextarea.RSyntaxDocument;
+import org.fife.ui.rsyntaxtextarea.RSyntaxTextAreaEditorKit;
+import org.fife.ui.rsyntaxtextarea.RSyntaxUtilities;
+import org.fife.ui.rtextarea.Gutter;
+import org.fife.ui.rtextarea.RTextScrollPane;
+
+import cc.arduino.UpdatableBoardsLibsFakeURLsHandler;
+import processing.app.helpers.DocumentTextChangeListener;
+import processing.app.syntax.ArduinoTokenMakerFactory;
+import processing.app.syntax.PdeKeywords;
+import processing.app.syntax.SketchTextArea;
+import processing.app.syntax.SketchTextAreaEditorKit;
+import processing.app.tools.DiscourseFormat;
+
+/**
+ * Single tab, editing a single file, in the main window.
+ */
+public class EditorTab extends JPanel implements SketchFile.TextStorage {
+ protected Editor editor;
+ protected SketchTextArea textarea;
+ protected RTextScrollPane scrollPane;
+ protected SketchFile file;
+ protected boolean modified;
+ /** Is external editing mode currently enabled? */
+ protected boolean external;
+
+ /**
+ * Create a new EditorTab
+ *
+ * @param editor
+ * The Editor this tab runs in
+ * @param file
+ * The file to display in this tab
+ * @param contents
+ * Initial contents to display in this tab. Can be used when editing
+ * a file that doesn't exist yet. If null is passed, code.load() is
+ * called and displayed instead.
+ * @throws IOException
+ */
+ public EditorTab(Editor editor, SketchFile file, String contents)
+ throws IOException {
+ super(new BorderLayout());
+
+ // Load initial contents contents from file if nothing was specified.
+ if (contents == null) {
+ contents = file.load();
+ modified = false;
+ } else {
+ modified = true;
+ }
+
+ this.editor = editor;
+ this.file = file;
+ RSyntaxDocument document = createDocument(contents);
+ textarea = createTextArea(document);
+ scrollPane = createScrollPane(textarea);
+ file.setStorage(this);
+ applyPreferences();
+ add(scrollPane, BorderLayout.CENTER);
+ editor.base.addEditorFontResizeMouseWheelListener(textarea);
+ }
+
+ private RSyntaxDocument createDocument(String contents) {
+ RSyntaxDocument document = new RSyntaxDocument(new ArduinoTokenMakerFactory(editor.base.getPdeKeywords()), RSyntaxDocument.SYNTAX_STYLE_CPLUSPLUS);
+ document.putProperty(PlainDocument.tabSizeAttribute, PreferencesData.getInteger("editor.tabs.size"));
+
+ // insert the program text into the document object
+ try {
+ document.insertString(0, contents, null);
+ } catch (BadLocationException bl) {
+ bl.printStackTrace();
+ }
+ document.addDocumentListener(new DocumentTextChangeListener(
+ () -> setModified(true)));
+ return document;
+ }
+
+ private RTextScrollPane createScrollPane(SketchTextArea textArea) throws IOException {
+ RTextScrollPane scrollPane = new RTextScrollPane(textArea, true);
+ scrollPane.setBorder(new MatteBorder(0, 6, 0, 0, Theme.getColor("editor.bgcolor")));
+ scrollPane.setViewportBorder(BorderFactory.createEmptyBorder());
+ scrollPane.setLineNumbersEnabled(PreferencesData.getBoolean("editor.linenumbers"));
+ scrollPane.setIconRowHeaderEnabled(false);
+
+ Gutter gutter = scrollPane.getGutter();
+ gutter.setBookmarkingEnabled(false);
+ //gutter.setBookmarkIcon(CompletionsRenderer.getIcon(CompletionType.TEMPLATE));
+ gutter.setIconRowHeaderInheritsGutterBackground(true);
+
+ return scrollPane;
+ }
+
+ private SketchTextArea createTextArea(RSyntaxDocument document)
+ throws IOException {
+ final SketchTextArea textArea = new SketchTextArea(document, editor.base.getPdeKeywords());
+ textArea.setName("editor");
+ textArea.setFocusTraversalKeysEnabled(false);
+ //textArea.requestFocusInWindow();
+ textArea.setMarkOccurrences(PreferencesData.getBoolean("editor.advanced"));
+ textArea.setMarginLineEnabled(false);
+ textArea.setCodeFoldingEnabled(PreferencesData.getBoolean("editor.code_folding"));
+ textArea.setAutoIndentEnabled(PreferencesData.getBoolean("editor.indent"));
+ textArea.setCloseCurlyBraces(PreferencesData.getBoolean("editor.auto_close_braces", true));
+ textArea.setAntiAliasingEnabled(PreferencesData.getBoolean("editor.antialias"));
+ textArea.setTabsEmulated(PreferencesData.getBoolean("editor.tabs.expand"));
+ textArea.setTabSize(PreferencesData.getInteger("editor.tabs.size"));
+ textArea.addHyperlinkListener(evt -> {
+ try {
+ UpdatableBoardsLibsFakeURLsHandler boardLibHandler = new UpdatableBoardsLibsFakeURLsHandler(editor.base);
+ boardLibHandler.openBoardLibManager(evt.getURL());
+ }
+ catch (Exception e) {
+ try {
+ editor.platform.openURL(editor.getSketch().getFolder(), evt.getURL().toExternalForm());
+ } catch (Exception f) {
+ Base.showWarning(f.getMessage(), f.getMessage(), f);
+ }
+ }
+ });
+ textArea.addCaretListener(e -> {
+ Element root = textArea.getDocument().getDefaultRootElement();
+ int lineStart = root.getElementIndex(e.getMark());
+ int lineEnd = root.getElementIndex(e.getDot());
+
+ editor.lineStatus.set(lineStart, lineEnd);
+ });
+ textArea.addFocusListener(new FocusListener() {
+ public void focusGained(FocusEvent e) {
+ Element root = textArea.getDocument().getDefaultRootElement();
+ int lineStart = root.getElementIndex(textArea.getCaret().getMark());
+ int lineEnd = root.getElementIndex(textArea.getCaret().getDot());
+ editor.lineStatus.set(lineStart, lineEnd);
+ };
+ public void focusLost(FocusEvent e) {};
+ });
+ ToolTipManager.sharedInstance().registerComponent(textArea);
+
+ configurePopupMenu(textArea);
+ return textArea;
+ }
+
+ private void configurePopupMenu(final SketchTextArea textarea){
+
+ JPopupMenu menu = textarea.getPopupMenu();
+
+ menu.addSeparator();
+
+ JMenuItem item = editor.createToolMenuItem("cc.arduino.packages.formatter.AStyle");
+ if (item == null) {
+ throw new NullPointerException("Tool cc.arduino.packages.formatter.AStyle unavailable");
+ }
+ item.setName("menuToolsAutoFormat");
+
+ menu.add(item);
+
+ item = new JMenuItem(tr("Comment/Uncomment"), '/');
+ item.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ handleCommentUncomment();
+ }
+ });
+ menu.add(item);
+
+ item = new JMenuItem(tr("Increase Indent"), ']');
+ item.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ handleIndentOutdent(true);
+ }
+ });
+ menu.add(item);
+
+ item = new JMenuItem(tr("Decrease Indent"), '[');
+ item.setName("menuDecreaseIndent");
+ item.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ handleIndentOutdent(false);
+ }
+ });
+ menu.add(item);
+
+ item = new JMenuItem(tr("Copy for Forum"));
+ item.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ handleDiscourseCopy();
+ }
+ });
+ menu.add(item);
+
+ item = new JMenuItem(tr("Copy as HTML"));
+ item.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ handleHTMLCopy();
+ }
+ });
+ menu.add(item);
+
+ final JMenuItem referenceItem = new JMenuItem(tr("Find in Reference"));
+ referenceItem.addActionListener(ev -> editor.handleFindReference(getCurrentKeyword()));
+ menu.add(referenceItem);
+
+ final JMenuItem openURLItem = new JMenuItem(tr("Open URL"));
+ openURLItem.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ Base.openURL(e.getActionCommand());
+ }
+ });
+ menu.add(openURLItem);
+
+ menu.addPopupMenuListener(new PopupMenuListener() {
+
+ @Override
+ public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
+ String referenceFile = editor.base.getPdeKeywords().getReference(getCurrentKeyword());
+ referenceItem.setEnabled(referenceFile != null);
+
+ int offset = textarea.getCaretPosition();
+ org.fife.ui.rsyntaxtextarea.Token token = RSyntaxUtilities.getTokenAtOffset(textarea, offset);
+ if (token != null && token.isHyperlink()) {
+ openURLItem.setEnabled(true);
+ openURLItem.setActionCommand(token.getLexeme());
+ } else {
+ openURLItem.setEnabled(false);
+ }
+ }
+
+ @Override
+ public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
+ }
+
+ @Override
+ public void popupMenuCanceled(PopupMenuEvent e) {
+ }
+ });
+
+ }
+
+ public void applyPreferences() {
+ textarea.setCodeFoldingEnabled(PreferencesData.getBoolean("editor.code_folding"));
+ scrollPane.setFoldIndicatorEnabled(PreferencesData.getBoolean("editor.code_folding"));
+ scrollPane.setLineNumbersEnabled(PreferencesData.getBoolean("editor.linenumbers"));
+
+ // apply the setting for 'use external editor', but only if it changed
+ if (external != PreferencesData.getBoolean("editor.external")) {
+ external = !external;
+ if (external) {
+ // disable line highlight and turn off the caret when disabling
+ textarea.setBackground(Theme.getColor("editor.external.bgcolor"));
+ textarea.setHighlightCurrentLine(false);
+ textarea.setEditable(false);
+ // Detach from the code, since we are no longer the authoritative source
+ // for file contents.
+ file.setStorage(null);
+ // Reload, in case the file contents already changed.
+ reload();
+ } else {
+ textarea.setBackground(Theme.getColor("editor.bgcolor"));
+ textarea.setHighlightCurrentLine(Theme.getBoolean("editor.linehighlight"));
+ textarea.setEditable(true);
+ file.setStorage(this);
+ // Reload once just before disabling external mode, to ensure we have
+ // the latest contents.
+ reload();
+ }
+ }
+ // apply changes to the font size for the editor
+ Font editorFont = scale(PreferencesData.getFont("editor.font"));
+
+ // check whether a theme-defined editor font is available
+ Font themeFont = Theme.getFont("editor.font");
+ if (themeFont != null)
+ {
+ // Apply theme font if the editor font has *not* been changed by the user,
+ // This allows themes to specify an editor font which will only be applied
+ // if the user hasn't already changed their editor font via preferences.txt
+ String defaultFontName = StringUtils.defaultIfEmpty(PreferencesData.getDefault("editor.font"), "").split(",")[0];
+ if (defaultFontName.equals(editorFont.getName())) {
+ editorFont = new Font(themeFont.getName(), themeFont.getStyle(), editorFont.getSize());
+ }
+ }
+
+ textarea.setFont(editorFont);
+ scrollPane.getGutter().setLineNumberFont(editorFont);
+ }
+
+ public void updateKeywords(PdeKeywords keywords) {
+ // update GUI for "Find In Reference"
+ textarea.setKeywords(keywords);
+ // update document for syntax highlighting
+ RSyntaxDocument document = (RSyntaxDocument) textarea.getDocument();
+ document.setTokenMakerFactory(new ArduinoTokenMakerFactory(keywords));
+ document.setSyntaxStyle(RSyntaxDocument.SYNTAX_STYLE_CPLUSPLUS);
+ }
+
+ /**
+ * Called when this tab is made the current one, or when it is the current one
+ * and the window is activated.
+ */
+ public void activated() {
+ // When external editing is enabled, reload the text whenever we get activated.
+ if (external) {
+ reload();
+ }
+ }
+
+ /**
+ * Reload the contents of our file.
+ */
+ public void reload() {
+ String text;
+ try {
+ text = file.load();
+ } catch (IOException e) {
+ System.err.println(I18n.format("Warning: Failed to reload file: \"{0}\"",
+ file.getFileName()));
+ return;
+ }
+ setText(text);
+ setModified(false);
+ }
+
+ /**
+ * Get the TextArea object for use (not recommended). This should only
+ * be used in obscure cases that really need to hack the internals of the
+ * JEditTextArea. Most tools should only interface via the get/set functions
+ * found in this class. This will maintain compatibility with future releases,
+ * which will not use TextArea.
+ */
+ public SketchTextArea getTextArea() {
+ return textarea;
+ }
+
+ /**
+ * Get the sketch this tab is editing a file from.
+ */
+ public SketchController getSketch() {
+ return editor.getSketchController();
+ }
+
+ /**
+ * Get the SketchFile that is being edited in this tab.
+ */
+ public SketchFile getSketchFile() {
+ return this.file;
+ }
+
+ /**
+ * Get the contents of the text area.
+ */
+ public String getText() {
+ return textarea.getText();
+ }
+
+ /**
+ * Replace the entire contents of this tab.
+ */
+ public void setText(String what) {
+ // Remove all highlights, since these will all end up at the start of the
+ // text otherwise. Preserving them is tricky, so better just remove them.
+ textarea.removeAllLineHighlights();
+ // Set the caret update policy to NEVER_UPDATE while completely replacing
+ // the current text. Normally, the caret tracks inserts and deletions, but
+ // replacing the entire text will always make the caret end up at the end,
+ // which isn't really useful. With NEVER_UPDATE, the caret will just keep
+ // its absolute position (number of characters from the start), which isn't
+ // always perfect, but the best we can do without making a diff of the old
+ // and new text and some guesswork.
+ // Note that we cannot use textarea.setText() here, since that first removes
+ // text and then inserts the new text. Even with NEVER_UPDATE, the caret
+ // always makes sure to stay valid, so first removing all text makes it
+ // reset to 0. Also note that simply saving and restoring the caret position
+ // will work, but then the scroll position might change in response to the
+ // caret position.
+ DefaultCaret caret = (DefaultCaret) textarea.getCaret();
+ int policy = caret.getUpdatePolicy();
+ caret.setUpdatePolicy(DefaultCaret.NEVER_UPDATE);
+ try {
+ Document doc = textarea.getDocument();
+ int oldLength = doc.getLength();
+ // The undo manager already seems to group the insert and remove together
+ // automatically, but better be explicit about it.
+ textarea.beginAtomicEdit();
+ try {
+ doc.insertString(oldLength, what, null);
+ doc.remove(0, oldLength);
+ } catch (BadLocationException e) {
+ System.err.println("Unexpected failure replacing text");
+ } finally {
+ textarea.endAtomicEdit();
+ }
+ } finally {
+ caret.setUpdatePolicy(policy);
+ }
+ // A trick to force textarea to recalculate the bracket matching rectangle.
+ // In the worst case scenario, this should be ineffective.
+ textarea.setLineWrap(textarea.getLineWrap());
+ }
+
+ /**
+ * Is the text modified since the last save / load?
+ */
+ public boolean isModified() {
+ return modified;
+ }
+
+ /**
+ * Clear modified status. Should only be called by SketchFile through the
+ * TextStorage interface.
+ */
+ public void clearModified() {
+ setModified(false);
+ }
+
+ private void setModified(boolean value) {
+ if (value != modified) {
+ modified = value;
+ // TODO: Improve decoupling
+ editor.getSketchController().calcModified();
+ }
+ }
+
+ public String getSelectedText() {
+ return textarea.getSelectedText();
+ }
+
+
+ public void setSelectedText(String what) {
+ textarea.replaceSelection(what);
+ }
+
+ public void setSelection(int start, int stop) {
+ textarea.select(start, stop);
+ }
+
+ public int getScrollPosition() {
+ return scrollPane.getVerticalScrollBar().getValue();
+ }
+
+ public void setScrollPosition(int pos) {
+ scrollPane.getVerticalScrollBar().setValue(pos);
+ }
+
+ /**
+ * Get the beginning point of the current selection.
+ */
+ public int getSelectionStart() {
+ return textarea.getSelectionStart();
+ }
+
+ /**
+ * Get the end point of the current selection.
+ */
+ public int getSelectionStop() {
+ return textarea.getSelectionEnd();
+ }
+
+ /**
+ * Get text for a specified line.
+ */
+ public String getLineText(int line) {
+ try {
+ return textarea.getText(textarea.getLineStartOffset(line), textarea.getLineEndOffset(line));
+ } catch (BadLocationException e) {
+ return "";
+ }
+ }
+
+ /**
+ * Jump to the given line
+ * @param line The line number to jump to, 1-based.
+ */
+ public void goToLine(int line) {
+ if (line <= 0) {
+ return;
+ }
+ try {
+ textarea.setCaretPosition(textarea.getLineStartOffset(line - 1));
+ } catch (BadLocationException e) {
+ //ignore
+ }
+ }
+
+ void handleCut() {
+ textarea.cut();
+ }
+
+ void handleCopy() {
+ textarea.copy();
+ }
+
+ void handlePaste() {
+ textarea.paste();
+ }
+
+ void handleSelectAll() {
+ textarea.selectAll();
+ }
+
+ void handleCommentUncomment() {
+ Action action = textarea.getActionMap().get(RSyntaxTextAreaEditorKit.rstaToggleCommentAction);
+ action.actionPerformed(null);
+ // XXX: RSyntaxDocument doesn't fire DocumentListener events, it should be fixed in RSyntaxTextArea?
+ editor.updateUndoRedoState();
+ }
+
+ void handleDiscourseCopy() {
+ new DiscourseFormat(editor, this, false).show();
+ }
+
+
+ void handleHTMLCopy() {
+ new DiscourseFormat(editor, this, true).show();
+ }
+
+ void handleIndentOutdent(boolean indent) {
+ if (indent) {
+ Action action = textarea.getActionMap().get(SketchTextAreaEditorKit.rtaIncreaseIndentAction);
+ action.actionPerformed(null);
+ } else {
+ Action action = textarea.getActionMap().get(RSyntaxTextAreaEditorKit.rstaDecreaseIndentAction);
+ action.actionPerformed(null);
+ }
+ // XXX: RSyntaxDocument doesn't fire DocumentListener events, it should be fixed in RSyntaxTextArea?
+ editor.updateUndoRedoState();
+ }
+
+ void handleUndo() {
+ textarea.undoLastAction();
+ }
+
+ void handleRedo() {
+ textarea.redoLastAction();
+ }
+
+ public String getCurrentKeyword() {
+ String text = "";
+ if (textarea.getSelectedText() != null)
+ text = textarea.getSelectedText().trim();
+
+ try {
+ int current = textarea.getCaretPosition();
+ int startOffset = 0;
+ int endIndex = current;
+ String tmp = textarea.getDocument().getText(current, 1);
+ // TODO probably a regexp that matches Arduino lang special chars
+ // already exists.
+ String regexp = "[\\s\\n();\\\\.!='\\[\\]{}]";
+
+ while (!tmp.matches(regexp)) {
+ endIndex++;
+ tmp = textarea.getDocument().getText(endIndex, 1);
+ }
+ // For some reason document index start at 2.
+ // if( current - start < 2 ) return;
+
+ tmp = "";
+ while (!tmp.matches(regexp)) {
+ startOffset++;
+ if (current - startOffset < 0) {
+ tmp = textarea.getDocument().getText(0, 1);
+ break;
+ } else
+ tmp = textarea.getDocument().getText(current - startOffset, 1);
+ }
+ startOffset--;
+
+ int length = endIndex - current + startOffset;
+ text = textarea.getDocument().getText(current - startOffset, length);
+
+ } catch (BadLocationException bl) {
+ bl.printStackTrace();
+ }
+ return text;
+ }
+
+ @Override
+ public boolean requestFocusInWindow() {
+ /** If focus is requested, focus the textarea instead. */
+ return textarea.requestFocusInWindow();
+ }
+
+}
diff --git a/app/src/processing/app/EditorToolbar.java b/app/src/processing/app/EditorToolbar.java
index 74ef71f94d8..a2a9b804e70 100644
--- a/app/src/processing/app/EditorToolbar.java
+++ b/app/src/processing/app/EditorToolbar.java
@@ -23,78 +23,111 @@
package processing.app;
-import java.awt.*;
-import java.awt.event.*;
-
+import javax.imageio.ImageIO;
import javax.swing.*;
-import javax.swing.event.*;
+import javax.swing.event.MouseInputListener;
+
+import com.thizzer.jtouchbar.JTouchBar;
+import com.thizzer.jtouchbar.item.TouchBarItem;
+import com.thizzer.jtouchbar.item.view.TouchBarButton;
+
+import cc.arduino.contributions.VersionComparator;
+import processing.app.helpers.OSUtils;
+
+import java.awt.*;
+import java.awt.event.KeyEvent;
+import java.awt.event.MouseEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import static processing.app.I18n.tr;
+import static processing.app.Theme.scale;
/**
* run/stop/etc buttons for the ide
*/
-public class EditorToolbar extends JComponent implements MouseInputListener, KeyListener {
+public class EditorToolbar extends JComponent implements MouseInputListener, KeyEventDispatcher {
- /** Rollover titles for each button. */
- static final String title[] = {
- "Verify", "Stop", "New", "Open", "Save", "Upload", "Serial Monitor"
+ /**
+ * Rollover titles for each button.
+ */
+ private static final String[] title = {
+ tr("Verify"), tr("Upload"), tr("New"), tr("Open"), tr("Save"), tr("Serial Monitor")
};
- /** Titles for each button when the shift key is pressed. */
- static final String titleShift[] = {
- "Verify (w/ Verbose Output)", "Stop", "New Editor Window", "Open in Another Window", "Save", "Upload (w/ Verbose Output)", "Serial Monitor"
+ /**
+ * Titles for each button when the shift key is pressed.
+ */
+ private static final String[] titleShift = {
+ tr("Verify"), tr("Upload Using Programmer"), tr("New"), tr("Open"), tr("Save As..."), tr("Serial Plotter")
};
- static final int BUTTON_COUNT = title.length;
- /** Width of each toolbar button. */
- static final int BUTTON_WIDTH = 27;
- /** Height of each toolbar button. */
- static final int BUTTON_HEIGHT = 32;
- /** The amount of space between groups of buttons on the toolbar. */
- static final int BUTTON_GAP = 5;
- /** Size of the button image being chopped up. */
- static final int BUTTON_IMAGE_SIZE = 33;
+ private static final int BUTTON_COUNT = title.length;
+ /**
+ * Width of each toolbar button.
+ */
+ private static final int BUTTON_WIDTH = scale(27);
+ /**
+ * Height of each toolbar button.
+ */
+ private static final int BUTTON_HEIGHT = scale(32);
+ /**
+ * The amount of space between groups of buttons on the toolbar.
+ */
+ private static final int BUTTON_GAP = scale(5);
+ /**
+ * Size of the button image being chopped up.
+ */
+ private static final int BUTTON_IMAGE_SIZE = scale(33);
- static final int RUN = 0;
- static final int STOP = 1;
+ private static final int RUN = 0;
+ private static final int EXPORT = 1;
- static final int NEW = 2;
- static final int OPEN = 3;
- static final int SAVE = 4;
- static final int EXPORT = 5;
+ private static final int NEW = 2;
+ private static final int OPEN = 3;
+ private static final int SAVE = 4;
- static final int SERIAL = 6;
+ private static final int SERIAL = 5;
- static final int INACTIVE = 0;
- static final int ROLLOVER = 1;
- static final int ACTIVE = 2;
+ private static final int INACTIVE = 0;
+ private static final int ROLLOVER = 1;
+ private static final int ACTIVE = 2;
- Editor editor;
+ private final Editor editor;
- Image offscreen;
- int width, height;
+ private Image offscreen;
+ private int width;
+ private int height;
- Color bgcolor;
+ private final Color bgcolor;
- static Image[][] buttonImages;
- int currentRollover;
+ private static Image[][] buttonImages;
+ private static com.thizzer.jtouchbar.common.Image[][] touchBarImages;
+ private int currentRollover;
- JPopupMenu popup;
- JMenu menu;
+ private JPopupMenu popup;
+ private final JMenu menu;
+ private JTouchBar touchBar;
+ private TouchBarButton[] touchBarButtons;
- int buttonCount;
- int[] state = new int[BUTTON_COUNT];
- Image[] stateImage;
- int which[]; // mapping indices to implementation
+ private int buttonCount;
+ private int[] state = new int[BUTTON_COUNT];
+ private Image[] stateImage;
+ private final int[] which; // mapping indices to implementation
- int x1[], x2[];
- int y1, y2;
+ private int[] x1;
+ private int[] x2;
+ private int y1;
+ private int y2;
- Font statusFont;
- Color statusColor;
+ private final Font statusFont;
+ private final Color statusColor;
- boolean shiftPressed;
+ private boolean shiftPressed;
public EditorToolbar(Editor editor, JMenu menu) {
this.editor = editor;
@@ -105,11 +138,10 @@ public EditorToolbar(Editor editor, JMenu menu) {
//which[buttonCount++] = NOTHING;
which[buttonCount++] = RUN;
- which[buttonCount++] = STOP;
+ which[buttonCount++] = EXPORT;
which[buttonCount++] = NEW;
which[buttonCount++] = OPEN;
which[buttonCount++] = SAVE;
- which[buttonCount++] = EXPORT;
which[buttonCount++] = SERIAL;
currentRollover = -1;
@@ -118,25 +150,110 @@ public EditorToolbar(Editor editor, JMenu menu) {
statusFont = Theme.getFont("buttons.status.font");
statusColor = Theme.getColor("buttons.status.color");
+ if (OSUtils.isMacOS() && VersionComparator.greaterThanOrEqual(OSUtils.version(), "10.12")) {
+ editor.addWindowListener(new WindowAdapter() {
+ public void windowActivated(WindowEvent e) {
+ if (touchBar == null) {
+ buildTouchBar();
+
+ touchBar.show(editor);
+ }
+ }
+ });
+ }
+
addMouseListener(this);
addMouseMotionListener(this);
+ KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(this);
+ }
+
+ private void buildTouchBar() {
+ if (touchBarImages == null) {
+ loadTouchBarImages();
+ }
+
+ touchBar = new JTouchBar();
+ touchBarButtons = new TouchBarButton[BUTTON_COUNT];
+ touchBar.setCustomizationIdentifier("Arduino");
+
+ for (int i = 0; i < BUTTON_COUNT; i++) {
+ final int selection = i;
+
+ // add spacers before NEW and SERIAL buttons
+ if (i == NEW) {
+ touchBar.addItem(new TouchBarItem(TouchBarItem.NSTouchBarItemIdentifierFixedSpaceSmall));
+ } else if (i == SERIAL) {
+ touchBar.addItem(new TouchBarItem(TouchBarItem.NSTouchBarItemIdentifierFlexibleSpace));
+ }
+
+ touchBarButtons[i] = new TouchBarButton();
+ touchBarButtons[i].setImage(touchBarImages[i][ROLLOVER]);
+ touchBarButtons[i].setAction(event -> {
+ // Run event handler later to prevent hanging if a dialog needs to be open
+ EventQueue.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ handleSelectionPressed(selection);
+ }
+ });
+ });
+
+ TouchBarItem touchBarItem = new TouchBarItem(title[i], touchBarButtons[i], true);
+ touchBarItem.setCustomizationLabel(title[i]);
+
+ touchBar.addItem(touchBarItem);
+ }
}
- protected void loadButtons() {
- Image allButtons = Base.getThemeImage("buttons.gif", this);
+ private void loadButtons() {
+ Image allButtons = Theme.getThemeImage("buttons", this,
+ BUTTON_IMAGE_SIZE * BUTTON_COUNT,
+ BUTTON_IMAGE_SIZE * 3);
buttonImages = new Image[BUTTON_COUNT][3];
- for (int i = 0; i < BUTTON_COUNT; i++) {
+ for (int i = 0; i < BUTTON_COUNT; i++) {
for (int state = 0; state < 3; state++) {
Image image = createImage(BUTTON_WIDTH, BUTTON_HEIGHT);
Graphics g = image.getGraphics();
- g.drawImage(allButtons,
- -(i*BUTTON_IMAGE_SIZE) - 3,
- (-2 + state)*BUTTON_IMAGE_SIZE, null);
+ g.setColor(bgcolor);
+ g.fillRect(0, 0, BUTTON_WIDTH, BUTTON_HEIGHT);
+ int offset = (BUTTON_IMAGE_SIZE - BUTTON_WIDTH) / 2;
+ g.drawImage(allButtons, -(i * BUTTON_IMAGE_SIZE) - offset,
+ (-2 + state) * BUTTON_IMAGE_SIZE, null);
buttonImages[i][state] = image;
}
}
}
+
+ private void loadTouchBarImages() {
+ Image allButtonsRetina = Theme.getThemeImage("buttons", this,
+ BUTTON_IMAGE_SIZE * BUTTON_COUNT * 2,
+ BUTTON_IMAGE_SIZE * 3 * 2);
+ touchBarImages = new com.thizzer.jtouchbar.common.Image[BUTTON_COUNT][3];
+
+ for (int i = 0; i < BUTTON_COUNT; i++) {
+ for (int state = 0; state < 3; state++) {
+ BufferedImage image = new BufferedImage(BUTTON_WIDTH * 2, BUTTON_HEIGHT * 2,
+ BufferedImage.TYPE_INT_ARGB);
+ Graphics g = image.getGraphics();
+
+ int offset = (BUTTON_IMAGE_SIZE * 2 - BUTTON_WIDTH * 2) / 2;
+ g.drawImage(allButtonsRetina, -(i * BUTTON_IMAGE_SIZE * 2) - offset,
+ (-2 + state) * BUTTON_IMAGE_SIZE * 2, null);
+
+ // convert the image to a PNG to display on the touch bar
+ ByteArrayOutputStream pngStream = new ByteArrayOutputStream();
+
+ try {
+ ImageIO.write(image, "PNG", pngStream);
+
+ touchBarImages[i][state] = new com.thizzer.jtouchbar.common.Image(pngStream.toByteArray());
+ } catch (IOException e) {
+ // ignore errors
+ }
+ }
+ }
+ }
@Override
public void paintComponent(Graphics screen) {
@@ -160,7 +277,7 @@ public void paintComponent(Graphics screen) {
Dimension size = getSize();
if ((offscreen == null) ||
- (size.width != width) || (size.height != height)) {
+ (size.width != width) || (size.height != height)) {
offscreen = createImage(size.width, size.height);
width = size.width;
height = size.height;
@@ -172,8 +289,12 @@ public void paintComponent(Graphics screen) {
x2[i] = x1[i] + BUTTON_WIDTH;
offsetX = x2[i];
}
+
+ // Serial button must be on the right
+ x1[SERIAL] = width - BUTTON_WIDTH - 14;
+ x2[SERIAL] = width - 14;
}
- Graphics g = offscreen.getGraphics();
+ Graphics2D g = Theme.setupGraphics2D(offscreen.getGraphics());
g.setColor(bgcolor); //getBackground());
g.fillRect(0, 0, width, height);
@@ -196,24 +317,30 @@ public void paintComponent(Graphics screen) {
g2.drawString(status, statusX, statusY);
*/
if (currentRollover != -1) {
- int statusY = (BUTTON_HEIGHT + g.getFontMetrics().getAscent()) / 2;
+ int statusY = (BUTTON_HEIGHT + g.getFontMetrics().getAscent()) / 2;
String status = shiftPressed ? titleShift[currentRollover] : title[currentRollover];
- g.drawString(status, buttonCount * BUTTON_WIDTH + 3 * BUTTON_GAP, statusY);
+ if (currentRollover != SERIAL)
+ g.drawString(status, (buttonCount - 1) * BUTTON_WIDTH + 3 * BUTTON_GAP, statusY);
+ else {
+ int statusX = x1[SERIAL] - BUTTON_GAP;
+ statusX -= g.getFontMetrics().stringWidth(status);
+ g.drawString(status, statusX, statusY);
+ }
}
screen.drawImage(offscreen, 0, 0, null);
-
+
if (!isEnabled()) {
- screen.setColor(new Color(0,0,0,100));
+ screen.setColor(new Color(0, 0, 0, 100));
screen.fillRect(0, 0, getWidth(), getHeight());
- }
+ }
}
public void mouseMoved(MouseEvent e) {
if (!isEnabled())
return;
-
+
// mouse events before paint();
if (state == null) return;
@@ -225,16 +352,17 @@ public void mouseMoved(MouseEvent e) {
}
- public void mouseDragged(MouseEvent e) { }
+ public void mouseDragged(MouseEvent e) {
+ }
- public void handleMouse(MouseEvent e) {
+ private void handleMouse(MouseEvent e) {
int x = e.getX();
int y = e.getY();
if (currentRollover != -1) {
if ((x > x1[currentRollover]) && (y > y1) &&
- (x < x2[currentRollover]) && (y < y2)) {
+ (x < x2[currentRollover]) && (y < y2)) {
return;
} else {
@@ -259,7 +387,7 @@ private int findSelection(int x, int y) {
for (int i = 0; i < buttonCount; i++) {
if ((y > y1) && (x > x1[i]) &&
- (y < y2) && (x < x2[i])) {
+ (y < y2) && (x < x2[i])) {
//System.out.println("sel is " + i);
return i;
}
@@ -274,6 +402,15 @@ private void setState(int slot, int newState, boolean updateAfter) {
if (updateAfter) {
repaint();
}
+
+ if (touchBarButtons != null) {
+ if (newState == INACTIVE) {
+ // use ROLLOVER state when INACTIVE
+ newState = ROLLOVER;
+ }
+
+ touchBarButtons[slot].setImage(touchBarImages[slot][newState]);
+ }
}
@@ -293,15 +430,13 @@ public void mouseExited(MouseEvent e) {
handleMouse(e);
}
- int wasDown = -1;
-
public void mousePressed(MouseEvent e) {
-
+
// jdf
if (!isEnabled())
return;
-
+
final int x = e.getX();
final int y = e.getY();
@@ -310,68 +445,114 @@ public void mousePressed(MouseEvent e) {
if (sel == -1) return;
currentRollover = -1;
- switch (sel) {
- case RUN:
- editor.handleRun(e.isShiftDown());
- break;
-
- case STOP:
- editor.handleStop();
- break;
-
- case OPEN:
- popup = menu.getPopupMenu();
- popup.show(EditorToolbar.this, x, y);
- break;
-
- case NEW:
- if (shiftPressed) {
- editor.base.handleNew();
- } else {
- editor.base.handleNewReplace();
- }
- break;
-
- case SAVE:
- editor.handleSave(false);
- break;
+ handleSelectionPressed(sel, e.isShiftDown(), x, y);
+ }
- case EXPORT:
- editor.handleExport(e.isShiftDown());
- break;
+ public void mouseClicked(MouseEvent e) {
+ }
- case SERIAL:
- editor.handleSerial();
- break;
+ public void mouseReleased(MouseEvent e) {
+ }
+
+ private void handleSelectionPressed(int sel) {
+ handleSelectionPressed(sel, false, 0, 0);
+ }
+
+ private void handleSelectionPressed(int sel, boolean isShiftDown, int x, int y) {
+ switch (sel) {
+ case RUN:
+ if (!editor.avoidMultipleOperations) {
+ editor.handleRun(false, editor.presentHandler, editor.runHandler);
+ editor.avoidMultipleOperations = true;
+ }
+ break;
+
+// case STOP:
+// editor.handleStop();
+// break;
+//
+ case OPEN:
+ popup = menu.getPopupMenu();
+ popup.show(EditorToolbar.this, x, y);
+ break;
+
+ case NEW:
+ try {
+ editor.base.handleNew();
+ } catch (Exception e1) {
+ throw new RuntimeException(e1);
+ }
+ break;
+
+ case SAVE:
+ if (isShiftDown) {
+ editor.handleSaveAs();
+ } else {
+ editor.handleSave(false);
+ }
+ break;
+
+ case EXPORT:
+ // launch a timeout timer which can reenable to upload button functionality an
+ if (!editor.avoidMultipleOperations) {
+ editor.handleExport(isShiftDown);
+ }
+ break;
+
+ case SERIAL:
+ if (isShiftDown) {
+ editor.handlePlotter();
+ } else {
+ editor.handleSerial();
+ }
+ break;
+
+ default:
+ break;
}
}
-
- public void mouseClicked(MouseEvent e) { }
-
-
- public void mouseReleased(MouseEvent e) { }
-
-
/**
* Set a particular button to be active.
*/
- public void activate(int what) {
+ private void activate(int what) {
if (buttonImages != null) {
- setState(what, ACTIVE, true);
+ setState(what, ACTIVE, true);
+ }
+ }
+
+ public void activateRun() {
+ activate(RUN);
}
+
+ public void activateSave() {
+ activate(SAVE);
}
+ public void activateExport() {
+ activate(EXPORT);
+ }
/**
* Set a particular button to be active.
*/
- public void deactivate(int what) {
+ private void deactivate(int what) {
if (buttonImages != null) {
- setState(what, INACTIVE, true);
+ setState(what, INACTIVE, true);
+ }
+ }
+
+ public void deactivateRun() {
+ deactivate(RUN);
}
+
+ public void deactivateSave() {
+ deactivate(SAVE);
}
+ public void deactivateExport() {
+ deactivate(EXPORT);
+ }
public Dimension getPreferredSize() {
return getMinimumSize();
@@ -379,30 +560,20 @@ public Dimension getPreferredSize() {
public Dimension getMinimumSize() {
- return new Dimension((BUTTON_COUNT + 1)*BUTTON_WIDTH, BUTTON_HEIGHT);
+ return new Dimension((BUTTON_COUNT + 1) * BUTTON_WIDTH, BUTTON_HEIGHT);
}
public Dimension getMaximumSize() {
- return new Dimension(3000, BUTTON_HEIGHT);
- }
-
-
- public void keyPressed(KeyEvent e) {
- if (e.getKeyCode() == KeyEvent.VK_SHIFT) {
- shiftPressed = true;
- repaint();
-}
+ return new Dimension(scale(30000), BUTTON_HEIGHT);
}
-
- public void keyReleased(KeyEvent e) {
- if (e.getKeyCode() == KeyEvent.VK_SHIFT) {
- shiftPressed = false;
+ public boolean dispatchKeyEvent(final KeyEvent e) {
+ if (shiftPressed != e.isShiftDown()) {
+ shiftPressed = !shiftPressed;
repaint();
}
+ // Return false to continue processing this keyEvent
+ return false;
}
-
-
- public void keyTyped(KeyEvent e) { }
}
diff --git a/app/src/processing/app/FindReplace.java b/app/src/processing/app/FindReplace.java
deleted file mode 100644
index 112eca3ade1..00000000000
--- a/app/src/processing/app/FindReplace.java
+++ /dev/null
@@ -1,350 +0,0 @@
-/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
-
-/*
- Part of the Processing project - http://processing.org
-
- Copyright (c) 2004-08 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 as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software Foundation,
- Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-*/
-
-package processing.app;
-
-import java.awt.*;
-import java.awt.event.*;
-import javax.swing.*;
-
-
-/**
- * Find & Replace window for the Processing editor.
- *
- * One major annoyance in this is that the window is re-created each time
- * that "Find" is called. This is because Mac OS X has a strange focus
- * issue with windows that are re-shown with setVisible() or show().
- * requestFocusInWindow() properly sets the focus to the find field,
- * however, just a short moment later, the focus is set to null. Even
- * trying to catch this scenario and request it again doesn't seem to work.
- * Most likely this is some annoyance buried deep in one of Apple's docs,
- * or in the doc for the focus stuff (I tend to think the former because
- * Windows doesn't seem to be quite so beligerent). Filed as
- * Bug 244
- * should anyone have clues about how to fix.
- */
-public class FindReplace extends JFrame implements ActionListener {
-
- static final int BIG = 13;
- static final int SMALL = 6;
-
- Editor editor;
-
- JTextField findField;
- JTextField replaceField;
- static String findString;
- static String replaceString;
-
- JButton replaceButton;
- JButton replaceAllButton;
- JButton replaceFindButton;
- JButton findButton;
-
- JCheckBox ignoreCaseBox;
- static boolean ignoreCase = true;
-
- /// true when there's something selected in the editor
- boolean found;
-
-
- public FindReplace(Editor editor) {
- super("Find");
- setResizable(false);
- this.editor = editor;
-
- Container pain = getContentPane();
- pain.setLayout(null);
-
- JLabel findLabel = new JLabel("Find:");
- Dimension d0 = findLabel.getPreferredSize();
- JLabel replaceLabel = new JLabel("Replace with:");
- Dimension d1 = replaceLabel.getPreferredSize();
-
- pain.add(findLabel);
- pain.add(replaceLabel);
-
- pain.add(findField = new JTextField(20));
- pain.add(replaceField = new JTextField(20));
- Dimension d2 = findField.getPreferredSize();
-
- if (findString != null) findField.setText(findString);
- if (replaceString != null) replaceField.setText(replaceString);
- //System.out.println("setting find str to " + findString);
- //findField.requestFocusInWindow();
-
- //pain.setDefault
- /*
- findField.addFocusListener(new FocusListener() {
- public void focusGained(FocusEvent e) {
- System.out.println("Focus gained " + e.getOppositeComponent());
- }
-
- public void focusLost(FocusEvent e) {
- System.out.println("Focus lost "); // + e.getOppositeComponent());
- if (e.getOppositeComponent() == null) {
- requestFocusInWindow();
- }
- }
- });
- */
-
- // +1 since it's better to tend downwards
- int yoff = (1 + d2.height - d1.height) / 2;
-
- findLabel.setBounds(BIG + (d1.width-d0.width) + yoff, BIG,
- d1.width, d1.height);
- replaceLabel.setBounds(BIG, BIG + d2.height + SMALL + yoff,
- d1.width, d1.height);
-
- //ignoreCase = true;
- ignoreCaseBox = new JCheckBox("Ignore Case");
- ignoreCaseBox.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- ignoreCase = ignoreCaseBox.isSelected();
- }
- });
- ignoreCaseBox.setSelected(ignoreCase);
- pain.add(ignoreCaseBox);
-
- //
-
- JPanel buttons = new JPanel();
- buttons.setLayout(new FlowLayout());
-
- // ordering is different on mac versus pc
- if (Base.isMacOS()) {
- buttons.add(replaceAllButton = new JButton("Replace All"));
- buttons.add(replaceButton = new JButton("Replace"));
- buttons.add(replaceFindButton = new JButton("Replace & Find"));
- buttons.add(findButton = new JButton("Find"));
-
- } else {
- buttons.add(findButton = new JButton("Find"));
- buttons.add(replaceFindButton = new JButton("Replace & Find"));
- buttons.add(replaceButton = new JButton("Replace"));
- buttons.add(replaceAllButton = new JButton("Replace All"));
- }
- pain.add(buttons);
-
- // to fix ugliness.. normally macosx java 1.3 puts an
- // ugly white border around this object, so turn it off.
- if (Base.isMacOS()) {
- buttons.setBorder(null);
- }
-
- Dimension d3 = buttons.getPreferredSize();
- //buttons.setBounds(BIG, BIG + d2.height*2 + SMALL + BIG,
- buttons.setBounds(BIG, BIG + d2.height*3 + SMALL*2 + BIG,
- d3.width, d3.height);
-
- //
-
- findField.setBounds(BIG + d1.width + SMALL, BIG,
- d3.width - (d1.width + SMALL), d2.height);
- replaceField.setBounds(BIG + d1.width + SMALL, BIG + d2.height + SMALL,
- d3.width - (d1.width + SMALL), d2.height);
-
- ignoreCaseBox.setBounds(BIG + d1.width + SMALL,
- BIG + d2.height*2 + SMALL*2,
- d3.width, d2.height);
-
- //
-
- replaceButton.addActionListener(this);
- replaceAllButton.addActionListener(this);
- replaceFindButton.addActionListener(this);
- findButton.addActionListener(this);
-
- // you mustn't replace what you haven't found, my son
- replaceButton.setEnabled(false);
- replaceFindButton.setEnabled(false);
-
- // so that typing will go straight to this field
- //findField.requestFocus();
-
- // make the find button the blinky default
- getRootPane().setDefaultButton(findButton);
-
- Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
-
- int wide = d3.width + BIG*2;
- Rectangle butt = buttons.getBounds(); // how big is your butt?
- int high = butt.y + butt.height + BIG*2 + SMALL;
-
- setBounds((screen.width - wide) / 2,
- (screen.height - high) / 2, wide, high);
-
- setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
- addWindowListener(new WindowAdapter() {
- public void windowClosing(WindowEvent e) {
- handleClose();
- }
- });
- Base.registerWindowCloseKeys(getRootPane(), new ActionListener() {
- public void actionPerformed(ActionEvent actionEvent) {
- //hide();
- handleClose();
- }
- });
- Base.setIcon(this);
-
- // hack to to get first field to focus properly on osx
- addWindowListener(new WindowAdapter() {
- public void windowActivated(WindowEvent e) {
- //System.out.println("activating");
- /*boolean ok =*/ findField.requestFocusInWindow();
- //System.out.println("got " + ok);
- findField.selectAll();
- }
- });
- }
-
-
- public void handleClose() {
- //System.out.println("handling close now");
- findString = findField.getText();
- replaceString = replaceField.getText();
-
- // this object should eventually become dereferenced
- setVisible(false);
- }
-
-
- /*
- public void show() {
- findField.requestFocusInWindow();
- super.show();
- //findField.selectAll();
- //findField.requestFocus();
- }
- */
-
-
- public void actionPerformed(ActionEvent e) {
- Object source = e.getSource();
-
- if (source == findButton) {
- find(true);
-
- } else if (source == replaceFindButton) {
- replace();
- find(true);
-
- } else if (source == replaceButton) {
- replace();
-
- } else if (source == replaceAllButton) {
- replaceAll();
- }
- }
-
-
- // look for the next instance of the find string
- // to be found later than the current caret selection
-
- // once found, select it (and go to that line)
-
- public void find(boolean wrap) {
- // in case search len is zero,
- // otherwise replace all will go into an infinite loop
- found = false;
-
- String search = findField.getText();
- //System.out.println("finding for " + search + " " + findString);
- // this will catch "find next" being called when no search yet
- if (search.length() == 0) return;
-
- String text = editor.getText();
-
- if (ignoreCase) {
- search = search.toLowerCase();
- text = text.toLowerCase();
- }
-
- //int selectionStart = editor.textarea.getSelectionStart();
- int selectionEnd = editor.getSelectionStop();
-
- int nextIndex = text.indexOf(search, selectionEnd);
- if (nextIndex == -1) {
- if (wrap) {
- // if wrapping, a second chance is ok, start from beginning
- nextIndex = text.indexOf(search, 0);
- }
-
- if (nextIndex == -1) {
- found = false;
- replaceButton.setEnabled(false);
- replaceFindButton.setEnabled(false);
- //Toolkit.getDefaultToolkit().beep();
- return;
- }
- }
- found = true;
- replaceButton.setEnabled(true);
- replaceFindButton.setEnabled(true);
- editor.setSelection(nextIndex, nextIndex + search.length());
- }
-
-
- /**
- * Replace the current selection with whatever's in the
- * replacement text field.
- */
- public void replace() {
- if (!found) return; // don't replace if nothing found
-
- // check to see if the document has wrapped around
- // otherwise this will cause an infinite loop
- String sel = editor.getSelectedText();
- if (sel.equals(replaceField.getText())) {
- found = false;
- replaceButton.setEnabled(false);
- replaceFindButton.setEnabled(false);
- return;
- }
-
- editor.setSelectedText(replaceField.getText());
- //editor.setSketchModified(true);
- //editor.sketch.setCurrentModified(true);
- editor.getSketch().setModified(true); // TODO is this necessary?
-
- // don't allow a double replace
- replaceButton.setEnabled(false);
- replaceFindButton.setEnabled(false);
- }
-
-
- /**
- * Replace everything that matches by doing find and replace
- * alternately until nothing more found.
- */
- public void replaceAll() {
- // move to the beginning
- editor.setSelection(0, 0);
-
- do {
- find(false);
- replace();
- } while (found);
- }
-}
diff --git a/app/src/processing/app/NetworkMonitor.java b/app/src/processing/app/NetworkMonitor.java
new file mode 100644
index 00000000000..b7f08026ace
--- /dev/null
+++ b/app/src/processing/app/NetworkMonitor.java
@@ -0,0 +1,169 @@
+package processing.app;
+
+import cc.arduino.packages.BoardPort;
+import cc.arduino.packages.ssh.NoInteractionUserInfo;
+import cc.arduino.packages.ssh.SSHClientSetupChainRing;
+import cc.arduino.packages.ssh.SSHConfigFileSetup;
+import cc.arduino.packages.ssh.SSHPwdSetup;
+
+import com.jcraft.jsch.*;
+
+import processing.app.debug.MessageConsumer;
+import processing.app.debug.MessageSiphon;
+
+import javax.swing.*;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import static processing.app.I18n.tr;
+
+@SuppressWarnings("serial")
+public class NetworkMonitor extends AbstractTextMonitor implements MessageConsumer {
+
+ private static final int MAX_CONNECTION_ATTEMPTS = 5;
+
+ private MessageSiphon inputConsumer;
+ private Session session;
+ private Channel channel;
+ private int connectionAttempts;
+
+ public NetworkMonitor(BoardPort port) {
+ super(port);
+
+ onSendCommand(new ActionListener() {
+ public void actionPerformed(ActionEvent event) {
+ try {
+ OutputStream out = channel.getOutputStream();
+ out.write(textField.getText().getBytes());
+ out.write('\n');
+ out.flush();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ textField.setText("");
+ }
+ });
+ }
+
+ @Override
+ public boolean requiresAuthorization() {
+ return true;
+ }
+
+ @Override
+ public String getAuthorizationKey() {
+ return "runtime.pwd." + getBoardPort().getAddress();
+ }
+
+ @Override
+ public void open() throws Exception {
+ super.open();
+ this.connectionAttempts = 0;
+
+ JSch jSch = new JSch();
+ SSHClientSetupChainRing sshClientSetupChain = new SSHConfigFileSetup(new SSHPwdSetup());
+ session = sshClientSetupChain.setup(getBoardPort(), jSch);
+
+ session.setConfig("PreferredAuthentications", "publickey,keyboard-interactive,password");
+ session.setUserInfo(new NoInteractionUserInfo(PreferencesData.get(getAuthorizationKey())));
+ session.connect(30000);
+
+ tryConnect();
+ }
+
+ private void tryConnect() throws JSchException, IOException {
+ connectionAttempts++;
+
+ if (connectionAttempts > 1) {
+ try {
+ Thread.sleep(2000);
+ } catch (InterruptedException e) {
+ // ignored
+ }
+ }
+
+ if (!session.isConnected()) {
+ return;
+ }
+ channel = session.openChannel("exec");
+ ((ChannelExec) channel).setCommand("telnet localhost 6571");
+
+ InputStream inputStream = channel.getInputStream();
+ InputStream errStream = ((ChannelExec) channel).getErrStream();
+
+ channel.connect();
+
+ inputConsumer = new MessageSiphon(inputStream, this);
+ new MessageSiphon(errStream, this);
+
+ if (connectionAttempts > 1) {
+ SwingUtilities.invokeLater(new Runnable() {
+
+ @Override
+ public void run() {
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ // ignore
+ }
+ if (channel.isConnected()) {
+ NetworkMonitor.this.message(tr("connected!") + '\n');
+ }
+ }
+
+ });
+ }
+ }
+
+ @Override
+ public synchronized void message(String s) {
+ if (s.contains("can't connect")) {
+ while (!channel.isClosed()) {
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ // ignore
+ }
+ }
+ if (connectionAttempts < MAX_CONNECTION_ATTEMPTS) {
+ s = "\n" + tr("Unable to connect: retrying") + " (" + connectionAttempts + ")... ";
+
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ NetworkMonitor.this.tryConnect();
+ } catch (JSchException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ });
+ } else {
+ s = "\n" + tr("Unable to connect: is the sketch using the bridge?");
+ }
+ }
+ super.message(s);
+ }
+
+ @Override
+ public void close() throws Exception {
+ super.close();
+
+ if (channel != null) {
+ inputConsumer.stop();
+ channel.disconnect();
+ textArea.setText("");
+ }
+
+ if (session != null) {
+ session.disconnect();
+ }
+ }
+
+}
diff --git a/app/src/processing/app/NewBoardListener.java b/app/src/processing/app/NewBoardListener.java
new file mode 100644
index 00000000000..7e0fe61d708
--- /dev/null
+++ b/app/src/processing/app/NewBoardListener.java
@@ -0,0 +1,106 @@
+/*
+ * This file is part of Arduino.
+ *
+ * Arduino is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * As a special exception, you may use this file as part of a free software
+ * library without restriction. Specifically, if other files instantiate
+ * templates or use macros or inline functions from this file, or you compile
+ * this file and link it with other files to produce an executable, this
+ * file does not by itself cause the resulting executable to be covered by
+ * the GNU General Public License. This exception does not however
+ * invalidate any other reasons why the executable file might be covered by
+ * the GNU General Public License.
+ *
+ * Copyright 2016 Arduino LLC (http://www.arduino.cc/)
+ */
+
+package processing.app;
+
+import java.awt.event.WindowEvent;
+import java.awt.event.WindowFocusListener;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
+import javax.swing.SwingUtilities;
+
+import cc.arduino.UpdatableBoardsLibsFakeURLsHandler;
+import cc.arduino.view.NotificationPopup;
+import processing.app.Base;
+
+public class NewBoardListener implements PropertyChangeListener, Runnable {
+ private Base base;
+ private Editor ed;
+
+ public NewBoardListener(Base base) {
+ this.base = base;
+ }
+
+ @Override
+ public void propertyChange(PropertyChangeEvent event) {
+ checkForNewBoardAttached();
+ }
+
+ @Override
+ public void run() {
+ while (base.getActiveEditor() == null) {
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ BaseNoGui.addPropertyChangeListener(this);
+ checkForNewBoardAttached();
+ }
+
+ public void checkForNewBoardAttached() {
+ String newBoardManagerLink = BaseNoGui.getBoardManagerLink();
+ if (newBoardManagerLink.isEmpty()) {
+ return;
+ }
+
+ SwingUtilities.invokeLater(() -> {
+
+ ed = base.getActiveEditor();
+ NotificationPopup notificationPopup = new NotificationPopup(ed,
+ new UpdatableBoardsLibsFakeURLsHandler(base),
+ newBoardManagerLink, false);
+ if (ed.isFocused()) {
+ notificationPopup.begin();
+ return;
+ }
+
+ // If the IDE is not focused wait until it is focused again to
+ // display the notification, this avoids the annoying side effect
+ // to "steal" the focus from another application.
+ WindowFocusListener wfl = new WindowFocusListener() {
+ @Override
+ public void windowLostFocus(WindowEvent evt) {
+ }
+
+ @Override
+ public void windowGainedFocus(WindowEvent evt) {
+ notificationPopup.begin();
+ for (Editor e : base.getEditors())
+ e.removeWindowFocusListener(this);
+ }
+ };
+
+ for (Editor e : base.getEditors())
+ e.addWindowFocusListener(wfl);
+ });
+ }
+}
diff --git a/app/src/processing/app/Platform.java b/app/src/processing/app/Platform.java
deleted file mode 100644
index 9fd0fd972f2..00000000000
--- a/app/src/processing/app/Platform.java
+++ /dev/null
@@ -1,172 +0,0 @@
-/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
-
-/*
- Part of the Processing project - http://processing.org
-
- Copyright (c) 2008 Ben Fry and Casey Reas
-
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software Foundation,
- Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-*/
-
-package processing.app;
-
-import java.io.File;
-
-import javax.swing.UIManager;
-
-import com.sun.jna.Library;
-import com.sun.jna.Native;
-
-
-/**
- * Used by Base for platform-specific tweaking, for instance finding the
- * sketchbook location using the Windows registry, or OS X event handling.
- *
- * The methods in this implementation are used by default, and can be
- * overridden by a subclass, if loaded by Base.main().
- *
- * These methods throw vanilla-flavored Exceptions, so that error handling
- * occurs inside Base.
- *
- * There is currently no mechanism for adding new platforms, as the setup is
- * not automated. We could use getProperty("os.arch") perhaps, but that's
- * debatable (could be upper/lowercase, have spaces, etc.. basically we don't
- * know if name is proper Java package syntax.)
- */
-public class Platform {
- Base base;
-
-
- /**
- * Set the default L & F. While I enjoy the bounty of the sixteen possible
- * exception types that this UIManager method might throw, I feel that in
- * just this one particular case, I'm being spoiled by those engineers
- * at Sun, those Masters of the Abstractionverse. It leaves me feeling sad
- * and overweight. So instead, I'll pretend that I'm not offered eleven dozen
- * ways to report to the user exactly what went wrong, and I'll bundle them
- * all into a single catch-all "Exception". Because in the end, all I really
- * care about is whether things worked or not. And even then, I don't care.
- *
- * @throws Exception Just like I said.
- */
- public void setLookAndFeel() throws Exception {
- UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
- }
-
-
- public void init(Base base) {
- this.base = base;
- }
-
-
- public File getSettingsFolder() throws Exception {
- // otherwise make a .processing directory int the user's home dir
- File home = new File(System.getProperty("user.home"));
- File dataFolder = new File(home, ".arduino");
- return dataFolder;
-
- /*
- try {
- Class clazz = Class.forName("processing.app.macosx.ThinkDifferent");
- Method m = clazz.getMethod("getLibraryFolder", new Class[] { });
- String libraryPath = (String) m.invoke(null, new Object[] { });
- //String libraryPath = BaseMacOS.getLibraryFolder();
- File libraryFolder = new File(libraryPath);
- dataFolder = new File(libraryFolder, "Processing");
-
- } catch (Exception e) {
- showError("Problem getting data folder",
- "Error getting the Processing data folder.", e);
- }
- */
- }
-
-
- /**
- * @return null if not overridden, which will cause a prompt to show instead.
- * @throws Exception
- */
- public File getDefaultSketchbookFolder() throws Exception {
- return null;
- }
-
-
- public void openURL(String url) throws Exception {
- String launcher = Preferences.get("launcher");
- if (launcher != null) {
- Runtime.getRuntime().exec(new String[] { launcher, url });
- } else {
- showLauncherWarning();
- }
- }
-
-
- public boolean openFolderAvailable() {
- return Preferences.get("launcher") != null;
- }
-
-
- public void openFolder(File file) throws Exception {
- String launcher = Preferences.get("launcher");
- if (launcher != null) {
- String folder = file.getAbsolutePath();
- Runtime.getRuntime().exec(new String[] { launcher, folder });
- } else {
- showLauncherWarning();
- }
- }
-
-
- // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
-
-
- public interface CLibrary extends Library {
- CLibrary INSTANCE = (CLibrary)Native.loadLibrary("c", CLibrary.class);
- int setenv(String name, String value, int overwrite);
- String getenv(String name);
- int unsetenv(String name);
- int putenv(String string);
- }
-
-
- public void setenv(String variable, String value) {
- CLibrary clib = CLibrary.INSTANCE;
- clib.setenv(variable, value, 1);
- }
-
-
- public String getenv(String variable) {
- CLibrary clib = CLibrary.INSTANCE;
- return clib.getenv(variable);
- }
-
-
- public int unsetenv(String variable) {
- CLibrary clib = CLibrary.INSTANCE;
- return clib.unsetenv(variable);
- }
-
-
- // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
-
-
- protected void showLauncherWarning() {
- Base.showWarning("No launcher available",
- "Unspecified platform, no launcher available.\n" +
- "To enable opening URLs or folders, add a \n" +
- "\"launcher=/path/to/app\" line to preferences.txt",
- null);
- }
-}
\ No newline at end of file
diff --git a/app/src/processing/app/Preferences.java b/app/src/processing/app/Preferences.java
index ffc63f7ae63..3879512d424 100644
--- a/app/src/processing/app/Preferences.java
+++ b/app/src/processing/app/Preferences.java
@@ -1,5 +1,3 @@
-/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
-
/*
Part of the Processing project - http://processing.org
@@ -23,36 +21,26 @@
package processing.app;
-import java.awt.*;
-import java.awt.event.*;
-import java.io.*;
-import java.util.*;
-
-import javax.swing.*;
-
-import processing.app.syntax.*;
-import processing.core.*;
-
-
+import processing.app.helpers.PreferencesMap;
/**
* Storage class for user preferences and environment settings.
- *
+ *
* This class no longer uses the Properties class, since
* properties files are iso8859-1, which is highly likely to
* be a problem when trying to save sketch folders and locations.
- *
+ *
* The GUI portion in here is really ugly, as it uses exact layout. This was
* done in frustration one evening (and pre-Swing), but that's long since past,
* and it should all be moved to a proper swing layout like BoxLayout.
- *
+ *
* This is very poorly put together, that the preferences panel and the actual
* preferences i/o is part of the same code. But there hasn't yet been a
* compelling reason to bother with the separation aside from concern about
* being lectured by strangers who feel that it doesn't look like what they
* learned in CS class.
- *
+ *
* Would also be possible to change this to use the Java Preferences API.
* Some useful articles
* here and
@@ -65,24 +53,12 @@
*/
public class Preferences {
- // what to call the feller
-
- static final String PREFS_FILE = "preferences.txt";
-
-
- // prompt text stuff
-
- static final String PROMPT_YES = "Yes";
- static final String PROMPT_NO = "No";
- static final String PROMPT_CANCEL = "Cancel";
- static final String PROMPT_OK = "OK";
- static final String PROMPT_BROWSE = "Browse";
/**
* Standardized width for buttons. Mac OS X 10.3 wants 70 as its default,
* Windows XP needs 66, and my Ubuntu machine needs 80+, so 80 seems proper.
*/
- static public int BUTTON_WIDTH = 80;
+ static public int BUTTON_WIDTH = 80;
/**
* Standardized button height. Mac OS X 10.3 (Java 1.4) wants 29,
@@ -93,666 +69,70 @@ public class Preferences {
*/
static public int BUTTON_HEIGHT = 24;
- // value for the size bars, buttons, etc
-
- static final int GRID_SIZE = 33;
-
-
// indents and spacing standards. these probably need to be modified
// per platform as well, since macosx is so huge, windows is smaller,
// and linux is all over the map
- static final int GUI_BIG = 13;
- static final int GUI_BETWEEN = 10;
- static final int GUI_SMALL = 6;
-
- // gui elements
-
- JFrame dialog;
- int wide, high;
-
- JTextField sketchbookLocationField;
- JCheckBox exportSeparateBox;
- JCheckBox deletePreviousBox;
- JCheckBox externalEditorBox;
- JCheckBox memoryOverrideBox;
- JTextField memoryField;
- JCheckBox checkUpdatesBox;
- JTextField fontSizeField;
- JCheckBox autoAssociateBox;
-
-
- // the calling editor, so updates can be applied
-
- Editor editor;
-
-
- // data model
-
- static Hashtable defaults;
- static Hashtable table = new Hashtable();;
- static File preferencesFile;
+ static final int GUI_SMALL = 6;
-
- static protected void init(String commandLinePrefs) {
-
- // start by loading the defaults, in case something
- // important was deleted from the user prefs
- try {
- load(Base.getLibStream("preferences.txt"));
- } catch (Exception e) {
- Base.showError(null, "Could not read default settings.\n" +
- "You'll need to reinstall Arduino.", e);
- }
-
- // check for platform-specific properties in the defaults
- String platformExt = "." + PConstants.platformNames[PApplet.platform];
- int platformExtLength = platformExt.length();
- Enumeration e = table.keys();
- while (e.hasMoreElements()) {
- String key = (String) e.nextElement();
- if (key.endsWith(platformExt)) {
- // this is a key specific to a particular platform
- String actualKey = key.substring(0, key.length() - platformExtLength);
- String value = get(key);
- table.put(actualKey, value);
- }
- }
-
- // clone the hash table
- defaults = (Hashtable) table.clone();
-
- // other things that have to be set explicitly for the defaults
- setColor("run.window.bgcolor", SystemColor.control);
-
- // Load a prefs file if specified on the command line
- if (commandLinePrefs != null) {
- try {
- load(new FileInputStream(commandLinePrefs));
-
- } catch (Exception poe) {
- Base.showError("Error",
- "Could not read preferences from " +
- commandLinePrefs, poe);
- }
- } else if (!Base.isCommandLine()) {
- // next load user preferences file
- preferencesFile = Base.getSettingsFile(PREFS_FILE);
- if (!preferencesFile.exists()) {
- // create a new preferences file if none exists
- // saves the defaults out to the file
- save();
-
- } else {
- // load the previous preferences file
-
- try {
- load(new FileInputStream(preferencesFile));
-
- } catch (Exception ex) {
- Base.showError("Error reading preferences",
- "Error reading the preferences file. " +
- "Please delete (or move)\n" +
- preferencesFile.getAbsolutePath() +
- " and restart Arduino.", ex);
- }
- }
- }
+ @Deprecated
+ public static String get(String attribute) {
+ return PreferencesData.get(attribute);
}
-
- public Preferences() {
-
- // setup dialog for the prefs
-
- //dialog = new JDialog(editor, "Preferences", true);
- dialog = new JFrame("Preferences");
- dialog.setResizable(false);
-
- Container pain = dialog.getContentPane();
- pain.setLayout(null);
-
- int top = GUI_BIG;
- int left = GUI_BIG;
- int right = 0;
-
- JLabel label;
- JButton button; //, button2;
- //JComboBox combo;
- Dimension d, d2; //, d3;
- int h, vmax;
-
-
- // Sketchbook location:
- // [...............................] [ Browse ]
-
- label = new JLabel("Sketchbook location:");
- pain.add(label);
- d = label.getPreferredSize();
- label.setBounds(left, top, d.width, d.height);
- top += d.height; // + GUI_SMALL;
-
- sketchbookLocationField = new JTextField(40);
- pain.add(sketchbookLocationField);
- d = sketchbookLocationField.getPreferredSize();
-
- button = new JButton(PROMPT_BROWSE);
- button.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- File dflt = new File(sketchbookLocationField.getText());
- File file =
- Base.selectFolder("Select new sketchbook location", dflt, dialog);
- if (file != null) {
- sketchbookLocationField.setText(file.getAbsolutePath());
- }
- }
- });
- pain.add(button);
- d2 = button.getPreferredSize();
-
- // take max height of all components to vertically align em
- vmax = Math.max(d.height, d2.height);
- sketchbookLocationField.setBounds(left, top + (vmax-d.height)/2,
- d.width, d.height);
- h = left + d.width + GUI_SMALL;
- button.setBounds(h, top + (vmax-d2.height)/2,
- d2.width, d2.height);
-
- right = Math.max(right, h + d2.width + GUI_BIG);
- top += vmax + GUI_BETWEEN;
-
-
- // Editor font size [ ]
-
- Container box = Box.createHorizontalBox();
- label = new JLabel("Editor font size: ");
- box.add(label);
- fontSizeField = new JTextField(4);
- box.add(fontSizeField);
- label = new JLabel(" (requires restart of Arduino)");
- box.add(label);
- pain.add(box);
- d = box.getPreferredSize();
- box.setBounds(left, top, d.width, d.height);
- Font editorFont = Preferences.getFont("editor.font");
- fontSizeField.setText(String.valueOf(editorFont.getSize()));
- top += d.height + GUI_BETWEEN;
-
-
- // [ ] Delete previous applet or application folder on export
-
- deletePreviousBox =
- new JCheckBox("Delete previous applet or application folder on export");
- pain.add(deletePreviousBox);
- d = deletePreviousBox.getPreferredSize();
- deletePreviousBox.setBounds(left, top, d.width + 10, d.height);
- right = Math.max(right, left + d.width);
- top += d.height + GUI_BETWEEN;
-
-
- // [ ] Use external editor
-
- externalEditorBox = new JCheckBox("Use external editor");
- pain.add(externalEditorBox);
- d = externalEditorBox.getPreferredSize();
- externalEditorBox.setBounds(left, top, d.width + 10, d.height);
- right = Math.max(right, left + d.width);
- top += d.height + GUI_BETWEEN;
-
-
- // [ ] Check for updates on startup
-
- checkUpdatesBox = new JCheckBox("Check for updates on startup");
- pain.add(checkUpdatesBox);
- d = checkUpdatesBox.getPreferredSize();
- checkUpdatesBox.setBounds(left, top, d.width + 10, d.height);
- right = Math.max(right, left + d.width);
- top += d.height + GUI_BETWEEN;
-
-
- // [ ] Automatically associate .pde files with Processing
-
- if (Base.isWindows()) {
- autoAssociateBox =
- new JCheckBox("Automatically associate .pde files with Arduino");
- pain.add(autoAssociateBox);
- d = autoAssociateBox.getPreferredSize();
- autoAssociateBox.setBounds(left, top, d.width + 10, d.height);
- right = Math.max(right, left + d.width);
- top += d.height + GUI_BETWEEN;
- }
-
-
- // More preferences are in the ...
-
- label = new JLabel("More preferences can be edited directly in the file");
- pain.add(label);
- d = label.getPreferredSize();
- label.setForeground(Color.gray);
- label.setBounds(left, top, d.width, d.height);
- right = Math.max(right, left + d.width);
- top += d.height; // + GUI_SMALL;
-
- label = new JLabel(preferencesFile.getAbsolutePath());
- final JLabel clickable = label;
- label.addMouseListener(new MouseAdapter() {
- public void mousePressed(MouseEvent e) {
- Base.openFolder(Base.getSettingsFolder());
- }
-
- public void mouseEntered(MouseEvent e) {
- clickable.setForeground(new Color(0, 0, 140));
- }
-
- public void mouseExited(MouseEvent e) {
- clickable.setForeground(Color.BLACK);
- }
- });
- pain.add(label);
- d = label.getPreferredSize();
- label.setBounds(left, top, d.width, d.height);
- right = Math.max(right, left + d.width);
- top += d.height;
-
- label = new JLabel("(edit only when Arduino is not running)");
- pain.add(label);
- d = label.getPreferredSize();
- label.setForeground(Color.gray);
- label.setBounds(left, top, d.width, d.height);
- right = Math.max(right, left + d.width);
- top += d.height; // + GUI_SMALL;
-
-
- // [ OK ] [ Cancel ] maybe these should be next to the message?
-
- button = new JButton(PROMPT_OK);
- button.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- applyFrame();
- disposeFrame();
- }
- });
- pain.add(button);
- d2 = button.getPreferredSize();
- BUTTON_HEIGHT = d2.height;
-
- h = right - (BUTTON_WIDTH + GUI_SMALL + BUTTON_WIDTH);
- button.setBounds(h, top, BUTTON_WIDTH, BUTTON_HEIGHT);
- h += BUTTON_WIDTH + GUI_SMALL;
-
- button = new JButton(PROMPT_CANCEL);
- button.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- disposeFrame();
- }
- });
- pain.add(button);
- button.setBounds(h, top, BUTTON_WIDTH, BUTTON_HEIGHT);
-
- top += BUTTON_HEIGHT + GUI_BETWEEN;
-
-
- // finish up
-
- wide = right + GUI_BIG;
- high = top + GUI_SMALL;
-
-
- // closing the window is same as hitting cancel button
-
- dialog.addWindowListener(new WindowAdapter() {
- public void windowClosing(WindowEvent e) {
- disposeFrame();
- }
- });
-
- ActionListener disposer = new ActionListener() {
- public void actionPerformed(ActionEvent actionEvent) {
- disposeFrame();
- }
- };
- Base.registerWindowCloseKeys(dialog.getRootPane(), disposer);
- Base.setIcon(dialog);
-
- Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
- dialog.setLocation((screen.width - wide) / 2,
- (screen.height - high) / 2);
-
- dialog.pack(); // get insets
- Insets insets = dialog.getInsets();
- dialog.setSize(wide + insets.left + insets.right,
- high + insets.top + insets.bottom);
-
-
- // handle window closing commands for ctrl/cmd-W or hitting ESC.
-
- pain.addKeyListener(new KeyAdapter() {
- public void keyPressed(KeyEvent e) {
- //System.out.println(e);
- KeyStroke wc = Editor.WINDOW_CLOSE_KEYSTROKE;
- if ((e.getKeyCode() == KeyEvent.VK_ESCAPE) ||
- (KeyStroke.getKeyStrokeForEvent(e).equals(wc))) {
- disposeFrame();
- }
- }
- });
- }
-
-
- public Dimension getPreferredSize() {
- return new Dimension(wide, high);
- }
-
-
- // .................................................................
-
-
- /**
- * Close the window after an OK or Cancel.
- */
- protected void disposeFrame() {
- dialog.dispose();
- }
-
-
- /**
- * Change internal settings based on what was chosen in the prefs,
- * then send a message to the editor saying that it's time to do the same.
- */
- protected void applyFrame() {
- // put each of the settings into the table
- setBoolean("export.delete_target_folder",
- deletePreviousBox.isSelected());
-
-// setBoolean("sketchbook.closing_last_window_quits",
-// closingLastQuitsBox.isSelected());
- //setBoolean("sketchbook.prompt", sketchPromptBox.isSelected());
- //setBoolean("sketchbook.auto_clean", sketchCleanBox.isSelected());
-
- // if the sketchbook path has changed, rebuild the menus
- String oldPath = get("sketchbook.path");
- String newPath = sketchbookLocationField.getText();
- if (!newPath.equals(oldPath)) {
- editor.base.rebuildSketchbookMenus();
- set("sketchbook.path", newPath);
- }
-
- setBoolean("editor.external", externalEditorBox.isSelected());
- setBoolean("update.check", checkUpdatesBox.isSelected());
-
- /*
- // was gonna use this to check memory settings,
- // but it quickly gets much too messy
- if (getBoolean("run.options.memory")) {
- Process process = Runtime.getRuntime().exec(new String[] {
- "java", "-Xms" + memoryMin + "m", "-Xmx" + memoryMax + "m"
- });
- processInput = new SystemOutSiphon(process.getInputStream());
- processError = new MessageSiphon(process.getErrorStream(), this);
- }
- */
-
- String newSizeText = fontSizeField.getText();
- try {
- int newSize = Integer.parseInt(newSizeText.trim());
- String pieces[] = PApplet.split(get("editor.font"), ',');
- pieces[2] = String.valueOf(newSize);
- set("editor.font", PApplet.join(pieces, ','));
-
- } catch (Exception e) {
- System.err.println("ignoring invalid font size " + newSizeText);
- }
-
- if (autoAssociateBox != null) {
- setBoolean("platform.auto_file_type_associations",
- autoAssociateBox.isSelected());
- }
-
- editor.applyPreferences();
- }
-
-
- protected void showFrame(Editor editor) {
- this.editor = editor;
-
- // set all settings entry boxes to their actual status
- deletePreviousBox.
- setSelected(getBoolean("export.delete_target_folder"));
-
- //closingLastQuitsBox.
- // setSelected(getBoolean("sketchbook.closing_last_window_quits"));
- //sketchPromptBox.
- // setSelected(getBoolean("sketchbook.prompt"));
- //sketchCleanBox.
- // setSelected(getBoolean("sketchbook.auto_clean"));
-
- sketchbookLocationField.
- setText(get("sketchbook.path"));
- externalEditorBox.
- setSelected(getBoolean("editor.external"));
- checkUpdatesBox.
- setSelected(getBoolean("update.check"));
-
- if (autoAssociateBox != null) {
- autoAssociateBox.
- setSelected(getBoolean("platform.auto_file_type_associations"));
- }
-
- dialog.setVisible(true);
- }
-
-
- // .................................................................
-
-
- static protected void load(InputStream input) throws IOException {
- load(input, table);
- }
-
- static public void load(InputStream input, Map table) throws IOException {
- String[] lines = PApplet.loadStrings(input); // Reads as UTF-8
- for (String line : lines) {
- if ((line.length() == 0) ||
- (line.charAt(0) == '#')) continue;
-
- // this won't properly handle = signs being in the text
- int equals = line.indexOf('=');
- if (equals != -1) {
- String key = line.substring(0, equals).trim();
- String value = line.substring(equals + 1).trim();
- table.put(key, value);
- }
- }
+ @Deprecated
+ public static String get(String attribute, String defaultValue) {
+ return PreferencesData.get(attribute, defaultValue);
}
-
- // .................................................................
-
-
- static protected void save() {
-// try {
- // on startup, don't worry about it
- // this is trying to update the prefs for who is open
- // before Preferences.init() has been called.
- if (preferencesFile == null) return;
-
- // Fix for 0163 to properly use Unicode when writing preferences.txt
- PrintWriter writer = PApplet.createWriter(preferencesFile);
-
- Enumeration e = table.keys(); //properties.propertyNames();
- while (e.hasMoreElements()) {
- String key = (String) e.nextElement();
- writer.println(key + "=" + ((String) table.get(key)));
- }
-
- writer.flush();
- writer.close();
-
-// } catch (Exception ex) {
-// Base.showWarning(null, "Error while saving the settings file", ex);
-// }
- }
-
-
- // .................................................................
-
-
- // all the information from preferences.txt
-
- //static public String get(String attribute) {
- //return get(attribute, null);
- //}
-
- static public String get(String attribute /*, String defaultValue */) {
- return (String) table.get(attribute);
- /*
- //String value = (properties != null) ?
- //properties.getProperty(attribute) : applet.getParameter(attribute);
- String value = properties.getProperty(attribute);
-
- return (value == null) ?
- defaultValue : value;
- */
- }
-
-
- static public String getDefault(String attribute) {
- return (String) defaults.get(attribute);
+ @Deprecated
+ public static boolean has(String key) {
+ return PreferencesData.has(key);
}
-
- static public void set(String attribute, String value) {
- table.put(attribute, value);
+ @Deprecated
+ public static void remove(String key) {
+ PreferencesData.remove(key);
}
-
- static public void unset(String attribute) {
- table.remove(attribute);
+ @Deprecated
+ public static void set(String attribute, String value) {
+ PreferencesData.set(attribute, value);
}
-
- static public boolean getBoolean(String attribute) {
- String value = get(attribute); //, null);
- return (new Boolean(value)).booleanValue();
-
- /*
- supposedly not needed, because anything besides 'true'
- (ignoring case) will just be false.. so if malformed -> false
- if (value == null) return defaultValue;
-
- try {
- return (new Boolean(value)).booleanValue();
- } catch (NumberFormatException e) {
- System.err.println("expecting an integer: " + attribute + " = " + value);
- }
- return defaultValue;
- */
+ @Deprecated
+ public static boolean getBoolean(String attribute) {
+ return PreferencesData.getBoolean(attribute);
}
-
- static public void setBoolean(String attribute, boolean value) {
- set(attribute, value ? "true" : "false");
+ @Deprecated
+ public static void setBoolean(String attribute, boolean value) {
+ PreferencesData.setBoolean(attribute, value);
}
-
- static public int getInteger(String attribute /*, int defaultValue*/) {
- return Integer.parseInt(get(attribute));
-
- /*
- String value = get(attribute, null);
- if (value == null) return defaultValue;
-
- try {
- return Integer.parseInt(value);
- } catch (NumberFormatException e) {
- // ignored will just fall through to returning the default
- System.err.println("expecting an integer: " + attribute + " = " + value);
- }
- return defaultValue;
- //if (value == null) return defaultValue;
- //return (value == null) ? defaultValue :
- //Integer.parseInt(value);
- */
+ @Deprecated
+ public static int getInteger(String attribute) {
+ return PreferencesData.getInteger(attribute);
}
-
- static public void setInteger(String key, int value) {
- set(key, String.valueOf(value));
+ @Deprecated
+ public static int getInteger(String attribute, int defaultValue) {
+ return PreferencesData.getInteger(attribute, defaultValue);
}
-
- static public Color getColor(String name) {
- Color parsed = Color.GRAY; // set a default
- String s = get(name);
- if ((s != null) && (s.indexOf("#") == 0)) {
- try {
- parsed = new Color(Integer.parseInt(s.substring(1), 16));
- } catch (Exception e) { }
- }
- return parsed;
+ @Deprecated
+ public static void setInteger(String key, int value) {
+ PreferencesData.setInteger(key, value);
}
-
- static public void setColor(String attr, Color what) {
- set(attr, "#" + PApplet.hex(what.getRGB() & 0xffffff, 6));
+ @Deprecated
+ public static PreferencesMap getMap() {
+ return PreferencesData.getMap();
}
-
- static public Font getFont(String attr) {
- boolean replace = false;
- String value = get(attr);
- if (value == null) {
- //System.out.println("reset 1");
- value = getDefault(attr);
- replace = true;
- }
-
- String[] pieces = PApplet.split(value, ',');
- if (pieces.length != 3) {
- value = getDefault(attr);
- //System.out.println("reset 2 for " + attr);
- pieces = PApplet.split(value, ',');
- //PApplet.println(pieces);
- replace = true;
- }
-
- String name = pieces[0];
- int style = Font.PLAIN; // equals zero
- if (pieces[1].indexOf("bold") != -1) {
- style |= Font.BOLD;
- }
- if (pieces[1].indexOf("italic") != -1) {
- style |= Font.ITALIC;
- }
- int size = PApplet.parseInt(pieces[2], 12);
- Font font = new Font(name, style, size);
-
- // replace bad font with the default
- if (replace) {
- set(attr, value);
- }
-
- return font;
+ @Deprecated
+ public static void setDoSave(boolean value) {
+ PreferencesData.setDoSave(value);
}
-
- static public SyntaxStyle getStyle(String what /*, String dflt*/) {
- String str = get("editor." + what + ".style"); //, dflt);
-
- StringTokenizer st = new StringTokenizer(str, ",");
-
- String s = st.nextToken();
- if (s.indexOf("#") == 0) s = s.substring(1);
- Color color = Color.DARK_GRAY;
- try {
- color = new Color(Integer.parseInt(s, 16));
- } catch (Exception e) { }
-
- s = st.nextToken();
- boolean bold = (s.indexOf("bold") != -1);
- boolean italic = (s.indexOf("italic") != -1);
- //System.out.println(what + " = " + str + " " + bold + " " + italic);
-
- return new SyntaxStyle(color, italic, bold);
- }
}
diff --git a/app/src/processing/app/PresentMode.java b/app/src/processing/app/PresentMode.java
index 34cba792818..49f888e2eba 100644
--- a/app/src/processing/app/PresentMode.java
+++ b/app/src/processing/app/PresentMode.java
@@ -59,7 +59,7 @@ public class PresentMode {
devices = environment.getScreenDevices();
GraphicsDevice defaultDevice = environment.getDefaultScreenDevice();
- Vector names = new Vector();
+ Vector names = new Vector<>();
for (int i = 0; i < devices.length; i++) {
String name = String.valueOf(i + 1);
if (devices[i] == defaultDevice) {
@@ -74,14 +74,14 @@ public class PresentMode {
public void actionPerformed(ActionEvent e) {
int index = selector.getSelectedIndex();
//device = devices[index];
- Preferences.setInteger("run.present.display", index + 1);
+ PreferencesData.setInteger("run.present.display", index + 1);
}
});
}
static public JComboBox getSelector() {
- int deviceIndex = Preferences.getInteger("run.present.display") - 1;
+ int deviceIndex = PreferencesData.getInteger("run.present.display") - 1;
selector.setSelectedIndex(deviceIndex);
return selector;
}
diff --git a/app/src/processing/app/Resources.java b/app/src/processing/app/Resources.java
new file mode 100644
index 00000000000..18d81d7ad52
--- /dev/null
+++ b/app/src/processing/app/Resources.java
@@ -0,0 +1,13 @@
+/*
+ * by Shigeru KANEMOTO at SWITCHSCIENCE.
+ * on 2011-10-15
+ */
+
+package processing.app;
+import java.util.ListResourceBundle;
+
+public class Resources extends ListResourceBundle {
+ protected Object[][] getContents() {
+ return new Object[][] {}; // Empty
+ }
+}
diff --git a/app/src/processing/app/debug/RunnerListener.java b/app/src/processing/app/RunnerListener.java
similarity index 97%
rename from app/src/processing/app/debug/RunnerListener.java
rename to app/src/processing/app/RunnerListener.java
index b9505a5107a..83381be66b5 100644
--- a/app/src/processing/app/debug/RunnerListener.java
+++ b/app/src/processing/app/RunnerListener.java
@@ -20,7 +20,7 @@
Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
-package processing.app.debug;
+package processing.app;
public interface RunnerListener {
@@ -30,4 +30,4 @@ public interface RunnerListener {
public void statusError(Exception exception);
public void statusNotice(String message);
-}
\ No newline at end of file
+}
diff --git a/app/src/processing/app/Serial.java b/app/src/processing/app/Serial.java
deleted file mode 100755
index c84c949cd1e..00000000000
--- a/app/src/processing/app/Serial.java
+++ /dev/null
@@ -1,632 +0,0 @@
-/* -*- mode: jde; c-basic-offset: 2; indent-tabs-mode: nil -*- */
-
-/*
- PSerial - class for serial port goodness
- Part of the Processing project - http://processing.org
-
- Copyright (c) 2004 Ben Fry & Casey Reas
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General
- Public License along with this library; if not, write to the
- Free Software Foundation, Inc., 59 Temple Place, Suite 330,
- Boston, MA 02111-1307 USA
-*/
-
-package processing.app;
-//import processing.core.*;
-
-import processing.app.debug.MessageConsumer;
-
-import gnu.io.*;
-
-import java.io.*;
-import java.util.*;
-
-
-public class Serial implements SerialPortEventListener {
-
- //PApplet parent;
-
- // properties can be passed in for default values
- // otherwise defaults to 9600 N81
-
- // these could be made static, which might be a solution
- // for the classloading problem.. because if code ran again,
- // the static class would have an object that could be closed
-
- SerialPort port;
-
- int rate;
- int parity;
- int databits;
- int stopbits;
- boolean monitor = false;
-
- // read buffer and streams
-
- InputStream input;
- OutputStream output;
-
- byte buffer[] = new byte[32768];
- int bufferIndex;
- int bufferLast;
-
- MessageConsumer consumer;
-
- public Serial(boolean monitor) throws SerialException {
- this(Preferences.get("serial.port"),
- Preferences.getInteger("serial.debug_rate"),
- Preferences.get("serial.parity").charAt(0),
- Preferences.getInteger("serial.databits"),
- new Float(Preferences.get("serial.stopbits")).floatValue());
- this.monitor = monitor;
- }
-
- public Serial() throws SerialException {
- this(Preferences.get("serial.port"),
- Preferences.getInteger("serial.debug_rate"),
- Preferences.get("serial.parity").charAt(0),
- Preferences.getInteger("serial.databits"),
- new Float(Preferences.get("serial.stopbits")).floatValue());
- }
-
- public Serial(int irate) throws SerialException {
- this(Preferences.get("serial.port"), irate,
- Preferences.get("serial.parity").charAt(0),
- Preferences.getInteger("serial.databits"),
- new Float(Preferences.get("serial.stopbits")).floatValue());
- }
-
- public Serial(String iname, int irate) throws SerialException {
- this(iname, irate, Preferences.get("serial.parity").charAt(0),
- Preferences.getInteger("serial.databits"),
- new Float(Preferences.get("serial.stopbits")).floatValue());
- }
-
- public Serial(String iname) throws SerialException {
- this(iname, Preferences.getInteger("serial.debug_rate"),
- Preferences.get("serial.parity").charAt(0),
- Preferences.getInteger("serial.databits"),
- new Float(Preferences.get("serial.stopbits")).floatValue());
- }
-
- public Serial(String iname, int irate,
- char iparity, int idatabits, float istopbits)
- throws SerialException {
- //if (port != null) port.close();
- //this.parent = parent;
- //parent.attach(this);
-
- this.rate = irate;
-
- parity = SerialPort.PARITY_NONE;
- if (iparity == 'E') parity = SerialPort.PARITY_EVEN;
- if (iparity == 'O') parity = SerialPort.PARITY_ODD;
-
- this.databits = idatabits;
-
- stopbits = SerialPort.STOPBITS_1;
- if (istopbits == 1.5f) stopbits = SerialPort.STOPBITS_1_5;
- if (istopbits == 2) stopbits = SerialPort.STOPBITS_2;
-
- try {
- port = null;
- Enumeration portList = CommPortIdentifier.getPortIdentifiers();
- while (portList.hasMoreElements()) {
- CommPortIdentifier portId =
- (CommPortIdentifier) portList.nextElement();
-
- if (portId.getPortType() == CommPortIdentifier.PORT_SERIAL) {
- //System.out.println("found " + portId.getName());
- if (portId.getName().equals(iname)) {
- //System.out.println("looking for "+iname);
- port = (SerialPort)portId.open("serial madness", 2000);
- input = port.getInputStream();
- output = port.getOutputStream();
- port.setSerialPortParams(rate, databits, stopbits, parity);
- port.addEventListener(this);
- port.notifyOnDataAvailable(true);
- //System.out.println("opening, ready to roll");
- }
- }
- }
- } catch (PortInUseException e) {
- throw new SerialException("Serial port '" + iname + "' already in use. Try quiting any programs that may be using it.");
- } catch (Exception e) {
- throw new SerialException("Error opening serial port '" + iname + "'.", e);
-// //errorMessage("", e);
-// //exception = e;
-// //e.printStackTrace();
- }
-
- if (port == null) {
- throw new SerialNotFoundException("Serial port '" + iname + "' not found. Did you select the right one from the Tools > Serial Port menu?");
- }
- }
-
-
- public void setup() {
- //parent.registerCall(this, DISPOSE);
- }
-
-
- //public void size(int w, int h) { }
-
- //public void pre() { }
-
- //public void draw() { }
-
- //public void post() { }
-
- //public void mouse(java.awt.event.MouseEvent event) { }
-
- //public void key(java.awt.event.KeyEvent e) { }
-
-
- public void dispose() {
- try {
- // do io streams need to be closed first?
- if (input != null) input.close();
- if (output != null) output.close();
-
- } catch (Exception e) {
- e.printStackTrace();
- }
- input = null;
- output = null;
-
- try {
- if (port != null) port.close(); // close the port
-
- } catch (Exception e) {
- e.printStackTrace();
- }
- port = null;
- }
-
-
- public void addListener(MessageConsumer consumer) {
- this.consumer = consumer;
- }
-
-
- synchronized public void serialEvent(SerialPortEvent serialEvent) {
- //System.out.println("serial port event"); // " + serialEvent);
- //System.out.flush();
- //System.out.println("into");
- //System.out.flush();
- //System.err.println("type " + serialEvent.getEventType());
- //System.err.println("ahoooyey");
- //System.err.println("ahoooyeysdfsdfsdf");
- if (serialEvent.getEventType() == SerialPortEvent.DATA_AVAILABLE) {
- //System.out.println("data available");
- //System.err.flush();
- try {
- while (input.available() > 0) {
- //if (input.available() > 0) {
- //serial = input.read();
- //serialEvent();
- //buffer[bufferCount++] = (byte) serial;
- synchronized (buffer) {
- if (bufferLast == buffer.length) {
- byte temp[] = new byte[bufferLast << 1];
- System.arraycopy(buffer, 0, temp, 0, bufferLast);
- buffer = temp;
- }
- //buffer[bufferLast++] = (byte) input.read();
- if(monitor == true)
- System.out.print((char) input.read());
- if (this.consumer != null)
- this.consumer.message("" + (char) input.read());
-
- /*
- System.err.println(input.available() + " " +
- ((char) buffer[bufferLast-1]));
- */ //}
- }
- }
- //System.out.println("no more");
-
- } catch (IOException e) {
- errorMessage("serialEvent", e);
- //e.printStackTrace();
- //System.out.println("angry");
- }
- catch (Exception e) {
- }
- }
- //System.out.println("out of");
- //System.err.println("out of event " + serialEvent.getEventType());
- }
-
-
- /**
- * Returns the number of bytes that have been read from serial
- * and are waiting to be dealt with by the user.
- */
- public int available() {
- return (bufferLast - bufferIndex);
- }
-
-
- /**
- * Ignore all the bytes read so far and empty the buffer.
- */
- public void clear() {
- bufferLast = 0;
- bufferIndex = 0;
- }
-
-
- /**
- * Returns a number between 0 and 255 for the next byte that's
- * waiting in the buffer.
- * Returns -1 if there was no byte (although the user should
- * first check available() to see if things are ready to avoid this)
- */
- public int read() {
- if (bufferIndex == bufferLast) return -1;
-
- synchronized (buffer) {
- int outgoing = buffer[bufferIndex++] & 0xff;
- if (bufferIndex == bufferLast) { // rewind
- bufferIndex = 0;
- bufferLast = 0;
- }
- return outgoing;
- }
- }
-
-
- /**
- * Returns the next byte in the buffer as a char.
- * Returns -1, or 0xffff, if nothing is there.
- */
- public char readChar() {
- if (bufferIndex == bufferLast) return (char)(-1);
- return (char) read();
- }
-
-
- /**
- * Return a byte array of anything that's in the serial buffer.
- * Not particularly memory/speed efficient, because it creates
- * a byte array on each read, but it's easier to use than
- * readBytes(byte b[]) (see below).
- */
- public byte[] readBytes() {
- if (bufferIndex == bufferLast) return null;
-
- synchronized (buffer) {
- int length = bufferLast - bufferIndex;
- byte outgoing[] = new byte[length];
- System.arraycopy(buffer, bufferIndex, outgoing, 0, length);
-
- bufferIndex = 0; // rewind
- bufferLast = 0;
- return outgoing;
- }
- }
-
-
- /**
- * Grab whatever is in the serial buffer, and stuff it into a
- * byte buffer passed in by the user. This is more memory/time
- * efficient than readBytes() returning a byte[] array.
- *
- * Returns an int for how many bytes were read. If more bytes
- * are available than can fit into the byte array, only those
- * that will fit are read.
- */
- public int readBytes(byte outgoing[]) {
- if (bufferIndex == bufferLast) return 0;
-
- synchronized (buffer) {
- int length = bufferLast - bufferIndex;
- if (length > outgoing.length) length = outgoing.length;
- System.arraycopy(buffer, bufferIndex, outgoing, 0, length);
-
- bufferIndex += length;
- if (bufferIndex == bufferLast) {
- bufferIndex = 0; // rewind
- bufferLast = 0;
- }
- return length;
- }
- }
-
-
- /**
- * Reads from the serial port into a buffer of bytes up to and
- * including a particular character. If the character isn't in
- * the serial buffer, then 'null' is returned.
- */
- public byte[] readBytesUntil(int interesting) {
- if (bufferIndex == bufferLast) return null;
- byte what = (byte)interesting;
-
- synchronized (buffer) {
- int found = -1;
- for (int k = bufferIndex; k < bufferLast; k++) {
- if (buffer[k] == what) {
- found = k;
- break;
- }
- }
- if (found == -1) return null;
-
- int length = found - bufferIndex + 1;
- byte outgoing[] = new byte[length];
- System.arraycopy(buffer, bufferIndex, outgoing, 0, length);
-
- bufferIndex = 0; // rewind
- bufferLast = 0;
- return outgoing;
- }
- }
-
-
- /**
- * Reads from the serial port into a buffer of bytes until a
- * particular character. If the character isn't in the serial
- * buffer, then 'null' is returned.
- *
- * If outgoing[] is not big enough, then -1 is returned,
- * and an error message is printed on the console.
- * If nothing is in the buffer, zero is returned.
- * If 'interesting' byte is not in the buffer, then 0 is returned.
- */
- public int readBytesUntil(int interesting, byte outgoing[]) {
- if (bufferIndex == bufferLast) return 0;
- byte what = (byte)interesting;
-
- synchronized (buffer) {
- int found = -1;
- for (int k = bufferIndex; k < bufferLast; k++) {
- if (buffer[k] == what) {
- found = k;
- break;
- }
- }
- if (found == -1) return 0;
-
- int length = found - bufferIndex + 1;
- if (length > outgoing.length) {
- System.err.println("readBytesUntil() byte buffer is" +
- " too small for the " + length +
- " bytes up to and including char " + interesting);
- return -1;
- }
- //byte outgoing[] = new byte[length];
- System.arraycopy(buffer, bufferIndex, outgoing, 0, length);
-
- bufferIndex += length;
- if (bufferIndex == bufferLast) {
- bufferIndex = 0; // rewind
- bufferLast = 0;
- }
- return length;
- }
- }
-
-
- /**
- * Return whatever has been read from the serial port so far
- * as a String. It assumes that the incoming characters are ASCII.
- *
- * If you want to move Unicode data, you can first convert the
- * String to a byte stream in the representation of your choice
- * (i.e. UTF8 or two-byte Unicode data), and send it as a byte array.
- */
- public String readString() {
- if (bufferIndex == bufferLast) return null;
- return new String(readBytes());
- }
-
-
- /**
- * Combination of readBytesUntil and readString. See caveats in
- * each function. Returns null if it still hasn't found what
- * you're looking for.
- *
- * If you want to move Unicode data, you can first convert the
- * String to a byte stream in the representation of your choice
- * (i.e. UTF8 or two-byte Unicode data), and send it as a byte array.
- */
- public String readStringUntil(int interesting) {
- byte b[] = readBytesUntil(interesting);
- if (b == null) return null;
- return new String(b);
- }
-
-
- /**
- * This will handle both ints, bytes and chars transparently.
- */
- public void write(int what) { // will also cover char
- try {
- output.write(what & 0xff); // for good measure do the &
- output.flush(); // hmm, not sure if a good idea
-
- } catch (Exception e) { // null pointer or serial port dead
- errorMessage("write", e);
- }
- }
-
-
- public void write(byte bytes[]) {
- try {
- output.write(bytes);
- output.flush(); // hmm, not sure if a good idea
-
- } catch (Exception e) { // null pointer or serial port dead
- //errorMessage("write", e);
- e.printStackTrace();
- }
- }
-
-
- /**
- * Write a String to the output. Note that this doesn't account
- * for Unicode (two bytes per char), nor will it send UTF8
- * characters.. It assumes that you mean to send a byte buffer
- * (most often the case for networking and serial i/o) and
- * will only use the bottom 8 bits of each char in the string.
- * (Meaning that internally it uses String.getBytes)
- *
- * If you want to move Unicode data, you can first convert the
- * String to a byte stream in the representation of your choice
- * (i.e. UTF8 or two-byte Unicode data), and send it as a byte array.
- */
- public void write(String what) {
- write(what.getBytes());
- }
-
- public void setDTR(boolean state) {
- port.setDTR(state);
- }
-
- public void setRTS(boolean state) {
- port.setRTS(state);
- }
-
- /**
- * If this just hangs and never completes on Windows,
- * it may be because the DLL doesn't have its exec bit set.
- * Why the hell that'd be the case, who knows.
- */
- static public String[] list() {
- Vector list = new Vector();
- try {
- //System.err.println("trying");
- Enumeration portList = CommPortIdentifier.getPortIdentifiers();
- //System.err.println("got port list");
- while (portList.hasMoreElements()) {
- CommPortIdentifier portId =
- (CommPortIdentifier) portList.nextElement();
- //System.out.println(portId);
-
- if (portId.getPortType() == CommPortIdentifier.PORT_SERIAL) {
- String name = portId.getName();
- list.addElement(name);
- }
- }
-
- } catch (UnsatisfiedLinkError e) {
- //System.err.println("1");
- errorMessage("ports", e);
-
- } catch (Exception e) {
- //System.err.println("2");
- errorMessage("ports", e);
- }
- //System.err.println("move out");
- String outgoing[] = new String[list.size()];
- list.copyInto(outgoing);
- return outgoing;
- }
-
-
- /**
- * General error reporting, all corraled here just in case
- * I think of something slightly more intelligent to do.
- */
- static public void errorMessage(String where, Throwable e) {
- System.err.println("Error inside Serial." + where + "()");
- e.printStackTrace();
- }
-}
-
-
- /*
- class SerialMenuListener implements ItemListener {
- //public SerialMenuListener() { }
-
- public void itemStateChanged(ItemEvent e) {
- int count = serialMenu.getItemCount();
- for (int i = 0; i < count; i++) {
- ((CheckboxMenuItem)serialMenu.getItem(i)).setState(false);
- }
- CheckboxMenuItem item = (CheckboxMenuItem)e.getSource();
- item.setState(true);
- String name = item.getLabel();
- //System.out.println(item.getLabel());
- PdeBase.properties.put("serial.port", name);
- //System.out.println("set to " + get("serial.port"));
- }
- }
- */
-
-
- /*
- protected Vector buildPortList() {
- // get list of names for serial ports
- // have the default port checked (if present)
- Vector list = new Vector();
-
- //SerialMenuListener listener = new SerialMenuListener();
- boolean problem = false;
-
- // if this is failing, it may be because
- // lib/javax.comm.properties is missing.
- // java is weird about how it searches for java.comm.properties
- // so it tends to be very fragile. i.e. quotes in the CLASSPATH
- // environment variable will hose things.
- try {
- //System.out.println("building port list");
- Enumeration portList = CommPortIdentifier.getPortIdentifiers();
- while (portList.hasMoreElements()) {
- CommPortIdentifier portId =
- (CommPortIdentifier) portList.nextElement();
- //System.out.println(portId);
-
- if (portId.getPortType() == CommPortIdentifier.PORT_SERIAL) {
- //if (portId.getName().equals(port)) {
- String name = portId.getName();
- //CheckboxMenuItem mi =
- //new CheckboxMenuItem(name, name.equals(defaultName));
-
- //mi.addItemListener(listener);
- //serialMenu.add(mi);
- list.addElement(name);
- }
- }
- } catch (UnsatisfiedLinkError e) {
- e.printStackTrace();
- problem = true;
-
- } catch (Exception e) {
- System.out.println("exception building serial menu");
- e.printStackTrace();
- }
-
- //if (serialMenu.getItemCount() == 0) {
- //System.out.println("dimming serial menu");
- //serialMenu.setEnabled(false);
- //}
-
- // only warn them if this is the first time
- if (problem && PdeBase.firstTime) {
- JOptionPane.showMessageDialog(this, //frame,
- "Serial port support not installed.\n" +
- "Check the readme for instructions\n" +
- "if you need to use the serial port. ",
- "Serial Port Warning",
- JOptionPane.WARNING_MESSAGE);
- }
- return list;
- }
- */
-
-
-
diff --git a/app/src/processing/app/SerialMonitor.java b/app/src/processing/app/SerialMonitor.java
index 009305413e0..b2656ca653d 100644
--- a/app/src/processing/app/SerialMonitor.java
+++ b/app/src/processing/app/SerialMonitor.java
@@ -18,211 +18,135 @@
package processing.app;
-import processing.app.debug.MessageConsumer;
-import processing.core.*;
+import cc.arduino.packages.BoardPort;
+import processing.app.legacy.PApplet;
-import java.awt.*;
-import java.awt.event.*;
-import javax.swing.*;
-import javax.swing.border.*;
-import javax.swing.event.*;
-import javax.swing.text.*;
+import java.awt.Color;
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+
+import static processing.app.I18n.tr;
+
+@SuppressWarnings("serial")
+public class SerialMonitor extends AbstractTextMonitor {
-public class SerialMonitor extends JFrame implements MessageConsumer {
private Serial serial;
- private String port;
- private JTextArea textArea;
- private JScrollPane scrollPane;
- private JTextField textField;
- private JButton sendButton;
- private JCheckBox autoscrollBox;
- private JComboBox lineEndings;
- private JComboBox serialRates;
private int serialRate;
- public SerialMonitor(String port) {
+ private static final int COMMAND_HISTORY_SIZE = 100;
+ private final CommandHistory commandHistory =
+ new CommandHistory(COMMAND_HISTORY_SIZE);
+
+ public SerialMonitor(BoardPort port) {
super(port);
-
- this.port = port;
-
- addWindowListener(new WindowAdapter() {
- public void windowClosing(WindowEvent e) {
- closeSerialPort();
- }
- });
-
- // obvious, no?
- KeyStroke wc = Editor.WINDOW_CLOSE_KEYSTROKE;
- getRootPane().getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(wc, "close");
- getRootPane().getActionMap().put("close", new AbstractAction() {
- public void actionPerformed(ActionEvent e) {
- closeSerialPort();
- setVisible(false);
- }});
-
- getContentPane().setLayout(new BorderLayout());
-
- Font font = Theme.getFont("console.font");
-
- textArea = new JTextArea(16, 40);
- textArea.setEditable(false);
- textArea.setFont(font);
-
- // don't automatically update the caret. that way we can manually decide
- // whether or not to do so based on the autoscroll checkbox.
- ((DefaultCaret)textArea.getCaret()).setUpdatePolicy(DefaultCaret.NEVER_UPDATE);
-
- scrollPane = new JScrollPane(textArea);
-
- getContentPane().add(scrollPane, BorderLayout.CENTER);
-
- JPanel pane = new JPanel();
- pane.setLayout(new BoxLayout(pane, BoxLayout.X_AXIS));
- pane.setBorder(new EmptyBorder(4, 4, 4, 4));
-
- textField = new JTextField(40);
- textField.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- send(textField.getText());
- textField.setText("");
- }});
-
- sendButton = new JButton("Send");
- sendButton.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- send(textField.getText());
- textField.setText("");
- }});
-
- pane.add(textField);
- pane.add(Box.createRigidArea(new Dimension(4, 0)));
- pane.add(sendButton);
-
- getContentPane().add(pane, BorderLayout.NORTH);
-
- pane = new JPanel();
- pane.setLayout(new BoxLayout(pane, BoxLayout.X_AXIS));
- pane.setBorder(new EmptyBorder(4, 4, 4, 4));
-
- autoscrollBox = new JCheckBox("Autoscroll", true);
-
- lineEndings = new JComboBox(new String[] { "No line ending", "Newline", "Carriage return", "Both NL & CR" });
- lineEndings.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent event) {
- Preferences.setInteger("serial.line_ending", lineEndings.getSelectedIndex());
- }
- });
- if (Preferences.get("serial.line_ending") != null) {
- lineEndings.setSelectedIndex(Preferences.getInteger("serial.line_ending"));
- }
- lineEndings.setMaximumSize(lineEndings.getMinimumSize());
-
- String[] serialRateStrings = {
- "300","1200","2400","4800","9600","14400",
- "19200","28800","38400","57600","115200"
- };
-
- serialRates = new JComboBox();
- for (int i = 0; i < serialRateStrings.length; i++)
- serialRates.addItem(serialRateStrings[i] + " baud");
-
- serialRate = Preferences.getInteger("serial.debug_rate");
- serialRates.setSelectedItem(serialRate + " baud");
- serialRates.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent event) {
- String wholeString = (String) serialRates.getSelectedItem();
- String rateString = wholeString.substring(0, wholeString.indexOf(' '));
- serialRate = Integer.parseInt(rateString);
- Preferences.set("serial.debug_rate", rateString);
- closeSerialPort();
+
+ serialRate = PreferencesData.getInteger("serial.debug_rate");
+ serialRates.setSelectedItem(serialRate + " " + tr("baud"));
+ onSerialRateChange((ActionEvent event) -> {
+ String wholeString = (String) serialRates.getSelectedItem();
+ String rateString = wholeString.substring(0, wholeString.indexOf(' '));
+ serialRate = Integer.parseInt(rateString);
+ PreferencesData.set("serial.debug_rate", rateString);
+ if (serial != null) {
try {
- openSerialPort();
- } catch (SerialException e) {
+ close();
+ Thread.sleep(100); // Wait for serial port to properly close
+ open();
+ } catch (InterruptedException e) {
+ // noop
+ } catch (Exception e) {
System.err.println(e);
}
- }});
-
- serialRates.setMaximumSize(serialRates.getMinimumSize());
-
- pane.add(autoscrollBox);
- pane.add(Box.createHorizontalGlue());
- pane.add(lineEndings);
- pane.add(Box.createRigidArea(new Dimension(8, 0)));
- pane.add(serialRates);
-
- getContentPane().add(pane, BorderLayout.SOUTH);
-
- pack();
-
- Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
- if (Preferences.get("last.screen.height") != null) {
- // if screen size has changed, the window coordinates no longer
- // make sense, so don't use them unless they're identical
- int screenW = Preferences.getInteger("last.screen.width");
- int screenH = Preferences.getInteger("last.screen.height");
- if ((screen.width == screenW) && (screen.height == screenH)) {
- String locationStr = Preferences.get("last.serial.location");
- if (locationStr != null) {
- int[] location = PApplet.parseInt(PApplet.split(locationStr, ','));
- setPlacement(location);
- }
}
- }
- }
-
- protected void setPlacement(int[] location) {
- setBounds(location[0], location[1], location[2], location[3]);
- }
-
- protected int[] getPlacement() {
- int[] location = new int[4];
+ });
- // 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;
+ onSendCommand((ActionEvent event) -> {
+ String command = textField.getText();
+ send(command);
+ commandHistory.addCommand(command);
+ textField.setText("");
+ });
- return location;
+ onClearCommand((ActionEvent event) -> textArea.setText(""));
+
+ // Add key listener to UP, DOWN, ESC keys for command history traversal.
+ textField.addKeyListener(new KeyAdapter() {
+ @Override
+ public void keyPressed(KeyEvent e) {
+ switch (e.getKeyCode()) {
+
+ // Select previous command.
+ case KeyEvent.VK_UP:
+ if (commandHistory.hasPreviousCommand()) {
+ textField.setText(
+ commandHistory.getPreviousCommand(textField.getText()));
+ }
+ break;
+
+ // Select next command.
+ case KeyEvent.VK_DOWN:
+ if (commandHistory.hasNextCommand()) {
+ textField.setText(commandHistory.getNextCommand());
+ }
+ break;
+
+ // Reset history location, restoring the last unexecuted command.
+ case KeyEvent.VK_ESCAPE:
+ textField.setText(commandHistory.resetHistoryLocation());
+ break;
+ }
+ }
+ });
}
private void send(String s) {
if (serial != null) {
switch (lineEndings.getSelectedIndex()) {
- case 1: s += "\n"; break;
- case 2: s += "\r"; break;
- case 3: s += "\r\n"; break;
+ case 1:
+ s += "\n";
+ break;
+ case 2:
+ s += "\r";
+ break;
+ case 3:
+ s += "\r\n";
+ break;
+ default:
+ break;
+ }
+ if ("".equals(s) && lineEndings.getSelectedIndex() == 0 && !PreferencesData.has("runtime.line.ending.alert.notified")) {
+ noLineEndingAlert.setForeground(Color.RED);
+ PreferencesData.set("runtime.line.ending.alert.notified", "true");
}
serial.write(s);
}
}
-
- public void openSerialPort() throws SerialException {
+
+ @Override
+ public void open() throws Exception {
+ super.open();
+
if (serial != null) return;
-
- serial = new Serial(port, serialRate);
- serial.addListener(this);
+
+ serial = new Serial(getBoardPort().getAddress(), serialRate) {
+ @Override
+ protected void message(char buff[], int n) {
+ addToUpdateBuffer(buff, n);
+ }
+ };
}
-
- public void closeSerialPort() {
+
+ @Override
+ public void close() throws Exception {
+ super.close();
if (serial != null) {
int[] location = getPlacement();
String locationStr = PApplet.join(PApplet.str(location), ",");
- Preferences.set("last.serial.location", locationStr);
- textArea.setText("");
+ PreferencesData.set("last.serial.location", locationStr);
serial.dispose();
serial = null;
}
}
- public void message(final String s) {
- SwingUtilities.invokeLater(new Runnable() {
- public void run() {
- textArea.append(s);
- if (autoscrollBox.isSelected()) {
- textArea.setCaretPosition(textArea.getDocument().getLength());
- }
- }});
- }
-}
\ No newline at end of file
+}
diff --git a/app/src/processing/app/SerialPlotter.java b/app/src/processing/app/SerialPlotter.java
new file mode 100644
index 00000000000..81b21cd7860
--- /dev/null
+++ b/app/src/processing/app/SerialPlotter.java
@@ -0,0 +1,497 @@
+/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
+
+/*
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+package processing.app;
+
+import cc.arduino.packages.BoardPort;
+import processing.app.helpers.CircularBuffer;
+import processing.app.helpers.Ticks;
+import processing.app.legacy.PApplet;
+
+import java.util.ArrayList;
+import javax.swing.*;
+import javax.swing.border.EmptyBorder;
+import javax.swing.text.DefaultEditorKit;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Rectangle2D;
+
+import static processing.app.I18n.tr;
+
+public class SerialPlotter extends AbstractMonitor {
+
+ private final StringBuffer messageBuffer;
+ private JComboBox serialRates;
+ private Serial serial;
+ private int serialRate, xCount;
+
+ private JLabel noLineEndingAlert;
+ private JTextField textField;
+ private JButton sendButton;
+ private JComboBox lineEndings;
+
+ private ArrayList graphs;
+ private final static int BUFFER_CAPACITY = 500;
+
+ private static class Graph {
+ public CircularBuffer buffer;
+ private Color color;
+ public String label;
+
+ public Graph(int id) {
+ buffer = new CircularBuffer(BUFFER_CAPACITY);
+ color = Theme.getColorCycleColor("plotting.graphcolor", id);
+ }
+
+ public void paint(Graphics2D g, float xstep, double minY,
+ double maxY, double rangeY, double height) {
+ g.setColor(color);
+ g.setStroke(new BasicStroke(1.0f));
+
+ for (int i = 0; i < buffer.size() - 1; ++i) {
+ g.drawLine(
+ (int) (i * xstep), (int) transformY(buffer.get(i), minY, rangeY, height),
+ (int) ((i + 1) * xstep), (int) transformY(buffer.get(i + 1), minY, rangeY, height)
+ );
+ }
+ }
+
+ private float transformY(double rawY, double minY, double rangeY, double height) {
+ return (float) (5 + (height - 10) * (1.0 - (rawY - minY) / rangeY));
+ }
+ }
+
+ private class GraphPanel extends JPanel {
+ private double minY, maxY, rangeY;
+ private Rectangle bounds;
+ private int xOffset, xPadding;
+ private final Font font;
+ private final Color bgColor, gridColor, boundsColor;
+
+ public GraphPanel() {
+ font = Theme.getFont("console.font");
+ bgColor = Theme.getColor("plotting.bgcolor");
+ gridColor = Theme.getColor("plotting.gridcolor");
+ boundsColor = Theme.getColor("plotting.boundscolor");
+ xOffset = 20;
+ xPadding = 20;
+ }
+
+ private Ticks computeBounds() {
+ minY = Double.POSITIVE_INFINITY;
+ maxY = Double.NEGATIVE_INFINITY;
+ for(Graph g : graphs) {
+ if (!g.buffer.isEmpty()) {
+ minY = Math.min(g.buffer.min(), minY);
+ maxY = Math.max(g.buffer.max(), maxY);
+ }
+ }
+
+ final double MIN_DELTA = 10.0;
+ if (maxY - minY < MIN_DELTA) {
+ double mid = (maxY + minY) / 2;
+ maxY = mid + MIN_DELTA / 2;
+ minY = mid - MIN_DELTA / 2;
+ }
+
+ Ticks ticks = new Ticks(minY, maxY, 5);
+ minY = Math.min(minY, ticks.getTick(0));
+ maxY = Math.max(maxY, ticks.getTick(ticks.getTickCount() - 1));
+ rangeY = maxY - minY;
+ minY -= 0.05 * rangeY;
+ maxY += 0.05 * rangeY;
+ rangeY = maxY - minY;
+ return ticks;
+ }
+
+ @Override
+ public void paintComponent(Graphics g1) {
+ Graphics2D g = (Graphics2D) g1;
+ g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+ g.setFont(font);
+ super.paintComponent(g);
+
+ bounds = g.getClipBounds();
+ setBackground(bgColor);
+ if (graphs.isEmpty()) {
+ return;
+ }
+
+ Ticks ticks = computeBounds();
+
+ g.setStroke(new BasicStroke(1.0f));
+ FontMetrics fm = g.getFontMetrics();
+ for (int i = 0; i < ticks.getTickCount(); ++i) {
+ double tick = ticks.getTick(i);
+ Rectangle2D fRect = fm.getStringBounds(String.valueOf(tick), g);
+ xOffset = Math.max(xOffset, (int) fRect.getWidth() + 15);
+
+ g.setColor(boundsColor);
+ // draw tick
+ g.drawLine(xOffset - 5, (int) transformY(tick), xOffset + 2, (int) transformY(tick));
+ // draw tick label
+ g.drawString(String.valueOf(tick), xOffset - (int) fRect.getWidth() - 10, transformY(tick) - (float) fRect.getHeight() * 0.5f + fm.getAscent());
+ // draw horizontal grid lines
+ g.setColor(gridColor);
+ g.drawLine(xOffset + 3, (int) transformY(tick), bounds.width - xPadding, (int) transformY(tick));
+ }
+
+ // handle data count
+ int cnt = xCount - BUFFER_CAPACITY;
+ if (xCount < BUFFER_CAPACITY) cnt = 0;
+
+ double zeroTick = ticks.getTick(0);
+ double lastTick = ticks.getTick(ticks.getTickCount() - 1);
+ double xTickRange = BUFFER_CAPACITY / ticks.getTickCount();
+
+ for (int i = 0; i < ticks.getTickCount() + 1; i++) {
+ String s;
+ int xValue;
+ int sWidth;
+ Rectangle2D fBounds;
+ if (i == 0) {
+ s = String.valueOf(cnt);
+ fBounds = fm.getStringBounds(s, g);
+ sWidth = (int)fBounds.getWidth()/2;
+ xValue = xOffset;
+ } else {
+ s = String.valueOf((int)(xTickRange * i)+cnt);
+ fBounds = fm.getStringBounds(s, g);
+ sWidth = (int)fBounds.getWidth()/2;
+ xValue = (int)((bounds.width - xOffset - xPadding) * ((xTickRange * i) / BUFFER_CAPACITY) + xOffset);
+ }
+ // draw graph x axis, ticks and labels
+ g.setColor(boundsColor);
+ g.drawString(s, xValue - sWidth, (int) bounds.y + (int) transformY(zeroTick) + 15);
+ g.drawLine(xValue, (int)transformY(zeroTick) - 2, xValue, bounds.y + (int)transformY(zeroTick) + 5);
+ // draw vertical grid lines
+ g.setColor(gridColor);
+ g.drawLine(xValue, (int)transformY(zeroTick) - 3, xValue, bounds.y + (int)transformY(lastTick));
+ }
+ g.setColor(boundsColor);
+ // draw major y axis
+ g.drawLine(bounds.x + xOffset, (int) transformY(lastTick) - 5, bounds.x + xOffset, bounds.y + (int) transformY(zeroTick) + 5);
+ // draw major x axis
+ g.drawLine(xOffset, (int) transformY(zeroTick), bounds.width - xPadding, (int)transformY(zeroTick));
+
+ g.setTransform(AffineTransform.getTranslateInstance(xOffset, 0));
+ float xstep = (float) (bounds.width - xOffset - xPadding) / (float) BUFFER_CAPACITY;
+
+ // draw legend
+ int legendXOffset = 0;
+ for(int i = 0; i < graphs.size(); ++i) {
+ graphs.get(i).paint(g, xstep, minY, maxY, rangeY, bounds.height);
+ if(graphs.size() > 1) {
+ //draw legend rectangle
+ g.fillRect(10 + legendXOffset, 10, 10, 10);
+ legendXOffset += 13;
+ //draw label
+ g.setColor(boundsColor);
+ String s = graphs.get(i).label;
+ if(s != null && s.length() > 0) {
+ Rectangle2D fBounds = fm.getStringBounds(s, g);
+ int sWidth = (int)fBounds.getWidth();
+ g.drawString(s, 10 + legendXOffset, 10 + (int)fBounds.getHeight() /2);
+ legendXOffset += sWidth + 3;
+ }
+ }
+ }
+ }
+
+ private float transformY(double rawY) {
+ return (float) (5 + (bounds.height - 10) * (1.0 - (rawY - minY) / rangeY));
+ }
+
+ @Override
+ public Dimension getMinimumSize() {
+ return new Dimension(200, 100);
+ }
+
+ @Override
+ public Dimension getPreferredSize() {
+ return new Dimension(500, 250);
+ }
+ }
+
+ public SerialPlotter(BoardPort port) {
+ super(port);
+
+ serialRate = PreferencesData.getInteger("serial.debug_rate");
+ serialRates.setSelectedItem(serialRate + " " + tr("baud"));
+ onSerialRateChange(event -> {
+ String wholeString = (String) serialRates.getSelectedItem();
+ String rateString = wholeString.substring(0, wholeString.indexOf(' '));
+ serialRate = Integer.parseInt(rateString);
+ PreferencesData.set("serial.debug_rate", rateString);
+ if (serial != null) {
+ try {
+ close();
+ Thread.sleep(100); // Wait for serial port to properly close
+ open();
+ } catch (Exception e) {
+ // ignore
+ }
+ }
+ });
+
+ messageBuffer = new StringBuffer();
+ graphs = new ArrayList<>();
+ }
+
+ protected void onCreateWindow(Container mainPane) {
+ mainPane.setLayout(new BorderLayout());
+
+ GraphPanel graphPanel = new GraphPanel();
+
+ mainPane.add(graphPanel, BorderLayout.CENTER);
+
+ JPanel pane = new JPanel();
+ pane.setLayout(new BoxLayout(pane, BoxLayout.X_AXIS));
+ pane.setBorder(new EmptyBorder(4, 4, 4, 4));
+
+ serialRates = new JComboBox<>();
+ for (String serialRateString : serialRateStrings) serialRates.addItem(serialRateString + " " + tr("baud"));
+
+ serialRates.setMaximumSize(serialRates.getMinimumSize());
+
+ pane.add(Box.createHorizontalGlue());
+ pane.add(Box.createRigidArea(new Dimension(8, 0)));
+ pane.add(serialRates);
+
+ mainPane.add(pane, BorderLayout.SOUTH);
+
+ textField = new JTextField(40);
+ // textField is selected every time the window is focused
+ addWindowFocusListener(new WindowAdapter() {
+ @Override
+ public void windowGainedFocus(WindowEvent e) {
+ textField.requestFocusInWindow();
+ }
+ });
+
+ // Add cut/copy/paste contextual menu to the text input field.
+ JPopupMenu menu = new JPopupMenu();
+
+ Action cut = new DefaultEditorKit.CutAction();
+ cut.putValue(Action.NAME, tr("Cut"));
+ menu.add(cut);
+
+ Action copy = new DefaultEditorKit.CopyAction();
+ copy.putValue(Action.NAME, tr("Copy"));
+ menu.add(copy);
+
+ Action paste = new DefaultEditorKit.PasteAction();
+ paste.putValue(Action.NAME, tr("Paste"));
+ menu.add(paste);
+
+ textField.setComponentPopupMenu(menu);
+
+ sendButton = new JButton(tr("Send"));
+
+ JPanel lowerPane = new JPanel();
+ lowerPane.setLayout(new BoxLayout(lowerPane, BoxLayout.X_AXIS));
+ lowerPane.setBorder(new EmptyBorder(4, 4, 4, 4));
+
+ noLineEndingAlert = new JLabel(I18n.format(tr("You've pressed {0} but nothing was sent. Should you select a line ending?"), tr("Send")));
+ noLineEndingAlert.setToolTipText(noLineEndingAlert.getText());
+ noLineEndingAlert.setForeground(pane.getBackground());
+ Dimension minimumSize = new Dimension(noLineEndingAlert.getMinimumSize());
+ minimumSize.setSize(minimumSize.getWidth() / 3, minimumSize.getHeight());
+ noLineEndingAlert.setMinimumSize(minimumSize);
+
+
+ lineEndings = new JComboBox(new String[]{tr("No line ending"), tr("Newline"), tr("Carriage return"), tr("Both NL & CR")});
+ lineEndings.addActionListener((ActionEvent event) -> {
+ PreferencesData.setInteger("serial.line_ending", lineEndings.getSelectedIndex());
+ noLineEndingAlert.setForeground(pane.getBackground());
+ });
+ lineEndings.setMaximumSize(lineEndings.getMinimumSize());
+
+ lowerPane.add(textField);
+ lowerPane.add(Box.createRigidArea(new Dimension(4, 0)));
+ lowerPane.add(sendButton);
+
+ pane.add(lowerPane);
+ pane.add(noLineEndingAlert);
+ pane.add(Box.createRigidArea(new Dimension(8, 0)));
+ pane.add(lineEndings);
+
+ applyPreferences();
+
+ onSendCommand((ActionEvent event) -> {
+ send(textField.getText());
+ textField.setText("");
+ });
+
+ }
+
+ private void send(String string) {
+ String s = string;
+ if (serial != null) {
+ switch (lineEndings.getSelectedIndex()) {
+ case 1:
+ s += "\n";
+ break;
+ case 2:
+ s += "\r";
+ break;
+ case 3:
+ s += "\r\n";
+ break;
+ default:
+ break;
+ }
+ if ("".equals(s) && lineEndings.getSelectedIndex() == 0 && !PreferencesData.has("runtime.line.ending.alert.notified")) {
+ noLineEndingAlert.setForeground(Color.RED);
+ PreferencesData.set("runtime.line.ending.alert.notified", "true");
+ }
+ serial.write(s);
+ }
+ }
+
+ public void onSendCommand(ActionListener listener) {
+ textField.addActionListener(listener);
+ sendButton.addActionListener(listener);
+ }
+
+ public void applyPreferences() {
+ // Apply line endings.
+ if (PreferencesData.get("serial.line_ending") != null) {
+ lineEndings.setSelectedIndex(PreferencesData.getInteger("serial.line_ending"));
+ }
+ }
+
+ protected void onEnableWindow(boolean enable) {
+ textField.setEnabled(enable);
+ sendButton.setEnabled(enable);
+ }
+
+ private void onSerialRateChange(ActionListener listener) {
+ serialRates.addActionListener(listener);
+ }
+
+ public void message(final String s) {
+ messageBuffer.append(s);
+ while (true) {
+ int linebreak = messageBuffer.indexOf("\n");
+ if (linebreak == -1) {
+ break;
+ }
+ xCount++;
+ String line = messageBuffer.substring(0, linebreak);
+ messageBuffer.delete(0, linebreak + 1);
+
+ line = line.trim();
+ if (line.length() == 0) {
+ // the line only contained trimmable characters
+ continue;
+ }
+ String[] parts = line.split("[, \t]+");
+ if(parts.length == 0) {
+ continue;
+ }
+
+ int validParts = 0;
+ int validLabels = 0;
+ for(int i = 0; i < parts.length; ++i) {
+ Double value = null;
+ String label = null;
+
+ // column formated name value pair
+ if(parts[i].contains(":")) {
+ // get label
+ String[] subString = parts[i].split("[:]+");
+
+ if(subString.length > 0) {
+ int labelLength = subString[0].length();
+
+ if(labelLength > 32) {
+ labelLength = 32;
+ }
+ label = subString[0].substring(0, labelLength);
+ } else {
+ label = "";
+ }
+
+ if(subString.length > 1) {
+ parts[i] = subString[1];
+ } else {
+ parts[i] = "";
+ }
+ }
+
+ try {
+ value = Double.valueOf(parts[i]);
+ } catch (NumberFormatException e) {
+ // ignored
+ }
+ //CSV header
+ if(label == null && value == null) {
+ label = parts[i];
+ }
+
+ if(value != null) {
+ if(validParts >= graphs.size()) {
+ graphs.add(new Graph(validParts));
+ }
+ graphs.get(validParts).buffer.add(value);
+ validParts++;
+ }
+ if(label != null) {
+ if(validLabels >= graphs.size()) {
+ graphs.add(new Graph(validLabels));
+ }
+ graphs.get(validLabels).label = label;
+ validLabels++;
+ }
+ if(validParts > validLabels) validLabels = validParts;
+ else if(validLabels > validParts) validParts = validLabels;
+ }
+ }
+
+ SwingUtilities.invokeLater(SerialPlotter.this::repaint);
+ }
+
+ public void open() throws Exception {
+ super.open();
+
+ if (serial != null) return;
+
+ serial = new Serial(getBoardPort().getAddress(), serialRate) {
+ @Override
+ protected void message(char buff[], int n) {
+ addToUpdateBuffer(buff, n);
+ }
+ };
+ }
+
+ public void close() throws Exception {
+ if (serial != null) {
+ super.close();
+ int[] location = getPlacement();
+ String locationStr = PApplet.join(PApplet.str(location), ",");
+ PreferencesData.set("last.serial.location", locationStr);
+ serial.dispose();
+ serial = null;
+ }
+ }
+}
diff --git a/app/src/processing/app/Sketch.java b/app/src/processing/app/Sketch.java
deleted file mode 100644
index 801067c5e2c..00000000000
--- a/app/src/processing/app/Sketch.java
+++ /dev/null
@@ -1,2011 +0,0 @@
-/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
-
-/*
- Part of the Processing project - http://processing.org
-
- Copyright (c) 2004-10 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 as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software Foundation,
- Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-*/
-
-package processing.app;
-
-import processing.app.debug.AvrdudeUploader;
-import processing.app.debug.Compiler;
-import processing.app.debug.RunnerException;
-import processing.app.debug.Sizer;
-import processing.app.debug.Uploader;
-import processing.app.preproc.*;
-import processing.core.*;
-
-import java.awt.*;
-import java.awt.event.*;
-import java.beans.*;
-import java.io.*;
-import java.util.*;
-import java.util.zip.*;
-
-import javax.swing.*;
-import javax.swing.border.EmptyBorder;
-import javax.swing.border.TitledBorder;
-
-
-/**
- * Stores information about files in the current sketch
- */
-public class Sketch {
- static private File tempBuildFolder;
-
- private Editor editor;
-
- /** main pde file for this sketch. */
- private File primaryFile;
-
- /**
- * Name of sketch, which is the name of main file
- * (without .pde or .java extension)
- */
- private String name;
-
- /** true if any of the files have been modified. */
- private boolean modified;
-
- /** folder that contains this sketch */
- private File folder;
-
- /** data folder location for this sketch (may not exist yet) */
- private File dataFolder;
-
- /** code folder location for this sketch (may not exist yet) */
- private File codeFolder;
-
- private SketchCode current;
- private int currentIndex;
- /**
- * Number of sketchCode objects (tabs) in the current sketch. Note that this
- * will be the same as code.length, because the getCode() method returns
- * just the code[] array, rather than a copy of it, or an array that's been
- * resized to just the relevant files themselves.
- * http://dev.processing.org/bugs/show_bug.cgi?id=940
- */
- private int codeCount;
- private SketchCode[] code;
-
- /** Class name for the PApplet, as determined by the preprocessor. */
- private String appletClassName;
- /** Class path determined during build. */
- private String classPath;
-
- /**
- * This is *not* the "Processing" libraries path, this is the Java libraries
- * path, as in java.library.path=BlahBlah, which identifies search paths for
- * DLLs or JNILIBs.
- */
- private String libraryPath;
- /**
- * List of library folders.
- */
- private ArrayList importedLibraries;
-
- /**
- * path is location of the main .pde file, because this is also
- * simplest to use when opening the file from the finder/explorer.
- */
- public Sketch(Editor editor, String path) throws IOException {
- this.editor = editor;
-
- primaryFile = new File(path);
-
- // get the name of the sketch by chopping .pde or .java
- // off of the main file name
- String mainFilename = primaryFile.getName();
- int suffixLength = getDefaultExtension().length() + 1;
- name = mainFilename.substring(0, mainFilename.length() - suffixLength);
-
- // lib/build must exist when the application is started
- // it is added to the CLASSPATH by default, but if it doesn't
- // exist when the application is started, then java will remove
- // the entry from the CLASSPATH, causing Runner to fail.
- //
- /*
- tempBuildFolder = new File(TEMP_BUILD_PATH);
- if (!tempBuildFolder.exists()) {
- tempBuildFolder.mkdirs();
- Base.showError("Required folder missing",
- "A required folder was missing from \n" +
- "from your installation of Processing.\n" +
- "It has now been replaced, please restart \n" +
- "the application to complete the repair.", null);
- }
- */
- tempBuildFolder = Base.getBuildFolder();
- //Base.addBuildFolderToClassPath();
-
- folder = new File(new File(path).getParent());
- //System.out.println("sketch dir is " + folder);
-
- load();
- }
-
-
- /**
- * Build the list of files.
- *
- * Generally this is only done once, rather than
- * each time a change is made, because otherwise it gets to be
- * a nightmare to keep track of what files went where, because
- * not all the data will be saved to disk.
- *
- * This also gets called when the main sketch file is renamed,
- * because the sketch has to be reloaded from a different folder.
- *
- * Another exception is when an external editor is in use,
- * in which case the load happens each time "run" is hit.
- */
- protected void load() {
- codeFolder = new File(folder, "code");
- dataFolder = new File(folder, "data");
-
- // get list of files in the sketch folder
- String list[] = folder.list();
-
- // reset these because load() may be called after an
- // external editor event. (fix for 0099)
- codeCount = 0;
-
- code = new SketchCode[list.length];
-
- String[] extensions = getExtensions();
-
- for (String filename : list) {
- // Ignoring the dot prefix files is especially important to avoid files
- // with the ._ prefix on Mac OS X. (You'll see this with Mac files on
- // non-HFS drives, i.e. a thumb drive formatted FAT32.)
- if (filename.startsWith(".")) continue;
-
- // Don't let some wacko name a directory blah.pde or bling.java.
- if (new File(folder, filename).isDirectory()) continue;
-
- // figure out the name without any extension
- String base = filename;
- // now strip off the .pde and .java extensions
- for (String extension : extensions) {
- if (base.toLowerCase().endsWith("." + extension)) {
- base = base.substring(0, base.length() - (extension.length() + 1));
-
- // Don't allow people to use files with invalid names, since on load,
- // it would be otherwise possible to sneak in nasty filenames. [0116]
- if (Sketch.isSanitaryName(base)) {
- code[codeCount++] =
- new SketchCode(new File(folder, filename), extension);
- }
- }
- }
- }
- // Remove any code that wasn't proper
- code = (SketchCode[]) PApplet.subset(code, 0, codeCount);
-
- // move the main class to the first tab
- // start at 1, if it's at zero, don't bother
- for (int i = 1; i < codeCount; i++) {
- //if (code[i].file.getName().equals(mainFilename)) {
- if (code[i].getFile().equals(primaryFile)) {
- SketchCode temp = code[0];
- code[0] = code[i];
- code[i] = temp;
- break;
- }
- }
-
- // sort the entries at the top
- sortCode();
-
- // set the main file to be the current tab
- if (editor != null) {
- setCurrentCode(0);
- }
- }
-
-
- protected void replaceCode(SketchCode newCode) {
- for (int i = 0; i < codeCount; i++) {
- if (code[i].getFileName().equals(newCode.getFileName())) {
- code[i] = newCode;
- break;
- }
- }
- }
-
-
- protected void insertCode(SketchCode newCode) {
- // make sure the user didn't hide the sketch folder
- ensureExistence();
-
- // add file to the code/codeCount list, resort the list
- //if (codeCount == code.length) {
- code = (SketchCode[]) PApplet.append(code, newCode);
- codeCount++;
- //}
- //code[codeCount++] = newCode;
- }
-
-
- protected void sortCode() {
- // cheap-ass sort of the rest of the files
- // it's a dumb, slow sort, but there shouldn't be more than ~5 files
- for (int i = 1; i < codeCount; i++) {
- int who = i;
- for (int j = i + 1; j < codeCount; j++) {
- if (code[j].getFileName().compareTo(code[who].getFileName()) < 0) {
- who = j; // this guy is earlier in the alphabet
- }
- }
- if (who != i) { // swap with someone if changes made
- SketchCode temp = code[who];
- code[who] = code[i];
- code[i] = temp;
- }
- }
- }
-
-
- boolean renamingCode;
-
- /**
- * Handler for the New Code menu option.
- */
- public void handleNewCode() {
- // make sure the user didn't hide the sketch folder
- ensureExistence();
-
- // if read-only, give an error
- if (isReadOnly()) {
- // if the files are read-only, need to first do a "save as".
- Base.showMessage("Sketch is Read-Only",
- "Some files are marked \"read-only\", so you'll\n" +
- "need to re-save the sketch in another location,\n" +
- "and try again.");
- return;
- }
-
- renamingCode = false;
- editor.status.edit("Name for new file:", "");
- }
-
-
- /**
- * Handler for the Rename Code menu option.
- */
- public void handleRenameCode() {
- // make sure the user didn't hide the sketch folder
- ensureExistence();
-
- if (currentIndex == 0 && editor.untitled) {
- Base.showMessage("Sketch is Untitled",
- "How about saving the sketch first \n" +
- "before trying to rename it?");
- return;
- }
-
- // if read-only, give an error
- if (isReadOnly()) {
- // if the files are read-only, need to first do a "save as".
- Base.showMessage("Sketch is Read-Only",
- "Some files are marked \"read-only\", so you'll\n" +
- "need to re-save the sketch in another location,\n" +
- "and try again.");
- return;
- }
-
- // ask for new name of file (internal to window)
- // TODO maybe just popup a text area?
- renamingCode = true;
- String prompt = (currentIndex == 0) ?
- "New name for sketch:" : "New name for file:";
- String oldName = (current.isExtension("pde")) ?
- current.getPrettyName() : current.getFileName();
- editor.status.edit(prompt, oldName);
- }
-
-
- /**
- * This is called upon return from entering a new file name.
- * (that is, from either newCode or renameCode after the prompt)
- * This code is almost identical for both the newCode and renameCode
- * cases, so they're kept merged except for right in the middle
- * where they diverge.
- */
- protected void nameCode(String newName) {
- // make sure the user didn't hide the sketch folder
- ensureExistence();
-
- // Add the extension here, this simplifies some of the logic below.
- if (newName.indexOf('.') == -1) {
- newName += "." + getDefaultExtension();
- }
-
- // if renaming to the same thing as before, just ignore.
- // also ignoring case here, because i don't want to write
- // a bunch of special stuff for each platform
- // (osx is case insensitive but preserving, windows insensitive,
- // *nix is sensitive and preserving.. argh)
- if (renamingCode) {
- if (newName.equalsIgnoreCase(current.getFileName())) {
- // exit quietly for the 'rename' case.
- // if it's a 'new' then an error will occur down below
- return;
- }
- }
-
- newName = newName.trim();
- if (newName.equals("")) return;
-
- int dot = newName.indexOf('.');
- if (dot == 0) {
- Base.showWarning("Problem with rename",
- "The name cannot start with a period.", null);
- return;
- }
-
- String newExtension = newName.substring(dot+1).toLowerCase();
- if (!validExtension(newExtension)) {
- Base.showWarning("Problem with rename",
- "\"." + newExtension + "\"" +
- "is not a valid extension.", null);
- return;
- }
-
- // Don't let the user create the main tab as a .java file instead of .pde
- if (!isDefaultExtension(newExtension)) {
- if (renamingCode) { // If creating a new tab, don't show this error
- if (current == code[0]) { // If this is the main tab, disallow
- Base.showWarning("Problem with rename",
- "The main file can't use an extension.\n" +
- "(It may be time for your to graduate to a\n" +
- "\"real\" programming environment)", null);
- return;
- }
- }
- }
-
- // dots are allowed for the .pde and .java, but not in the name
- // make sure the user didn't name things poo.time.pde
- // or something like that (nothing against poo time)
- String shortName = newName.substring(0, dot);
- String sanitaryName = Sketch.sanitizeName(shortName);
- if (!shortName.equals(sanitaryName)) {
- newName = sanitaryName + "." + newExtension;
- }
-
- // In Arduino, we want to allow files with the same name but different
- // extensions, so compare the full names (including extensions). This
- // might cause problems: http://dev.processing.org/bugs/show_bug.cgi?id=543
- for (SketchCode c : code) {
- if (newName.equalsIgnoreCase(c.getFileName())) {
- Base.showMessage("Nope",
- "A file named \"" + c.getFileName() + "\" already exists\n" +
- "in \"" + folder.getAbsolutePath() + "\"");
- return;
- }
- }
-
- // In Arduino, don't allow a .cpp file with the same name as the sketch,
- // because the sketch is concatenated into a file with that name as part
- // of the build process.
- if (newName.equals(getName() + ".cpp")) {
- Base.showMessage("Nope",
- "You can't have a .cpp file with the same name as the sketch.");
- return;
- }
-
- if (renamingCode && currentIndex == 0) {
- for (int i = 1; i < codeCount; i++) {
- if (sanitaryName.equalsIgnoreCase(code[i].getPrettyName()) &&
- code[i].getExtension().equalsIgnoreCase("cpp")) {
- Base.showMessage("Nope",
- "You can't rename the sketch to \"" + sanitaryName + "\"\n" +
- "because the sketch already has a .cpp file with that name.");
- return;
- }
- }
- }
-
-
- File newFile = new File(folder, newName);
-// if (newFile.exists()) { // yay! users will try anything
-// Base.showMessage("Nope",
-// "A file named \"" + newFile + "\" already exists\n" +
-// "in \"" + folder.getAbsolutePath() + "\"");
-// return;
-// }
-
-// File newFileHidden = new File(folder, newName + ".x");
-// if (newFileHidden.exists()) {
-// // don't let them get away with it if they try to create something
-// // with the same name as something hidden
-// Base.showMessage("No Way",
-// "A hidden tab with the same name already exists.\n" +
-// "Use \"Unhide\" to bring it back.");
-// return;
-// }
-
- if (renamingCode) {
- if (currentIndex == 0) {
- // get the new folder name/location
- String folderName = newName.substring(0, newName.indexOf('.'));
- File newFolder = new File(folder.getParentFile(), folderName);
- if (newFolder.exists()) {
- Base.showWarning("Cannot Rename",
- "Sorry, a sketch (or folder) named " +
- "\"" + newName + "\" already exists.", null);
- return;
- }
-
- // unfortunately this can't be a "save as" because that
- // only copies the sketch files and the data folder
- // however this *will* first save the sketch, then rename
-
- // first get the contents of the editor text area
- if (current.isModified()) {
- current.setProgram(editor.getText());
- try {
- // save this new SketchCode
- current.save();
- } catch (Exception e) {
- Base.showWarning("Error", "Could not rename the sketch. (0)", e);
- return;
- }
- }
-
- if (!current.renameTo(newFile, newExtension)) {
- Base.showWarning("Error",
- "Could not rename \"" + current.getFileName() +
- "\" to \"" + newFile.getName() + "\"", null);
- return;
- }
-
- // save each of the other tabs because this is gonna be re-opened
- try {
- for (int i = 1; i < codeCount; i++) {
- code[i].save();
- }
- } catch (Exception e) {
- Base.showWarning("Error", "Could not rename the sketch. (1)", e);
- return;
- }
-
- // now rename the sketch folder and re-open
- boolean success = folder.renameTo(newFolder);
- if (!success) {
- Base.showWarning("Error", "Could not rename the sketch. (2)", null);
- return;
- }
- // if successful, set base properties for the sketch
-
- File newMainFile = new File(newFolder, newName + ".pde");
- String newMainFilePath = newMainFile.getAbsolutePath();
-
- // having saved everything and renamed the folder and the main .pde,
- // use the editor to re-open the sketch to re-init state
- // (unfortunately this will kill positions for carets etc)
- editor.handleOpenUnchecked(newMainFilePath,
- currentIndex,
- editor.getSelectionStart(),
- editor.getSelectionStop(),
- editor.getScrollPosition());
-
- // get the changes into the sketchbook menu
- // (re-enabled in 0115 to fix bug #332)
- editor.base.rebuildSketchbookMenus();
-
- } else { // else if something besides code[0]
- if (!current.renameTo(newFile, newExtension)) {
- Base.showWarning("Error",
- "Could not rename \"" + current.getFileName() +
- "\" to \"" + newFile.getName() + "\"", null);
- return;
- }
- }
-
- } else { // creating a new file
- try {
- if (!newFile.createNewFile()) {
- // Already checking for IOException, so make our own.
- throw new IOException("createNewFile() returned false");
- }
- } catch (IOException e) {
- Base.showWarning("Error",
- "Could not create the file \"" + newFile + "\"\n" +
- "in \"" + folder.getAbsolutePath() + "\"", e);
- return;
- }
- SketchCode newCode = new SketchCode(newFile, newExtension);
- //System.out.println("new code is named " + newCode.getPrettyName() + " " + newCode.getFile());
- insertCode(newCode);
- }
-
- // sort the entries
- sortCode();
-
- // set the new guy as current
- setCurrentCode(newName);
-
- // update the tabs
- editor.header.rebuild();
- }
-
-
- /**
- * Remove a piece of code from the sketch and from the disk.
- */
- public void handleDeleteCode() {
- // make sure the user didn't hide the sketch folder
- ensureExistence();
-
- // if read-only, give an error
- if (isReadOnly()) {
- // if the files are read-only, need to first do a "save as".
- Base.showMessage("Sketch is Read-Only",
- "Some files are marked \"read-only\", so you'll\n" +
- "need to re-save the sketch in another location,\n" +
- "and try again.");
- return;
- }
-
- // confirm deletion with user, yes/no
- Object[] options = { "OK", "Cancel" };
- String prompt = (currentIndex == 0) ?
- "Are you sure you want to delete this sketch?" :
- "Are you sure you want to delete \"" + current.getPrettyName() + "\"?";
- int result = JOptionPane.showOptionDialog(editor,
- prompt,
- "Delete",
- JOptionPane.YES_NO_OPTION,
- JOptionPane.QUESTION_MESSAGE,
- null,
- options,
- options[0]);
- if (result == JOptionPane.YES_OPTION) {
- if (currentIndex == 0) {
- // need to unset all the modified flags, otherwise tries
- // to do a save on the handleNew()
-
- // delete the entire sketch
- Base.removeDir(folder);
-
- // get the changes into the sketchbook menu
- //sketchbook.rebuildMenus();
-
- // make a new sketch, and i think this will rebuild the sketch menu
- //editor.handleNewUnchecked();
- //editor.handleClose2();
- editor.base.handleClose(editor);
-
- } else {
- // delete the file
- if (!current.deleteFile()) {
- Base.showMessage("Couldn't do it",
- "Could not delete \"" +
- current.getFileName() + "\".");
- return;
- }
-
- // remove code from the list
- removeCode(current);
-
- // just set current tab to the main tab
- setCurrentCode(0);
-
- // update the tabs
- editor.header.repaint();
- }
- }
- }
-
-
- protected void removeCode(SketchCode which) {
- // remove it from the internal list of files
- // resort internal list of files
- for (int i = 0; i < codeCount; i++) {
- if (code[i] == which) {
- for (int j = i; j < codeCount-1; j++) {
- code[j] = code[j+1];
- }
- codeCount--;
- code = (SketchCode[]) PApplet.shorten(code);
- return;
- }
- }
- System.err.println("removeCode: internal error.. could not find code");
- }
-
-
- /**
- * Move to the previous tab.
- */
- public void handlePrevCode() {
- int prev = currentIndex - 1;
- if (prev < 0) prev = codeCount-1;
- setCurrentCode(prev);
- }
-
-
- /**
- * Move to the next tab.
- */
- public void handleNextCode() {
- setCurrentCode((currentIndex + 1) % codeCount);
- }
-
-
- /**
- * Sets the modified value for the code in the frontmost tab.
- */
- public void setModified(boolean state) {
- //System.out.println("setting modified to " + state);
- //new Exception().printStackTrace();
- current.setModified(state);
- calcModified();
- }
-
-
- protected void calcModified() {
- modified = false;
- for (int i = 0; i < codeCount; i++) {
- if (code[i].isModified()) {
- modified = true;
- break;
- }
- }
- editor.header.repaint();
-
- if (Base.isMacOS()) {
- // http://developer.apple.com/qa/qa2001/qa1146.html
- Object modifiedParam = modified ? Boolean.TRUE : Boolean.FALSE;
- editor.getRootPane().putClientProperty("windowModified", modifiedParam);
- }
- }
-
-
- public boolean isModified() {
- return modified;
- }
-
-
- /**
- * Save all code in the current sketch.
- */
- public boolean save() throws IOException {
- // make sure the user didn't hide the sketch folder
- ensureExistence();
-
- // first get the contents of the editor text area
- if (current.isModified()) {
- current.setProgram(editor.getText());
- }
-
- // don't do anything if not actually modified
- //if (!modified) return false;
-
- if (isReadOnly()) {
- // if the files are read-only, need to first do a "save as".
- Base.showMessage("Sketch is read-only",
- "Some files are marked \"read-only\", so you'll\n" +
- "need to re-save this sketch to another location.");
- // if the user cancels, give up on the save()
- if (!saveAs()) return false;
- }
-
- for (int i = 0; i < codeCount; i++) {
- if (code[i].isModified()) code[i].save();
- }
- calcModified();
- return true;
- }
-
-
- /**
- * Handles 'Save As' for a sketch.
- *
- * This basically just duplicates the current sketch folder to
- * a new location, and then calls 'Save'. (needs to take the current
- * state of the open files and save them to the new folder..
- * but not save over the old versions for the old sketch..)
- *
- * Also removes the previously-generated .class and .jar files,
- * because they can cause trouble.
- */
- protected boolean saveAs() throws IOException {
- String newParentDir = null;
- String newName = null;
-
- /*
- JFileChooser fc = new JFileChooser();
- fc.setDialogTitle("Save sketch folder as...");
- if (isReadOnly() || isUntitled()) {
- // default to the sketchbook folder
- fc.setCurrentDirectory(new File(Preferences.get("sketchbook.path")));
- } else {
- // default to the parent folder of where this was
- fc.setCurrentDirectory(folder.getParentFile());
- }
- // can't do this, will try to save into itself by default
- //fc.setSelectedFile(folder);
- int result = fc.showSaveDialog(editor);
- if (result == JFileChooser.APPROVE_OPTION) {
- File selection = fc.getSelectedFile();
- newParentDir = selection.getParent();
- newName = selection.getName();
- }
- */
-
- // get new name for folder
- FileDialog fd = new FileDialog(editor,
- "Save sketch folder as...",
- FileDialog.SAVE);
- if (isReadOnly() || isUntitled()) {
- // default to the sketchbook folder
- fd.setDirectory(Preferences.get("sketchbook.path"));
- } else {
- // default to the parent folder of where this was
- fd.setDirectory(folder.getParent());
- }
- String oldName = folder.getName();
- fd.setFile(oldName);
-
- fd.setVisible(true);
- newParentDir = fd.getDirectory();
- newName = fd.getFile();
-
- // user canceled selection
- if (newName == null) return false;
- newName = Sketch.checkName(newName);
-
- File newFolder = new File(newParentDir, newName);
-// String newPath = newFolder.getAbsolutePath();
-// String oldPath = folder.getAbsolutePath();
-
-// if (newPath.equals(oldPath)) {
-// return false; // Can't save a sketch over itself
-// }
-
- // make sure there doesn't exist a .cpp file with that name already
- // but ignore this situation for the first tab, since it's probably being
- // resaved (with the same name) to another location/folder.
- for (int i = 1; i < codeCount; i++) {
- if (newName.equalsIgnoreCase(code[i].getPrettyName()) &&
- code[i].getExtension().equalsIgnoreCase("cpp")) {
- Base.showMessage("Nope",
- "You can't save the sketch as \"" + newName + "\"\n" +
- "because the sketch already has a .cpp file with that name.");
- return false;
- }
- }
-
- // check if the paths are identical
- if (newFolder.equals(folder)) {
- // just use "save" here instead, because the user will have received a
- // message (from the operating system) about "do you want to replace?"
- return save();
- }
-
- // check to see if the user is trying to save this sketch inside itself
- try {
- String newPath = newFolder.getCanonicalPath() + File.separator;
- String oldPath = folder.getCanonicalPath() + File.separator;
-
- if (newPath.indexOf(oldPath) == 0) {
- Base.showWarning("How very Borges of you",
- "You cannot save the sketch into a folder\n" +
- "inside itself. This would go on forever.", null);
- return false;
- }
- } catch (IOException e) { }
-
- // if the new folder already exists, then need to remove
- // its contents before copying everything over
- // (user will have already been warned)
- if (newFolder.exists()) {
- Base.removeDir(newFolder);
- }
- // in fact, you can't do this on windows because the file dialog
- // will instead put you inside the folder, but it happens on osx a lot.
-
- // now make a fresh copy of the folder
- newFolder.mkdirs();
-
- // grab the contents of the current tab before saving
- // first get the contents of the editor text area
- if (current.isModified()) {
- current.setProgram(editor.getText());
- }
-
- // save the other tabs to their new location
- for (int i = 1; i < codeCount; i++) {
- File newFile = new File(newFolder, code[i].getFileName());
- code[i].saveAs(newFile);
- }
-
- // re-copy the data folder (this may take a while.. add progress bar?)
- if (dataFolder.exists()) {
- File newDataFolder = new File(newFolder, "data");
- Base.copyDir(dataFolder, newDataFolder);
- }
-
- // re-copy the code folder
- if (codeFolder.exists()) {
- File newCodeFolder = new File(newFolder, "code");
- Base.copyDir(codeFolder, newCodeFolder);
- }
-
- // copy custom applet.html file if one exists
- // http://dev.processing.org/bugs/show_bug.cgi?id=485
- File customHtml = new File(folder, "applet.html");
- if (customHtml.exists()) {
- File newHtml = new File(newFolder, "applet.html");
- Base.copyFile(customHtml, newHtml);
- }
-
- // save the main tab with its new name
- File newFile = new File(newFolder, newName + ".pde");
- code[0].saveAs(newFile);
-
- editor.handleOpenUnchecked(newFile.getPath(),
- currentIndex,
- editor.getSelectionStart(),
- editor.getSelectionStop(),
- editor.getScrollPosition());
-
- // Name changed, rebuild the sketch menus
- //editor.sketchbook.rebuildMenusAsync();
- editor.base.rebuildSketchbookMenus();
-
- // Make sure that it's not an untitled sketch
- setUntitled(false);
-
- // let Editor know that the save was successful
- return true;
- }
-
-
- /**
- * Prompt the user for a new file to the sketch, then call the
- * other addFile() function to actually add it.
- */
- public void handleAddFile() {
- // make sure the user didn't hide the sketch folder
- ensureExistence();
-
- // if read-only, give an error
- if (isReadOnly()) {
- // if the files are read-only, need to first do a "save as".
- Base.showMessage("Sketch is Read-Only",
- "Some files are marked \"read-only\", so you'll\n" +
- "need to re-save the sketch in another location,\n" +
- "and try again.");
- return;
- }
-
- // get a dialog, select a file to add to the sketch
- String prompt =
- "Select an image or other data file to copy to your sketch";
- //FileDialog fd = new FileDialog(new Frame(), prompt, FileDialog.LOAD);
- FileDialog fd = new FileDialog(editor, prompt, FileDialog.LOAD);
- fd.setVisible(true);
-
- String directory = fd.getDirectory();
- String filename = fd.getFile();
- if (filename == null) return;
-
- // copy the file into the folder. if people would rather
- // it move instead of copy, they can do it by hand
- File sourceFile = new File(directory, filename);
-
- // now do the work of adding the file
- boolean result = addFile(sourceFile);
-
- if (result) {
- editor.statusNotice("One file added to the sketch.");
- }
- }
-
-
- /**
- * Add a file to the sketch.
- *
- * .pde or .java files will be added to the sketch folder.
- * .jar, .class, .dll, .jnilib, and .so files will all
- * be added to the "code" folder.
- * All other files will be added to the "data" folder.
- *
- * If they don't exist already, the "code" or "data" folder
- * will be created.
- *
- * @return true if successful.
- */
- public boolean addFile(File sourceFile) {
- String filename = sourceFile.getName();
- File destFile = null;
- String codeExtension = null;
- boolean replacement = false;
-
- // if the file appears to be code related, drop it
- // into the code folder, instead of the data folder
- if (filename.toLowerCase().endsWith(".o") ||
- filename.toLowerCase().endsWith(".a") ||
- filename.toLowerCase().endsWith(".so")) {
-
- //if (!codeFolder.exists()) codeFolder.mkdirs();
- prepareCodeFolder();
- destFile = new File(codeFolder, filename);
-
- } else {
- for (String extension : getExtensions()) {
- String lower = filename.toLowerCase();
- if (lower.endsWith("." + extension)) {
- destFile = new File(this.folder, filename);
- codeExtension = extension;
- }
- }
- if (codeExtension == null) {
- prepareDataFolder();
- destFile = new File(dataFolder, filename);
- }
- }
-
- // check whether this file already exists
- if (destFile.exists()) {
- Object[] options = { "OK", "Cancel" };
- String prompt = "Replace the existing version of " + filename + "?";
- int result = JOptionPane.showOptionDialog(editor,
- prompt,
- "Replace",
- JOptionPane.YES_NO_OPTION,
- JOptionPane.QUESTION_MESSAGE,
- null,
- options,
- options[0]);
- if (result == JOptionPane.YES_OPTION) {
- replacement = true;
- } else {
- return false;
- }
- }
-
- // If it's a replacement, delete the old file first,
- // otherwise case changes will not be preserved.
- // http://dev.processing.org/bugs/show_bug.cgi?id=969
- if (replacement) {
- boolean muchSuccess = destFile.delete();
- if (!muchSuccess) {
- Base.showWarning("Error adding file",
- "Could not delete the existing '" +
- filename + "' file.", null);
- return false;
- }
- }
-
- // make sure they aren't the same file
- if ((codeExtension == null) && sourceFile.equals(destFile)) {
- Base.showWarning("You can't fool me",
- "This file has already been copied to the\n" +
- "location from which where you're trying to add it.\n" +
- "I ain't not doin nuthin'.", null);
- return false;
- }
-
- // in case the user is "adding" the code in an attempt
- // to update the sketch's tabs
- if (!sourceFile.equals(destFile)) {
- try {
- Base.copyFile(sourceFile, destFile);
-
- } catch (IOException e) {
- Base.showWarning("Error adding file",
- "Could not add '" + filename + "' to the sketch.", e);
- return false;
- }
- }
-
- if (codeExtension != null) {
- SketchCode newCode = new SketchCode(destFile, codeExtension);
-
- if (replacement) {
- replaceCode(newCode);
-
- } else {
- insertCode(newCode);
- sortCode();
- }
- setCurrentCode(filename);
- editor.header.repaint();
- if (editor.untitled) { // TODO probably not necessary? problematic?
- // Mark the new code as modified so that the sketch is saved
- current.setModified(true);
- }
-
- } else {
- if (editor.untitled) { // TODO probably not necessary? problematic?
- // If a file has been added, mark the main code as modified so
- // that the sketch is properly saved.
- code[0].setModified(true);
- }
- }
- return true;
- }
-
-
- /**
- * Add import statements to the current tab for all of packages inside
- * the specified jar file.
- */
- public void importLibrary(String jarPath) {
- // make sure the user didn't hide the sketch folder
- ensureExistence();
-
- String list[] = Compiler.headerListFromIncludePath(jarPath);
-
- // import statements into the main sketch file (code[0])
- // if the current code is a .java file, insert into current
- //if (current.flavor == PDE) {
- if (hasDefaultExtension(current)) {
- setCurrentCode(0);
- }
- // could also scan the text in the file to see if each import
- // statement is already in there, but if the user has the import
- // commented out, then this will be a problem.
- StringBuffer buffer = new StringBuffer();
- for (int i = 0; i < list.length; i++) {
- buffer.append("#include <");
- buffer.append(list[i]);
- buffer.append(">\n");
- }
- buffer.append('\n');
- buffer.append(editor.getText());
- editor.setText(buffer.toString());
- editor.setSelection(0, 0); // scroll to start
- setModified(true);
- }
-
-
- /**
- * Change what file is currently being edited. Changes the current tab index.
- *
- * - store the String for the text of the current file.
- *
- retrieve the String for the text of the new file.
- *
- change the text that's visible in the text area
- *
- */
- public void setCurrentCode(int which) {
- // if current is null, then this is the first setCurrent(0)
- if ((currentIndex == which) && (current != null)) {
- return;
- }
-
- // get the text currently being edited
- if (current != null) {
- current.setState(editor.getText(),
- editor.getSelectionStart(),
- editor.getSelectionStop(),
- editor.getScrollPosition());
- }
-
- current = code[which];
- currentIndex = which;
-
- editor.setCode(current);
- editor.header.rebuild();
- }
-
-
- /**
- * Internal helper function to set the current tab based on a name.
- * @param findName the file name (not pretty name) to be shown
- */
- protected void setCurrentCode(String findName) {
- for (int i = 0; i < codeCount; i++) {
- if (findName.equals(code[i].getFileName()) ||
- findName.equals(code[i].getPrettyName())) {
- setCurrentCode(i);
- return;
- }
- }
- }
-
-
- /**
- * Cleanup temporary files used during a build/run.
- */
- protected void cleanup() {
- // if the java runtime is holding onto any files in the build dir, we
- // won't be able to delete them, so we need to force a gc here
- System.gc();
-
- // note that we can't remove the builddir itself, otherwise
- // the next time we start up, internal runs using Runner won't
- // work because the build dir won't exist at startup, so the classloader
- // will ignore the fact that that dir is in the CLASSPATH in run.sh
- Base.removeDescendants(tempBuildFolder);
- }
-
-
- /**
- * Preprocess, Compile, and Run the current code.
- *
- * There are three main parts to this process:
- *
- * (0. if not java, then use another 'engine'.. i.e. python)
- *
- * 1. do the p5 language preprocessing
- * this creates a working .java file in a specific location
- * better yet, just takes a chunk of java code and returns a
- * new/better string editor can take care of saving this to a
- * file location
- *
- * 2. compile the code from that location
- * catching errors along the way
- * placing it in a ready classpath, or .. ?
- *
- * 3. run the code
- * needs to communicate location for window
- * and maybe setup presentation space as well
- * run externally if a code folder exists,
- * or if more than one file is in the project
- *
- * X. afterwards, some of these steps need a cleanup function
- *
- */
- //protected String compile() throws RunnerException {
-
-
- /**
- * When running from the editor, take care of preparations before running
- * the build.
- */
- public void prepare() {
- // make sure the user didn't hide the sketch folder
- ensureExistence();
-
- current.setProgram(editor.getText());
-
- // TODO record history here
- //current.history.record(program, SketchHistory.RUN);
-
- // if an external editor is being used, need to grab the
- // latest version of the code from the file.
- if (Preferences.getBoolean("editor.external")) {
- // history gets screwed by the open..
- //String historySaved = history.lastRecorded;
- //handleOpen(sketch);
- //history.lastRecorded = historySaved;
-
- // set current to null so that the tab gets updated
- // http://dev.processing.org/bugs/show_bug.cgi?id=515
- current = null;
- // nuke previous files and settings, just get things loaded
- load();
- }
-
- // in case there were any boogers left behind
- // do this here instead of after exiting, since the exit
- // can happen so many different ways.. and this will be
- // better connected to the dataFolder stuff below.
- cleanup();
-
-// // handle preprocessing the main file's code
-// return build(tempBuildFolder.getAbsolutePath());
- }
-
-
- /**
- * Build all the code for this sketch.
- *
- * In an advanced program, the returned class name could be different,
- * which is why the className is set based on the return value.
- * A compilation error will burp up a RunnerException.
- *
- * Setting purty to 'true' will cause exception line numbers to be incorrect.
- * Unless you know the code compiles, you should first run the preprocessor
- * with purty set to false to make sure there are no errors, then once
- * successful, re-export with purty set to true.
- *
- * @param buildPath Location to copy all the .java files
- * @return null if compilation failed, main class name if not
- */
- public String preprocess(String buildPath) throws RunnerException {
- return preprocess(buildPath, new PdePreprocessor());
- }
-
- public String preprocess(String buildPath, PdePreprocessor preprocessor) throws RunnerException {
- // make sure the user didn't hide the sketch folder
- ensureExistence();
-
- String[] codeFolderPackages = null;
- classPath = buildPath;
-
-// // figure out the contents of the code folder to see if there
-// // are files that need to be added to the imports
-// if (codeFolder.exists()) {
-// libraryPath = codeFolder.getAbsolutePath();
-//
-// // get a list of .jar files in the "code" folder
-// // (class files in subfolders should also be picked up)
-// String codeFolderClassPath =
-// Compiler.contentsToClassPath(codeFolder);
-// // append the jar files in the code folder to the class path
-// classPath += File.pathSeparator + codeFolderClassPath;
-// // get list of packages found in those jars
-// codeFolderPackages =
-// Compiler.packageListFromClassPath(codeFolderClassPath);
-//
-// } else {
-// libraryPath = "";
-// }
-
- // 1. concatenate all .pde files to the 'main' pde
- // store line number for starting point of each code bit
-
- StringBuffer bigCode = new StringBuffer();
- int bigCount = 0;
- for (SketchCode sc : code) {
- if (sc.isExtension("pde")) {
- sc.setPreprocOffset(bigCount);
- bigCode.append(sc.getProgram());
- bigCode.append('\n');
- bigCount += sc.getLineCount();
- }
- }
-
- // Note that the headerOffset isn't applied until compile and run, because
- // it only applies to the code after it's been written to the .java file.
- int headerOffset = 0;
- //PdePreprocessor preprocessor = new PdePreprocessor();
- try {
- headerOffset = preprocessor.writePrefix(bigCode.toString(),
- buildPath,
- name,
- codeFolderPackages);
- } catch (FileNotFoundException fnfe) {
- fnfe.printStackTrace();
- String msg = "Build folder disappeared or could not be written";
- throw new RunnerException(msg);
- }
-
- // 2. run preproc on that code using the sugg class name
- // to create a single .java file and write to buildpath
-
- String primaryClassName = null;
-
- try {
- // if (i != 0) preproc will fail if a pde file is not
- // java mode, since that's required
- String className = preprocessor.write();
-
- if (className == null) {
- throw new RunnerException("Could not find main class");
- // this situation might be perfectly fine,
- // (i.e. if the file is empty)
- //System.out.println("No class found in " + code[i].name);
- //System.out.println("(any code in that file will be ignored)");
- //System.out.println();
-
-// } else {
-// code[0].setPreprocName(className + ".java");
- }
-
- // store this for the compiler and the runtime
- primaryClassName = className + ".cpp";
-
- } catch (FileNotFoundException fnfe) {
- fnfe.printStackTrace();
- String msg = "Build folder disappeared or could not be written";
- throw new RunnerException(msg);
- } catch (RunnerException pe) {
- // RunnerExceptions are caught here and re-thrown, so that they don't
- // get lost in the more general "Exception" handler below.
- throw pe;
-
- } catch (Exception ex) {
- // TODO better method for handling this?
- System.err.println("Uncaught exception type:" + ex.getClass());
- ex.printStackTrace();
- throw new RunnerException(ex.toString());
- }
-
- // grab the imports from the code just preproc'd
-
- importedLibraries = new ArrayList();
-
- for (String item : preprocessor.getExtraImports()) {
- File libFolder = (File) Base.importToLibraryTable.get(item);
-
- if (libFolder != null && !importedLibraries.contains(libFolder)) {
- importedLibraries.add(libFolder);
- //classPath += Compiler.contentsToClassPath(libFolder);
- libraryPath += File.pathSeparator + libFolder.getAbsolutePath();
- }
- }
-
- // 3. then loop over the code[] and save each .java file
-
- for (SketchCode sc : code) {
- if (sc.isExtension("c") || sc.isExtension("cpp") || sc.isExtension("h")) {
- // no pre-processing services necessary for java files
- // just write the the contents of 'program' to a .java file
- // into the build directory. uses byte stream and reader/writer
- // shtuff so that unicode bunk is properly handled
- String filename = sc.getFileName(); //code[i].name + ".java";
- try {
- Base.saveFile(sc.getProgram(), new File(buildPath, filename));
- } catch (IOException e) {
- e.printStackTrace();
- throw new RunnerException("Problem moving " + filename +
- " to the build folder");
- }
-// sc.setPreprocName(filename);
-
- } else if (sc.isExtension("pde")) {
- // The compiler and runner will need this to have a proper offset
- sc.addPreprocOffset(headerOffset);
- }
- }
- return primaryClassName;
- }
-
-
- public ArrayList getImportedLibraries() {
- return importedLibraries;
- }
-
-
- /**
- * Map an error from a set of processed .java files back to its location
- * in the actual sketch.
- * @param message The error message.
- * @param filename The .java file where the exception was found.
- * @param line Line number of the .java file for the exception (1-indexed)
- * @return A RunnerException to be sent to the editor, or null if it wasn't
- * possible to place the exception to the sketch code.
- */
-// public RunnerException placeExceptionAlt(String message,
-// String filename, int line) {
-// String appletJavaFile = appletClassName + ".java";
-// SketchCode errorCode = null;
-// if (filename.equals(appletJavaFile)) {
-// for (SketchCode code : getCode()) {
-// if (code.isExtension("pde")) {
-// if (line >= code.getPreprocOffset()) {
-// errorCode = code;
-// }
-// }
-// }
-// } else {
-// for (SketchCode code : getCode()) {
-// if (code.isExtension("java")) {
-// if (filename.equals(code.getFileName())) {
-// errorCode = code;
-// }
-// }
-// }
-// }
-// int codeIndex = getCodeIndex(errorCode);
-//
-// if (codeIndex != -1) {
-// //System.out.println("got line num " + lineNumber);
-// // in case this was a tab that got embedded into the main .java
-// line -= getCode(codeIndex).getPreprocOffset();
-//
-// // lineNumber is 1-indexed, but editor wants zero-indexed
-// line--;
-//
-// // getMessage() will be what's shown in the editor
-// RunnerException exception =
-// new RunnerException(message, codeIndex, line, -1);
-// exception.hideStackTrace();
-// return exception;
-// }
-// return null;
-// }
-
-
- /**
- * Map an error from a set of processed .java files back to its location
- * in the actual sketch.
- * @param message The error message.
- * @param filename The .java file where the exception was found.
- * @param line Line number of the .java file for the exception (0-indexed!)
- * @return A RunnerException to be sent to the editor, or null if it wasn't
- * possible to place the exception to the sketch code.
- */
- public RunnerException placeException(String message,
- String dotJavaFilename,
- int dotJavaLine) {
- int codeIndex = 0; //-1;
- int codeLine = -1;
-
-// System.out.println("placing " + dotJavaFilename + " " + dotJavaLine);
-// System.out.println("code count is " + getCodeCount());
-
- // first check to see if it's a .java file
- for (int i = 0; i < getCodeCount(); i++) {
- SketchCode code = getCode(i);
- if (!code.isExtension(getDefaultExtension())) {
- if (dotJavaFilename.equals(code.getFileName())) {
- codeIndex = i;
- codeLine = dotJavaLine;
- return new RunnerException(message, codeIndex, codeLine);
- }
- }
- }
-
- // If not the preprocessed file at this point, then need to get out
- if (!dotJavaFilename.equals(name + ".cpp")) {
- return null;
- }
-
- // if it's not a .java file, codeIndex will still be 0
- // this section searches through the list of .pde files
- codeIndex = 0;
- for (int i = 0; i < getCodeCount(); i++) {
- SketchCode code = getCode(i);
-
- if (code.isExtension(getDefaultExtension())) {
-// System.out.println("preproc offset is " + code.getPreprocOffset());
-// System.out.println("looking for line " + dotJavaLine);
- if (code.getPreprocOffset() <= dotJavaLine) {
- codeIndex = i;
-// System.out.println("i'm thinkin file " + i);
- codeLine = dotJavaLine - code.getPreprocOffset();
- }
- }
- }
- // could not find a proper line number, so deal with this differently.
- // but if it was in fact the .java file we're looking for, though,
- // send the error message through.
- // this is necessary because 'import' statements will be at a line
- // that has a lower number than the preproc offset, for instance.
-// if (codeLine == -1 && !dotJavaFilename.equals(name + ".java")) {
-// return null;
-// }
- return new RunnerException(message, codeIndex, codeLine);
- }
-
-
- /**
- * Run the build inside the temporary build folder.
- * @return null if compilation failed, main class name if not
- * @throws RunnerException
- */
- public String build(boolean verbose) throws RunnerException {
- return build(tempBuildFolder.getAbsolutePath(), verbose);
- }
-
-
- /**
- * Preprocess and compile all the code for this sketch.
- *
- * In an advanced program, the returned class name could be different,
- * which is why the className is set based on the return value.
- * A compilation error will burp up a RunnerException.
- *
- * @return null if compilation failed, main class name if not
- */
- public String build(String buildPath, boolean verbose)
- throws RunnerException {
-
- // run the preprocessor
- String primaryClassName = preprocess(buildPath);
-
- // compile the program. errors will happen as a RunnerException
- // that will bubble up to whomever called build().
- Compiler compiler = new Compiler();
- if (compiler.compile(this, buildPath, primaryClassName, verbose)) {
- size(buildPath, primaryClassName);
- return primaryClassName;
- }
- return null;
- }
-
-
- protected boolean exportApplet(boolean verbose) throws Exception {
- return exportApplet(tempBuildFolder.getAbsolutePath(), verbose);
- }
-
-
- /**
- * Handle export to applet.
- */
- public boolean exportApplet(String appletPath, boolean verbose)
- throws RunnerException, IOException, SerialException {
-
- // Make sure the user didn't hide the sketch folder
- ensureExistence();
-
- current.setProgram(editor.getText());
-
- // Reload the code when an external editor is being used
- if (Preferences.getBoolean("editor.external")) {
- current = null;
- // nuke previous files and settings
- load();
- }
-
- File appletFolder = new File(appletPath);
- // Nuke the old applet folder because it can cause trouble
- if (Preferences.getBoolean("export.delete_target_folder")) {
- Base.removeDir(appletFolder);
- }
- // Create a fresh applet folder (needed before preproc is run below)
- appletFolder.mkdirs();
-
- // build the sketch
- String foundName = build(appletFolder.getPath(), false);
- // (already reported) error during export, exit this function
- if (foundName == null) return false;
-
-// // If name != exportSketchName, then that's weirdness
-// // BUG unfortunately, that can also be a bug in the preproc :(
-// if (!name.equals(foundName)) {
-// Base.showWarning("Error during export",
-// "Sketch name is " + name + " but the sketch\n" +
-// "name in the code was " + foundName, null);
-// return false;
-// }
-
- upload(appletFolder.getPath(), foundName, verbose);
-
- return true;
- }
-
-
- protected void size(String buildPath, String suggestedClassName)
- throws RunnerException {
- long size = 0;
- String maxsizeString = Base.getBoardPreferences().get("upload.maximum_size");
- if (maxsizeString == null) return;
- long maxsize = Integer.parseInt(maxsizeString);
- Sizer sizer = new Sizer(buildPath, suggestedClassName);
- try {
- size = sizer.computeSize();
- System.out.println("Binary sketch size: " + size + " bytes (of a " +
- maxsize + " byte maximum)");
- } catch (RunnerException e) {
- System.err.println("Couldn't determine program size: " + e.getMessage());
- }
-
- if (size > maxsize)
- throw new RunnerException(
- "Sketch too big; see http://www.arduino.cc/en/Guide/Troubleshooting#size for tips on reducing it.");
- }
-
-
- protected String upload(String buildPath, String suggestedClassName, boolean verbose)
- throws RunnerException, SerialException {
-
- Uploader uploader;
-
- // download the program
- //
- uploader = new AvrdudeUploader();
- boolean success = uploader.uploadUsingPreferences(buildPath,
- suggestedClassName,
- verbose);
-
- return success ? suggestedClassName : null;
- }
-
- /**
- * Replace all commented portions of a given String as spaces.
- * Utility function used here and in the preprocessor.
- */
- static public String scrubComments(String what) {
- char p[] = what.toCharArray();
-
- int index = 0;
- while (index < p.length) {
- // for any double slash comments, ignore until the end of the line
- if ((p[index] == '/') &&
- (index < p.length - 1) &&
- (p[index+1] == '/')) {
- p[index++] = ' ';
- p[index++] = ' ';
- while ((index < p.length) &&
- (p[index] != '\n')) {
- p[index++] = ' ';
- }
-
- // check to see if this is the start of a new multiline comment.
- // if it is, then make sure it's actually terminated somewhere.
- } else if ((p[index] == '/') &&
- (index < p.length - 1) &&
- (p[index+1] == '*')) {
- p[index++] = ' ';
- p[index++] = ' ';
- boolean endOfRainbow = false;
- while (index < p.length - 1) {
- if ((p[index] == '*') && (p[index+1] == '/')) {
- p[index++] = ' ';
- p[index++] = ' ';
- endOfRainbow = true;
- break;
-
- } else {
- // continue blanking this area
- p[index++] = ' ';
- }
- }
- if (!endOfRainbow) {
- throw new RuntimeException("Missing the */ from the end of a " +
- "/* comment */");
- }
- } else { // any old character, move along
- index++;
- }
- }
- return new String(p);
- }
-
-
- public boolean exportApplicationPrompt() throws IOException, RunnerException {
- return false;
- }
-
-
- /**
- * Export to application via GUI.
- */
- protected boolean exportApplication() throws IOException, RunnerException {
- return false;
- }
-
-
- /**
- * Export to application without GUI.
- */
- public boolean exportApplication(String destPath,
- int exportPlatform) throws IOException, RunnerException {
- return false;
- }
-
-
- /**
- * Make sure the sketch hasn't been moved or deleted by some
- * nefarious user. If they did, try to re-create it and save.
- * Only checks to see if the main folder is still around,
- * but not its contents.
- */
- protected void ensureExistence() {
- if (folder.exists()) return;
-
- Base.showWarning("Sketch Disappeared",
- "The sketch folder has disappeared.\n " +
- "Will attempt to re-save in the same location,\n" +
- "but anything besides the code will be lost.", null);
- try {
- folder.mkdirs();
- modified = true;
-
- for (int i = 0; i < codeCount; i++) {
- code[i].save(); // this will force a save
- }
- calcModified();
-
- } catch (Exception e) {
- Base.showWarning("Could not re-save sketch",
- "Could not properly re-save the sketch. " +
- "You may be in trouble at this point,\n" +
- "and it might be time to copy and paste " +
- "your code to another text editor.", e);
- }
- }
-
-
- /**
- * Returns true if this is a read-only sketch. Used for the
- * examples directory, or when sketches are loaded from read-only
- * volumes or folders without appropriate permissions.
- */
- public boolean isReadOnly() {
- String apath = folder.getAbsolutePath();
- if (apath.startsWith(Base.getExamplesPath()) ||
- apath.startsWith(Base.getLibrariesPath()) ||
- apath.startsWith(Base.getSketchbookLibrariesPath())) {
- return true;
-
- // canWrite() doesn't work on directories
- //} else if (!folder.canWrite()) {
- } else {
- // check to see if each modified code file can be written to
- for (int i = 0; i < codeCount; i++) {
- if (code[i].isModified() &&
- code[i].fileReadOnly() &&
- code[i].fileExists()) {
- //System.err.println("found a read-only file " + code[i].file);
- return true;
- }
- }
- //return true;
- }
- return false;
- }
-
-
- // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
-
- // Breaking out extension types in order to clean up the code, and make it
- // easier for other environments (like Arduino) to incorporate changes.
-
-
- /**
- * True if the specified extension should be hidden when shown on a tab.
- * For Processing, this is true for .pde files. (Broken out for subclasses.)
- */
- public boolean hideExtension(String what) {
- return what.equals(getDefaultExtension());
- }
-
-
- /**
- * True if the specified code has the default file extension.
- */
- public boolean hasDefaultExtension(SketchCode code) {
- return code.getExtension().equals(getDefaultExtension());
- }
-
-
- /**
- * True if the specified extension is the default file extension.
- */
- public boolean isDefaultExtension(String what) {
- return what.equals(getDefaultExtension());
- }
-
-
- /**
- * Check this extension (no dots, please) against the list of valid
- * extensions.
- */
- public boolean validExtension(String what) {
- String[] ext = getExtensions();
- for (int i = 0; i < ext.length; i++) {
- if (ext[i].equals(what)) return true;
- }
- return false;
- }
-
-
- /**
- * Returns the default extension for this editor setup.
- */
- public String getDefaultExtension() {
- return "pde";
- }
-
-
- /**
- * Returns a String[] array of proper extensions.
- */
- public String[] getExtensions() {
- return new String[] { "pde", "c", "cpp", "h" };
- }
-
-
- // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
-
- // Additional accessors added in 0136 because of package work.
- // These will also be helpful for tool developers.
-
-
- /**
- * Returns the name of this sketch. (The pretty name of the main tab.)
- */
- public String getName() {
- return name;
- }
-
-
- /**
- * Returns a file object for the primary .pde of this sketch.
- */
- public File getPrimaryFile() {
- return primaryFile;
- }
-
-
- /**
- * Returns path to the main .pde file for this sketch.
- */
- public String getMainFilePath() {
- return primaryFile.getAbsolutePath();
- //return code[0].file.getAbsolutePath();
- }
-
-
- /**
- * Returns the sketch folder.
- */
- public File getFolder() {
- return folder;
- }
-
-
- /**
- * Returns the location of the sketch's data folder. (It may not exist yet.)
- */
- public File getDataFolder() {
- return dataFolder;
- }
-
-
- /**
- * Create the data folder if it does not exist already. As a convenience,
- * it also returns the data folder, since it's likely about to be used.
- */
- public File prepareDataFolder() {
- if (!dataFolder.exists()) {
- dataFolder.mkdirs();
- }
- return dataFolder;
- }
-
-
- /**
- * Returns the location of the sketch's code folder. (It may not exist yet.)
- */
- public File getCodeFolder() {
- return codeFolder;
- }
-
-
- /**
- * Create the code folder if it does not exist already. As a convenience,
- * it also returns the code folder, since it's likely about to be used.
- */
- public File prepareCodeFolder() {
- if (!codeFolder.exists()) {
- codeFolder.mkdirs();
- }
- return codeFolder;
- }
-
-
- public String getClassPath() {
- return classPath;
- }
-
-
- public String getLibraryPath() {
- return libraryPath;
- }
-
-
- public SketchCode[] getCode() {
- return code;
- }
-
-
- public int getCodeCount() {
- return codeCount;
- }
-
-
- public SketchCode getCode(int index) {
- return code[index];
- }
-
-
- public int getCodeIndex(SketchCode who) {
- for (int i = 0; i < codeCount; i++) {
- if (who == code[i]) {
- return i;
- }
- }
- return -1;
- }
-
-
- public SketchCode getCurrentCode() {
- return current;
- }
-
-
- public void setUntitled(boolean u) {
- editor.untitled = u;
- }
-
-
- public boolean isUntitled() {
- return editor.untitled;
- }
-
-
- public String getAppletClassName2() {
- return appletClassName;
- }
-
-
- // .................................................................
-
-
- /**
- * Convert to sanitized name and alert the user
- * if changes were made.
- */
- static public String checkName(String origName) {
- String newName = sanitizeName(origName);
-
- if (!newName.equals(origName)) {
- String msg =
- "The sketch name had to be modified. Sketch names can only consist\n" +
- "of ASCII characters and numbers (but cannot start with a number).\n" +
- "They should also be less less than 64 characters long.";
- System.out.println(msg);
- }
- return newName;
- }
-
-
- /**
- * Return true if the name is valid for a Processing sketch.
- */
- static public boolean isSanitaryName(String name) {
- return sanitizeName(name).equals(name);
- }
-
-
- /**
- * Produce a sanitized name that fits our standards for likely to work.
- *
- * Java classes have a wider range of names that are technically allowed
- * (supposedly any Unicode name) than what we support. The reason for
- * going more narrow is to avoid situations with text encodings and
- * converting during the process of moving files between operating
- * systems, i.e. uploading from a Windows machine to a Linux server,
- * or reading a FAT32 partition in OS X and using a thumb drive.
- *
- * This helper function replaces everything but A-Z, a-z, and 0-9 with
- * underscores. Also disallows starting the sketch name with a digit.
- */
- static public String sanitizeName(String origName) {
- char c[] = origName.toCharArray();
- StringBuffer buffer = new StringBuffer();
-
- // can't lead with a digit, so start with an underscore
- if ((c[0] >= '0') && (c[0] <= '9')) {
- buffer.append('_');
- }
- for (int i = 0; i < c.length; i++) {
- if (((c[i] >= '0') && (c[i] <= '9')) ||
- ((c[i] >= 'a') && (c[i] <= 'z')) ||
- ((c[i] >= 'A') && (c[i] <= 'Z'))) {
- buffer.append(c[i]);
-
- } else {
- buffer.append('_');
- }
- }
- // let's not be ridiculous about the length of filenames.
- // in fact, Mac OS 9 can handle 255 chars, though it can't really
- // deal with filenames longer than 31 chars in the Finder.
- // but limiting to that for sketches would mean setting the
- // upper-bound on the character limit here to 25 characters
- // (to handle the base name + ".class")
- if (buffer.length() > 63) {
- buffer.setLength(63);
- }
- return buffer.toString();
- }
-}
diff --git a/app/src/processing/app/SketchCode.java b/app/src/processing/app/SketchCode.java
deleted file mode 100644
index f7dae32bba2..00000000000
--- a/app/src/processing/app/SketchCode.java
+++ /dev/null
@@ -1,285 +0,0 @@
-/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
-
-/*
- SketchCode - data class for a single file inside a sketch
- Part of the Processing project - http://processing.org
-
- Copyright (c) 2004-08 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 as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software Foundation,
- Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-*/
-
-package processing.app;
-
-import java.io.*;
-
-import javax.swing.text.Document;
-import javax.swing.undo.*;
-
-
-/**
- * Represents a single tab of a sketch.
- */
-public class SketchCode {
- /** Pretty name (no extension), not the full file name */
- private String prettyName;
-
- /** File object for where this code is located */
- private File file;
-
- /** Extension for this file (no dots, and in lowercase). */
- private String extension;
-
- /** Text of the program text for this tab */
- private String program;
-
- /** Document object for this tab. Currently this is a SyntaxDocument. */
- private Document document;
-
- /**
- * Undo Manager for this tab, each tab keeps track of their own
- * Editor.undo will be set to this object when this code is the tab
- * that's currently the front.
- */
- private UndoManager undo = new UndoManager();
-
- // saved positions from last time this tab was used
- private int selectionStart;
- private int selectionStop;
- private int scrollPosition;
-
- private boolean modified;
-
- /** name of .java file after preproc */
-// private String preprocName;
- /** where this code starts relative to the concat'd code */
- private int preprocOffset;
-
-
- public SketchCode(File file, String extension) {
- this.file = file;
- this.extension = extension;
-
- makePrettyName();
-
- try {
- load();
- } catch (IOException e) {
- System.err.println("Error while loading code " + file.getName());
- }
- }
-
-
- protected void makePrettyName() {
- prettyName = file.getName();
- int dot = prettyName.indexOf('.');
- prettyName = prettyName.substring(0, dot);
- }
-
-
- public File getFile() {
- return file;
- }
-
-
- protected boolean fileExists() {
- return file.exists();
- }
-
-
- protected boolean fileReadOnly() {
- return !file.canWrite();
- }
-
-
- protected boolean deleteFile() {
- return file.delete();
- }
-
-
- protected boolean renameTo(File what, String ext) {
- boolean success = file.renameTo(what);
- if (success) {
- this.file = what; // necessary?
- this.extension = ext;
- makePrettyName();
- }
- return success;
- }
-
-
- protected void copyTo(File dest) throws IOException {
- Base.saveFile(program, dest);
- }
-
-
- public String getFileName() {
- return file.getName();
- }
-
-
- public String getPrettyName() {
- return prettyName;
- }
-
-
- public String getExtension() {
- return extension;
- }
-
-
- public boolean isExtension(String what) {
- return extension.equals(what);
- }
-
-
- public String getProgram() {
- return program;
- }
-
-
- public void setProgram(String replacement) {
- program = replacement;
- }
-
-
- public int getLineCount() {
- return Base.countLines(program);
- }
-
-
- public void setModified(boolean modified) {
- this.modified = modified;
- }
-
-
- public boolean isModified() {
- return modified;
- }
-
-
-// public void setPreprocName(String preprocName) {
-// this.preprocName = preprocName;
-// }
-//
-//
-// public String getPreprocName() {
-// return preprocName;
-// }
-
-
- public void setPreprocOffset(int preprocOffset) {
- this.preprocOffset = preprocOffset;
- }
-
-
- public int getPreprocOffset() {
- return preprocOffset;
- }
-
-
- public void addPreprocOffset(int extra) {
- preprocOffset += extra;
- }
-
-
- public Document getDocument() {
- return document;
- }
-
-
- public void setDocument(Document d) {
- document = d;
- }
-
-
- public UndoManager getUndo() {
- return undo;
- }
-
-
- // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
-
-
- // TODO these could probably be handled better, since it's a general state
- // issue that's read/write from only one location in Editor (on tab switch.)
-
-
- public int getSelectionStart() {
- return selectionStart;
- }
-
-
- public int getSelectionStop() {
- return selectionStop;
- }
-
-
- public int getScrollPosition() {
- return scrollPosition;
- }
-
-
- protected void setState(String p, int start, int stop, int pos) {
- program = p;
- selectionStart = start;
- selectionStop = stop;
- scrollPosition = pos;
- }
-
-
- // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
-
-
- /**
- * Load this piece of code from a file.
- */
- public void load() throws IOException {
- program = Base.loadFile(file);
-
- if (program.indexOf('\uFFFD') != -1) {
- System.err.println(file.getName() + " contains unrecognized characters.");
- System.err.println("If this code was created with an older version of Processing,");
- System.err.println("you may need to use Tools -> Fix Encoding & Reload to update");
- System.err.println("the sketch to use UTF-8 encoding. If not, you may need to");
- System.err.println("delete the bad characters to get rid of this warning.");
- System.err.println();
- }
-
- setModified(false);
- }
-
-
- /**
- * Save this piece of code, regardless of whether the modified
- * flag is set or not.
- */
- public void save() throws IOException {
- // TODO re-enable history
- //history.record(s, SketchHistory.SAVE);
-
- Base.saveFile(program, file);
- setModified(false);
- }
-
-
- /**
- * Save this file to another location, used by Sketch.saveAs()
- */
- public void saveAs(File newFile) throws IOException {
- Base.saveFile(program, newFile);
- }
-}
diff --git a/app/src/processing/app/SketchController.java b/app/src/processing/app/SketchController.java
new file mode 100644
index 00000000000..ce9e468cc68
--- /dev/null
+++ b/app/src/processing/app/SketchController.java
@@ -0,0 +1,858 @@
+/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
+
+/*
+ Part of the Processing project - http://processing.org
+
+ Copyright (c) 2004-10 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 as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+package processing.app;
+
+import cc.arduino.Compiler;
+import cc.arduino.CompilerProgressListener;
+import cc.arduino.UploaderUtils;
+import cc.arduino.packages.Uploader;
+import processing.app.debug.RunnerException;
+import processing.app.forms.PasswordAuthorizationDialog;
+import processing.app.helpers.FileUtils;
+import processing.app.helpers.OSUtils;
+import processing.app.helpers.PreferencesMapException;
+import processing.app.packages.LibraryList;
+import processing.app.packages.UserLibrary;
+
+import javax.swing.*;
+import java.awt.*;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static processing.app.I18n.tr;
+
+
+/**
+ * Handles various tasks related to a sketch, in response to user inter-action.
+ */
+public class SketchController {
+ private final Editor editor;
+ private final Sketch sketch;
+
+ public SketchController(Editor _editor, Sketch _sketch) {
+ editor = _editor;
+ sketch = _sketch;
+ }
+
+ private boolean renamingCode;
+
+ /**
+ * Handler for the New Code menu option.
+ */
+ public void handleNewCode() {
+ editor.status.clearState();
+ // make sure the user didn't hide the sketch folder
+ ensureExistence();
+
+ // if read-only, give an error
+ if (isReadOnly()) {
+ // if the files are read-only, need to first do a "save as".
+ Base.showMessage(tr("Sketch is Read-Only"),
+ tr("Some files are marked \"read-only\", so you'll\n" +
+ "need to re-save the sketch in another location,\n" +
+ "and try again."));
+ return;
+ }
+
+ renamingCode = false;
+ editor.status.edit(tr("Name for new file:"), "");
+ }
+
+
+ /**
+ * Handler for the Rename Code menu option.
+ */
+ public void handleRenameCode() {
+ SketchFile current = editor.getCurrentTab().getSketchFile();
+
+ editor.status.clearState();
+ // make sure the user didn't hide the sketch folder
+ ensureExistence();
+
+ if (current.isPrimary() && editor.untitled) {
+ Base.showMessage(tr("Sketch is Untitled"),
+ tr("How about saving the sketch first \n" +
+ "before trying to rename it?"));
+ return;
+ }
+
+ // if read-only, give an error
+ if (isReadOnly()) {
+ // if the files are read-only, need to first do a "save as".
+ Base.showMessage(tr("Sketch is Read-Only"),
+ tr("Some files are marked \"read-only\", so you'll\n" +
+ "need to re-save the sketch in another location,\n" +
+ "and try again."));
+ return;
+ }
+
+ // ask for new name of file (internal to window)
+ // TODO maybe just popup a text area?
+ renamingCode = true;
+ String prompt = current.isPrimary() ?
+ "New name for sketch:" : "New name for file:";
+ String oldName = current.getPrettyName();
+ editor.status.edit(prompt, oldName);
+ }
+
+
+ /**
+ * This is called upon return from entering a new file name.
+ * (that is, from either newCode or renameCode after the prompt)
+ * This code is almost identical for both the newCode and renameCode
+ * cases, so they're kept merged except for right in the middle
+ * where they diverge.
+ */
+ protected void nameCode(String newName) {
+ // make sure the user didn't hide the sketch folder
+ ensureExistence();
+
+ newName = newName.trim();
+ if (newName.equals("")) return;
+
+ if (newName.charAt(0) == '.') {
+ Base.showWarning(tr("Problem with rename"),
+ tr("The name cannot start with a period."), null);
+ return;
+ }
+
+ FileUtils.SplitFile split = FileUtils.splitFilename(newName);
+ if (split.extension.equals(""))
+ split.extension = Sketch.DEFAULT_SKETCH_EXTENSION;
+
+ if (!Sketch.EXTENSIONS.contains(split.extension.toLowerCase())) {
+ String msg = I18n.format(tr("\".{0}\" is not a valid extension."),
+ split.extension);
+ Base.showWarning(tr("Problem with rename"), msg, null);
+ return;
+ }
+
+ // Sanitize name
+ split.basename = BaseNoGui.sanitizeName(split.basename);
+ newName = split.join();
+
+ if (renamingCode) {
+ SketchFile current = editor.getCurrentTab().getSketchFile();
+
+ if (current.isPrimary()) {
+ if (!split.extension.equals(Sketch.DEFAULT_SKETCH_EXTENSION)) {
+ Base.showWarning(tr("Problem with rename"),
+ tr("The main file cannot use an extension"), null);
+ return;
+ }
+
+ // Primary file, rename the entire sketch
+ final File parent = sketch.getFolder().getParentFile();
+ File newFolder = new File(parent, split.basename);
+ try {
+ sketch.renameTo(newFolder);
+ } catch (IOException e) {
+ // This does not pass on e, to prevent showing a backtrace for
+ // "normal" errors.
+ Base.showWarning(tr("Error"), e.getMessage(), null);
+ return;
+ }
+
+ editor.base.rebuildSketchbookMenus();
+ } else {
+ // Non-primary file, rename just that file
+ try {
+ current.renameTo(newName);
+ } catch (IOException e) {
+ // This does not pass on e, to prevent showing a backtrace for
+ // "normal" errors.
+ Base.showWarning(tr("Error"), e.getMessage(), null);
+ return;
+ }
+ }
+
+ } else { // creating a new file
+ SketchFile file;
+ try {
+ file = sketch.addFile(newName);
+ editor.addTab(file, "");
+ } catch (IOException e) {
+ // This does not pass on e, to prevent showing a backtrace for
+ // "normal" errors.
+ Base.showWarning(tr("Error"), e.getMessage(), null);
+ return;
+ }
+ editor.selectTab(editor.findTabIndex(file));
+ }
+
+ // update the tabs
+ editor.header.rebuild();
+ }
+
+
+ /**
+ * Remove a piece of code from the sketch and from the disk.
+ */
+ public void handleDeleteCode() throws IOException {
+ SketchFile current = editor.getCurrentTab().getSketchFile();
+ editor.status.clearState();
+ // make sure the user didn't hide the sketch folder
+ ensureExistence();
+
+ // if read-only, give an error
+ if (isReadOnly()) {
+ // if the files are read-only, need to first do a "save as".
+ Base.showMessage(tr("Sketch is Read-Only"),
+ tr("Some files are marked \"read-only\", so you'll\n" +
+ "need to re-save the sketch in another location,\n" +
+ "and try again."));
+ return;
+ }
+
+ // confirm deletion with user, yes/no
+ Object[] options = { tr("OK"), tr("Cancel") };
+ String prompt = current.isPrimary() ?
+ tr("Are you sure you want to delete this sketch?") :
+ I18n.format(tr("Are you sure you want to delete \"{0}\"?"),
+ current.getPrettyName());
+ int result = JOptionPane.showOptionDialog(editor,
+ prompt,
+ tr("Delete"),
+ JOptionPane.YES_NO_OPTION,
+ JOptionPane.QUESTION_MESSAGE,
+ null,
+ options,
+ options[0]);
+ if (result == JOptionPane.YES_OPTION) {
+ if (current.isPrimary()) {
+ sketch.delete();
+ editor.base.handleClose(editor);
+ } else {
+
+ boolean neverSavedTab = !current.fileExists();
+
+ // delete the file
+ if (!current.delete(sketch.getBuildPath().toPath()) && !neverSavedTab) {
+ Base.showMessage(tr("Couldn't do it"),
+ I18n.format(tr("Could not delete \"{0}\"."), current.getFileName()));
+ return;
+ }
+
+ if (neverSavedTab) {
+ // remove the file from the sketch list
+ sketch.removeFile(current);
+ }
+
+ editor.removeTab(current);
+
+ // just set current tab to the main tab
+ editor.selectTab(0);
+
+ // update the tabs
+ editor.header.repaint();
+ }
+ }
+ }
+
+ /**
+ * Called whenever the modification status of one of the tabs changes. TODO:
+ * Move this code into Editor and improve decoupling from EditorTab
+ */
+ public void calcModified() {
+ editor.header.repaint();
+
+ if (OSUtils.isMacOS()) {
+ // http://developer.apple.com/qa/qa2001/qa1146.html
+ Object modifiedParam = sketch.isModified() ? Boolean.TRUE : Boolean.FALSE;
+ editor.getRootPane().putClientProperty("windowModified", modifiedParam);
+ editor.getRootPane().putClientProperty("Window.documentModified", modifiedParam);
+ }
+ }
+
+
+
+ /**
+ * Save all code in the current sketch.
+ */
+ public boolean save() throws IOException {
+ // make sure the user didn't hide the sketch folder
+ ensureExistence();
+
+ if (isReadOnly()) {
+ Base.showMessage(tr("Sketch is read-only"),
+ tr("Some files are marked \"read-only\", so you'll\n" +
+ "need to re-save this sketch to another location."));
+ return saveAs();
+ }
+
+ // rename .pde files to .ino
+ List oldFiles = new ArrayList<>();
+ for (SketchFile file : sketch.getFiles()) {
+ if (file.isExtension(Sketch.OLD_SKETCH_EXTENSIONS))
+ oldFiles.add(file);
+ }
+
+ if (oldFiles.size() > 0) {
+ if (PreferencesData.get("editor.update_extension") == null) {
+ Object[] options = {tr("OK"), tr("Cancel")};
+ int result = JOptionPane.showOptionDialog(editor,
+ tr("In Arduino 1.0, the default file extension has changed\n" +
+ "from .pde to .ino. New sketches (including those created\n" +
+ "by \"Save-As\") will use the new extension. The extension\n" +
+ "of existing sketches will be updated on save, but you can\n" +
+ "disable this in the Preferences dialog.\n" +
+ "\n" +
+ "Save sketch and update its extension?"),
+ tr(".pde -> .ino"),
+ JOptionPane.OK_CANCEL_OPTION,
+ JOptionPane.QUESTION_MESSAGE,
+ null,
+ options,
+ options[0]);
+
+ if (result != JOptionPane.OK_OPTION) return false; // save cancelled
+
+ PreferencesData.setBoolean("editor.update_extension", true);
+ }
+
+ if (PreferencesData.getBoolean("editor.update_extension")) {
+ // Do rename of all .pde files to new .ino extension
+ for (SketchFile file : oldFiles) {
+ File newName = FileUtils.replaceExtension(file.getFile(), Sketch.DEFAULT_SKETCH_EXTENSION);
+ file.renameTo(newName.getName());
+ }
+ }
+ }
+
+ sketch.save();
+ return true;
+ }
+
+ /**
+ * Handles 'Save As' for a sketch.
+ *
+ * This basically just duplicates the current sketch folder to
+ * a new location, and then calls 'Save'. (needs to take the current
+ * state of the open files and save them to the new folder..
+ * but not save over the old versions for the old sketch..)
+ *
+ * Also removes the previously-generated .class and .jar files,
+ * because they can cause trouble.
+ */
+ protected boolean saveAs() throws IOException {
+ // get new name for folder
+ FileDialog fd = new FileDialog(editor, tr("Save sketch folder as..."), FileDialog.SAVE);
+ if (isReadOnly() || isUntitled()) {
+ // default to the sketchbook folder
+ fd.setDirectory(BaseNoGui.getSketchbookFolder().getAbsolutePath());
+ } else {
+ // default to the parent folder of where this was
+ // on macs a .getParentFile() method is required
+
+ fd.setDirectory(sketch.getFolder().getParentFile().getAbsolutePath());
+ }
+ String oldName = sketch.getName();
+ fd.setFile(oldName);
+
+ fd.setVisible(true);
+ String newParentDir = fd.getDirectory();
+ String newName = fd.getFile();
+
+ // user canceled selection
+ if (newName == null) return false;
+ newName = SketchController.checkName(newName);
+
+ File newFolder;
+ // User may want to overwrite a .ino
+ // check if the parent folder name ends with the sketch name
+ if (newName.endsWith(".ino") && newParentDir.endsWith(newName.substring(0, newName.lastIndexOf('.'))+ File.separator)) {
+ newFolder = new File(newParentDir);
+ } else {
+ newFolder = new File(newParentDir, newName);
+ }
+
+ // check if the paths are identical
+ if (newFolder.equals(sketch.getFolder())) {
+ // just use "save" here instead, because the user will have received a
+ // message (from the operating system) about "do you want to replace?"
+ return save();
+ }
+
+ // check to see if the user is trying to save this sketch inside itself
+ try {
+ String newPath = newFolder.getCanonicalPath() + File.separator;
+ String oldPath = sketch.getFolder().getCanonicalPath() + File.separator;
+
+ if (newPath.indexOf(oldPath) == 0) {
+ Base.showWarning(tr("How very Borges of you"),
+ tr("You cannot save the sketch into a folder\n" +
+ "inside itself. This would go on forever."), null);
+ return false;
+ }
+ } catch (IOException e) {
+ //ignore
+ }
+
+ // if the new folder already exists, then need to remove
+ // its contents before copying everything over
+ // (user will have already been warned)
+ if (newFolder.exists()) {
+ FileUtils.recursiveDelete(newFolder);
+ }
+ // in fact, you can't do this on windows because the file dialog
+ // will instead put you inside the folder, but it happens on osx a lot.
+
+ try {
+ sketch.saveAs(newFolder);
+ } catch (IOException e) {
+ // This does not pass on e, to prevent showing a backtrace for "normal"
+ // errors.
+ Base.showWarning(tr("Error"), e.getMessage(), null);
+ }
+ // Name changed, rebuild the sketch menus
+ //editor.sketchbook.rebuildMenusAsync();
+ editor.base.rebuildSketchbookMenus();
+ editor.header.rebuild();
+ editor.updateTitle();
+ // Make sure that it's not an untitled sketch
+ setUntitled(false);
+
+ // let Editor know that the save was successful
+ return true;
+ }
+
+
+ /**
+ * Prompt the user for a new file to the sketch, then call the
+ * other addFile() function to actually add it.
+ */
+ public void handleAddFile() {
+ // make sure the user didn't hide the sketch folder
+ ensureExistence();
+
+ // if read-only, give an error
+ if (isReadOnly()) {
+ // if the files are read-only, need to first do a "save as".
+ Base.showMessage(tr("Sketch is Read-Only"),
+ tr("Some files are marked \"read-only\", so you'll\n" +
+ "need to re-save the sketch in another location,\n" +
+ "and try again."));
+ return;
+ }
+
+ // get a dialog, select a file to add to the sketch
+ FileDialog fd = new FileDialog(editor, tr("Select an image or other data file to copy to your sketch"), FileDialog.LOAD);
+ fd.setVisible(true);
+
+ String directory = fd.getDirectory();
+ String filename = fd.getFile();
+ if (filename == null) return;
+
+ // copy the file into the folder. if people would rather
+ // it move instead of copy, they can do it by hand
+ File sourceFile = new File(directory, filename);
+
+ // now do the work of adding the file
+ boolean result = addFile(sourceFile);
+
+ if (result) {
+ editor.statusNotice(tr("One file added to the sketch."));
+ PreferencesData.set("last.folder", sourceFile.getAbsolutePath());
+ }
+ }
+
+
+ /**
+ * Add a file to the sketch.
+ *
+ * Supported code files will be copied to the sketch folder. All other files
+ * will be copied to the "data" folder (which is created if it does not exist
+ * yet).
+ *
+ * @return true if successful.
+ */
+ public boolean addFile(File sourceFile) {
+ String filename = sourceFile.getName();
+ File destFile = null;
+ boolean isData = false;
+ boolean replacement = false;
+
+ if (FileUtils.hasExtension(sourceFile, Sketch.EXTENSIONS)) {
+ destFile = new File(sketch.getFolder(), filename);
+ } else {
+ sketch.prepareDataFolder();
+ destFile = new File(sketch.getDataFolder(), filename);
+ isData = true;
+ }
+
+ if (!sourceFile.equals(destFile)) {
+ // The typical case here is adding a file from somewhere else.
+ // This however fails if the source and destination are equal
+
+ // check whether this file already exists
+ if (destFile.exists()) {
+ Object[] options = { tr("OK"), tr("Cancel") };
+ String prompt = I18n.format(tr("Replace the existing version of {0}?"), filename);
+ int result = JOptionPane.showOptionDialog(editor,
+ prompt,
+ tr("Replace"),
+ JOptionPane.YES_NO_OPTION,
+ JOptionPane.QUESTION_MESSAGE,
+ null,
+ options,
+ options[0]);
+ if (result == JOptionPane.YES_OPTION) {
+ replacement = true;
+ } else {
+ return false;
+ }
+ }
+
+ // If it's a replacement, delete the old file first,
+ // otherwise case changes will not be preserved.
+ // http://dev.processing.org/bugs/show_bug.cgi?id=969
+ if (replacement) {
+ if (!destFile.delete()) {
+ Base.showWarning(tr("Error adding file"),
+ I18n.format(tr("Could not delete the existing ''{0}'' file."), filename),
+ null);
+ return false;
+ }
+ }
+
+ // perform the copy
+ try {
+ Base.copyFile(sourceFile, destFile);
+
+ } catch (IOException e) {
+ Base.showWarning(tr("Error adding file"),
+ I18n.format(tr("Could not add ''{0}'' to the sketch."), filename),
+ e);
+ return false;
+ }
+ }
+ else {
+ // If the source and destination are equal, a code file is handled
+ // - as a replacement, if there is a corresponding tab,
+ // (eg. user wants to update the file after modifying it outside the editor)
+ // - as an addition, otherwise.
+ // (eg. the user copied the file to the sketch folder and wants to edit it)
+ // For a data file, this is a no-op.
+ if (editor.findTabIndex(destFile) >= 0)
+ replacement = true;
+ }
+
+ // open/refresh the tab
+ if (!isData) {
+ int tabIndex;
+ if (replacement) {
+ tabIndex = editor.findTabIndex(destFile);
+ editor.getTabs().get(tabIndex).reload();
+ } else {
+ SketchFile sketchFile;
+ try {
+ sketchFile = sketch.addFile(destFile.getName());
+ editor.addTab(sketchFile, null);
+ } catch (IOException e) {
+ // This does not pass on e, to prevent showing a backtrace for
+ // "normal" errors.
+ Base.showWarning(tr("Error"), e.getMessage(), null);
+ return false;
+ }
+ tabIndex = editor.findTabIndex(sketchFile);
+ }
+ editor.selectTab(tabIndex);
+ }
+ return true;
+ }
+
+
+ /**
+ * Add import statements to the current tab for the specified library
+ */
+ public void importLibrary(UserLibrary lib) throws IOException {
+ // make sure the user didn't hide the sketch folder
+ ensureExistence();
+
+ List list = lib.getIncludes();
+ if (list == null) {
+ File srcFolder = lib.getSrcFolder();
+ String[] headers = Base.headerListFromIncludePath(srcFolder);
+ list = Arrays.asList(headers);
+ }
+ if (list.isEmpty()) {
+ return;
+ }
+
+ // import statements into the main sketch file (code[0])
+ // if the current code is a .java file, insert into current
+ //if (current.flavor == PDE) {
+ SketchFile file = editor.getCurrentTab().getSketchFile();
+ if (file.isExtension(Sketch.SKETCH_EXTENSIONS))
+ editor.selectTab(0);
+
+ // could also scan the text in the file to see if each import
+ // statement is already in there, but if the user has the import
+ // commented out, then this will be a problem.
+ StringBuilder buffer = new StringBuilder();
+ for (String aList : list) {
+ buffer.append("#include <");
+ buffer.append(aList);
+ buffer.append(">\n");
+ }
+ buffer.append('\n');
+ buffer.append(editor.getCurrentTab().getText());
+ editor.getCurrentTab().setText(buffer.toString());
+ editor.getCurrentTab().setSelection(0, 0); // scroll to start
+ }
+
+ /**
+ * Preprocess and compile all the code for this sketch.
+ *
+ * In an advanced program, the returned class name could be different,
+ * which is why the className is set based on the return value.
+ * A compilation error will burp up a RunnerException.
+ *
+ * @return null if compilation failed, main class name if not
+ */
+ public String build(boolean verbose, boolean save) throws RunnerException, PreferencesMapException, IOException {
+ // run the preprocessor
+ for (CompilerProgressListener progressListener : editor.status.getCompilerProgressListeners()){
+ progressListener.progress(20);
+ }
+
+ EditorConsole.setCurrentEditorConsole(editor.console);
+
+ ensureExistence();
+
+
+ boolean deleteTemp = false;
+ File pathToSketch = sketch.getPrimaryFile().getFile();
+ if (sketch.isModified()) {
+ // If any files are modified, make a copy of the sketch with the changes
+ // saved, so arduino-builder will see the modifications.
+ pathToSketch = saveSketchInTempFolder();
+ deleteTemp = true;
+ }
+
+ try {
+ return new Compiler(pathToSketch, sketch).build(editor.status.getCompilerProgressListeners(), save);
+ } finally {
+ // Make sure we clean up any temporary sketch copy
+ if (deleteTemp)
+ FileUtils.recursiveDelete(pathToSketch.getParentFile());
+ }
+ }
+
+ private File saveSketchInTempFolder() throws IOException {
+ File tempFolder = FileUtils.createTempFolder("arduino_modified_sketch_");
+ FileUtils.copy(sketch.getFolder(), tempFolder);
+
+ for (SketchFile file : Stream.of(sketch.getFiles()).filter(SketchFile::isModified).collect(Collectors.toList())) {
+ Files.write(Paths.get(tempFolder.getAbsolutePath(), file.getFileName()), file.getProgram().getBytes("UTF-8"));
+ }
+
+ return Paths.get(tempFolder.getAbsolutePath(), sketch.getPrimaryFile().getFileName()).toFile();
+ }
+
+ /**
+ * Handle export to applet.
+ */
+ protected boolean exportApplet(boolean usingProgrammer) throws Exception {
+ // build the sketch
+ editor.status.progressNotice(tr("Compiling sketch..."));
+ String foundName = build(false, false);
+ // (already reported) error during export, exit this function
+ if (foundName == null) return false;
+
+// // If name != exportSketchName, then that's weirdness
+// // BUG unfortunately, that can also be a bug in the preproc :(
+// if (!name.equals(foundName)) {
+// Base.showWarning("Error during export",
+// "Sketch name is " + name + " but the sketch\n" +
+// "name in the code was " + foundName, null);
+// return false;
+// }
+
+ editor.status.progressNotice(tr("Uploading..."));
+ boolean success = upload(foundName, usingProgrammer);
+ editor.status.progressUpdate(100);
+ return success;
+ }
+
+ private boolean upload(String suggestedClassName, boolean usingProgrammer) throws Exception {
+
+ UploaderUtils uploaderInstance = new UploaderUtils();
+ Uploader uploader = uploaderInstance.getUploaderByPreferences(false);
+
+ EditorConsole.setCurrentEditorConsole(editor.console);
+
+ boolean success = false;
+ do {
+ if (uploader.requiresAuthorization() && !PreferencesData.has(uploader.getAuthorizationKey())) {
+ PasswordAuthorizationDialog dialog = new PasswordAuthorizationDialog(editor, tr("Type board password to upload a new sketch"));
+ dialog.setLocationRelativeTo(editor);
+ dialog.setVisible(true);
+
+ if (dialog.isCancelled()) {
+ editor.statusNotice(tr("Upload cancelled"));
+ return false;
+ }
+
+ PreferencesData.set(uploader.getAuthorizationKey(), dialog.getPassword());
+ }
+
+ List warningsAccumulator = new LinkedList<>();
+ try {
+ success = uploaderInstance.upload(sketch, uploader, suggestedClassName, usingProgrammer, false, warningsAccumulator);
+ } finally {
+ if (uploader.requiresAuthorization() && !success) {
+ PreferencesData.remove(uploader.getAuthorizationKey());
+ }
+ }
+
+ for (String warning : warningsAccumulator) {
+ System.out.print(tr("Warning"));
+ System.out.print(": ");
+ System.out.println(warning);
+ }
+
+ } while (uploader.requiresAuthorization() && !success);
+
+ if (!success) {
+ String errorMessage = uploader.getFailureMessage();
+ if (errorMessage.equals("")) {
+ errorMessage = tr("An error occurred while uploading the sketch");
+ }
+ editor.statusError(errorMessage);
+ }
+
+ return success;
+ }
+
+ /**
+ * Make sure the sketch hasn't been moved or deleted by some
+ * nefarious user. If they did, try to re-create it and save.
+ * Only checks to see if the main folder is still around,
+ * but not its contents.
+ */
+ private void ensureExistence() {
+ if (sketch.getFolder().exists()) return;
+
+ Base.showWarning(tr("Sketch Disappeared"),
+ tr("The sketch folder has disappeared.\n " +
+ "Will attempt to re-save in the same location,\n" +
+ "but anything besides the code will be lost."), null);
+ try {
+ sketch.getFolder().mkdirs();
+
+ for (SketchFile file : sketch.getFiles()) {
+ file.save(); // this will force a save
+ }
+ calcModified();
+
+ } catch (Exception e) {
+ Base.showWarning(tr("Could not re-save sketch"),
+ tr("Could not properly re-save the sketch. " +
+ "You may be in trouble at this point,\n" +
+ "and it might be time to copy and paste " +
+ "your code to another text editor."), e);
+ }
+ }
+
+
+ /**
+ * Returns true if this is a read-only sketch. Used for the
+ * examples directory, or when sketches are loaded from read-only
+ * volumes or folders without appropriate permissions.
+ */
+ public boolean isReadOnly() {
+ LibraryList libraries = BaseNoGui.librariesIndexer.getInstalledLibraries();
+ String examplesPath = BaseNoGui.getExamplesPath();
+ String apath = sketch.getFolder().getAbsolutePath();
+
+ Optional libraryThatIncludesSketch = libraries.stream().filter(lib -> apath.startsWith(lib.getInstalledFolder().getAbsolutePath())).findFirst();
+ if (libraryThatIncludesSketch.isPresent() && !libraryThatIncludesSketch.get().onGoingDevelopment()) {
+ return true;
+ }
+
+ return sketchIsSystemExample(apath, examplesPath) || sketchFilesAreReadOnly();
+ }
+
+ private boolean sketchIsSystemExample(String apath, String examplesPath) {
+ return apath.startsWith(examplesPath);
+ }
+
+ private boolean sketchFilesAreReadOnly() {
+ for (SketchFile file : sketch.getFiles()) {
+ if (file.isModified() && file.fileReadOnly() && file.fileExists()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
+
+
+
+ private void setUntitled(boolean u) {
+ editor.untitled = u;
+ }
+
+
+ public boolean isUntitled() {
+ return editor.untitled;
+ }
+
+ public Sketch getSketch() {
+ return sketch;
+ }
+
+ // .................................................................
+
+
+ /**
+ * Convert to sanitized name and alert the user
+ * if changes were made.
+ */
+ private static String checkName(String origName) {
+ String newName = BaseNoGui.sanitizeName(origName);
+
+ if (!newName.equals(origName)) {
+ String msg =
+ tr("The sketch name had to be modified.\n" +
+ "Sketch names must start with a letter or number, followed by letters,\n" +
+ "numbers, dashes, dots and underscores. Maximum length is 63 characters.");
+ System.out.println(msg);
+ }
+ return newName;
+ }
+
+
+}
diff --git a/app/src/processing/app/TextAreaFIFO.java b/app/src/processing/app/TextAreaFIFO.java
new file mode 100644
index 00000000000..abf953dfd93
--- /dev/null
+++ b/app/src/processing/app/TextAreaFIFO.java
@@ -0,0 +1,90 @@
+/*
+ Copyright (c) 2014 Paul Stoffregen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+// adapted from https://community.oracle.com/thread/1479784
+
+package processing.app;
+
+import javax.swing.JTextArea;
+import javax.swing.SwingUtilities;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+import javax.swing.text.BadLocationException;
+
+public class TextAreaFIFO extends JTextArea implements DocumentListener {
+ private int maxChars;
+ private int trimMaxChars;
+
+ private int updateCount; // limit how often we trim the document
+
+ private boolean doTrim;
+
+ public TextAreaFIFO(int max) {
+ maxChars = max;
+ trimMaxChars = max / 2;
+ updateCount = 0;
+ doTrim = true;
+ getDocument().addDocumentListener(this);
+ }
+
+ public void insertUpdate(DocumentEvent e) {
+ if (++updateCount > 150 && doTrim) {
+ updateCount = 0;
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ trimDocument();
+ }
+ });
+ }
+ }
+
+ public void removeUpdate(DocumentEvent e) {
+ }
+
+ public void changedUpdate(DocumentEvent e) {
+ }
+
+ public void trimDocument() {
+ int len = 0;
+ len = getDocument().getLength();
+ if (len > trimMaxChars) {
+ int n = len - trimMaxChars;
+ //System.out.println("trimDocument: remove " + n + " chars");
+ try {
+ getDocument().remove(0, n);
+ } catch (BadLocationException ble) {
+ }
+ }
+ }
+
+ public void appendNoTrim(String s) {
+ int free = maxChars - getDocument().getLength();
+ if (free <= 0)
+ return;
+ if (s.length() > free)
+ append(s.substring(0, free));
+ else
+ append(s);
+ doTrim = false;
+ }
+
+ public void appendTrim(String str) {
+ append(str);
+ doTrim = true;
+ }
+}
diff --git a/app/src/processing/app/Theme.java b/app/src/processing/app/Theme.java
index 8b68f4f45e6..d38875b3597 100644
--- a/app/src/processing/app/Theme.java
+++ b/app/src/processing/app/Theme.java
@@ -1,5 +1,3 @@
-/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
-
/*
Part of the Processing project - http://processing.org
@@ -23,13 +21,54 @@
package processing.app;
-import java.awt.*;
-import java.io.*;
-import java.util.*;
-
-import processing.app.syntax.*;
-import processing.core.*;
-
+import static processing.app.I18n.format;
+import static processing.app.I18n.tr;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Image;
+import java.awt.MediaTracker;
+import java.awt.Rectangle;
+import java.awt.RenderingHints;
+import java.awt.SystemColor;
+import java.awt.Toolkit;
+import java.awt.font.TextAttribute;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.Properties;
+import java.util.TreeMap;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+import javax.swing.text.StyleContext;
+
+import org.apache.batik.transcoder.Transcoder;
+import org.apache.batik.transcoder.TranscoderException;
+import org.apache.batik.transcoder.TranscoderInput;
+import org.apache.batik.transcoder.TranscoderOutput;
+import org.apache.batik.transcoder.image.PNGTranscoder;
+import org.apache.commons.compress.utils.IOUtils;
+import org.apache.commons.lang3.StringUtils;
+import processing.app.helpers.OSUtils;
+import processing.app.helpers.PreferencesHelper;
+import processing.app.helpers.PreferencesMap;
/**
* Storage class for theme settings. This was separated from the Preferences
@@ -37,166 +76,652 @@
* and to make way for future ability to customize.
*/
public class Theme {
+
+ static final String THEME_DIR = "theme/";
+ static final String THEME_FILE_NAME = "theme.txt";
+
+ static final String NAMESPACE_APP = "app:";
+ static final String NAMESPACE_USER = "user:";
+
+ /**
+ * A theme resource, this is returned instead of {@link File} so that we can
+ * support zip-packaged resources as well as files in the file system
+ */
+ public static class Resource {
+
+ // Priority levels used to determine whether one resource should override
+ // another
+ static public final int PRIORITY_DEFAULT = 0;
+ static public final int PRIORITY_USER_ZIP = 1;
+ static public final int PRIORITY_USER_FILE = 2;
+
+ /**
+ * Priority of this resource.
+ */
+ private final int priority;
+
+ /**
+ * Resource name (original name of requested resource, relative path only).
+ */
+ private final String name;
+
+ /**
+ * File if this resource represents a file, can be null.
+ */
+ private final File file;
+
+ /**
+ * Zip theme if the resource is contained within a zipped theme
+ */
+ private final ZippedTheme theme;
+
+ /**
+ * Zip entry if this resource represents a zip entry, can be null.
+ */
+ private final ZipEntry zipEntry;
+
+ /**
+ * URL of this resource regardless of type, theoretically shouldn't ever be
+ * null though it might be if a particular resource path can't be
+ * successfully transformed into a URL (eg. {@link Theme#getUrl} traps a
+ * MalformedURLException).
+ */
+ private final URL url;
+
+ /**
+ * If this resource supercedes a resource with a lower priority, this field
+ * stores a reference to the superceded resource. This allows consumers to
+ * traverse the resource hierarchy if required.
+ */
+ private Resource parent;
+
+ /**
+ * ctor for file resources
+ */
+ Resource(int priority, String name, URL url, File file) {
+ this(priority, name, url, file, null, null);
+ }
+
+ /**
+ * ctor for zip resources
+ */
+ Resource(int priority, String name, URL url, ZippedTheme theme, ZipEntry entry) {
+ this(priority, name, url, null, theme, entry);
+ }
- /** Copy of the defaults in case the user mangles a preference. */
- static HashMap defaults;
- /** Table of attributes/values for the theme. */
- static HashMap table = new HashMap();;
-
+ private Resource(int priority, String name, URL url, File file, ZippedTheme theme, ZipEntry zipEntry) {
+ this.priority = priority;
+ this.name = name;
+ this.file = file;
+ this.theme = theme;
+ this.zipEntry = zipEntry;
+ this.url = url;
+ }
+
+ public Resource getParent() {
+ return this.parent;
+ }
+
+ public String getName() {
+ return this.name;
+ }
+
+ public URL getUrl() {
+ return this.url;
+ }
+
+ public int getPriority() {
+ return this.priority;
+ }
+
+ public boolean isUserDefined() {
+ return this.priority > PRIORITY_DEFAULT;
+ }
+
+ public boolean exists() {
+ return this.zipEntry != null || this.file == null || this.file.exists();
+ }
+
+ public InputStream getInputStream() throws IOException {
+ if (this.file != null) {
+ return new FileInputStream(this.file);
+ }
+
+ if (this.zipEntry != null) {
+ return this.theme.getZip().getInputStream(this.zipEntry);
+ }
+
+ if (this.url != null) {
+ return this.url.openStream();
+ }
+
+ throw new FileNotFoundException(this.name);
+ }
+
+ public String toString() {
+ return this.name;
+ }
+
+ Resource withParent(Resource parent) {
+ this.parent = parent;
+ return this;
+ }
+ }
+
+ /**
+ * Struct which keeps information about a discovered .zip theme file
+ */
+ public static class ZippedTheme {
+
+ /**
+ * Configuration key, this key consists of a "namespace" which determines
+ * the root folder the theme was found in without actually storing the path
+ * itself, followed by the file name.
+ */
+ private final String key;
+
+ /**
+ * File containing the theme
+ */
+ private final File file;
+
+ /**
+ * Zip file handle for retrieving entries
+ */
+ private final ZipFile zip;
+
+ /**
+ * Display name, defaulted to filename but can be read from metadata
+ */
+ private final String name;
+
+ /**
+ * Version number, plain text string read from metadata
+ */
+ private final String version;
+
+ private ZippedTheme(String namespace, File file, ZipFile zip, String name, String version) {
+ this.key = namespace + file.getName();
+ this.file = file;
+ this.zip = zip;
+ this.name = name;
+ this.version = version;
+ }
+
+ public String getKey() {
+ return this.key;
+ }
+
+ public File getFile() {
+ return this.file;
+ }
+
+ public ZipFile getZip() {
+ return this.zip;
+ }
+
+ public String getName() {
+ return this.name;
+ }
+
+ public String getVersion() {
+ return this.version;
+ }
+
+ public String toString() {
+ String description = String.format("%s %s (%s)", this.getName(), this.getVersion(), this.file.getName());
+ return StringUtils.abbreviate(description, 40);
+ }
+
+ /**
+ * Attempts to parse the supplied zip file as a theme file. This is largely
+ * determined by the file being readable and containing a theme.txt entry.
+ * Returns null if the file is unreadable or doesn't contain theme.txt
+ */
+ static ZippedTheme load(String namespace, File file) {
+ ZipFile zip = null;
+ try {
+ zip = new ZipFile(file);
+ ZipEntry themeTxtEntry = zip.getEntry(THEME_FILE_NAME);
+ if (themeTxtEntry != null) {
+ String name = file.getName().substring(0, file.getName().length() - 4);
+ String version = "";
+
+ ZipEntry themePropsEntry = zip.getEntry("theme.properties");
+ if (themePropsEntry != null) {
+ Properties themeProperties = new Properties();
+ themeProperties.load(zip.getInputStream(themePropsEntry));
+
+ name = themeProperties.getProperty("name", name);
+ version = themeProperties.getProperty("version", version);
+ }
+
+ return new ZippedTheme(namespace, file, zip, name, version);
+ }
+ } catch (Exception ex) {
+ System.err.println(format(tr("Error loading theme {0}: {1}"),
+ file.getAbsolutePath(), ex.getMessage()));
+ IOUtils.closeQuietly(zip);
+ }
+
+ return null;
+ }
+
+ }
+ /**
+ * Copy of the defaults in case the user mangles a preference.
+ */
+ static PreferencesMap defaults;
+ /**
+ * Table of attributes/values for the theme.
+ */
+ static PreferencesMap table = new PreferencesMap();
+
+ /**
+ * Available zipped themes
+ */
+ static private final Map availableThemes = new TreeMap<>();
+
+ /**
+ * Zip file containing user-defined theme elements
+ */
+ static private ZippedTheme zipTheme;
+
static protected void init() {
+ zipTheme = openZipTheme();
+
try {
- load(Base.getLibStream("theme/theme.txt"));
+ loadFromResource(table, THEME_DIR + THEME_FILE_NAME);
} catch (Exception te) {
- Base.showError(null, "Could not read color theme settings.\n" +
- "You'll need to reinstall Processing.", te);
- }
-
- // check for platform-specific properties in the defaults
- String platformExt = "." + Base.getPlatformName();
- int platformExtLength = platformExt.length();
- for (String key : table.keySet()) {
- if (key.endsWith(platformExt)) {
- // this is a key specific to a particular platform
- String actualKey = key.substring(0, key.length() - platformExtLength);
- String value = get(key);
- table.put(actualKey, value);
- }
+ Base.showError(null, tr("Could not read color theme settings.\n"
+ + "You'll need to reinstall Arduino."),
+ te);
}
// other things that have to be set explicitly for the defaults
setColor("run.window.bgcolor", SystemColor.control);
// clone the hash table
- defaults = (HashMap) table.clone();
+ defaults = new PreferencesMap(table);
+ }
+
+ static private ZippedTheme openZipTheme() {
+ refreshAvailableThemes();
+ String selectedTheme = PreferencesData.get("theme.file", "");
+ synchronized(availableThemes) {
+ return availableThemes.get(selectedTheme);
+ }
+ }
+
+ static private void refreshAvailableThemes() {
+ Map discoveredThemes = new TreeMap<>();
+
+ refreshAvailableThemes(discoveredThemes, NAMESPACE_APP, new File(BaseNoGui.getContentFile("lib"), THEME_DIR));
+ refreshAvailableThemes(discoveredThemes, NAMESPACE_USER, new File(BaseNoGui.getSketchbookFolder(), THEME_DIR));
+
+ synchronized (availableThemes) {
+ availableThemes.clear();
+ availableThemes.putAll(discoveredThemes);
+ }
}
-
- static protected void load(InputStream input) throws IOException {
- String[] lines = PApplet.loadStrings(input);
- for (String line : lines) {
- if ((line.length() == 0) ||
- (line.charAt(0) == '#')) continue;
-
- // this won't properly handle = signs being in the text
- int equals = line.indexOf('=');
- if (equals != -1) {
- String key = line.substring(0, equals).trim();
- String value = line.substring(equals + 1).trim();
- table.put(key, value);
+ static private void refreshAvailableThemes(Map discoveredThemes, String namespace, File folder) {
+ if (!folder.isDirectory()) {
+ return;
+ }
+
+ for (File zipFile : folder.listFiles((dir, name) -> name.endsWith(".zip"))) {
+ ZippedTheme theme = ZippedTheme.load(namespace, zipFile);
+ if (theme != null) {
+ discoveredThemes.put(theme.getKey(), theme);
}
}
}
-
+
+ public static Collection getAvailablethemes() {
+ refreshAvailableThemes();
+ return Collections.unmodifiableCollection(availableThemes.values());
+ }
static public String get(String attribute) {
- return (String) table.get(attribute);
+ return table.get(attribute);
}
-
static public String getDefault(String attribute) {
- return (String) defaults.get(attribute);
+ return defaults.get(attribute);
}
-
static public void set(String attribute, String value) {
table.put(attribute, value);
}
-
static public boolean getBoolean(String attribute) {
- String value = get(attribute);
- return (new Boolean(value)).booleanValue();
+ return table.getBoolean(attribute);
}
-
static public void setBoolean(String attribute, boolean value) {
- set(attribute, value ? "true" : "false");
+ table.putBoolean(attribute, value);
}
-
static public int getInteger(String attribute) {
return Integer.parseInt(get(attribute));
}
-
static public void setInteger(String key, int value) {
set(key, String.valueOf(value));
}
-
- static public Color getColor(String name) {
- Color parsed = null;
- String s = get(name);
- if ((s != null) && (s.indexOf("#") == 0)) {
- try {
- int v = Integer.parseInt(s.substring(1), 16);
- parsed = new Color(v);
- } catch (Exception e) {
- }
+ static public int getScale() {
+ try {
+ int scale = PreferencesData.getInteger("gui.scale", -1);
+ if (scale != -1)
+ return scale;
+ } catch (NumberFormatException ignore) {
}
- return parsed;
+ return BaseNoGui.getPlatform().getSystemDPI() * 100 / 96;
+ }
+
+ static public int scale(int size) {
+ return size * getScale() / 100;
+ }
+
+ static public Dimension scale(Dimension dim) {
+ return new Dimension(scale(dim.width), scale(dim.height));
+ }
+
+ static public Font scale(Font font) {
+ float size = scale(font.getSize());
+ // size must be float to call the correct Font.deriveFont(float)
+ // method that is different from Font.deriveFont(int)!
+ Font scaled = font.deriveFont(size);
+ return scaled;
+ }
+
+ static public Rectangle scale(Rectangle rect) {
+ Rectangle res = new Rectangle(rect);
+ res.x = scale(res.x);
+ res.y = scale(res.y);
+ res.width = scale(res.width);
+ res.height = scale(res.height);
+ return res;
}
+ static public Color getColorCycleColor(String name, int i) {
+ int cycleSize = getInteger(name + ".size");
+ name = String.format("%s.%02d", name, i % cycleSize);
+ return PreferencesHelper.parseColor(get(name));
+ }
+
+ static public void setColorCycleColor(String name, int i, Color color) {
+ name = String.format("%s.%02d", name, i);
+ PreferencesHelper.putColor(table, name, color);
+ int cycleSize = getInteger(name + ".size");
+ setInteger(name + ".size", (i + 1) > cycleSize ? (i + 1) : cycleSize);
+ }
- static public void setColor(String attr, Color what) {
- set(attr, "#" + PApplet.hex(what.getRGB() & 0xffffff, 6));
+ static public Color getColor(String name) {
+ return PreferencesHelper.parseColor(get(name));
}
+ static public void setColor(String attr, Color color) {
+ PreferencesHelper.putColor(table, attr, color);
+ }
static public Font getFont(String attr) {
- boolean replace = false;
- String value = get(attr);
- if (value == null) {
- //System.out.println("reset 1");
- value = getDefault(attr);
- replace = true;
- }
-
- String[] pieces = PApplet.split(value, ',');
- if (pieces.length != 3) {
- value = getDefault(attr);
- //System.out.println("reset 2 for " + attr);
- pieces = PApplet.split(value, ',');
- //PApplet.println(pieces);
- replace = true;
- }
-
- String name = pieces[0];
- int style = Font.PLAIN; // equals zero
- if (pieces[1].indexOf("bold") != -1) {
- style |= Font.BOLD;
- }
- if (pieces[1].indexOf("italic") != -1) {
- style |= Font.ITALIC;
- }
- int size = PApplet.parseInt(pieces[2], 12);
- Font font = new Font(name, style, size);
-
- // replace bad font with the default
- if (replace) {
- //System.out.println(attr + " > " + value);
- //setString(attr, font.getName() + ",plain," + font.getSize());
+ Font font = PreferencesHelper.getFont(table, attr);
+ if (font == null) {
+ String value = getDefault(attr);
set(attr, value);
+ font = PreferencesHelper.getFont(table, attr);
+ if (font == null) {
+ return null;
+ }
+ }
+ return font.deriveFont((float) scale(font.getSize()));
+ }
+
+ /**
+ * Returns the default font for text areas.
+ *
+ * @return The default font.
+ */
+ public static final Font getDefaultFont() {
+
+ // Use StyleContext to get a composite font for better Asian language
+ // support; see Sun bug S282887.
+ StyleContext sc = StyleContext.getDefaultStyleContext();
+ Font font = null;
+
+ if (OSUtils.isMacOS()) {
+ // Snow Leopard (1.6) uses Menlo as default monospaced font,
+ // pre-Snow Leopard used Monaco.
+ font = sc.getFont("Menlo", Font.PLAIN, 12);
+ if (!"Menlo".equals(font.getFamily())) {
+ font = sc.getFont("Monaco", Font.PLAIN, 12);
+ if (!"Monaco".equals(font.getFamily())) { // Shouldn't happen
+ font = sc.getFont("Monospaced", Font.PLAIN, 13);
+ }
+ }
+ } else {
+ // Consolas added in Vista, used by VS2010+.
+ font = sc.getFont("Consolas", Font.PLAIN, 13);
+ if (!"Consolas".equals(font.getFamily())) {
+ font = sc.getFont("Monospaced", Font.PLAIN, 13);
+ }
}
+ // System.out.println(font.getFamily() + ", " + font.getName());
return font;
}
+ public static Map getStyledFont(String what, Font font) {
+ String split[] = get("editor." + what + ".style").split(",");
+
+ Color color = PreferencesHelper.parseColor(split[0]);
+
+ String style = split[1];
+ boolean bold = style.contains("bold");
+ boolean italic = style.contains("italic");
+ boolean underlined = style.contains("underlined");
+
+ Font styledFont = new Font(font.getFamily(),
+ (bold ? Font.BOLD : 0) | (italic ? Font.ITALIC : 0), font.getSize());
+ if (underlined) {
+ Map attr = new Hashtable<>();
+ attr.put(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON);
+ styledFont = styledFont.deriveFont(attr);
+ }
+
+ Map result = new HashMap<>();
+ result.put("color", color);
+ result.put("font", styledFont);
+
+ return result;
+ }
+
+ /**
+ * Return an Image object from inside the Processing lib folder.
+ */
+ static public Image getLibImage(String filename, Component who, int width,
+ int height) {
+ Image image = null;
+
+ // Use vector image when available
+ Resource vectorFile = getThemeResource(filename + ".svg");
+ if (vectorFile.exists()) {
+ try {
+ image = imageFromSVG(vectorFile.getUrl(), width, height);
+ } catch (Exception e) {
+ System.err.println("Failed to load " + vectorFile + ": " + e.getMessage());
+ }
+ }
+
+ Resource bitmapFile = getThemeResource(filename + ".png");
+
+ // Otherwise fall-back to PNG bitmaps, allowing user-defined bitmaps to
+ // override built-in svgs
+ if (image == null || bitmapFile.getPriority() > vectorFile.getPriority()) {
+ Resource bitmap2xFile = getThemeResource(filename + "@2x.png");
+
+ Resource imageFile;
+ if (((getScale() > 125 && bitmap2xFile.exists()) || !bitmapFile.exists())
+ && (bitmapFile.isUserDefined() && bitmap2xFile.isUserDefined())) {
+ imageFile = bitmap2xFile;
+ } else {
+ imageFile = bitmapFile;
+ }
+ Toolkit tk = Toolkit.getDefaultToolkit();
+ image = tk.getImage(imageFile.getUrl());
+ }
+
+ MediaTracker tracker = new MediaTracker(who);
+ try {
+ tracker.addImage(image, 0);
+ tracker.waitForAll();
+ } catch (InterruptedException e) {
+ }
+
+ if (image.getWidth(null) != width || image.getHeight(null) != height) {
+ image = image.getScaledInstance(width, height, Image.SCALE_SMOOTH);
+ try {
+ tracker.addImage(image, 1);
+ tracker.waitForAll();
+ } catch (InterruptedException e) {
+ }
+ }
+
+ return image;
+ }
+
+ /**
+ * Get an image associated with the current color theme.
+ */
+ static public Image getThemeImage(String name, Component who, int width,
+ int height) {
+ return getLibImage(THEME_DIR + name, who, width, height);
+ }
+
+ private static Image imageFromSVG(URL url, int width, int height)
+ throws TranscoderException {
+ Transcoder t = new PNGTranscoder();
+ t.addTranscodingHint(PNGTranscoder.KEY_WIDTH, new Float(width));
+ t.addTranscodingHint(PNGTranscoder.KEY_HEIGHT, new Float(height));
- static public SyntaxStyle getStyle(String what) {
- String str = get("editor." + what + ".style");
+ TranscoderInput input = new TranscoderInput(url.toString());
+ ByteArrayOutputStream ostream = new ByteArrayOutputStream();
+ TranscoderOutput output = new TranscoderOutput(ostream);
+ t.transcode(input, output);
- StringTokenizer st = new StringTokenizer(str, ",");
+ byte[] imgData = ostream.toByteArray();
+ return Toolkit.getDefaultToolkit().createImage(imgData);
+ }
- String s = st.nextToken();
- if (s.indexOf("#") == 0) s = s.substring(1);
- Color color = new Color(Integer.parseInt(s, 16));
+ static public Graphics2D setupGraphics2D(Graphics graphics) {
+ Graphics2D g = (Graphics2D) graphics;
+ if (PreferencesData.getBoolean("editor.antialias")) {
+ g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
+ RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
+ }
+ return g;
+ }
+
+ /**
+ * Loads the supplied {@link PreferencesMap} from the specified resource,
+ * recursively loading parent resources such that entries are loaded in order
+ * of priority (lowest first).
+ *
+ * @param map preference map to populate
+ * @param name name of resource to load
+ */
+ static public PreferencesMap loadFromResource(PreferencesMap map, String name) throws IOException {
+ return loadFromResource(map, getThemeResource(name));
+ }
- s = st.nextToken();
- boolean bold = (s.indexOf("bold") != -1);
- boolean italic = (s.indexOf("italic") != -1);
+ static private PreferencesMap loadFromResource(PreferencesMap map, Resource resource) throws IOException {
+ if (resource != null) {
+ loadFromResource(map, resource.getParent());
+ map.load(resource.getInputStream());
+ }
+ return map;
+ }
- return new SyntaxStyle(color, italic, bold);
+ /**
+ * @param name
+ * @return
+ */
+ static public Resource getThemeResource(String name) {
+ File defaultfile = getDefaultFile(name);
+ Resource resource = new Resource(Resource.PRIORITY_DEFAULT, name, getUrl(defaultfile), defaultfile);
+
+ ZipEntry themeZipEntry = getThemeZipEntry(name);
+ if (themeZipEntry != null) {
+ resource = new Resource(Resource.PRIORITY_USER_ZIP, name, getUrl(themeZipEntry), zipTheme, themeZipEntry).withParent(resource);
+ }
+
+ File themeFile = getThemeFile(name);
+ if (themeFile != null) {
+ resource = new Resource(Resource.PRIORITY_USER_FILE, name, getUrl(themeFile), themeFile).withParent(resource);
+ }
+
+ return resource;
+ }
+
+ static private File getThemeFile(String name) {
+ File sketchBookThemeFolder = new File(BaseNoGui.getSketchbookFolder(), THEME_DIR);
+ File themeFile = new File(sketchBookThemeFolder, name);
+ if (themeFile.exists()) {
+ return themeFile;
+ }
+
+ if (name.startsWith(THEME_DIR)) {
+ themeFile = new File(sketchBookThemeFolder, name.substring(THEME_DIR.length()));
+ if (themeFile.exists()) {
+ return themeFile;
+ }
+ }
+
+ return null;
+ }
+
+ static private ZipEntry getThemeZipEntry(String name) {
+ if (zipTheme == null) {
+ return null;
+ }
+
+ if (name.startsWith(THEME_DIR)) {
+ name = name.substring(THEME_DIR.length());
+ }
+
+ return zipTheme.getZip().getEntry(name);
+ }
+
+ static private File getDefaultFile(String name) {
+ return new File(BaseNoGui.getContentFile("lib"), name);
+ }
+
+ static URL getUrl(File file) {
+ try {
+ return file.toURI().toURL();
+ } catch (MalformedURLException ex) {
+ return null;
+ }
+ }
+
+ static URL getUrl(ZipEntry entry) {
+ try {
+ // Adjust file name for URL format on Windows
+ String zipFile = zipTheme.getZip().getName().replace('\\', '/');
+ if (!zipFile.startsWith("/")) {
+ zipFile = "/" + zipFile;
+ }
+
+ // Construct a URL which points to the internal resource
+ URI uri = new URI("jar", "file:" + zipFile + "!/" + entry.getName(), null);
+ return uri.toURL();
+
+ } catch (MalformedURLException | URISyntaxException ex) {
+ return null;
+ }
}
-}
\ No newline at end of file
+}
diff --git a/app/src/processing/app/UpdateCheck.java b/app/src/processing/app/UpdateCheck.java
index b59c1a9107d..4c736e60413 100644
--- a/app/src/processing/app/UpdateCheck.java
+++ b/app/src/processing/app/UpdateCheck.java
@@ -22,16 +22,18 @@
package processing.app;
+import org.apache.commons.compress.utils.IOUtils;
+import processing.app.legacy.PApplet;
+
+import javax.swing.*;
import java.io.BufferedReader;
-import java.io.InputStream;
+import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLEncoder;
import java.util.Random;
-import javax.swing.JOptionPane;
-
-import processing.core.PApplet;
+import static processing.app.I18n.tr;
/**
@@ -49,7 +51,7 @@
*/
public class UpdateCheck implements Runnable {
Base base;
- String downloadURL = "http://www.arduino.cc/latest.txt";
+ String downloadURL = "https://www.arduino.cc/latest.txt";
static final long ONE_DAY = 24 * 60 * 60 * 1000;
@@ -64,21 +66,21 @@ public UpdateCheck(Base base) {
public void run() {
//System.out.println("checking for updates...");
- // generate a random id in case none exists yet
- Random r = new Random();
- long id = r.nextLong();
-
- String idString = Preferences.get("update.id");
+ long id;
+ String idString = PreferencesData.get("update.id");
if (idString != null) {
id = Long.parseLong(idString);
} else {
- Preferences.set("update.id", String.valueOf(id));
+ // generate a random id in case none exists yet
+ Random r = new Random();
+ id = r.nextLong();
+ PreferencesData.set("update.id", String.valueOf(id));
}
try {
String info;
info = URLEncoder.encode(id + "\t" +
- PApplet.nf(Base.REVISION, 4) + "\t" +
+ PApplet.nf(BaseNoGui.REVISION, 4) + "\t" +
System.getProperty("java.version") + "\t" +
System.getProperty("java.vendor") + "\t" +
System.getProperty("os.name") + "\t" +
@@ -87,7 +89,7 @@ public void run() {
int latest = readInt(downloadURL + "?" + info);
- String lastString = Preferences.get("update.last");
+ String lastString = PreferencesData.get("update.last");
long now = System.currentTimeMillis();
if (lastString != null) {
long when = Long.parseLong(lastString);
@@ -96,25 +98,25 @@ public void run() {
return;
}
}
- Preferences.set("update.last", String.valueOf(now));
+ PreferencesData.set("update.last", String.valueOf(now));
String prompt =
- "A new version of Arduino is available,\n" +
- "would you like to visit the Arduino download page?";
+ tr("A new version of Arduino is available,\n" +
+ "would you like to visit the Arduino download page?");
if (base.activeEditor != null) {
- if (latest > Base.REVISION) {
- Object[] options = { "Yes", "No" };
+ if (latest > BaseNoGui.REVISION) {
+ Object[] options = { tr("Yes"), tr("No") };
int result = JOptionPane.showOptionDialog(base.activeEditor,
prompt,
- "Update",
+ tr("Update"),
JOptionPane.YES_NO_OPTION,
JOptionPane.QUESTION_MESSAGE,
null,
options,
options[0]);
if (result == JOptionPane.YES_OPTION) {
- Base.openURL("http://www.arduino.cc/en/Main/Software");
+ Base.openURL("https://www.arduino.cc/en/software");
}
}
}
@@ -125,11 +127,14 @@ public void run() {
}
- protected int readInt(String filename) throws Exception {
+ protected int readInt(String filename) throws IOException {
URL url = new URL(filename);
- InputStream stream = url.openStream();
- InputStreamReader isr = new InputStreamReader(stream);
- BufferedReader reader = new BufferedReader(isr);
- return Integer.parseInt(reader.readLine());
+ BufferedReader reader = null;
+ try {
+ reader = new BufferedReader(new InputStreamReader(url.openStream()));
+ return Integer.parseInt(reader.readLine());
+ } finally {
+ IOUtils.closeQuietly(reader);
+ }
}
}
diff --git a/app/src/processing/app/WebServer.java b/app/src/processing/app/WebServer.java
deleted file mode 100644
index fc2089d8e22..00000000000
--- a/app/src/processing/app/WebServer.java
+++ /dev/null
@@ -1,573 +0,0 @@
-package processing.app;
-
-import java.io.*;
-import java.net.*;
-import java.util.*;
-import java.util.zip.*;
-
-//import javax.swing.SwingUtilities;
-
-/**
- * This code is placed here in anticipation of running the reference from an
- * internal web server that reads the docs from a zip file, instead of using
- * thousands of .html files on the disk, which is really inefficient.
- *
- * This is a very simple, multi-threaded HTTP server, originally based on
- * this article on java.sun.com.
- */
-public class WebServer implements HttpConstants {
-
- /* Where worker threads stand idle */
- static Vector threads = new Vector();
-
- /* the web server's virtual root */
- //static File root;
-
- /* timeout on client connections */
- static int timeout = 10000;
-
- /* max # worker threads */
- static int workers = 5;
-
-// static PrintStream log = System.out;
-
-
- /*
- static void loadProps() throws IOException {
- File f = new File
- (System.getProperty("java.home")+File.separator+
- "lib"+File.separator+"www-server.properties");
- if (f.exists()) {
- InputStream is =new BufferedInputStream(new
- FileInputStream(f));
- props.load(is);
- is.close();
- String r = props.getProperty("root");
- if (r != null) {
- root = new File(r);
- if (!root.exists()) {
- throw new Error(root + " doesn't exist as server root");
- }
- }
- r = props.getProperty("timeout");
- if (r != null) {
- timeout = Integer.parseInt(r);
- }
- r = props.getProperty("workers");
- if (r != null) {
- workers = Integer.parseInt(r);
- }
- r = props.getProperty("log");
- if (r != null) {
- p("opening log file: " + r);
- log = new PrintStream(new BufferedOutputStream(
- new FileOutputStream(r)));
- }
- }
-
- // if no properties were specified, choose defaults
- if (root == null) {
- root = new File(System.getProperty("user.dir"));
- }
- if (timeout <= 1000) {
- timeout = 5000;
- }
- if (workers < 25) {
- workers = 5;
- }
- if (log == null) {
- p("logging to stdout");
- log = System.out;
- }
- }
-
- static void printProps() {
- p("root="+root);
- p("timeout="+timeout);
- p("workers="+workers);
- }
- */
-
-
- /* print to stdout */
-// protected static void p(String s) {
-// System.out.println(s);
-// }
-
- /* print to the log file */
- protected static void log(String s) {
- if (false) {
- System.out.println(s);
- }
-// synchronized (log) {
-// log.println(s);
-// log.flush();
-// }
- }
-
-
- //public static void main(String[] a) throws Exception {
- static public int launch(String zipPath) throws IOException {
- final ZipFile zip = new ZipFile(zipPath);
- final HashMap entries = new HashMap();
- Enumeration en = zip.entries();
- while (en.hasMoreElements()) {
- ZipEntry entry = (ZipEntry) en.nextElement();
- entries.put(entry.getName(), entry);
- }
-
-// if (a.length > 0) {
-// port = Integer.parseInt(a[0]);
-// }
-// loadProps();
-// printProps();
- // start worker threads
- for (int i = 0; i < workers; ++i) {
- WebServerWorker w = new WebServerWorker(zip, entries);
- Thread t = new Thread(w, "Web Server Worker #" + i);
- t.start();
- threads.addElement(w);
- }
-
- final int port = 8080;
-
- //SwingUtilities.invokeLater(new Runnable() {
- Runnable r = new Runnable() {
- public void run() {
- try {
- ServerSocket ss = new ServerSocket(port);
- while (true) {
- Socket s = ss.accept();
- WebServerWorker w = null;
- synchronized (threads) {
- if (threads.isEmpty()) {
- WebServerWorker ws = new WebServerWorker(zip, entries);
- ws.setSocket(s);
- (new Thread(ws, "additional worker")).start();
- } else {
- w = (WebServerWorker) threads.elementAt(0);
- threads.removeElementAt(0);
- w.setSocket(s);
- }
- }
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- };
- new Thread(r).start();
-// });
- return port;
- }
-}
-
-
-class WebServerWorker /*extends WebServer*/ implements HttpConstants, Runnable {
- ZipFile zip;
- HashMap entries;
-
- final static int BUF_SIZE = 2048;
-
- static final byte[] EOL = { (byte)'\r', (byte)'\n' };
-
- /* buffer to use for requests */
- byte[] buf;
- /* Socket to client we're handling */
- private Socket s;
-
- WebServerWorker(ZipFile zip, HashMap entries) {
- this.entries = entries;
- this.zip = zip;
-
- buf = new byte[BUF_SIZE];
- s = null;
- }
-
-// Worker() {
-// buf = new byte[BUF_SIZE];
-// s = null;
-// }
-//
- synchronized void setSocket(Socket s) {
- this.s = s;
- notify();
- }
-
- public synchronized void run() {
- while(true) {
- if (s == null) {
- /* nothing to do */
- try {
- wait();
- } catch (InterruptedException e) {
- /* should not happen */
- continue;
- }
- }
- try {
- handleClient();
- } catch (Exception e) {
- e.printStackTrace();
- }
- /* go back in wait queue if there's fewer
- * than numHandler connections.
- */
- s = null;
- Vector pool = WebServer.threads;
- synchronized (pool) {
- if (pool.size() >= WebServer.workers) {
- /* too many threads, exit this one */
- return;
- } else {
- pool.addElement(this);
- }
- }
- }
- }
-
-
- void handleClient() throws IOException {
- InputStream is = new BufferedInputStream(s.getInputStream());
- PrintStream ps = new PrintStream(s.getOutputStream());
- // we will only block in read for this many milliseconds
- // before we fail with java.io.InterruptedIOException,
- // at which point we will abandon the connection.
- s.setSoTimeout(WebServer.timeout);
- s.setTcpNoDelay(true);
- // zero out the buffer from last time
- for (int i = 0; i < BUF_SIZE; i++) {
- buf[i] = 0;
- }
- try {
- // We only support HTTP GET/HEAD, and don't support any fancy HTTP
- // options, so we're only interested really in the first line.
- int nread = 0, r = 0;
-
-outerloop:
- while (nread < BUF_SIZE) {
- r = is.read(buf, nread, BUF_SIZE - nread);
- if (r == -1) {
- return; // EOF
- }
- int i = nread;
- nread += r;
- for (; i < nread; i++) {
- if (buf[i] == (byte)'\n' || buf[i] == (byte)'\r') {
- break outerloop; // read one line
- }
- }
- }
-
- /* are we doing a GET or just a HEAD */
- boolean doingGet;
- /* beginning of file name */
- int index;
- if (buf[0] == (byte)'G' &&
- buf[1] == (byte)'E' &&
- buf[2] == (byte)'T' &&
- buf[3] == (byte)' ') {
- doingGet = true;
- index = 4;
- } else if (buf[0] == (byte)'H' &&
- buf[1] == (byte)'E' &&
- buf[2] == (byte)'A' &&
- buf[3] == (byte)'D' &&
- buf[4] == (byte)' ') {
- doingGet = false;
- index = 5;
- } else {
- /* we don't support this method */
- ps.print("HTTP/1.0 " + HTTP_BAD_METHOD +
- " unsupported method type: ");
- ps.write(buf, 0, 5);
- ps.write(EOL);
- ps.flush();
- s.close();
- return;
- }
-
- int i = 0;
- /* find the file name, from:
- * GET /foo/bar.html HTTP/1.0
- * extract "/foo/bar.html"
- */
- for (i = index; i < nread; i++) {
- if (buf[i] == (byte)' ') {
- break;
- }
- }
-
- String fname = new String(buf, index, i-index);
- // get the zip entry, remove the front slash
- ZipEntry entry = entries.get(fname.substring(1));
- //System.out.println(fname + " " + entry);
- boolean ok = printHeaders(entry, ps);
- if (entry != null) {
- InputStream stream = zip.getInputStream(entry);
- if (doingGet && ok) {
- sendFile(stream, ps);
- }
- } else {
- send404(ps);
- }
- /*
- String fname =
- (new String(buf, 0, index, i-index)).replace('/', File.separatorChar);
- if (fname.startsWith(File.separator)) {
- fname = fname.substring(1);
- }
- File targ = new File(WebServer.root, fname);
- if (targ.isDirectory()) {
- File ind = new File(targ, "index.html");
- if (ind.exists()) {
- targ = ind;
- }
- }
- boolean OK = printHeaders(targ, ps);
- if (doingGet) {
- if (OK) {
- sendFile(targ, ps);
- } else {
- send404(targ, ps);
- }
- }
- */
- } finally {
- s.close();
- }
- }
-
-
- boolean printHeaders(ZipEntry targ, PrintStream ps) throws IOException {
- boolean ret = false;
- int rCode = 0;
- if (targ == null) {
- rCode = HTTP_NOT_FOUND;
- ps.print("HTTP/1.0 " + HTTP_NOT_FOUND + " Not Found");
- ps.write(EOL);
- ret = false;
- } else {
- rCode = HTTP_OK;
- ps.print("HTTP/1.0 " + HTTP_OK + " OK");
- ps.write(EOL);
- ret = true;
- }
- if (targ != null) {
- WebServer.log("From " +s.getInetAddress().getHostAddress()+": GET " + targ.getName()+" --> "+rCode);
- }
- ps.print("Server: Processing Documentation Server");
- ps.write(EOL);
- ps.print("Date: " + (new Date()));
- ps.write(EOL);
- if (ret) {
- if (!targ.isDirectory()) {
- ps.print("Content-length: " + targ.getSize());
- ps.write(EOL);
- ps.print("Last Modified: " + new Date(targ.getTime()));
- ps.write(EOL);
- String name = targ.getName();
- int ind = name.lastIndexOf('.');
- String ct = null;
- if (ind > 0) {
- ct = (String) map.get(name.substring(ind));
- }
- if (ct == null) {
- //System.err.println("unknown content type " + name.substring(ind));
- ct = "application/x-unknown-content-type";
- }
- ps.print("Content-type: " + ct);
- ps.write(EOL);
- } else {
- ps.print("Content-type: text/html");
- ps.write(EOL);
- }
- }
- ps.write(EOL); // adding another newline here [fry]
- return ret;
- }
-
-
- boolean printHeaders(File targ, PrintStream ps) throws IOException {
- boolean ret = false;
- int rCode = 0;
- if (!targ.exists()) {
- rCode = HTTP_NOT_FOUND;
- ps.print("HTTP/1.0 " + HTTP_NOT_FOUND + " Not Found");
- ps.write(EOL);
- ret = false;
- } else {
- rCode = HTTP_OK;
- ps.print("HTTP/1.0 " + HTTP_OK+" OK");
- ps.write(EOL);
- ret = true;
- }
- WebServer.log("From " +s.getInetAddress().getHostAddress()+": GET " + targ.getAbsolutePath()+"-->"+rCode);
- ps.print("Server: Simple java");
- ps.write(EOL);
- ps.print("Date: " + (new Date()));
- ps.write(EOL);
- if (ret) {
- if (!targ.isDirectory()) {
- ps.print("Content-length: " + targ.length());
- ps.write(EOL);
- ps.print("Last Modified: " + new Date(targ.lastModified()));
- ps.write(EOL);
- String name = targ.getName();
- int ind = name.lastIndexOf('.');
- String ct = null;
- if (ind > 0) {
- ct = (String) map.get(name.substring(ind));
- }
- if (ct == null) {
- ct = "unknown/unknown";
- }
- ps.print("Content-type: " + ct);
- ps.write(EOL);
- } else {
- ps.print("Content-type: text/html");
- ps.write(EOL);
- }
- }
- return ret;
- }
-
-
- void send404(PrintStream ps) throws IOException {
- ps.write(EOL);
- ps.write(EOL);
- ps.print("404 Not Found
"+
- "The requested resource was not found.");
- ps.write(EOL);
- ps.write(EOL);
- }
-
-
- void sendFile(File targ, PrintStream ps) throws IOException {
- InputStream is = null;
- ps.write(EOL);
- if (targ.isDirectory()) {
- listDirectory(targ, ps);
- return;
- } else {
- is = new FileInputStream(targ.getAbsolutePath());
- }
- sendFile(is, ps);
- }
-
-
- void sendFile(InputStream is, PrintStream ps) throws IOException {
- try {
- int n;
- while ((n = is.read(buf)) > 0) {
- ps.write(buf, 0, n);
- }
- } finally {
- is.close();
- }
- }
-
- /* mapping of file extensions to content-types */
- static java.util.Hashtable map = new java.util.Hashtable();
-
- static {
- fillMap();
- }
- static void setSuffix(String k, String v) {
- map.put(k, v);
- }
-
- static void fillMap() {
- setSuffix("", "content/unknown");
-
- setSuffix(".uu", "application/octet-stream");
- setSuffix(".exe", "application/octet-stream");
- setSuffix(".ps", "application/postscript");
- setSuffix(".zip", "application/zip");
- setSuffix(".sh", "application/x-shar");
- setSuffix(".tar", "application/x-tar");
- setSuffix(".snd", "audio/basic");
- setSuffix(".au", "audio/basic");
- setSuffix(".wav", "audio/x-wav");
-
- setSuffix(".gif", "image/gif");
- setSuffix(".jpg", "image/jpeg");
- setSuffix(".jpeg", "image/jpeg");
-
- setSuffix(".htm", "text/html");
- setSuffix(".html", "text/html");
- setSuffix(".css", "text/css");
- setSuffix(".java", "text/javascript");
-
- setSuffix(".txt", "text/plain");
- setSuffix(".java", "text/plain");
-
- setSuffix(".c", "text/plain");
- setSuffix(".cc", "text/plain");
- setSuffix(".c++", "text/plain");
- setSuffix(".h", "text/plain");
- setSuffix(".pl", "text/plain");
- }
-
- void listDirectory(File dir, PrintStream ps) throws IOException {
- ps.println("Directory listing\n");
- ps.println("Parent Directory
\n");
- String[] list = dir.list();
- for (int i = 0; list != null && i < list.length; i++) {
- File f = new File(dir, list[i]);
- if (f.isDirectory()) {
- ps.println(""+list[i]+"/
");
- } else {
- ps.println(""+list[i]+"
" + (new Date()) + "");
- }
-
-}
-
-
-interface HttpConstants {
- /** 2XX: generally "OK" */
- public static final int HTTP_OK = 200;
- public static final int HTTP_CREATED = 201;
- public static final int HTTP_ACCEPTED = 202;
- public static final int HTTP_NOT_AUTHORITATIVE = 203;
- public static final int HTTP_NO_CONTENT = 204;
- public static final int HTTP_RESET = 205;
- public static final int HTTP_PARTIAL = 206;
-
- /** 3XX: relocation/redirect */
- public static final int HTTP_MULT_CHOICE = 300;
- public static final int HTTP_MOVED_PERM = 301;
- public static final int HTTP_MOVED_TEMP = 302;
- public static final int HTTP_SEE_OTHER = 303;
- public static final int HTTP_NOT_MODIFIED = 304;
- public static final int HTTP_USE_PROXY = 305;
-
- /** 4XX: client error */
- public static final int HTTP_BAD_REQUEST = 400;
- public static final int HTTP_UNAUTHORIZED = 401;
- public static final int HTTP_PAYMENT_REQUIRED = 402;
- public static final int HTTP_FORBIDDEN = 403;
- public static final int HTTP_NOT_FOUND = 404;
- public static final int HTTP_BAD_METHOD = 405;
- public static final int HTTP_NOT_ACCEPTABLE = 406;
- public static final int HTTP_PROXY_AUTH = 407;
- public static final int HTTP_CLIENT_TIMEOUT = 408;
- public static final int HTTP_CONFLICT = 409;
- public static final int HTTP_GONE = 410;
- public static final int HTTP_LENGTH_REQUIRED = 411;
- public static final int HTTP_PRECON_FAILED = 412;
- public static final int HTTP_ENTITY_TOO_LARGE = 413;
- public static final int HTTP_REQ_TOO_LONG = 414;
- public static final int HTTP_UNSUPPORTED_TYPE = 415;
-
- /** 5XX: server error */
- public static final int HTTP_SERVER_ERROR = 500;
- public static final int HTTP_INTERNAL_ERROR = 501;
- public static final int HTTP_BAD_GATEWAY = 502;
- public static final int HTTP_UNAVAILABLE = 503;
- public static final int HTTP_GATEWAY_TIMEOUT = 504;
- public static final int HTTP_VERSION = 505;
-}
diff --git a/app/src/processing/app/debug/AvrdudeUploader.java b/app/src/processing/app/debug/AvrdudeUploader.java
deleted file mode 100755
index 97ef91a20a3..00000000000
--- a/app/src/processing/app/debug/AvrdudeUploader.java
+++ /dev/null
@@ -1,203 +0,0 @@
-/* -*- mode: jde; c-basic-offset: 2; indent-tabs-mode: nil -*- */
-
-/*
- AvrdudeUploader - uploader implementation using avrdude
- Part of the Arduino project - http://www.arduino.cc/
-
- Copyright (c) 2004-05
- Hernando Barragan
-
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software Foundation,
- Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-
- $Id$
-*/
-
-package processing.app.debug;
-
-import processing.app.Base;
-import processing.app.Preferences;
-import processing.app.Serial;
-import processing.app.SerialException;
-
-import java.io.*;
-import java.util.*;
-import java.util.zip.*;
-import javax.swing.*;
-import gnu.io.*;
-
-
-public class AvrdudeUploader extends Uploader {
- public AvrdudeUploader() {
- }
-
- // XXX: add support for uploading sketches using a programmer
- public boolean uploadUsingPreferences(String buildPath, String className, boolean verbose)
- throws RunnerException, SerialException {
- this.verbose = verbose;
- Map boardPreferences = Base.getBoardPreferences();
- String uploadUsing = boardPreferences.get("upload.using");
- if (uploadUsing == null) {
- // fall back on global preference
- uploadUsing = Preferences.get("upload.using");
- }
- if (uploadUsing.equals("bootloader")) {
- return uploadViaBootloader(buildPath, className);
- } else {
- Target t;
-
- if (uploadUsing.indexOf(':') == -1) {
- t = Base.getTarget(); // the current target (associated with the board)
- } else {
- String targetName = uploadUsing.substring(0, uploadUsing.indexOf(':'));
- t = Base.targetsTable.get(targetName);
- uploadUsing = uploadUsing.substring(uploadUsing.indexOf(':') + 1);
- }
-
- Collection params = getProgrammerCommands(t, uploadUsing);
- params.add("-Uflash:w:" + buildPath + File.separator + className + ".hex:i");
- return avrdude(params);
- }
- }
-
- private boolean uploadViaBootloader(String buildPath, String className)
- throws RunnerException, SerialException {
- Map boardPreferences = Base.getBoardPreferences();
- List commandDownloader = new ArrayList();
- String protocol = boardPreferences.get("upload.protocol");
-
- // avrdude wants "stk500v1" to distinguish it from stk500v2
- if (protocol.equals("stk500"))
- protocol = "stk500v1";
- commandDownloader.add("-c" + protocol);
- commandDownloader.add(
- "-P" + (Base.isWindows() ? "\\\\.\\" : "") + Preferences.get("serial.port"));
- commandDownloader.add(
- "-b" + Integer.parseInt(boardPreferences.get("upload.speed")));
- commandDownloader.add("-D"); // don't erase
- commandDownloader.add("-Uflash:w:" + buildPath + File.separator + className + ".hex:i");
-
- if (boardPreferences.get("upload.disable_flushing") == null ||
- boardPreferences.get("upload.disable_flushing").toLowerCase().equals("false")) {
- flushSerialBuffer();
- }
-
- return avrdude(commandDownloader);
- }
-
- public boolean burnBootloader(String targetName, String programmer) throws RunnerException {
- return burnBootloader(getProgrammerCommands(Base.targetsTable.get(targetName), programmer));
- }
-
- private Collection getProgrammerCommands(Target target, String programmer) {
- Map programmerPreferences = target.getProgrammers().get(programmer);
- List params = new ArrayList();
- params.add("-c" + programmerPreferences.get("protocol"));
-
- if ("usb".equals(programmerPreferences.get("communication"))) {
- params.add("-Pusb");
- } else if ("serial".equals(programmerPreferences.get("communication"))) {
- params.add("-P" + (Base.isWindows() ? "\\\\.\\" : "") + Preferences.get("serial.port"));
- if (programmerPreferences.get("speed") != null) {
- params.add("-b" + Integer.parseInt(programmerPreferences.get("speed")));
- }
- }
- // XXX: add support for specifying the port address for parallel
- // programmers, although avrdude has a default that works in most cases.
-
- if (programmerPreferences.get("force") != null &&
- programmerPreferences.get("force").toLowerCase().equals("true"))
- params.add("-F");
-
- if (programmerPreferences.get("delay") != null)
- params.add("-i" + programmerPreferences.get("delay"));
-
- return params;
- }
-
- protected boolean burnBootloader(Collection params)
- throws RunnerException {
- Map boardPreferences = Base.getBoardPreferences();
- List fuses = new ArrayList();
- fuses.add("-e"); // erase the chip
- fuses.add("-Ulock:w:" + boardPreferences.get("bootloader.unlock_bits") + ":m");
- if (boardPreferences.get("bootloader.extended_fuses") != null)
- fuses.add("-Uefuse:w:" + boardPreferences.get("bootloader.extended_fuses") + ":m");
- fuses.add("-Uhfuse:w:" + boardPreferences.get("bootloader.high_fuses") + ":m");
- fuses.add("-Ulfuse:w:" + boardPreferences.get("bootloader.low_fuses") + ":m");
-
- if (!avrdude(params, fuses))
- return false;
-
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {}
-
- Target t;
- String bootloaderPath = boardPreferences.get("bootloader.path");
-
- if (bootloaderPath.indexOf(':') == -1) {
- t = Base.getTarget(); // the current target (associated with the board)
- } else {
- String targetName = bootloaderPath.substring(0, bootloaderPath.indexOf(':'));
- t = Base.targetsTable.get(targetName);
- bootloaderPath = bootloaderPath.substring(bootloaderPath.indexOf(':') + 1);
- }
-
- File bootloadersFile = new File(t.getFolder(), "bootloaders");
- File bootloaderFile = new File(bootloadersFile, bootloaderPath);
- bootloaderPath = bootloaderFile.getAbsolutePath();
-
- List bootloader = new ArrayList();
- bootloader.add("-Uflash:w:" + bootloaderPath + File.separator +
- boardPreferences.get("bootloader.file") + ":i");
- bootloader.add("-Ulock:w:" + boardPreferences.get("bootloader.lock_bits") + ":m");
-
- return avrdude(params, bootloader);
- }
-
- public boolean avrdude(Collection p1, Collection p2) throws RunnerException {
- ArrayList p = new ArrayList(p1);
- p.addAll(p2);
- return avrdude(p);
- }
-
- public boolean avrdude(Collection params) throws RunnerException {
- List commandDownloader = new ArrayList();
- commandDownloader.add("avrdude");
-
- // Point avrdude at its config file since it's in a non-standard location.
- if (Base.isLinux()) {
- // ???: is it better to have Linux users install avrdude themselves, in
- // a way that it can find its own configuration file?
- commandDownloader.add("-C" + Base.getHardwarePath() + "/tools/avrdude.conf");
- } else {
- commandDownloader.add("-C" + Base.getHardwarePath() + "/tools/avr/etc/avrdude.conf");
- }
-
- if (verbose || Preferences.getBoolean("upload.verbose")) {
- commandDownloader.add("-v");
- commandDownloader.add("-v");
- commandDownloader.add("-v");
- commandDownloader.add("-v");
- } else {
- commandDownloader.add("-q");
- commandDownloader.add("-q");
- }
- commandDownloader.add("-p" + Base.getBoardPreferences().get("build.mcu"));
- commandDownloader.addAll(params);
-
- return executeUploadCommand(commandDownloader);
- }
-}
diff --git a/app/src/processing/app/debug/Compiler.java b/app/src/processing/app/debug/Compiler.java
deleted file mode 100644
index f2fa5e26d30..00000000000
--- a/app/src/processing/app/debug/Compiler.java
+++ /dev/null
@@ -1,510 +0,0 @@
-/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
-
-/*
- Part of the Processing project - http://processing.org
-
- Copyright (c) 2004-08 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 as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software Foundation,
- Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-*/
-
-package processing.app.debug;
-
-import processing.app.Base;
-import processing.app.Preferences;
-import processing.app.Sketch;
-import processing.app.SketchCode;
-import processing.core.*;
-
-import java.io.*;
-import java.util.*;
-import java.util.zip.*;
-
-
-public class Compiler implements MessageConsumer {
- static final String BUGS_URL =
- "http://code.google.com/p/arduino/issues/list";
- static final String SUPER_BADNESS =
- "Compiler error, please submit this code to " + BUGS_URL;
-
- Sketch sketch;
- String buildPath;
- String primaryClassName;
- boolean verbose;
-
- RunnerException exception;
-
- public Compiler() { }
-
- /**
- * Compile with avr-gcc.
- *
- * @param sketch Sketch object to be compiled.
- * @param buildPath Where the temporary files live and will be built from.
- * @param primaryClassName the name of the combined sketch file w/ extension
- * @return true if successful.
- * @throws RunnerException Only if there's a problem. Only then.
- */
- public boolean compile(Sketch sketch,
- String buildPath,
- String primaryClassName,
- boolean verbose) throws RunnerException {
- this.sketch = sketch;
- this.buildPath = buildPath;
- this.primaryClassName = primaryClassName;
- this.verbose = verbose;
-
- // the pms object isn't used for anything but storage
- MessageStream pms = new MessageStream(this);
-
- String avrBasePath = Base.getAvrBasePath();
- Map boardPreferences = Base.getBoardPreferences();
- String core = boardPreferences.get("build.core");
- if (core == null) {
- RunnerException re = new RunnerException("No board selected; please choose a board from the Tools > Board menu.");
- re.hideStackTrace();
- throw re;
- }
- String corePath;
-
- if (core.indexOf(':') == -1) {
- Target t = Base.getTarget();
- File coreFolder = new File(new File(t.getFolder(), "cores"), core);
- corePath = coreFolder.getAbsolutePath();
- } else {
- Target t = Base.targetsTable.get(core.substring(0, core.indexOf(':')));
- File coresFolder = new File(t.getFolder(), "cores");
- File coreFolder = new File(coresFolder, core.substring(core.indexOf(':') + 1));
- corePath = coreFolder.getAbsolutePath();
- }
-
- List objectFiles = new ArrayList();
-
- // 0. include paths for core + all libraries
-
- List includePaths = new ArrayList();
- includePaths.add(corePath);
- for (File file : sketch.getImportedLibraries()) {
- includePaths.add(file.getPath());
- }
-
- // 1. compile the sketch (already in the buildPath)
-
- objectFiles.addAll(
- compileFiles(avrBasePath, buildPath, includePaths,
- findFilesInPath(buildPath, "S", false),
- findFilesInPath(buildPath, "c", false),
- findFilesInPath(buildPath, "cpp", false),
- boardPreferences));
-
- // 2. compile the libraries, outputting .o files to: