88import java .io .FilenameFilter ;
99import java .lang .reflect .InvocationTargetException ;
1010
11+ import javax .swing .JOptionPane ;
12+
13+
1114public class ChangeDetector implements WindowFocusListener {
1215 private Sketch sketch ;
13-
1416 private Editor editor ;
1517
16- // private boolean enabled = true;
17- private boolean enabled = true ; // broken on OS X (possibly fixed? tested and it seems to work)
18-
18+ private boolean enabled = true ;
1919 private boolean skip = false ;
2020
21+
2122 public ChangeDetector (Editor editor ) {
2223 this .sketch = editor .sketch ;
2324 this .editor = editor ;
2425 }
2526
26- @ Override
27- public void windowGainedFocus (WindowEvent e ) {
28- //remove the detector from main if it is disabled during runtime (due to an error?)
29- if (!enabled || !Preferences .getBoolean ("editor.watcher" )) {
30- editor .removeWindowFocusListener (this );
31- return ;
32- }
33- //if they selected no, skip the next focus event
34- if (skip ) {
35- skip = false ;
36- return ;
37- }
38- checkFileChangeAsync ();
39- }
40-
41- private void checkFileChangeAsync () {
42- Thread th = new Thread (new Runnable () {
43- @ Override
44- public void run () {
45- checkFileChange ();
46- }
47- });
48- th .start ();
49- }
50-
51- private void showErrorAsync (final String title , final String message ,
52- final Exception e ) {
53- EventQueue .invokeLater (new Runnable () {
54- @ Override
55- public void run () {
56- Base .showError (title , message , e );
57- }
58- });
59- }
60-
61- private void showWarningAsync (final String title , final String message ) {
62- EventQueue .invokeLater (new Runnable () {
63- @ Override
64- public void run () {
65- Base .showWarning (title , message );
66- }
67- });
68- }
69-
70- private int showYesNoQuestionAsync (final Frame editor , final String title ,
71- final String message1 ,
72- final String message2 ) {
73- final int [] res = { -1 };
74- try {
75- //have to wait for a response on this one
76- EventQueue .invokeAndWait (new Runnable () {
77- @ Override
78- public void run () {
79- res [0 ] = Base .showYesNoQuestion (editor , title , message1 , message2 );
80- }
81- });
82- } catch (InvocationTargetException e ) {
83- //occurs if Base.showYesNoQuestion throws an error, so, shouldn't happen
84- e .printStackTrace ();
85- } catch (InterruptedException e ) {
86- //occurs if the EDT is interrupted, so, shouldn't happen
87- e .printStackTrace ();
88- }
89- return res [0 ];
90- }
91-
92- private void rebuildHeaderAsync () {
93- EventQueue .invokeLater (new Runnable () {
94- @ Override
95- public void run () {
96- editor .header .rebuild ();
97- }
98- });
99- }
10027
10128 private void checkFileChange () {
10229 //check that the content of each of the files in sketch matches what is in memory
10330 if (sketch == null ) {
10431 return ;
10532 }
10633
107- //make sure the sketch folder exists at all. if it does not, it will be re-saved, and no changes will be detected
108- //
34+ // make sure the sketch folder exists at all.
35+ // if it does not, it will be re-saved, and no changes will be detected
10936 sketch .ensureExistence ();
110- //check file count first
37+
38+ // check file count first
11139 File sketchFolder = sketch .getFolder ();
112- int fileCount = sketchFolder .list (new FilenameFilter () {
113- //return true if the file is a code file for this mode
40+ File [] sketchFiles = sketchFolder .listFiles (new FilenameFilter () {
11441 @ Override
11542 public boolean accept (File dir , String name ) {
11643 for (String s : editor .getMode ().getExtensions ()) {
@@ -120,28 +47,28 @@ public boolean accept(File dir, String name) {
12047 }
12148 return false ;
12249 }
123- }).length ;
50+ });
51+ int fileCount = sketchFiles .length ;
12452
12553 if (fileCount != sketch .getCodeCount ()) {
126- //if they chose to reload and there aren't any files left
54+ // if they chose to reload and there aren't any files left
12755 if (reloadSketch (null ) && fileCount < 1 ) {
12856 try {
12957 //make a blank file
13058 sketch .getMainFile ().createNewFile ();
13159 } catch (Exception e1 ) {
13260 //if that didn't work, tell them it's un-recoverable
133- showErrorAsync ("Reload failed" , "The sketch contains no code files." ,
134- e1 );
61+ showErrorEDT ("Reload failed" , "The sketch contains no code files." , e1 );
13562 //don't try to reload again after the double fail
13663 //this editor is probably trashed by this point, but a save-as might be possible
13764 skip = true ;
13865 return ;
13966 }
14067 //it's okay to do this without confirmation, because they already confirmed to deleting the unsaved changes above
14168 sketch .reload ();
142- showWarningAsync ("Modified Reload" ,
143- "You cannot delete the last code file in a sketch.\n "
144- + "A new blank sketch file has been generated for you." );
69+ showWarningEDT ("Modified Reload" ,
70+ "You cannot delete the last code file in a sketch.\n " +
71+ "A new blank sketch file has been generated for you." );
14572
14673 }
14774 return ;
@@ -150,62 +77,152 @@ public boolean accept(File dir, String name) {
15077 SketchCode [] codes = sketch .getCode ();
15178 for (SketchCode sc : codes ) {
15279 File sketchFile = sc .getFile ();
153- if (!sketchFile .exists ()) {
154- //if a file in the sketch was not found, then it must have been deleted externally
155- //so reload the sketch
156- reloadSketch (sc );
157- return ;
158- }
159- //if a file's tab was saved before the file was
160- if (sketchFile .lastModified () > sc .lastModified ()) {
80+ if (sketchFile .exists ()) {
81+ long diff = sketchFile .lastModified () - sc .lastModified ();
82+ if (diff != 0 ) {
83+ if (Base .isMacOS () && diff == 1000L ) {
84+ // Mac OS X has a one second difference. Not sure if it's a Java bug
85+ // or something else about how OS X is writing files.
86+ continue ;
87+ }
88+ System .out .println (sketchFile .getName () + " " + diff );
89+ reloadSketch (sc );
90+ return ;
91+ }
92+ } else {
93+ // If a file in the sketch was not found, then it must have been
94+ // deleted externally, so reload the sketch.
16195 reloadSketch (sc );
16296 return ;
16397 }
16498 }
16599 }
166100
101+
167102 private void setSketchCodeModified (SketchCode sc ) {
168103 sc .setModified (true );
169104 sketch .setModified (true );
170105 }
171106
172- //returns true if the files in the sketch have been reloaded
173- private boolean reloadSketch (SketchCode changed ) {
174- //new Exception().printStackTrace(System.out);
175- int response = showYesNoQuestionAsync (editor ,
176- "File Modified" ,
177- "Your sketch has been modified externally.<br>Would you like to reload the sketch?" ,
178- "If you reload the sketch, any unsaved changes will be lost!" );
179- if (response == 0 ) {
180- //reload the sketch
181107
108+ /**
109+ * @param changed The file that was known to be modified
110+ * @return true if the files in the sketch have been reloaded
111+ */
112+ private boolean reloadSketch (SketchCode changed ) {
113+ int response = blockingYesNoPrompt (editor ,
114+ "File Modified" ,
115+ "Your sketch has been modified externally.<br>" +
116+ "Would you like to reload the sketch?" ,
117+ "If you reload the sketch, any unsaved changes will be lost." );
118+ if (response == JOptionPane .YES_OPTION ) {
182119 sketch .reload ();
183- rebuildHeaderAsync ();
120+ rebuildHeaderEDT ();
184121 return true ;
122+ }
123+
124+ // they said no (or canceled), make it possible to stop the msgs by saving
125+ if (changed != null ) {
126+ //set it to be modified so that it will actually save to disk when the user saves from inside processing
127+ setSketchCodeModified (changed );
128+
185129 } else {
186- //they said no, make it possible for them to stop the errors by saving
187- if (changed != null ) {
188- //set it to be modified so that it will actually save to disk when the user saves from inside processing
189- setSketchCodeModified (changed );
190- } else {
191- //the number of files changed, so they may be working with a file that doesn't exist any more
192- //find the files that are missing, and mark them as modified
193- for (SketchCode sc : sketch .getCode ()) {
194- if (!sc .getFile ().exists ()) {
195- setSketchCodeModified (sc );
196- }
130+ // Because the number of files changed, they may be working with a file
131+ // that doesn't exist any more. So find the files that are missing,
132+ // and mark them as modified so that the next "Save" will write them.
133+ for (SketchCode sc : sketch .getCode ()) {
134+ if (!sc .getFile ().exists ()) {
135+ setSketchCodeModified (sc );
197136 }
198- //if files were simply added, then nothing needs done
199137 }
200- rebuildHeaderAsync ();
201- skip = true ;
202- return false ;
138+ // If files were simply added, then nothing needs done
203139 }
140+ rebuildHeaderEDT ();
141+ skip = true ;
142+ return false ;
204143 }
205144
145+
206146 @ Override
207147 public void windowLostFocus (WindowEvent e ) {
208148 //shouldn't need to do anything here
209149 }
210150
151+
152+ @ Override
153+ public void windowGainedFocus (WindowEvent e ) {
154+ if (enabled ) {
155+ //remove the detector from main if it is disabled during runtime (due to an error?)
156+ //if (!enabled || !Preferences.getBoolean("editor.watcher")) {
157+ //editor.removeWindowFocusListener(this);
158+ //} else if (skip) {
159+
160+ // if they selected no, skip the next focus event
161+ if (skip ) {
162+ skip = false ;
163+
164+ } else {
165+ new Thread (new Runnable () {
166+ @ Override
167+ public void run () {
168+ checkFileChange ();
169+ }
170+ }).start ();
171+ }
172+ }
173+ }
174+
175+
176+ private void showErrorEDT (final String title , final String message ,
177+ final Exception e ) {
178+ EventQueue .invokeLater (new Runnable () {
179+ @ Override
180+ public void run () {
181+ Base .showError (title , message , e );
182+ }
183+ });
184+ }
185+
186+
187+ private void showWarningEDT (final String title , final String message ) {
188+ EventQueue .invokeLater (new Runnable () {
189+ @ Override
190+ public void run () {
191+ Base .showWarning (title , message );
192+ }
193+ });
194+ }
195+
196+
197+ private int blockingYesNoPrompt (final Frame editor , final String title ,
198+ final String message1 ,
199+ final String message2 ) {
200+ final int [] result = { -1 }; // yuck
201+ try {
202+ //have to wait for a response on this one
203+ EventQueue .invokeAndWait (new Runnable () {
204+ @ Override
205+ public void run () {
206+ result [0 ] = Base .showYesNoQuestion (editor , title , message1 , message2 );
207+ }
208+ });
209+ } catch (InvocationTargetException e ) {
210+ //occurs if Base.showYesNoQuestion throws an error, so, shouldn't happen
211+ e .getTargetException ().printStackTrace ();
212+ } catch (InterruptedException e ) {
213+ //occurs if the EDT is interrupted, so, shouldn't happen
214+ e .printStackTrace ();
215+ }
216+ return result [0 ];
217+ }
218+
219+
220+ private void rebuildHeaderEDT () {
221+ EventQueue .invokeLater (new Runnable () {
222+ @ Override
223+ public void run () {
224+ editor .header .rebuild ();
225+ }
226+ });
227+ }
211228}
0 commit comments