From ebf72c103e3cb75a66050fdc210e372af949d196 Mon Sep 17 00:00:00 2001 From: Robert Greene Date: Sun, 1 Dec 2002 02:21:00 +0000 Subject: [PATCH] Version 1.1.1 is the initial GPL release of AppleCommander. --- .classpath | 8 + .cvsignore | 2 + .project | 18 + HEADER | 21 + LICENSE | 270 ++++++ build/manifest.mf | 3 + imageSource/AppleCommanderLogo.mix | Bin 0 -> 141312 bytes imageSource/ExportWizardLogo.mix | Bin 0 -> 77312 bytes .../applecommander/storage/AppleUtil.java | 170 ++++ .../AppleWorksWordProcessorFileFilter.java | 212 +++++ .../storage/ApplesoftFileFilter.java | 124 +++ .../storage/BinaryFileFilter.java | 54 ++ .../applecommander/storage/Disk.java | 316 +++++++ .../applecommander/storage/DiskHelper.java | 89 ++ .../applecommander/storage/DosFileEntry.java | 271 ++++++ .../applecommander/storage/DosFormatDisk.java | 398 +++++++++ .../applecommander/storage/FileEntry.java | 91 ++ .../storage/FileEntryComparator.java | 99 +++ .../applecommander/storage/FileFilter.java | 40 + .../applecommander/storage/FormattedDisk.java | 299 +++++++ .../storage/GraphicsFileFilter.java | 402 +++++++++ .../storage/IntegerBasicFileFilter.java | 132 +++ .../storage/PascalFileEntry.java | 209 +++++ .../storage/PascalFormatDisk.java | 356 ++++++++ .../storage/ProdosCommonDirectoryHeader.java | 72 ++ .../storage/ProdosCommonEntry.java | 153 ++++ .../storage/ProdosFileEntry.java | 457 ++++++++++ .../storage/ProdosFormatDisk.java | 392 ++++++++ .../storage/ProdosSubdirectoryHeader.java | 66 ++ .../storage/ProdosVolumeDirectoryHeader.java | 44 + .../applecommander/storage/RdosFileEntry.java | 239 +++++ .../storage/RdosFormatDisk.java | 328 +++++++ .../storage/TextFileFilter.java | 69 ++ .../applecommander/test/DiskHelperTest.java | 223 +++++ .../applecommander/ui/AppleCommander.java | 66 ++ .../applecommander/ui/UserPreferences.java | 100 +++ .../ui/images/AppleCommanderLogo.gif | Bin 0 -> 10669 bytes .../ui/images/ExportWizardLogo.gif | Bin 0 -> 13334 bytes .../applecommander/ui/images/about.gif | Bin 0 -> 169 bytes .../applecommander/ui/images/appleicon.gif | Bin 0 -> 887 bytes .../applecommander/ui/images/deletedfiles.gif | Bin 0 -> 915 bytes .../applecommander/ui/images/deletefile.gif | Bin 0 -> 143 bytes .../ui/images/detailfileview.gif | Bin 0 -> 916 bytes .../applecommander/ui/images/diskicon.gif | Bin 0 -> 863 bytes .../applecommander/ui/images/exportfile.gif | Bin 0 -> 877 bytes .../applecommander/ui/images/importfile.gif | Bin 0 -> 879 bytes .../ui/images/nativefileview.gif | Bin 0 -> 887 bytes .../applecommander/ui/images/newdisk.gif | Bin 0 -> 896 bytes .../applecommander/ui/images/opendisk.gif | Bin 0 -> 907 bytes .../applecommander/ui/images/saveimage.gif | Bin 0 -> 187 bytes .../ui/images/standardfileview.gif | Bin 0 -> 878 bytes .../ui/swt/AppleWorksWordProcessorPane.java | 112 +++ .../ui/swt/DiskExplorerTab.java | 836 ++++++++++++++++++ .../applecommander/ui/swt/DiskInfoTab.java | 74 ++ .../applecommander/ui/swt/DiskMapTab.java | 351 ++++++++ .../applecommander/ui/swt/DiskWindow.java | 93 ++ .../ui/swt/DropDownSelectionListener.java | 106 +++ .../ui/swt/ExportFileDestinationPane.java | 121 +++ .../ui/swt/ExportFileStartPane.java | 160 ++++ .../ui/swt/ExportGraphicsTypePane.java | 146 +++ .../applecommander/ui/swt/ExportWizard.java | 225 +++++ .../applecommander/ui/swt/ImageCanvas.java | 61 ++ .../applecommander/ui/swt/ImageManager.java | 214 +++++ .../ui/swt/SwtAppleCommander.java | 229 +++++ .../applecommander/ui/swt/WizardPane.java | 47 + 65 files changed, 8568 insertions(+) create mode 100644 .classpath create mode 100644 .cvsignore create mode 100644 .project create mode 100644 HEADER create mode 100644 LICENSE create mode 100644 build/manifest.mf create mode 100644 imageSource/AppleCommanderLogo.mix create mode 100644 imageSource/ExportWizardLogo.mix create mode 100644 src/com/webcodepro/applecommander/storage/AppleUtil.java create mode 100644 src/com/webcodepro/applecommander/storage/AppleWorksWordProcessorFileFilter.java create mode 100644 src/com/webcodepro/applecommander/storage/ApplesoftFileFilter.java create mode 100644 src/com/webcodepro/applecommander/storage/BinaryFileFilter.java create mode 100644 src/com/webcodepro/applecommander/storage/Disk.java create mode 100644 src/com/webcodepro/applecommander/storage/DiskHelper.java create mode 100644 src/com/webcodepro/applecommander/storage/DosFileEntry.java create mode 100644 src/com/webcodepro/applecommander/storage/DosFormatDisk.java create mode 100644 src/com/webcodepro/applecommander/storage/FileEntry.java create mode 100644 src/com/webcodepro/applecommander/storage/FileEntryComparator.java create mode 100644 src/com/webcodepro/applecommander/storage/FileFilter.java create mode 100644 src/com/webcodepro/applecommander/storage/FormattedDisk.java create mode 100644 src/com/webcodepro/applecommander/storage/GraphicsFileFilter.java create mode 100644 src/com/webcodepro/applecommander/storage/IntegerBasicFileFilter.java create mode 100644 src/com/webcodepro/applecommander/storage/PascalFileEntry.java create mode 100644 src/com/webcodepro/applecommander/storage/PascalFormatDisk.java create mode 100644 src/com/webcodepro/applecommander/storage/ProdosCommonDirectoryHeader.java create mode 100644 src/com/webcodepro/applecommander/storage/ProdosCommonEntry.java create mode 100644 src/com/webcodepro/applecommander/storage/ProdosFileEntry.java create mode 100644 src/com/webcodepro/applecommander/storage/ProdosFormatDisk.java create mode 100644 src/com/webcodepro/applecommander/storage/ProdosSubdirectoryHeader.java create mode 100644 src/com/webcodepro/applecommander/storage/ProdosVolumeDirectoryHeader.java create mode 100644 src/com/webcodepro/applecommander/storage/RdosFileEntry.java create mode 100644 src/com/webcodepro/applecommander/storage/RdosFormatDisk.java create mode 100644 src/com/webcodepro/applecommander/storage/TextFileFilter.java create mode 100644 src/com/webcodepro/applecommander/test/DiskHelperTest.java create mode 100644 src/com/webcodepro/applecommander/ui/AppleCommander.java create mode 100644 src/com/webcodepro/applecommander/ui/UserPreferences.java create mode 100644 src/com/webcodepro/applecommander/ui/images/AppleCommanderLogo.gif create mode 100644 src/com/webcodepro/applecommander/ui/images/ExportWizardLogo.gif create mode 100644 src/com/webcodepro/applecommander/ui/images/about.gif create mode 100644 src/com/webcodepro/applecommander/ui/images/appleicon.gif create mode 100644 src/com/webcodepro/applecommander/ui/images/deletedfiles.gif create mode 100644 src/com/webcodepro/applecommander/ui/images/deletefile.gif create mode 100644 src/com/webcodepro/applecommander/ui/images/detailfileview.gif create mode 100644 src/com/webcodepro/applecommander/ui/images/diskicon.gif create mode 100644 src/com/webcodepro/applecommander/ui/images/exportfile.gif create mode 100644 src/com/webcodepro/applecommander/ui/images/importfile.gif create mode 100644 src/com/webcodepro/applecommander/ui/images/nativefileview.gif create mode 100644 src/com/webcodepro/applecommander/ui/images/newdisk.gif create mode 100644 src/com/webcodepro/applecommander/ui/images/opendisk.gif create mode 100644 src/com/webcodepro/applecommander/ui/images/saveimage.gif create mode 100644 src/com/webcodepro/applecommander/ui/images/standardfileview.gif create mode 100644 src/com/webcodepro/applecommander/ui/swt/AppleWorksWordProcessorPane.java create mode 100644 src/com/webcodepro/applecommander/ui/swt/DiskExplorerTab.java create mode 100644 src/com/webcodepro/applecommander/ui/swt/DiskInfoTab.java create mode 100644 src/com/webcodepro/applecommander/ui/swt/DiskMapTab.java create mode 100644 src/com/webcodepro/applecommander/ui/swt/DiskWindow.java create mode 100644 src/com/webcodepro/applecommander/ui/swt/DropDownSelectionListener.java create mode 100644 src/com/webcodepro/applecommander/ui/swt/ExportFileDestinationPane.java create mode 100644 src/com/webcodepro/applecommander/ui/swt/ExportFileStartPane.java create mode 100644 src/com/webcodepro/applecommander/ui/swt/ExportGraphicsTypePane.java create mode 100644 src/com/webcodepro/applecommander/ui/swt/ExportWizard.java create mode 100644 src/com/webcodepro/applecommander/ui/swt/ImageCanvas.java create mode 100644 src/com/webcodepro/applecommander/ui/swt/ImageManager.java create mode 100644 src/com/webcodepro/applecommander/ui/swt/SwtAppleCommander.java create mode 100644 src/com/webcodepro/applecommander/ui/swt/WizardPane.java diff --git a/.classpath b/.classpath new file mode 100644 index 0000000..657091d --- /dev/null +++ b/.classpath @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/.cvsignore b/.cvsignore new file mode 100644 index 0000000..4e62da2 --- /dev/null +++ b/.cvsignore @@ -0,0 +1,2 @@ +bin +AppleCommander.preferences diff --git a/.project b/.project new file mode 100644 index 0000000..c9433a1 --- /dev/null +++ b/.project @@ -0,0 +1,18 @@ + + + AppleCommander + + + JUnit + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/HEADER b/HEADER new file mode 100644 index 0000000..04c1f40 --- /dev/null +++ b/HEADER @@ -0,0 +1,21 @@ +Attach the following code to the header of all source files. + +/* + * 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 + */ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..037b5fe --- /dev/null +++ b/LICENSE @@ -0,0 +1,270 @@ +The GNU General Public License (GPL) +Version 2, June 1991 +Copyright (C) 1989, 1991 Free Software Foundation, Inc. +59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Everyone is permitted to copy and distribute verbatim copies +of this license document, but changing it is not allowed. + +Preamble + +The licenses for most software are designed to take away your freedom to +share and change it. By contrast, the GNU General Public License is +intended to guarantee your freedom to share and change free software--to +make sure the software is free for all its users. This General Public +License applies to most of the Free Software Foundation's software and to +any other program whose authors commit to using it. (Some other Free +Software Foundation software is covered by the GNU Library General Public +License instead.) You can apply it to your programs, too. + +When we speak of free software, we are referring to freedom, not price. +Our General Public Licenses are designed to make sure that you have the +freedom to distribute copies of free software (and charge for this service +if you wish), that you receive source code or can get it if you want it, +that you can change the software or use pieces of it in new free programs; +and that you know you can do these things. + +To protect your rights, we need to make restrictions that forbid anyone to +deny you these rights or to ask you to surrender the rights. These +restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + +For example, if you distribute copies of such a program, whether gratis or +for a fee, you must give the recipients all the rights that you have. You +must make sure that they, too, receive or can get the source code. And you +must show them these terms so they know their rights. + +We protect your rights with two steps: (1) copyright the software, and (2) +offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + +Also, for each author's protection and ours, we want to make certain that +everyone understands that there is no warranty for this free software. If +the software is modified by someone else and passed on, we want its +recipients to know that what they have is not the original, so that any +problems introduced by others will not reflect on the original authors' +reputations. + +Finally, any free program is threatened constantly by software patents. We +wish to avoid the danger that redistributors of a free program will +individually obtain patent licenses, in effect making the program +proprietary. To prevent this, we have made it clear that any patent must +be licensed for everyone's free use or not licensed at all. + +The precise terms and conditions for copying, distribution and +modification follow. + +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +0. This License applies to any program or other work which contains a +notice placed by the copyright holder saying it may be distributed under +the terms of this General Public License. The "Program", below, refers to +any such program or work, and a "work based on the Program" means either +the Program or any derivative work under copyright law: that is to say, a +work containing the Program or a portion of it, either verbatim or with +modifications and/or translated into another language. (Hereinafter, +translation is included without limitation in the term "modification".) +Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of running +the Program is not restricted, and the output from the Program is covered +only if its contents constitute a work based on the Program (independent +of having been made by running the Program). Whether that is true depends +on what theProgram does. + +1. You may copy and distribute verbatim copies of the Program's source +code as you receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice and +disclaimer of warranty; keep intact all the notices that refer to this +License and to the absence of any warranty; and give any other recipients +of the Program a copy of this License along with the Program. + +You may charge a fee for the physical act of transferring a copy, and you +may at your option offer warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Program or any portion of it, +thus forming a work based on the Program, and copy and distribute such +modifications or work under the terms of Section 1 above, provided that +you also meet all of these conditions: + +a) You must cause the modified files to carry prominent notices stating +that you changed the files and the date of any change. + +b) You must cause any work that you distribute or publish, that in whole +or in part contains or is derived from the Program or any part thereof, to +be licensed as a whole at no charge to all third parties under the terms +of this License. + +c) If the modified program normally reads commands interactively when run, +you must cause it, when started running for such interactive use in the +most ordinary way, to print or display an announcement including an +appropriate copyright notice and a notice that there is no warranty (or +else, saying that you provide a warranty) and that users may redistribute +the program under these conditions, and telling the user how to view a +copy of this License. (Exception: if the Program itself is interactive but +does not normally print such an announcement, your work based on the +Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If identifiable +sections of that work are not derived from the Program, and can be +reasonably considered independent and separate works in themselves, then +this License, and its terms, do not apply to those sections when you +distribute them as separate works. But when you distribute the same +sections as part of a whole which is a work based on the Program, the +distribution of the whole must be on the terms of this License, whose +permissions for other licensees extend to the entire whole, and thus to +each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest your +rights to work written entirely by you; rather, the intent is to exercise +the right to control the distribution of derivative or collective works +based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of a +storage or distribution medium does not bring the other work under the +scope of this License. + +3. You may copy and distribute the Program (or a work based on it, under +Section 2) in object code or executable form under the terms of Sections 1 +and 2 above provided that you also do one of the following: + +a) Accompany it with the complete corresponding machine-readable source +code, which must be distributed under the terms of Sections 1 and 2 above +on a medium customarily used for software interchange; or, + +b) Accompany it with a written offer, valid for at least three years, to +give any third party, for a charge no more than your cost of physically +performing source distribution, a complete machine-readable copy of the +corresponding source code, to be distributed under the terms of Sections 1 +and 2 above on a medium customarily used for software interchange; or, + +c) Accompany it with the information you received as to the offer to +distribute corresponding source code. (This alternative is allowed only +for noncommercial distribution and only if you received the program in +object code or executable form with such an offer, in accord with +Subsection b above.) + +The source code for a work means the preferred form of the work for making +modifications to it. For an executable work, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the executable. However, as a special exception, the +source code distributed need not include anything that is normally +distributed (in either source or binary form) with the major components +(compiler, kernel, and so on) of the operating system on which the +executable runs, unless that component itself accompanies the executable. + +If distribution of executable or object code is made by offering access to +copy from a designated place, then offering equivalent access to copy the +source code from the same place counts as distribution of the source code, +even though third parties are not compelled to copy the source along with +the object code. + +4. You may not copy, modify, sublicense, or distribute the Program except +as expressly provided under this License. Any attempt otherwise to copy, +modify, sublicense or distribute the Program is void, and will +automatically terminate your rights under this License. However, parties +who have received copies, or rights, from you under this License will not +have their licenses terminated so long as such parties remain in full +compliance. + +5. You are not required to accept this License, since you have not signed +it. However, nothing else grants you permission to modify or distribute +the Program or its derivative works. These actions are prohibited by law +if you do not accept this License. Therefore, by modifying or distributing +the Program (or any work based on the Program), you indicate your +acceptance of this License to do so, and all its terms and conditions for +copying, distributing or modifying the Program or works based on it. + +6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the original +licensor to copy, distribute or modify the Program subject to these terms +and conditions. You may not impose any further restrictions on the +recipients' exercise of the rights granted herein. You are not responsible +for enforcing compliance by third parties to this License. + +7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot distribute +so as to satisfy simultaneously your obligations under this License and +any other pertinent obligations, then as a consequence you may not +distribute the Program at all. For example, if a patent license would not +permit royalty-free redistribution of the Program by all those who receive +copies directly or indirectly through you, then the only way you could +satisfy both it and this License would be to refrain entirely from +distribution of the Program. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any such +claims; this section has the sole purpose of protecting the integrity of +the free software distribution system, which is implemented by public +license practices. Many people have made generous contributions to the +wide range of software distributed through that system in reliance on +consistent application of that system; it is up to the author/donor to +decide if he or she is willing to distribute software through any other +system and a licensee cannot impose that choice. + +This section is intended to make thoroughly clear what is believed to be a +consequence of the rest of this License. + +8. If the distribution and/or use of the Program is restricted in certain +countries either by patents or by copyrighted interfaces, the original +copyright holder who places the Program under this License may add an +explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + +9. The Free Software Foundation may publish revised and/or new versions of +the General Public License from time to time. Such new versions will be +similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free +Software Foundation. + +10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals of +preserving the free status of all derivatives of our free software and of +promoting the sharing and reuse of software generally. + +NO WARRANTY + +11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + +12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES +ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT +LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES +SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE +WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN +ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +END OF TERMS AND CONDITIONS + diff --git a/build/manifest.mf b/build/manifest.mf new file mode 100644 index 0000000..7f627e3 --- /dev/null +++ b/build/manifest.mf @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Class-Path: swt.jar +Main-Class: com.webcodepro.applecommander.ui.AppleCommander diff --git a/imageSource/AppleCommanderLogo.mix b/imageSource/AppleCommanderLogo.mix new file mode 100644 index 0000000000000000000000000000000000000000..8cff0033281d1e85317a1e01e5660332a665ec16 GIT binary patch literal 141312 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 literal 0 HcmV?d00001 diff --git a/src/com/webcodepro/applecommander/storage/AppleUtil.java b/src/com/webcodepro/applecommander/storage/AppleUtil.java new file mode 100644 index 0000000..a47f688 --- /dev/null +++ b/src/com/webcodepro/applecommander/storage/AppleUtil.java @@ -0,0 +1,170 @@ +/* + * 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; + +import java.util.Date; +import java.util.GregorianCalendar; + +/** + * This class contains helper methods for dealing with Apple2 data. + *

+ * Date created: Oct 5, 2002 4:16:16 PM + * @author: Rob Greene + */ +public class AppleUtil { + + /** + * Compute the value of a word. + * Pulls value from buffer given the offset. + * A word is two bytes, in standard Apple LO/HI format. + */ + public static int getWordValue(byte[] buffer, int offset) { + return getWordValue(buffer[offset], buffer[offset+1]); + } + /** + * Compute the value of a word. + */ + public static int getWordValue(byte low, byte high) { + return getUnsignedByte(low) + getUnsignedByte(high)*256; + } + + /** + * Compute the value of a 3 byte value. This may be ProDOS specific. + * Pulls value from buffer given the offset. + * Stored in standard Apple LO/HI format. + */ + public static int get3ByteValue(byte[] buffer, int offset) { + return getUnsignedByte(buffer[offset]) + + getUnsignedByte(buffer[offset+1])*256 + + getUnsignedByte(buffer[offset+2])*65536; + } + + /** + * Extract out an unsigned byte as an int. + * All Java bytes are signed; need to convert to an int + * and remove the sign. + */ + public static int getUnsignedByte(byte value) { + return (int) value & 0xff; + } + + /** + * Count the number of bits set in a byte. + */ + public static int getBitCount(byte byt) { + int count = 0; + for (int ix=0; ix<8; ix++) { + if (isBitSet(byt, ix)) count++; + } + return count; + } + + /** + * Determine if a specific bit is set. + */ + public static boolean isBitSet(byte byt, int bit) { + byte[] masks = { (byte)0x01, (byte)0x02,(byte)0x04, (byte)0x08, + (byte)0x10, (byte)0x20, (byte)0x40, (byte)0x80 }; + return (byt & masks[bit]) != 0; + } + + /** + * Extract a string from the buffer. + */ + public static String getString(byte[] buffer, int offset, int length) { + byte[] value = new byte[length]; + for (int i=0; i> 4; + int year = (pascalDate & 0xff00) >> 8; + if (year < 50) year+= 2000; + if (year < 100) year+= 1900; + GregorianCalendar gc = new GregorianCalendar(year, month, day); + return gc.getTime(); + } + + /** + * Extract a ProDOS string from the buffer. + */ + public static String getProdosString(byte[] buffer, int offset) { + int length = getUnsignedByte(buffer[offset]) & 0x0f; + return getString(buffer, offset+1, length); + } + + /** + * Format a byte value as hexidecimal. + */ + public static String getFormattedByte(int byt) { + String[] values = { "0", "1", "2", "3", "4", "5", "6", "7", + "8", "9", "A", "B", "C", "D", "E", "F" }; + int byt1 = byt & 0x0f; + int byt2 = (byt & 0xf0) >> 4; + return values[byt2] + values[byt1]; + } + + /** + * Format a word value as hexidecimal. + */ + public static String getFormattedWord(int word) { + return getFormattedByte((word & 0xff00) >> 8) + + getFormattedByte(word & 0x00ff); + } + + /** + * Extract a ProDOS date from the buffer. + */ + public static Date getProdosDate(byte[] buffer, int offset) { + int ymd = getWordValue(buffer, offset); + if (ymd == 0) return null; + int hm = getWordValue(buffer, offset+2); + + int day = ymd & 0x001f; // bits 0-4 + int month = (ymd & 0x01e0) >> 5; // bits 5-8 + int year = (ymd & 0xfe00) >> 9; // bits 9-15 + int minute = hm & 0x003f; // bits 0-5 + int hour = (hm & 0x1f00) >> 8; // bits 8-12 + + if (year < 50) year+= 2000; + if (year < 100) year+= 1900; + + GregorianCalendar gc = new GregorianCalendar(year, month, day, hour, minute); + return gc.getTime(); + } +} diff --git a/src/com/webcodepro/applecommander/storage/AppleWorksWordProcessorFileFilter.java b/src/com/webcodepro/applecommander/storage/AppleWorksWordProcessorFileFilter.java new file mode 100644 index 0000000..5727e15 --- /dev/null +++ b/src/com/webcodepro/applecommander/storage/AppleWorksWordProcessorFileFilter.java @@ -0,0 +1,212 @@ +/* + * 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; + +import java.io.ByteArrayOutputStream; +import java.io.PrintWriter; +import java.text.SimpleDateFormat; +import java.util.Date; + +/** + * Extract the contents of an AWP (AppleWorks word processor) document and + * convert to a text format. + * See format documentation at: + * http://www.gno.org/pub/apple2/doc/apple/filetypes/ftn.1a.xxxx + *

+ * Date created: Nov 15, 2002 3:55:21 PM + * @author: Rob Greene + */ +public class AppleWorksWordProcessorFileFilter implements FileFilter { + public static final int RENDER_AS_TEXT = 0; + public static final int RENDER_AS_HTML = 1; + private int rendering = RENDER_AS_TEXT; + /** + * Constructor for AppleWorksWordProcessorFileFilter. + */ + public AppleWorksWordProcessorFileFilter() { + 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(); + if (fileData[4] != 0x4f) return null; // not an AWP file! + ByteArrayOutputStream byteArray = new ByteArrayOutputStream(fileData.length); + PrintWriter printWriter = new PrintWriter(byteArray, true); + if (isHtmlRendering()) { + printWriter.println(""); + } + boolean version3 = (fileData[183] != 0); + int offset = 300 + (version3 ? 2 : 0); // version 3.0's first line record is invalid + while (offset < fileData.length) { + int byte0 = AppleUtil.getUnsignedByte(fileData[offset++]); + int byte1 = AppleUtil.getUnsignedByte(fileData[offset++]); + + if (byte0 == 0xff && byte1 == 0xff) { // end of file + break; + } else if (byte1 == 0xd0) { // Carriage return line records + handleReturn(printWriter); + } else if (byte1 > 0xd0) { // Command line records + if (isHtmlRendering()) { + offset = handleCommandRecordAsHtml(byte0, byte1, printWriter, offset); + } + } else { // Text records (assumed) + offset = handleTextRecord(fileData, printWriter, offset); + } + } + if (isHtmlRendering()) { + printWriter.println(""); + } + return byteArray.toByteArray(); + } + /** + * Deal with an individual text record. + */ + protected int handleTextRecord(byte[] fileData, PrintWriter printWriter, int offset) { + int byte2 = AppleUtil.getUnsignedByte(fileData[offset++]); + int byte3 = AppleUtil.getUnsignedByte(fileData[offset++]); + boolean addReturn = (byte3 >= 0x80); + int length = (byte3 & 0x7f); + while (length > 0) { + byte ch = fileData[offset++]; + length--; + if (ch < 0x20) { // special formatting character + if (isHtmlRendering()) handleSpecialCodesAsHtml(printWriter, ch); + } else { + if (isHtmlRendering() && ch == ' ') { + int extraSpaces = 0; + while (fileData[offset+extraSpaces] == ' ') { + extraSpaces++; + } + if (extraSpaces > 0) { + printWriter.print(" "); + while (fileData[offset] == ' ') { + offset++; + length--; + printWriter.print(" "); + } + } else { + printWriter.print((char)ch); + } + } else { + printWriter.print((char)ch); + } + } + } + if (addReturn) handleReturn(printWriter); + return offset; + } + /** + * Deal with carriage-return. + */ + protected void handleReturn(PrintWriter printWriter) { + if (isHtmlRendering()) printWriter.println("
"); + else printWriter.println(); + } + /** + * Process special coding of a text record. + */ + protected void handleSpecialCodesAsHtml(PrintWriter printWriter, byte ch) { + switch (ch) { + case 0x01: printWriter.print(""); + break; + case 0x02: printWriter.print(""); + break; + case 0x03: printWriter.print(""); + break; + case 0x04: printWriter.print(""); + break; + case 0x05: printWriter.print(""); + break; + case 0x06: printWriter.print(""); + break; + case 0x07: printWriter.print(""); + break; + case 0x08: printWriter.print(""); + break; + case 0x09: printWriter.print("[Page#]"); + break; + case 0x0b: printWriter.print(" "); + break; + case 0x0e: SimpleDateFormat dateFormat = new SimpleDateFormat("MM/dd/yy"); + printWriter.print(dateFormat.format(new Date())); + break; + case 0x0f: SimpleDateFormat timeFormat = new SimpleDateFormat("HH:mm:ss"); + printWriter.print(timeFormat.format(new Date())); + break; + } + } + /** + * Deal with an individual command line record. + */ + protected int handleCommandRecordAsHtml(int byte0, int byte1, + PrintWriter printWriter, int offset) { + + switch (byte1) { + case 0xd7: printWriter.println(""); + break; + case 0xdf: printWriter.println(""); + break; + case 0xe0: printWriter.println(""); + break; + case 0xe1: printWriter.println(""); + break; + case 0xee: for (int i=0; i"); + } + break; + } + return offset; + } + /** + * 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"; + if (isHtmlRendering()) extension = ".html"; + + if (!fileName.toLowerCase().endsWith(extension)) { + fileName = fileName + extension; + } + return fileName; + } + /** + * Set the rendering method. + */ + public 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; + } +} \ No newline at end of file diff --git a/src/com/webcodepro/applecommander/storage/ApplesoftFileFilter.java b/src/com/webcodepro/applecommander/storage/ApplesoftFileFilter.java new file mode 100644 index 0000000..2c64a43 --- /dev/null +++ b/src/com/webcodepro/applecommander/storage/ApplesoftFileFilter.java @@ -0,0 +1,124 @@ +/* + * 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; + +import java.io.ByteArrayOutputStream; +import java.io.PrintWriter; + +/** + * Filter the given file as an Applesoft file. + *

+ * Applesoft memory format:
+ * [Line]
+ * ... + * [Line]
+ *
+ * where is:
+ * [Next addr - $0000 is end of program] (word)
+ * [Line no] (word)
+ * [Tokens and/or characters]
+ * [End-of-line marker: $00 bytes] + *

+ * Date created: Nov 2, 2002 10:04:10 PM + * @author: Rob Greene + */ +public class ApplesoftFileFilter implements FileFilter { + private static String tokens[] = { // starts at $80 + " END ", " FOR ", " NEXT ", " DATA ", " INPUT ", " DEL ", + " DIM ", " READ ", " GR ", " TEXT ", " PR# ", " IN# ", + " CALL ", " PLOT ", " HLIN ", " VLIN ", " HGR2 ", " HGR ", + " HCOLOR= ", " HPLOT ", " DRAW ", " XDRAW ", " HTAB ", " HOME ", + " ROT= ", " SCALE= ", " SHLOAD ", " TRACE ", " NOTRACE ", " NORMAL ", + " INVERSE ", " FLASH ", " COLOR= ", " POP ", " VTAB ", " HIMEM: ", + " LOMEM: ", " ONERR ", " RESUME ", " RECALL ", " STORE ", " SPEED= ", + " LET ", " GOTO ", " RUN ", " IF ", " RESTORE ", " & ", + " GOSUB ", " RETURN ", " REM ", " STOP ", " ON ", " WAIT ", + " LOAD ", " SAVE ", " DEF ", " POKE ", " PRINT ", " CONT ", + " LIST ", " CLEAR ", " GET ", " NEW ", " TAB( ", " TO ", + " FN ", " SPC( ", " THEN ", " AT ", " NOT ", " STEP ", + " +", " -", " *", "/", " ^", " AND ", + " OR ", " >", " =", " <", " SGN", " INT", + " ABS", " USR", " FRE", " SCRN( ", " PDL", " POS", + " SQR", " RND", " LOG", " EXP", " COS", " SIN", + " TAN", " ATN", " PEEK", " LEN", " STR$", " VAL", + " ASC", " CHR$", " LEFT$", " RIGHT$", " MID$ " }; + + /** + * Constructor for ApplesoftFileFilter. + */ + public ApplesoftFileFilter() { + super(); + } + + /** + * Process the given FileEntry and return a text image of the Applesoft file. + * @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 * 2); + PrintWriter printWriter = new PrintWriter(byteArray, true); + while (offset < fileData.length) { + int nextAddress = AppleUtil.getWordValue(fileData, offset); + if (nextAddress == 0) break; // next address of 0 indicates end of program + offset+= 2; + int lineNumber = AppleUtil.getWordValue(fileData, offset); + offset+= 2; + printWriter.print(lineNumber); + printWriter.print(' '); + while (fileData[offset] != 0) { + byte byt = fileData[offset++]; + if ((byt & 0x80) != 0) { + int token = AppleUtil.getUnsignedByte(byt) - 0x80; + if (token >= tokens.length) { + printWriter.print(""); + } else { + String tokenString = tokens[token]; + printWriter.print(tokenString); + } + } else { + char ch = (char)byt; + if (ch < 0x20) { + printWriter.print(""); + } else { + printWriter.print(ch); + } + } + } + printWriter.println(); + offset++; // skip to next line + } + return byteArray.toByteArray(); + } + + /** + * Give suggested file name. + */ + public String getSuggestedFileName(FileEntry fileEntry) { + String fileName = fileEntry.getFilename().trim(); + if (!fileName.toLowerCase().endsWith(".bas")) { + fileName = fileName + ".bas"; + } + return fileName; + } +} diff --git a/src/com/webcodepro/applecommander/storage/BinaryFileFilter.java b/src/com/webcodepro/applecommander/storage/BinaryFileFilter.java new file mode 100644 index 0000000..c043df7 --- /dev/null +++ b/src/com/webcodepro/applecommander/storage/BinaryFileFilter.java @@ -0,0 +1,54 @@ +/* + * 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; + +/** + * Filter the given file data to be the appropriate length. + *

+ * Date created: Nov 2, 2002 9:07:52 PM + * @author: Rob Greene + */ +public class BinaryFileFilter implements FileFilter { + /** + * Constructor for BinaryFileFilter. + */ + public BinaryFileFilter() { + super(); + } + + /** + * Process the given FileEntry and return a byte array with filtered data. + * @see com.webcodepro.applecommander.storage.FileFilter#filter(byte[]) + */ + public byte[] filter(FileEntry fileEntry) { + return fileEntry.getFileData(); // should be nothing to do + } + + /** + * Give suggested file name. + */ + public String getSuggestedFileName(FileEntry fileEntry) { + String fileName = fileEntry.getFilename().trim(); + if (!fileName.toLowerCase().endsWith(".dump")) { + fileName = fileName + ".dump"; + } + return fileName; + } +} diff --git a/src/com/webcodepro/applecommander/storage/Disk.java b/src/com/webcodepro/applecommander/storage/Disk.java new file mode 100644 index 0000000..fbe134c --- /dev/null +++ b/src/com/webcodepro/applecommander/storage/Disk.java @@ -0,0 +1,316 @@ +/* + * 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; + +import java.io.ByteArrayOutputStream; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.zip.GZIPInputStream; + +/** + * Abstract representation of an Apple2 disk (floppy, 800k, hard disk). + *

+ * Date created: Oct 3, 2002 10:59:47 PM + * @author: Rob Greene + */ +public class Disk { + /** + * Specifies a filter to be used in determining filetypes which are supported. + * This works from a file extension, so it may or may not apply to the Macintosh. + */ + public class FilenameFilter { + private String names; + private String extensions; + public FilenameFilter(String names, String extensions) { + this.names = names; + this.extensions = extensions; + } + public String getExtensions() { + return extensions; + } + public String getNames() { + return names; + } + } + + public static final int BLOCK_SIZE = 512; + public static final int SECTOR_SIZE = 256; + public static final int APPLE_140KB_DISK = 143360; + public static final int APPLE_800KB_DISK = 819200; + public static final int APPLE_800KB_2IMG_DISK = APPLE_800KB_DISK + 0x40; + + private static FilenameFilter[] filenameFilters; + private byte[] diskImage; + private String filename; + + /** + * Get the supported file filters supported by the Disk interface. + * This is due to the fact that FilenameFilter is an innerclass of Disk - + * without an instance of the class, the filters cannot be created. + */ + public static FilenameFilter[] getFilenameFilters() { + if (filenameFilters == null) { + new Disk(); + } + return filenameFilters; + } + + /** + * Constructor for a Disk - used only to generate FilenameFilter objects. + */ + private Disk() { + filenameFilters = new FilenameFilter[] { + new FilenameFilter("All Emulator Images", + "*.do; *.dsk; *.po; *.2mg; *.2img; *.hdv; *.do.gz; *.dsk.gz; *.po.gz; *.2mg.gz; *.2img.gz"), + new FilenameFilter("140K DOS 3.3 Ordered Images (*.do, *.dsk)", + "*.do; *.dsk; *.do.gz; *.dsk.gz"), + new FilenameFilter("140K ProDOS Ordered Images (*.po)", + "*.po; *.po.gz"), + new FilenameFilter("800K ProDOS Ordered Images (*.2mg, *.2img)", + "*.2mg; *.2img; *.2mg.gz, *.2img.gz"), + new FilenameFilter("ApplePC Hard Disk Images (*.hdv)", + "*.hdv"), + new FilenameFilter("All Compressed Images", + "*.do.gz; *.dsk.gz; *.po.gz; *.2mg.gz; *.2img.gz"), + new FilenameFilter("All Files", + "*.*") + }; + } + + /** + * Construct a Disk with the given byte array. + */ + protected Disk(String filename, byte[] diskImage) { + this.diskImage = diskImage; + this.filename = filename; + } + + /** + * Construct a Disk and load the specified file. + * Read in the entire contents of the file. + */ + public Disk(String filename) throws IOException { + this.filename = filename; + InputStream input = new FileInputStream(filename); + if (isCompressed()) { + input = new GZIPInputStream(input); + } + int diskSize = APPLE_140KB_DISK; + if (is2ImgOrder()) { + diskSize = APPLE_800KB_2IMG_DISK; + } + ByteArrayOutputStream diskImageByteArray = + new ByteArrayOutputStream(diskSize); + byte[] data = new byte[1024]; + int bytes; + while ((bytes = input.read(data)) > 0) { + diskImageByteArray.write(data, 0, bytes); + } + input.close(); + this.diskImage = diskImageByteArray.toByteArray(); + } + + /** + * Determine type of disk, and return the appropriate + * FormattedDisk object. Returns null if none are + * recognized. + */ + public FormattedDisk getFormattedDisk() { + if (isProdosFormat()) { + return new ProdosFormatDisk(filename, diskImage); + } else if (isDosFormat()) { + return new DosFormatDisk(filename, diskImage); + } else if (isPascalFormat()) { + return new PascalFormatDisk(filename, diskImage); + } else if (isRdosFormat()) { + return new RdosFormatDisk(filename, diskImage); + } + return null; + } + + /** + * Returns the diskImage. + * @return byte[] + */ + public byte[] getDiskImage() { + return diskImage; + } + + /** + * Extract a portion of the disk image. + */ + public byte[] readBytes(int start, int length) { + byte[] buffer = new byte[length]; + System.arraycopy(diskImage, start + (is2ImgOrder() ? 0x40 : 0), buffer, 0, length); + return buffer; + } + + /** + * Returns the filename. + * @return String + */ + public String getFilename() { + return filename; + } + + /** + * Indicate if this disk is GZIP compressed. + */ + public boolean isCompressed() { + return filename.toLowerCase().endsWith(".gz"); + } + + /** + * Indicate if this disk is ProDOS ordered (beginning with block 0). + */ + public boolean isProdosOrder() { + return filename.toLowerCase().endsWith(".po") + || filename.toLowerCase().endsWith(".po.gz") + || is2ImgOrder() + || filename.toLowerCase().endsWith(".hdv"); + } + + /** + * Indicate if this disk is DOS ordered (T0,S0 - T35,S15). + */ + public boolean isDosOrder() { + return filename.toLowerCase().endsWith(".do") + || filename.toLowerCase().endsWith(".do.gz") + || filename.toLowerCase().endsWith(".dsk") + || filename.toLowerCase().endsWith(".dsk.gz"); + } + + /** + * Indicate if this disk is a 2IMG disk. + * This is ProDOS ordered, but with a header on the disk. + */ + public boolean is2ImgOrder() { + return filename.toLowerCase().endsWith(".2img") + || filename.toLowerCase().endsWith(".2img.gz") + || filename.toLowerCase().endsWith(".2mg") + || filename.toLowerCase().endsWith(".2mg.gz"); + } + + /** + * Identify the size of this disk. + */ + public int getPhysicalSize() { + return diskImage.length; + } + + /** + * Get the block from the disk image. + */ + public byte[] readBlock(int block) { + if (block * BLOCK_SIZE > getPhysicalSize()) { + return null; + } else { + if (isProdosOrder()) { + return readBytes(block*BLOCK_SIZE, BLOCK_SIZE); + } else if (isDosOrder()) { + int[] sectorMapping1 = { 0, 13, 11, 9, 7, 5, 3, 1 }; + int[] sectorMapping2 = { 14, 12, 10, 8, 6, 4, 2, 15 }; + int track = block / 8; + int sectorOffset = block % 8; + int sector1 = sectorMapping1[sectorOffset]; + int sector2 = sectorMapping2[sectorOffset]; + int physicalLocation1 = (track * 16 + sector1) * SECTOR_SIZE; + int physicalLocation2 = (track * 16 + sector2) * SECTOR_SIZE; + byte[] data = new byte[BLOCK_SIZE]; + System.arraycopy(readBytes(physicalLocation1, SECTOR_SIZE), + 0, data, 0, SECTOR_SIZE); + System.arraycopy(readBytes(physicalLocation2, SECTOR_SIZE), + 0, data, SECTOR_SIZE, SECTOR_SIZE); + return data; + } else { + return null; + } + } + } + + /** + * Retrieve the specified sector. + */ + public byte[] readSector(int track, int sector) { + if ((track * 16 + sector) * SECTOR_SIZE > getPhysicalSize()) { + return null; + } else if (isProdosOrder()) { + // what block a sector belongs to: + int[] blockInterleave = { 0, 7, 6, 6, 5, 5, 4, 4, 3, 3, 2, 2, 1, 1, 0, 7 }; + // where in that block a sector resides: + int[] blockOffsets = { 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1 }; + return readBytes( + ((track * 8) + blockInterleave[sector]) * BLOCK_SIZE + + blockOffsets[sector] * SECTOR_SIZE, SECTOR_SIZE); + } else if (isDosOrder()) { + return readBytes((track * 16 + sector) * SECTOR_SIZE, SECTOR_SIZE); + } + return null; + } + + /** + * Test the disk format to see if this is a ProDOS formatted + * disk. + */ + public boolean isProdosFormat() { + byte[] prodosVolumeDirectory = readBlock(2); + return prodosVolumeDirectory[0] == 0 && + prodosVolumeDirectory[1] == 0 && + (prodosVolumeDirectory[4]&0xf0) == 0xf0; + } + + /** + * Test the disk format to see if this is a DOS 3.3 formatted + * disk. + */ + public boolean isDosFormat() { + byte[] vtoc = readSector(17, 0); + return vtoc[0x01] == 17 // expect catalog to start on track 17 + && vtoc[0x02] == 15 // expect catalog to start on sector 15 + && vtoc[0x03] == 3 // expect DOS release number of 3 + && vtoc[0x27] == 122 // expect 122 tract/sector pairs per sector + && vtoc[0x34] == 35 // expect 35 tracks per disk (140KB disk only!) + && vtoc[0x35] == 16 // expect 16 sectors per disk (140KB disk only!) + && vtoc[0x36] == 0 // bytes per sector (low byte) + && vtoc[0x37] == 1; // bytes per sector (high byte) + } + + /** + * Test the disk format to see if this is a Pascal formatted + * disk. + */ + public boolean isPascalFormat() { + byte[] directory = readBlock(2); + return directory[0] == 0 && directory[1] == 0 + && directory[2] == 6 && directory[3] == 0 + && directory[4] == 0 && directory[5] == 0; + } + + /** + * Test the disk format to see if this is a RDOS formatted + * disk. + */ + public boolean isRdosFormat() { + byte[] block = readSector(0, 0x0d); + String id = AppleUtil.getString(block, 0xe0, 4); + return "RDOS".equals(id); + } +} diff --git a/src/com/webcodepro/applecommander/storage/DiskHelper.java b/src/com/webcodepro/applecommander/storage/DiskHelper.java new file mode 100644 index 0000000..5fccdd8 --- /dev/null +++ b/src/com/webcodepro/applecommander/storage/DiskHelper.java @@ -0,0 +1,89 @@ +/* + * 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; + +import java.io.ByteArrayOutputStream; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.zip.GZIPInputStream; + +/** + * Helper class to load Apple2 disk images. + * This class identifies the format, and the type of disk, loading the + * appropriate disk management class. + *

+ * Date created: Oct 3, 2002 10:54:34 PM + * @author: Rob Greene + * @deprecated + */ +public class DiskHelper { + public static final int APPLE_140KB_DISK = 143360; + public static final int APPLE_800KB_DISK = 819200; + public static final int APPLE_800KB_2IMG_DISK = APPLE_800KB_DISK + 0x40; + + /** + * Load an Apple2 disk from a given filename. + * Determine type of disk, load approparitely, and + * construct the appropriate Disk object. + */ + public static FormattedDisk load(String filename) throws IOException { + if (filename == null) return null; + byte[] diskImage = loadDisk(filename); + + Disk test = new Disk(filename, diskImage); + if (test.isProdosFormat()) { + return new ProdosFormatDisk(filename, diskImage); + } else if (test.isDosFormat()) { + return new DosFormatDisk(filename, diskImage); + } else if (test.isPascalFormat()) { + return new PascalFormatDisk(filename, diskImage); + } else if (test.isRdosFormat()) { + return new RdosFormatDisk(filename, diskImage); + } + + // FIXME: Should return unknown disk Disk + return null; + } + + /** + * Read in a disk in the same order as the image. + * Disk itself will handle location translation. + */ + private static byte[] loadDisk(String filename) throws IOException { + InputStream input = new FileInputStream(filename); + if (filename.toLowerCase().endsWith(".gz")) { + input = new GZIPInputStream(input); + } + int diskSize = APPLE_140KB_DISK; + if (filename.toLowerCase().endsWith(".2img") || filename.toLowerCase().endsWith(".2mg")) { + diskSize = APPLE_800KB_2IMG_DISK; + } + ByteArrayOutputStream diskImage = + new ByteArrayOutputStream(diskSize); + byte[] data = new byte[1024]; + int bytes; + while ((bytes = input.read(data)) > 0) { + diskImage.write(data, 0, bytes); + } + input.close(); + return diskImage.toByteArray(); + } +} diff --git a/src/com/webcodepro/applecommander/storage/DosFileEntry.java b/src/com/webcodepro/applecommander/storage/DosFileEntry.java new file mode 100644 index 0000000..a5ab69a --- /dev/null +++ b/src/com/webcodepro/applecommander/storage/DosFileEntry.java @@ -0,0 +1,271 @@ +/* + * 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; + +import java.text.NumberFormat; +import java.util.ArrayList; +import java.util.List; + +/** + * Represents a DOS file entry on disk. + *

+ * Date created: Oct 4, 2002 5:15:25 PM + * @author: Rob Greene + */ +public class DosFileEntry implements FileEntry { + private byte[] fileEntry; + private DosFormatDisk disk; + + /** + * Constructor for DosFileEntry. + */ + public DosFileEntry(byte[] fileEntry, DosFormatDisk disk) { + super(); + this.fileEntry = fileEntry; + this.disk = disk; + } + + /** + * Return the name of this file. + * @see com.webcodepro.applecommander.storage.FileEntry#getFilename() + */ + public String getFilename() { + byte[] filename = new byte[30]; + System.arraycopy(fileEntry, 3, filename, 0, filename.length); + for (int i=0; i= 8185 && size <= 8192) { + filter.setMode(GraphicsFileFilter.MODE_HGR_COLOR); + return filter; + } else if (size >= 16377 && size <= 16384) { + filter.setMode(GraphicsFileFilter.MODE_DHR_COLOR); + return filter; + } + // fall through to BinaryFileFilter... + } + return new BinaryFileFilter(); + } + + /** + * Determine if this is a text file. + */ + public boolean isTextFile() { + return "T".equals(getFiletype()); + } + + /** + * Determine if this is an Applesoft BASIC file. + */ + public boolean isApplesoftBasicFile() { + return "A".equals(getFiletype()); + } + + /** + * Determine if this is an Integer BASIC file. + */ + public boolean isIntegerBasicFile() { + return "I".equals(getFiletype()); + } + + /** + * Determine if this is a binary file. + */ + public boolean isBinaryFile() { + return "B".equals(getFiletype()); + } +} diff --git a/src/com/webcodepro/applecommander/storage/DosFormatDisk.java b/src/com/webcodepro/applecommander/storage/DosFormatDisk.java new file mode 100644 index 0000000..0ae6343 --- /dev/null +++ b/src/com/webcodepro/applecommander/storage/DosFormatDisk.java @@ -0,0 +1,398 @@ +/* + * 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; + +import java.util.ArrayList; +import java.util.BitSet; +import java.util.List; + +/** + * Manages a disk that is in Apple DOS 3.3 format. + *

+ * Date created: Oct 4, 2002 12:29:23 AM + * @author: Rob Greene + */ +public class DosFormatDisk extends FormattedDisk { + /** + * Indicates the length in bytes of the DOS file entry field. + */ + public static final int FILE_DESCRIPTIVE_ENTRY_LENGTH = 35; + /** + * 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; + + /** + * Use this inner interface for managing the disk usage data. + * This offloads format-specific implementation to the implementing class. + */ + private class DosDiskUsage 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. + */ + public boolean isFree() { + if (location == null || location.length != 2) { + throw new IllegalArgumentException("Invalid dimension for isFree! Did you call next first?"); + } + byte[] vtoc = getVtoc(); + byte byt = vtoc[0x38 + (location[TRACK_LOCATION_INDEX] * 4) + + (location[SECTOR_LOCATION_INDEX] / 8)]; + boolean free = AppleUtil.isBitSet(byt, 7 - (location[SECTOR_LOCATION_INDEX] % 8)); + return free; + } + public boolean isUsed() { + return !isFree(); + } + } + + /** + * Constructor for DosFormatDisk. + * @param filename + * @param diskImage + * @param order + */ + public DosFormatDisk(String filename, byte[] diskImage) { + super(filename, diskImage); + } + + /** + * Identify the operating system format of this disk as DOS 3.3. + * @see com.webcodepro.applecommander.storage.Disk#getFormat() + */ + public String getFormat() { + return "DOS 3.3"; + } + + /** + * Retrieve a list of files. + * @see com.webcodepro.applecommander.storage.Disk#getFiles() + */ + public List getFiles() { + List list = new ArrayList(); + byte[] vtoc = getVtoc(); + int track = AppleUtil.getUnsignedByte(vtoc[1]); + int sector = AppleUtil.getUnsignedByte(vtoc[2]); + while (track != 0) { // iterate through all catalog sectors + byte[] catalogSector = readSector(track, sector); + int offset = 0x0b; + while (offset < 0xff) { // iterate through all entries + byte[] entry = new byte[FILE_DESCRIPTIVE_ENTRY_LENGTH]; + System.arraycopy(catalogSector, offset, entry, 0, entry.length); + if (entry[0] != 0) { + list.add(new DosFileEntry(entry, this)); + } + offset+= entry.length; + } + track = catalogSector[1]; + sector = catalogSector[2]; + } + return list; + } + + /** + * Identify if this disk format as not capable of having directories. + * @see com.webcodepro.applecommander.storage.Disk#hasDirectories() + */ + public boolean canHaveDirectories() { + 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.Disk#getFreeSpace() + */ + public int getFreeSpace() { + return getFreeSectors() * SECTOR_SIZE; + } + + /** + * Comput the number of free sectors available on the disk. + */ + public int getFreeSectors() { + byte[] vtoc = getVtoc(); + int freeSectors = 0; + for (int offset=0x38; offset<0xff; offset++) { + byte bitmap = vtoc[offset]; + freeSectors+= AppleUtil.getBitCount(bitmap); + } + return freeSectors; + } + + /** + * Return the amount of used space in bytes. + * @see com.webcodepro.applecommander.storage.Disk#getUsedSpace() + */ + public int getUsedSpace() { + return getUsedSectors() * SECTOR_SIZE; + } + + /** + * 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 DOS disk name. Basically, the DISK VOLUME #xxx + * that a CATALOG command would show. Note that Java bytes are + * signed, so a little mojo is in order. + * @see com.webcodepro.applecommander.storage.Disk#getDiskName() + */ + public String getDiskName() { + int volumeNumber = AppleUtil.getUnsignedByte(getVtoc()[0x06]); + return "DISK VOLUME #" + volumeNumber; + } + + /** + * Return the VTOC (Volume Table Of Contents). + */ + protected byte[] getVtoc() { + return readSector(0x11, 0); + } + + /** + * Get the disk usage bitmap. The size could vary and is stored in the + * VTOC. + * @see com.webcodepro.applecommander.storage.FormattedDisk#getBitmap() + * @deprecated DOS 3.3.po comes up with 448 entries in the bitmap?! + */ + public BitSet getBitmap() { + byte[] vtoc = getVtoc(); + int tracks = getTracks(); + int sectors = getSectors(); + BitSet bitmap = new BitSet(tracks * sectors); + // individually test each track & sector - should handle 140K or 400K disks! + int count = 0; + for (int t=0; t + * Date created: Oct 4, 2002 4:46:42 PM + * @author: Rob Greene + */ +public interface FileEntry { + /** + * Return the name of this file. + */ + public String getFilename(); + + /** + * Return the filetype of this file. + * This will be OS specific. + */ + public String getFiletype(); + + /** + * Identify if this file is locked. + */ + public boolean isLocked(); + + /** + * Compute the size of this file (in bytes). + */ + public int getSize(); + + /** + * Identify if this is a directory file. + */ + public boolean isDirectory(); + + /** + * Retrieve the list of files in this directory. + * Note that if this is not a directory, the return + * value should be null. If this a directory, the + * return value should always be a list - a directory + * with 0 entries returns an empty list. + */ + public List getFiles(); + + /** + * Identify if this file has been deleted. + */ + public boolean isDeleted(); + + /** + * 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); + + /** + * Get file data. This handles any operating-system specific issues. + * Specifically, DOS 3.3 places address and length into binary files + * and length into Applesoft files. + */ + public byte[] getFileData(); + + /** + * 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. + */ + public FileFilter getSuggestedFilter(); +} diff --git a/src/com/webcodepro/applecommander/storage/FileEntryComparator.java b/src/com/webcodepro/applecommander/storage/FileEntryComparator.java new file mode 100644 index 0000000..639e0c0 --- /dev/null +++ b/src/com/webcodepro/applecommander/storage/FileEntryComparator.java @@ -0,0 +1,99 @@ +/* + * 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; + +import java.util.Comparator; + +/** + * Sort FileEntry objects by the columnIndex. Mostly useful to the interface. + * The columns are tested to see if they are numerical - if so, an Integer compare + * is done (instead of a String). + *

+ * Date created: Oct 27, 2002 8:24:39 PM + * @author: Rob Greene + */ +public class FileEntryComparator implements Comparator { + private int columnIndex; + private int displayMode; + + /** + * Construct a FileEntryComparator for the given columnIndex. + */ + public FileEntryComparator(int columnIndex, int displayMode) { + this.columnIndex = columnIndex; + this.displayMode = displayMode; + } + + /** + * Compare two FileEntry objects. + * @see java.util.Comparator#compare(Object, Object) + */ + public int compare(Object o1, Object o2) { + if (!(o1 instanceof FileEntry) || !(o2 instanceof FileEntry)) { + return 0; + } + + if (o1 == null || o2 == null) { + return ((o1 == null) ? -1 : 0) + ((o2 == null) ? 1 : 0); + } + + FileEntry entry1 = (FileEntry) o1; + FileEntry entry2 = (FileEntry) o2; + + String column1 = (String) entry1.getFileColumnData(displayMode).get(columnIndex); + String column2 = (String) entry2.getFileColumnData(displayMode).get(columnIndex); + + if (isAllDigits(column1) && isAllDigits(column2)) { + int int1 = toInt(column1); + int int2 = toInt(column2); + return int1 - int2; + } else { + return column1.compareTo(column2); + } + } + + /** + * Test for digits in the screen. + */ + protected boolean isAllDigits(String string) { + if (string == null || string.length() == 0) return false; + for (int i=0; i + * Date created: Nov 2, 2002 9:02:47 PM + * @author: Rob Greene + */ +public interface FileFilter { + /** + * Process the given FileEntry and return a byte array with filtered data. + */ + public byte[] filter(FileEntry fileEntry); + /** + * Give suggested file name. + */ + public String getSuggestedFileName(FileEntry fileEntry); +} diff --git a/src/com/webcodepro/applecommander/storage/FormattedDisk.java b/src/com/webcodepro/applecommander/storage/FormattedDisk.java new file mode 100644 index 0000000..ea370ec --- /dev/null +++ b/src/com/webcodepro/applecommander/storage/FormattedDisk.java @@ -0,0 +1,299 @@ +/* + * 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; + +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +/** + * Abstract representation of a formatted Apple2 disk (floppy, 800k, hard disk). + *

+ * Date created: Oct 5, 2002 3:51:44 PM + * @author: Rob Greene + */ +public abstract class FormattedDisk extends Disk { + /** + * Use this inner class for label/value mappings in the disk info page. + */ + public class DiskInformation { + private String label; + private String value; + public DiskInformation(String label, String value) { + this.label = label; + this.value = value; + } + public DiskInformation(String label, int value) { + this.label = label; + this.value = Integer.toString(value); + } + public DiskInformation(String label, Date value) { + SimpleDateFormat dateFormat = new SimpleDateFormat("MM/dd/yyyy"); + this.label = label; + if (value != null) { + this.value = dateFormat.format(value); + } else { + this.value = "-None-"; + } + } + public String getLabel() { + return this.label; + } + public String getValue() { + return this.value; + } + } + + /** + * Use this inner interface for managing the disk usage data. + * This offloads format-specific implementation to the implementing class. + * The usage is very similar to a Java2 Iterator - next must be called to + * set the value and isFree/isUsed are available for that location. + */ + public interface DiskUsage { + public boolean hasNext(); + public void next(); + public boolean isFree(); + public boolean isUsed(); + } + + /** + * This inner class represents the column header information used + * in the directory display. Note that this needs to be synchronized + * with the appropriate FileEntry objects. + */ + public static final int FILE_DISPLAY_STANDARD = 1; + public static final int FILE_DISPLAY_NATIVE = 2; + public static final int FILE_DISPLAY_DETAIL = 3; + public class FileColumnHeader { + public static final int ALIGN_LEFT = 1; + public static final int ALIGN_CENTER = 2; + public static final int ALIGN_RIGHT = 3; + private String title; + private int maximumWidth; + private int alignment; + public FileColumnHeader(String title, int maximumWidth, int alignment) { + this.title = title; + this.maximumWidth = maximumWidth; + this.alignment = alignment; + } + public String getTitle() { + return title; + } + public int getMaximumWidth() { + return maximumWidth; + } + public int getAlignment() { + return alignment; + } + public boolean isLeftAlign() { + return alignment == ALIGN_LEFT; + } + public boolean isCenterAlign() { + return alignment == ALIGN_CENTER; + } + public boolean isRightAlign() { + return alignment == ALIGN_RIGHT; + } + } + + /** + * Constructor for FormattedDisk. + * @param filename + * @param diskImage + */ + public FormattedDisk(String filename, byte[] diskImage) { + super(filename, diskImage); + } + + /** + * Identify if this disk format is capable of having directories. + */ + public abstract boolean canHaveDirectories(); + + /** + * Return the name of the disk. Not the physical file name, + * but "DISK VOLUME #xxx" (DOS 3.3) or "/MY.DISK" (ProDOS). + */ + public abstract String getDiskName(); + + /** + * Retrieve a list of files. + */ + public abstract List getFiles(); + + /** + * Identify the operating system format of this disk. + */ + public abstract String getFormat(); + + /** + * Return the amount of free space in bytes. + */ + public abstract int getFreeSpace(); + + /** + * Return the amount of used space in bytes. + */ + public abstract int getUsedSpace(); + + /** + * Get suggested dimensions for display of bitmap. + * Typically, this will be only used for 5.25" floppies. + * This can return null if there is no suggestion. + */ + public abstract int[] getBitmapDimensions(); + + /** + * Get the length of the bitmap. + */ + public abstract int getBitmapLength(); + + /** + * Get the disk usage iterator. + */ + public abstract DiskUsage getDiskUsage(); + + /** + * Get the labels to use in the bitmap. + * Note that this should, at a minimum, return an array of + * String[1] unless the bitmap has not been implemented. + */ + public abstract String[] getBitmapLabels(); + + /** + * Get disk information. This is intended to be pretty generic - + * each disk format can build this as appropriate. Each subclass should + * override this method and add its own detail. + */ + public List getDiskInformation() { + List list = new ArrayList(); + list.add(new DiskInformation("File Name", getFilename())); + list.add(new DiskInformation("Disk Name", getDiskName())); + list.add(new DiskInformation("Physical Size (bytes)", getPhysicalSize())); + list.add(new DiskInformation("Free Space (bytes)", getFreeSpace())); + list.add(new DiskInformation("Used Space (bytes)", getUsedSpace())); + list.add(new DiskInformation("Physical Size (KB)", getPhysicalSize() / 1024)); + list.add(new DiskInformation("Free Space (KB)", getFreeSpace() / 1024)); + list.add(new DiskInformation("Used Space (KB)", getUsedSpace() / 1024)); + list.add(new DiskInformation("Archive Order", + is2ImgOrder() ? "2IMG" : + isDosOrder() ? "DOS 3.3" : + isProdosOrder() ? "ProDOS" : "Unknown")); + list.add(new DiskInformation("Disk Format", getFormat())); + 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(); + list.add(new FileColumnHeader("Name", 30, FileColumnHeader.ALIGN_LEFT)); + list.add(new FileColumnHeader("Type", 8, FileColumnHeader.ALIGN_CENTER)); + list.add(new FileColumnHeader("Size (bytes)", 6, FileColumnHeader.ALIGN_RIGHT)); + list.add(new FileColumnHeader("Locked?", 6, FileColumnHeader.ALIGN_CENTER)); + return list; + } + + /** + * Indicates if this disk format supports "deleted" files. + * Not to be confused with being able to delete a file, this indicates that + * deleted entries remain in the filesystem after the file has been deleted. + * There are some filesystems that "compress" the file out of the structure + * by completely removing the entry instead of marking it deleted (like + * Apple Pascal). + */ + public abstract boolean supportsDeletedFiles(); + + /** + * Indicates if this disk image can read data from a file. + * If not, the reason may be as simple as it has not beem implemented + * to something specific about the disk. + */ + public abstract boolean canReadFileData(); + + /** + * Indicates if this disk image can write data to a file. + * If not, the reason may be as simple as it has not beem implemented + * to something specific about the disk (such as read-only image). + */ + public abstract boolean canWriteFileData(); + + /** + * 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 abstract boolean canCreateFile(); + + /** + * Indicates if this disk image can delete a file. + * If not, the reason may be as simple as it has not beem implemented + * to something specific about the disk. + */ + public abstract boolean canDeleteFile(); + + /** + * Get the data associated with the specified FileEntry. + * This is just the raw data. Use the FileEntry itself to read + * data appropriately! For instance, DOS "B" (binary) files store + * length and address as part of the file itself, but it is not treated + * as file data. + * @see FileEntry#getFileData() + */ + public abstract byte[] getFileData(FileEntry fileEntry); + + /** + * Locate a specific file by filename. + * Returns a null if specific filename is not located. + */ + public FileEntry getFile(String filename) { + List files = getFiles(); + return getFile(files, filename.trim()); + } + + /** + * Recursive routine to locate a specific file by filename. + * Note that in the instance of a system with directories (ie, ProDOS), + * this really returns the first file with the given filename. + */ + protected FileEntry getFile(List files, String filename) { + FileEntry theFileEntry = null; + if (files != null) { + for (int i=0; i + * Address for Apple2 HGR/DHR address is calculated from an observation of a pattern:
+ * line number bits: 87654321
+ * 87 are multipled by 0x0028
+ * 65 are multipled by 0x0100
+ * 4 is multiplied by 0x0080
+ * 321 are multipled by 0x0400 + *

+ * HGR bit values ignore the high bit, as that switches the "palette", and for B&W mode, + * the bit does nothing. The other 7 bits simply toggle the pixel on or off. Double hires + * does not follow this - it uses a real 4 bit value, but the high bit is still ignored for + * graphics (hence, the 560 instead of 640 resolution). + *

+ * Date created: Nov 3, 2002 12:06:36 PM + * @author: Rob Greene + */ +public class GraphicsFileFilter implements FileFilter { + public static final int MODE_HGR_BLACK_AND_WHITE = 1; + public static final int MODE_HGR_COLOR = 2; + public static final int MODE_DHR_BLACK_AND_WHITE = 3; + public static final int MODE_DHR_COLOR = 4; + + private String extension; + private int mode = MODE_HGR_COLOR; + + private static final int CODEC_NONE = 0; // disabled! + private static final int CODEC_IMAGEIO = 1; // JDK 1.4 + private static final int CODEC_JPEGCODEC = 2; // SUN JDK's only + private int imageCodec; + + /** + * Constructor for GraphicsFileFilter. + */ + public GraphicsFileFilter() { + super(); + determineImageCodec(); + } + + /** + * Start guessing which codec is avilable for images. + */ + protected void determineImageCodec() { + try { + Class.forName("javax.imageio.ImageIO"); + imageCodec = CODEC_IMAGEIO; + extension = "PNG"; + return; + } catch (ClassNotFoundException ignored) { + try { + Class.forName("com.sun.image.codec.jpeg.JPEGCodec"); + imageCodec = CODEC_JPEGCODEC; + extension = "JPEG"; + } catch (ClassNotFoundException ignored2) { + imageCodec = CODEC_NONE; + } + } + } + + /** + * Indicate if a codec is available (assist with interface requirements). + */ + public boolean isCodecAvailable() { + return imageCodec != CODEC_NONE; + } + + /** + * Indicate if the ImageIO codec is avilable. + */ + protected boolean isCodecImageIo() { + return imageCodec == CODEC_IMAGEIO; + } + + /** + * Indicate if the SUN JPEG Codec is avilable. + */ + protected boolean isCodecJpegCodec() { + return imageCodec == CODEC_JPEGCODEC; + } + + /** + * Filter the file data and produce an image. + * @see com.webcodepro.applecommander.storage.FileFilter#filter(FileEntry) + */ + public byte[] filter(FileEntry fileEntry) { + byte[] fileData = fileEntry.getFileData(); + BufferedImage image = null; + if (isHiresColorMode()) { + image = new BufferedImage(280, 192, BufferedImage.TYPE_INT_RGB); + } else if (isDoubleHiresMode()) { + image = new BufferedImage(560, 192*2, BufferedImage.TYPE_INT_RGB); + } else { + return new byte[0]; + } + for (int y=0; y<192; y++) { + int base = ( // odd notation - bit value shifted right * hex value + ((y & 0x7) << 10) // 00000111 * 0x0400 + | (y & 0x8) << 4 // 00001000 * 0x0080 + | (y & 0x30) << 4 // 00110000 * 0x0100 + | ((y & 0xc0) >> 6) * 0x028 // 11000000 * 0x0028 + ) & 0x1fff; + byte[] lineData = new byte[40]; + System.arraycopy(fileData, base, lineData, 0, 40); + if (isHiresBlackAndWhiteMode()) { + processHiresBlackAndWhiteLine(lineData, image, y); + } else if (isHiresColorMode()) { + processHiresColorLine(lineData, image, y); + } else if (isDoubleHiresMode()) { + byte[] lineData2 = new byte[40]; + System.arraycopy(fileData, base + 0x2000, lineData2, 0, 40); + if (isDoubleHiresBlackAndWhiteMode()) { + processDoubleHiresBlackAndWhiteLine(lineData, lineData2, image, y); + } else if (isDoubleHiresColorMode()) { + processDoubleHiresColorLine(lineData, lineData2, image, y); + } + } else { + // oops... + } + } + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + try { + if (isCodecImageIo()) { + ImageIO.write(image, getExtension(), outputStream); + } else if (isCodecJpegCodec()) { + JPEGCodec.createJPEGEncoder(outputStream).encode(image); + } + return outputStream.toByteArray(); + } catch (IOException ex) { + return null; + } + } + + /** + * Given a specific line in the image, process it in hires black and white + * mode. + */ + protected void processHiresBlackAndWhiteLine(byte[] lineData, BufferedImage image, int y) { + for (int x=0; x<280; x++) { + int offset = x / 7; // byte across row + int bit = x % 7; // bit to test + byte byt = lineData[offset]; + if (AppleUtil.isBitSet(byt, bit)) { + image.setRGB(x, y, 0xffffff); + } else { + image.setRGB(x, y, 0x0); + } + } + } + + /** + * Given a specific line in the image, process it in hires color mode. + * HGR color is two bits to determine color - essentially resolution is + * 140 horizontally, but it indicates the color for two pixels. + *

+ * The names of pixles is a bit confusion - pixel0 is really the left-most + * pixel (not the low-value bit). + * To alleviate my bad naming, here is a color table to assist:
+ *

+	 * Color   Bits      RGB
+	 * ======= ==== ========
+	 * Black1   000 0x000000
+	 * Green    001 0x00ff00
+	 * Violet   010 0xff00ff
+	 * White1   011 0xffffff
+	 * Black2   100 0x000000
+	 * Orange   101 0xff8000
+	 * Blue     110 0x0000ff
+	 * White2   111 0xffffff
+	 * 
+ * Remember: bits are listed as "highbit", "pixel0", "pixel1"! + */ + protected void processHiresColorLine(byte[] lineData, BufferedImage image, int y) { + for (int x=0; x<140; x++) { + int x0 = x*2; + int x1 = x0+1; + int offset0 = x0 / 7; // byte across row + int bit0 = x0 % 7; // bit to test + boolean pixel0 = AppleUtil.isBitSet(lineData[offset0], bit0); + int offset1 = x1 / 7; // byte across row + int bit1 = x1 % 7; // bit to test + boolean pixel1 = AppleUtil.isBitSet(lineData[offset1], bit1); + int color; + if (pixel0 && pixel1) { + color = 0xffffff; // white + } else if (!pixel0 && !pixel1) { + color = 0; // black + } else { + boolean highbit = pixel0 ? AppleUtil.isBitSet(lineData[offset0], 7) : + AppleUtil.isBitSet(lineData[offset1], 7); + if (pixel0 && highbit) { + color = 0x0000ff; // blue + } else if (pixel0 && !highbit) { + color = 0xff00ff; // voilet + } else if (pixel1 && !highbit) { + color = 0x00ff00; // green + } else { // pixel1 && highbit + color = 0xff8000; // orange + } + } + if (pixel0) image.setRGB(x0, y, color); + if (pixel1) image.setRGB(x1, y, color); + } + } + + /** + * Given a specific line in the image, process it in double hires black and white + * mode. + */ + protected void processDoubleHiresBlackAndWhiteLine(byte[] lineData1, byte[] lineData2, + BufferedImage image, int y) { + + for (int x=0; x<560; x++) { + // alternate bytes - switching memory banks + byte[] lineData = (x % 14 < 7) ? lineData1 : lineData2; + int rowOffset = x / 14; // byte across row + int bit = x % 7; // bit to test + byte byt = lineData[rowOffset]; + if (AppleUtil.isBitSet(byt, bit)) { + image.setRGB(x, y*2, 0xffffff); + image.setRGB(x, y*2+1, 0xffffff); + } else { + image.setRGB(x, y*2, 0x0); + image.setRGB(x, y*2+1, 0x0); + } + } + } + + /** + * Given a specific line in the image, process it in double hires color + * mode. Treat image as 140x192 mode. + *
+	 *                                          Repeated
+ * Binary
+ * Color aux1 main1 aux2 main2 Pattern
+ * Black 00 00 00 00 0000
+ * Magenta 08 11 22 44 0001
+ * Brown 44 08 11 22 0010
+ * Orange 4C 19 33 66 0011
+ * Dark Green 22 44 08 11 0100
+ * Grey1 2A 55 2A 55 0101
+ * Green 66 4C 19 33 0110
+ * Yellow 6E 5D 3B 77 0111
+ * Dark Blue 11 22 44 08 1000
+ * Violet 19 33 66 4C 1001
+ * Grey2 55 2A 55 2A 1010
+ * Pink 5D 3B 77 6E 1011
+ * Medium Blue 33 66 4C 19 1100
+ * Light Blue 3B 77 6E 5D 1101
+ * Aqua 77 6E 5D 3B 1110
+ * White 7F 7F 7F 7F 1111 + *
+ */ + protected void processDoubleHiresColorLine(byte[] lineData1, byte[] lineData2, + BufferedImage image, int y) { + + int[] bitValues = { 8,4,2,1 }; + int[] colorValues = { + 0x000000, 0xff0000, 0x800000, 0xff8000, // black, magenta, brown, orange + 0x008000, 0x808080, 0x00ff00, 0xffff00, // dark green, grey1, green, yellow + 0x000080, 0xff00ff, 0x808080, 0xff80c0, // dark blue, voilet, grey2, pink + 0x0000a0, 0x0000ff, 0x00c080, 0xffffff // medium blue, light blue, aqua, white + }; + for (int x=0; x<560; x+=4) { + int colorValue = 0; + for (int b = 0; b < 4; b++) { + int xb = x+b; + // alternate bytes - switching memory banks + byte[] lineData = (xb % 14 < 7) ? lineData1 : lineData2; + int rowOffset = xb / 14; // byte across row + int bit = xb % 7; // bit to test + byte byt = lineData[rowOffset]; + if (AppleUtil.isBitSet(byt, bit)) { + colorValue+= bitValues[b]; + } + } + for (int b = 0; b < 4; b++) { + image.setRGB(x+b, y*2, colorValues[colorValue]); + image.setRGB(x+b, y*2+1, colorValues[colorValue]); + } + } + } + + /** + * Give file extensions. + */ + public String[] getFileExtensions() { + if (isCodecImageIo()) { + return new String[] { "PNG", "JPEG" }; + } else if (isCodecJpegCodec()) { + return new String[] { "JPEG" }; + } else { + return new String[0]; + } + } + + /** + * Give suggested file name. + */ + public String getSuggestedFileName(FileEntry fileEntry) { + String fileName = fileEntry.getFilename().trim(); + if (!fileName.toLowerCase().endsWith("." + getExtension())) { + fileName = fileName + "." + getExtension(); + } + return fileName; + } + + /** + * Set the format name. + */ + public void setExtension(String extension) { + this.extension = extension; + } + + /** + * Get the format name. + */ + public String getExtension() { + return extension; + } + + /** + * Set the color mode. + */ + public void setMode(int mode) { + this.mode = mode; + } + + /** + * Indicates if this is configured for hires black & white mode. + */ + public boolean isHiresBlackAndWhiteMode() { + return mode == MODE_HGR_BLACK_AND_WHITE; + } + + /** + * Indicates if this is configured for hires color mode. + */ + public boolean isHiresColorMode() { + return mode == MODE_HGR_COLOR; + } + + /** + * Indicates if this is configured for double hires black & white mode. + */ + public boolean isDoubleHiresBlackAndWhiteMode() { + return mode == MODE_DHR_BLACK_AND_WHITE; + } + + /** + * Indicates if this is configured for double hires color mode. + */ + public boolean isDoubleHiresColorMode() { + return mode == MODE_DHR_COLOR; + } + + /** + * Indicates if this is a hires mode. + */ + protected boolean isHiresMode() { + return isHiresBlackAndWhiteMode() || isHiresColorMode(); + } + + /** + * Indicates if this is a double hires mode. + */ + protected boolean isDoubleHiresMode() { + return isDoubleHiresBlackAndWhiteMode() || isDoubleHiresColorMode(); + } +} diff --git a/src/com/webcodepro/applecommander/storage/IntegerBasicFileFilter.java b/src/com/webcodepro/applecommander/storage/IntegerBasicFileFilter.java new file mode 100644 index 0000000..fe32afe --- /dev/null +++ b/src/com/webcodepro/applecommander/storage/IntegerBasicFileFilter.java @@ -0,0 +1,132 @@ +/* + * 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; + +import java.io.ByteArrayOutputStream; +import java.io.PrintWriter; + +/** + * Filter the given file as an Integer BASIC file. + *

+ * On disk, it looks similar to Applesoft - first two bytes are length; rest of + * image is raw data for Integer Basic. + *

+ * [byte] length of line
+ * [word] line number
+ * [byte]* line data
+ * $01 end of line
+ * Repeat until end of program (line length of 0). + *

+ * Tokens are $00 - $7F, some are duplicated.
+ * $01 = end of line.
+ * $B0 - $B9 = signifies a number stored in a word.
+ *

+ * Date created: Nov 3, 2002 1:14:47 AM + * @author: Rob Greene + */ +public class IntegerBasicFileFilter implements FileFilter { + private static String[] tokens = { + null, null, null, ": ", "LOAD ", "SAVE ", null, "RUN ", // $00-$07 + null, "DEL ", ", ", "NEW ", "CLR ", "AUTO ", null, "MAN ", // $08-$0F + "HIMEM: ", "LOMEM: ", "+", "-", "*", "/", "=", "#", // $10-$17 + ">=", ">", "<=", "<>", "<", " AND ", " OR ", " MOD ",// $18-$1F + "^", null, "(", ",", " THEN ", " THEN ", ",", ",", // $20-$27 + "\"", "\"", "(", null, null, "(", " PEEK ", "RND ", // $28-$2F + "SGN ", "ABS ", "PDL ", null, "(", "+", "-", "NOT ", // $30-$37 + "(", "=", "#", " LEN(", " ASC(", " SCRN(", ",", " (", // $38-$3F + "$", null, "(", ",", ",", ";", ";", ";", // $40-$47 + ",", ",", ",", "TEXT ", "GR ", "CALL ", "DIM ", "DIM ", // $48-$4F + "TAB ", "END ", "INPUT ", "INPUT ", "INPUT ", "FOR ", "=", " TO ", // $50-$57 + " STEP ", "NEXT ", ",", "RETURN ", "GOSUB ", "REM ", "LET ", "GOTO ",// $58-$5F + "IF ", "PRINT ", "PRINT ", "PRINT ", " POKE ", ",", "COLOR= ", "PLOT ",// $60-$67 + ",", "HLIN ", ",", " AT ", "VLIN ", ",", " AT ", "VTAB ",// $68-$6F + "=", "=", ")", null, "LIST ", ",", null, "POP ", // $70-$77 + null, "NO DSP ", "NO TRACE ", "DSP ", "DSP ", "TRACE ", "PR # ", "IN # " // $78-$7F + }; + + /** + * Constructor for IntegerBasicFileFilter. + */ + public IntegerBasicFileFilter() { + super(); + } + + /** + * Process the given FileEntry and return a text image of the Integer BASIC file. + * @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 * 2); + PrintWriter printWriter = new PrintWriter(byteArray, true); + while (offset < fileData.length) { + int lineLength = AppleUtil.getUnsignedByte(fileData[offset]); + int lineNumber = AppleUtil.getWordValue(fileData, offset+1); + boolean inComment = false; + printWriter.print(lineNumber); + printWriter.print(' '); + for (int i=offset+3; i<(offset+lineLength); i++) { + byte byt = fileData[i]; + if ((byt & 0x80) != 0) { + int value = AppleUtil.getUnsignedByte(byt); + // numbers follow a number digit + if (!inComment && value >= 0xb1 && value <= 0xb9) { + int integer = AppleUtil.getWordValue(fileData, i+1); + printWriter.print(integer); + i+= 2; + } else { + char ch = (char)(byt&0x7f); + if (ch < 0x20) { // handle control characters + printWriter.print(""); + } else { + printWriter.print(ch); + } + } + } else { + String token = tokens[(int)byt]; + if (token != null) { + printWriter.print(token); + inComment = (byt == 0x5d); // REM statement + } else { + // ignoring unknown tokens + // $00 and $01 seem to be valid; the others may or may not be valid + } + } + } + offset+= lineLength; + printWriter.println(); + } + return byteArray.toByteArray(); + } + + /** + * Give suggested file name. + */ + public String getSuggestedFileName(FileEntry fileEntry) { + String fileName = fileEntry.getFilename().trim(); + if (!fileName.toLowerCase().endsWith(".int")) { + fileName = fileName + ".int"; + } + return fileName; + } +} diff --git a/src/com/webcodepro/applecommander/storage/PascalFileEntry.java b/src/com/webcodepro/applecommander/storage/PascalFileEntry.java new file mode 100644 index 0000000..410aaf2 --- /dev/null +++ b/src/com/webcodepro/applecommander/storage/PascalFileEntry.java @@ -0,0 +1,209 @@ +/* + * 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; + +import java.text.NumberFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +/** + * Represents a Pascal file entry on disk. + *

+ * Date created: Oct 5, 2002 12:22:34 AM + * @author: Rob Greene + */ +public class PascalFileEntry implements FileEntry { + private byte[] fileEntry; + private PascalFormatDisk disk; + + /** + * Constructor for PascalFileEntry. + */ + public PascalFileEntry(byte[] fileEntry, PascalFormatDisk disk) { + super(); + this.fileEntry = fileEntry; + this.disk = disk; + } + + /** + * Get the block number of the files 1st block. + */ + public int getFirstBlock() { + return AppleUtil.getWordValue(fileEntry, 0); + } + + /** + * Get the block number of the files last block +1. + */ + public int getLastBlock() { + return AppleUtil.getWordValue(fileEntry, 2); + } + + /** + * Return the name of this file. + */ + public String getFilename() { + return AppleUtil.getPascalString(fileEntry, 6); + } + + /** + * Return the filetype of this file. + */ + public String getFiletype() { + String filetypes[] = { + "xdskfile (for bad blocks)", + "codefile", + "textfile", + "infofile", + "datafile", + "graffile", + "fotofile", + "securedir" }; + int filetype = fileEntry[4] & 0x0f; + if (filetype == 0 || filetype > filetypes.length) { + return "unknown (" + filetype + ")"; + } else { + return filetypes[filetype-1]; + } + } + + /** + * Identify if this file is locked - not applicable in Pascal? + */ + public boolean isLocked() { + return false; + } + + /** + * Get the number of bytes used in files last block. + */ + public int getBytesUsedInLastBlock() { + return AppleUtil.getWordValue(fileEntry, 22); + } + + /** + * Compute the size of this file (in bytes). + */ + public int getSize() { + int blocks = getBlocksUsed() - 1; + return blocks*Disk.BLOCK_SIZE + getBytesUsedInLastBlock(); + } + + /** + * Compute the blocks used. + */ + public int getBlocksUsed() { + return AppleUtil.getWordValue(fileEntry, 2) - AppleUtil.getWordValue(fileEntry, 0); + } + + /** + * Pascal does not support directories. + */ + public boolean isDirectory() { + return false; + } + + /** + * Retrieve the list of files in this directory. + * Always returns null, as Pascal does not support directories. + */ + public List getFiles() { + return null; + } + + /** + * Pascal file entries are removed upon deletion. + * Thus, a file entry cannot be marked as deleted. + */ + public boolean isDeleted() { + return false; + } + + /** + * Get the file modification date. + */ + public Date getModificationDate() { + return AppleUtil.getPascalDate(fileEntry, 24); + } + + /** + * 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(); + SimpleDateFormat dateFormat = new SimpleDateFormat("MM/dd/yy"); + + List list = new ArrayList(); + switch (displayMode) { + case FormattedDisk.FILE_DISPLAY_NATIVE: + list.add(dateFormat.format(getModificationDate())); + numberFormat.setMinimumIntegerDigits(3); + list.add(numberFormat.format(getBlocksUsed())); + list.add(getFiletype()); + list.add(getFilename()); + break; + case FormattedDisk.FILE_DISPLAY_DETAIL: + list.add(dateFormat.format(getModificationDate())); + numberFormat.setMinimumIntegerDigits(3); + list.add(numberFormat.format(getBlocksUsed())); + numberFormat.setMinimumIntegerDigits(1); + list.add(numberFormat.format(getBytesUsedInLastBlock())); + list.add(numberFormat.format(getSize())); + list.add(getFiletype()); + list.add(getFilename()); + numberFormat.setMinimumIntegerDigits(3); + list.add(numberFormat.format(getFirstBlock())); + list.add(numberFormat.format(getLastBlock()-1)); + break; + default: // FILE_DISPLAY_STANDARD + list.add(getFilename()); + list.add(getFiletype()); + list.add(numberFormat.format(getSize())); + list.add(isLocked() ? "Locked" : ""); + break; + } + return list; + } + + /** + * Get file data. This handles any operating-system specific issues. + * Currently, the disk itself handles this. + */ + public byte[] getFileData() { + return disk.getFileData(this); + } + + /** + * 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. + */ + public FileFilter getSuggestedFilter() { + if ("textfile".equals(getFiletype())) { + return new TextFileFilter(); + } + return new BinaryFileFilter(); + } + +} diff --git a/src/com/webcodepro/applecommander/storage/PascalFormatDisk.java b/src/com/webcodepro/applecommander/storage/PascalFormatDisk.java new file mode 100644 index 0000000..20fa590 --- /dev/null +++ b/src/com/webcodepro/applecommander/storage/PascalFormatDisk.java @@ -0,0 +1,356 @@ +/* + * 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; + +import java.util.ArrayList; +import java.util.BitSet; +import java.util.Date; +import java.util.Iterator; +import java.util.List; + +/** + * Manages a disk that is in the Pascal format. + *

+ * Date created: Oct 4, 2002 11:56:50 PM + * @author: Rob Greene + */ +public class PascalFormatDisk extends FormattedDisk { + /** + * The size of the Pascal file entry. + */ + public static final int ENTRY_SIZE = 26; + + /** + * Use this inner interface for managing the disk usage data. + * This offloads format-specific implementation to the implementing class. + * A BitSet is used to track all blocks, as Pascal disks do not have a + * bitmap stored on the disk. This is safe since we know the number of blocks + * that exist. (BitSet length is of last set bit - unset bits at the end are + * "lost".) + */ + private class PascalDiskUsage implements DiskUsage { + private int location = -1; + private BitSet bitmap = null; + public boolean hasNext() { + return location == -1 || location < getBlocksOnDisk() - 1; + } + public void next() { + if (bitmap == null) { + bitmap = new BitSet(getBlocksOnDisk()); + // assume all blocks are unused + for (int block=6; block + * Date created: Oct 5, 2002 11:17:00 PM + * @author: Rob Greene + */ +public class ProdosCommonDirectoryHeader extends ProdosCommonEntry { + + /** + * Constructor for ProdosCommonDirectoryHeader. + * @param fileEntry + */ + public ProdosCommonDirectoryHeader(byte[] fileEntry) { + super(fileEntry); + } + + /** + * Get the length of each entry. Expected to be 0x27. + */ + public int getEntryLength() { + return AppleUtil.getUnsignedByte(getFileEntry()[0x1f]); + } + + /** + * Get the number of entries per block. Expected to be 0x0d. + */ + public int getEntriesPerBlock() { + return AppleUtil.getUnsignedByte(getFileEntry()[0x20]); + } + + /** + * Get the number of active entries in the volume directory. + */ + public int getFileCount() { + return AppleUtil.getWordValue(getFileEntry(), 0x21); + } + + /** + * Get the block number of the bit map. + */ + public int getBitMapPointer() { + return AppleUtil.getWordValue(getFileEntry(), 0x23); + } + + /** + * Get the total number of blocks on this volume. + */ + public int getTotalBlocks() { + return AppleUtil.getWordValue(getFileEntry(), 0x25); + } +} diff --git a/src/com/webcodepro/applecommander/storage/ProdosCommonEntry.java b/src/com/webcodepro/applecommander/storage/ProdosCommonEntry.java new file mode 100644 index 0000000..cb61e53 --- /dev/null +++ b/src/com/webcodepro/applecommander/storage/ProdosCommonEntry.java @@ -0,0 +1,153 @@ +/* + * 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; + +import java.util.Date; + +/** + * Represents the common Prodos entry behavior. + *

+ * Date created: Oct 5, 2002 10:55:41 PM + * @author: Rob Greene + */ +public class ProdosCommonEntry { + private byte[] fileEntry; + + /** + * Constructor for ProdosCommonEntry. + */ + public ProdosCommonEntry(byte[] fileEntry) { + super(); + this.fileEntry = fileEntry; + } + + /** + * Get the fileEntry bytes. + */ + protected byte[] getFileEntry() { + return fileEntry; + } + + /** + * Get storage type. + */ + protected int getStorageType() { + return AppleUtil.getUnsignedByte(getFileEntry()[0]) >> 4; + } + + /** + * Indicates if this is a "seedling" file (only one data block). + */ + public boolean isSeedlingFile() { + return getStorageType() == 0x01; + } + + /** + * Indicates if this is a "sapling" file (2 to 256 data blocks). + */ + public boolean isSaplingFile() { + return getStorageType() == 0x02; + } + + /** + * Indicates if this is a "tree" file (257 to 32768 data blocks). + */ + public boolean isTreeFile() { + return getStorageType() == 0x03; + } + + /** + * Indicates if this is a subdirectory header entry. + */ + public boolean isSubdirectoryHeader() { + return getStorageType() == 0x0e; + } + + /** + * Indicates if this is a volume header entry. + */ + public boolean isVolumeHeader() { + return getStorageType() == 0x0f; + } + + /** + * Get the creation date. + */ + public Date getCreationDate() { + return AppleUtil.getProdosDate(getFileEntry(), 0x18); + } + + /** + * Get the version of ProDOS that created this file. + */ + public int getProdosVersion() { + return AppleUtil.getUnsignedByte(getFileEntry()[0x1c]); + } + + /** + * Get the minimum version of ProDOS which can access this file. + */ + public int getMinimumProdosVersion() { + return AppleUtil.getUnsignedByte(getFileEntry()[0x1d]); + } + + /** + * Get the access byte. + */ + protected byte getAccess() { + return getFileEntry()[0x1e]; + } + + /** + * Indicates if this file may be destroyed. + */ + public boolean canDestroy() { + return AppleUtil.isBitSet(getAccess(), 7); + } + + /** + * Indicates if this file may be renamed. + */ + public boolean canRename() { + return AppleUtil.isBitSet(getAccess(), 6); + } + + /** + * Indicates if this file has changed since last backup. + */ + public boolean hasChanged() { + return AppleUtil.isBitSet(getAccess(), 5); + } + + /** + * Indicates if this file may be written. + */ + public boolean canWrite() { + return AppleUtil.isBitSet(getAccess(), 1); + } + + /** + * Indicates if this file may be read. + */ + public boolean canRead() { + return AppleUtil.isBitSet(getAccess(), 0); + } + +} diff --git a/src/com/webcodepro/applecommander/storage/ProdosFileEntry.java b/src/com/webcodepro/applecommander/storage/ProdosFileEntry.java new file mode 100644 index 0000000..dd2f799 --- /dev/null +++ b/src/com/webcodepro/applecommander/storage/ProdosFileEntry.java @@ -0,0 +1,457 @@ +/* + * 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; + +import java.text.NumberFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +/** + * Represents a ProDOS file entry on disk. + *

+ * Date created: Oct 5, 2002 6:01:15 PM + * @author: Rob Greene + */ +public class ProdosFileEntry extends ProdosCommonEntry implements FileEntry { + private List files; + private ProdosSubdirectoryHeader subdirectoryHeader; + private ProdosFormatDisk disk; + + /** + * Constructor for ProdosFileEntry. + */ + public ProdosFileEntry(byte[] fileEntry, ProdosFormatDisk disk) { + super(fileEntry); + this.disk = disk; + } + + /** + * Return the name of this file. + * This handles normal files, deleted files, and AppleWorks files - which use + * the AUXTYPE attribute to indicate upper/lower-case in the filename. + */ + public String getFilename() { + String fileName; + if (isDeleted()) { + fileName = AppleUtil.getString(getFileEntry(), 1, 15); + StringBuffer buf = new StringBuffer(); + for (int i=0; i> 8), 7-(i%8)); + } + if (lowerCase) { + char ch = mixedCase.charAt(i); + if (ch == '.') { + mixedCase.setCharAt(i, ' '); + } else { + mixedCase.setCharAt(i, Character.toLowerCase(ch)); + } + } + } + fileName = mixedCase.toString(); + } + return fileName; + } + + /** + * Return the filetype of this file. This will be three characters, + * according to ProDOS - a "$xx" if unknown. + *

+ * This could be improved should specific information regarding file types + * be needed; the file type could become a separate object which works with + * the file in some manner. + *

+ * Note: Source of information is the following url - + * http://www.apple2.org.za/gswv/gsezine/GS.WorldView/ProDOS.File.Types.v2.0.txt + */ + public String getFiletype() { + int filetype = AppleUtil.getUnsignedByte(getFileEntry()[0x10]); + switch (filetype) { + case 0x00: return "UNK"; + case 0x01: return "BAD"; + case 0x02: return "PCD"; + case 0x03: return "PTX"; + case 0x04: return "TXT"; + case 0x05: return "PDA"; + case 0x06: return "BIN"; + case 0x07: return "FNT"; + case 0x08: return "FOT"; + case 0x09: return "BA3"; + case 0x0a: return "DA3"; + case 0x0b: return "WPF"; + case 0x0c: return "SOS"; + case 0x0f: return "DIR"; + case 0x10: return "RPD"; + case 0x11: return "RPI"; + case 0x12: return "AFD"; + case 0x13: return "AFM"; + case 0x14: return "AFR"; + case 0x15: return "SCL"; + case 0x16: return "PFS"; + case 0x19: return "ADB"; // AppleWorks: AUX TYPE indicates UPPER/lower case + case 0x1a: return "AWP"; // AppleWorks: AUX TYPE indicates UPPER/lower case + case 0x1b: return "ASP"; // AppleWorks: AUX TYPE indicates UPPER/lower case + case 0x20: return "TDM"; + case 0x21: return "IPS"; + case 0x22: return "UPV"; + case 0x29: return "3SD"; + case 0x2a: return "8SC"; + case 0x2b: return "8OB"; + case 0x2c: return "8IC"; + case 0x2d: return "8LD"; + case 0x2e: return "P8C"; // P8C or PTP, depending on AUX TYPE + case 0x41: return "OCR"; + case 0x42: return "FTD"; + case 0x50: return "GWP"; + case 0x51: return "GSS"; + case 0x52: return "GDB"; + case 0x53: return "DRW"; + case 0x54: return "GDP"; + case 0x55: return "HMD"; + case 0x56: return "EDU"; + case 0x57: return "STN"; + case 0x58: return "HLP"; + case 0x59: return "COM"; + case 0x5a: return "CFG"; // CFG or PTP, depending on AUX TYPE + case 0x5b: return "ANM"; + case 0x5c: return "MUM"; + case 0x5d: return "ENT"; + case 0x5e: return "DVU"; + case 0x60: return "PRE"; + case 0x6b: return "BIO"; + case 0x6d: return "DVR"; // DVR/TDR + case 0x6e: return "PRE"; + case 0x6f: return "HDV"; // PC Volume + case 0xa0: return "WP_"; + case 0xab: return "GSB"; + case 0xac: return "TDF"; + case 0xad: return "BDF"; + case 0xb0: return "SRC"; + case 0xb1: return "OBJ"; + case 0xb2: return "LIB"; + case 0xb3: return "S16"; + case 0xb4: return "RTL"; + case 0xb5: return "EXE"; + case 0xb6: return "STR"; // STR/PIF + case 0xb7: return "TSF"; // TSF/TIF + case 0xb8: return "NDA"; + case 0xb9: return "CDA"; + case 0xba: return "TOL"; + case 0xbb: return "DRV"; // DRV/DVR + case 0xbc: return "LDF"; + case 0xbd: return "FST"; + case 0xbf: return "DOC"; + case 0xc0: return "PNT"; + case 0xc1: return "PIC"; + case 0xc2: return "ANI"; + case 0xc3: return "PAL"; + case 0xc5: return "OOG"; + case 0xc6: return "SCR"; + case 0xc7: return "CDV"; + case 0xc8: return "FON"; + case 0xc9: return "FND"; + case 0xca: return "ICN"; + case 0xd5: return "MUS"; + case 0xd6: return "INS"; + case 0xd7: return "MDI"; + case 0xd8: return "SND"; + case 0xdb: return "DBM"; + case 0xe0: return "SHK"; + case 0xe2: return "DTS"; // DTS/ATK + case 0xee: return "R16"; + case 0xef: return "PAS"; + case 0xf0: return "CMD"; + // Left $F1 - $F8 alone as these are user-defined types + case 0xf9: return "P16"; + case 0xfa: return "INT"; + case 0xfb: return "IVR"; + case 0xfc: return "BAS"; + case 0xfd: return "VAR"; + case 0xfe: return "REL"; + case 0xff: return "SYS"; + default : + return "$" + AppleUtil.getFormattedByte(filetype); + } + } + + /** + * Indicate if this is an AppleWorks file. + * Intended to force upper/lowercase into the filename. + */ + public boolean isAppleWorksFile() { + int filetype = AppleUtil.getUnsignedByte(getFileEntry()[0x10]); + return (filetype == 0x19 || filetype == 0x1a || filetype == 0x1b); + } + + /** + * Get the key pointer. This is either the data block (seedling), + * index block (sapling), or master index block (tree). + */ + public int getKeyPointer() { + return AppleUtil.getWordValue(getFileEntry(), 0x11); + } + + /** + * Get the number of blocks used. + */ + public int getBlocksUsed() { + return AppleUtil.getWordValue(getFileEntry(), 0x13); + } + + /** + * Get the EOF position. This can indicate the length of a file. + */ + public int getEofPosition() { + return AppleUtil.get3ByteValue(getFileEntry(), 0x15); + } + + + /** + * Get the auxiliary type for this file. + * TXT - random access record length. + * BIN - load address for binary image. + * BAS - load address for program image. + * VAR - address of compressed variables image. + * SYS - load address for system program (usually 0x2000). + */ + public int getAuxiliaryType() { + return AppleUtil.getWordValue(getFileEntry(), 0x1f); + } + + /** + * Get the last modification date. + */ + public Date getLastModificationDate() { + return AppleUtil.getProdosDate(getFileEntry(), 0x21); + } + + /** + * Get the block number of the key block for the directory which describes this file. + */ + public int getHeaderPointer() { + return AppleUtil.getWordValue(getFileEntry(), 0x25); + } + + /** + * Identify if this file is locked. + */ + public boolean isLocked() { + return !canDestroy() && !canRename() && !canWrite(); + } + + /** + * Compute the size of this file (in bytes). + */ + public int getSize() { + return getEofPosition(); + } + + /** + * Identify if this is a directory file. + */ + public boolean isDirectory() { + return getStorageType() == 0x0d; + } + + /** + * Retrieve the list of files in this directory. + * Note that if this is not a directory, the return + * value should be null. If this a directory, the + * return value should always be a list - a directory + * with 0 entries returns an empty list. + */ + public List getFiles() { + return files; + } + + /** + * Set the list of files. + */ + public void setFiles(List files) { + this.files = files; + } + + /** + * Identify if this file has been deleted. + */ + public boolean isDeleted() { + return getStorageType() == 0; + } + + /** + * Set the subdirectory header. + */ + public void setSubdirectoryHeader(ProdosSubdirectoryHeader subdirectoryHeader) { + this.subdirectoryHeader = subdirectoryHeader; + } + + /** + * Get the subdirectory header. + */ + public ProdosSubdirectoryHeader getSubdirectoryHeader() { + return this.subdirectoryHeader; + } + + /** + * 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(); + SimpleDateFormat dateFormat = new SimpleDateFormat("MM/dd/yyyy"); + + List list = new ArrayList(); + switch (displayMode) { + case FormattedDisk.FILE_DISPLAY_NATIVE: + list.add(isLocked() ? "*" : " "); + list.add(getFilename()); + list.add(getFiletype()); + numberFormat.setMinimumIntegerDigits(3); + list.add(numberFormat.format(getBlocksUsed())); + list.add(getLastModificationDate() == null ? " " : + dateFormat.format(getLastModificationDate())); + list.add(getCreationDate() == null ? " " : + dateFormat.format(getCreationDate())); + numberFormat.setMinimumIntegerDigits(1); + list.add(numberFormat.format(getEofPosition())); + if ("TXT".equals(getFiletype()) && getAuxiliaryType() > 0) { + numberFormat.setMinimumIntegerDigits(1); + list.add("L=" + numberFormat.format(getAuxiliaryType()).trim()); + } else if (("BIN".equals(getFiletype()) || "BAS".equals(getFiletype()) + || "VAR".equals(getFiletype()) || "SYS".equals(getFiletype())) + && getAuxiliaryType() > 0) { + list.add("A=$" + AppleUtil.getFormattedWord(getAuxiliaryType())); + } else { + list.add(""); + } + break; + case FormattedDisk.FILE_DISPLAY_DETAIL: + list.add(isLocked() ? "*" : " "); + list.add(getFilename()); + list.add(isDeleted() ? "Deleted" : ""); + String permissions = ""; + if (canDestroy()) permissions+= "Destroy "; + if (canRead()) permissions+= "Read "; + if (canRename()) permissions+= "Rename "; + if (canWrite()) permissions+= "Write "; + list.add(permissions); + list.add(getFiletype()); + list.add(isDirectory() ? "Directory" : ""); + numberFormat.setMinimumIntegerDigits(3); + list.add(numberFormat.format(getBlocksUsed())); + list.add(getLastModificationDate() == null ? " " : + dateFormat.format(getLastModificationDate())); + list.add(getCreationDate() == null ? " " : + dateFormat.format(getCreationDate())); + numberFormat.setMinimumIntegerDigits(1); + list.add(numberFormat.format(getEofPosition())); + if ("TXT".equals(getFiletype()) && getAuxiliaryType() > 0) { + numberFormat.setMinimumIntegerDigits(1); + list.add("L=" + numberFormat.format(getAuxiliaryType()).trim()); + } else if (("BIN".equals(getFiletype()) || "BAS".equals(getFiletype()) + || "VAR".equals(getFiletype()) || "SYS".equals(getFiletype())) + && getAuxiliaryType() > 0) { + list.add("A=$" + AppleUtil.getFormattedWord(getAuxiliaryType())); + } else { + list.add(""); + } + list.add(AppleUtil.getFormattedWord(getHeaderPointer())); + list.add(AppleUtil.getFormattedWord(getKeyPointer())); + list.add(isSaplingFile() ? "Sapling" : isSeedlingFile() ? "Seedling" : + isTreeFile() ? "Tree" : "Unknown"); + list.add(hasChanged() ? "Changed" : ""); + numberFormat.setMinimumIntegerDigits(1); + list.add(numberFormat.format(getMinimumProdosVersion())); + list.add(numberFormat.format(getProdosVersion())); + break; + default: // FILE_DISPLAY_STANDARD + list.add(getFilename()); + list.add(getFiletype()); + list.add(numberFormat.format(getSize())); + list.add(isLocked() ? "Locked" : ""); + break; + } + return list; + } + + /** + * Get file data. This handles any operating-system specific issues. + * Currently, the disk itself handles this. + */ + public byte[] getFileData() { + return disk.getFileData(this); + } + + /** + * 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. + */ + public FileFilter getSuggestedFilter() { + if ("TXT".equals(getFiletype()) || "SRC".equals(getFiletype())) { + return new TextFileFilter(); + } else if ("AWP".equals(getFiletype())) { + return new AppleWorksWordProcessorFileFilter(); + } else if ("BAS".equals(getFiletype())) { + return new ApplesoftFileFilter(); + } else if ("INT".equals(getFiletype())) { // supposedly not available in ProDOS, however + return new IntegerBasicFileFilter(); + } else if ("BIN".equals(getFiletype())) { + int size = getSize(); + // the minimum size is guessed a bit - I don't remember, but maybe there + // are 8 spare bytes at the end of the graphics screen + GraphicsFileFilter filter = new GraphicsFileFilter(); + if (size >= 8185 && size <= 8192) { + filter.setMode(GraphicsFileFilter.MODE_HGR_COLOR); + return filter; + } else if (size >= 16377 && size <= 16384) { + filter.setMode(GraphicsFileFilter.MODE_DHR_COLOR); + return filter; + } + // fall through to BinaryFileFilter... + } + return new BinaryFileFilter(); + } +} diff --git a/src/com/webcodepro/applecommander/storage/ProdosFormatDisk.java b/src/com/webcodepro/applecommander/storage/ProdosFormatDisk.java new file mode 100644 index 0000000..8a04e0c --- /dev/null +++ b/src/com/webcodepro/applecommander/storage/ProdosFormatDisk.java @@ -0,0 +1,392 @@ +/* + * 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; + +import java.util.ArrayList; +import java.util.List; + +/** + * Manages a disk that is in the ProDOS format. + *

+ * Date created: Oct 3, 2002 11:45:25 PM + * @author: Rob Greene + */ +public class ProdosFormatDisk extends FormattedDisk { + /** + * The standard ProDOS file entry length. + */ + public static final int ENTRY_LENGTH = 0x27; + + /** + * Hold on to the volume directory header. + */ + private ProdosVolumeDirectoryHeader volumeHeader; + + /** + * Use this inner interface for managing the disk usage data. + * This offloads format-specific implementation to the implementing class. + */ + private class ProdosDiskUsage implements DiskUsage { + private int location = -1; + private transient byte[] data = null; + public boolean hasNext() { + return location == -1 || location < volumeHeader.getTotalBlocks() - 1; + } + public void next() { + location++; + } + /** + * Get the free setting for the bitmap at the current location. + */ + public boolean isFree() { + if (location == -1) { + throw new IllegalArgumentException("Invalid dimension for isFree! Did you call next first?"); + } + if (data == null) { + int volumeBitmapBlock = volumeHeader.getBitMapPointer(); + int volumeBitmapBlocks = volumeHeader.getTotalBlocks(); + int blocksToRead = (volumeBitmapBlocks / 4096) + 1; + // Read in the entire volume bitmap: + data = new byte[blocksToRead * BLOCK_SIZE]; + for (int i=0; i fileData.length) { // end of file + int bytesToCopy = fileData.length - offset; + if (blockNumber != 0) System.arraycopy(blockData, 0, fileData, offset, bytesToCopy); + offset+= bytesToCopy; + } else { + if (blockNumber != 0) System.arraycopy(blockData, 0, fileData, offset, blockData.length); + offset+= blockData.length; + } + } + return offset; + } +} diff --git a/src/com/webcodepro/applecommander/storage/ProdosSubdirectoryHeader.java b/src/com/webcodepro/applecommander/storage/ProdosSubdirectoryHeader.java new file mode 100644 index 0000000..39a3cd8 --- /dev/null +++ b/src/com/webcodepro/applecommander/storage/ProdosSubdirectoryHeader.java @@ -0,0 +1,66 @@ +/* + * 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; + +/** + * Provides commone subdirectory attributes. + *

+ * Date created: Oct 5, 2002 11:17:57 PM + * @author: Rob Greene + */ +public class ProdosSubdirectoryHeader extends ProdosCommonDirectoryHeader { + + /** + * Constructor for ProdosSubdirectoryHeader. + * @param fileEntry + */ + public ProdosSubdirectoryHeader(byte[] fileEntry) { + super(fileEntry); + } + + /** + * Return the name of this subdirectory. + */ + public String getSubdirectoryName() { + return AppleUtil.getProdosString(getFileEntry(), 0); + } + + /** + * Return the block number of the parent directory which contains the + * file entry for this subdirectory. + */ + public int getParentPointer() { + return AppleUtil.getWordValue(getFileEntry(), 0x23); + } + + /** + * Return the number of the file entry within the parent block. + */ + public int getParentEntry() { + return AppleUtil.getUnsignedByte(getFileEntry()[0x25]); + } + + /** + * Return the length of the parent entry. + */ + public int getParentEntryLength() { + return AppleUtil.getWordValue(getFileEntry(), 0x26); + } +} diff --git a/src/com/webcodepro/applecommander/storage/ProdosVolumeDirectoryHeader.java b/src/com/webcodepro/applecommander/storage/ProdosVolumeDirectoryHeader.java new file mode 100644 index 0000000..7659b22 --- /dev/null +++ b/src/com/webcodepro/applecommander/storage/ProdosVolumeDirectoryHeader.java @@ -0,0 +1,44 @@ +/* + * 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; + +/** + * Represents the ProDOS volume directory header. + *

+ * Date created: Oct 5, 2002 10:58:24 PM + * @author: Rob Greene + */ +public class ProdosVolumeDirectoryHeader extends ProdosCommonDirectoryHeader { + + /** + * Constructor for ProdosVolumeDirectoryHeaderEntry. + * @param fileEntry + */ + public ProdosVolumeDirectoryHeader(byte[] fileEntry) { + super(fileEntry); + } + + /** + * Return the name of this volume. + */ + public String getVolumeName() { + return AppleUtil.getProdosString(getFileEntry(), 0); + } +} diff --git a/src/com/webcodepro/applecommander/storage/RdosFileEntry.java b/src/com/webcodepro/applecommander/storage/RdosFileEntry.java new file mode 100644 index 0000000..c7770f2 --- /dev/null +++ b/src/com/webcodepro/applecommander/storage/RdosFileEntry.java @@ -0,0 +1,239 @@ +/* + * 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; + +import java.text.NumberFormat; +import java.util.ArrayList; +import java.util.List; + +/** + * Handle RDOS file entry format. + *

+ * Since I was unable to locate the file entries on the internet, it is documented here: + * + * Offset Description
+ * ====== ====================================================
+ * $00-$17 File name; space-filled. If the first byte is $00, that is the end of the
+ * directory. If the first byte is $80, the file is deleted.
+ * $18 File type. Appears to be actual letter ('A'=Applesoft, etc)
+ * $19 File length in blocks (block = sector = 256 bytes)
+ * $1A-$1B Address of application. For Applesoft and binary; others may vary.
+ * $1C-$1D Length in bytes of file.
+ * $1E-$1F Starting block of application.
+ *
+ *

+ * Date created: Oct 7, 2002 1:36:56 PM + * @author: Rob Greene + */ +public class RdosFileEntry implements FileEntry { + private byte[] fileEntry; + private RdosFormatDisk disk; + + /** + * Constructor for RdosFileEntry. + */ + public RdosFileEntry(byte[] fileEntry, RdosFormatDisk disk) { + super(); + this.fileEntry = fileEntry; + this.disk = disk; + } + + /** + * Return the number of blocks this file uses. + */ + public int getSizeInBlocks() { + return AppleUtil.getUnsignedByte(fileEntry[0x19]); + } + + /** + * Return the starting block of this application. + */ + public int getStartingBlock() { + return AppleUtil.getWordValue(fileEntry, 0x1e); + } + + /** + * Return the address of application. + */ + public int getAddress() { + return AppleUtil.getWordValue(fileEntry, 0x1a); + } + + /** + * Return the name of this file. + */ + public String getFilename() { + return isDeleted() ? " " : AppleUtil.getString(fileEntry, 0, 24); + } + + /** + * Return the filetype of this file. + */ + public String getFiletype() { + return isDeleted() ? " " : AppleUtil.getString(fileEntry, 0x18, 1); + } + + /** + * Locked doesn't appear to be a concept under RDOS. + */ + public boolean isLocked() { + return false; + } + + /** + * Compute the size of this file (in bytes). + */ + public int getSize() { + return AppleUtil.getWordValue(fileEntry, 0x1c); + } + + /** + * RDOS does not support directories. + */ + public boolean isDirectory() { + return false; + } + + /** + * Retrieve the list of files in this directory. + * Since RDOS does not support directories, this will always return null. + */ + public List getFiles() { + return null; + } + + /** + * Identify if this file has been deleted. + */ + public boolean isDeleted() { + return AppleUtil.getUnsignedByte(fileEntry[0]) == 0x80; + } + + /** + * 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(getFiletype()); + numberFormat.setMinimumIntegerDigits(3); + list.add(numberFormat.format(getSizeInBlocks())); + list.add(getFilename()); + numberFormat.setMinimumIntegerDigits(1); + list.add(numberFormat.format(getSize())); + numberFormat.setMinimumIntegerDigits(3); + list.add(numberFormat.format(getStartingBlock())); + break; + case FormattedDisk.FILE_DISPLAY_DETAIL: + list.add(getFiletype()); + numberFormat.setMinimumIntegerDigits(3); + list.add(numberFormat.format(getSizeInBlocks())); + list.add(getFilename()); + numberFormat.setMinimumIntegerDigits(1); + list.add(numberFormat.format(getSize())); + numberFormat.setMinimumIntegerDigits(3); + list.add(numberFormat.format(getStartingBlock())); + list.add("$" + AppleUtil.getFormattedWord(getAddress())); + list.add(isDeleted() ? "Deleted" : ""); + break; + default: // FILE_DISPLAY_STANDARD + list.add(getFilename()); + list.add(getFiletype()); + list.add(numberFormat.format(getSize())); + list.add(isLocked() ? "Locked" : ""); + break; + } + return list; + } + + /** + * Get file data. This handles any operating-system specific issues. + * Currently, the disk itself handles this. + */ + public byte[] getFileData() { + byte[] rawdata = disk.getFileData(this); + byte[] filedata = new byte[getSize()]; + System.arraycopy(rawdata, 0, filedata, 0, filedata.length); + return filedata; + } + + /** + * 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() { + if (isApplesoftBasicFile()) { + return new ApplesoftFileFilter(); + } else if (isIntegerBasicFile()) { + return new IntegerBasicFileFilter(); + } else if (isTextFile()) { + return new TextFileFilter(); + } else if (isBinaryFile()) { + int size = getSize(); + // the minimum size is guessed a bit - I don't remember, but maybe there + // are 8 spare bytes at the end of the graphics screen + GraphicsFileFilter filter = new GraphicsFileFilter(); + if (size >= 8185 && size <= 8192) { + filter.setMode(GraphicsFileFilter.MODE_HGR_COLOR); + return filter; + } else if (size >= 16377 && size <= 16384) { + filter.setMode(GraphicsFileFilter.MODE_DHR_COLOR); + return filter; + } + // fall through to BinaryFileFilter... + } + return new BinaryFileFilter(); + } + + /** + * Determine if this is a text file. + */ + public boolean isTextFile() { + return "T".equals(getFiletype()); + } + + /** + * Determine if this is an Applesoft BASIC file. + */ + public boolean isApplesoftBasicFile() { + return "A".equals(getFiletype()); + } + + /** + * Determine if this is an Integer BASIC file. + */ + public boolean isIntegerBasicFile() { + return "I".equals(getFiletype()); + } + + /** + * Determine if this is a binary file. + */ + public boolean isBinaryFile() { + return "B".equals(getFiletype()); + } +} diff --git a/src/com/webcodepro/applecommander/storage/RdosFormatDisk.java b/src/com/webcodepro/applecommander/storage/RdosFormatDisk.java new file mode 100644 index 0000000..2db2572 --- /dev/null +++ b/src/com/webcodepro/applecommander/storage/RdosFormatDisk.java @@ -0,0 +1,328 @@ +/* + * 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; + +import java.util.ArrayList; +import java.util.BitSet; +import java.util.Iterator; +import java.util.List; + +/** + * Manages a disk that is in the RDOS format. + *

+ * Note that the RDOS block interleave is different than the standard DOS 3.3 format. + * Thus, when the image is made, the sectors are skewed differently - use readRdosBlock + * to read the appropriate block number. + *

+ * Also note that the operating system is itself the first file. Block #0 is really + * track 0, sector 0 - meaning that the first file should not (cannot) be deleted. + *

+ * RDOS appears to have been placed on 13 sector disks. This limits the number of blocks + * to 455. It also may also cause incompatibilities with other formats and other cracks. + *

+ * Date created: Oct 7, 2002 2:03:58 PM + * @author: Rob Greene + */ +public class RdosFormatDisk extends FormattedDisk { + /** + * Specifies the length of a file entry. + */ + public static final int ENTRY_LENGTH = 0x20; + /** + * Specifies the number of blocks on the disk. + * RDOS apparantly only worked on 5.25" disks. + */ + public static final int BLOCKS_ON_DISK = 455; + + /** + * Use this inner interface for managing the disk usage data. + * This offloads format-specific implementation to the implementing class. + * A BitSet is used to track all blocks, as RDOS disks do not have a + * bitmap stored on the disk. This is safe since we know the number of blocks + * that exist. (BitSet length is of last set bit - unset bits at the end are + * "lost".) + *

+ * Note one really unique point about RDOS - the entire disk is mapped out + * by the file entries. There are no blocks marked off, by default, by the + * operating system. However, the first file (RDOS itself) starts on block + * 0 (track 0, sector 0) and runs for 26 blocks - which covers all of track 0 + * (the operating system) and the 10 sectors used for file entries. + */ + private class RdosDiskUsage implements DiskUsage { + private int location = -1; + private BitSet bitmap = null; + public boolean hasNext() { + return location == -1 || location < BLOCKS_ON_DISK - 1; + } + public void next() { + if (bitmap == null) { + bitmap = new BitSet(BLOCKS_ON_DISK); + // mark all blocks as unused + for (int b=0; b + * Note that sectorSkew has the full 16 sectors, even though RDOS + * itself is a 13 sector format. + */ + public byte[] readRdosBlock(int block) { + int sectorSkew[] = { 0, 7, 0x0e, 6, 0x0d, 5, 0x0c, 4, + 0x0b, 3, 0x0a, 2, 9, 1, 8, 0x0f }; + int track = block / 13; + int sector = sectorSkew[block % 13]; + return readSector(track, sector); + } + + /** + * RDOS dos not support directories. + */ + public boolean canHaveDirectories() { + return false; + } + + /** + * RDOS really does not have a disk name. Fake one. + */ + public String getDiskName() { + byte[] block = readRdosBlock(4); + return AppleUtil.getString(block, 0xe0, 0x20); + } + + /** + * Retrieve a list of files. + */ + public List getFiles() { + List files = new ArrayList(); + for (int b=13; b<23; b++) { + byte[] data = readRdosBlock(b); + for (int i=0; i + * Date created: Nov 2, 2002 9:11:27 PM + * @author: Rob Greene + */ +public class TextFileFilter implements FileFilter { + /** + * Constructor for TextFileFilter. + */ + public TextFileFilter() { + super(); + } + + /** + * Process the given FileEntry and return a byte array with filtered data. + * @see com.webcodepro.applecommander.storage.FileFilter#filter(byte[]) + */ + public byte[] filter(FileEntry fileEntry) { + byte[] fileData = fileEntry.getFileData(); + byte[] workingData = new byte[fileData.length]; + int position = 0; + for (int i=0; i + * Date created: Oct 3, 2002 11:35:26 PM + * @author: Rob Greene + */ +public class DiskHelperTest extends TestCase { + + public DiskHelperTest(String name) { + super(name); + } + + public static void main(String[] args) { + junit.textui.TestRunner.run(DiskHelperTest.class); + } + + public void testLoadDos33() throws IOException { + FormattedDisk disk = showDirectory("C:/My Apple2/Disks/DOS 3.3.po"); + assertApplesoftFile(disk, "HELLO"); + assertIntegerFile(disk, "ANIMALS"); + assertTextFile(disk, "APPLE PROMS"); + assertBinaryFile(disk, "BOOT13"); + } + + public void testLoadMaster() throws IOException { + showDirectory("C:/My Apple2/Disks/MASTER.DSK"); + } + + public void testLoadGalacticAttack1() throws IOException { + showDirectory("C:/My Apple2/Disks/galatt.dsk"); + } + + public void testLoadProdos() throws IOException { + FormattedDisk disk = showDirectory("C:/My Apple2/Disks/Prodos.dsk"); + assertApplesoftFile(disk, "COPY.ME"); + assertBinaryFile(disk, "SETTINGS"); + assertBinaryFile(disk, "PRODOS"); + } + + public void testLoadMarbleMadness() throws IOException { + showDirectory("C:/My Apple2/Disks/Marble Madness (1985)(Electronic Arts).2mg"); + } + + public void testLoadHd1() throws IOException { + showDirectory("C:/My Apple2/ApplePC/hd1.hdv"); + } + + public void testRdosBoot() throws IOException { + showDirectory("C:/My Apple2/Disks/RDOSboot.dsk"); + } + + public void testSsiSave() throws IOException { + showDirectory("C:/My Apple2/Disks/SSIsave.dsk"); + } + + public void testPhan2d1() throws IOException { + FormattedDisk disk = showDirectory("C:/My Apple2/Disks/phan2d1.dsk"); + assertApplesoftFile(disk, "PHANTASIE II"); + assertBinaryFile(disk, "TWN21"); + assertTextFile(disk, "ITEM"); + assertGraphicsFile(disk, "ICE DRAGON"); + } + + public void testPhan2d2() throws IOException { + showDirectory("C:/My Apple2/Disks/phan2d2.dsk"); + } + + public void testPhantasie1() throws IOException { + showDirectory("C:/My Apple2/Disks/Phantasie1.dsk"); + } + + public void testPhantasie2() throws IOException { + showDirectory("C:/My Apple2/Disks/Phantasie2.dsk"); + } + + public void testCavernsOfFreitag() throws IOException { + FormattedDisk disk = showDirectory("C:/My Apple2/Disks/CavernsOfFreitag.dsk"); + assertGraphicsFile(disk, "TITLE.PIC"); + } + + protected FormattedDisk showDirectory(String imageName) throws IOException { + Disk disk = new Disk(imageName); + FormattedDisk formattedDisk = disk.getFormattedDisk(); + System.out.println(); + System.out.println(formattedDisk.getDiskName()); + List files = formattedDisk.getFiles(); + if (files != null) { + showFiles(files, ""); + } + System.out.println(formattedDisk.getFreeSpace() + " bytes free."); + System.out.println(formattedDisk.getUsedSpace() + " bytes used."); + System.out.println("This disk " + (formattedDisk.canHaveDirectories() ? "does" : "does not") + + " support directories."); + System.out.println("This disk is formatted in the " + formattedDisk.getFormat() + " format."); + System.out.println(); + + showDiskUsage(formattedDisk); + + return formattedDisk; + } + + protected void showFiles(List files, String indent) { + for (int i=0; i 0 && i % 80 == 0) System.out.println(); + usage.next(); + System.out.print(usage.isFree() ? "." : "U"); + i++; + } + System.out.println(); + } else { + for (int y=dimensions[0]-1; y>=0; y--) { + for (int x=0; x + * Date created: Nov 16, 2002 9:13:25 PM + * @author: Rob Greene + */ +public class AppleCommander { + public static final String VERSION = "1.1.1"; + /** + * Launch AppleCommander. + */ + public static void main(String[] args) { + if (args.length == 0) { + try { + Class.forName("org.eclipse.swt.SWT"); + SwtAppleCommander.main(args); + } catch (ClassNotFoundException ex) { + System.err.println("Sorry, the SWT libraries do not appear to be available (yet)."); + //SwingAppleCommander.main(args); + } + } else { + String[] extraArgs = new String[args.length - 1]; + System.arraycopy(args, 1, extraArgs, 0, extraArgs.length); + if ("-swt".equalsIgnoreCase(args[0])) { + SwtAppleCommander.main(extraArgs); + } else if ("-swing".equalsIgnoreCase(args[0])) { + System.err.println("Sorry, the Swing GUI is not available (yet)."); + //SwingAppleCommander.main(extraArgs); + } else if ("-command".equalsIgnoreCase(args[0])) { + System.err.println("Sorry, the command line user interface is not available (yet)."); + //CommandLineAppleCommander.main(extraArgs); + } else { + System.err.println("Unknown user interface specified!"); + System.err.println("Use -swt, -swing, or -command."); + } + } + } +} diff --git a/src/com/webcodepro/applecommander/ui/UserPreferences.java b/src/com/webcodepro/applecommander/ui/UserPreferences.java new file mode 100644 index 0000000..ea0256a --- /dev/null +++ b/src/com/webcodepro/applecommander/ui/UserPreferences.java @@ -0,0 +1,100 @@ +/* + * 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.ui; + +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.util.Properties; + +/** + * Provide a generalized and common mechanism to handle user preferences throughout + * all AppleCommander user interfaces. + *

+ * Date created: Nov 18, 2002 10:08:34 PM + * @author: Rob Greene + */ +public class UserPreferences { + private static final String FILENAME = "AppleCommander.preferences"; + private static final String IMAGE_DIRECTORY = "imageDirectory"; + private static final String EXPORT_DIRECTORY = "exportDirectory"; + private static UserPreferences instance; + private Properties properties = new Properties(); + /** + * Hide constructor from other classes. + */ + private UserPreferences() { + } + /** + * Get the singleton UserPreferences. + */ + public static UserPreferences getInstance() { + if (instance == null) { + instance = new UserPreferences(); + instance.load(); + } + return instance; + } + /** + * Initialize the user preferences from disk. + */ + private void load() { + try { + FileInputStream inputStream = new FileInputStream(FILENAME); + properties.load(inputStream); + inputStream.close(); + } catch (Exception ignored) { + } + } + /** + * Save the user preferences to disk. + */ + public void save() { + try { + FileOutputStream outputStream = new FileOutputStream(FILENAME); + properties.store(outputStream, "AppleCommander user preferences"); + outputStream.close(); + } catch (Exception ignored) { + } + } + /** + * Get the disk image directory. + */ + public String getDiskImageDirectory() { + return properties.getProperty(IMAGE_DIRECTORY); + } + /** + * Get the export directory. + */ + public String getExportDirectory() { + return properties.getProperty(EXPORT_DIRECTORY); + } + /** + * Set the disk image directory. + */ + public void setDiskImageDirectory(String diskImageDirectory) { + properties.setProperty(IMAGE_DIRECTORY, diskImageDirectory); + } + /** + * Set the export directory. + */ + public void setExportDirectory(String exportDirectory) { + properties.setProperty(EXPORT_DIRECTORY, exportDirectory); + } +} diff --git a/src/com/webcodepro/applecommander/ui/images/AppleCommanderLogo.gif b/src/com/webcodepro/applecommander/ui/images/AppleCommanderLogo.gif new file mode 100644 index 0000000000000000000000000000000000000000..e2b82837f1ce5852caa05a61733fcd6b2e4f8ce5 GIT binary patch literal 10669 zcmW+)c{tRM^MCK|dvC|O?vpMI@CrMUu3H z)34~U6HSX*0nch3+A5dd(EL?W%OeqLK!`}XbI`uaN14UtF}$L0YR zs{vXBpjALcC01(@kd=+9uO9@4ArFs|H4uWvfZ^dqW8)+OfsmQjqNNpESy>6J0?y7k zPywK*7!?sw1F?YB)o)M|(Azs>Y;0_s33YS~pE*;Xl+T>QMaxCmKWS5|hnwG2W)69iy7I(n_G|E2q9FTr3i7cVwo zFvohk2L}hIYHDgqN;;yVYQ~0V6crU^Wn~>49O~3Tol9F=1<&>0^G&MCD8X87?j_By<7+W6$K@jqR==Ac{)kP>1gf2pm z76h$9v9YnXtG53tZfIz5$b_J_C=d*lU6k$a=vW_HuUxBKCqPgpVSTI~D$!b8hsL^x zo7S5Cb)vGe7wU!}B5@srF!LA{l{4k55bGNYDgoBN&3CVL`}p{*O|?M{KI`3S*254K z1z|7|kQhNrOY8I0poK*eX%KQ=b%u6BGc*67aJas{0V-H)X&GFvtAkvC`StnG&`_w- z0hNsp#+Mv%7-Q82&O;fv9EqcE}>oYShV-RF)3>7GT?j2O@g?hWEzCquH>)W7J zm(`XIsNNY`-Mv1q=;BfWWtKp6qT!+;bn)2Qx3=!4VQ6g)Tm+#s5NLzW%s^W6{}82O zY|MfhptY7Z=vx}pa12_j$;oM1g;vM9r=X;y^?3m7fWUe1GxXUf2O9jRoB^PVQ@}so zYiyeaK}n#PSQJETn{R-i!^4YXs~|`xeFmmh!S0SJ_21zCP|`cC#0T%$b%^%l)>2Ca*uxGj9T=U&zGD@2<#6^DlFFLFuZ0oNJE>ep1sbh`#p zR%l%DRK~E#scb>iT=5fro737M9Obs=B5v7GzRG4VNN}pgk+--gwlwI>t+t9^t$=?ma?t-V(8h zk*XQzC*nV=ym%$7Zb5FfWq(>0EefjKY!$-2L+@3en-5TOjN6Nq)`)>NA1c4TdH)MH zg)oIeBaI^%WywMs8dHp2__jJ$dpvnK_SR!}WKoB!-h~}k;LWjn)8b_XQb8#c^O(6S zFPB;NoX4q18hv|Y;+vBT3H#escNvjvQYoI;Qlq7W>g%*9sn?l1F8s0%h(S0o_T|A; zW~=f~oU(&S96shycqvzj6@oJE>c?+LeWlOR zntvz-?lK0u-=+utK0=Is(98I6&+V-o3>gxm ztRcxdaCLKIv@dC#yv@NZLA5Y`DqQMefwo!XlePu1h)*gy+u6uYjvNk z6nA-FzlxvsHHWYCuYqR~`oa_^)i>8q`5bBa@IXx(VH>6PKFJ#n%q}G85O&{ajJ7Xo zHP;M9U6QNWEJOdUW25)?gnW$0u87YmzgO6sH`zAFfr$Fo`-0XzKapS2FVxt`S+k0d8rh8K42AC>=nD4jn5oLm%-;OEGvvP?W6v)W zc1r!O!0%il?A!g*K{eyakF1>@1=j}@E`i@SJM&H6fBcL&{oOPB)X3kFmer?l<;l%z zMbQMOeI29?yr49QUHYB)Tna8L<1EW+E!}=6z8F7ZPf9WqQ^N=Lm@b{$fk5=VSaX)XdHx<6RMO(&BdnK2aFQh2VIZk zT{T=df({x9*jvXV68jT^NZAy*C0*O|UE!L&r{PPuRGnyRZA-7~&oraC2t$2)G4RV+ zW+t($fQ*nyp}vfJ7iO2E*Lx}Vo>)eYceib}i9ySkjE*|mZL7D|K3b{36UE|3A?+uL zQeg!;u~?%r$|+(r0ik`F&!ofCh`WxnVyMl4TJDbX`4lzcW5Jz)_O!w~SHc!hjS1$bABIUEwhqDhKl(9S z7#W^o$}>(sXgJlnyAeq$U?D1PwmdJ1oY@JibJAo}J$&CgoxT2kES>5;Q{02T8dFhSV zl+sNKKb0-LD2s{Z1(kpN@lN*SRH&nOM=gS99G0oQ}pKR=_~Y)M#q zq|lkDFE(}m>>X^BSsT|EQ?QMn^(Cvz;N2OrlFAUnZu>#QFCLD{>c29j2|xMY%58-B z^nEZKnUm!CePhXf%|fv0=FP!OUrG15tuGY52MU4U=Y4jkpHvAG%`kH-I~I+vXWy9* z>)a3L_B}J`=KnMx+%utL#$|_hQTfXaW`7pHT6&>?O))#Pjp5g_m}mg4fsxZ7Bn6o#g?J~0lEkAJ z@%R`(mxdmqB{39}Pnjpvq1r@b6#dRzB36-oK@$Oa;+=Y8_Fu;u6|s|wn2ssd&JPmL zytcx+>~VLGNjM_y+rtSU$yM^;CC%_kL27w(DpxU$+k^4q0ZHSiT4GvVbK33Zw6dPm zYt8UH2EqPyqM|7ALUTg#`|S%g;9XH-axGh8+)A5^W^odJATH{A?71qJ^1C`+rJ1vF zF4cuDX~f7FUxJeX#7~+eIV(e0l<|{>O90aSmyq?ejM?VQ5968RzcP#j8J{tT9;!r_ z@CfHd^2+mM92)2;vJhkJ^J1`%r^KUTv(S%VcEWT^*T9(shQV2a&6rg?3V3WD`5#6) z-#jBwQFa;v5Ew$v)Dj#6tpqmS17+Op^Y!9vmD@b6n`9J&&8_oP_7pF?t1ywl;d67w0m~IWG1Wx7+^Tr zT3HjOx&IUipefzUUfrKTHOB6q$eku*v>#W&Xn8J80BA1F~SroA? zih4wa7-FHi1?EQzfyDp`OjK;l<@3KTHPQ%yOca4vTsl#Fhq_fvNIVR{@hM<1109}K zQunCj)(hYmQ>S>MxFHtPV@o*3EpEWVD7@%P%&QH>0iyhv-|w!LJ-Vu1S$qy6!~7o= zUz^Z>^t-fSqV)7DoZwOM@|jb|jxkmA+r> z8M{Eiwh_cm3SmEEul@Wj87#!rQgZe5%E^538^mkI1n^hvHEROWn1?(^*|V?sGSV0w z!b2L1B+)A{>`v4nF5(W47(&tB%17eE5T~hoE>UqIM3gc0n!*aqg$6b*#{?0?P~rcA zAhuKS=bs_;eXA6e5W!r`V_tL-9BD~GBh$F;Jh9V!r*o7FgFjVAxoDd-9S;K1aI(@Y z4s}Qf=1l0HXQ1Q$MTo1gCWu8AN}UlROsR;-KQ$+nFd+1(K-GufzyZ-zF`czaj73cDmySE3>(M4UMAZUD~JP&s7&&?_& zi2xzuA{Rv=8oP^dcoOo%sO7ONbRXfiE+5_%Ct3anTkVUAFCn-yVg3yGZsEUJ~WW&lbv3w<|LC!CC0q}J~y-clCfju6DoQc?Oe7%xup+aH`E0TCjq zRyRTU^MI+AJIiqh8yXk~+}j1Y;`|uM0D{DO-`f%bm@f0S8n8!NfHI?{VPdmZlFOsx z2wSPBSHy-zrCSnwxI7QV51(*Z^T0kk(j=fpJfYX%Im&PzP$+y%iZw-y$f*2B4CYu~m z;^{P$oBkGe24d&{Oi9=55)*FCfCsHM1>@0_^8`C$<@=sXaK`xz?n5*QkQbpyBD9Mu zE|@G{`1m232D2xiiq122x!6;Un!%#W^ghngL)fASR8a=P8K?kqOS&@3hET79LbWPC zp7J5IH!;D&U5^@WJ%Shn+mx0)VXy+R0}B(y6FPDgr+avu5D@ z9W#II;-du!2oNXzM�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 literal 0 HcmV?d00001 diff --git a/src/com/webcodepro/applecommander/ui/images/ExportWizardLogo.gif b/src/com/webcodepro/applecommander/ui/images/ExportWizardLogo.gif new file mode 100644 index 0000000000000000000000000000000000000000..3e520272c5740dc205fb9e99174c48d694703eaf GIT binary patch literal 13334 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 L-gSDjFjxZsE&f1l literal 0 HcmV?d00001 diff --git a/src/com/webcodepro/applecommander/ui/images/appleicon.gif b/src/com/webcodepro/applecommander/ui/images/appleicon.gif new file mode 100644 index 0000000000000000000000000000000000000000..4c9a77a25333d7eefc6de161278e54082c18ac4b GIT binary patch literal 887 zcmZ?wbhEHb6krfw_|Cw<@{5J#*DnwOrhrU_Q7{?;BO?U<|NpP}lZBCi;Xi{8$j6{O z;lL5X@Sju0LV)35Ba4t$%#05Y>=^` literal 0 HcmV?d00001 diff --git a/src/com/webcodepro/applecommander/ui/images/deletedfiles.gif b/src/com/webcodepro/applecommander/ui/images/deletedfiles.gif new file mode 100644 index 0000000000000000000000000000000000000000..7fd24534f612a5778787f03eeb370771886eb62e GIT binary patch literal 915 zcmb`GO-q$=5XGkuO9&E?77?OGkk%p?ZWL_-EfZTr5Y#Rq#+JDcx&?s{^a4cX7=*A^Yeo;D7#Y8ZJ%y?bji9E zy&cqG0e7Zwyf!xJBI z8Ux@(J4T}xEAa!GOk)R}5R5`t0|sd(p#={XgAH`niK?42IgwlpBhklNvS=?^`P%MX z#dgB7xm~4to6+TZ^R7ml9d(4Ju#g(*x=G_vzzv$$fh%Dinpicq*SD`;zSH$q*MC9| z-jf_Sc4htD=LaW;Muy*<8S5;pA6*??+JB^T^ZWZ-_wO!_Pc7_>Twm^A{5|#T(8n9o zD?fg1Jvz0v@_gdwiK{+&eDmdij(>Y~c4Bkq>(Hg`hZmpxncmy@F!y?L{_oq_&Zq4c UhdWR2Y+YOZ^7!_F{yw?z4-Tb_aR2}S literal 0 HcmV?d00001 diff --git a/src/com/webcodepro/applecommander/ui/images/deletefile.gif b/src/com/webcodepro/applecommander/ui/images/deletefile.gif new file mode 100644 index 0000000000000000000000000000000000000000..6f647666d3246d97b6e5471736ad102ab4fe2d9e GIT binary patch literal 143 zcmZ?wbhEHb6krfw*v!E2pMjwPNZQ-qx3}MKZ$BT%m_Psi{{8>|17)xP#h)yU3=A9$ zIv_ca84N5o0w+CJ@3n{*?ylU1t(AAR;DlBM~ZSEi-q53?|FTE@ea0@ Z#!u6n*|vHWalV)VF1^`wJD^>si literal 0 HcmV?d00001 diff --git a/src/com/webcodepro/applecommander/ui/images/detailfileview.gif b/src/com/webcodepro/applecommander/ui/images/detailfileview.gif new file mode 100644 index 0000000000000000000000000000000000000000..f84852b27263bea2aef9ee25db2de1929da28c1a GIT binary patch literal 916 zcmZ?wbhEHb6krfw_|Cw<0tCN+=og3pQY}CX;xUYZ(GVE!A@KkIf5o3Hj0_C_8FWB? z1?33`jzWh2oH7;$3*L4c8Ef`y=xBNGcl!j%<*%5LpUx=nx57c609 zQ1@P>qgl}4&7jYIpv3aP!iL#Q`Hy5IFDabwU}6lg$uN3)YAPFx5zB;t6^9y4QqRqa Z{G4>GH{4c8p&~dVd@fVPCms$4YXH$4J|6%8 literal 0 HcmV?d00001 diff --git a/src/com/webcodepro/applecommander/ui/images/diskicon.gif b/src/com/webcodepro/applecommander/ui/images/diskicon.gif new file mode 100644 index 0000000000000000000000000000000000000000..a6978e3480905dbb8ed2f5956312cc1c4ed2cd5e GIT binary patch literal 863 zcmZ?wbhEHb6krfw_|Cv!Zy(Rl&=5a={`~#>|NoD_e}De}`}X_i$N#@Se}26E{&@R% zd;9#gW zSa5)YLr}9_}IBe#xY9fh2f)P-70})e=0VmuuK%@ LJ*6Tcz+epk`g2yEz;O6pnTaNG6^6p`X;yEvDHv$D%0Nt&Jm? iOT^*A68A<4X}v2NmmYe~RLPxH6H%1zKbet{!5RRclqpOA literal 0 HcmV?d00001 diff --git a/src/com/webcodepro/applecommander/ui/images/importfile.gif b/src/com/webcodepro/applecommander/ui/images/importfile.gif new file mode 100644 index 0000000000000000000000000000000000000000..7db9b64e615945ed0f1542b1cbfa833634258048 GIT binary patch literal 879 zcmZ?wbhEHb6krfw_|Cw__*j`D-%o7mKPfnJ$p1n lja)hc9=lCcjV)W^5%l;(hn{cPm6=89=O^3Cv#~H(0|2JTE*$^> literal 0 HcmV?d00001 diff --git a/src/com/webcodepro/applecommander/ui/images/nativefileview.gif b/src/com/webcodepro/applecommander/ui/images/nativefileview.gif new file mode 100644 index 0000000000000000000000000000000000000000..4c9a77a25333d7eefc6de161278e54082c18ac4b GIT binary patch literal 887 zcmZ?wbhEHb6krfw_|Cw<@{5J#*DnwOrhrU_Q7{?;BO?U<|NpP}lZBCi;Xi{8$j6{O z;lL5X@Sju0LV)35Ba4t$%#05Y>=^` literal 0 HcmV?d00001 diff --git a/src/com/webcodepro/applecommander/ui/images/newdisk.gif b/src/com/webcodepro/applecommander/ui/images/newdisk.gif new file mode 100644 index 0000000000000000000000000000000000000000..d670c0dd80711e58476bd0fbc16b431458a6e423 GIT binary patch literal 896 zcmZ?wbhEHb6krfw_|Cv!Zy(Rl&=5a={`~#>|NoD_e}De}`}X_i$N#@Se}26E{&@R% zd;9}~J&o5(@00!Q!RU$7xEj_cGhlz>78USeqNpS!G literal 0 HcmV?d00001 diff --git a/src/com/webcodepro/applecommander/ui/images/opendisk.gif b/src/com/webcodepro/applecommander/ui/images/opendisk.gif new file mode 100644 index 0000000000000000000000000000000000000000..0429f57b460e7354a2552a07c78fce1f28dfc6af GIT binary patch literal 907 zcmZ?wbhEHb6krfw_|Cx4(7-T%zWx9I^W)>~=g+Udzu$g8kb57(4S``D z0*XIb7#SEi8FWCt1my__jtmB24jGRP2}}%3A{sIWHY_~M$SEKb;IUE3k%5^fGw;p03Bc#KdEBf=9PmzSbU#jVcXYjBHFVRth&-RQW+YJT7x#sfF6@Z4lh@=`AHs literal 0 HcmV?d00001 diff --git a/src/com/webcodepro/applecommander/ui/images/standardfileview.gif b/src/com/webcodepro/applecommander/ui/images/standardfileview.gif new file mode 100644 index 0000000000000000000000000000000000000000..0c1a6988d2b60abd1b73366859d5a834f81298a3 GIT binary patch literal 878 zcmZ?wbhEHb6krfw_|CwC$|V~e;T(~2E}iw<KU^X@@6h+MoNfqVW02`Tv=i7_0$NR4twW literal 0 HcmV?d00001 diff --git a/src/com/webcodepro/applecommander/ui/swt/AppleWorksWordProcessorPane.java b/src/com/webcodepro/applecommander/ui/swt/AppleWorksWordProcessorPane.java new file mode 100644 index 0000000..f8b5f8c --- /dev/null +++ b/src/com/webcodepro/applecommander/ui/swt/AppleWorksWordProcessorPane.java @@ -0,0 +1,112 @@ +/* + * 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.ui.swt; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.layout.RowLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Label; + +import com.webcodepro.applecommander.storage.AppleWorksWordProcessorFileFilter; + +/** + * Choose format for AppleWorks Word Processor export. + *

+ * Date created: Nov 15, 2002 11:31:15 PM + * @author: Rob Greene + */ +public class AppleWorksWordProcessorPane extends WizardPane { + private Composite parent; + private Object layoutData; + private Composite control; + private ExportWizard wizard; + /** + * Constructor for AppleWorksWordProcessorPane. + */ + public AppleWorksWordProcessorPane(Composite parent, ExportWizard exportWizard, Object layoutData) { + super(); + this.parent = parent; + this.wizard = exportWizard; + this.layoutData = layoutData; + } + /** + * Get the next WizardPane. + * @see com.webcodepro.applecommander.gui.WizardPane#getNextPane() + */ + public WizardPane getNextPane() { + return new ExportFileDestinationPane(parent, wizard, layoutData); + } + /** + * Create and display the wizard pane. + * @see com.webcodepro.applecommander.gui.WizardPane#open() + */ + public void open() { + wizard.enableFinishButton(false); + wizard.enableNextButton(true); + control = new Composite(parent, SWT.NULL); + control.setLayoutData(layoutData); + RowLayout layout = new RowLayout(SWT.VERTICAL); + layout.justify = true; + layout.marginBottom = 5; + layout.marginLeft = 5; + layout.marginRight = 5; + layout.marginTop = 5; + layout.spacing = 3; + control.setLayout(layout); + Label label = new Label(control, SWT.WRAP); + label.setText("Please choose the appropriate format:"); + RowLayout subpanelLayout = new RowLayout(SWT.VERTICAL); + subpanelLayout.justify = true; + subpanelLayout.spacing = 3; + Button button = new Button(control, SWT.RADIO); + button.setText("Text"); + button.setSelection(getFilter().isTextRendering()); + button.addSelectionListener(new SelectionAdapter() { + public void widgetSelected(SelectionEvent e) { + getFilter().setRendering(AppleWorksWordProcessorFileFilter.RENDER_AS_TEXT); + } + }); + button = new Button(control, SWT.RADIO); + button.setText("HTML"); + button.setSelection(getFilter().isHtmlRendering()); + button.addSelectionListener(new SelectionAdapter() { + public void widgetSelected(SelectionEvent e) { + getFilter().setRendering(AppleWorksWordProcessorFileFilter.RENDER_AS_HTML); + } + }); + } + /** + * Dispose of any resources. + * @see com.webcodepro.applecommander.gui.WizardPane#dispose() + */ + public void dispose() { + control.dispose(); + control = null; + } + /** + * Get the AppleWorks word processor filter. + */ + protected AppleWorksWordProcessorFileFilter getFilter() { + return (AppleWorksWordProcessorFileFilter) wizard.getFileFilter(); + } +} diff --git a/src/com/webcodepro/applecommander/ui/swt/DiskExplorerTab.java b/src/com/webcodepro/applecommander/ui/swt/DiskExplorerTab.java new file mode 100644 index 0000000..39d4d0c --- /dev/null +++ b/src/com/webcodepro/applecommander/ui/swt/DiskExplorerTab.java @@ -0,0 +1,836 @@ +/* + * 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.ui.swt; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.OutputStream; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import com.webcodepro.applecommander.storage.AppleWorksWordProcessorFileFilter; +import com.webcodepro.applecommander.storage.ApplesoftFileFilter; +import com.webcodepro.applecommander.storage.BinaryFileFilter; +import com.webcodepro.applecommander.storage.FileEntry; +import com.webcodepro.applecommander.storage.FileEntryComparator; +import com.webcodepro.applecommander.storage.FileFilter; +import com.webcodepro.applecommander.storage.FormattedDisk; +import com.webcodepro.applecommander.storage.GraphicsFileFilter; +import com.webcodepro.applecommander.storage.IntegerBasicFileFilter; +import com.webcodepro.applecommander.storage.TextFileFilter; +import com.webcodepro.applecommander.storage.FormattedDisk.FileColumnHeader; +import com.webcodepro.applecommander.ui.UserPreferences; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.CTabFolder; +import org.eclipse.swt.custom.CTabItem; +import org.eclipse.swt.custom.SashForm; +import org.eclipse.swt.events.MenuAdapter; +import org.eclipse.swt.events.MenuEvent; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.FileDialog; +import org.eclipse.swt.widgets.Menu; +import org.eclipse.swt.widgets.MenuItem; +import org.eclipse.swt.widgets.MessageBox; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.TableColumn; +import org.eclipse.swt.widgets.TableItem; +import org.eclipse.swt.widgets.ToolBar; +import org.eclipse.swt.widgets.ToolItem; +import org.eclipse.swt.widgets.Tree; +import org.eclipse.swt.widgets.TreeItem; + +/** + * Build the Disk File tab for the Disk Window. + *

+ * Date created: Nov 17, 2002 9:46:53 PM + * @author: Rob Greene + */ +public class DiskExplorerTab { + private Shell shell; + private SashForm sashForm; + private Tree directoryTree; + private Table fileTable; + private ToolBar toolBar; + private ToolItem exportToolItem; + private ToolItem importToolItem; + private ToolItem deleteToolItem; + private ImageManager imageManager; + + private UserPreferences userPreferences = UserPreferences.getInstance(); + private FormattedDisk disk; + private FileFilter fileFilter; + private GraphicsFileFilter graphicsFilter = new GraphicsFileFilter(); + private AppleWorksWordProcessorFileFilter awpFilter = new AppleWorksWordProcessorFileFilter(); + + private int currentFormat = FormattedDisk.FILE_DISPLAY_STANDARD; + private boolean formatChanged; + private List currentFileList; + private Map columnWidths = new HashMap(); + private boolean showDeletedFiles; + /** + * Create the DISK INFO tab. + */ + public DiskExplorerTab(CTabFolder tabFolder, FormattedDisk disk, ImageManager imageManager) { + this.disk = disk; + this.shell = tabFolder.getShell(); + this.imageManager = imageManager; + + createFilesTab(tabFolder); + } + /** + * Dispose of resources. + */ + public void dispose() { + sashForm.dispose(); + directoryTree.dispose(); + fileTable.dispose(); + exportToolItem.dispose(); + importToolItem.dispose(); + deleteToolItem.dispose(); + toolBar.dispose(); + + directoryTree = null; + fileTable = null; + currentFileList = null; + } + /** + * Create the FILES tab. + */ + protected void createFilesTab(CTabFolder tabFolder) { + CTabItem ctabitem = new CTabItem(tabFolder, SWT.NULL); + ctabitem.setText("Files"); + + Composite composite = new Composite(tabFolder, SWT.NULL); + ctabitem.setControl(composite); + GridLayout gridLayout = new GridLayout(1, false); + composite.setLayout(gridLayout); + + GridData gridData = new GridData(GridData.FILL_HORIZONTAL); + createFileToolBar(composite, gridData); + + sashForm = new SashForm(composite, SWT.NONE); + sashForm.setOrientation(SWT.HORIZONTAL); + gridData = new GridData(GridData.FILL_BOTH); + gridData.horizontalSpan = 2; + sashForm.setLayoutData(gridData); + + directoryTree = new Tree(sashForm, SWT.SINGLE | SWT.BORDER); + directoryTree.setMenu(createDirectoryPopupMenu()); + directoryTree.addSelectionListener(new SelectionListener() { + /** + * Single-click handler. + */ + public void widgetSelected(SelectionEvent event) { + changeCurrentFormat(currentFormat); // minor hack + } + /** + * Double-click handler. + */ + public void widgetDefaultSelected(SelectionEvent event) { + Tree item = (Tree) event.getSource(); + TreeItem[] treeItem = item.getSelection(); + treeItem[0].setExpanded(!treeItem[0].getExpanded()); + } + }); + + fileTable = new Table(sashForm, SWT.MULTI | SWT.FULL_SELECTION | SWT.BORDER); + fileTable.setHeaderVisible(true); + + sashForm.setWeights(new int[] {1,2}); + + TreeItem diskItem = new TreeItem(directoryTree, SWT.BORDER); + diskItem.setText(disk.getDiskName()); + diskItem.setData(disk); + directoryTree.setSelection(new TreeItem[] { diskItem }); + + if (disk.canHaveDirectories()) { + Iterator files = disk.getFiles().iterator(); + while (files.hasNext()) { + FileEntry entry = (FileEntry) files.next(); + if (entry.isDirectory()) { + TreeItem item = new TreeItem(diskItem, SWT.BORDER); + item.setText(entry.getFilename()); + item.setData(entry); + addDirectoriesToTree(item, entry); + } + } + } + + computeColumnWidths(FormattedDisk.FILE_DISPLAY_STANDARD); + computeColumnWidths(FormattedDisk.FILE_DISPLAY_NATIVE); + computeColumnWidths(FormattedDisk.FILE_DISPLAY_DETAIL); + + formatChanged = true; + fillFileTable(disk.getFiles()); + } + /** + * Construct the popup menu for the directory table on the File tab. + */ + protected Menu createDirectoryPopupMenu() { + Menu menu = new Menu(shell, SWT.POP_UP); + + MenuItem item = new MenuItem(menu, SWT.CASCADE); + item.setText("Expand"); + item.addSelectionListener(new SelectionAdapter() { + public void widgetSelected(SelectionEvent event) { + TreeItem[] treeItem = directoryTree.getSelection(); + treeItem[0].setExpanded(true); + } + }); + item.setEnabled(disk.canHaveDirectories()); + + item = new MenuItem(menu, SWT.CASCADE); + item.setText("Collapse"); + item.addSelectionListener(new SelectionAdapter() { + public void widgetSelected(SelectionEvent event) { + TreeItem[] treeItem = directoryTree.getSelection(); + treeItem[0].setExpanded(false); + } + }); + item.setEnabled(disk.canHaveDirectories()); + + item = new MenuItem(menu, SWT.SEPARATOR); + + item = new MenuItem(menu, SWT.CASCADE); + item.setText("Expand All"); + item.addSelectionListener(new SelectionAdapter() { + public void widgetSelected(SelectionEvent event) { + TreeItem[] treeItem = directoryTree.getSelection(); + setDirectoryExpandedStates(treeItem[0], true); + } + }); + item.setEnabled(disk.canHaveDirectories()); + + item = new MenuItem(menu, SWT.CASCADE); + item.setText("Collapse All"); + item.addSelectionListener(new SelectionAdapter() { + public void widgetSelected(SelectionEvent event) { + TreeItem[] treeItem = directoryTree.getSelection(); + setDirectoryExpandedStates(treeItem[0], false); + } + }); + item.setEnabled(disk.canHaveDirectories()); + + return menu; + } + /** + * Construct the popup menu for the file table on the File tab. + */ + protected Menu createFilePopupMenu() { + Menu menu = new Menu(shell, SWT.POP_UP); + + MenuItem item = new MenuItem(menu, SWT.CASCADE); + item.setText("Import..."); + item.setEnabled(disk.canCreateFile() && disk.canWriteFileData()); + item.addSelectionListener(new SelectionAdapter() { + public void widgetSelected(SelectionEvent event) { + // FIXME + } + }); + + item = new MenuItem(menu, SWT.CASCADE); + item.setText("Export"); + item.setEnabled(disk.canReadFileData()); + item.setMenu(createFileExportMenu(SWT.DROP_DOWN)); + + item = new MenuItem(menu, SWT.SEPARATOR); + + item = new MenuItem(menu, SWT.CASCADE); + item.setText("Delete..."); + item.setEnabled(disk.canDeleteFile()); + item.addSelectionListener(new SelectionAdapter() { + public void widgetSelected(SelectionEvent event) { + // FIXME + } + }); + + return menu; + } + /** + * Construct the popup menu for the export button on the toolbar. + */ + protected Menu createFileExportMenu(int style) { + Menu menu = new Menu(shell, style); + + MenuItem item = new MenuItem(menu, SWT.NONE); + item.setText("Raw disk data..."); + item.addSelectionListener(new SelectionAdapter() { + public void widgetSelected(SelectionEvent event) { + fileFilter = null; + exportFile(null); + } + }); + + item = new MenuItem(menu, SWT.NONE); + item.setText("Binary..."); + item.addSelectionListener(new SelectionAdapter() { + public void widgetSelected(SelectionEvent event) { + fileFilter = new BinaryFileFilter(); + exportFile(null); + } + }); + + item = new MenuItem(menu, SWT.NONE); + item.setText("Applesoft Basic..."); + item.addSelectionListener(new SelectionAdapter() { + public void widgetSelected(SelectionEvent event) { + fileFilter = new ApplesoftFileFilter(); + exportFile(null); + } + }); + + item = new MenuItem(menu, SWT.NONE); + item.setText("Integer Basic..."); + item.addSelectionListener(new SelectionAdapter() { + public void widgetSelected(SelectionEvent event) { + fileFilter = new IntegerBasicFileFilter(); + exportFile(null); + } + }); + + item = new MenuItem(menu, SWT.NONE); + item.setText("ASCII Text..."); + item.addSelectionListener(new SelectionAdapter() { + public void widgetSelected(SelectionEvent event) { + fileFilter = new TextFileFilter(); + exportFile(null); + } + }); + + item = new MenuItem(menu, SWT.SEPARATOR); + + item = new MenuItem(menu, SWT.NONE); + item.setText("AppleWorks WordProcessor File..."); + item.addSelectionListener(new SelectionAdapter() { + public void widgetSelected(SelectionEvent event) { + fileFilter = awpFilter; + exportFile(null); + } + }); + item = new MenuItem(menu, SWT.CASCADE); + item.setText("Rendering"); + Menu subMenu = new Menu(shell, SWT.DROP_DOWN); + item.setMenu(subMenu); + 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(awpFilter.isTextRendering()); + subItems[1].setSelection(awpFilter.isHtmlRendering()); + } + }); + item = new MenuItem(subMenu, SWT.RADIO); + item.setText("Text"); + item.addSelectionListener(new SelectionAdapter() { + /** + * Set the appropriate rendering style. + */ + public void widgetSelected(SelectionEvent event) { + awpFilter.setRendering(AppleWorksWordProcessorFileFilter.RENDER_AS_TEXT); + } + }); + item = new MenuItem(subMenu, SWT.RADIO); + item.setText("HTML"); + item.addSelectionListener(new SelectionAdapter() { + /** + * Set the appropriate rendering style. + */ + public void widgetSelected(SelectionEvent event) { + awpFilter.setRendering(AppleWorksWordProcessorFileFilter.RENDER_AS_HTML); + } + }); + + item = new MenuItem(menu, SWT.SEPARATOR); + + item = new MenuItem(menu, SWT.NONE); + item.setText("Graphics..."); + item.setEnabled(graphicsFilter.isCodecAvailable()); + item.addSelectionListener(new SelectionAdapter() { + public void widgetSelected(SelectionEvent event) { + fileFilter = graphicsFilter; + exportFile(null); + } + }); + + // Add graphics mode + item = new MenuItem(menu, SWT.CASCADE); + item.setText("Mode"); + item.setEnabled(graphicsFilter.isCodecAvailable()); + subMenu = new Menu(shell, SWT.DROP_DOWN); + item.setMenu(subMenu); + 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(graphicsFilter.isHiresBlackAndWhiteMode()); + subItems[1].setSelection(graphicsFilter.isHiresColorMode()); + subItems[2].setSelection(graphicsFilter.isDoubleHiresBlackAndWhiteMode()); + subItems[3].setSelection(graphicsFilter.isDoubleHiresColorMode()); + } + }); + item = new MenuItem(subMenu, SWT.RADIO); + item.setText("Hi-Res B&W"); + item.addSelectionListener(new SelectionAdapter() { + /** + * Set the appropriate graphics mode. + */ + public void widgetSelected(SelectionEvent event) { + graphicsFilter.setMode(GraphicsFileFilter.MODE_HGR_BLACK_AND_WHITE); + } + }); + item = new MenuItem(subMenu, SWT.RADIO); + item.setText("Hi-Res Color"); + item.addSelectionListener(new SelectionAdapter() { + /** + * Set the appropriate graphics mode. + */ + public void widgetSelected(SelectionEvent event) { + graphicsFilter.setMode(GraphicsFileFilter.MODE_HGR_COLOR); + } + }); + item = new MenuItem(subMenu, SWT.RADIO); + item.setText("Double Hi-Res B&W"); + item.addSelectionListener(new SelectionAdapter() { + /** + * Set the appropriate graphics mode. + */ + public void widgetSelected(SelectionEvent event) { + graphicsFilter.setMode(GraphicsFileFilter.MODE_DHR_BLACK_AND_WHITE); + } + }); + item = new MenuItem(subMenu, SWT.RADIO); + item.setText("Double Hi-Res COLOR"); + item.addSelectionListener(new SelectionAdapter() { + /** + * Set the appropriate graphics mode. + */ + public void widgetSelected(SelectionEvent event) { + graphicsFilter.setMode(GraphicsFileFilter.MODE_DHR_COLOR); + } + }); + + // Add graphics formats, if any are defined. + String[] formats = graphicsFilter.getFileExtensions(); + if (formats != null && formats.length > 0) { + item = new MenuItem(menu, SWT.CASCADE); + item.setText("Format"); + item.setEnabled(graphicsFilter.isCodecAvailable()); + subMenu = new Menu(shell, SWT.DROP_DOWN); + item.setMenu(subMenu); + 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(); + for (int i=0; i= header.getMaximumWidth()) { + headerWidths[i] = gc.stringExtent(header.getTitle()).x + gc.stringExtent("WW").x; + } else { + headerWidths[i] = gc.stringExtent("W").x * header.getMaximumWidth(); + } + } + gc.dispose(); + gc = null; + columnWidths.put(new Integer(format), headerWidths); + } + /** + * Preserve the column widths. + */ + protected void preserveColumnWidths() { + TableColumn[] columns = fileTable.getColumns(); + int[] widths = new int[columns.length]; + for (int i=0; i 0) { + exportToolItem.setEnabled(disk.canReadFileData()); + importToolItem.setEnabled(disk.canCreateFile() && disk.canWriteFileData()); + deleteToolItem.setEnabled(disk.canDeleteFile()); + } else { + exportToolItem.setEnabled(false); + importToolItem.setEnabled(false); + deleteToolItem.setEnabled(false); + } + } + /** + * Double-click handler. + */ + public void widgetDefaultSelected(SelectionEvent event) { + // No action defined at this time + } + }); + TableColumn column = null; + List headers = disk.getFileColumnHeaders(currentFormat); + int[] widths = (int[])columnWidths.get(new Integer(currentFormat)); + for (int i=0; i + * Date created: Nov 17, 2002 9:15:29 PM + * @author: Rob Greene + */ +public class DiskInfoTab { + /** + * Create the DISK INFO tab. + */ + public DiskInfoTab(CTabFolder tabFolder, FormattedDisk disk) { + CTabItem ctabitem = new CTabItem(tabFolder, SWT.NULL); + ctabitem.setText("Disk Info"); + + Table table = new Table(tabFolder, SWT.FULL_SELECTION); + ctabitem.setControl(table); + table.setHeaderVisible(true); + TableColumn column = new TableColumn(table, SWT.LEFT); + column.setResizable(true); + column.setText("Label"); + column.setWidth(200); + column = new TableColumn(table, SWT.LEFT); + column.setResizable(true); + column.setText("Value"); + column.setWidth(400); + + Iterator iterator = disk.getDiskInformation().iterator(); + TableItem item = null; + while (iterator.hasNext()) { + DiskInformation diskinfo = (DiskInformation) iterator.next(); + item = new TableItem(table, SWT.NULL); + item.setText(new String[] { diskinfo.getLabel(), diskinfo.getValue() }); + } + + } + /** + * Dispose of resources. + */ + public void dispose() { + } +} diff --git a/src/com/webcodepro/applecommander/ui/swt/DiskMapTab.java b/src/com/webcodepro/applecommander/ui/swt/DiskMapTab.java new file mode 100644 index 0000000..22a4ee4 --- /dev/null +++ b/src/com/webcodepro/applecommander/ui/swt/DiskMapTab.java @@ -0,0 +1,351 @@ +/* + * 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.ui.swt; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.CTabFolder; +import org.eclipse.swt.custom.CTabItem; +import org.eclipse.swt.events.PaintEvent; +import org.eclipse.swt.events.PaintListener; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Canvas; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Label; + +import com.webcodepro.applecommander.storage.FormattedDisk; +import com.webcodepro.applecommander.storage.FormattedDisk.DiskUsage; + +/** + * Build the Disk Map tab for the Disk Window. + *

+ * Date created: Nov 17, 2002 9:27:44 PM + * @author: Rob Greene + */ +public class DiskMapTab { + private FormattedDisk disk; + // used locally - not shared between windows; hopefully will + // not be a resource drain! + private Color freeFill; + private Color usedFill; + private Color black; + private Color gray; + /** + * Construct the DiskMapTab. + */ + public DiskMapTab(CTabFolder tabFolder, FormattedDisk disk) { + this.disk = disk; + + // these items are reused; need to dispose of them when done! + freeFill = new Color(tabFolder.getDisplay(), 100,200,100); + usedFill = new Color(tabFolder.getDisplay(), 200,100,100); + black = new Color(tabFolder.getDisplay(), 0,0,0); + gray = new Color(tabFolder.getDisplay(), 50,50,50); + + createDiskMapTab(tabFolder); + } + /** + * Create the DISK MAP tab. + */ + protected void createDiskMapTab(CTabFolder tabFolder) { + CTabItem item = new CTabItem(tabFolder, SWT.NULL); + item.setText("Disk Map"); + + Canvas canvas = new Canvas(tabFolder, SWT.NULL); + GridLayout grid = new GridLayout(2, false); + canvas.setLayout(grid); + item.setControl(canvas); + + String[] labels = disk.getBitmapLabels(); + + // ROW #1 - title + GridData data = new GridData(GridData.FILL_HORIZONTAL); + data.horizontalSpan = 2; + data.heightHint = 20; + Label title = new Label(canvas, SWT.LEFT); + StringBuffer buf = new StringBuffer(); + if (labels.length == 1) { + buf.append("This disk is organized by the "); + buf.append(labels[0].toLowerCase()); + buf.append(". Therefore, no organization has been forced on the "); + buf.append(labels[0].toLowerCase()); + buf.append(" layout."); + } else { + buf.append("This disk is organized by "); + for (int i=0; i 0) buf.append(" and "); + buf.append(labels[i].toLowerCase()); + } + buf.append(", and this implies a rigid layout to the disk. "); + buf.append("This will be reflected in the disk map."); + } + title.setText(buf.toString()); + title.setLayoutData(data); + + // ROW #2 - blank and horizontal ruler + data = new GridData(); + data.heightHint = 20; + data.widthHint = 20; + Composite blank = new Composite(canvas, SWT.NULL); + blank.setLayoutData(data); + data = new GridData(GridData.HORIZONTAL_ALIGN_FILL); + data.heightHint = 20; + Canvas ruler = new Canvas(canvas, SWT.NULL); + ruler.addPaintListener(new PaintListener() { + public void paintControl(PaintEvent event) { + paintHorizontalRuler(event); + } + }); + ruler.setLayoutData(data); + + // ROW #3 - vertical ruler and map + data = new GridData(GridData.VERTICAL_ALIGN_FILL); + data.widthHint = 20; + ruler = new Canvas(canvas, SWT.NULL); + ruler.addPaintListener(new PaintListener() { + public void paintControl(PaintEvent event) { + paintVerticalRuler(event); + } + }); + ruler.setLayoutData(data); + data = new GridData(GridData.FILL_BOTH); + data.grabExcessHorizontalSpace = true; + data.grabExcessVerticalSpace = true; + Canvas map = new Canvas(canvas, SWT.BORDER); + map.addPaintListener(new PaintListener() { + public void paintControl(PaintEvent event) { + paintMap(event); + } + }); + map.setLayoutData(data); + + // ROW #5 + data = new GridData(GridData.FILL_HORIZONTAL); + data.heightHint = 20; + data.horizontalSpan = 2; + Canvas legend = new Canvas(canvas, SWT.NULL); + legend.addPaintListener(new PaintListener() { + public void paintControl(PaintEvent event) { + paintLegend(event); + } + }); + legend.setLayoutData(data); + } + /** + * Dispose of resources. + */ + public void dispose() { + freeFill.dispose(); + usedFill.dispose(); + black.dispose(); + gray.dispose(); + } + /** + * Handle paint requests for horizontal ruler. + */ + private void paintHorizontalRuler(PaintEvent event) { + String label = (disk.getBitmapLabels()[0] + "s").toUpperCase(); + Canvas canvas = (Canvas) event.widget; + Rectangle area = canvas.getClientArea(); + event.gc.drawLine(area.x, area.y + area.height/2, area.x + area.width, area.y + area.height/2); + Point size = event.gc.textExtent(label); + event.gc.drawString(label, area.x + area.width/2 - size.x, area.y + area.height/2 - size.y/2); + } + /** + * Handle paint requests for vertical ruler. + */ + private void paintVerticalRuler(PaintEvent event) { + String label = (disk.getBitmapLabels()[0] + "s").toUpperCase(); + if (disk.getBitmapLabels().length == 2) { + label = (disk.getBitmapLabels()[1] + "s").toUpperCase(); + } + StringBuffer buf = new StringBuffer(); + for (int i=0; i0) buf.append("\n"); + buf.append(label.charAt(i)); + } + label = buf.toString(); + Canvas canvas = (Canvas) event.widget; + Rectangle area = canvas.getClientArea(); + event.gc.drawLine(area.x + area.width/2, area.y, area.x + area.width/2, area.y + area.height); + Point size = event.gc.textExtent(label); + event.gc.drawText(label, area.x + area.width/2 - size.x/2, area.y + area.height/2 - size.y/2); + } + /** + * Handle paint requests for disk map. + */ + private void paintMap(PaintEvent event) { + if (disk.getDiskUsage() == null) { + paintNoMap(event); + } else if (disk.getBitmapDimensions() == null) { + paintBlockMap(event); + } else { + paintSectorMap(event); + } + } + /** + * Handle paint requests for legend. + */ + private void paintLegend(PaintEvent event) { + Color background = event.gc.getBackground(); + Canvas canvas = (Canvas) event.widget; + + int height = event.gc.getFontMetrics().getHeight(); + String freeText = " = Free"; + String usedText = " = Used"; + int padding = 50; // space between items + + int totalWidth = + (height + 5) * 2 // free/used box + + event.gc.textExtent(freeText).x + + event.gc.textExtent(usedText).x; + + int offset = canvas.getClientArea().width / 2 - totalWidth; + + Rectangle box = new Rectangle(offset, 0, height, height); + drawBox(box, event.gc, freeFill, black, gray); + offset+= height; + event.gc.setBackground(background); + event.gc.drawText(freeText, offset, 0); + offset+= event.gc.textExtent(freeText).x; + + offset+= padding; + box = new Rectangle(offset, 0, height, height); + drawBox(box, event.gc, usedFill, black, gray); + offset+= height; + event.gc.setBackground(background); + event.gc.drawText(" = Used", offset, 0); + } + /** + * Display message to user regarding no disk map being available. + */ + private void paintNoMap(PaintEvent event) { + Canvas canvas = (Canvas) event.widget; + Rectangle bounds = canvas.getClientArea(); + event.gc.drawString("A disk map is unavailable.", 0, 0); + } + /** + * Paint a track/sector map. + */ + private void paintSectorMap(PaintEvent event) { + int[] dimensions = disk.getBitmapDimensions(); + int ydim = dimensions[1]; + int xdim = dimensions[0]; + + paintDiskMap(xdim, ydim, event); + } + /** + * Paint a block map. + */ + private void paintBlockMap(PaintEvent event) { + Canvas canvas = (Canvas) event.widget; + Rectangle area = canvas.getClientArea(); + + double blocks = disk.getBitmapLength(); + double width = area.width; + double height = area.height; + double factor = Math.sqrt(blocks / (width * height)); + int xdim = (int) (width * factor + 0.5); + int ydim = (int) (height * factor + 0.5); + if (xdim * ydim < blocks) { + xdim++; + } + if (xdim * ydim < blocks) { + ydim++; + } + + paintDiskMap(xdim, ydim, event); + } + /** + * Paint a map with the given dimensions. + */ + private void paintDiskMap(int xdim, int ydim, PaintEvent event) { + int bitmapLength = disk.getBitmapLength(); + Canvas canvas = (Canvas) event.widget; + Rectangle area = canvas.getClientArea(); + area.width-= 2; + area.height-= 2; + + int[] ypos = new int[ydim + 1]; + for (int i=0; i= 10 && box.height >= 10) { + // square the rectangle shape: + int size = Math.min(box.height, box.width); + box.height = size + ((box.height - size) / 2); + box.width = size + ((box.width - size) / 2); + // offset internal box: + box.x+= 2; + box.y+= 2; + box.width-= 5; + box.height-= 5; + // draw! + gc.setBackground(shadow); + gc.fillRectangle(box); + box.x-= 2; + box.y-= 2; + gc.setBackground(fill); + gc.fillRectangle(box); + gc.setForeground(outline); + gc.drawRectangle(box); + } else { + // just fill: + gc.setBackground(fill); + gc.fillRectangle(box); + } + } +} diff --git a/src/com/webcodepro/applecommander/ui/swt/DiskWindow.java b/src/com/webcodepro/applecommander/ui/swt/DiskWindow.java new file mode 100644 index 0000000..186a41f --- /dev/null +++ b/src/com/webcodepro/applecommander/ui/swt/DiskWindow.java @@ -0,0 +1,93 @@ +/* + * 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.ui.swt; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.CTabFolder; +import org.eclipse.swt.events.DisposeEvent; +import org.eclipse.swt.events.DisposeListener; +import org.eclipse.swt.layout.FillLayout; +import org.eclipse.swt.widgets.Shell; + +import com.webcodepro.applecommander.storage.FormattedDisk; + +/** + * Displays disk information on the screen. + *

+ * Date created: Oct 12, 2002 3:28:41 PM + * @author: Rob Greene + */ +public class DiskWindow { + private Shell parentShell; + private ImageManager imageManager; + + private Shell shell; + private FormattedDisk disk; + + private DiskInfoTab diskInfoTab; + private DiskMapTab diskMapTab; + private DiskExplorerTab diskExplorerTab; + + /** + * Construct the disk window. + */ + public DiskWindow(Shell parentShell, FormattedDisk disk, ImageManager imageManager) { + this.parentShell = shell; + this.disk = disk; + this.imageManager = imageManager; + } + + /** + * Setup the Disk window and display (open) it. + */ + public void open() { + shell = new Shell(parentShell, SWT.SHELL_TRIM); + shell.setLayout(new FillLayout()); + shell.setImage(imageManager.getDiskIcon()); + shell.setText("AppleCommander - " + disk.getFilename()); + shell.addDisposeListener(new DisposeListener() { + public void widgetDisposed(DisposeEvent event) { + dispose(event); + } + }); + + CTabFolder tabFolder = new CTabFolder(shell, SWT.BOTTOM); + diskExplorerTab = new DiskExplorerTab(tabFolder, disk, imageManager); + diskMapTab = new DiskMapTab(tabFolder, disk); + diskInfoTab = new DiskInfoTab(tabFolder, disk); + tabFolder.setSelection(tabFolder.getItems()[0]); + + shell.open(); + } + + /** + * Dispose of all shared resources. + */ + private void dispose(DisposeEvent event) { + diskMapTab.dispose(); + diskInfoTab.dispose(); + + disk = null; + diskMapTab = null; + diskInfoTab = null; + System.gc(); + } + +} diff --git a/src/com/webcodepro/applecommander/ui/swt/DropDownSelectionListener.java b/src/com/webcodepro/applecommander/ui/swt/DropDownSelectionListener.java new file mode 100644 index 0000000..ebaa1d5 --- /dev/null +++ b/src/com/webcodepro/applecommander/ui/swt/DropDownSelectionListener.java @@ -0,0 +1,106 @@ +/* + * 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.ui.swt; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.widgets.Menu; +import org.eclipse.swt.widgets.MenuItem; +import org.eclipse.swt.widgets.ToolBar; +import org.eclipse.swt.widgets.ToolItem; + +/** + * Listens to widgetSelected() events on SWT.DROP_DOWN type ToolItems + * and opens/closes a menu when appropriate. + * Code taken and modified from SWT examples to be more generic. + *

+ * Date created: Nov 2, 2002 8:25:11 PM + * @author: Rob Greene + */ +public class DropDownSelectionListener extends SelectionAdapter { + private Menu menu = null; + private boolean visible = false; + + /** + * Construct the DropDownSelectionListener with the specific menu to be used. + */ + public DropDownSelectionListener(Menu menu) { + this.menu = menu; + + MenuItem[] menuItems = menu.getItems(); + for (int i=0; i

+ * Date created: Nov 7, 2002 8:43:27 PM + * @author: Rob Greene + */ +public class ExportFileStartPane extends WizardPane { + private Composite parent; + private Object layoutData; + private Composite control; + private ExportWizard wizard; + private GraphicsFileFilter graphicsFileFilter = new GraphicsFileFilter(); + private AppleWorksWordProcessorFileFilter awpFilter = new AppleWorksWordProcessorFileFilter(); + /** + * Constructor for ExportFileStartPane. + */ + public ExportFileStartPane(Composite parent, ExportWizard exportWizard, Object layoutData) { + super(); + this.parent = parent; + this.wizard = exportWizard; + this.layoutData = layoutData; + } + /** + * Open up and configure the wizard pane. + */ + public void open() { + control = new Composite(parent, SWT.NULL); + control.setLayoutData(layoutData); + wizard.enableNextButton(true); + wizard.enableFinishButton(false); + RowLayout layout = new RowLayout(SWT.VERTICAL); + layout.justify = true; + layout.marginBottom = 5; + layout.marginLeft = 5; + layout.marginRight = 5; + layout.marginTop = 5; + layout.spacing = 3; + control.setLayout(layout); + Label label = new Label(control, SWT.WRAP); + label.setText("Please choose the type of file that is being exported."); + RowLayout subpanelLayout = new RowLayout(SWT.VERTICAL); + subpanelLayout.justify = true; + subpanelLayout.spacing = 3; + Composite buttonSubpanel = new Composite(control, SWT.NULL); + buttonSubpanel.setLayout(subpanelLayout); + Button button = new Button(buttonSubpanel, SWT.RADIO); + button.setText("Raw disk data"); + button.setSelection(wizard.getFileFilter() == null); + button.addSelectionListener(new SelectionAdapter() { + public void widgetSelected(SelectionEvent e) { + wizard.setFileFilter(null); + } + }); + button = new Button(buttonSubpanel, SWT.RADIO); + button.setText("Binary file"); + button.setSelection(wizard.getFileFilter() instanceof BinaryFileFilter); + button.addSelectionListener(new SelectionAdapter() { + public void widgetSelected(SelectionEvent e) { + wizard.setFileFilter(new BinaryFileFilter()); + } + }); + button = new Button(buttonSubpanel, SWT.RADIO); + button.setText("ASCII text file"); + button.setSelection(wizard.getFileFilter() instanceof TextFileFilter); + button.addSelectionListener(new SelectionAdapter() { + public void widgetSelected(SelectionEvent e) { + wizard.setFileFilter(new TextFileFilter()); + } + }); + button = new Button(buttonSubpanel, SWT.RADIO); + button.setText("Applesoft BASIC file"); + button.setSelection(wizard.getFileFilter() instanceof ApplesoftFileFilter); + button.addSelectionListener(new SelectionAdapter() { + public void widgetSelected(SelectionEvent e) { + wizard.setFileFilter(new ApplesoftFileFilter()); + } + }); + button = new Button(buttonSubpanel, SWT.RADIO); + button.setText("Integer BASIC file"); + button.setSelection(wizard.getFileFilter() instanceof IntegerBasicFileFilter); + button.addSelectionListener(new SelectionAdapter() { + public void widgetSelected(SelectionEvent e) { + wizard.setFileFilter(new IntegerBasicFileFilter()); + } + }); + button = new Button(buttonSubpanel, SWT.RADIO); + button.setText("AppleWorks Word Processor file"); + button.setSelection(wizard.getFileFilter() instanceof AppleWorksWordProcessorFileFilter); + button.addSelectionListener(new SelectionAdapter() { + public void widgetSelected(SelectionEvent e) { + wizard.setFileFilter(new AppleWorksWordProcessorFileFilter()); + } + }); + button = new Button(buttonSubpanel, SWT.RADIO); + button.setText("Graphic file..."); + button.setEnabled(graphicsFileFilter.isCodecAvailable()); + button.setSelection(wizard.getFileFilter() instanceof GraphicsFileFilter); + button.addSelectionListener(new SelectionAdapter() { + public void widgetSelected(SelectionEvent e) { + wizard.setFileFilter(graphicsFileFilter); + } + }); + } + /** + * Get the next pane. A null return indicates the end of the wizard. + * @see com.webcodepro.applecommander.gui.WizardPane#getNextPane() + */ + public WizardPane getNextPane() { + if (wizard.getFileFilter() instanceof GraphicsFileFilter) { + return new ExportGraphicsTypePane(parent, wizard, layoutData); + } else if (wizard.getFileFilter() instanceof AppleWorksWordProcessorFileFilter) { + return new AppleWorksWordProcessorPane(parent, wizard, layoutData); + } + return new ExportFileDestinationPane(parent, wizard, layoutData); + } + /** + * Dispose of resources. + * @see com.webcodepro.applecommander.gui.WizardPane#dispose() + */ + public void dispose() { + control.dispose(); + control = null; + } +} diff --git a/src/com/webcodepro/applecommander/ui/swt/ExportGraphicsTypePane.java b/src/com/webcodepro/applecommander/ui/swt/ExportGraphicsTypePane.java new file mode 100644 index 0000000..1d22c5b --- /dev/null +++ b/src/com/webcodepro/applecommander/ui/swt/ExportGraphicsTypePane.java @@ -0,0 +1,146 @@ +/* + * 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.ui.swt; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.layout.RowLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Label; + +import com.webcodepro.applecommander.storage.GraphicsFileFilter; + +/** + * Choose graphics options for file export. + *

+ * Date created: Nov 7, 2002 10:25:43 PM + * @author: Rob Greene + */ +public class ExportGraphicsTypePane extends WizardPane { + private Composite parent; + private Object layoutData; + private Composite control; + private ExportWizard wizard; + /** + * Constructor for ExportGraphicsTypePane. + */ + public ExportGraphicsTypePane(Composite parent, ExportWizard exportWizard, Object layoutData) { + super(); + this.parent = parent; + this.wizard = exportWizard; + this.layoutData = layoutData; + } + /** + * Determine the next wizard pane and return an instance. + * @see com.webcodepro.applecommander.gui.WizardPane#getNextPane() + */ + public WizardPane getNextPane() { + return new ExportFileDestinationPane(parent, wizard, layoutData); + } + /** + * Open up and configure the wizard pane. + * @see com.webcodepro.applecommander.gui.WizardPane#open() + */ + public void open() { + wizard.enableFinishButton(false); + wizard.enableNextButton(true); + control = new Composite(parent, SWT.NULL); + control.setLayoutData(layoutData); + RowLayout layout = new RowLayout(SWT.VERTICAL); + layout.justify = true; + layout.marginBottom = 5; + layout.marginLeft = 5; + layout.marginRight = 5; + layout.marginTop = 5; + layout.spacing = 3; + control.setLayout(layout); + Label label = new Label(control, SWT.WRAP); + label.setText("Please choose the appropriate graphics mode:"); + RowLayout subpanelLayout = new RowLayout(SWT.VERTICAL); + subpanelLayout.justify = true; + subpanelLayout.spacing = 3; + Composite graphicsModeGroup = new Composite(control, SWT.NULL); + graphicsModeGroup.setLayout(subpanelLayout); + Button button = new Button(graphicsModeGroup, SWT.RADIO); + button.setText("Hires black and white (280x192)"); + button.setSelection(getGraphicsFilter().isHiresBlackAndWhiteMode()); + button.addSelectionListener(new SelectionAdapter() { + public void widgetSelected(SelectionEvent e) { + getGraphicsFilter().setMode(GraphicsFileFilter.MODE_HGR_BLACK_AND_WHITE); + } + }); + button = new Button(graphicsModeGroup, SWT.RADIO); + button.setText("Hires color (280x192)"); + button.setSelection(getGraphicsFilter().isHiresColorMode()); + button.addSelectionListener(new SelectionAdapter() { + public void widgetSelected(SelectionEvent e) { + getGraphicsFilter().setMode(GraphicsFileFilter.MODE_HGR_COLOR); + } + }); + button = new Button(graphicsModeGroup, SWT.RADIO); + button.setText("Double hires black and white (560x384)"); + button.setSelection(getGraphicsFilter().isDoubleHiresBlackAndWhiteMode()); + button.addSelectionListener(new SelectionAdapter() { + public void widgetSelected(SelectionEvent e) { + getGraphicsFilter().setMode(GraphicsFileFilter.MODE_DHR_BLACK_AND_WHITE); + } + }); + button = new Button(graphicsModeGroup, SWT.RADIO); + button.setText("Double hires color (560x384)"); + button.setSelection(getGraphicsFilter().isDoubleHiresColorMode()); + button.addSelectionListener(new SelectionAdapter() { + public void widgetSelected(SelectionEvent e) { + getGraphicsFilter().setMode(GraphicsFileFilter.MODE_DHR_COLOR); + } + }); + label = new Label(control, SWT.WRAP); + label.setText("Please choose the appropriate file format to save image as:"); + Composite graphicsFormatGroup = new Composite(control, SWT.NULL); + graphicsFormatGroup.setLayout(subpanelLayout); + String[] formats = getGraphicsFilter().getFileExtensions(); + for (int i=0; i + * Date created: Nov 7, 2002 9:22:35 PM + * @author: Rob Greene + */ +public class ExportWizard { + private FormattedDisk disk; + private Shell parent; + private Shell dialog; + private Image logo; // managed by SwtAppleCommander + private Stack wizardPanes = new Stack(); + private FileFilter fileFilter; + private String directory; + private boolean wizardCompleted; + private Button backButton; + private Button nextButton; + private Button finishButton; + private Composite contentPane; + /** + * Constructor for ExportWizard. + */ + public ExportWizard(Shell parent, Image logo, FormattedDisk disk) { + super(); + this.parent = parent; + this.logo = logo; + this.disk = disk; + } + /** + * Create the dialog. + */ + private void createDialog() { + dialog = new Shell(parent, SWT.DIALOG_TRIM | SWT.APPLICATION_MODAL); + dialog.setText("Export Wizard"); + RowLayout layout = new RowLayout(SWT.VERTICAL); + layout.justify = true; + layout.marginBottom = 5; + layout.marginLeft = 5; + layout.marginRight = 5; + layout.marginTop = 5; + layout.spacing = 3; + dialog.setLayout(layout); + + // Wizard logo + RowData rowData = new RowData(); + rowData.width = logo.getImageData().width; + rowData.height = logo.getImageData().height; + ImageCanvas imageCanvas = new ImageCanvas(dialog, SWT.BORDER, logo, rowData); + + // Starting pane + rowData = new RowData(); + rowData.width = logo.getImageData().width; + contentPane = new Composite(dialog, SWT.BORDER); + contentPane.setLayoutData(rowData); + contentPane.setLayout(new FillLayout()); + + // Bottom row of buttons + Composite composite = new Composite(dialog, SWT.NONE); + composite.setLayoutData(rowData); + composite.setLayout(new FillLayout(SWT.HORIZONTAL)); + Button button = new Button(composite, SWT.PUSH); + button.setText("Cancel"); + button.addSelectionListener(new SelectionAdapter() { + public void widgetSelected(SelectionEvent e) { + wizardCompleted = false; + dialog.close(); + } + }); + backButton = new Button(composite, SWT.PUSH); + backButton.setEnabled(false); + backButton.setText("< Back"); + backButton.addSelectionListener(new SelectionAdapter() { + public void widgetSelected(SelectionEvent e) { + WizardPane current = (WizardPane) wizardPanes.pop(); + WizardPane previous = (WizardPane) wizardPanes.peek(); + backButton.setEnabled(wizardPanes.size() > 1); + current.dispose(); + previous.open(); + dialog.pack(); + } + }); + nextButton = new Button(composite, SWT.PUSH); + nextButton.setText("Next >"); + nextButton.addSelectionListener(new SelectionAdapter() { + public void widgetSelected(SelectionEvent e) { + WizardPane current = (WizardPane) wizardPanes.peek(); + WizardPane next = current.getNextPane(); + wizardPanes.add(next); + backButton.setEnabled(wizardPanes.size() > 1); + current.dispose(); + next.open(); + dialog.pack(); + } + }); + finishButton = new Button(composite, SWT.PUSH); + finishButton.setEnabled(false); + finishButton.setText("Finish"); + finishButton.addSelectionListener(new SelectionAdapter() { + public void widgetSelected(SelectionEvent e) { + wizardCompleted = true; + dialog.close(); + } + }); + + WizardPane wizardPane = new ExportFileStartPane(contentPane, this, null); + wizardPanes.add(wizardPane); + wizardPane.open(); + + dialog.pack(); + } + /** + * Open and display the dialog. + */ + public void open() { + createDialog(); + dialog.open(); + Display display = dialog.getDisplay(); + while (!dialog.isDisposed()) { + if (!display.readAndDispatch()) display.sleep (); + } + } + /** + * Dispose of all panels and resources. + */ + public void dispose() { + while (!wizardPanes.empty()) { + WizardPane pane = (WizardPane) wizardPanes.pop(); + pane.dispose(); + pane = null; + } + dialog.dispose(); + backButton.dispose(); + nextButton.dispose(); + finishButton.dispose(); + contentPane.dispose(); + } + /** + * Get the FileFilter. + */ + public FileFilter getFileFilter() { + return fileFilter; + } + /** + * Set the FileFilter. + */ + public void setFileFilter(FileFilter fileFilter) { + this.fileFilter = fileFilter; + } + /** + * Indicates if the wizard was completed. + */ + public boolean isWizardCompleted() { + return wizardCompleted; + } + /** + * Enable/disable the next button. + */ + public void enableNextButton(boolean state) { + nextButton.setEnabled(state); + } + /** + * Enable/disable the finish button. + */ + public void enableFinishButton(boolean state) { + finishButton.setEnabled(state); + } + /** + * Get the disk that is being worked on. + */ + public FormattedDisk getDisk() { + return disk; + } + /** + * Returns the directory. + * @return String + */ + public String getDirectory() { + return directory; + } + /** + * Sets the directory. + * @param directory The directory to set + */ + public void setDirectory(String directory) { + this.directory = directory; + } +} diff --git a/src/com/webcodepro/applecommander/ui/swt/ImageCanvas.java b/src/com/webcodepro/applecommander/ui/swt/ImageCanvas.java new file mode 100644 index 0000000..4ff907a --- /dev/null +++ b/src/com/webcodepro/applecommander/ui/swt/ImageCanvas.java @@ -0,0 +1,61 @@ +/* + * 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.ui.swt; + +import org.eclipse.swt.events.PaintEvent; +import org.eclipse.swt.events.PaintListener; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.widgets.Canvas; +import org.eclipse.swt.widgets.Composite; + +/** + * Displays an image. + *

+ * Date created: Nov 7, 2002 9:28:21 PM + * @author: Rob Greene + */ +public class ImageCanvas extends Canvas implements PaintListener { + private Image image; + /** + * Constructor for ImageCanvas. + */ + public ImageCanvas(Composite parent, int style, Image image, Object layoutData) { + super(parent, style); + this.image = image; + setLayoutData(layoutData); + setSize(image.getImageData().width, image.getImageData().height); + addPaintListener(this); + } + /** + * Handle paint events. + */ + public void paintControl(PaintEvent event) { + event.gc.drawImage( + image, + 0, + 0, + image.getImageData().width, + image.getImageData().height, + 0, + 0, + image.getImageData().width, + image.getImageData().height); + } +} diff --git a/src/com/webcodepro/applecommander/ui/swt/ImageManager.java b/src/com/webcodepro/applecommander/ui/swt/ImageManager.java new file mode 100644 index 0000000..dd1fc01 --- /dev/null +++ b/src/com/webcodepro/applecommander/ui/swt/ImageManager.java @@ -0,0 +1,214 @@ +/* + * 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.ui.swt; + +import java.io.InputStream; + +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.widgets.Display; + +/** + * Manage image for the SWT-base AppleCommander. + *

+ * Date created: Nov 17, 2002 6:53:08 PM + * @author: Rob Greene + */ +public class ImageManager { + private Image logoImage; + private Image diskIcon; + private Image standardFileViewIcon; + private Image nativeFileViewIcon; + private Image detailFileViewIcon; + private Image importFileIcon; + private Image exportFileIcon; + private Image saveImageIcon; + private Image deleteFileIcon; + private Image deletedFilesIcon; + private Image exportWizardLogo; + private Image openDiskIcon; + private Image newDiskIcon; + private Image aboutIcon; + /** + * Construct the ImageManager. + */ + protected ImageManager(Display display) { + diskIcon = createImage(display, "diskicon.gif"); + standardFileViewIcon = createImage(display, "standardfileview.gif"); + nativeFileViewIcon = createImage(display, "nativefileview.gif"); + detailFileViewIcon = createImage(display, "detailfileview.gif"); + importFileIcon = createImage(display, "importfile.gif"); + exportFileIcon = createImage(display, "exportfile.gif"); + saveImageIcon = createImage(display, "saveimage.gif"); + deleteFileIcon = createImage(display, "deletefile.gif"); + deletedFilesIcon = createImage(display, "deletedfiles.gif"); + exportWizardLogo = createImage(display, "ExportWizardLogo.gif"); + logoImage = createImage(display, "AppleCommanderLogo.gif"); + openDiskIcon = createImage(display, "opendisk.gif"); + newDiskIcon = createImage(display, "newdisk.gif"); + aboutIcon = createImage(display, "about.gif"); + } + /** + * Dispose of resources. + */ + public void dispose() { + diskIcon.dispose(); + standardFileViewIcon.dispose(); + nativeFileViewIcon.dispose(); + detailFileViewIcon.dispose(); + importFileIcon.dispose(); + exportFileIcon.dispose(); + saveImageIcon.dispose(); + deleteFileIcon.dispose(); + deletedFilesIcon.dispose(); + logoImage.dispose(); + exportWizardLogo.dispose(); + openDiskIcon.dispose(); + newDiskIcon.dispose(); + aboutIcon.dispose(); + } + /** + * Creates an image. + */ + private Image createImage(Display display, String path) { + try { + InputStream stream = getClass().getResourceAsStream( + "/com/webcodepro/applecommander/ui/images/" + path); + if (stream != null) { + Image image = new Image(display, stream); + stream.close(); + return image; + } + } catch (Exception e) { + } + return null; + } + /** + * Returns the deletedFilesIcon. + * @return Image + */ + public Image getDeletedFilesIcon() { + return deletedFilesIcon; + } + + /** + * Returns the deleteFileIcon. + * @return Image + */ + public Image getDeleteFileIcon() { + return deleteFileIcon; + } + + /** + * Returns the detailFileViewIcon. + * @return Image + */ + public Image getDetailFileViewIcon() { + return detailFileViewIcon; + } + + /** + * Returns the diskIcon. + * @return Image + */ + public Image getDiskIcon() { + return diskIcon; + } + + /** + * Returns the exportFileIcon. + * @return Image + */ + public Image getExportFileIcon() { + return exportFileIcon; + } + + /** + * Returns the exportWizardLogo. + * @return Image + */ + public Image getExportWizardLogo() { + return exportWizardLogo; + } + + /** + * Returns the importFileIcon. + * @return Image + */ + public Image getImportFileIcon() { + return importFileIcon; + } + + /** + * Returns the logoImage. + * @return Image + */ + public Image getLogoImage() { + return logoImage; + } + + /** + * Returns the nativeFileViewIcon. + * @return Image + */ + public Image getNativeFileViewIcon() { + return nativeFileViewIcon; + } + + /** + * Returns the saveImageIcon. + * @return Image + */ + public Image getSaveImageIcon() { + return saveImageIcon; + } + + /** + * Returns the standardFileViewIcon. + * @return Image + */ + public Image getStandardFileViewIcon() { + return standardFileViewIcon; + } + + /** + * Returns the aboutIcon. + * @return Image + */ + public Image getAboutIcon() { + return aboutIcon; + } + + /** + * Returns the newDiskIcon. + * @return Image + */ + public Image getNewDiskIcon() { + return newDiskIcon; + } + + /** + * Returns the openDiskIcon. + * @return Image + */ + public Image getOpenDiskIcon() { + return openDiskIcon; + } + +} diff --git a/src/com/webcodepro/applecommander/ui/swt/SwtAppleCommander.java b/src/com/webcodepro/applecommander/ui/swt/SwtAppleCommander.java new file mode 100644 index 0000000..33fb408 --- /dev/null +++ b/src/com/webcodepro/applecommander/ui/swt/SwtAppleCommander.java @@ -0,0 +1,229 @@ +/* + * 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.ui.swt; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.DisposeEvent; +import org.eclipse.swt.events.DisposeListener; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.FileDialog; +import org.eclipse.swt.widgets.MessageBox; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.ToolBar; +import org.eclipse.swt.widgets.ToolItem; + +import com.webcodepro.applecommander.storage.Disk; +import com.webcodepro.applecommander.storage.FormattedDisk; +import com.webcodepro.applecommander.storage.Disk.FilenameFilter; +import com.webcodepro.applecommander.ui.AppleCommander; +import com.webcodepro.applecommander.ui.UserPreferences; + +/** + * Main class for the SwtAppleCommander interface. + *

+ * Date created: Oct 7, 2002 9:43:37 PM + * @author: Rob Greene + */ +public class SwtAppleCommander { + private Display display; + private Shell shell; + private ToolBar toolBar; + private UserPreferences userPreferences = UserPreferences.getInstance(); + private static ImageManager imageManager; + + /** + * Launch SwtAppleCommander. + */ + public static void main(String[] args) { + Display display = new Display(); + imageManager = new ImageManager(display); + SwtAppleCommander application = new SwtAppleCommander(); + Shell shell = application.open(display); + + while (!shell.isDisposed()) { + if (!display.readAndDispatch()) display.sleep(); + } + + UserPreferences.getInstance().save(); + } + + /** + * Constructor for SwtAppleCommander. + */ + public SwtAppleCommander() { + super(); + } + + /** + * Opens the main program. + */ + private Shell open(Display display) { + this.display = display; + display.setAppName("AppleCommander"); + shell = new Shell(display, SWT.BORDER | SWT.CLOSE | SWT.MIN | SWT.TITLE); + shell.setText("AppleCommander"); + shell.setImage(imageManager.getDiskIcon()); + shell.addDisposeListener(new DisposeListener() { + public void widgetDisposed(DisposeEvent event) { + dispose(event); + } + }); + + GridLayout gridLayout = new GridLayout(); + gridLayout.marginHeight = 5; + gridLayout.marginWidth = 5; + shell.setLayout(gridLayout); + + GridData gridData = new GridData(GridData.FILL_HORIZONTAL); + gridData.grabExcessHorizontalSpace = true; + createToolBar(shell, gridData); + + gridData = new GridData(); + Image logoImage = imageManager.getLogoImage(); + gridData.widthHint = logoImage.getImageData().width; + gridData.heightHint = logoImage.getImageData().height; + ImageCanvas imageCanvas = new ImageCanvas(shell, SWT.BORDER, logoImage, gridData); + + shell.pack(); + shell.open(); + return shell; + } + + /** + * Dispose of all shared resources. + */ + private void dispose(DisposeEvent event) { + toolBar.dispose(); + imageManager.dispose(); + } + + /** + * Exits the main program. + */ + private void exit() { + shell.close(); + } + + /** + * Open a file. + */ + private void openFile() { + FileDialog fileDialog = new FileDialog(shell, SWT.OPEN); + FilenameFilter[] fileFilters = Disk.getFilenameFilters(); + String[] names = new String[fileFilters.length]; + String[] extensions = new String[fileFilters.length]; + for (int i=0; i + * Date created: Nov 7, 2002 8:40:44 PM + * @author: Rob Greene + */ +public abstract class WizardPane { + /** + * Constructor for WizardPane. + */ + public WizardPane() { + super(); + } + /** + * Get the next WizardPane. + */ + public abstract WizardPane getNextPane(); + /** + * Create and display the wizard pane. + */ + public abstract void open(); + /** + * Dispose of any resources. + */ + public abstract void dispose(); +}

+ * From the Apple2 + * technical note: + *