Skip to content

Commit ed56ffa

Browse files
committed
trying to clear up the "Save As" progress frame and avoid bugs
1 parent dde9984 commit ed56ffa

File tree

6 files changed

+230
-241
lines changed

6 files changed

+230
-241
lines changed

app/src/processing/app/Sketch.java

Lines changed: 189 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -24,21 +24,25 @@
2424
package processing.app;
2525

2626
import processing.app.ui.Editor;
27-
import processing.app.ui.ProgressFrame;
2827
import processing.app.ui.Recent;
2928
import processing.app.ui.Toolkit;
3029
import processing.core.*;
3130

31+
import java.awt.Color;
3232
import java.awt.Component;
3333
import java.awt.Container;
34+
import java.awt.EventQueue;
3435
import java.awt.FileDialog;
3536
import java.awt.event.ActionListener;
3637
import java.awt.event.KeyAdapter;
3738
import java.awt.event.KeyEvent;
39+
import java.beans.PropertyChangeEvent;
40+
import java.beans.PropertyChangeListener;
3841
import java.io.*;
3942
import java.util.List;
4043

4144
import javax.swing.*;
45+
import javax.swing.border.EmptyBorder;
4246

4347

4448
/**
@@ -792,7 +796,7 @@ public boolean saveAs() throws IOException {
792796
String newParentDir = null;
793797
String newName = null;
794798

795-
final String oldName2 = folder.getName();
799+
String oldName = folder.getName();
796800
// TODO rewrite this to use shared version from PApplet
797801
final String PROMPT = Language.text("save");
798802
if (Preferences.getBoolean("chooser.files.native")) {
@@ -805,8 +809,8 @@ public boolean saveAs() throws IOException {
805809
// default to the parent folder of where this was
806810
fd.setDirectory(folder.getParent());
807811
}
808-
String oldName = folder.getName();
809-
fd.setFile(oldName);
812+
String oldFolderName = folder.getName();
813+
fd.setFile(oldFolderName);
810814
fd.setVisible(true);
811815
newParentDir = fd.getDirectory();
812816
newName = fd.getFile();
@@ -868,7 +872,7 @@ public boolean saveAs() throws IOException {
868872
// just use "save" here instead, because the user will have received a
869873
// message (from the operating system) about "do you want to replace?"
870874
return save();
871-
}
875+
}
872876

873877
// check to see if the user is trying to save this sketch inside itself
874878
try {
@@ -927,21 +931,21 @@ public boolean accept(File file) {
927931
}
928932
});
929933

930-
931-
final File newFolder2 = newFolder;
932-
final File[] copyItems2 = copyItems;
933-
final String newName2 = newName;
934-
935-
// Create a new event dispatch thread- to display ProgressBar
936-
// while Saving As
937-
javax.swing.SwingUtilities.invokeLater(new Runnable() {
938-
public void run() {
939-
new ProgressFrame(copyItems2, newFolder2, oldName2, newName2, editor);
940-
}
941-
});
942-
943-
944-
// save the other tabs to their new location
934+
// Kick off a background thread to copy everything *but* the .pde files.
935+
// Due to the poor way (dating back to the late 90s with DBN) that our
936+
// save() and saveAs() methods have been implemented to return booleans,
937+
// there isn't a good way to return a value to the calling thread without
938+
// a good bit of refactoring (that should be done at some point).
939+
// As a result, this method will return 'true' before the full "Save As"
940+
// has completed, which will cause problems in weird cases.
941+
// For instance, saving an untitled sketch that has an enormous data
942+
// folder while quitting. The save thread to move those data folder files
943+
// won't have finished before this returns true, and the PDE may quit
944+
// before the SwingWorker completes its job.
945+
// https://github.com/processing/processing/issues/3843
946+
startSaveAsThread(oldName, newName, newFolder, copyItems);
947+
948+
// save the other tabs to their new location (main tab saved below)
945949
for (int i = 1; i < codeCount; i++) {
946950
File newFile = new File(newFolder, code[i].getFileName());
947951
code[i].saveAs(newFile);
@@ -971,6 +975,171 @@ public void run() {
971975
}
972976

973977

978+
void startSaveAsThread(final String oldName, final String newName,
979+
final File newFolder, final File[] copyItems) {
980+
EventQueue.invokeLater(new Runnable() {
981+
public void run() {
982+
final JFrame frame = new JFrame("Saving \u201C" + newName + "\u201C...");
983+
// the UI of the progress bar follows
984+
frame.setDefaultCloseOperation(WindowConstants.HIDE_ON_CLOSE);
985+
// frame.setBounds(200, 200, 400, 140);
986+
// frame.setResizable(false);
987+
988+
Box box = Box.createVerticalBox();
989+
box.setBorder(new EmptyBorder(16, 16, 16, 16));
990+
// JPanel panel = new JPanel(null);
991+
// frame.add(panel);
992+
// frame.setContentPane(panel); // uh, both?
993+
994+
if (Platform.isMacOS()) {
995+
frame.setBackground(Color.WHITE);
996+
}
997+
998+
JLabel label = new JLabel("Saving additional files from the sketch folder...");
999+
// JLabel label = new JLabel("Saving " + oldName + " as " + newName + "...");
1000+
// label.setBounds(40, 20, 300, 20);
1001+
box.add(label);
1002+
1003+
final JProgressBar progressBar = new JProgressBar(0, 100);
1004+
// no luck, stuck with ugly on OS X
1005+
//progressBar.putClientProperty("JComponent.sizeVariant", "regular");
1006+
progressBar.setValue(0);
1007+
// progressBar.setBounds(40, 50, 300, 30);
1008+
progressBar.setStringPainted(true);
1009+
box.add(progressBar);
1010+
1011+
// panel.add(progressBar);
1012+
// panel.add(label);
1013+
1014+
frame.getContentPane().add(box);
1015+
frame.pack();
1016+
frame.setLocationRelativeTo(editor);
1017+
Toolkit.setIcon(frame);
1018+
frame.setVisible(true);
1019+
1020+
new SwingWorker<Void, Void>() {
1021+
1022+
@Override
1023+
protected Void doInBackground() throws Exception {
1024+
addPropertyChangeListener(new PropertyChangeListener() {
1025+
public void propertyChange(PropertyChangeEvent evt) {
1026+
if ("progress".equals(evt.getPropertyName())) {
1027+
System.out.println(evt.getNewValue());
1028+
progressBar.setValue((Integer) evt.getNewValue());
1029+
}
1030+
}
1031+
});
1032+
1033+
long totalSize = 0;
1034+
for (File copyable : copyItems) {
1035+
totalSize += Util.calcSize(copyable);
1036+
}
1037+
1038+
long progress = 0;
1039+
setProgress(0);
1040+
for (File copyable : copyItems) {
1041+
if (copyable.isDirectory()) {
1042+
copyDir(copyable,
1043+
new File(newFolder, copyable.getName()),
1044+
progress, totalSize);
1045+
progress += Util.calcSize(copyable);
1046+
} else {
1047+
copyFile(copyable,
1048+
new File(newFolder, copyable.getName()),
1049+
progress, totalSize);
1050+
if (Util.calcSize(copyable) < 512 * 1024) {
1051+
// If the file length > 0.5MB, the copyFile() function has
1052+
// been redesigned to change progress every 0.5MB so that
1053+
// the progress bar doesn't stagnate during that time
1054+
progress += Util.calcSize(copyable);
1055+
setProgress((int) (progress * 100L / totalSize));
1056+
}
1057+
}
1058+
}
1059+
return null;
1060+
}
1061+
1062+
1063+
/**
1064+
* Overloaded copyFile that is called whenever a Save As is being done,
1065+
* so that the ProgressBar is updated for very large files as well.
1066+
*/
1067+
void copyFile(File sourceFile, File targetFile,
1068+
long progress, long totalSize) throws IOException {
1069+
BufferedInputStream from =
1070+
new BufferedInputStream(new FileInputStream(sourceFile));
1071+
BufferedOutputStream to =
1072+
new BufferedOutputStream(new FileOutputStream(targetFile));
1073+
byte[] buffer = new byte[16 * 1024];
1074+
int bytesRead;
1075+
int progRead = 0;
1076+
while ((bytesRead = from.read(buffer)) != -1) {
1077+
to.write(buffer, 0, bytesRead);
1078+
progRead += bytesRead;
1079+
if (progRead >= 512 * 1024) { // to update progress bar every 0.5MB
1080+
progress += progRead;
1081+
//progressBar.setValue((int) Math.min(Math.ceil(progress * 100.0 / totalSize), 100));
1082+
setProgress((int) (100L * progress / totalSize));
1083+
progRead = 0;
1084+
}
1085+
}
1086+
// Final update to progress bar
1087+
setProgress((int) (100L * progress / totalSize));
1088+
1089+
from.close();
1090+
from = null;
1091+
to.flush();
1092+
to.close();
1093+
to = null;
1094+
1095+
targetFile.setLastModified(sourceFile.lastModified());
1096+
targetFile.setExecutable(sourceFile.canExecute());
1097+
}
1098+
1099+
1100+
long copyDir(File sourceDir, File targetDir,
1101+
long progress, long totalSize) throws IOException {
1102+
// Overloaded copyDir so that the Save As progress bar gets updated when the
1103+
// files are in folders as well (like in the data folder)
1104+
if (sourceDir.equals(targetDir)) {
1105+
final String urDum = "source and target directories are identical";
1106+
throw new IllegalArgumentException(urDum);
1107+
}
1108+
targetDir.mkdirs();
1109+
String files[] = sourceDir.list();
1110+
for (String filename : files) {
1111+
// Ignore dot files (.DS_Store), dot folders (.svn) while copying
1112+
if (filename.charAt(0) == '.') {
1113+
continue;
1114+
}
1115+
1116+
File source = new File(sourceDir, filename);
1117+
File target = new File(targetDir, filename);
1118+
if (source.isDirectory()) {
1119+
progress = copyDir(source, target, progress, totalSize);
1120+
//progressBar.setValue((int) Math.min(Math.ceil(progress * 100.0 / totalSize), 100));
1121+
setProgress((int) (100L * progress / totalSize));
1122+
target.setLastModified(source.lastModified());
1123+
} else {
1124+
copyFile(source, target, progress, totalSize);
1125+
progress += source.length();
1126+
//progressBar.setValue((int) Math.min(Math.ceil(progress * 100.0 / totalSize), 100));
1127+
setProgress((int) (100L * progress / totalSize));
1128+
}
1129+
}
1130+
return progress;
1131+
}
1132+
1133+
1134+
@Override
1135+
public void done() {
1136+
frame.dispose();
1137+
}
1138+
}.execute();
1139+
}
1140+
});
1141+
}
1142+
9741143

9751144
/**
9761145
* Update internal state for new sketch name or folder location.

app/src/processing/app/Util.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -340,12 +340,22 @@ static public void removeDescendants(File dir) {
340340
}
341341

342342

343+
/**
344+
* Function to return the length of the file, or entire directory, including
345+
* the component files and sub-folders if passed.
346+
* @param file The file or folder to calculate
347+
*/
348+
static public long calcSize(File file) {
349+
return file.isFile() ? file.length() : Util.calcFolderSize(file);
350+
}
351+
352+
343353
/**
344354
* Calculate the size of the contents of a folder.
345355
* Used to determine whether sketches are empty or not.
346356
* Note that the function calls itself recursively.
347357
*/
348-
static public int calcFolderSize(File folder) {
358+
static public long calcFolderSize(File folder) {
349359
int size = 0;
350360

351361
String files[] = folder.list();

app/src/processing/app/ui/Editor.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2540,6 +2540,29 @@ public boolean handleSaveAs() {
25402540
}
25412541

25422542

2543+
/*
2544+
public void handleSaveAs() {
2545+
statusNotice(Language.text("editor.status.saving"));
2546+
sketch.saveAs();
2547+
}
2548+
2549+
2550+
public void handleSaveAsSuccess() {
2551+
statusNotice(Language.text("editor.status.saving.done"));
2552+
}
2553+
2554+
2555+
public void handleSaveAsCanceled() {
2556+
statusNotice(Language.text("editor.status.saving.canceled"));
2557+
}
2558+
2559+
2560+
public void handleSaveAsError(Exception e) {
2561+
statusError(e);
2562+
}
2563+
*/
2564+
2565+
25432566
/**
25442567
* Handler for File &rarr; Page Setup.
25452568
*/

0 commit comments

Comments
 (0)