diff --git a/app/src/cc/arduino/view/preferences/Preferences.java b/app/src/cc/arduino/view/preferences/Preferences.java index 005d2f83e..a92663687 100644 --- a/app/src/cc/arduino/view/preferences/Preferences.java +++ b/app/src/cc/arduino/view/preferences/Preferences.java @@ -130,6 +130,7 @@ public class Preferences extends javax.swing.JDialog { checkboxesContainer = new javax.swing.JPanel(); displayLineNumbersBox = new javax.swing.JCheckBox(); enableCodeFoldingBox = new javax.swing.JCheckBox(); + enableBookmarks = new javax.swing.JCheckBox(); verifyUploadBox = new javax.swing.JCheckBox(); externalEditorBox = new javax.swing.JCheckBox(); checkUpdatesBox = new javax.swing.JCheckBox(); @@ -255,6 +256,9 @@ public class Preferences extends javax.swing.JDialog { enableCodeFoldingBox.setText(tr("Enable Code Folding")); checkboxesContainer.add(enableCodeFoldingBox); + enableBookmarks.setText(tr("Enable Bookmarks")); + checkboxesContainer.add(enableBookmarks); + verifyUploadBox.setText(tr("Verify code after upload")); checkboxesContainer.add(verifyUploadBox); @@ -725,6 +729,7 @@ public class Preferences extends javax.swing.JDialog { private javax.swing.JLabel comboWarningsLabel; private javax.swing.JCheckBox displayLineNumbersBox; private javax.swing.JCheckBox enableCodeFoldingBox; + private javax.swing.JCheckBox enableBookmarks; private javax.swing.JButton extendedAdditionalUrlFieldWindow; private javax.swing.JCheckBox externalEditorBox; private javax.swing.JTextField fontSizeField; @@ -823,6 +828,8 @@ public class Preferences extends javax.swing.JDialog { PreferencesData.setBoolean("editor.code_folding", enableCodeFoldingBox.isSelected()); + PreferencesData.setBoolean("editor.bookmarks", enableBookmarks.isSelected()); + PreferencesData.setBoolean("upload.verify", verifyUploadBox.isSelected()); PreferencesData.setBoolean("editor.save_on_verify", saveVerifyUploadBox.isSelected()); @@ -897,6 +904,8 @@ public class Preferences extends javax.swing.JDialog { enableCodeFoldingBox.setSelected(PreferencesData.getBoolean("editor.code_folding")); + enableBookmarks.setSelected(PreferencesData.getBoolean("editor.bookmarks")); + verifyUploadBox.setSelected(PreferencesData.getBoolean("upload.verify")); externalEditorBox.setSelected(PreferencesData.getBoolean("editor.external")); diff --git a/app/src/processing/app/EditorTab.java b/app/src/processing/app/EditorTab.java index 3e2945306..696979ac7 100644 --- a/app/src/processing/app/EditorTab.java +++ b/app/src/processing/app/EditorTab.java @@ -28,18 +28,30 @@ import static processing.app.Theme.scale; import java.awt.BorderLayout; import java.awt.Font; +import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; +import java.awt.event.InputEvent; +import java.awt.event.KeyEvent; +import java.awt.event.MouseWheelListener; +import java.awt.event.MouseWheelEvent; + import java.io.IOException; import javax.swing.Action; import javax.swing.BorderFactory; +import javax.swing.Icon; +import javax.swing.InputMap; +import javax.swing.JComponent; import javax.swing.JMenuItem; import javax.swing.JPanel; import javax.swing.JPopupMenu; +import javax.swing.KeyStroke; +import javax.swing.SwingUtilities; import javax.swing.ToolTipManager; +import javax.swing.UIManager; import javax.swing.border.MatteBorder; import javax.swing.event.PopupMenuEvent; import javax.swing.event.PopupMenuListener; @@ -51,6 +63,8 @@ import javax.swing.text.Document; import static java.nio.file.StandardWatchEventKinds.*; import java.nio.file.WatchService; +import java.util.ArrayList; +import java.util.List; import java.nio.file.WatchKey; import java.nio.file.WatchEvent; import java.nio.file.FileSystems; @@ -61,15 +75,23 @@ import org.apache.commons.lang3.StringUtils; import org.fife.ui.autocomplete.AutoCompletion; import org.fife.ui.autocomplete.DefaultCompletionProvider; import org.fife.ui.rsyntaxtextarea.RSyntaxDocument; +import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; import org.fife.ui.rsyntaxtextarea.RSyntaxTextAreaEditorKit; import org.fife.ui.rsyntaxtextarea.RSyntaxUtilities; import org.fife.ui.rtextarea.Gutter; +import org.fife.ui.rtextarea.GutterIconInfo; +import org.fife.ui.rtextarea.RTADefaultInputMap; +import org.fife.ui.rtextarea.RTextArea; +import org.fife.ui.rtextarea.RTextAreaEditorKit; import org.fife.ui.rtextarea.RTextScrollPane; +import org.fife.ui.rtextarea.RecordableTextAction; import cc.arduino.UpdatableBoardsLibsFakeURLsHandler; import cc.arduino.autocomplete.ClangCompletionProvider; +import cc.arduino.autocomplete.CompletionType; import cc.arduino.autocomplete.CompletionsRenderer; import processing.app.helpers.DocumentTextChangeListener; +import processing.app.helpers.PreferencesMap; import processing.app.syntax.ArduinoTokenMakerFactory; import processing.app.syntax.PdeKeywords; import processing.app.syntax.SketchTextArea; @@ -153,6 +175,8 @@ public class EditorTab extends JPanel implements SketchFile.TextStorage { return document; } + public static final String rtaNextBookmarkAction = "RTA.NextBookmarkAction"; + 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"))); @@ -161,13 +185,118 @@ public class EditorTab extends JPanel implements SketchFile.TextStorage { scrollPane.setIconRowHeaderEnabled(false); Gutter gutter = scrollPane.getGutter(); - gutter.setBookmarkingEnabled(false); - //gutter.setBookmarkIcon(CompletionsRenderer.getIcon(CompletionType.TEMPLATE)); + + if (PreferencesData.getBoolean("editor.bookmarks")) { + gutter.setBookmarkingEnabled(true); + gutter.setBookmarkIcon(CompletionsRenderer.getIcon(CompletionType.FUNCTION)); + InputMap map = SwingUtilities.getUIInputMap(textArea, JComponent.WHEN_FOCUSED); + NextBookmarkAction action = new NextBookmarkAction(rtaNextBookmarkAction); + map.put(KeyStroke.getKeyStroke(KeyEvent.VK_F3, 0), action); + SwingUtilities.replaceUIInputMap(textArea,JComponent.WHEN_FOCUSED,map); + } + gutter.setIconRowHeaderInheritsGutterBackground(true); return scrollPane; } + public class GutterIconInfoTabs { + GutterIconInfo bookmark; + SketchFile file; + RTextArea textArea; + + public String toString( ) { + return "File:" + file.getFileName() + " -- Line:" + bookmark.getMarkedOffset(); + } + } + + /** + * Action that moves the caret to the next bookmark. + */ + public class NextBookmarkAction extends RecordableTextAction { + + public NextBookmarkAction(String name) { + super(name); + } + + @Override + public void actionPerformedImpl(ActionEvent e, RTextArea textArea) { + + Gutter gutter = RSyntaxUtilities.getGutter(textArea); + + List bookmarks = new ArrayList(); + + try { + + for (EditorTab tab : editor.getTabs()) { + gutter = RSyntaxUtilities.getGutter(tab.getTextArea()); + + if (gutter!=null) { + GutterIconInfo[] tabBookmarks = gutter.getBookmarks(); + for (GutterIconInfo element : tabBookmarks) { + GutterIconInfoTabs bookmark = new GutterIconInfoTabs(); + bookmark.file = tab.getSketchFile(); + bookmark.bookmark = element; + bookmark.textArea = tab.getTextArea(); + bookmarks.add(bookmark); + } + } + } + + if (bookmarks.size()==0) { + UIManager.getLookAndFeel(). + provideErrorFeedback(textArea); + return; + } + + GutterIconInfoTabs moveTo = null; + int curLine = textArea.getCaretLineNumber(); + + for (int i=0; icurLine) || (curTabIndex < bookmarkTabIndex)) { + moveTo = bookmarks.get(i); + break; + } + } + if (moveTo==null) { // Loop back to beginning + moveTo = bookmarks.get(0); + } + + editor.selectTab(editor.findTabIndex(moveTo.file)); + + int offs = moveTo.bookmark.getMarkedOffset(); + if (moveTo.textArea instanceof RSyntaxTextArea) { + RSyntaxTextArea rsta = (RSyntaxTextArea)moveTo.textArea; + if (rsta.isCodeFoldingEnabled()) { + rsta.getFoldManager(). + ensureOffsetNotInClosedFold(offs); + } + } + int line = moveTo.textArea.getLineOfOffset(offs); + offs = moveTo.textArea.getLineStartOffset(line); + moveTo.textArea.setCaretPosition(offs); + + } catch (BadLocationException ble) { // Never happens + UIManager.getLookAndFeel(). + provideErrorFeedback(textArea); + ble.printStackTrace(); + } + } + + @Override + public String getMacroID() { + return getName(); + } + } + private SketchTextArea createTextArea(RSyntaxDocument document) throws IOException { final SketchTextArea textArea = new SketchTextArea(document, editor.base.getPdeKeywords());