diff --git a/app/src/cc/arduino/packages/formatter/AStyle.java b/app/src/cc/arduino/packages/formatter/AStyle.java index fc74ea06a..5550223f7 100644 --- a/app/src/cc/arduino/packages/formatter/AStyle.java +++ b/app/src/cc/arduino/packages/formatter/AStyle.java @@ -94,7 +94,6 @@ public class AStyle implements Tool { textArea.getUndoManager().beginInternalAtomicEdit(); editor.removeAllLineHighlights(); editor.getCurrentTab().setText(formattedText); - editor.getSketch().setModified(true); textArea.getUndoManager().endInternalAtomicEdit(); if (line != -1 && lineOffset != -1) { diff --git a/app/src/cc/arduino/view/findreplace/FindReplace.java b/app/src/cc/arduino/view/findreplace/FindReplace.java index 112726d16..81891ab1b 100644 --- a/app/src/cc/arduino/view/findreplace/FindReplace.java +++ b/app/src/cc/arduino/view/findreplace/FindReplace.java @@ -392,7 +392,6 @@ public class FindReplace extends javax.swing.JFrame { if (find(false, false, searchAllFilesBox.isSelected(), -1)) { foundAtLeastOne = true; editor.getCurrentTab().setSelectedText(replaceField.getText()); - editor.getSketch().setModified(true); // TODO is this necessary? } if (!foundAtLeastOne) { @@ -430,7 +429,6 @@ public class FindReplace extends javax.swing.JFrame { if (find(false, false, searchAllFilesBox.isSelected(), -1)) { foundAtLeastOne = true; editor.getCurrentTab().setSelectedText(replaceField.getText()); - editor.getSketch().setModified(true); // TODO is this necessary? } else { break; } diff --git a/app/src/processing/app/Editor.java b/app/src/processing/app/Editor.java index 0ada0155e..414a8fe78 100644 --- a/app/src/processing/app/Editor.java +++ b/app/src/processing/app/Editor.java @@ -1317,7 +1317,6 @@ public class Editor extends JFrame implements RunnerListener { pasteItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { getCurrentTab().handlePaste(); - sketch.setModified(true); } }); menu.add(pasteItem); @@ -1482,7 +1481,6 @@ public class Editor extends JFrame implements RunnerListener { public void actionPerformed(ActionEvent e) { try { getCurrentTab().handleUndo(); - sketch.setModified(true); } catch (CannotUndoException ex) { //System.out.println("Unable to undo: " + ex); //ex.printStackTrace(); @@ -1516,7 +1514,6 @@ public class Editor extends JFrame implements RunnerListener { public void actionPerformed(ActionEvent e) { try { getCurrentTab().handleRedo(); - sketch.setModified(true); } catch (CannotRedoException ex) { //System.out.println("Unable to redo: " + ex); //ex.printStackTrace(); @@ -1622,7 +1619,7 @@ public class Editor extends JFrame implements RunnerListener { tabs.ensureCapacity(sketch.getCodeCount()); for (SketchCode code : sketch.getCodes()) { try { - addTab(code); + addTab(code, null); } catch(IOException e) { // TODO: Improve / move error handling System.err.println(e); @@ -1638,8 +1635,18 @@ public class Editor extends JFrame implements RunnerListener { selectTab(findTabIndex(codeDoc.getCode())); } - protected void addTab(SketchCode code) throws IOException { - EditorTab tab = new EditorTab(this, code); + /** + * Add a new tab. + * + * @param code + * 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 + */ + protected void addTab(SketchCode code, String contents) throws IOException { + EditorTab tab = new EditorTab(this, code, contents); tabs.add(tab); } diff --git a/app/src/processing/app/EditorTab.java b/app/src/processing/app/EditorTab.java index 98b8c04ba..cb39423f5 100644 --- a/app/src/processing/app/EditorTab.java +++ b/app/src/processing/app/EditorTab.java @@ -51,6 +51,7 @@ 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 org.fife.ui.rtextarea.RUndoManager; import cc.arduino.UpdatableBoardsLibsFakeURLsHandler; import processing.app.helpers.DocumentTextChangeListener; @@ -63,37 +64,64 @@ import processing.app.tools.DiscourseFormat; /** * Single tab, editing a single file, in the main window. */ -public class EditorTab extends JPanel { +public class EditorTab extends JPanel implements SketchCode.TextStorage { protected Editor editor; protected SketchTextArea textarea; protected RTextScrollPane scrollPane; protected SketchCode code; + protected boolean modified; - public EditorTab(Editor editor, SketchCode code) throws IOException { + /** + * Create a new EditorTab + * + * @param editor + * The Editor this tab runs in + * @param code + * 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, SketchCode code, String contents) + throws IOException { super(new BorderLayout()); + + // Load initial contents contents from file if nothing was specified. + if (contents == null) { + contents = code.load(); + modified = false; + } else { + modified = true; + } + this.editor = editor; this.code = code; - this.textarea = createTextArea(); + RSyntaxDocument document = createDocument(contents); + this.textarea = createTextArea(document); this.scrollPane = createScrollPane(this.textarea); applyPreferences(); add(this.scrollPane, BorderLayout.CENTER); - - UndoManager undo = new LastUndoableEditAwareUndoManager(this.textarea, this.editor); - ((RSyntaxDocument)textarea.getDocument()).addUndoableEditListener(undo); + + RUndoManager undo = new LastUndoableEditAwareUndoManager(this.textarea, this.editor); + document.addUndoableEditListener(undo); + textarea.setUndoManager(undo); + code.setStorage(this); } - private RSyntaxDocument createDocument() { + 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, code.getProgram(), null); + document.insertString(0, contents, null); } catch (BadLocationException bl) { bl.printStackTrace(); } document.addDocumentListener(new DocumentTextChangeListener( - () -> code.setModified(true))); + () -> setModified(true))); return document; } @@ -112,8 +140,8 @@ public class EditorTab extends JPanel { return scrollPane; } - private SketchTextArea createTextArea() throws IOException { - RSyntaxDocument document = createDocument(); + private SketchTextArea createTextArea(RSyntaxDocument document) + throws IOException { final SketchTextArea textArea = new SketchTextArea(document, editor.base.getPdeKeywords()); textArea.setName("editor"); textArea.setFocusTraversalKeysEnabled(false); @@ -318,6 +346,29 @@ public class EditorTab extends JPanel { textarea.setText(what); } + /** + * Is the text modified since the last save / load? + */ + public boolean isModified() { + return modified; + } + + /** + * Clear modified status. Should only be called by SketchCode through + * the TextStorage interface. + */ + public void clearModified() { + setModified(false); + } + + private void setModified(boolean value) { + if (value != modified) { + modified = value; + // TODO: Improve decoupling + editor.getSketch().calcModified(); + } + } + public String getSelectedText() { return textarea.getSelectedText(); } diff --git a/app/src/processing/app/Sketch.java b/app/src/processing/app/Sketch.java index 9500aaa31..e6c07bac1 100644 --- a/app/src/processing/app/Sketch.java +++ b/app/src/processing/app/Sketch.java @@ -57,9 +57,6 @@ import static processing.app.I18n.tr; public class Sketch { private final Editor editor; - /** true if any of the files have been modified. */ - private boolean modified; - private SketchCodeDocument current; private int currentIndex; @@ -319,7 +316,6 @@ public class Sketch { // first get the contents of the editor text area if (current.getCode().isModified()) { - current.getCode().setProgram(editor.getCurrentTab().getText()); try { // save this new SketchCode current.getCode().save(); @@ -402,7 +398,7 @@ public class Sketch { ensureExistence(); SketchCode code = (new SketchCodeDocument(this, newFile)).getCode(); try { - editor.addTab(code); + editor.addTab(code, ""); } catch (IOException e) { Base.showWarning(tr("Error"), I18n.format( @@ -509,31 +505,16 @@ public class Sketch { setCurrentCode((currentIndex + 1) % data.getCodeCount()); } - /** - * Sets the modified value for the code in the frontmost tab. + * Called whenever the modification status of one of the tabs changes. TODO: + * Move this code into Editor and improve decoupling from EditorTab */ - public void setModified(boolean state) { - //System.out.println("setting modified to " + state); - //new Exception().printStackTrace(); - current.getCode().setModified(state); - calcModified(); - } - - - private void calcModified() { - modified = false; - for (SketchCode code : data.getCodes()) { - if (code.isModified()) { - modified = true; - break; - } - } + public void calcModified() { editor.header.repaint(); if (OSUtils.isMacOS()) { // http://developer.apple.com/qa/qa2001/qa1146.html - Object modifiedParam = modified ? Boolean.TRUE : Boolean.FALSE; + Object modifiedParam = isModified() ? Boolean.TRUE : Boolean.FALSE; editor.getRootPane().putClientProperty("windowModified", modifiedParam); editor.getRootPane().putClientProperty("Window.documentModified", modifiedParam); } @@ -541,7 +522,11 @@ public class Sketch { public boolean isModified() { - return modified; + for (SketchCode code : data.getCodes()) { + if (code.isModified()) + return true; + } + return false; } @@ -552,14 +537,6 @@ public class Sketch { // make sure the user didn't hide the sketch folder ensureExistence(); - // first get the contents of the editor text area - if (current.getCode().isModified()) { - current.getCode().setProgram(editor.getCurrentTab().getText()); - } - - // don't do anything if not actually modified - //if (!modified) return false; - if (isReadOnly(BaseNoGui.librariesIndexer.getInstalledLibraries(), BaseNoGui.getExamplesPath())) { Base.showMessage(tr("Sketch is read-only"), tr("Some files are marked \"read-only\", so you'll\n" + @@ -605,7 +582,6 @@ public class Sketch { } data.save(); - calcModified(); return true; } @@ -707,12 +683,6 @@ public class Sketch { // 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.getCode().isModified()) { - current.getCode().setProgram(editor.getCurrentTab().getText()); - } - // save the other tabs to their new location for (SketchCode code : data.getCodes()) { if (data.indexOfCode(code) == 0) continue; @@ -912,18 +882,6 @@ public class Sketch { data.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.getCode().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. - data.getCode(0).setModified(true); - } } return true; } @@ -965,7 +923,6 @@ public class Sketch { buffer.append(editor.getCurrentTab().getText()); editor.getCurrentTab().setText(buffer.toString()); editor.getCurrentTab().setSelection(0, 0); // scroll to start - setModified(true); } @@ -987,10 +944,6 @@ public class Sketch { return; } - // get the text currently being edited - if (current != null) - current.getCode().setProgram(editor.getCurrentTab().getText()); - current = (SketchCodeDocument) data.getCode(which).getMetadata(); currentIndex = which; editor.setCode(current); @@ -1049,8 +1002,6 @@ public class Sketch { // make sure the user didn't hide the sketch folder ensureExistence(); - current.getCode().setProgram(editor.getCurrentTab().getText()); - // TODO record history here //current.history.record(program, SketchHistory.RUN); @@ -1223,7 +1174,6 @@ public class Sketch { "but anything besides the code will be lost."), null); try { data.getFolder().mkdirs(); - modified = true; for (SketchCode code : data.getCodes()) { code.save(); // this will force a save diff --git a/app/src/processing/app/tools/FixEncoding.java b/app/src/processing/app/tools/FixEncoding.java index 89c2957be..3ee5d0554 100644 --- a/app/src/processing/app/tools/FixEncoding.java +++ b/app/src/processing/app/tools/FixEncoding.java @@ -67,9 +67,7 @@ public class FixEncoding implements Tool { try { for (int i = 0; i < sketch.getCodeCount(); i++) { 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()); + editor.findTab(code).setText(loadWithLocalEncoding(code.getFile())); } } catch (IOException e) { String msg = diff --git a/arduino-core/src/processing/app/SketchCode.java b/arduino-core/src/processing/app/SketchCode.java index 558234d15..23b2f70de 100644 --- a/arduino-core/src/processing/app/SketchCode.java +++ b/arduino-core/src/processing/app/SketchCode.java @@ -45,15 +45,34 @@ public class SketchCode { */ private File file; - /** - * Text of the program text for this tab - */ - private String program; - - private boolean modified; - private Object metadata; + /** + * Interface for an in-memory storage of text file contents. This is + * intended to allow a GUI to keep modified text in memory, and allow + * SketchCode to check for changes when needed. + */ + public static interface TextStorage { + /** Get the current text */ + public String getText(); + + /** + * Is the text modified externally, after the last call to + * clearModified() or setText()? + */ + public boolean isModified(); + + /** Clear the isModified() result value */ + public void clearModified(); + }; + + /** + * A storage for this file's text. This can be set by a GUI, so we can + * have access to any modified version of the file. This can be null, + * in which case the file is never modified, and saving is a no-op. + */ + private TextStorage storage; + public SketchCode(File file) { init(file, null); } @@ -65,13 +84,14 @@ public class SketchCode { private void init(File file, Object metadata) { this.file = file; this.metadata = metadata; + } - try { - load(); - } catch (IOException e) { - System.err.println( - I18n.format(tr("Error while loading code {0}"), file.getName())); - } + /** + * Set an in-memory storage for this file's text, that will be queried + * on compile, save, and whenever the text is needed. + */ + public void setStorage(TextStorage text) { + this.storage = text; } @@ -159,36 +179,33 @@ public class SketchCode { public String getProgram() { - return program; - } + if (storage != null) + return storage.getText(); - - public void setProgram(String replacement) { - program = replacement; - } - - - public void setModified(boolean modified) { - this.modified = modified; + return null; } public boolean isModified() { - return modified; + if (storage != null) + return storage.isModified(); + return false; } /** - * Load this piece of code from a file. + * Load this piece of code from a file and return the contents. This + * completely ignores any changes in the linked storage, if any, and + * just directly reads the file. */ - private void load() throws IOException { - program = BaseNoGui.loadFile(file); + public String load() throws IOException { + String text = BaseNoGui.loadFile(file); - if (program == null) { + if (text == null) { throw new IOException(); } - if (program.indexOf('\uFFFD') != -1) { + if (text.indexOf('\uFFFD') != -1) { System.err.println( I18n.format( tr("\"{0}\" contains unrecognized characters. " + @@ -201,8 +218,7 @@ public class SketchCode { ); System.err.println(); } - - setModified(false); + return text; } @@ -211,11 +227,11 @@ public class SketchCode { * flag is set or not. */ public void save() throws IOException { - // TODO re-enable history - //history.record(s, SketchHistory.SAVE); + if (storage == null) + return; /* Nothing to do */ - BaseNoGui.saveFile(program, file); - setModified(false); + BaseNoGui.saveFile(storage.getText(), file); + storage.clearModified(); } @@ -223,7 +239,10 @@ public class SketchCode { * Save this file to another location, used by Sketch.saveAs() */ public void saveAs(File newFile) throws IOException { - BaseNoGui.saveFile(program, newFile); + if (storage == null) + return; /* Nothing to do */ + + BaseNoGui.saveFile(storage.getText(), newFile); }