diff --git a/.gitignore b/.gitignore index 0ff213c4047..5f5d747e730 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ app/pde.jar build/macosx/work/ arduino-core/bin/ arduino-core/arduino-core.jar +lib/* hardware/arduino/bootloaders/caterina_LUFA/Descriptors.o hardware/arduino/bootloaders/caterina_LUFA/Descriptors.lst hardware/arduino/bootloaders/caterina_LUFA/Caterina.sym diff --git a/app/src/processing/app/Base.java b/app/src/processing/app/Base.java index 1e4819bba34..b0648173c70 100644 --- a/app/src/processing/app/Base.java +++ b/app/src/processing/app/Base.java @@ -510,7 +510,10 @@ public Base(String[] args) throws Exception { contributionsSelfCheck = new ContributionsSelfCheck(this, new UpdatableBoardsLibsFakeURLsHandler(this), contributionInstaller, libraryInstaller); new Timer(false).schedule(contributionsSelfCheck, Constants.BOARDS_LIBS_UPDATABLE_CHECK_START_PERIOD); } - + // Load the build settings + for(Editor editor: editors){ + editor.findTab(editor.sketch.getPrimaryFile()).loadBuildSettings(this); + } } else if (parser.isNoOpMode()) { // Do nothing (intended for only changing preferences) System.exit(0); @@ -715,10 +718,8 @@ protected int[] nextEditorLocation() { } } - // ................................................................. - boolean breakTime = false; String[] months = { "jan", "feb", "mar", "apr", "may", "jun", diff --git a/app/src/processing/app/Editor.java b/app/src/processing/app/Editor.java index 2ec29c498cb..9581c2d8d94 100644 --- a/app/src/processing/app/Editor.java +++ b/app/src/processing/app/Editor.java @@ -110,6 +110,34 @@ import processing.app.tools.MenuScroller; import processing.app.tools.Tool; +import javax.swing.*; +import javax.swing.event.*; +import javax.swing.text.BadLocationException; +import java.awt.*; +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.Transferable; +import java.awt.event.*; +import java.awt.print.PageFormat; +import java.awt.print.PrinterException; +import java.awt.print.PrinterJob; +import java.io.File; +import java.io.FileFilter; +import java.io.FilenameFilter; +import java.io.IOException; +import java.net.ConnectException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.*; +import java.util.List; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import java.util.ArrayList; + +import static processing.app.I18n.tr; +import static processing.app.Theme.scale; + /** * Main editor panel for the Processing Development Environment. */ @@ -748,6 +776,10 @@ private JMenu buildToolsMenu() { item = new JMenuItem(tr("Get Board Info")); item.addActionListener(e -> handleBoardInfo()); toolsMenu.add(item); + + item = new JMenuItem(tr("Add build settings to .INO file")); + item.addActionListener(e -> handleAddBuildSettings()); + toolsMenu.add(item); toolsMenu.addSeparator(); base.rebuildProgrammerMenu(); @@ -2369,6 +2401,21 @@ public void handlePlotter() { } + public void handleAddBuildSettings(){ + final LinkedHashMap settingsMap = base.getBoardsCustomMenus().stream().filter(JMenu::isVisible).map((e)->{ + String setting = e.getText().substring(0, e.getText().indexOf(":")); + String value = e.getText().replace(setting + ":", "").replace("\"", "").trim(); + return new String[]{setting, value}; + }).collect(LinkedHashMap::new, (map, menu) -> map.put(menu[0], menu[1]), LinkedHashMap::putAll); + handleSave(true); + Optional optionalEditorTab = tabs.stream().filter(tab -> tab.getSketch().getSketch().equals(sketch)).findFirst(); + if(optionalEditorTab.isPresent()){ + optionalEditorTab.get().setText(sketch.setBuildSettings(sketch, settingsMap)); + handleSave(true); + System.out.println("Build settings header should be added"); + } + } + private void handleBurnBootloader() { console.clear(); EditorConsole.setCurrentEditorConsole(this.console); diff --git a/app/src/processing/app/EditorTab.java b/app/src/processing/app/EditorTab.java index 5e8f3e4bfcf..b79de0b99c6 100644 --- a/app/src/processing/app/EditorTab.java +++ b/app/src/processing/app/EditorTab.java @@ -33,13 +33,12 @@ import java.awt.event.FocusEvent; import java.awt.event.FocusListener; import java.io.IOException; +import java.lang.annotation.Target; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.Optional; -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.*; import javax.swing.border.MatteBorder; import javax.swing.event.PopupMenuEvent; import javax.swing.event.PopupMenuListener; @@ -57,6 +56,8 @@ import org.fife.ui.rtextarea.RTextScrollPane; import cc.arduino.UpdatableBoardsLibsFakeURLsHandler; +import processing.app.debug.TargetBoard; +import processing.app.debug.TargetPackage; import processing.app.helpers.DocumentTextChangeListener; import processing.app.syntax.ArduinoTokenMakerFactory; import processing.app.syntax.PdeKeywords; @@ -447,6 +448,42 @@ public void setText(String what) { textarea.setLineWrap(textarea.getLineWrap()); } + + /** + * This method loads the build settings from the main .INO file and sets the Arduino IDE accordingly + */ + public void loadBuildSettings(Base base){ + + LinkedHashMap buildSettings = getSketch().getSketch().getBuildSettingsFromProgram(getText()); + + Optional optionalTargetBoard = BaseNoGui.getTargetPlatform().getBoards().values().stream().filter(board -> board.getPreferences().get("name","").equals("Node32s")).findFirst(); + + TargetBoard targetBoard; + if(optionalTargetBoard.isPresent()){ + targetBoard = optionalTargetBoard.get(); + }else{ + return; + } + + for(String readableName : buildSettings.values()){ + base.getBoardsCustomMenus().forEach(menuItem -> { + Optional optionalBoardMenuItem = Arrays.stream(menuItem.getMenuComponents()).filter(subItem->{ + return subItem instanceof JRadioButtonMenuItem && ((JRadioButtonMenuItem)subItem).getText().equals(readableName) && (((JRadioButtonMenuItem) subItem).getAction().getValue("board") == null || (((JRadioButtonMenuItem) subItem).getAction().getValue("board").equals(targetBoard))); + } + ).map(subItem-> { + return subItem instanceof JRadioButtonMenuItem ? (JRadioButtonMenuItem)subItem : new JRadioButtonMenuItem(); + }).findFirst(); + if(optionalBoardMenuItem.isPresent()){ + optionalBoardMenuItem.get().setSelected(true); + optionalBoardMenuItem.get().getAction().actionPerformed(new ActionEvent(this, -1, "")); + }else{ + // TODO Ask the user which value should replace the current value + + } + }); + } + } + /** * Is the text modified since the last save / load? */ diff --git a/arduino-core/src/processing/app/Sketch.java b/arduino-core/src/processing/app/Sketch.java index 6c417403ec9..c9c42bc7f59 100644 --- a/arduino-core/src/processing/app/Sketch.java +++ b/arduino-core/src/processing/app/Sketch.java @@ -139,6 +139,111 @@ public void save() throws IOException { } } + private final String buildToolsHeader = "\n/** Arduino IDE Board Tool details\n"; + private final String buildToolsHeaderEnd = "*/"; + + /** + * Checks the code for a valid build tool header + * @param program The code to scan for the build tools + * @return True if the build tool header was found ONE time. Returns false if found MORE than one time, or not found at all. + */ + private boolean containsBuildSettings(String program){ + return program.contains(buildToolsHeader) && (program.indexOf(buildToolsHeader) == program.lastIndexOf(buildToolsHeader)); + } + + /** + * This function returns the index of the Nth occurrence of the substring in the specified string (http://programming.guide/java/nth-occurrence-in-string.html) + * @param str The string to find the Nth occurrence in + * @param substr The string to find + * @param n The occurrence number you'd like to find + * @return + */ + private static int ordinalIndexOf(String str, String substr, int n) { + int pos = str.indexOf(substr); + while (--n > 0 && pos != -1) + pos = str.indexOf(substr, pos + 1); + return pos; + } + + private String removeBuildSettingsHeader(Sketch sketch){ + if(sketch.getPrimaryFile().getProgram().contains(buildToolsHeader)) { + int headerStartIndex = sketch.getPrimaryFile().getProgram().indexOf(buildToolsHeader); + int headerStopIndex = sketch.getPrimaryFile().getProgram().indexOf(buildToolsHeaderEnd); + if (headerStartIndex > headerStopIndex) { + System.err.println("The build tool header is not the first comment block in your file! Please fix this."); + for (int i = 0; i < sketch.getPrimaryFile().getProgram().length(); i++) { + if (headerStartIndex < ordinalIndexOf(sketch.getPrimaryFile().getProgram(), buildToolsHeaderEnd, i)) { + headerStopIndex = ordinalIndexOf(sketch.getPrimaryFile().getProgram(), buildToolsHeaderEnd, i); + break; + } + } + } + String header = sketch.getPrimaryFile().getProgram().substring(headerStartIndex, headerStopIndex + buildToolsHeaderEnd.length()); + return sketch.getPrimaryFile().getProgram().replace(header, ""); + } + return sketch.getPrimaryFile().getProgram(); + } + + /** + * This checks the program code for a valid build tool settings header and returns the LinkedHashMap with the setting name and the value. + * The build tools header should not be changed or manipulated by the pre-processor as the pre-processors output may depend on the build tools. + * @param program The program code + * @return The {@code LinkedHashMap} with the settings and their values of the first header that was found in the program code + */ + public LinkedHashMap getBuildSettingsFromProgram(String program){ + LinkedHashMap buildSettings = new LinkedHashMap<>(); + if(containsBuildSettings(program)){ + int headerStartIndex = program.indexOf(buildToolsHeader); + int headerStopIndex = program.indexOf(buildToolsHeaderEnd); + if(headerStartIndex > headerStopIndex){ + System.err.println("The build tool header is not the first comment block in your file! Please fix this."); + for(int i = 0; i < program.length(); i++){ + if(headerStartIndex < ordinalIndexOf(program, buildToolsHeaderEnd, i)){ + headerStopIndex = ordinalIndexOf(program, buildToolsHeaderEnd, i); + break; + } + } + } + String header = program.substring(headerStartIndex + buildToolsHeader.length(), headerStopIndex); + + String[] headerLines = header.split("\n"); + + for(int line = 0; line < headerLines.length; line++){ + String[] setting = headerLines[line].replace("*","").trim().split(": "); + if(headerLines[line].indexOf(": ") != (headerLines[line].length() -1)){ + // The value of the setting is not empty + buildSettings.put(setting[0].trim(), setting[1].trim()); + }else{ + buildSettings.put(setting[0], ""); + } + } + }else{ + if(!program.contains(buildToolsHeader)){ + // There are multiple headers, remove them + // TODO Create a dialog asking the user to add a build header to the file + } + } + + return buildSettings; + } + + private boolean isBuildSettingsEqual(LinkedHashMap first, LinkedHashMap second){ + return first.keySet().containsAll(second.keySet()) && first.values().containsAll(second.values()); + } + + public String setBuildSettings(Sketch sketch, LinkedHashMap buildSettings){ + if(sketch != this){ + return ""; + } + + String customBoardSettingsHeader = buildSettings.entrySet().stream().map(entry-> String.format(" * %s: %s\n", entry.getKey(), entry.getValue())).collect(Collectors.joining("", buildToolsHeader, "*/")); + if(!isBuildSettingsEqual(getBuildSettingsFromProgram(sketch.getPrimaryFile().getProgram()),buildSettings)){ + String headerLessProgram = removeBuildSettingsHeader(sketch); + return customBoardSettingsHeader + ((headerLessProgram.charAt(0) == '\n') ? "" : "\n") + headerLessProgram; + } + return ""; + } + public int getCodeCount() { return files.size(); } diff --git a/build/shared/examples/01.Basics/BareMinimum/BareMinimum.ino b/build/shared/examples/01.Basics/BareMinimum/BareMinimum.ino index 95c2b6eb0a8..6586e6e69ab 100644 --- a/build/shared/examples/01.Basics/BareMinimum/BareMinimum.ino +++ b/build/shared/examples/01.Basics/BareMinimum/BareMinimum.ino @@ -1,3 +1,7 @@ +/** Arduino IDE Board Tool details + * Board: Arduino/Genuino Uno +*/ + void setup() { // put your setup code here, to run once: @@ -6,4 +10,4 @@ void setup() { void loop() { // put your main code here, to run repeatedly: -} +} \ No newline at end of file