diff --git a/app/src/processing/app/Sketch.java b/app/src/processing/app/Sketch.java index 4e059e3db..8a7a9fa32 100644 --- a/app/src/processing/app/Sketch.java +++ b/app/src/processing/app/Sketch.java @@ -24,21 +24,25 @@ package processing.app; import processing.app.ui.Editor; -import processing.app.ui.ProgressFrame; import processing.app.ui.Recent; import processing.app.ui.Toolkit; import processing.core.*; +import java.awt.Color; import java.awt.Component; import java.awt.Container; +import java.awt.EventQueue; import java.awt.FileDialog; import java.awt.event.ActionListener; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; import java.io.*; import java.util.List; import javax.swing.*; +import javax.swing.border.EmptyBorder; /** @@ -792,7 +796,7 @@ public class Sketch { String newParentDir = null; String newName = null; - final String oldName2 = folder.getName(); + String oldName = folder.getName(); // TODO rewrite this to use shared version from PApplet final String PROMPT = Language.text("save"); if (Preferences.getBoolean("chooser.files.native")) { @@ -805,8 +809,8 @@ public class Sketch { // default to the parent folder of where this was fd.setDirectory(folder.getParent()); } - String oldName = folder.getName(); - fd.setFile(oldName); + String oldFolderName = folder.getName(); + fd.setFile(oldFolderName); fd.setVisible(true); newParentDir = fd.getDirectory(); newName = fd.getFile(); @@ -868,7 +872,7 @@ public class Sketch { // just use "save" here instead, because the user will have received a // message (from the operating system) about "do you want to replace?" return save(); - } + } // check to see if the user is trying to save this sketch inside itself try { @@ -927,21 +931,21 @@ public class Sketch { } }); + // Kick off a background thread to copy everything *but* the .pde files. + // Due to the poor way (dating back to the late 90s with DBN) that our + // save() and saveAs() methods have been implemented to return booleans, + // there isn't a good way to return a value to the calling thread without + // a good bit of refactoring (that should be done at some point). + // As a result, this method will return 'true' before the full "Save As" + // has completed, which will cause problems in weird cases. + // For instance, saving an untitled sketch that has an enormous data + // folder while quitting. The save thread to move those data folder files + // won't have finished before this returns true, and the PDE may quit + // before the SwingWorker completes its job. + // https://github.com/processing/processing/issues/3843 + startSaveAsThread(oldName, newName, newFolder, copyItems); - final File newFolder2 = newFolder; - final File[] copyItems2 = copyItems; - final String newName2 = newName; - - // Create a new event dispatch thread- to display ProgressBar - // while Saving As - javax.swing.SwingUtilities.invokeLater(new Runnable() { - public void run() { - new ProgressFrame(copyItems2, newFolder2, oldName2, newName2, editor); - } - }); - - - // save the other tabs to their new location + // save the other tabs to their new location (main tab saved below) for (int i = 1; i < codeCount; i++) { File newFile = new File(newFolder, code[i].getFileName()); code[i].saveAs(newFile); @@ -971,6 +975,171 @@ public class Sketch { } + void startSaveAsThread(final String oldName, final String newName, + final File newFolder, final File[] copyItems) { + EventQueue.invokeLater(new Runnable() { + public void run() { + final JFrame frame = new JFrame("Saving \u201C" + newName + "\u201C..."); + // the UI of the progress bar follows + frame.setDefaultCloseOperation(WindowConstants.HIDE_ON_CLOSE); +// frame.setBounds(200, 200, 400, 140); +// frame.setResizable(false); + + Box box = Box.createVerticalBox(); + box.setBorder(new EmptyBorder(16, 16, 16, 16)); +// JPanel panel = new JPanel(null); +// frame.add(panel); +// frame.setContentPane(panel); // uh, both? + + if (Platform.isMacOS()) { + frame.setBackground(Color.WHITE); + } + + JLabel label = new JLabel("Saving additional files from the sketch folder..."); +// JLabel label = new JLabel("Saving " + oldName + " as " + newName + "..."); +// label.setBounds(40, 20, 300, 20); + box.add(label); + + final JProgressBar progressBar = new JProgressBar(0, 100); + // no luck, stuck with ugly on OS X + //progressBar.putClientProperty("JComponent.sizeVariant", "regular"); + progressBar.setValue(0); +// progressBar.setBounds(40, 50, 300, 30); + progressBar.setStringPainted(true); + box.add(progressBar); + +// panel.add(progressBar); +// panel.add(label); + + frame.getContentPane().add(box); + frame.pack(); + frame.setLocationRelativeTo(editor); + Toolkit.setIcon(frame); + frame.setVisible(true); + + new SwingWorker() { + + @Override + protected Void doInBackground() throws Exception { + addPropertyChangeListener(new PropertyChangeListener() { + public void propertyChange(PropertyChangeEvent evt) { + if ("progress".equals(evt.getPropertyName())) { + System.out.println(evt.getNewValue()); + progressBar.setValue((Integer) evt.getNewValue()); + } + } + }); + + long totalSize = 0; + for (File copyable : copyItems) { + totalSize += Util.calcSize(copyable); + } + + long progress = 0; + setProgress(0); + for (File copyable : copyItems) { + if (copyable.isDirectory()) { + copyDir(copyable, + new File(newFolder, copyable.getName()), + progress, totalSize); + progress += Util.calcSize(copyable); + } else { + copyFile(copyable, + new File(newFolder, copyable.getName()), + progress, totalSize); + if (Util.calcSize(copyable) < 512 * 1024) { + // If the file length > 0.5MB, the copyFile() function has + // been redesigned to change progress every 0.5MB so that + // the progress bar doesn't stagnate during that time + progress += Util.calcSize(copyable); + setProgress((int) (progress * 100L / totalSize)); + } + } + } + return null; + } + + + /** + * Overloaded copyFile that is called whenever a Save As is being done, + * so that the ProgressBar is updated for very large files as well. + */ + void copyFile(File sourceFile, File targetFile, + long progress, long totalSize) throws IOException { + BufferedInputStream from = + new BufferedInputStream(new FileInputStream(sourceFile)); + BufferedOutputStream to = + new BufferedOutputStream(new FileOutputStream(targetFile)); + byte[] buffer = new byte[16 * 1024]; + int bytesRead; + int progRead = 0; + while ((bytesRead = from.read(buffer)) != -1) { + to.write(buffer, 0, bytesRead); + progRead += bytesRead; + if (progRead >= 512 * 1024) { // to update progress bar every 0.5MB + progress += progRead; + //progressBar.setValue((int) Math.min(Math.ceil(progress * 100.0 / totalSize), 100)); + setProgress((int) (100L * progress / totalSize)); + progRead = 0; + } + } + // Final update to progress bar + setProgress((int) (100L * progress / totalSize)); + + from.close(); + from = null; + to.flush(); + to.close(); + to = null; + + targetFile.setLastModified(sourceFile.lastModified()); + targetFile.setExecutable(sourceFile.canExecute()); + } + + + long copyDir(File sourceDir, File targetDir, + long progress, long totalSize) throws IOException { + // Overloaded copyDir so that the Save As progress bar gets updated when the + // files are in folders as well (like in the data folder) + if (sourceDir.equals(targetDir)) { + final String urDum = "source and target directories are identical"; + throw new IllegalArgumentException(urDum); + } + targetDir.mkdirs(); + String files[] = sourceDir.list(); + for (String filename : files) { + // Ignore dot files (.DS_Store), dot folders (.svn) while copying + if (filename.charAt(0) == '.') { + continue; + } + + File source = new File(sourceDir, filename); + File target = new File(targetDir, filename); + if (source.isDirectory()) { + progress = copyDir(source, target, progress, totalSize); + //progressBar.setValue((int) Math.min(Math.ceil(progress * 100.0 / totalSize), 100)); + setProgress((int) (100L * progress / totalSize)); + target.setLastModified(source.lastModified()); + } else { + copyFile(source, target, progress, totalSize); + progress += source.length(); + //progressBar.setValue((int) Math.min(Math.ceil(progress * 100.0 / totalSize), 100)); + setProgress((int) (100L * progress / totalSize)); + } + } + return progress; + } + + + @Override + public void done() { + frame.dispose(); + } + }.execute(); + } + }); + } + /** * Update internal state for new sketch name or folder location. diff --git a/app/src/processing/app/Util.java b/app/src/processing/app/Util.java index 0eb42201c..14621f64e 100644 --- a/app/src/processing/app/Util.java +++ b/app/src/processing/app/Util.java @@ -340,12 +340,22 @@ public class Util { } + /** + * Function to return the length of the file, or entire directory, including + * the component files and sub-folders if passed. + * @param file The file or folder to calculate + */ + static public long calcSize(File file) { + return file.isFile() ? file.length() : Util.calcFolderSize(file); + } + + /** * Calculate the size of the contents of a folder. * Used to determine whether sketches are empty or not. * Note that the function calls itself recursively. */ - static public int calcFolderSize(File folder) { + static public long calcFolderSize(File folder) { int size = 0; String files[] = folder.list(); diff --git a/app/src/processing/app/ui/Editor.java b/app/src/processing/app/ui/Editor.java index 41585683d..553804096 100644 --- a/app/src/processing/app/ui/Editor.java +++ b/app/src/processing/app/ui/Editor.java @@ -2540,6 +2540,29 @@ public abstract class Editor extends JFrame implements RunnerListener { } + /* + public void handleSaveAs() { + statusNotice(Language.text("editor.status.saving")); + sketch.saveAs(); + } + + + public void handleSaveAsSuccess() { + statusNotice(Language.text("editor.status.saving.done")); + } + + + public void handleSaveAsCanceled() { + statusNotice(Language.text("editor.status.saving.canceled")); + } + + + public void handleSaveAsError(Exception e) { + statusError(e); + } + */ + + /** * Handler for File → Page Setup. */ diff --git a/app/src/processing/app/ui/ProgressFrame.java b/app/src/processing/app/ui/ProgressFrame.java index 0dcab2e87..93b97f0cd 100644 --- a/app/src/processing/app/ui/ProgressFrame.java +++ b/app/src/processing/app/ui/ProgressFrame.java @@ -15,9 +15,6 @@ import javax.swing.JPanel; import javax.swing.JProgressBar; import javax.swing.SwingWorker; -import processing.app.Language; -import processing.app.Util; - // TODO This code was contributed and needs a lot of work. [fry] // + It probably shouldn't a "Frame" object at all. @@ -33,137 +30,7 @@ import processing.app.Util; public class ProgressFrame extends JFrame implements PropertyChangeListener { private JProgressBar progressBar; private JLabel label; - private File[] copyItems; - private File newFolder; private File addFile, sourceFile; - private Editor editor; - - - /** Create a new background thread to Save As */ - public class TaskSaveAs extends SwingWorker { - - @Override - protected Void doInBackground() throws Exception { - // a large part of the file copying happens in this background - // thread - - long totalSize = 0; - for (File copyable : copyItems) { - totalSize += calcSize(copyable); - } - - long progress = 0; - setProgress(0); - for (File copyable : ProgressFrame.this.copyItems) { - // loop to copy over the items that make sense, and to set the - // current progress - - if (copyable.isDirectory()) { - copyDir(copyable, - new File(ProgressFrame.this.newFolder, copyable.getName()), - progress, totalSize); - progress += calcSize(copyable); - } else { - copyFile(copyable, - new File(ProgressFrame.this.newFolder, copyable.getName()), - progress, totalSize); - if (calcSize(copyable) < 524288) { - // If the file length > 0.5MB, the copyFile() function has - // been redesigned to change progress every 0.5MB so that - // the progress bar doesn't stagnate during that time - progress += calcSize(copyable); - setProgress((int) (progress * 100L / totalSize)); - } - } - } - return null; - } - - - /** - * Overloaded copyFile that is called whenever a Save As is being done, - * so that the ProgressBar is updated for very large files as well. - */ - private void copyFile(File sourceFile, File targetFile, - double progress, long totalSize) throws IOException { - BufferedInputStream from = new BufferedInputStream(new FileInputStream(sourceFile)); - BufferedOutputStream to = new BufferedOutputStream(new FileOutputStream(targetFile)); - byte[] buffer = new byte[16 * 1024]; - int bytesRead; - int totalRead = 0; - while ((bytesRead = from.read(buffer)) != -1) { - to.write(buffer, 0, bytesRead); - totalRead += bytesRead; - if (totalRead >= 512 * 1024) { // to update progress bar every 0.5MB - progress += totalRead; - setProgressBarStatus((int) Math.min(Math.ceil(progress * 100.0 / totalSize), 100)); - totalRead = 0; - } - } - if (sourceFile.length() > 512 * 1024) { - // Update the progress bar one final time if file size is more than 0.5MB, - // otherwise, the update is handled either by the copyDir function, - // or directly by ProgressFrame.TaskSaveAs.doInBackground() - progress += totalRead; - setProgressBarStatus((int) Math.min(Math.ceil(progress * 100.0 / totalSize), 100)); - } - from.close(); - from = null; - to.flush(); - to.close(); - to = null; - - targetFile.setLastModified(sourceFile.lastModified()); - targetFile.setExecutable(sourceFile.canExecute()); - } - - - private double copyDir(File sourceDir, File targetDir, - double progress, long totalSize) throws IOException { - // Overloaded copyDir so that the Save As progress bar gets updated when the - // files are in folders as well (like in the data folder) - if (sourceDir.equals(targetDir)) { - final String urDum = "source and target directories are identical"; - throw new IllegalArgumentException(urDum); - } - targetDir.mkdirs(); - String files[] = sourceDir.list(); - for (int i = 0; i < files.length; i++) { - // Ignore dot files (.DS_Store), dot folders (.svn) while copying - if (files[i].charAt(0) == '.') - continue; - //if (files[i].equals(".") || files[i].equals("..")) continue; - File source = new File(sourceDir, files[i]); - File target = new File(targetDir, files[i]); - if (source.isDirectory()) { - //target.mkdirs(); - progress = copyDir(source, target, progress, totalSize); - setProgressBarStatus((int) Math.min(Math.ceil(progress * 100.0 / totalSize), 100)); - target.setLastModified(source.lastModified()); - } else { - copyFile(source, target, progress, totalSize); - // Update SaveAs progress bar - progress += source.length(); - setProgressBarStatus((int) Math.min(Math.ceil(progress * 100.0 / totalSize), 100)); - } - } - return progress; - } - - - public void setProgressBarStatus(int status) { - setProgress(status); - } - - - @Override - public void done() { - // to close the progress bar automatically when done, and to - // print that Saving is done in Message Area - editor.statusNotice(Language.text("editor.status.saving.done")); - dispose(); - } - } /** Create a new background thread to add a file. */ @@ -245,49 +112,10 @@ public class ProgressFrame extends JFrame implements PropertyChangeListener { } - /** Use for Save As */ - public ProgressFrame(File[] c, File nf, String oldName, String newName, - Editor editor) { - // initialize a copyItems and newFolder, which are used for file - // copying in the background thread - copyItems = c; - newFolder = nf; - this.editor = editor; - - // the UI of the progress bar follows - setDefaultCloseOperation(HIDE_ON_CLOSE); - setBounds(200, 200, 400, 140); - setResizable(false); - setTitle("Saving As..."); - JPanel panel = new JPanel(null); - add(panel); - setContentPane(panel); - label = new JLabel("Saving " + oldName + " as " + newName + "..."); - label.setBounds(40, 20, 300, 20); - - progressBar = new JProgressBar(0, 100); - progressBar.setValue(0); - progressBar.setBounds(40, 50, 300, 30); - progressBar.setStringPainted(true); - - panel.add(progressBar); - panel.add(label); - Toolkit.setIcon(this); - this.setVisible(true); - - // create an instance of TaskSaveAs and run execute() on this - // instance to start background thread - TaskSaveAs t = new TaskSaveAs(); - t.addPropertyChangeListener(this); - t.execute(); - } - - /** Used for Add File */ public ProgressFrame(File sf, File add, Editor editor) { addFile = add; sourceFile = sf; - this.editor = editor; // the UI of the progress bar follows setDefaultCloseOperation(HIDE_ON_CLOSE); @@ -319,15 +147,6 @@ public class ProgressFrame extends JFrame implements PropertyChangeListener { } - /** - * Function to return the length of the file, or entire directory, including - * the component files and sub-folders if passed. - */ - long calcSize(File file) { - return file.isFile() ? file.length() : Util.calcFolderSize(file); - } - - /** * Detects a change in the property of the background task, * i.e., is called when the size of files already copied changes. diff --git a/java/src/processing/mode/java/JavaEditor.java b/java/src/processing/mode/java/JavaEditor.java index 33efb289c..29268e2d7 100644 --- a/java/src/processing/mode/java/JavaEditor.java +++ b/java/src/processing/mode/java/JavaEditor.java @@ -1730,39 +1730,6 @@ public class JavaEditor extends Editor { @Override public boolean handleSave(boolean immediately) { - //System.out.println("handleSave " + immediately); - - //log("handleSave, viewing autosave? " + viewingAutosaveBackup); - /* If user wants to save a backup, the backup sketch should get - * copied to the main sketch directory, simply reload the main sketch. - */ - if(viewingAutosaveBackup){ - /* - File files[] = autosaver.getSketchBackupFolder().listFiles(); - File src = autosaver.getSketchBackupFolder(), dst = autosaver - .getActualSketchFolder(); - for (File f : files) { - log("Copying " + f.getAbsolutePath() + " to " + dst.getAbsolutePath()); - try { - if (f.isFile()) { - f.delete(); - Base.copyFile(f, new File(dst + File.separator + f.getName())); - } else { - Base.removeDir(f); - Base.copyDir(f, new File(dst + File.separator + f.getName())); - } - } catch (IOException e) { - e.printStackTrace(); - } - } - File sk = autosaver.getActualSketchFolder(); - Base.removeDir(autosaver.getAutoSaveDir()); - //handleOpenInternal(sk.getAbsolutePath() + File.separator + sk.getName() + ".pde"); - getBase().handleOpen(sk.getAbsolutePath() + File.separator + sk.getName() + ".pde"); - //viewingAutosaveBackup = false; - */ - } - // note modified tabs final List modified = new ArrayList(); for (int i = 0; i < getSketch().getCodeCount(); i++) { @@ -1795,9 +1762,6 @@ public class JavaEditor extends Editor { } - private boolean viewingAutosaveBackup; - - /** * Set text contents of a specific tab. Updates underlying document and text * area. Clears Breakpoints. diff --git a/todo.txt b/todo.txt index 636a6f71f..feca29ff3 100644 --- a/todo.txt +++ b/todo.txt @@ -73,10 +73,11 @@ X https://github.com/processing/processing/issues/3806 X https://github.com/processing/processing/pull/3817 X CM list should be sortable by status and author name X https://github.com/processing/processing/issues/3608 +X "update all" button appears to do nothing in library manager +X https://github.com/processing/processing/issues/3837 +X https://github.com/processing/processing/pull/3842 _ All contributions listed after install button is clicked _ https://github.com/processing/processing/issues/3826 -_ "update all" button appears to do nothing in library manager -_ https://github.com/processing/processing/issues/3837 jakub X Make preprocessor scope-aware @@ -232,7 +233,8 @@ _ pass through the source to update licenses _ add Processing Foundation as 2012-15 _ update license info to state gplv2 not v3 _ run through that online license checker - +_ save() and saveAs() need to be refactored +_ https://github.com/processing/processing/issues/3843 breakage @@ -962,6 +964,8 @@ _ this may already work with SingleInstance stuff DIST / Mac OS X +_ client properties +_ https://developer.apple.com/library/mac/technotes/tn2007/tn2196.html _ built-in images: http://nadeausoftware.com/articles/2008/12/mac_java_tip_how_access_mac_specific_nsimage_icons _ replace appbundler with the Java 8 packager _ https://github.com/processing/processing/issues/3071