diff --git a/app/src/cc/arduino/packages/formatter/AStyle.java b/app/src/cc/arduino/packages/formatter/AStyle.java index 7f7c244d6..fc74ea06a 100644 --- a/app/src/cc/arduino/packages/formatter/AStyle.java +++ b/app/src/cc/arduino/packages/formatter/AStyle.java @@ -78,7 +78,7 @@ public class AStyle implements Tool { @Override public void run() { - String originalText = editor.getText(); + String originalText = editor.getCurrentTab().getText(); String formattedText = aStyleInterface.AStyleMain(originalText, formatterConfiguration); if (formattedText.equals(originalText)) { @@ -86,14 +86,14 @@ public class AStyle implements Tool { return; } - SketchTextArea textArea = editor.getTextArea(); + SketchTextArea textArea = editor.getCurrentTab().getTextArea(); int line = getLineOfOffset(textArea); int lineOffset = getLineOffset(textArea, line); textArea.getUndoManager().beginInternalAtomicEdit(); editor.removeAllLineHighlights(); - editor.setText(formattedText); + editor.getCurrentTab().setText(formattedText); editor.getSketch().setModified(true); textArea.getUndoManager().endInternalAtomicEdit(); diff --git a/app/src/cc/arduino/view/GoToLineNumber.java b/app/src/cc/arduino/view/GoToLineNumber.java index 3a3bc6fca..475b0bbe5 100644 --- a/app/src/cc/arduino/view/GoToLineNumber.java +++ b/app/src/cc/arduino/view/GoToLineNumber.java @@ -127,7 +127,7 @@ public class GoToLineNumber extends javax.swing.JDialog { private void okActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_okActionPerformed try { int line = Integer.parseInt(lineNumber.getText()); - editor.goToLine(line); + editor.getCurrentTab().goToLine(line); cancelActionPerformed(evt); } catch (Exception e) { // ignore diff --git a/app/src/cc/arduino/view/findreplace/FindReplace.java b/app/src/cc/arduino/view/findreplace/FindReplace.java index cffb6099c..112726d16 100644 --- a/app/src/cc/arduino/view/findreplace/FindReplace.java +++ b/app/src/cc/arduino/view/findreplace/FindReplace.java @@ -292,7 +292,7 @@ public class FindReplace extends javax.swing.JFrame { return false; } - String text = editor.getText(); + String text = editor.getCurrentTab().getText(); if (ignoreCaseBox.isSelected()) { search = search.toLowerCase(); @@ -302,7 +302,7 @@ public class FindReplace extends javax.swing.JFrame { int nextIndex; if (!backwards) { // int selectionStart = editor.textarea.getSelectionStart(); - int selectionEnd = editor.getSelectionStop(); + int selectionEnd = editor.getCurrentTab().getSelectionStop(); nextIndex = text.indexOf(search, selectionEnd); if (wrap && nextIndex == -1) { @@ -311,7 +311,7 @@ public class FindReplace extends javax.swing.JFrame { } } else { // int selectionStart = editor.textarea.getSelectionStart(); - int selectionStart = editor.getSelectionStart() - 1; + int selectionStart = editor.getCurrentTab().getSelectionStart() - 1; if (selectionStart >= 0) { nextIndex = text.lastIndexOf(search, selectionStart); @@ -346,12 +346,12 @@ public class FindReplace extends javax.swing.JFrame { if (backwards) { sketch.handlePrevCode(); this.setVisible(true); - int l = editor.getText().length() - 1; - editor.setSelection(l, l); + int l = editor.getCurrentTab().getText().length() - 1; + editor.getCurrentTab().setSelection(l, l); } else { sketch.handleNextCode(); this.setVisible(true); - editor.setSelection(0, 0); + editor.getCurrentTab().setSelection(0, 0); } return find(wrap, backwards, true, originTab); @@ -365,7 +365,7 @@ public class FindReplace extends javax.swing.JFrame { } if (nextIndex != -1) { - editor.setSelection(nextIndex, nextIndex + search.length()); + editor.getCurrentTab().setSelection(nextIndex, nextIndex + search.length()); return true; } @@ -381,17 +381,17 @@ public class FindReplace extends javax.swing.JFrame { return; } - int newpos = editor.getSelectionStart() - findField.getText().length(); + int newpos = editor.getCurrentTab().getSelectionStart() - findField.getText().length(); if (newpos < 0) { newpos = 0; } - editor.setSelection(newpos, newpos); + editor.getCurrentTab().setSelection(newpos, newpos); boolean foundAtLeastOne = false; if (find(false, false, searchAllFilesBox.isSelected(), -1)) { foundAtLeastOne = true; - editor.setSelectedText(replaceField.getText()); + editor.getCurrentTab().setSelectedText(replaceField.getText()); editor.getSketch().setModified(true); // TODO is this necessary? } @@ -423,13 +423,13 @@ public class FindReplace extends javax.swing.JFrame { editor.getSketch().setCurrentCode(0); // select the first tab } - editor.setSelection(0, 0); // move to the beginning + editor.getCurrentTab().setSelection(0, 0); // move to the beginning boolean foundAtLeastOne = false; while (true) { if (find(false, false, searchAllFilesBox.isSelected(), -1)) { foundAtLeastOne = true; - editor.setSelectedText(replaceField.getText()); + editor.getCurrentTab().setSelectedText(replaceField.getText()); editor.getSketch().setModified(true); // TODO is this necessary? } else { break; diff --git a/app/src/processing/app/Base.java b/app/src/processing/app/Base.java index 95becc3b0..f2a984679 100644 --- a/app/src/processing/app/Base.java +++ b/app/src/processing/app/Base.java @@ -608,10 +608,10 @@ public class Base { activeEditor.rebuildRecentSketchesMenu(); if (PreferencesData.getBoolean("editor.external")) { try { - int previousCaretPosition = activeEditor.getTextArea().getCaretPosition(); + int previousCaretPosition = activeEditor.getCurrentTab().getTextArea().getCaretPosition(); activeEditor.getSketch().load(true); - if (previousCaretPosition < activeEditor.getText().length()) { - activeEditor.getTextArea().setCaretPosition(previousCaretPosition); + if (previousCaretPosition < activeEditor.getCurrentTab().getText().length()) { + activeEditor.getCurrentTab().getTextArea().setCaretPosition(previousCaretPosition); } } catch (IOException e) { // noop diff --git a/app/src/processing/app/Editor.java b/app/src/processing/app/Editor.java index 1079f4439..0ada0155e 100644 --- a/app/src/processing/app/Editor.java +++ b/app/src/processing/app/Editor.java @@ -29,34 +29,21 @@ import cc.arduino.packages.uploaders.SerialUploader; import cc.arduino.view.GoToLineNumber; import cc.arduino.view.StubMenuListener; import cc.arduino.view.findreplace.FindReplace; -import cc.arduino.UpdatableBoardsLibsFakeURLsHandler; import com.jcraft.jsch.JSchException; import jssc.SerialPortException; -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 processing.app.debug.RunnerException; import processing.app.forms.PasswordAuthorizationDialog; import processing.app.helpers.Keys; import processing.app.helpers.OSUtils; import processing.app.helpers.PreferencesMapException; import processing.app.legacy.PApplet; -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; import processing.app.tools.MenuScroller; import processing.app.tools.Tool; import javax.swing.*; -import javax.swing.border.MatteBorder; import javax.swing.event.*; import javax.swing.text.BadLocationException; -import javax.swing.text.Element; -import javax.swing.text.PlainDocument; import javax.swing.undo.CannotRedoException; import javax.swing.undo.CannotUndoException; import javax.swing.undo.UndoManager; @@ -91,9 +78,12 @@ public class Editor extends JFrame implements RunnerListener { public static final int MAX_TIME_AWAITING_FOR_RESUMING_SERIAL_MONITOR = 10000; - private final Platform platform; + 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 { @@ -170,12 +160,12 @@ public class Editor extends JFrame implements RunnerListener { // currently opened program Sketch sketch; - private EditorLineStatus lineStatus; + EditorLineStatus lineStatus; //JEditorPane editorPane; - private SketchTextArea textarea; - private RTextScrollPane scrollPane; + /** Contains all EditorTabs, of which only one will be visible */ + private JPanel codePanel; //Runner runtime; @@ -263,7 +253,7 @@ public class Editor extends JFrame implements RunnerListener { contentPain.add(pane, BorderLayout.CENTER); Box box = Box.createVerticalBox(); - Box upper = Box.createVerticalBox(); + upper = Box.createVerticalBox(); if (toolbarMenu == null) { toolbarMenu = new JMenu(); @@ -275,9 +265,6 @@ public class Editor extends JFrame implements RunnerListener { header = new EditorHeader(this); upper.add(header); - textarea = createTextArea(); - textarea.setName("editor"); - // assemble console panel, consisting of status area and the console itself JPanel consolePanel = new JPanel(); consolePanel.setLayout(new BorderLayout()); @@ -294,19 +281,9 @@ public class Editor extends JFrame implements RunnerListener { lineStatus = new EditorLineStatus(); consolePanel.add(lineStatus, BorderLayout.SOUTH); - // 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); + codePanel = new JPanel(new BorderLayout()); + upper.add(codePanel); - upper.add(scrollPane); splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, upper, consolePanel); // repaint child panes while resizing @@ -466,44 +443,11 @@ public class Editor extends JFrame implements RunnerListener { * with things in the Preferences window. */ public void applyPreferences() { - - // apply the setting for 'use external editor' boolean external = PreferencesData.getBoolean("editor.external"); - - textarea.setEditable(!external); saveMenuItem.setEnabled(!external); saveAsMenuItem.setEnabled(!external); - - textarea.setCodeFoldingEnabled(PreferencesData.getBoolean("editor.code_folding")); - scrollPane.setFoldIndicatorEnabled(PreferencesData.getBoolean("editor.code_folding")); - scrollPane.setLineNumbersEnabled(PreferencesData.getBoolean("editor.linenumbers")); - - 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); - - } else { - textarea.setBackground(Theme.getColor("editor.bgcolor")); - textarea.setHighlightCurrentLine(Theme.getBoolean("editor.linehighlight")); - textarea.setEditable(true); - } - - // apply changes to the font size for the editor - Font editorFont = scale(PreferencesData.getFont("editor.font")); - textarea.setFont(editorFont); - scrollPane.getGutter().setLineNumberFont(editorFont); - - // 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(); - + for (EditorTab tab: tabs) + tab.applyPreferences(); } @@ -1003,55 +947,14 @@ public class Editor extends JFrame implements RunnerListener { } } return null; - } - - - private SketchTextArea createTextArea() throws IOException { - final SketchTextArea textArea = new SketchTextArea(base.getPdeKeywords()); - textArea.setFocusTraversalKeysEnabled(false); - textArea.requestFocusInWindow(); - textArea.setMarkOccurrences(PreferencesData.getBoolean("editor.advanced")); - textArea.setMarginLineEnabled(false); - textArea.setCodeFoldingEnabled(PreferencesData.getBoolean("editor.code_folding")); - 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(base); - boardLibHandler.openBoardLibManager(evt.getURL()); - } - catch (Exception e) { - try { - platform.openURL(sketch.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()); - - lineStatus.set(lineStart, lineEnd); - }); - ToolTipManager.sharedInstance().registerComponent(textArea); - - configurePopupMenu(textArea); - return textArea; - } + } 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); + for (EditorTab tab : tabs) + tab.updateKeywords(keywords); } - private JMenuItem createToolMenuItem(String className) { + JMenuItem createToolMenuItem(String className) { try { Class toolClass = Class.forName(className); final Tool tool = (Tool) toolClass.newInstance(); @@ -1381,7 +1284,7 @@ public class Editor extends JFrame implements RunnerListener { JMenuItem cutItem = newJMenuItem(tr("Cut"), 'X'); cutItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { - handleCut(); + getCurrentTab().handleCut(); } }); menu.add(cutItem); @@ -1389,7 +1292,7 @@ public class Editor extends JFrame implements RunnerListener { JMenuItem copyItem = newJMenuItem(tr("Copy"), 'C'); copyItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { - textarea.copy(); + getCurrentTab().getTextArea().copy(); } }); menu.add(copyItem); @@ -1397,11 +1300,7 @@ public class Editor extends JFrame implements RunnerListener { JMenuItem copyForumItem = newJMenuItemShift(tr("Copy for Forum"), 'C'); copyForumItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { -// SwingUtilities.invokeLater(new Runnable() { -// public void run() { - new DiscourseFormat(Editor.this, false).show(); -// } -// }); + getCurrentTab().handleHTMLCopy(); } }); menu.add(copyForumItem); @@ -1409,11 +1308,7 @@ public class Editor extends JFrame implements RunnerListener { JMenuItem copyHTMLItem = newJMenuItemAlt(tr("Copy as HTML"), 'C'); copyHTMLItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { -// SwingUtilities.invokeLater(new Runnable() { -// public void run() { - new DiscourseFormat(Editor.this, true).show(); -// } -// }); + getCurrentTab().handleDiscourseCopy(); } }); menu.add(copyHTMLItem); @@ -1421,7 +1316,7 @@ public class Editor extends JFrame implements RunnerListener { JMenuItem pasteItem = newJMenuItem(tr("Paste"), 'V'); pasteItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { - textarea.paste(); + getCurrentTab().handlePaste(); sketch.setModified(true); } }); @@ -1430,7 +1325,7 @@ public class Editor extends JFrame implements RunnerListener { JMenuItem selectAllItem = newJMenuItem(tr("Select All"), 'A'); selectAllItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { - textarea.selectAll(); + getCurrentTab().handleSelectAll(); } }); menu.add(selectAllItem); @@ -1448,7 +1343,7 @@ public class Editor extends JFrame implements RunnerListener { JMenuItem commentItem = newJMenuItem(tr("Comment/Uncomment"), '/'); commentItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { - handleCommentUncomment(); + getCurrentTab().handleCommentUncomment(); } }); menu.add(commentItem); @@ -1457,7 +1352,7 @@ public class Editor extends JFrame implements RunnerListener { increaseIndentItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0)); increaseIndentItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { - handleIndentOutdent(true); + getCurrentTab().handleIndentOutdent(true); } }); menu.add(increaseIndentItem); @@ -1467,7 +1362,7 @@ public class Editor extends JFrame implements RunnerListener { decreseIndentItem.setName("menuDecreaseIndent"); decreseIndentItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { - handleIndentOutdent(false); + getCurrentTab().handleIndentOutdent(false); } }); menu.add(decreseIndentItem); @@ -1481,7 +1376,7 @@ public class Editor extends JFrame implements RunnerListener { find = new FindReplace(Editor.this, Base.FIND_DIALOG_STATE); } if (!OSUtils.isMacOS()) { - find.setFindText(getSelectedText()); + find.setFindText(getCurrentTab().getSelectedText()); } find.setLocationRelativeTo(Editor.this); find.setVisible(true); @@ -1516,7 +1411,7 @@ public class Editor extends JFrame implements RunnerListener { if (find == null) { find = new FindReplace(Editor.this, Base.FIND_DIALOG_STATE); } - find.setFindText(getSelectedText()); + find.setFindText(getCurrentTab().getSelectedText()); } }); menu.add(useSelectionForFindItem); @@ -1586,7 +1481,7 @@ public class Editor extends JFrame implements RunnerListener { public void actionPerformed(ActionEvent e) { try { - textarea.undoLastAction(); + getCurrentTab().handleUndo(); sketch.setModified(true); } catch (CannotUndoException ex) { //System.out.println("Unable to undo: " + ex); @@ -1595,8 +1490,7 @@ public class Editor extends JFrame implements RunnerListener { } protected void updateUndoState() { - - UndoManager undo = textarea.getUndoManager(); + UndoManager undo = getCurrentTab().getUndoManager(); if (undo.canUndo()) { this.setEnabled(true); @@ -1621,7 +1515,7 @@ public class Editor extends JFrame implements RunnerListener { public void actionPerformed(ActionEvent e) { try { - textarea.redoLastAction(); + getCurrentTab().handleRedo(); sketch.setModified(true); } catch (CannotRedoException ex) { //System.out.println("Unable to redo: " + ex); @@ -1630,8 +1524,8 @@ public class Editor extends JFrame implements RunnerListener { } protected void updateRedoState() { - UndoManager undo = textarea.getUndoManager(); - + UndoManager undo = getCurrentTab().getUndoManager(); + if (undo.canRedo()) { redoItem.setEnabled(true); redoItem.setText(undo.getRedoPresentationName()); @@ -1677,240 +1571,82 @@ public class Editor extends JFrame implements RunnerListener { return sketch; } - /** - * 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. + * Gets the currently displaying tab. */ - public SketchTextArea getTextArea() { - return textarea; + public EditorTab getCurrentTab() { + return tabs.get(currentTabIndex); } - - + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . /** - * Get the contents of the current buffer. Used by the Sketch class. + * 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 getText() { - return textarea.getText(); + public void selectTab(final int index) { + currentTabIndex = index; + undoAction.updateUndoState(); + redoAction.updateRedoState(); + updateTitle(); + + // This must be run in the GUI thread + SwingUtilities.invokeLater(() -> { + codePanel.removeAll(); + codePanel.add(tabs.get(index), BorderLayout.CENTER); + tabs.get(index).requestFocus(); // 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 entire contents of the front-most tab. - */ - public void setText(String what) { - textarea.setText(what); + public EditorTab findTab(final SketchCode doc) { + return tabs.get(findTabIndex(doc)); } - - - /** - * 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 int findTabIndex(final SketchCode doc) { + for (int i = 0; i < tabs.size(); ++i) { + if (tabs.get(i).getSketchCode() == doc) + return i; + } + return -1; } - - public void setSelectedText(String what) { - textarea.replaceSelection(what); - } - - public void setSelection(int start, int stop) { - textarea.select(start, stop); - } - - - /** - * 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. - */ - private String getLineText(int line) { - try { - return textarea.getText(textarea.getLineStartOffset(line), textarea.getLineEndOffset(line)); - } catch (BadLocationException e) { - return ""; + public void sketchLoaded(Sketch sketch) { + tabs.clear(); + currentTabIndex = -1; + tabs.ensureCapacity(sketch.getCodeCount()); + for (SketchCode code : sketch.getCodes()) { + try { + addTab(code); + } catch(IOException e) { + // TODO: Improve / move error handling + System.err.println(e); + } } } - - public int getScrollPosition() { - return scrollPane.getVerticalScrollBar().getValue(); - } - - - // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - - /** * Switch between tabs, this swaps out the Document object * that's currently being manipulated. */ protected void setCode(final SketchCodeDocument codeDoc) { - RSyntaxDocument document = (RSyntaxDocument) codeDoc.getDocument(); - - if (document == null) { // this document not yet inited - document = new RSyntaxDocument(new ArduinoTokenMakerFactory(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, codeDoc.getCode().getProgram(), null); - } catch (BadLocationException bl) { - bl.printStackTrace(); - } - // set up this guy's own undo manager -// code.undo = new UndoManager(); - - codeDoc.setDocument(document); - } - - if(codeDoc.getUndo() == null){ - codeDoc.setUndo(new LastUndoableEditAwareUndoManager(textarea, this)); - document.addUndoableEditListener(codeDoc.getUndo()); - } - - // Update the document object that's in use - textarea.switchDocument(document, codeDoc.getUndo()); - - // HACK multiple tabs: for update Listeners of Gutter, forcin call: Gutter.setTextArea(RTextArea) - // BUG: https://github.com/bobbylight/RSyntaxTextArea/issues/84 - scrollPane.setViewportView(textarea); - - textarea.select(codeDoc.getSelectionStart(), codeDoc.getSelectionStop()); - textarea.requestFocus(); // get the caret blinking - - final int position = codeDoc.getScrollPosition(); - - // invokeLater: Expect the document to be rendered correctly to set the new position - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - scrollPane.getVerticalScrollBar().setValue(position); - undoAction.updateUndoState(); - redoAction.updateRedoState(); - } - }); - - updateTitle(); + selectTab(findTabIndex(codeDoc.getCode())); } + protected void addTab(SketchCode code) throws IOException { + EditorTab tab = new EditorTab(this, code); + tabs.add(tab); + } // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - - /** - * Implements Edit → Cut. - */ - private void handleCut() { - textarea.cut(); - } - - - private void handleDiscourseCopy() { - new DiscourseFormat(Editor.this, false).show(); - } - - - private void handleHTMLCopy() { - new DiscourseFormat(Editor.this, true).show(); - } - - - void handleCommentUncomment() { - - Action action = textarea.getActionMap().get(RSyntaxTextAreaEditorKit.rstaToggleCommentAction); - action.actionPerformed(null); - - } - - - private 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); - } - } - - private 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; - } - - private void handleFindReference(ActionEvent e) { - String text = getCurrentKeyword(); + void handleFindReference(ActionEvent e) { + String text = getCurrentTab().getCurrentKeyword(); String referenceFile = base.getPdeKeywords().getReference(text); if (referenceFile == null) { @@ -2000,12 +1736,13 @@ public class Editor extends JFrame implements RunnerListener { } public void removeAllLineHighlights() { - textarea.removeAllLineHighlights(); + for (EditorTab tab : tabs) + tab.getTextArea().removeAllLineHighlights(); } public void addLineHighlight(int line) throws BadLocationException { - textarea.addLineHighlight(line, new Color(1, 0, 0, 0.2f)); - textarea.setCaretPosition(textarea.getLineStartOffset(line)); + getCurrentTab().getTextArea().addLineHighlight(line, new Color(1, 0, 0, 0.2f)); + getCurrentTab().getTextArea().setCaretPosition(getCurrentTab().getTextArea().getLineStartOffset(line)); } private class DefaultStopHandler implements Runnable { @@ -2130,11 +1867,10 @@ public class Editor extends JFrame implements RunnerListener { untitled = false; sketch.setCurrentCode(codeIndex); - textarea.select(selStart, selStop); - scrollPane.getVerticalScrollBar().setValue(scrollPos); + getCurrentTab().setSelection(selStart, selStop); + getCurrentTab().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. @@ -2205,6 +1941,7 @@ public class Editor extends JFrame implements RunnerListener { Base.showWarning(tr("Error"), tr("Could not create the sketch."), e); return false; } + header.rebuild(); updateTitle(); // Disable untitled setting from previous document, if any @@ -2842,9 +2579,9 @@ public class Editor extends JFrame implements RunnerListener { PrinterJob printerJob = PrinterJob.getPrinterJob(); if (pageFormat != null) { //System.out.println("setting page format " + pageFormat); - printerJob.setPrintable(textarea, pageFormat); + printerJob.setPrintable(getCurrentTab().getTextArea(), pageFormat); } else { - printerJob.setPrintable(textarea); + printerJob.setPrintable(getCurrentTab().getTextArea()); } // set the name of the job to the code name printerJob.setJobName(sketch.getCurrentCode().getPrettyName()); @@ -2897,17 +2634,17 @@ public class Editor extends JFrame implements RunnerListener { 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 (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()) { + if (line < 0 || line >= getCurrentTab().getTextArea().getLineCount()) { System.err.println(I18n.format(tr("Bad error line: {0}"), line)); } else { try { @@ -2952,7 +2689,6 @@ public class Editor extends JFrame implements RunnerListener { statusNotice(EMPTY); } - // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . protected void onBoardOrPortChange() { @@ -2966,110 +2702,4 @@ public class Editor extends JFrame implements RunnerListener { } - private void configurePopupMenu(final SketchTextArea textarea){ - - JPopupMenu menu = textarea.getPopupMenu(); - - menu.addSeparator(); - - JMenuItem item = 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 = newJMenuItem(tr("Comment/Uncomment"), '/'); - item.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - handleCommentUncomment(); - } - }); - menu.add(item); - - item = newJMenuItem(tr("Increase Indent"), ']'); - item.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - handleIndentOutdent(true); - } - }); - menu.add(item); - - item = newJMenuItem(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(this::handleFindReference); - 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 = 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 goToLine(int line) { - if (line <= 0) { - return; - } - try { - textarea.setCaretPosition(textarea.getLineStartOffset(line - 1)); - } catch (BadLocationException e) { - //ignore - } - } - } diff --git a/app/src/processing/app/EditorTab.java b/app/src/processing/app/EditorTab.java new file mode 100644 index 000000000..88add6a47 --- /dev/null +++ b/app/src/processing/app/EditorTab.java @@ -0,0 +1,481 @@ +/* -*- 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.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.undo.UndoManager; + +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.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 { + protected Editor editor; + protected SketchTextArea textarea; + protected RTextScrollPane scrollPane; + protected SketchCode code; + + public EditorTab(Editor editor, SketchCode code) throws IOException { + super(new BorderLayout()); + this.editor = editor; + this.code = code; + this.textarea = createTextArea(); + this.scrollPane = createScrollPane(this.textarea); + applyPreferences(); + add(this.scrollPane, BorderLayout.CENTER); + + UndoManager undo = new LastUndoableEditAwareUndoManager(this.textarea, this.editor); + ((RSyntaxDocument)textarea.getDocument()).addUndoableEditListener(undo); + } + + private RSyntaxDocument createDocument() { + 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, code.getProgram(), null); + } catch (BadLocationException bl) { + bl.printStackTrace(); + } + ((SketchCodeDocument) code.getMetadata()).setDocument(document); + + 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() throws IOException { + RSyntaxDocument document = createDocument(); + 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.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); + }); + + 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(editor::handleFindReference); + 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' + if (PreferencesData.getBoolean("editor.external")) { + // disable line highlight and turn off the caret when disabling + textarea.setBackground(Theme.getColor("editor.external.bgcolor")); + textarea.setHighlightCurrentLine(false); + textarea.setEditable(false); + + } else { + textarea.setBackground(Theme.getColor("editor.bgcolor")); + textarea.setHighlightCurrentLine(Theme.getBoolean("editor.linehighlight")); + textarea.setEditable(true); + } + + // apply changes to the font size for the editor + Font editorFont = scale(PreferencesData.getFont("editor.font")); + 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); + } + + /** + * 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 Sketch getSketch() { + return editor.getSketch(); + } + + /** + * Get the SketchCodeDocument that is being edited in this tab. + */ + public SketchCode getSketchCode() { + return this.code; + } + + /** + * 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) { + textarea.setText(what); + } + + 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); + + } + + 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); + } + } + + void handleUndo() { + textarea.undoLastAction(); + } + + void handleRedo() { + textarea.redoLastAction(); + } + + public UndoManager getUndoManager() { + return textarea.getUndoManager(); + } + + 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 void requestFocus() { + /** If focus is requested, focus the textarea instead. */ + textarea.requestFocus(); + } + +} \ No newline at end of file diff --git a/app/src/processing/app/EditorToolbar.java b/app/src/processing/app/EditorToolbar.java index ba349e801..a4fbe97d3 100644 --- a/app/src/processing/app/EditorToolbar.java +++ b/app/src/processing/app/EditorToolbar.java @@ -456,7 +456,7 @@ public class EditorToolbar extends JComponent implements MouseInputListener, Key shiftPressed = !shiftPressed; repaint(); } - // Return false to continue processing this keyEvent + // Return false to continue processing this keyEvent return false; } } diff --git a/app/src/processing/app/Sketch.java b/app/src/processing/app/Sketch.java index be8729744..9500aaa31 100644 --- a/app/src/processing/app/Sketch.java +++ b/app/src/processing/app/Sketch.java @@ -95,6 +95,7 @@ public class Sketch { } protected void load(boolean forceUpdate) throws IOException { + current = null; data.load(); for (SketchCode code : data.getCodes()) { @@ -104,6 +105,7 @@ public class Sketch { // set the main file to be the current tab if (editor != null) { + editor.sketchLoaded(this); setCurrentCode(currentIndex, forceUpdate); } } @@ -317,7 +319,7 @@ public class Sketch { // first get the contents of the editor text area if (current.getCode().isModified()) { - current.getCode().setProgram(editor.getText()); + current.getCode().setProgram(editor.getCurrentTab().getText()); try { // save this new SketchCode current.getCode().save(); @@ -362,9 +364,9 @@ public class Sketch { // (unfortunately this will kill positions for carets etc) editor.handleOpenUnchecked(newMainFile, currentIndex, - editor.getSelectionStart(), - editor.getSelectionStop(), - editor.getScrollPosition()); + editor.getCurrentTab().getSelectionStart(), + editor.getCurrentTab().getSelectionStop(), + editor.getCurrentTab().getScrollPosition()); // get the changes into the sketchbook menu // (re-enabled in 0115 to fix bug #332) @@ -398,7 +400,17 @@ public class Sketch { return; } ensureExistence(); - data.addCode((new SketchCodeDocument(this, newFile)).getCode()); + SketchCode code = (new SketchCodeDocument(this, newFile)).getCode(); + try { + editor.addTab(code); + } catch (IOException e) { + Base.showWarning(tr("Error"), + I18n.format( + "Failed to open tab for new file" + ), e); + return; + } + data.addCode(code); } // sort the entries @@ -542,7 +554,7 @@ public class Sketch { // first get the contents of the editor text area if (current.getCode().isModified()) { - current.getCode().setProgram(editor.getText()); + current.getCode().setProgram(editor.getCurrentTab().getText()); } // don't do anything if not actually modified @@ -698,7 +710,7 @@ public class Sketch { // grab the contents of the current tab before saving // first get the contents of the editor text area if (current.getCode().isModified()) { - current.getCode().setProgram(editor.getText()); + current.getCode().setProgram(editor.getCurrentTab().getText()); } // save the other tabs to their new location @@ -734,9 +746,9 @@ public class Sketch { editor.handleOpenUnchecked(newFile, currentIndex, - editor.getSelectionStart(), - editor.getSelectionStop(), - editor.getScrollPosition()); + editor.getCurrentTab().getSelectionStart(), + editor.getCurrentTab().getSelectionStop(), + editor.getCurrentTab().getScrollPosition()); // Name changed, rebuild the sketch menus //editor.sketchbook.rebuildMenusAsync(); @@ -950,9 +962,9 @@ public class Sketch { buffer.append(">\n"); } buffer.append('\n'); - buffer.append(editor.getText()); - editor.setText(buffer.toString()); - editor.setSelection(0, 0); // scroll to start + buffer.append(editor.getCurrentTab().getText()); + editor.getCurrentTab().setText(buffer.toString()); + editor.getCurrentTab().setSelection(0, 0); // scroll to start setModified(true); } @@ -976,26 +988,12 @@ public class Sketch { } // get the text currently being edited - if (current != null) { - current.getCode().setProgram(editor.getText()); - current.setSelectionStart(editor.getSelectionStart()); - current.setSelectionStop(editor.getSelectionStop()); - current.setScrollPosition(editor.getScrollPosition()); - } + if (current != null) + current.getCode().setProgram(editor.getCurrentTab().getText()); current = (SketchCodeDocument) data.getCode(which).getMetadata(); currentIndex = which; - - if (SwingUtilities.isEventDispatchThread()) { - editor.setCode(current); - } else { - try { - SwingUtilities.invokeAndWait(() -> editor.setCode(current)); - } catch (Exception e) { - e.printStackTrace(); - } - } - + editor.setCode(current); editor.header.rebuild(); } @@ -1051,7 +1049,7 @@ public class Sketch { // make sure the user didn't hide the sketch folder ensureExistence(); - current.getCode().setProgram(editor.getText()); + current.getCode().setProgram(editor.getCurrentTab().getText()); // TODO record history here //current.history.record(program, SketchHistory.RUN); diff --git a/app/src/processing/app/SketchCodeDocument.java b/app/src/processing/app/SketchCodeDocument.java index 681f0af91..66f399e35 100644 --- a/app/src/processing/app/SketchCodeDocument.java +++ b/app/src/processing/app/SketchCodeDocument.java @@ -5,23 +5,11 @@ import java.io.File; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import javax.swing.text.Document; -import javax.swing.undo.UndoManager; -public class SketchCodeDocument implements DocumentListener{ +public class SketchCodeDocument implements DocumentListener { private SketchCode code; private Sketch sketch; - 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; - - // saved positions from last time this tab was used - private int selectionStart; - private int selectionStop; - private int scrollPosition; public SketchCodeDocument(Sketch sketch, SketchCode code) { this.code = code; @@ -30,40 +18,7 @@ public class SketchCodeDocument implements DocumentListener{ } public SketchCodeDocument(Sketch sketch, File file) { - this.code = new SketchCode(file, this); - this.sketch = sketch; - } - - public UndoManager getUndo() { - return undo; - } - - public void setUndo(UndoManager undo) { - this.undo = undo; - } - - public int getSelectionStart() { - return selectionStart; - } - - public void setSelectionStart(int selectionStart) { - this.selectionStart = selectionStart; - } - - public int getSelectionStop() { - return selectionStop; - } - - public void setSelectionStop(int selectionStop) { - this.selectionStop = selectionStop; - } - - public int getScrollPosition() { - return scrollPosition; - } - - public void setScrollPosition(int scrollPosition) { - this.scrollPosition = scrollPosition; + this(sketch, new SketchCode(file)); } public SketchCode getCode() { @@ -74,12 +29,7 @@ public class SketchCodeDocument implements DocumentListener{ this.code = code; } - public Document getDocument() { - return document; - } - public void setDocument(Document document) { - this.document = document; document.addDocumentListener(this); } @@ -96,8 +46,7 @@ public class SketchCodeDocument implements DocumentListener{ @Override public void changedUpdate(DocumentEvent e) { - // Callback for when styles in the current document change. - // This method is never called. + // ignore } } diff --git a/app/src/processing/app/syntax/SketchTextArea.java b/app/src/processing/app/syntax/SketchTextArea.java index aace515fb..a2d78703a 100644 --- a/app/src/processing/app/syntax/SketchTextArea.java +++ b/app/src/processing/app/syntax/SketchTextArea.java @@ -38,7 +38,6 @@ import org.fife.ui.rsyntaxtextarea.*; import org.fife.ui.rsyntaxtextarea.Token; import org.fife.ui.rtextarea.RTextArea; import org.fife.ui.rtextarea.RTextAreaUI; -import org.fife.ui.rtextarea.RUndoManager; import processing.app.Base; import processing.app.BaseNoGui; import processing.app.PreferencesData; @@ -47,9 +46,7 @@ import javax.swing.event.EventListenerList; import javax.swing.event.HyperlinkEvent; import javax.swing.event.HyperlinkListener; import javax.swing.text.BadLocationException; -import javax.swing.text.Document; import javax.swing.text.Segment; -import javax.swing.undo.UndoManager; import java.awt.*; import java.awt.event.MouseEvent; import java.io.File; @@ -73,7 +70,8 @@ public class SketchTextArea extends RSyntaxTextArea { private PdeKeywords pdeKeywords; - public SketchTextArea(PdeKeywords pdeKeywords) throws IOException { + public SketchTextArea(RSyntaxDocument document, PdeKeywords pdeKeywords) throws IOException { + super(document); this.pdeKeywords = pdeKeywords; installFeatures(); fixCtrlDeleteBehavior(); @@ -153,25 +151,6 @@ public class SketchTextArea extends RSyntaxTextArea { return this.getSelectedText() != null; } - public void switchDocument(Document document, UndoManager newUndo) { - - // HACK: Dont discard changes on curret UndoManager. - // BUG: https://github.com/bobbylight/RSyntaxTextArea/issues/84 - setUndoManager(null); // bypass reset current undo manager... - - super.setDocument(document); - - setUndoManager((RUndoManager) newUndo); - - // HACK: Complement previous hack (hide code folding on switch) | Drawback: Lose folding state -// if(sketch.getCodeCount() > 1 && textarea.isCodeFoldingEnabled()){ -// textarea.setCodeFoldingEnabled(false); -// textarea.setCodeFoldingEnabled(true); -// } - - - } - @Override protected RTAMouseListener createMouseListener() { return new SketchTextAreaMouseListener(this); diff --git a/app/src/processing/app/tools/DiscourseFormat.java b/app/src/processing/app/tools/DiscourseFormat.java index c631df8bd..c79f7d110 100644 --- a/app/src/processing/app/tools/DiscourseFormat.java +++ b/app/src/processing/app/tools/DiscourseFormat.java @@ -25,6 +25,7 @@ package processing.app.tools; import org.fife.ui.rsyntaxtextarea.Token; import processing.app.Editor; +import processing.app.EditorTab; import processing.app.syntax.SketchTextArea; import javax.swing.text.BadLocationException; @@ -65,9 +66,9 @@ public class DiscourseFormat { * from the actual Processing Tab ready to send to the processing discourse * web (copy & paste) */ - public DiscourseFormat(Editor editor, boolean html) { + public DiscourseFormat(Editor editor, EditorTab tab, boolean html) { this.editor = editor; - this.textarea = editor.getTextArea(); + this.textarea = tab.getTextArea(); this.html = html; } diff --git a/app/src/processing/app/tools/FixEncoding.java b/app/src/processing/app/tools/FixEncoding.java index d76d9b1cb..89c2957be 100644 --- a/app/src/processing/app/tools/FixEncoding.java +++ b/app/src/processing/app/tools/FixEncoding.java @@ -69,10 +69,8 @@ public class FixEncoding implements Tool { SketchCode code = sketch.getCode(i); code.setProgram(loadWithLocalEncoding(code.getFile())); code.setModified(true); // yes, because we want them to save this + editor.findTab(code).setText(code.getProgram()); } - // Update the currently visible program with its code - editor.setText(sketch.getCurrentCode().getProgram()); - } catch (IOException e) { String msg = tr("An error occurred while trying to fix the file encoding.\nDo not attempt to save this sketch as it may overwrite\nthe old version. Use Open to re-open the sketch and try again.\n") + diff --git a/app/test/processing/app/BlockCommentGeneratesOneUndoActionTest.java b/app/test/processing/app/BlockCommentGeneratesOneUndoActionTest.java index 411cb5de6..1a213eb1e 100644 --- a/app/test/processing/app/BlockCommentGeneratesOneUndoActionTest.java +++ b/app/test/processing/app/BlockCommentGeneratesOneUndoActionTest.java @@ -55,7 +55,7 @@ public class BlockCommentGeneratesOneUndoActionTest extends AbstractGUITest { GuiActionRunner.execute(new GuiQuery() { protected Frame executeInEDT() { - window.getEditor().handleCommentUncomment(); + window.getEditor().getCurrentTab().handleCommentUncomment(); return window.getEditor(); }