diff --git a/src/com/webcodepro/applecommander/storage/Disk.java b/src/com/webcodepro/applecommander/storage/Disk.java index 42c6a23..6b943db 100644 --- a/src/com/webcodepro/applecommander/storage/Disk.java +++ b/src/com/webcodepro/applecommander/storage/Disk.java @@ -34,6 +34,7 @@ import com.webcodepro.applecommander.storage.os.cpm.CpmFormatDisk; import com.webcodepro.applecommander.storage.os.dos33.DosFormatDisk; import com.webcodepro.applecommander.storage.os.dos33.OzDosFormatDisk; import com.webcodepro.applecommander.storage.os.dos33.UniDosFormatDisk; +import com.webcodepro.applecommander.storage.os.gutenberg.GutenbergFormatDisk; import com.webcodepro.applecommander.storage.os.pascal.PascalFormatDisk; import com.webcodepro.applecommander.storage.os.prodos.ProdosFormatDisk; import com.webcodepro.applecommander.storage.os.rdos.RdosFormatDisk; @@ -191,7 +192,7 @@ public class Disk { if (isProdosOrder()) { imageOrder = new ProdosOrder(diskImageManager); } else if (isDosOrder()) { - imageOrder = new DosOrder(diskImageManager); + imageOrder = new DosOrder(diskImageManager); } else if (isNibbleOrder()) { imageOrder = new NibbleOrder(diskImageManager); } @@ -256,10 +257,13 @@ public class Disk { } else if (isCpmFormat()) { return new FormattedDisk[] { new CpmFormatDisk(filename, imageOrder) }; + } else if (isWPFormat()) { + return new FormattedDisk[] + { new GutenbergFormatDisk(filename, imageOrder) }; } return null; } - + /** * Returns the diskImage. * @return byte[] @@ -556,6 +560,20 @@ public class Disk { return "RDOS".equals(id); //$NON-NLS-1$ } + /** + * Test the disk format to see if this is a WP formatted + * disk. + */ + public boolean isWPFormat() { + if (!is140KbDisk()) return false; + byte[] vtoc = readSector(17, 7); + return (imageOrder.isSizeApprox(APPLE_140KB_DISK) + || imageOrder.isSizeApprox(APPLE_140KB_NIBBLE_DISK)) + && vtoc[0x00] == 17 // expect catalog to start on track 17 + && vtoc[0x01] == 7 // expect catalog to start on sector 7 + && vtoc[0x0f] == -115; // expect 0x8d's every 16 bytes + } + /** * Indicates if the disk has changed. Triggered when data is * written and cleared when data is saved. diff --git a/src/com/webcodepro/applecommander/storage/StorageBundle.properties b/src/com/webcodepro/applecommander/storage/StorageBundle.properties index b766f47..1544aeb 100644 --- a/src/com/webcodepro/applecommander/storage/StorageBundle.properties +++ b/src/com/webcodepro/applecommander/storage/StorageBundle.properties @@ -23,6 +23,7 @@ DiskNameN={0} (Disk {1}) Dos33=DOS 3.3 LockedQ=Locked? DirectoryCreationNotSupported=Unable to create directories. +Gutenberg=Gutenberg ##### FIX ##### ############### @@ -155,6 +156,9 @@ DosFormatDisk.InvalidTrackAndSectorCombinationError=Invalid track ({0}), sector DosFileEntry.DosFileEntryLengthError=A DOS 3.3 file entry must be {0} bytes long\! DosFileEntry.UnableToSetAddressError=Unable to set address for DosFileEntry [{0}] +# GutenbergFileEntry +GutenbergFileEntry.GutenbergFileEntryLengthError=A Gutenberg file entry must be {0} bytes long\! + # CpmFormatDisk CpmFormatDisk.DiskName=CP/M Volume CpmFormatDisk.Cpm=CP/M diff --git a/src/com/webcodepro/applecommander/storage/filters/GutenbergFileFilter.java b/src/com/webcodepro/applecommander/storage/filters/GutenbergFileFilter.java new file mode 100644 index 0000000..430733d --- /dev/null +++ b/src/com/webcodepro/applecommander/storage/filters/GutenbergFileFilter.java @@ -0,0 +1,553 @@ +/* + * AppleCommander - An Apple ][ image utility. + * Copyright (C) 2002, 2008 by Robert Greene + * robgreene at users.sourceforge.net + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * 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 GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package com.webcodepro.applecommander.storage.filters; + +import java.io.ByteArrayOutputStream; +import java.io.PrintWriter; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.StringTokenizer; + +import com.webcodepro.applecommander.storage.FileEntry; +import com.webcodepro.applecommander.storage.FileFilter; +import com.webcodepro.applecommander.ui.AppleCommander; +import com.webcodepro.applecommander.util.AppleUtil; + +/** + * Extract the contents of an ancient word processor file (might be Word + * Perfect) and convert to a text format. Currently supported formats are plain + * text, HTML, or RTF. These are not exact duplicates, but they are close + * approximations. RTF format is suitable for conversion to other word + * processors. + *

+ * To choose export format, use the appropriately named select method. + *

+ * Date created: Dec 18, 2008 9:09:21 AM + * + * @author David Schmidt + */ +public class GutenbergFileFilter implements FileFilter { + /* + * This list identifies the various rendering options. As the internal + * format may change in the future, the internal representation is hidden + * and the developer should use the appropriate select method. + */ + private static final int RENDER_AS_TEXT = 0; + private static final int RENDER_AS_HTML = 1; + private static final int RENDER_AS_RTF = 2; + private int rendering = RENDER_AS_HTML; + /** + * Constructor for GutenbergFileFilter. + */ + public GutenbergFileFilter() { + super(); + } + + /** + * Process the given FileEntry and return a byte array with filtered data. + * + * @see com.webcodepro.applecommander.storage.FileFilter#filter(FileEntry) + */ + public byte[] filter(FileEntry fileEntry) { + byte[] fileData = fileEntry.getFileData(); + int offset = 0; + ByteArrayOutputStream byteArray = new ByteArrayOutputStream( + fileData.length); + PrintWriter printWriter = new PrintWriter(byteArray, true); + while (offset < fileData.length) { + fileData[offset] = (byte) (fileData[offset++] & 0x7f); + } + String preprocess = new String(fileData).trim(); + handleTranslation(preprocess, printWriter, rendering); + printWriter.flush(); + return byteArray.toByteArray(); + } + + /** + * Transform text into desired destination format + */ + protected void handleTranslation(String raw, PrintWriter output, int rendering) { + boolean ignoreBr = false; + boolean inHeader = false; + boolean inItalics = false; + boolean inBold = false; + boolean inCenter = false; + boolean inUnderline = false; + boolean inSuperscript = false; + String cooked = raw.replaceAll("\\x00", ""); //$NON-NLS-1$ $NON-NLS-2$ Remove nulls + cooked=cooked.replaceAll("<[a|A]1>", ""); //$NON-NLS-1$ $NON-NLS-2$ File start + cooked=cooked.replaceAll("", ""); //$NON-NLS-1$ $NON-NLS-2$ End of file + cooked=cooked.replaceAll("", ""); //$NON-NLS-1$ $NON-NLS-2$ File start + cooked=cooked.replaceAll("", ""); //$NON-NLS-1$ $NON-NLS-2$ File start + cooked=cooked.replaceAll("<2>", ""); //$NON-NLS-1$ $NON-NLS-2$ Dunno what this is + cooked=cooked.replaceAll("", ""); //$NON-NLS-1$ $NON-NLS-2$ Dunno what this is + cooked=cooked.replaceAll("", ""); //$NON-NLS-1$ $NON-NLS-2$ Dunno what this is + cooked=cooked.replaceAll("", ""); //$NON-NLS-1$ $NON-NLS-2$ Dunno what this is + cooked=cooked.replaceAll("<[h|H]1>(.*)", "

$1

"); //$NON-NLS-1$ $NON-NLS-2$ Bound a h1 heading + cooked=cooked.replaceAll("<[h|H]2>(.*)", "

$1

"); //$NON-NLS-1$ $NON-NLS-2$ Bound a h2 heading + cooked=cooked.replaceAll("<[h|H]3>(.*)", "

$1

"); //$NON-NLS-1$ $NON-NLS-2$ Bound a h3 heading + cooked=cooked.replaceAll("<[h|H]4>(.*)", "

$1

"); //$NON-NLS-1$ $NON-NLS-2$ Bound a h4 heading + cooked=cooked.replaceAll("<[n|N]1>(.*)", "

$1

"); //$NON-NLS-1$ $NON-NLS-2$ Another kind of heading? Give it boundaries + cooked=cooked.replaceAll("<[n|N]2>(.*)", "

$1

"); //$NON-NLS-1$ $NON-NLS-2$ Another kind of heading? Give it boundaries + cooked=cooked.replaceAll("<[n|N]3>(.*)", "

$1

"); //$NON-NLS-1$ $NON-NLS-2$ Another kind of heading? Give it boundaries + cooked=cooked.replaceAll("<[t|T]1>", "

"); //$NON-NLS-1$ $NON-NLS-2$ Tab level 1 + cooked=cooked.replaceAll("<[t|T]2>(.*)", "

$1
"); //$NON-NLS-1$ $NON-NLS-2$ Tab level 2 + cooked=cooked.replaceAll("<[t|T]3>(.*)", "
$1
"); //$NON-NLS-1$ $NON-NLS-2$ Tab level 3 + cooked=cooked.replaceAll("\\x0f", "
"); //$NON-NLS-1$ $NON-NLS-2$ Italics off + cooked=cooked.replaceAll("\\x01(.*)", " $1"); //$NON-NLS-1$ $NON-NLS-2$ Italics on + cooked=cooked.replaceAll("\\x02(.*)", " $1"); //$NON-NLS-1$ $NON-NLS-2$ Italics on + cooked=cooked.replaceAll("~", "\""); //$NON-NLS-1$ $NON-NLS-2$ Leading quote + cooked=cooked.replaceAll("_", "-"); //$NON-NLS-1$ $NON-NLS-2$ + StringTokenizer newlines = new StringTokenizer(cooked, "\r", false); //$NON-NLS-1$ + switch (rendering) + { + case RENDER_AS_HTML: + output.println(""); + break; + case RENDER_AS_RTF: + output.print("{\\rtf1"); //$NON-NLS-1$ + output.print("{\\fonttbl{\\f0\\fmodern\\fprq1;}}"); //$NON-NLS-1$ + output.print("{\\*\\generator AppleCommander "); //$NON-NLS-1$ + output.print(AppleCommander.VERSION); + output.println(";}"); //$NON-NLS-1$ + output.print("\\f0 "); //$NON-NLS-1$ + break; + default: + break; + } + while (newlines.hasMoreTokens()) + { + int mode = 0; + String line = newlines.nextToken(); + StringTokenizer commands = new StringTokenizer(line,"<>",true); + while (commands.hasMoreTokens()) + { + String t = commands.nextToken(); + if (t.equals("<")) //$NON-NLS-1$ + mode = 1; + else if (t.equals(">")) //$NON-NLS-1$ + mode = 0; + else if (mode == 1) { + if (t.startsWith("NF")) //$NON-NLS-1$ + { + // do nothing... consume it + } + else if (t.equalsIgnoreCase("i") && (!inItalics)) //$NON-NLS-1$ + { + // Italics on + switch (rendering) + { + case RENDER_AS_HTML: + output.print(""); //$NON-NLS-1$ + break; + case RENDER_AS_RTF: + output.println("\\i "); //$NON-NLS-1$ + break; + default: + break; + } + inItalics = true; + } + else if (t.equalsIgnoreCase("/i")) //$NON-NLS-1$ + { + // Italics off + switch (rendering) + { + case RENDER_AS_HTML: + output.print(""); //$NON-NLS-1$ + break; + case RENDER_AS_RTF: + output.println("\\i0 "); //$NON-NLS-1$ + break; + default: + break; + } + inItalics = false; + } + else if (t.equalsIgnoreCase("p") || t.startsWith("j") || t.startsWith("J")) //$NON-NLS-1$ $NON-NLS-2$ $NON-NLS-3$ + { + switch (rendering) + { + case RENDER_AS_HTML: + output.print("

"); //$NON-NLS-1$ + break; + case RENDER_AS_RTF: + output.println("\\par "); //$NON-NLS-1$ + break; + default: + break; + } + ignoreBr = true; + } + else if (t.equalsIgnoreCase("UL") && (!inUnderline)) //$NON-NLS-1$ + { + switch (rendering) + { + // Underline on + case RENDER_AS_HTML: + output.print(""); //$NON-NLS-1$ + break; + case RENDER_AS_RTF: + output.println("\\ul "); //$NON-NLS-1$ + break; + default: + break; + } + inUnderline = true; + } + else if ((t.equalsIgnoreCase("KU") || t.equalsIgnoreCase("KL") || t.equalsIgnoreCase("UK")) && (inUnderline)) //$NON-NLS-1$ $NON-NLS-2$ $NON-NLS-3$ + { + // Underline off + switch (rendering) + { + case RENDER_AS_HTML: + output.print(""); //$NON-NLS-1$ + break; + case RENDER_AS_RTF: + output.println("\\ulnone "); //$NON-NLS-1$ + break; + default: + break; + } + inUnderline = false; + } + else if ((t.equalsIgnoreCase("BO") || t.equalsIgnoreCase("b1")) && (!inBold)) //$NON-NLS-1$ $NON-NLS-2$ + { + // Bold on + switch (rendering) + { + case RENDER_AS_HTML: + output.print(""); //$NON-NLS-1$ + break; + case RENDER_AS_RTF: + output.println("\\b "); //$NON-NLS-1$ + break; + default: + break; + } + inBold = true; + } + else if (t.equalsIgnoreCase("KB")) //$NON-NLS-1$ + { + // Bold off + switch (rendering) + { + case RENDER_AS_HTML: + output.print(""); //$NON-NLS-1$ + break; + case RENDER_AS_RTF: + output.println("\\b0 "); //$NON-NLS-1$ + break; + default: + break; + } + inUnderline = false; + } + else if ((t.equalsIgnoreCase("UFA") || t.equalsIgnoreCase("UFP") || t.equals("UFY") || t.equalsIgnoreCase("f1")) && (inSuperscript == false)) //$NON-NLS-1$ $NON-NLS-2$ $NON-NLS-3$ $NON-NLS-4$ + { + // Superscript on + switch (rendering) + { + case RENDER_AS_HTML: + output.print(""); //$NON-NLS-1$ + break; + case RENDER_AS_RTF: + output.println("\\super "); //$NON-NLS-1$ + break; + default: + break; + } + inSuperscript = true; + } + else if ((t.equalsIgnoreCase("UFM") || t.equalsIgnoreCase("f2")) && (inSuperscript == true)) //$NON-NLS-1$ $NON-NLS-2$ + { + // Superscript off + switch (rendering) + { + case RENDER_AS_HTML: + output.print(""); //$NON-NLS-1$ + break; + case RENDER_AS_RTF: + output.println("\\nosupersub"); //$NON-NLS-1$ + break; + default: + break; + } + inSuperscript = false; + } + else if (t.equalsIgnoreCase("co") && (inCenter == false)) //$NON-NLS-1$ + { + switch (rendering) + { + case RENDER_AS_HTML: + output.print("

"); //$NON-NLS-1$ + break; + case RENDER_AS_RTF: + output.println("\\pard\\qc "); //$NON-NLS-1$ + break; + default: + break; + } + inCenter = true; + ignoreBr = true; + } + else if (t.equalsIgnoreCase("h8") && (inCenter == true)) //$NON-NLS-1$ + { + // Center off + switch (rendering) + { + case RENDER_AS_HTML: + output.print("
"); //$NON-NLS-1$ + break; + case RENDER_AS_RTF: + output.println("\\par \\pard "); //$NON-NLS-1$ + break; + default: + break; + } + inCenter = false; + ignoreBr = true; + } + else if (t.startsWith("h") && (!inHeader)) //$NON-NLS-1$ + { + ignoreBr = true; + inHeader = true; + switch (rendering) + { + case RENDER_AS_HTML: + if (t.equalsIgnoreCase("h1")) //$NON-NLS-1$ + output.print("

"); //$NON-NLS-1$ + else if (t.equalsIgnoreCase("h2")) //$NON-NLS-1$ + output.print("

"); //$NON-NLS-1$ + else if (t.equalsIgnoreCase("h3")) //$NON-NLS-1$ + output.print("

"); //$NON-NLS-1$ + else if (t.equalsIgnoreCase("h4")) //$NON-NLS-1$ + output.print("

"); //$NON-NLS-1$ + break; + case RENDER_AS_RTF: + if (t.equalsIgnoreCase("h1")) //$NON-NLS-1$ + output.print("\\pard\\s1\\b\\fs48 "); //$NON-NLS-1$ + else if (t.equalsIgnoreCase("h2")) //$NON-NLS-1$ + output.print("\\pard\\s2\\b\\fs36 "); //$NON-NLS-1$ + else if (t.equalsIgnoreCase("h3")) //$NON-NLS-1$ + output.print("\\pard\\s3\\b\\fs27 "); //$NON-NLS-1$ + else if (t.equalsIgnoreCase("h4")) //$NON-NLS-1$ + output.print("\\pard\\s4\\b\\fs24 "); //$NON-NLS-1$ + break; + default: + output.println(); + break; + } + } + else if ((t.startsWith("/h")) && (inHeader)) + { + ignoreBr = true; + inHeader = false; + switch (rendering) + { + case RENDER_AS_HTML: + if (t.equalsIgnoreCase("/h1")) //$NON-NLS-1$ + output.print("

"); //$NON-NLS-1$ + else if (t.equalsIgnoreCase("/h2")) //$NON-NLS-1$ + output.print(""); //$NON-NLS-1$ + else if (t.equalsIgnoreCase("/h3")) //$NON-NLS-1$ + output.print(""); //$NON-NLS-1$ + else if (t.equalsIgnoreCase("/h4")) //$NON-NLS-1$ + output.print(""); //$NON-NLS-1$ + break; + case RENDER_AS_RTF: + output.print("\\b0\\par\\fs24 "); //$NON-NLS-1$ + break; + default: + output.println(); + break; + } + } + else if (t.startsWith("blockquote")) // Indent $NON-NLS-1$ + { + switch (rendering) + { + case RENDER_AS_HTML: + output.print("
"); //$NON-NLS-1$ + break; + case RENDER_AS_RTF: + output.println(""); //$NON-NLS-1$ + break; + default: + output.print(" "); //$NON-NLS-1$ + break; + } + ignoreBr = true; + } + else if (t.startsWith("/blockquote")) // Outdent $NON-NLS-1$ + { + switch (rendering) + { + case RENDER_AS_HTML: + output.print("
"); //$NON-NLS-1$ + break; + case RENDER_AS_RTF: + output.println(""); //$NON-NLS-1$ + break; + default: + break; + } + ignoreBr = true; + } +// else +// System.err.println("Ignored command: <"+t+">"); + } + else + { + // System.out.println("Data: ["+t+"]"); + output.print(t); + } + } + if (!ignoreBr) + handleReturn(output); + ignoreBr = false; + + switch (rendering) + { + // turn off many types of formatting stuff at the end of lines + case RENDER_AS_HTML: + if (inItalics) + output.print(""); //$NON-NLS-1$ + if (inBold) + output.print(""); //$NON-NLS-1$ + if (inUnderline) + output.print(""); //$NON-NLS-1$ + if (inSuperscript) + output.print(""); //$NON-NLS-1$ + case RENDER_AS_RTF: + if (inItalics) + output.print("\\i0 "); //$NON-NLS-1$ + if (inBold) + output.print("\\b0 "); //$NON-NLS-1$ + if (inUnderline) + output.print("\\ulnone"); //$NON-NLS-1$ + if (inSuperscript) + output.print("\\nosupersub"); //$NON-NLS-1$ + break; + default: + break; + } + inItalics = false; + inBold = false; + inUnderline = false; + inSuperscript = false; + inHeader = false; + } + // Put the finishing touches on the document + switch (rendering) + { + case RENDER_AS_HTML: + output.println(""); //$NON-NLS-1$ + break; + case RENDER_AS_RTF: + output.println("}"); //$NON-NLS-1$ + break; + default: + break; + } + return; + } + + /** + * Deal with carriage-return. + */ + protected void handleReturn(PrintWriter printWriter) { + if (isHtmlRendering()) + printWriter.println("
"); //$NON-NLS-1$ + else if (isRtfRendering()) + printWriter.println("\\par"); //$NON-NLS-1$ + else + printWriter.println(); + } + + + /** + * Give suggested file name. + * + * @see com.webcodepro.applecommander.storage.FileFilter#getSuggestedFileName(FileEntry) + */ + public String getSuggestedFileName(FileEntry fileEntry) { + String fileName = fileEntry.getFilename().trim(); + String extension = ".txt"; //$NON-NLS-1$ + if (isHtmlRendering()) + extension = ".html"; //$NON-NLS-1$ + else if (isRtfRendering()) + extension = ".rtf"; //$NON-NLS-1$ + + if (!fileName.toLowerCase().endsWith(extension)) { + fileName = fileName + extension; + } + return fileName; + } + + /** + * Set the rendering method. + */ + protected void setRendering(int rendering) { + this.rendering = rendering; + } + + /** + * Indicates if this is a text rendering. + */ + public boolean isTextRendering() { + return rendering == RENDER_AS_TEXT; + } + + /** + * Indicates if this is an HTML rendering. + */ + public boolean isHtmlRendering() { + return rendering == RENDER_AS_HTML; + } + + /** + * Indicates if this is an RTF rendering. + */ + public boolean isRtfRendering() { + return rendering == RENDER_AS_RTF; + } + + /** + * Selects the text rendering engine. + */ + public void selectTextRendering() { + rendering = RENDER_AS_TEXT; + } + + /** + * Selects the HTML rendering engine. + */ + public void selectHtmlRendering() { + rendering = RENDER_AS_HTML; + } + + /** + * Selects the RTF rendering engine. + */ + public void selectRtfRendering() { + rendering = RENDER_AS_RTF; + } +} \ No newline at end of file diff --git a/src/com/webcodepro/applecommander/storage/os/gutenberg/GutenbergFileEntry.java b/src/com/webcodepro/applecommander/storage/os/gutenberg/GutenbergFileEntry.java new file mode 100644 index 0000000..dfa929c --- /dev/null +++ b/src/com/webcodepro/applecommander/storage/os/gutenberg/GutenbergFileEntry.java @@ -0,0 +1,343 @@ +/* + * AppleCommander - An Apple ][ image utility. + * Copyright (C) 2002 by Robert Greene + * robgreene at users.sourceforge.net + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * 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 GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package com.webcodepro.applecommander.storage.os.gutenberg; + +import java.text.NumberFormat; +import java.util.ArrayList; +import java.util.List; + +import com.webcodepro.applecommander.storage.Disk; +import com.webcodepro.applecommander.storage.DiskFullException; +import com.webcodepro.applecommander.storage.FileEntry; +import com.webcodepro.applecommander.storage.FileFilter; +import com.webcodepro.applecommander.storage.FormattedDisk; +import com.webcodepro.applecommander.storage.StorageBundle; +import com.webcodepro.applecommander.storage.filters.GutenbergFileFilter; +import com.webcodepro.applecommander.util.AppleUtil; +import com.webcodepro.applecommander.util.TextBundle; + +/** + * Represents a DOS file entry on disk. + *

+ * Date created: Oct 4, 2002 5:15:25 PM + * @author Rob Greene + */ +public class GutenbergFileEntry implements FileEntry { + private TextBundle textBundle = StorageBundle.getInstance(); + /** + * Indicates the length in bytes of the DOS file entry field. + */ + public static final int FILE_DESCRIPTIVE_ENTRY_LENGTH = 16; + /** + * Holds the disk the FileEntry is attached to. + */ + private GutenbergFormatDisk disk; + /** + * Track of the FileEntry location. + */ + private int track; + /** + * Sector of the FileEntry location. + */ + private int sector; + /** + * Offset into sector of FileEntry location. + */ + private int offset; + + /** + * Constructor for GutenbergFileEntry. + */ + public GutenbergFileEntry(GutenbergFormatDisk disk, int track, int sector, int offset) { + super(); + this.disk = disk; + this.track = track; + this.sector = sector; + this.offset = offset; + } + + /** + * Read the FileEntry from the disk image. + */ + protected byte[] readFileEntry() { + byte[] sectorData = disk.readSector(track, sector); + byte[] fileEntry = new byte[FILE_DESCRIPTIVE_ENTRY_LENGTH]; + System.arraycopy(sectorData, offset, fileEntry, 0, fileEntry.length); + return fileEntry; + } + + /** + * Write the FileEntry to the disk image. + */ + protected void writeFileEntry(byte[] fileEntry) { + if (fileEntry.length != FILE_DESCRIPTIVE_ENTRY_LENGTH) { + throw new IllegalArgumentException(textBundle. + format("GutenbergFileEntry.GutenbergFileEntryLengthError", //$NON-NLS-1$ + FILE_DESCRIPTIVE_ENTRY_LENGTH)); + } + byte[] sectorData = disk.readSector(track, sector); + System.arraycopy(fileEntry, 0, sectorData, offset, fileEntry.length); + disk.writeSector(track, sector, sectorData); + } + + /** + * Return the maximum filename length. + */ + public int getMaximumFilenameLength() { + return 12; + } + + /** + * Return the name of this file. + * @see com.webcodepro.applecommander.storage.FileEntry#getFilename() + */ + public String getFilename() { + return AppleUtil.getString(readFileEntry(), 0, getMaximumFilenameLength()).trim(); + } + + /** + * Set the name of this file. + */ + public void setFilename(String filename) { + byte[] data = readFileEntry(); + AppleUtil.setString(data, 0, filename.toUpperCase(), getMaximumFilenameLength()); + writeFileEntry(data); + } + + /** + * Return the filetype of this file. + * @see com.webcodepro.applecommander.storage.FileEntry#getFiletype() + */ + public String getFiletype() { + return "T"; // Only one file type... text //$NON-NLS-1$ + } + + /** + * Set the filetype (typeless - unused) + */ + public void setFiletype(String filetype) { + } + + /** + * Identify if this file is locked. + * @see com.webcodepro.applecommander.storage.FileEntry#isLocked() + */ + public boolean isLocked() { + return true; + } + + /** + * Set the lock indicator (unused) + */ + public void setLocked(boolean lock) { + } + + /** + * Compute the size of this file (in bytes). + * @see com.webcodepro.applecommander.storage.FileEntry#getSize() + */ + public int getSize() { + // Nothing special, just compute from number of sectors + int size = getSectorsUsed() * Disk.SECTOR_SIZE; + return size; + } + + /** + * Compute the number of sectors used. + */ + public int getSectorsUsed() { + // Follow the chain of sectors to find the end. + int track = getTrack(); + int sector = getSector(); + int sectors = 0; + while (track < 128) { + byte[] sectorData = disk.readSector(track, sector); + track = AppleUtil.getUnsignedByte(sectorData[0x04]); + sector = AppleUtil.getUnsignedByte(sectorData[0x05]); + sectors++; + } + return sectors; + } + + /** + * Set the number of sectors used. + */ + public void setSectorsUsed(int sectorsUsed) { + } + + /** + * Identify if this is a directory file. + * @see com.webcodepro.applecommander.storage.FileEntry#isDirectory() + */ + public boolean isDirectory() { + return false; + } + + /** + * Identify if this file has been deleted. + * @see com.webcodepro.applecommander.storage.FileEntry#isDeleted() + */ + public boolean isDeleted() { + return AppleUtil.getUnsignedByte(readFileEntry()[0x0d]) == 0x40; + } + + /** + * Delete this file. + */ + public void delete() { + } + + /** + * Get the standard file column header information. + * This default implementation is intended only for standard mode. + * displayMode is specified in FormattedDisk. + */ + public List getFileColumnData(int displayMode) { + NumberFormat numberFormat = NumberFormat.getNumberInstance(); + List list = new ArrayList(); + switch (displayMode) { + case FormattedDisk.FILE_DISPLAY_NATIVE: + list.add(isLocked() ? "*" : " "); //$NON-NLS-1$ //$NON-NLS-2$ + list.add(getFiletype()); + numberFormat.setMinimumIntegerDigits(3); + list.add(numberFormat.format(getSectorsUsed())); + list.add(getFilename()); + break; + case FormattedDisk.FILE_DISPLAY_DETAIL: + list.add(isLocked() ? "*" : " "); //$NON-NLS-1$ //$NON-NLS-2$ + list.add(getFiletype()); + list.add(getFilename()); + list.add(numberFormat.format(getSize())); + numberFormat.setMinimumIntegerDigits(3); + list.add(numberFormat.format(getSectorsUsed())); + list.add(isDeleted() ? textBundle.get("Deleted") : ""); //$NON-NLS-1$//$NON-NLS-2$ + list.add("T" + getTrack() + " S" + getSector()); //$NON-NLS-1$ //$NON-NLS-2$ + break; + default: // FILE_DISPLAY_STANDARD + list.add(getFilename()); + list.add(getFiletype()); + list.add(numberFormat.format(getSize())); + list.add(isLocked() ? textBundle.get("Locked") : ""); //$NON-NLS-1$//$NON-NLS-2$ + break; + } + return list; + } + + /** + * Get the track of first track/sector list sector. + */ + public int getTrack() { + return AppleUtil.getUnsignedByte(readFileEntry()[0x0c]); + } + + /** + * Set the track of the first track/sector list sector. + */ + public void setTrack(int track) { + byte[] data = readFileEntry(); + data[0x0c] = (byte) track; + writeFileEntry(data); + } + + /** + * Get the sector of first track/sector list sector. + */ + public int getSector() { + return AppleUtil.getUnsignedByte(readFileEntry()[0x0d]); + } + + /** + * Set the sector of the first track/sector list sector. + */ + public void setSector(int sector) { + byte[] data = readFileEntry(); + data[0x0d] = (byte) sector; + writeFileEntry(data); + } + + /** + * Get file data. This handles any operating-system specific issues. + */ + public byte[] getFileData() { + return disk.getFileData(this); + } + + /** + * Set the file data. + * + * Note: The address can be set before the data is saved or + * after the data is saved. This is an attempt to make the + * API more easily usable. + * + * Empirically, the data must be set before the address is set. + */ + public void setFileData(byte[] data) throws DiskFullException { + disk.setFileData(this, data); + } + + /** + * Get the suggested FileFilter. This appears to be operating system + * specific, so each operating system needs to implement some manner + * of guessing the appropriate filter. + * FIXME - this code should be a helper class for DOS and RDOS! + */ + public FileFilter getSuggestedFilter() { + return new GutenbergFileFilter(); + } + + /** + * Determine if this is an assembly source code file. + */ + public boolean isAssemblySourceFile() { + return false; + } + + /** + * Get the FormattedDisk associated with this FileEntry. + * This is useful to interfaces that need to retrieve the associated + * disk. + */ + public FormattedDisk getFormattedDisk() { + return disk; + } + + /** + * Indicates if this filetype requires an address component. + * Note that the FormattedDisk also has this method - normally, + * this will defer to the method on FormattedDisk, as it will be + * more generic. + */ + public boolean needsAddress() { + return false; + } + + /** + * Set the address that this file loads at. + */ + public void setAddress(int address) { + } + + /** + * Indicates that this filetype can be compiled. + */ + public boolean canCompile() { + return false; + } +} diff --git a/src/com/webcodepro/applecommander/storage/os/gutenberg/GutenbergFormatDisk.java b/src/com/webcodepro/applecommander/storage/os/gutenberg/GutenbergFormatDisk.java new file mode 100644 index 0000000..0a242c5 --- /dev/null +++ b/src/com/webcodepro/applecommander/storage/os/gutenberg/GutenbergFormatDisk.java @@ -0,0 +1,718 @@ +/* + * AppleCommander - An Apple ][ image utility. + * Copyright (C) 2002, 2008 by Robert Greene + * robgreene at users.sourceforge.net + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * 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 GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package com.webcodepro.applecommander.storage.os.gutenberg; + +import java.util.ArrayList; +import java.util.List; + +import com.webcodepro.applecommander.storage.DirectoryEntry; +import com.webcodepro.applecommander.storage.DiskFullException; +import com.webcodepro.applecommander.storage.FileEntry; +import com.webcodepro.applecommander.storage.FormattedDisk; +import com.webcodepro.applecommander.storage.StorageBundle; +import com.webcodepro.applecommander.storage.physical.ImageOrder; +import com.webcodepro.applecommander.util.AppleUtil; +import com.webcodepro.applecommander.util.TextBundle; + +/** + * Manages a disk that is in Gutenberg Word Processor format. + *

+ * Date created: Dec 17, 2008 04:29:23 PM + * @author David Schmidt + */ +public class GutenbergFormatDisk extends FormattedDisk { + private TextBundle textBundle = StorageBundle.getInstance(); + /** + * Indicates the index of the track in the location array. + */ + public static final int TRACK_LOCATION_INDEX = 0; + /** + * Indicates the index of the sector in the location array. + */ + public static final int SECTOR_LOCATION_INDEX = 1; + /** + * The catalog track. + */ + public static final int CATALOG_TRACK = 17; + /** + * The VTOC sector. + */ + public static final int VTOC_SECTOR = 7; + /** + * The standard track/sector pairs in a track/sector list. + */ + public static final int TRACK_SECTOR_PAIRS = 122; + /** + * The list of filetypes available. + */ + private static final String[] filetypes = { + "T" //$NON-NLS-1$ + }; + + /** + * Use this inner interface for managing the disk usage data. + * This offloads format-specific implementation to the implementing class. + */ + private class WPDiskUsage implements DiskUsage { + private int[] location = null; + public boolean hasNext() { + return location == null + || (location[TRACK_LOCATION_INDEX] < getTracks() + && location[SECTOR_LOCATION_INDEX] < getSectors()); + } + public void next() { + if (location == null) { + location = new int[2]; + } else { + location[SECTOR_LOCATION_INDEX]++; + if (location[SECTOR_LOCATION_INDEX] >= getSectors()) { + location[SECTOR_LOCATION_INDEX] = 0; + location[TRACK_LOCATION_INDEX]++; + } + } + } + /** + * Get the free setting for the bitmap at the current location. + * I don't think there is a map stored on disk, however. + */ + public boolean isFree() { + if (location == null || location.length != 2) { + throw new IllegalArgumentException(StorageBundle.getInstance() + .get("DosFormatDisk.InvalidDimensionError")); //$NON-NLS-1$ + } + return false; + } + public boolean isUsed() { + return !isFree(); + } + } + + /** + * Constructor for GutenbergFormatDisk. + */ + public GutenbergFormatDisk(String filename, ImageOrder imageOrder) { + super(filename, imageOrder); + } + + /** + * Create a GutenbergFormatDisk. All DOS disk images are expected to + * be 140K in size. + */ + public static GutenbergFormatDisk[] create(String filename, ImageOrder imageOrder) { + GutenbergFormatDisk disk = new GutenbergFormatDisk(filename, imageOrder); + disk.format(); + return new GutenbergFormatDisk[] { disk }; + } + + /** + * Identify the operating system format of this disk as Gutenberg. + * @see com.webcodepro.applecommander.storage.FormattedDisk#getFormat() + */ + public String getFormat() { + return textBundle.get("Gutenberg"); //$NON-NLS-1$ + } + + /** + * Retrieve a list of files. + * @see com.webcodepro.applecommander.storage.FormattedDisk#getFiles() + */ + public List getFiles() { + List list = new ArrayList(); + int track = CATALOG_TRACK; + int sector = VTOC_SECTOR; + while (track < 40) { // iterate through all catalog sectors + byte[] catalogSector = readSector(track, sector); + int offset = 0x20; // First entry is 0x20 deep + while (offset < 0xff) { // iterate through all entries + if (catalogSector[offset] != -96) { + list.add(new GutenbergFileEntry(this, track, sector, offset)); + } + offset+= GutenbergFileEntry.FILE_DESCRIPTIVE_ENTRY_LENGTH; + } + track = AppleUtil.getUnsignedByte(catalogSector[4]); // Pull in the next catalog sector + sector = AppleUtil.getUnsignedByte(catalogSector[5]); + } + return list; + } + + /** + * Create a FileEntry. + */ + public FileEntry createFile() throws DiskFullException { + byte[] vtoc = readVtoc(); + int track = AppleUtil.getUnsignedByte(vtoc[1]); + int sector = AppleUtil.getUnsignedByte(vtoc[2]); + while (sector != 0) { // bug fix: iterate through all catalog _sectors_ + byte[] catalogSector = readSector(track, sector); + int offset = 0x0b; + while (offset < 0xff) { // iterate through all entries + int value = AppleUtil.getUnsignedByte(catalogSector[offset]); + if (value == 0 || value == 0xff) { + return new GutenbergFileEntry(this, track, sector, offset); + } + offset+= GutenbergFileEntry.FILE_DESCRIPTIVE_ENTRY_LENGTH; + } + track = catalogSector[1]; + sector = catalogSector[2]; + } + throw new DiskFullException(textBundle.get("DosFormatDisk.NoMoreSpaceError")); //$NON-NLS-1$ + } + + /** + * Identify if additional directories can be created. This + * may indicate that directories are not available to this + * operating system or simply that the disk image is "locked" + * to writing. + */ + public boolean canCreateDirectories() { + return false; + } + + /** + * Indicates if this disk image can create a file. + * If not, the reason may be as simple as it has not beem implemented + * to something specific about the disk. + */ + public boolean canCreateFile() { + return false; + } + + /** + * Compute the amount of freespace available on the disk. + * This algorithm completely ignores tracks and sectors by + * running through the entire bitmap stored on the VTOC. + * @see com.webcodepro.applecommander.storage.FormattedDisk#getFreeSpace() + */ + public int getFreeSpace() { + return getFreeSectors() * SECTOR_SIZE; + } + + /** + * Comput the number of free sectors available on the disk. + */ + public int getFreeSectors() { + return 0; + } + + /** + * Return the amount of used space in bytes. + * @see com.webcodepro.applecommander.storage.FormattedDisk#getUsedSpace() + */ + public int getUsedSpace() { + return APPLE_140KB_DISK; + } + + /** + * Compute the number of used sectors on the disk. + */ + public int getUsedSectors() { + return getTotalSectors() - getFreeSectors(); + } + + /** + * Compute the total number of sectors available on the disk. + */ + public int getTotalSectors() { + int tracks = getTracks(); + int sectors = getSectors(); + return tracks * sectors; + } + + /** + * Return the WP disk name. + * @see com.webcodepro.applecommander.storage.FormattedDisk#getDiskName() + */ + public String getDiskName() { + // Pull the disk name out... + return AppleUtil.getString(readVtoc(), 6, 9).trim(); + } + + /** + * Return the VTOC (Volume Table Of Contents). + */ + protected byte[] readVtoc() { + return readSector(CATALOG_TRACK, VTOC_SECTOR); + } + + /** + * Save the VTOC (Volume Table Of Contents) to disk. + */ + protected void writeVtoc(byte[] vtoc) { + writeSector(CATALOG_TRACK, VTOC_SECTOR, vtoc); + } + + /** + * Get the disk usage iterator. + */ + public DiskUsage getDiskUsage() { + return new WPDiskUsage(); + } + + /** + * Get the number of tracks on this disk. + */ + public int getTracks() { + byte[] vtoc = readVtoc(); + return AppleUtil.getUnsignedByte(vtoc[0x34]); + } + + /** + * Get the number of sectors on this disk. + */ + public int getSectors() { + byte[] vtoc = readVtoc(); + return AppleUtil.getUnsignedByte(vtoc[0x35]); + } + + /** + * Get suggested dimensions for display of bitmap. For DOS 3.3, that information + * is stored in the VTOC, and that information is fairly important. + * @see com.webcodepro.applecommander.storage.FormattedDisk#getBitmapDimensions() + */ + public int[] getBitmapDimensions() { + int tracks = getTracks(); + int sectors = getSectors(); + return new int[] { tracks, sectors }; + } + + /** + * Get the length of the bitmap. + */ + public int getBitmapLength() { + return getTotalSectors(); + } + + /** + * Get the labels to use in the bitmap. + */ + public String[] getBitmapLabels() { + return new String[] { textBundle.get("DosFormatDisk.Track"), textBundle.get("DosFormatDisk.Sector") }; //$NON-NLS-1$ //$NON-NLS-2$ + } + + /** + * Get WP-specific disk information. + */ + public List getDiskInformation() { + List list = super.getDiskInformation(); + return list; + } + + /** + * Get the standard file column header information. + * This default implementation is intended only for standard mode. + */ + public List getFileColumnHeaders(int displayMode) { + List list = new ArrayList(); + switch (displayMode) { + case FILE_DISPLAY_NATIVE: + list.add(new FileColumnHeader(" ", 1, FileColumnHeader.ALIGN_CENTER)); //$NON-NLS-1$ + list.add(new FileColumnHeader(textBundle.get("DosFormatDisk.Type"), 1, FileColumnHeader.ALIGN_CENTER)); //$NON-NLS-1$ + list.add(new FileColumnHeader(textBundle.get("DosFormatDisk.SizeInSectors"), 3, FileColumnHeader.ALIGN_RIGHT)); //$NON-NLS-1$ + list.add(new FileColumnHeader(textBundle.get("Name"), 30, //$NON-NLS-1$ + FileColumnHeader.ALIGN_LEFT)); + break; + case FILE_DISPLAY_DETAIL: + list.add(new FileColumnHeader(" ", 1, FileColumnHeader.ALIGN_CENTER)); //$NON-NLS-1$ + list.add(new FileColumnHeader(textBundle.get("DosFormatDisk.Type"), 1, FileColumnHeader.ALIGN_CENTER)); //$NON-NLS-1$ + list.add(new FileColumnHeader(textBundle.get("Name"), 30, //$NON-NLS-1$ + FileColumnHeader.ALIGN_LEFT)); + list.add(new FileColumnHeader(textBundle.get("SizeInBytes"), 6, //$NON-NLS-1$ + FileColumnHeader.ALIGN_RIGHT)); + list.add(new FileColumnHeader(textBundle.get("DosFormatDisk.SizeInSectors"), 3, FileColumnHeader.ALIGN_RIGHT)); //$NON-NLS-1$ + list.add(new FileColumnHeader(textBundle.get("DeletedQ"), 7, //$NON-NLS-1$ + FileColumnHeader.ALIGN_CENTER)); + list.add(new FileColumnHeader(textBundle.get("DosFormatDisk.TrackAndSectorList"), 7, FileColumnHeader.ALIGN_CENTER)); //$NON-NLS-1$ + break; + default: // FILE_DISPLAY_STANDARD + list.addAll(super.getFileColumnHeaders(displayMode)); + break; + } + return list; + } + + /** + * Indicates if this disk format supports "deleted" files. + */ + public boolean supportsDeletedFiles() { + return true; + } + + /** + * Indicates if this disk image can read data from a file. + */ + public boolean canReadFileData() { + return true; + } + + /** + * Indicates if this disk image can write data to a file. + */ + public boolean canWriteFileData() { + return false; + } + + /** + * Identify if this disk format as not capable of having directories. + * @see com.webcodepro.applecommander.storage.FormattedDisk#canHaveDirectories() + */ + public boolean canHaveDirectories() { + return false; + } + + /** + * Indicates if this disk image can delete a file. + */ + public boolean canDeleteFile() { + return false; + } + + /** + * Get the data associated with the specified FileEntry. + */ + public byte[] getFileData(FileEntry fileEntry) { + if ( !(fileEntry instanceof GutenbergFileEntry)) { + throw new IllegalArgumentException(textBundle.get("DosFormatDisk.InvalidFileEntryError")); //$NON-NLS-1$ + } + GutenbergFileEntry wpEntry = (GutenbergFileEntry) fileEntry; + // Size is calculated by sectors used - not actual size - as size varies + // on filetype, etc. + int filesize = wpEntry.getSectorsUsed(); + byte[] fileData = null; + if (filesize > 0) { + fileData = new byte[(wpEntry.getSectorsUsed()) * SECTOR_SIZE]; + } else { + fileData = new byte[0]; + // don't need to load it - also bypass potential issues + return fileData; + } + int track = wpEntry.getTrack(); + int sector = wpEntry.getSector(); + int offset = 0; + while (track < 128) { + byte[] sectorData = readSector(track,sector); + track = AppleUtil.getUnsignedByte(sectorData[0x04]); + sector = AppleUtil.getUnsignedByte(sectorData[0x05]); + System.arraycopy(sectorData, 6, fileData, offset, sectorData.length-6); + offset+= sectorData.length-6; + } + return fileData; + } + + /** + * Writes the raw bytes into the file. This bypasses any special formatting + * of the data (such as prepending the data with a length and/or an address). + * Typically, the FileEntry.setFileData method should be used. + */ + public void setFileData(FileEntry fileEntry, byte[] fileData) throws DiskFullException { + setFileData((GutenbergFileEntry)fileEntry, fileData); + } + + /** + * Set the data associated with the specified GutenbergFileEntry into sectors + * on the disk. + */ + protected void setFileData(GutenbergFileEntry fileEntry, byte[] data) throws DiskFullException { + // compute free space and see if the data will fit! + int numberOfDataSectors = (data.length + SECTOR_SIZE - 1) / SECTOR_SIZE; + int numberOfSectors = numberOfDataSectors + + (numberOfDataSectors + TRACK_SECTOR_PAIRS - 1) / TRACK_SECTOR_PAIRS; + if (numberOfSectors > getFreeSectors() + fileEntry.getSectorsUsed()) { + throw new DiskFullException( + textBundle.format("DosFormatDisk.NotEnoughSectorsError", //$NON-NLS-1$ + numberOfSectors, getFreeSectors())); + } + // free "old" data and just rewrite stuff... + // freeSectors(fileEntry); (not going to work...) + byte[] vtoc = readVtoc(); + int track = fileEntry.getTrack(); + int sector = fileEntry.getSector(); + if (track == 0 || track == 255) { + track = 1; + sector = 0; + while (true) { + if (isSectorFree(track,sector,vtoc)) { + break; + } + sector++; + if (sector >= getSectors()) { + track++; + sector = 0; + } + } + fileEntry.setTrack(track); + fileEntry.setSector(sector); + } + setSectorUsed(track, sector, vtoc); + byte[] trackSectorList = new byte[SECTOR_SIZE]; + int offset = 0; + int trackSectorOffset = 0x0c; + int totalSectors = 0; + int t=1; // initial search for space + int s=0; + while (offset < data.length) { + // locate next free sector + while (true) { + if (isSectorFree(t,s,vtoc)) { + break; + } + s++; + if (s >= getSectors()) { + t++; + s = 0; + } + } + setSectorUsed(t,s,vtoc); + if (trackSectorOffset >= 0x100) { + // filled up the first track/sector list - save it + trackSectorList[0x01] = (byte) t; + trackSectorList[0x02] = (byte) s; + writeSector(track, sector, trackSectorList); + trackSectorList = new byte[SECTOR_SIZE]; + trackSectorOffset = 0x0c; + track = t; + sector = s; + } else { + // write out a data sector + trackSectorList[trackSectorOffset] = (byte) t; + trackSectorList[trackSectorOffset+1] = (byte) s; + trackSectorOffset+= 2; + byte[] sectorData = new byte[SECTOR_SIZE]; + int length = Math.min(SECTOR_SIZE, data.length - offset); + System.arraycopy(data, offset, sectorData, 0, length); + writeSector(t,s,sectorData); + offset+= SECTOR_SIZE; + } + totalSectors++; + } + writeSector(track, sector, trackSectorList); // last T/S list + totalSectors++; + fileEntry.setSectorsUsed(totalSectors); + writeVtoc(vtoc); + } + + /** + * Free sectors used by a GutenbergFileEntry. + */ +/* + protected void freeSectors(GutenbergFileEntry GutenbergFileEntry) { + byte[] vtoc = readVtoc(); + int track = GutenbergFileEntry.getTrack(); + if (track == 255) return; + int sector = GutenbergFileEntry.getSector(); + while (track != 0) { + setSectorFree(track,sector,vtoc); + byte[] trackSectorList = readSector(track, sector); + track = AppleUtil.getUnsignedByte(trackSectorList[0x01]); + sector = AppleUtil.getUnsignedByte(trackSectorList[0x02]); + for (int i=0x0c; i<0x100; i+=2) { + int t = AppleUtil.getUnsignedByte(trackSectorList[i]); + if (t == 0) break; + int s = AppleUtil.getUnsignedByte(trackSectorList[i+1]); + setSectorFree(t,s,vtoc); + } + } + writeVtoc(vtoc); + } +*/ + /** + * Format the disk as DOS 3.3. + * @see com.webcodepro.applecommander.storage.FormattedDisk#format() + */ + public void format() { + getImageOrder().format(); + format(15, 35, 16); + } + + /** + * Format the disk as DOS 3.3 given the dymanic parameters. + * (Used for UniDOS and OzDOS.) + */ + protected void format(int firstCatalogSector, int tracksPerDisk, + int sectorsPerTrack) { + + writeBootCode(); + // create catalog sectors + byte[] data = new byte[SECTOR_SIZE]; + for (int sector=firstCatalogSector; sector > 0; sector--) { + if (sector > 1) { + data[0x01] = CATALOG_TRACK; + data[0x02] = (byte)(sector-1); + } else { + data[0x01] = 0; + data[0x02] = 0; + } + writeSector(CATALOG_TRACK, sector, data); + } + // create VTOC + data[0x01] = CATALOG_TRACK; // track# of first catalog sector + data[0x02] = (byte)firstCatalogSector; // sector# of first catalog sector + data[0x03] = 3; // DOS 3.3 formatted + data[0x06] = (byte)254; // DISK VOLUME# + data[0x27] = TRACK_SECTOR_PAIRS;// maximum # of T/S pairs in a sector + data[0x30] = CATALOG_TRACK+1; // last track where sectors allocated + data[0x31] = 1; // direction of allocation + data[0x34] = (byte)tracksPerDisk; // tracks per disk + data[0x35] = (byte)sectorsPerTrack;// sectors per track + data[0x37] = 1; // 36/37 are # of bytes per sector + for (int track=0; track> 3); + return 0x38 + trackOffset + sectorOffset; + } + + /** + * Compute the VTOC bit for the T/S map. + */ + protected int getFreeMapBit(int sector) { + int bit = sector & 0x7; + return bit; + } + + /** + * Validate track/sector range. This just validates the + * maximum values allowable for track and sector. + */ + protected void checkRange(int track, int sector) { + if (track > 50 || sector > 32) { + throw new IllegalArgumentException( + textBundle.format("DosFormatDisk.InvalidTrackAndSectorCombinationError", //$NON-NLS-1$ + track, sector)); + } + } + + /** + * Returns the logical disk number. Returns a 0 to indicate no numbering. + */ + public int getLogicalDiskNumber() { + return 0; + } + + /** + * Returns a valid filename for the given filename. + */ + public String getSuggestedFilename(String filename) { + int len = Math.min(filename.length(), 12); + return filename.toUpperCase().substring(0, len).trim(); + } + + /** + * Returns a valid filetype for the given filename. The most simple + * format will just assume a filetype of binary. This method is + * available for the interface to make an intelligent first guess + * as to the filetype. + */ + public String getSuggestedFiletype(String filename) { + return "T"; //$NON-NLS-1$ + } + + /** + * Returns a list of possible file types. Since the filetype is + * specific to each operating system, a simple String is used. + */ + public String[] getFiletypes() { + return filetypes; + } + + /** + * Indicates if this filetype requires an address component. + * For DOS, only the Binary type needs an address. + */ + public boolean needsAddress(String filetype) { + return "B".equals(filetype); //$NON-NLS-1$ + } + + /** + * Indicates if this FormattedDisk supports a disk map. + */ + public boolean supportsDiskMap() { + return true; + } + + /** + * Change to a different ImageOrder. Remains in DOS 3.3 format but + * the underlying order can chage. + * @see ImageOrder + */ + public void changeImageOrder(ImageOrder imageOrder) { + AppleUtil.changeImageOrderByTrackAndSector(getImageOrder(), imageOrder); + setImageOrder(imageOrder); + } + + /** + * Create a new DirectoryEntry. + * @see com.webcodepro.applecommander.storage.DirectoryEntry#createDirectory() + */ + public DirectoryEntry createDirectory() throws DiskFullException { + throw new UnsupportedOperationException(textBundle.get("DirectoryCreationNotSupported")); //$NON-NLS-1$ + } +} diff --git a/src/com/webcodepro/applecommander/ui/UiBundle.properties b/src/com/webcodepro/applecommander/ui/UiBundle.properties index 351a6c8..9d53bce 100644 --- a/src/com/webcodepro/applecommander/ui/UiBundle.properties +++ b/src/com/webcodepro/applecommander/ui/UiBundle.properties @@ -49,6 +49,7 @@ WordProcessorRenderingMenuItem=Rendering WordProcessorRenderAsTextMenuItem=Text WordProcessorRenderAsHtmlMenuItem=HTML WordProcessorRenderAsRtfMenuItem=RTF +GutenbergRenderingMenuItem=Gutenberg File Rendered as... ExportAsGraphicsMenuItem=Graphics... ExportGraphicsModeMenuItem=Mode ExportGraphicsAsHiresBlackAndWhiteMenuItem=Hi-Res B&W diff --git a/src/com/webcodepro/applecommander/ui/swt/DiskExplorerTab.java b/src/com/webcodepro/applecommander/ui/swt/DiskExplorerTab.java index b1ca1e1..d488fdb 100644 --- a/src/com/webcodepro/applecommander/ui/swt/DiskExplorerTab.java +++ b/src/com/webcodepro/applecommander/ui/swt/DiskExplorerTab.java @@ -87,6 +87,7 @@ import com.webcodepro.applecommander.storage.filters.GraphicsFileFilter; import com.webcodepro.applecommander.storage.filters.IntegerBasicFileFilter; import com.webcodepro.applecommander.storage.filters.PascalTextFileFilter; import com.webcodepro.applecommander.storage.filters.TextFileFilter; +import com.webcodepro.applecommander.storage.filters.GutenbergFileFilter; import com.webcodepro.applecommander.storage.os.prodos.ProdosDiskSizeDoesNotMatchException; import com.webcodepro.applecommander.storage.os.prodos.ProdosFormatDisk; import com.webcodepro.applecommander.storage.physical.ByteArrayImageLayout; @@ -153,6 +154,7 @@ public class DiskExplorerTab { private FileFilter fileFilter; private GraphicsFileFilter graphicsFilter = new GraphicsFileFilter(); private AppleWorksWordProcessorFileFilter awpFilter = new AppleWorksWordProcessorFileFilter(); + private GutenbergFileFilter wpFilter = new GutenbergFileFilter(); private int currentFormat = FormattedDisk.FILE_DISPLAY_STANDARD; private boolean formatChanged; @@ -612,6 +614,62 @@ public class DiskExplorerTab { item = new MenuItem(menu, SWT.SEPARATOR); + item = new MenuItem(menu, SWT.CASCADE); + item.setText(textBundle.get("GutenbergRenderingMenuItem")); //$NON-NLS-1$ + Menu subMenu2 = new Menu(shell, SWT.DROP_DOWN); + item.setMenu(subMenu2); + subMenu.addMenuListener(new MenuAdapter() { + /** + * Toggle all sub-menu MenuItems to the proper state to reflect + * the current file extension chosen. + */ + public void menuShown(MenuEvent event) { + Menu theMenu = (Menu) event.getSource(); + MenuItem[] subItems = theMenu.getItems(); + subItems[0].setSelection(getWPFilter().isTextRendering()); + subItems[1].setSelection(getWPFilter().isHtmlRendering()); + subItems[2].setSelection(getWPFilter().isRtfRendering()); + } + }); + item = new MenuItem(subMenu2, SWT.RADIO); + item.setText(textBundle.get("WordProcessorRenderAsTextMenuItem")); //$NON-NLS-1$ + item.addSelectionListener(new SelectionAdapter() { + /** + * Set the appropriate rendering style. + */ + public void widgetSelected(SelectionEvent event) { + getWPFilter().selectTextRendering(); + setFileFilter(getWPFilter()); + exportFile(null); + } + }); + item = new MenuItem(subMenu2, SWT.RADIO); + item.setText(textBundle.get("WordProcessorRenderAsHtmlMenuItem")); //$NON-NLS-1$ + item.addSelectionListener(new SelectionAdapter() { + /** + * Set the appropriate rendering style. + */ + public void widgetSelected(SelectionEvent event) { + getWPFilter().selectHtmlRendering(); + setFileFilter(getWPFilter()); + exportFile(null); + } + }); + item = new MenuItem(subMenu2, SWT.RADIO); + item.setText(textBundle.get("WordProcessorRenderAsRtfMenuItem")); //$NON-NLS-1$ + item.addSelectionListener(new SelectionAdapter() { + /** + * Set the appropriate rendering style. + */ + public void widgetSelected(SelectionEvent event) { + getWPFilter().selectRtfRendering(); + setFileFilter(getWPFilter()); + exportFile(null); + } + }); + + item = new MenuItem(menu, SWT.SEPARATOR); + item = new MenuItem(menu, SWT.NONE); item.setText(textBundle.get("ExportAsGraphicsMenuItem")); //$NON-NLS-1$ item.setEnabled(GraphicsFileFilter.isCodecAvailable()); @@ -953,6 +1011,7 @@ public class DiskExplorerTab { outputStream.write(data); outputStream.close(); } catch (Exception ex) { + ex.printStackTrace(); String errorMessage = ex.getMessage(); if (errorMessage == null) { errorMessage = ex.getClass().getName(); @@ -1596,6 +1655,7 @@ public class DiskExplorerTab { } } } else { // No CTRL key + if ((event.stateMask & SWT.ALT) != SWT.ALT) { // Ignore ALT key combinations like alt-F4! switch (event.keyCode) { case SWT.F2: // Standard file display changeCurrentFormat(FormattedDisk.FILE_DISPLAY_STANDARD); @@ -1611,6 +1671,7 @@ public class DiskExplorerTab { getShowDeletedFilesToolItem().setSelection(isShowDeletedFiles()); fillFileTable(getCurrentFileList()); break; + } } } } @@ -1939,6 +2000,10 @@ public class DiskExplorerTab { return awpFilter; } + protected GutenbergFileFilter getWPFilter() { + return wpFilter; + } + protected GraphicsFileFilter getGraphicsFilter() { return graphicsFilter; } diff --git a/src/com/webcodepro/applecommander/ui/swt/FileViewerWindow.java b/src/com/webcodepro/applecommander/ui/swt/FileViewerWindow.java index 4279f22..0081420 100644 --- a/src/com/webcodepro/applecommander/ui/swt/FileViewerWindow.java +++ b/src/com/webcodepro/applecommander/ui/swt/FileViewerWindow.java @@ -52,6 +52,7 @@ import com.webcodepro.applecommander.storage.filters.GraphicsFileFilter; import com.webcodepro.applecommander.storage.filters.IntegerBasicFileFilter; import com.webcodepro.applecommander.storage.filters.PascalTextFileFilter; import com.webcodepro.applecommander.storage.filters.TextFileFilter; +import com.webcodepro.applecommander.storage.filters.GutenbergFileFilter; import com.webcodepro.applecommander.ui.UiBundle; import com.webcodepro.applecommander.ui.swt.filteradapter.ApplesoftFilterAdapter; import com.webcodepro.applecommander.ui.swt.filteradapter.BusinessBASICFilterAdapter; @@ -187,10 +188,15 @@ public class FileViewerWindow { imageManager.get(ImageManager.ICON_VIEW_AS_SPREADSHEET) )); nativeFilterAdapterMap.put(AppleWorksWordProcessorFileFilter.class, - new TextFilterAdapter(this, textBundle.get("FileViewerWindow.WordprocessorButton"), //$NON-NLS-1$ - textBundle.get("FileViewerWindow.WordprocessorTooltip"), //$NON-NLS-1$ - imageManager.get(ImageManager.ICON_VIEW_AS_WORDPROCESSOR) - )); + new TextFilterAdapter(this, textBundle.get("FileViewerWindow.WordprocessorButton"), //$NON-NLS-1$ + textBundle.get("FileViewerWindow.WordprocessorTooltip"), //$NON-NLS-1$ + imageManager.get(ImageManager.ICON_VIEW_AS_WORDPROCESSOR) + )); + nativeFilterAdapterMap.put(GutenbergFileFilter.class, + new TextFilterAdapter(this, textBundle.get("FileViewerWindow.WordprocessorButton"), //$NON-NLS-1$ + textBundle.get("FileViewerWindow.WordprocessorTooltip"), //$NON-NLS-1$ + imageManager.get(ImageManager.ICON_VIEW_AS_WORDPROCESSOR) + )); nativeFilterAdapterMap.put(AssemblySourceFileFilter.class, new TextFilterAdapter(this, textBundle.get("FileViewerWindow.AssemblyButton"), //$NON-NLS-1$ textBundle.get("FileViewerWindow.AssemblyTooltip"), //$NON-NLS-1$