This repository has been archived by the owner on Mar 6, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
EraseDisk3.java
2818 lines (2438 loc) · 129 KB
/
EraseDisk3.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
Erase Disk #3 - Erase Empty Space on Disk Drive
Written by: Keith Fenske, http://kwfenske.github.io/
Wednesday, 23 March 2022
Java class name: EraseDisk3
Copyright (c) 2022 by Keith Fenske. Apache License or GNU GPL.
EraseDisk is a Java 1.4 graphical (GUI) application to erase and test disk
drives or flash drives. Large temporary files are created and filled with
zeros, ones, or pseudo-random data. Previously deleted files are
overwritten. Existing files are not affected. This cleans up an old disk
before it goes in a new location. Don't trust a new disk until you write
data, then read to confirm. One complete test is usually enough. (Repeated
testing may degrade flash drives.) Use this program as follows:
1. If you want to erase an entire disk drive or partition (including all
files), then first "format" or "initialize" the drive according to your
system's usual procedure.
2. Otherwise, empty the "Recycle Bin" or "Trash" folder. This releases
hidden space still allocated to some deleted files.
3. Run this program.
4. Navigate to any folder on the disk drive where temporary files can be
created. Files with names similar to "ERASE123.DAT" are assumed to
belong to this program and will be replaced or deleted without notice.
5. Decide whether you want the program to write only zeros (one pass) or to
write three passes with all ones (0xFF bytes), pseudo-random bytes, then
all zeros (0x00 bytes).
6. Decide whether you want the program to read verify the written data to
test if the drive is working correctly. Skip this step if you intend to
destroy the disk drive after it is erased.
7. Click the "Start" button and be patient.
8. Check that the number of bytes reported by this program agrees with what
your system says for free space. (Java doesn't really know.) Also, Java
doesn't report hardware errors if the operating system recovers. Look at
your system error logs; see Event Viewer on Microsoft Windows.
EraseDisk is not a "secure erase" program and does not meet the DoD 5220.22-M
standard for the United States. Please refer to the following Wikipedia.org
web page:
http://en.wikipedia.org/wiki/Data_remanence
There are many such applications available, some for free, by searching "disk
wipe" or "secure erase" on Google.com or similar web search engines. The
programs that do the best job operate at a low level, requiring direct or
"raw" access to a disk drive (not allowed for regular users), or running as
stand-alone programs without a full operating system. They are sensitive to
what hardware is supported and often fail when new hardware requires drivers
that are not included in their programming. Other programs (such as this
one) work within a standard file system, which allows them to be run by
regular users, but prevents them from erasing any part of a drive occupied by
file directories or boot blocks. Most people just want to make sure that
files they deleted are truly gone, and this program is good enough for that.
Apache License or GNU General Public License
--------------------------------------------
EraseDisk3 is free software and has been released under the terms and
conditions of the Apache License (version 2.0 or later) and/or the GNU
General Public License (GPL, version 2 or later). 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 license(s) for more details. You should have
received a copy of the licenses along with this program. If not, see the
http://www.apache.org/licenses/ and http://www.gnu.org/licenses/ web pages.
Frequently Asked Questions (FAQ)
--------------------------------
Why is there no read verify when writing zeros or ones? This feature is
currently disabled. Any sequence that repeats the same value can be highly
manipulated. Since all zeros are the same, there is no guarantee that data
is coming from the disk drive, or even the correct location.
What's so great about pseudo-random data? It can't be compressed or
predicted by the operating system or hardware. The system has no choice but
to do what it's told. Generating random numbers is slower. The most
time-consuming part is actually the read verify.
I am using disk compression and this program never ends. Of course not, not
when writing ones or zeros or multiple copies of the same byte. Instead of
writing a constant value, the system counts the repetitions until it reaches
a huge number like 9,223,372,036,854,775,807 (the largest signed 64-bit
integer). Use pseudo-random data with compressed disk drives or folders.
Why doesn't the program know the exact amount of free space on a disk?
Compression and encryption affect the reported space. Only when the program
runs out of space does it know that the disk drive is full ... probably.
Can this program be changed to meet the "secure erase" standard? No. First,
there are multiple standards for securely erasing a disk drive. Many
programs that claim this standard in fact ignore special cases which prevent
them from finishing the job. Second, the proper way to erase a disk drive is
to overwrite every data block on the entire disk, including the boot block,
disk directories, file contents, and bad (redirected) sectors. This can't be
done from within an existing file system.
Does the program support custom data patterns? Yes, after some editing and
compilation. Each write pass can use any byte from 0x00 to 0xFF, or
pseudo-random data. Each pass can have a read verify. Each read verify can
prompt the user to eject and reinsert removable media before the verify
begins (for flash drives, floppy disks, etc). There can be any number of
passes.
Graphical Versus Console Application
------------------------------------
The Java command line may contain options for the position and size of the
application window, and the size of the display font. See the "-?" option
for a help summary:
java EraseDisk3 -?
The command line has more options than are visible in the graphical
interface. An option such as -u16 or -u18 is recommended because the default
Java font is too small.
Restrictions and Limitations
----------------------------
As computers become more protective of their files, it is increasingly
difficult to be certain that a particular file has actually been deleted.
(See any feature that offers to restore previous versions of a file.)
Devices with small or unusual block sizes may not be completely erased. Some
systems leave partial file names in old directory entries even after files
are deleted. Others store very small files inside the directory structure
(around 728 bytes or less for NTFS). The only practical way of removing this
information is to reformat the disk drive or partition ... and lose all
files. An operating system's disk cache may make a read verify meaningless
if the amount of data written to the disk is less than the physical memory
size (RAM) on a computer. Windows 2000/XP/Vista/7 tends to misallocate a few
clusters when large FAT32 volumes are nearly full or files reach their
maximum size; these show up later as "lost" single-cluster files in CHKDSK.
Suggestions for New Features
----------------------------
(1) Comments in this program and its documentation have two spaces between
sentences. That is an old tradition from the days of manual typewriters
(source code being inherently monospaced). Unfortunately, Java may only
remove a single space in text boxes with the "word wrap" attribute set.
Our text strings have one space between sentences, while comments have
two spaces. Yes, somewhat schizophrenic. KF, 2011-11-17.
(2) Java 6 (1.6) and later have a File.getFreeSpace() method to estimate the
number of unallocated bytes on a disk partition. This can be an initial
value for our <runPassEstMax> global variable. KF, 2012-08-03.
(3) There are holes in the verification logic, mostly if files change size
between writing and reading. KF, 2014-01-12.
*/
import java.awt.*; // older Java GUI support
import java.awt.event.*; // older Java GUI event support
import java.io.*; // standard I/O
import java.text.*; // number formatting
import java.util.regex.*; // regular expressions
import javax.swing.*; // newer Java GUI support
import javax.swing.border.*; // decorative borders
public class EraseDisk3
{
/* constants */
static final String COPYRIGHT_NOTICE =
"Copyright (c) 2022 by Keith Fenske. Apache License or GNU GPL.";
static final int DEFAULT_HEIGHT = -1; // default window height in pixels
static final int DEFAULT_LEFT = 50; // default window left position ("x")
static final int DEFAULT_TOP = 50; // default window top position ("y")
static final int DEFAULT_WIDTH = -1; // default window width in pixels
static final String EMPTY_STATUS = " "; // message when no status to display
static final int EXIT_FAILURE = -1; // incorrect request or errors found
static final int EXIT_SUCCESS = 1; // request completed successfully
static final int EXIT_UNKNOWN = 0; // don't know or nothing really done
static final boolean FAST_RANDOM = true; // if we re-use old random numbers
static final int MIN_FRAME = 200; // minimum window height or width in pixels
static final int PAUSE_BUTTON_MNEMONIC = KeyEvent.VK_P;
// need to restore original after "Resume"
static final String PAUSE_BUTTON_TEXT = "Pause";
static final String PROGRAM_TITLE =
"Erase Empty Space on Disk Drive - by: Keith Fenske";
static final int SMALL_MILLIS = 499; // too close to zero in milliseconds
static final String SYSTEM_FONT = "Dialog"; // this font is always available
static final int TIMER_DELAY = 1000; // 1.000 seconds between status updates
/* Buffer sizes must be powers of two, and they must be in descending order
from largest to smallest. The first or preferred size should be bigger than
the block size or "allocation unit" of all disks on your system. The second
size will be used after the first size generates a "disk full" error, as will
the third and later sizes. The last size should be equal to the smallest
block size expected, such as 512 bytes for a floppy disk drive.
The intention is to write with larger and more efficient buffers for most of
the file space, switching to smaller buffers for completion. In the future,
it may be necessary to add a megabyte size and to delete the smallest size.
Bigger is not always better or faster. On a test system, Java 6 for Windows
XP (32-bit) wrote 256 KB buffers at 81 MB/s to an internal SATA 3Gb/s hard
disk drive, while 2 MB buffers were slower at 63 MB/s. Windows 7 (32-bit)
was faster and more consistent on the same hardware. A good buffer size for
everyone else (256 KB) was the worst for Windows 10.
Average Write Speed (All Zeros, MB/s)
---Version--- -------------------------Buffer Size-------------------------
Windows Java 64 KB 128 KB 256 KB 512 KB 1 MB 2 MB 4 MB 8 MB
WinXP 1.4 73.8 75.3 80.5 51.2 61.0 66.0 72.4 75.7
WinXP 5.0 74.0 75.0 80.1 50.4 60.6 64.0 70.2 75.7
WinXP J6 74.6 75.1 81.0 51.0 61.1 63.2 72.2 75.6
Win7 J6 77.2 81.2 82.2 82.1 83.6 83.9 84.0 81.3
Win7 J7 79.3 82.1 83.6 81.5 83.0 84.1 83.9 80.1
Win7 J8 79.4 83.2 83.5 82.5 82.3 82.9 83.0 81.6
Win10 J8 79.3 81.4 74.2 79.0 77.9 78.9 76.8 79.8
Interaction between Java and the operating system can create unexpected
results, such as pseudo-random data being written faster than all zeros or
ones, even with extra CPU time needed to generate the data.
It is possible to use sizes that are not powers of two if (1) the list has
only one element, or (2) each element divides all preceding elements without
a remainder ("is a factor of"). */
static final int[] BUFFER_SIZES = { 0x40000, 0x8000, 0x1000, 0x200 };
// 256 KB, 32 KB, 4 KB, 512 bytes
//static final int[] BUFFER_SIZES = { 0x200000, 0x40000, 0x8000, 0x1000 };
// // 2 MB, 256 KB, 32 KB, 4 KB
/* The read verify allows for and reports the occasional error, so long as
there aren't too many, because it's not unusual to see one bad bit/byte per
gigabyte for USB flash drives. When more than ERROR_LIMIT bytes are wrong,
the verify stops for one temporary file and proceeds with the next, if any.
One error is "forgiven" (subtracted) after ERROR_RESET consecutively correct
bytes, making the average tolerance to be one error per ERROR_RESET bytes.
If entire disk blocks are bad, this program gives up quickly; Java provides
no physical device information that can be used to compensate. */
static final long ERROR_LIMIT = 5; // maximum number of recent error bytes
static final long ERROR_RESET = 123456789; // forgive one error in ... bytes
/* We place a limit on the number of temporary files created, to prevent this
program from wasting space in directories. File systems can be slow if there
are too many files in one directory. A thousand files is more than enough
for FAT32 volumes up to 2 TB having a maximum of 4 GB per file. Newer disk
formats, such as NTFS, allow much bigger files. The constants here affect
the createFilename() method, which assumes three or more digits. */
static final int FILE_COUNT_DEFAULT = 999; // default value if no option given
static final int FILE_COUNT_LOWER = 1; // minimum legal value as an option
static final int FILE_COUNT_UPPER = 9999; // maximum legal value as an option
/* There is an advantage to limiting the maximum size of each temporary file,
in that read verify restarts (resynchronizes) at the beginning of each file,
when there is more than one. A maximum file size should be divisible by all
buffer sizes; otherwise, files may be up to one buffer too big. Reducing a
size may require a larger value for the number of files.
To make output look pretty, choose a number that is a multiple of the largest
buffer size, and slightly less than a string of nines in decimal. Messages
are better if file sizes are also in easy-to-read multiples of a full
megabyte, gigabyte, terabyte, or petabyte (marked below with "*"). Buffer
sizes are shown from one kilobyte (KB or KiB) to one gigabyte (GB), although
large sizes over 64 MB and very small sizes are impractical.
999,999 (roughly 1 MB) = 0xF423F
0xF4000 = 999,424 (multiple 1K*, 2K, 4K, 8K, 16K) = 976 KB
0xF0000 = 983,040 (multiple 32K, 64K)
0xE0000 = 917,504 (multiple 128K)
9,999,999 (roughly 10 MB) = 0x98967F
0x989400 = 9,999,360 (multiple 1K*) = 9765 KB
0x989000 = 9,998,336 (multiple 2K, 4K)
0x988000 = 9,994,240 (multiple 8K, 16K, 32K)
0x980000 = 9,961,472 (multiple 64K, 128K, 256K, 512K)
0x900000 = 9,437,184 (multiple 1M*) = 9 MB
99,999,999 (roughly 100 MB) = 0x5F5E0FF
0x5F5E000 = 99,999,744 (multiple 4K, 8K)
0x5F5C000 = 99,991,552 (multiple 16K)
0x5F58000 = 99,975,168 (multiple 32K)
0x5F50000 = 99,942,400 (multiple 64K)
0x5F40000 = 99,876,864 (multiple 128K, 256K)
0x5F00000 = 99,614,720 (multiple 512K, 1M*) = 95 MB
0x5E00000 = 98,566,144 (multiple 2M)
0x5C00000 = 96,468,992 (multiple 4M)
0x5800000 = 92,274,688 (multiple 8M)
999,999,999 (roughly 1 GB) = 0x3B9AC9FF
0x3B9AC000 = 999,997,440 (multiple 16K)
0x3B9A8000 = 999,981,056 (multiple 32K)
0x3B9A0000 = 999,948,288 (multiple 64K, 128K)
0x3B980000 = 999,817,216 (multiple 256K, 512K)
0x3B900000 = 999,292,928 (multiple 1M*) = 953 MB
0x3B800000 = 998,244,352 (multiple 2M, 4M, 8M)
0x3B000000 = 989,855,744 (multiple 16M)
0x3A000000 = 973,078,528 (multiple 32M)
0x38000000 = 939,524,096 (multiple 64M, 128M)
9,999,999,999 (roughly 10 GB) = 0x2540BE3FF
0x2540B0000 = 9,999,941,632 (multiple 64K)
0x2540A0000 = 9,999,876,096 (multiple 128K)
0x254080000 = 9,999,745,024 (multiple 256K, 512K)
0x254000000 = 9,999,220,736 (multiple 1M*, 2M, 4M, 8M, 16M, 32M, 64M) = 9536 MB
0x250000000 = 9,932,111,872 (multiple 128M, 256M)
0x240000000 = 9,663,676,416 (multiple 512M, 1G*) = 9 GB
99,999,999,999 (roughly 100 GB) = 0x174876E7FF
0x1748740000 = 99,999,809,536 (multiple 256K), default in 2009-2015
0x1748700000 = 99,999,547,392 (multiple 512K, 1M)
0x1748600000 = 99,998,498,816 (multiple 2M)
0x1748400000 = 99,996,401,664 (multiple 4M)
0x1748000000 = 99,992,207,360 (multiple 8M, 16M, 32M, 64M, 128M)
0x1740000000 = 99,857,989,632 (multiple 256M, 512M, 1G*) = 93 GB
999,999,999,999 (roughly 1 TB) = 0xE8D4A50FFF
0xE8D4A00000 = 999,999,668,224 (multiple 1M, 2M)
0xE8D4800000 = 999,997,571,072 (multiple 4M, 8M)
0xE8D4000000 = 999,989,182,464 (multiple 16M, 32M, 64M)
0xE8D0000000 = 999,922,073,600 (multiple 128M, 256M)
0xE8C0000000 = 999,653,638,144 (multiple 512M, 1G*) = 931 GB
9,999,999,999,999 (roughly 10 TB) = 0x9184E729FFF
0x9184E400000 = 9,999,996,682,240 (multiple 4M)
0x9184E000000 = 9,999,992,487,936 (multiple 8M, 16M, 32M)
0x9184C000000 = 9,999,958,933,504 (multiple 64M)
0x91848000000 = 9,999,891,824,640 (multiple 128M)
0x91840000000 = 9,999,757,606,912 (multiple 256M, 512M, 1G*) = 9313 GB
0x90000000000 = 9,895,604,649,984 (multiple 1T*) = 9 TB
99,999,999,999,999 (roughly 100 TB) = 0x5AF3107A3FFF
0x5AF310000000 = 99,999,991,988,224 (multiple 16M, 32M, 64M, 128M, 256M)
0x5AF300000000 = 99,999,723,552,768 (multiple 512M, 1G)
0x5A0000000000 = 98,956,046,499,840 (multiple 1T*) = 90 TB
999,999,999,999,999 (roughly 1 PB) = 0x38D7EA4C67FFF
0x38D7EA4000000 = 999,999,986,991,104 (multiple 64M)
0x38D7EA0000000 = 999,999,919,882,240 (multiple 128M, 256M, 512M)
0x38D7E80000000 = 999,999,383,011,328 (multiple 1G)
0x38D0000000000 = 999,456,069,648,384 (multiple 1T*) = 909 TB
9,999,999,999,999,999 (roughly 10 PB) = 0x2386F26FC0FFFF
0x2386F260000000 = 9,999,999,735,693,312 (multiple 256M, 512M)
0x2386F240000000 = 9,999,999,198,822,400 (multiple 1G)
0x23860000000000 = 9,998,958,742,994,944 (multiple 1T*) = 9094 TB
0x20000000000000 = 9,007,199,254,740,992 (multiple 1P*) = 8 PB
99,999,999,999,999,999 (roughly 100 PB) = 0x16345785D89FFFF
0x163457840000000 = 99,999,999,504,416,768 (multiple 1G)
0x163450000000000 = 99,999,483,034,599,424 (multiple 1T)
0x160000000000000 = 99,079,191,802,150,912 (multiple 1P*) = 88 PB
999,999,999,999,999,999 (roughly 1 EB) = 0xDE0B6B3A763FFFF
0xDE0B60000000000 = 999,999,228,392,505,344 (multiple 1T)
0xDE0000000000000 = 999,799,117,276,250,112 (multiple 1P*) = 888 PB
*/
static final long FILE_SIZE_DEFAULT = 0x1740000000L;
// 99,857,989,632 bytes (93 GB)
static final long FILE_SIZE_LOWER = BUFFER_SIZES[0];
// minimum legal value and likely multiple
static final long FILE_SIZE_UPPER = 0x7FFC000000000000L;
// safe positive 64-bit integer (8191 PB)
static final long PASS_SIZE_DEFAULT = 0x7FFC000000000000L;
static final long PASS_SIZE_LOWER = BUFFER_SIZES[0];
static final long PASS_SIZE_UPPER = 0x7FFC000000000000L;
/* class variables */
static JButton cancelButton, erasePanelBack, erasePanelNext, exitButton,
folderButton, optionPanelBack, optionPanelNext, pauseButton, saveButton,
startButton, summaryPanelBack, wherePanelNext; // buttons
static boolean cancelFlag; // our signal from user to stop processing
static long clockJobSaved, clockPassSaved;
// elapsed time before pause or prompt
static long clockJobStart, clockPassStart;
// system millis after pause or prompt
static boolean debugFlag; // true if we show debug information
static boolean deleteFlag; // true if we delete our temporary files
static JTextField erasePanelFileAction, erasePanelFileDone,
erasePanelPassDone, erasePanelPassTime, erasePanelTitle,
erasePanelTotalTime; // information fields on "Erase" panel
static JProgressBar erasePanelFileBar, erasePanelPassBar;
static EraseDisk3Grid erasePanelGrid; // graphical history of data rates
static JLabel erasePanelGridScale; // current maximum grid scale value
static int erasePanelIndex, optionPanelIndex, summaryPanelIndex,
wherePanelIndex; // used by Back/Next to navigate tabs
static JFileChooser fileChooser; // asks for input and output file names
static NumberFormat formatComma; // formats with commas (digit grouping)
static NumberFormat formatPointOne; // formats with one decimal digit
static JFrame mainFrame; // this application's window if GUI
static boolean mswinFlag; // true if running on Microsoft Windows
static JCheckBox optionCustomWrite, optionOneWrite, optionRandomPrompt,
optionRandomRead, optionRandomWrite, optionZeroWrite; // checkboxes
static JTextField optionFileCount, optionFileSize, optionTotalSize;
static JTextArea outputText; // generated report while opening files
static boolean pauseFlag; // true if we should delay processing
static Integer pauseWaiter; // wait on this object for "Pause" button
static String runFileAction, runPassAction;
// tags saying if reading or writing
static long runFileBytesDone, runFileEstMax, runPassBytesDone, runPassEstMax,
runPassPrevBytes, runTotalBytesDone, runTotalErrors;
// running status counters during erase
static String runFileName; // current read/write file name, if any
static double runPassPrevRate; // previous bytes per second
static javax.swing.Timer statusTimer; // timer for updating status message
static JTabbedPane tabbedPane; // tabbed pane with multiple panels
static int userFileCount; // user's maximum number of files
static long userFileSize; // user's maximum size of each file
static File userFolder; // directory or folder for temporary files
static String userFolderPath; // canonical path name for user's folder
static long userPassSize; // user's maximum all files, one pass
static JTextField whereFolderText; // shows name of user's selected folder
/*
main() method
We run as a graphical application only. Set the window layout and then let
the graphical interface run the show.
*/
public static void main(String[] args)
{
ActionListener action; // our shared action listener
boolean borderFlag; // true if main window has borders, controls
Font buttonFont; // font for buttons, labels, status, etc
Border emptyBorder; // remove borders around text areas
String fontName; // our preferred font name for everything
int gridBarGap, gridBarWidth; // pixel sizes for columns in bar graph
int i; // index variable
boolean maxDataRateFlag; // true if we show maximum observed data rate
boolean maximizeFlag; // true if we maximize our main window
int windowHeight, windowLeft, windowTop, windowWidth;
// position and size for <mainFrame>
String word; // one parameter from command line
/* Initialize variables used by both console and GUI applications. */
borderFlag = true; // by default, window has borders, controls
buttonFont = null; // by default, don't use customized font
cancelFlag = false; // don't cancel unless user complains
debugFlag = false; // by default, don't show debug information
fontName = null; // "Verdana"; // preferred font name or null for default
gridBarGap = gridBarWidth = -1; // no pixel sizes for columns in bar graph
mainFrame = null; // during setup, there is no GUI window
maxDataRateFlag = false; // by default, don't show maximum data rate
maximizeFlag = false; // by default, don't maximize our main window
mswinFlag = System.getProperty("os.name").startsWith("Windows");
pauseFlag = false; // don't pause until user clicks button
userFolder = null; // no folder yet for temporary files
userFolderPath = null; // no path name, because no folder yet
windowHeight = DEFAULT_HEIGHT; // default window position and size
windowLeft = DEFAULT_LEFT;
windowTop = DEFAULT_TOP;
windowWidth = DEFAULT_WIDTH;
/* If our preferred font is not available, then use a pre-defined system
font. Comment out the <buttonFont> line below to use whatever the current
Java run-time has for each of the various GUI elements. */
if ((fontName == null) || (fontName.equals((new Font(fontName, Font.PLAIN,
16)).getFamily()) == false)) // create font, read back created name
{
fontName = SYSTEM_FONT; // must replace with standard system font
}
buttonFont = new Font(fontName, Font.PLAIN, 18); // for buttons, text
/* Initialize number formatting styles. */
formatComma = NumberFormat.getInstance(); // current locale
formatComma.setGroupingUsed(true); // use commas or digit groups
formatPointOne = NumberFormat.getInstance(); // current locale
formatPointOne.setGroupingUsed(true); // use commas or digit groups
formatPointOne.setMaximumFractionDigits(1); // force one decimal digit
formatPointOne.setMinimumFractionDigits(1);
/* Check command-line parameters for options. */
for (i = 0; i < args.length; i ++)
{
word = args[i].toLowerCase(); // easier to process if consistent case
if (word.length() == 0)
{
/* Ignore empty parameters, which are more common than you might think,
when programs are being run from inside scripts (command files). */
}
else if (word.equals("?") || word.equals("-?") || word.equals("/?")
|| word.equals("-h") || (mswinFlag && word.equals("/h"))
|| word.equals("-help") || (mswinFlag && word.equals("/help")))
{
showHelp(); // show help summary
System.exit(EXIT_UNKNOWN); // exit application after printing help
}
else if (word.equals("-b") || (mswinFlag && word.equals("/b"))
|| word.equals("-b1") || (mswinFlag && word.equals("/b1")))
{
borderFlag = true; // our main window has borders, controls
}
else if (word.equals("-b0") || (mswinFlag && word.equals("/b0")))
borderFlag = false; // no borders, controls on main window
else if (word.equals("-d") || (mswinFlag && word.equals("/d"))
|| word.equals("-d1") || (mswinFlag && word.equals("/d1")))
{
debugFlag = true; // yes, show debug information
System.err.println("main args.length = " + args.length);
for (int k = 0; k < args.length; k ++)
System.err.println("main args[" + k + "] = <" + args[k] + ">");
}
else if (word.equals("-d0") || (mswinFlag && word.equals("/d0")))
debugFlag = false; // don't show debug information
else if (word.startsWith("-g") || (mswinFlag && word.startsWith("/g")))
{
/* This option is followed by a list of two numbers for the pixel width
and gap spacing of the vertical columns in the bar graph. We accept
all values with correct syntax; defaults are silently applied later if
numbers are out of range. */
Pattern pattern = Pattern.compile(
"\\s*\\(\\s*(-?\\d{1,5})\\s*,\\s*(-?\\d{1,5})\\s*\\)\\s*");
Matcher matcher = pattern.matcher(word.substring(2)); // parse option
if (matcher.matches()) // if option has proper syntax
{
gridBarWidth = Integer.parseInt(matcher.group(1));
gridBarGap = Integer.parseInt(matcher.group(2));
}
else // bad syntax or too many digits
{
System.err.println("Invalid column width or gap spacing: "
+ args[i]); // syntax error only, no semantic checking
showHelp(); // show help summary
System.exit(EXIT_FAILURE); // exit application after printing help
}
}
else if (word.equals("-r") || (mswinFlag && word.equals("/r"))
|| word.equals("-r1") || (mswinFlag && word.equals("/r1")))
{
/* Some people want to see the maximum observed data rate as a number,
with the bar graph. Others find this distracting. */
maxDataRateFlag = true; // yes, show maximum observed data rate
}
else if (word.equals("-r0") || (mswinFlag && word.equals("/r0")))
maxDataRateFlag = false; // don't show maximum observed data rate
else if (word.startsWith("-u") || (mswinFlag && word.startsWith("/u")))
{
/* This option is followed by a font point size that will be used for
buttons, dialogs, labels, etc. */
int size = -1; // default value for font point size
try // try to parse remainder as unsigned integer
{
size = Integer.parseInt(word.substring(2));
}
catch (NumberFormatException nfe) // if not a number or bad syntax
{
size = -1; // set result to an illegal value
}
if ((size < 10) || (size > 99))
{
System.err.println("Dialog font size must be from 10 to 99: "
+ args[i]); // notify user of our arbitrary limits
showHelp(); // show help summary
System.exit(EXIT_FAILURE); // exit application after printing help
}
buttonFont = new Font(fontName, Font.PLAIN, size); // for big sizes
// buttonFont = new Font(fontName, Font.BOLD, size); // for small sizes
if (gridBarGap < 0) gridBarGap = (int) Math.round(size * 0.17);
// default pixel size if not already set
if (gridBarWidth < 0) gridBarWidth = (int) Math.round(size * 0.56);
}
else if (word.startsWith("-w") || (mswinFlag && word.startsWith("/w")))
{
/* This option is followed by a list of four numbers for the initial
window position and size. All values are accepted, but small heights
or widths will later force the minimum packed size for the layout. */
Pattern pattern = Pattern.compile(
"\\s*\\(\\s*(-?\\d{1,5})\\s*,\\s*(-?\\d{1,5})\\s*,\\s*(\\d{1,5})\\s*,\\s*(\\d{1,5})\\s*\\)\\s*");
Matcher matcher = pattern.matcher(word.substring(2)); // parse option
if (matcher.matches()) // if option has proper syntax
{
windowLeft = Integer.parseInt(matcher.group(1));
windowTop = Integer.parseInt(matcher.group(2));
windowWidth = Integer.parseInt(matcher.group(3));
windowHeight = Integer.parseInt(matcher.group(4));
}
else // bad syntax or too many digits
{
System.err.println("Invalid window position or size: " + args[i]);
showHelp(); // show help summary
System.exit(EXIT_FAILURE); // exit application after printing help
}
}
else if (word.equals("-x") || (mswinFlag && word.equals("/x"))
|| word.equals("-x1") || (mswinFlag && word.equals("/x1")))
{
maximizeFlag = true; // yes, maximize our main window
}
else if (word.equals("-x0") || (mswinFlag && word.equals("/x0")))
maximizeFlag = false; // regular window, don't maximize
else // parameter is not a recognized option
{
System.err.println("Option not recognized: " + args[i]);
showHelp(); // show help summary
System.exit(EXIT_FAILURE); // exit application after printing help
}
}
/* Open the graphical user interface (GUI). The standard Java style is the
most reliable, but you can switch to something closer to the local system,
if you want. */
// try
// {
// UIManager.setLookAndFeel(
// UIManager.getCrossPlatformLookAndFeelClassName());
//// UIManager.getSystemLookAndFeelClassName());
// }
// catch (Exception ulafe)
// {
// System.err.println("Unsupported Java look-and-feel: " + ulafe);
// }
/* Initialize shared graphical objects. */
action = new EraseDisk3User(); // create our shared action listener
emptyBorder = BorderFactory.createEmptyBorder(); // for removing borders
fileChooser = new JFileChooser(); // create our shared file chooser
pauseWaiter = new Integer(0); // wait on this object for "Pause" button
statusTimer = new javax.swing.Timer(TIMER_DELAY, action);
// update status message on clock ticks only
/* Create the graphical interface as a series of smaller panels inside
bigger panels. The intermediate panel names are of no lasting importance
and hence are only numbered (panel261, label354, etc). */
tabbedPane = new JTabbedPane(JTabbedPane.TOP);
if (buttonFont != null) tabbedPane.setFont(buttonFont);
/* The first panel has a general introduction and selects the location of
a drive folder where temporary files will be created. This panel could be
combined with later options, but is better by itself.
Java GUI items change their appearance depending upon which options are
selected. JTextArea has a white background by default (i.e., is opaque).
An editable JTextField has a white background, while a non-editable field
is transparent. */
wherePanelIndex = tabbedPane.getTabCount(); // used by Back/Next buttons
JPanel panel210 = new JPanel(new BorderLayout(0, 20));
JPanel panel230 = new JPanel(); // horizontal row of buttons
panel230.setLayout(new BoxLayout(panel230, BoxLayout.X_AXIS));
folderButton = new JButton("Folder...");
folderButton.addActionListener(action);
if (buttonFont != null) folderButton.setFont(buttonFont);
folderButton.setMnemonic(KeyEvent.VK_F);
folderButton.setToolTipText("Select folder for temporary files.");
panel230.add(folderButton);
panel230.add(Box.createHorizontalStrut(20));
whereFolderText = new JTextField("No folder selected.");
whereFolderText.setBorder(emptyBorder);
whereFolderText.setEditable(false); // user can't change this text field
if (buttonFont != null) whereFolderText.setFont(buttonFont);
// whereFolderText.setOpaque(false); // transparent background, not white
panel230.add(whereFolderText);
panel230.add(Box.createHorizontalStrut(20));
wherePanelNext = new JButton("Next >");
wherePanelNext.addActionListener(action);
if (buttonFont != null) wherePanelNext.setFont(buttonFont);
wherePanelNext.setMnemonic(KeyEvent.VK_N);
panel230.add(wherePanelNext);
panel210.add(panel230, BorderLayout.NORTH);
JTextArea text250 = new JTextArea(10, 20);
// nominal size to avoid scroll bars
text250.setEditable(false); // user can't change this text area
if (buttonFont != null) text250.setFont(buttonFont);
text250.setLineWrap(true); // allow text lines to wrap
text250.setOpaque(false); // transparent background, not white
text250.setText(
"EraseDisk is a Java 1.4 graphical (GUI) application to erase and test"
+ " disk drives or flash drives. Large temporary files are created and"
+ " filled with zeros, ones, or pseudo-random data. Previously deleted"
+ " files are overwritten. Existing files are not affected. This cleans"
+ " up an old disk before it goes in a new location. Don't trust a new"
+ " disk until you write data, then read to confirm. One complete test"
+ " is usually enough. (Repeated testing may degrade flash drives.)"
+ "\n\nFirst, choose a directory or folder where temporary files can be"
+ " created. Then follow the \"Next\" and \"Start\" buttons."
+ "\n\nCopyright (c) 2022 by Keith Fenske. By using this program, you"
+ " agree to terms and conditions of the Apache License and/or GNU"
+ " General Public License.");
text250.setWrapStyleWord(true); // wrap at word boundaries
JScrollPane panel251 = new JScrollPane(text250);
panel251.setBorder(emptyBorder);
panel210.add(panel251, BorderLayout.CENTER);
JPanel panel290 = new JPanel(new BorderLayout(0, 0));
panel290.add(Box.createVerticalStrut(15), BorderLayout.NORTH);
panel290.add(Box.createHorizontalStrut(10), BorderLayout.WEST);
panel290.add(panel210, BorderLayout.CENTER);
panel290.add(Box.createHorizontalStrut(10), BorderLayout.EAST);
panel290.add(Box.createVerticalStrut(10), BorderLayout.SOUTH);
tabbedPane.addTab(" Where ", panel290);
/* The second panel has options. This is the most detailed panel and will
determine the default or "packed" layout size. */
optionPanelIndex = tabbedPane.getTabCount(); // used by Back/Next buttons
JPanel panel310 = new JPanel(new BorderLayout(0, 0));
JPanel panel320 = new JPanel(); // vertical box for stacking rows
panel320.setLayout(new BoxLayout(panel320, BoxLayout.Y_AXIS));
JPanel panel330 = new JPanel(); // horizontal row of buttons
panel330.setLayout(new BoxLayout(panel330, BoxLayout.X_AXIS));
optionPanelBack = new JButton("< Back");
optionPanelBack.addActionListener(action);
if (buttonFont != null) optionPanelBack.setFont(buttonFont);
optionPanelBack.setMnemonic(KeyEvent.VK_B);
panel330.add(optionPanelBack);
panel330.add(Box.createHorizontalStrut(20));
panel330.add(Box.createHorizontalGlue());
optionPanelNext = new JButton("Next >");
optionPanelNext.addActionListener(action);
if (buttonFont != null) optionPanelNext.setFont(buttonFont);
optionPanelNext.setMnemonic(KeyEvent.VK_N);
panel330.add(optionPanelNext);
panel320.add(panel330);
panel320.add(Box.createVerticalStrut(20));
JPanel panel340 = new JPanel();
panel340.setLayout(new BoxLayout(panel340, BoxLayout.X_AXIS));
JLabel label341 = new JLabel("What type of data do you want to write?");
if (buttonFont != null) label341.setFont(buttonFont);
panel340.add(label341);
panel340.add(Box.createHorizontalGlue());
panel320.add(panel340);
panel320.add(Box.createVerticalStrut(6));
JPanel panel342 = new JPanel();
panel342.setLayout(new BoxLayout(panel342, BoxLayout.X_AXIS));
optionCustomWrite = new JCheckBox("custom data pattern (0x69 and 0x96)",
false); // special requests, see startErase() method
if (buttonFont != null) optionCustomWrite.setFont(buttonFont);
panel342.add(optionCustomWrite);
panel342.add(Box.createHorizontalGlue());
panel320.add(panel342);
panel320.add(Box.createVerticalStrut(2));
JPanel panel343 = new JPanel();
panel343.setLayout(new BoxLayout(panel343, BoxLayout.X_AXIS));
optionOneWrite = new JCheckBox("write all ones (0xFF bytes)", false);
if (buttonFont != null) optionOneWrite.setFont(buttonFont);
panel343.add(optionOneWrite);
panel343.add(Box.createHorizontalGlue());
panel320.add(panel343);
panel320.add(Box.createVerticalStrut(2));
JPanel panel344 = new JPanel();
panel344.setLayout(new BoxLayout(panel344, BoxLayout.X_AXIS));
optionRandomWrite = new JCheckBox("pseudo-random data and ", true);
optionRandomWrite.addActionListener(action);
if (buttonFont != null) optionRandomWrite.setFont(buttonFont);
panel344.add(optionRandomWrite);
optionRandomRead = new JCheckBox("read verify after ", false);
optionRandomRead.addActionListener(action);
if (buttonFont != null) optionRandomRead.setFont(buttonFont);
panel344.add(optionRandomRead);
optionRandomPrompt = new JCheckBox("prompt", false);
// optionRandomPrompt.addActionListener(action);
optionRandomPrompt.setEnabled(false);
if (buttonFont != null) optionRandomPrompt.setFont(buttonFont);
panel344.add(optionRandomPrompt);
panel344.add(Box.createHorizontalGlue());
panel320.add(panel344);
panel320.add(Box.createVerticalStrut(2));
JPanel panel345 = new JPanel();
panel345.setLayout(new BoxLayout(panel345, BoxLayout.X_AXIS));
optionZeroWrite = new JCheckBox("write all zeros (0x00 bytes)", false);
if (buttonFont != null) optionZeroWrite.setFont(buttonFont);
panel345.add(optionZeroWrite);
panel345.add(Box.createHorizontalGlue());
panel320.add(panel345);
panel320.add(Box.createVerticalStrut(24));
JPanel panel350 = new JPanel();
panel350.setLayout(new BoxLayout(panel350, BoxLayout.X_AXIS));
JLabel label351 = new JLabel("Maximum number of temporary files is ");
if (buttonFont != null) label351.setFont(buttonFont);
panel350.add(label351);
optionFileCount = new JTextField(formatComma.format(FILE_COUNT_DEFAULT));
optionFileCount.setColumns(5);
if (buttonFont != null) optionFileCount.setFont(buttonFont);
optionFileCount.setHorizontalAlignment(JTextField.CENTER);
optionFileCount.setMargin(new Insets(0, 2, 1, 2));
optionFileCount.setMaximumSize(optionFileCount.getPreferredSize());
// force a fixed size only after setting
// ... columns, font, margin, etc.
optionFileCount.setMinimumSize(optionFileCount.getPreferredSize());
optionFileCount.setOpaque(false); // transparent background, not white
panel350.add(optionFileCount);
JLabel label352 = new JLabel(" from "
+ formatComma.format(FILE_COUNT_LOWER) + " to "
+ formatComma.format(FILE_COUNT_UPPER) + ".");
if (buttonFont != null) label352.setFont(buttonFont);
panel350.add(label352);
panel350.add(Box.createHorizontalGlue());
panel320.add(panel350);
panel320.add(Box.createVerticalStrut(5));
JPanel panel360 = new JPanel();
panel360.setLayout(new BoxLayout(panel360, BoxLayout.X_AXIS));
JLabel label361 = new JLabel("Maximum size of each temporary file is ");
if (buttonFont != null) label361.setFont(buttonFont);
panel360.add(label361);
optionFileSize = new JTextField(formatByteSize(FILE_SIZE_DEFAULT));
optionFileSize.setColumns(6);
if (buttonFont != null) optionFileSize.setFont(buttonFont);
optionFileSize.setHorizontalAlignment(JTextField.CENTER);
optionFileSize.setMargin(new Insets(0, 2, 1, 2));
optionFileSize.setMaximumSize(optionFileSize.getPreferredSize());
optionFileSize.setMinimumSize(optionFileSize.getPreferredSize());
optionFileSize.setOpaque(false);
panel360.add(optionFileSize);
JLabel label362 = new JLabel(" from " + formatByteSize(FILE_SIZE_LOWER)
+ " to " + formatByteSize(FILE_SIZE_UPPER) + ".");
if (buttonFont != null) label362.setFont(buttonFont);
panel360.add(label362);
panel360.add(Box.createHorizontalGlue());
panel320.add(panel360);
panel320.add(Box.createVerticalStrut(5));
JPanel panel370 = new JPanel();
panel370.setLayout(new BoxLayout(panel370, BoxLayout.X_AXIS));
JLabel label371 = new JLabel("Maximum total size for all files is ");
if (buttonFont != null) label371.setFont(buttonFont);
panel370.add(label371);
optionTotalSize = new JTextField(formatByteSize(PASS_SIZE_DEFAULT));
optionTotalSize.setColumns(7);
if (buttonFont != null) optionTotalSize.setFont(buttonFont);
optionTotalSize.setHorizontalAlignment(JTextField.CENTER);
optionTotalSize.setMargin(new Insets(0, 2, 1, 2));
optionTotalSize.setMaximumSize(optionTotalSize.getPreferredSize());
optionTotalSize.setMinimumSize(optionTotalSize.getPreferredSize());
optionTotalSize.setOpaque(false);
panel370.add(optionTotalSize);
JLabel label372 = new JLabel(" from " + formatByteSize(PASS_SIZE_LOWER)
+ " to " + formatByteSize(PASS_SIZE_UPPER) + ".");
if (buttonFont != null) label372.setFont(buttonFont);
panel370.add(label372);
panel370.add(Box.createHorizontalGlue());
panel320.add(panel370);
panel320.add(Box.createVerticalStrut(7));
panel310.add(panel320, BorderLayout.NORTH);
JTextArea text380 = new JTextArea(3, 48);
// actual size affects "packed" layout
text380.setEditable(false); // user can't change this text area
if (buttonFont != null) text380.setFont(buttonFont);
text380.setLineWrap(true); // allow text lines to wrap
text380.setOpaque(false); // transparent background, not white
text380.setText("File sizes that are not a multiple of "
+ formatByteSize(FILE_SIZE_LOWER)
+ " will be rounded up to the next nearest multiple.");
text380.setWrapStyleWord(true); // wrap at word boundaries
JScrollPane panel381 = new JScrollPane(text380);
panel381.setBorder(emptyBorder);
panel310.add(panel381, BorderLayout.CENTER);
JPanel panel390 = new JPanel(new BorderLayout(0, 0));
panel390.add(Box.createVerticalStrut(15), BorderLayout.NORTH);
panel390.add(Box.createHorizontalStrut(10), BorderLayout.WEST);
panel390.add(panel310, BorderLayout.CENTER);
panel390.add(Box.createHorizontalStrut(10), BorderLayout.EAST);
panel390.add(Box.createVerticalStrut(10), BorderLayout.SOUTH);
tabbedPane.addTab(" Options ", panel390);
/* The third panel has the status for when we actually do the writing. As
ugly as this code may be, it was much worse when it was a GridBagLayout. */
erasePanelIndex = tabbedPane.getTabCount(); // used by Back/Next buttons
JPanel panel510 = new JPanel(new BorderLayout(0, 0));
JPanel panel520 = new JPanel(); // vertical box for stacking rows
panel520.setLayout(new BoxLayout(panel520, BoxLayout.Y_AXIS));
JPanel panel530 = new JPanel(); // horizontal row of buttons
panel530.setLayout(new BoxLayout(panel530, BoxLayout.X_AXIS));
erasePanelBack = new JButton("< Back");
erasePanelBack.addActionListener(action);
if (buttonFont != null) erasePanelBack.setFont(buttonFont);
erasePanelBack.setMnemonic(KeyEvent.VK_B);
panel530.add(erasePanelBack);
panel530.add(Box.createHorizontalStrut(20));
panel530.add(Box.createHorizontalGlue());
startButton = new JButton("Start");
startButton.addActionListener(action);
if (buttonFont != null) startButton.setFont(buttonFont);
startButton.setMnemonic(KeyEvent.VK_S);
startButton.setToolTipText("Write, write, write, and maybe read.");
panel530.add(startButton);
panel530.add(Box.createHorizontalStrut(20));
pauseButton = new JButton(PAUSE_BUTTON_TEXT);
pauseButton.addActionListener(action);
pauseButton.setEnabled(false);
if (buttonFont != null) pauseButton.setFont(buttonFont);
pauseButton.setMnemonic(PAUSE_BUTTON_MNEMONIC);
pauseButton.setToolTipText("Wait to resume later.");
panel530.add(pauseButton);
panel530.add(Box.createHorizontalStrut(20));
cancelButton = new JButton("Cancel");
cancelButton.addActionListener(action);
cancelButton.setEnabled(false);
if (buttonFont != null) cancelButton.setFont(buttonFont);
cancelButton.setMnemonic(KeyEvent.VK_C);
cancelButton.setToolTipText("Stop writing, stop everything.");
panel530.add(cancelButton);
panel530.add(Box.createHorizontalStrut(20));
panel530.add(Box.createHorizontalGlue());
erasePanelNext = new JButton("Next >");
erasePanelNext.addActionListener(action);
if (buttonFont != null) erasePanelNext.setFont(buttonFont);
erasePanelNext.setMnemonic(KeyEvent.VK_N);
panel530.add(erasePanelNext);
panel520.add(panel530);
panel520.add(Box.createVerticalStrut(20));
erasePanelTitle = new JTextField("Write and maybe read data...");
erasePanelTitle.setBorder(emptyBorder);
erasePanelTitle.setEditable(false); // user can't change this text field
if (buttonFont != null) erasePanelTitle.setFont(buttonFont);
// erasePanelTitle.setOpaque(false); // transparent background, not white
panel520.add(erasePanelTitle);
panel520.add(Box.createVerticalStrut(10));
erasePanelFileAction = new JTextField(EMPTY_STATUS);
erasePanelFileAction.setBorder(emptyBorder);
erasePanelFileAction.setEditable(false);
if (buttonFont != null) erasePanelFileAction.setFont(buttonFont);
// erasePanelFileAction.setOpaque(false);
panel520.add(erasePanelFileAction);
panel520.add(Box.createVerticalStrut(10));
erasePanelFileDone = new JTextField(EMPTY_STATUS);
erasePanelFileDone.setBorder(emptyBorder);
erasePanelFileDone.setEditable(false);
if (buttonFont != null) erasePanelFileDone.setFont(buttonFont);
// erasePanelFileDone.setOpaque(false);
panel520.add(erasePanelFileDone);
panel520.add(Box.createVerticalStrut(9));
erasePanelFileBar = new JProgressBar(0, 100);
erasePanelFileBar.setBorder(emptyBorder);
erasePanelFileBar.setBorderPainted(false);
// remove drop shadow on empty border
if (buttonFont != null) erasePanelFileBar.setFont(buttonFont);
erasePanelFileBar.setString(EMPTY_STATUS);
erasePanelFileBar.setStringPainted(true);
erasePanelFileBar.setValue(0);
panel520.add(erasePanelFileBar);
panel520.add(Box.createVerticalStrut(8));
erasePanelPassTime = new JTextField(EMPTY_STATUS);
erasePanelPassTime.setBorder(emptyBorder);
erasePanelPassTime.setEditable(false);
if (buttonFont != null) erasePanelPassTime.setFont(buttonFont);
// erasePanelPassTime.setOpaque(false);
panel520.add(erasePanelPassTime);
panel520.add(Box.createVerticalStrut(10));
erasePanelPassDone = new JTextField(EMPTY_STATUS);
erasePanelPassDone.setBorder(emptyBorder);
erasePanelPassDone.setEditable(false);
if (buttonFont != null) erasePanelPassDone.setFont(buttonFont);
// erasePanelPassDone.setOpaque(false);
panel520.add(erasePanelPassDone);
panel520.add(Box.createVerticalStrut(9));
erasePanelPassBar = new JProgressBar(0, 100);
erasePanelPassBar.setBorder(emptyBorder);
erasePanelPassBar.setBorderPainted(false);
if (buttonFont != null) erasePanelPassBar.setFont(buttonFont);
erasePanelPassBar.setString(EMPTY_STATUS);
erasePanelPassBar.setStringPainted(true);
erasePanelPassBar.setValue(0);
panel520.add(erasePanelPassBar);
panel520.add(Box.createVerticalStrut(8));