/* * AppleCommander - An Apple ][ image utility. * Copyright (C) 2002-3 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 com.webcodepro.applecommander.compiler.ApplesoftCompiler; import com.webcodepro.applecommander.storage.AppleWorksWordProcessorFileFilter; import com.webcodepro.applecommander.storage.ApplesoftFileFilter; import com.webcodepro.applecommander.storage.BinaryFileFilter; import com.webcodepro.applecommander.storage.DirectoryEntry; 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.ProdosDiskSizeDoesNotMatchException; import com.webcodepro.applecommander.storage.ProdosFormatDisk; import com.webcodepro.applecommander.storage.TextFileFilter; import com.webcodepro.applecommander.storage.FormattedDisk.FileColumnHeader; import com.webcodepro.applecommander.ui.ImportSpecification; import com.webcodepro.applecommander.ui.UserPreferences; import com.webcodepro.applecommander.util.AppleUtil; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; 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 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.Font; import org.eclipse.swt.graphics.GC; 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.printing.PrintDialog; import org.eclipse.swt.printing.Printer; import org.eclipse.swt.printing.PrinterData; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.FileDialog; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Menu; import org.eclipse.swt.widgets.MenuItem; import org.eclipse.swt.widgets.MessageBox; import org.eclipse.swt.widgets.ProgressBar; 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 static final char CTRL_C = 'C' - '@'; private static final char CTRL_D = 'D' - '@'; private static final char CTRL_E = 'E' - '@'; private static final char CTRL_I = 'I' - '@'; private static final char CTRL_P = 'P' - '@'; private static final char CTRL_S = 'S' - '@'; private static final char CTRL_V = 'V' - '@'; // These are given to us from DiskWindow private Shell shell; private ImageManager imageManager; private DiskWindow diskWindow; private FormattedDisk[] disks; private SashForm sashForm; private Tree directoryTree; private Table fileTable; private ToolBar toolBar; private ToolItem standardFormatToolItem; private ToolItem nativeFormatToolItem; private ToolItem detailFormatToolItem; private ToolItem showDeletedFilesToolItem; private ToolItem exportToolItem; private ToolItem importToolItem; private ToolItem compileToolItem; private ToolItem viewFileItem; private ToolItem printToolItem; private ToolItem deleteToolItem; private ToolItem saveToolItem; private ToolItem saveAsToolItem; private UserPreferences userPreferences = UserPreferences.getInstance(); 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[] disks, ImageManager imageManager, DiskWindow diskWindow) { this.disks = disks; this.shell = tabFolder.getShell(); this.imageManager = imageManager; this.diskWindow = diskWindow; createFilesTab(tabFolder); } /** * Dispose of resources. */ public void dispose() { sashForm.dispose(); directoryTree.dispose(); fileTable.dispose(); standardFormatToolItem.dispose(); nativeFormatToolItem.dispose(); detailFormatToolItem.dispose(); showDeletedFilesToolItem.dispose(); exportToolItem.dispose(); importToolItem.dispose(); deleteToolItem.dispose(); compileToolItem.dispose(); viewFileItem.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()); } }); directoryTree.addListener(SWT.KeyUp, createDirectoryKeyboardHandler()); directoryTree.addListener(SWT.KeyUp, createToolbarCommandHandler()); fileTable = new Table(sashForm, SWT.MULTI | SWT.FULL_SELECTION | SWT.BORDER); fileTable.setHeaderVisible(true); sashForm.setWeights(new int[] {1,2}); for (int i=0; i 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) { FileEntry fileEntry = getSelectedFileEntry(); exportToolItem.setEnabled(disks[0].canReadFileData()); deleteToolItem.setEnabled(disks[0].canDeleteFile()); compileToolItem.setEnabled(fileEntry != null && fileEntry.canCompile()); viewFileItem.setEnabled(true); } else { exportToolItem.setEnabled(false); deleteToolItem.setEnabled(false); compileToolItem.setEnabled(false); viewFileItem.setEnabled(false); } } /** * Double-click handler. */ public void widgetDefaultSelected(SelectionEvent event) { viewFile(); } }); TableColumn column = null; List headers = disks[0].getFileColumnHeaders(currentFormat); int[] widths = (int[])columnWidths.get(new Integer(currentFormat)); for (int i=0; i 1) ? "these files" : "this file") + "?\n\n" + "Choose YES to proceed or NO to cancel."); int button = box.open(); if (button == SWT.YES) { for (int i=0; i assume a new/unsaved image return; } disks[0].save(); saveToolItem.setEnabled(disks[0].hasChanged()); } catch (IOException ex) { showSaveError(ex); } } /** * Display the Save error dialog box. * @see #save * @see #saveAs */ protected void showSaveError(IOException ex) { Shell finalShell = shell; String errorMessage = ex.getMessage(); if (errorMessage == null) { errorMessage = ex.getClass().getName(); } MessageBox box = new MessageBox(finalShell, SWT.ICON_ERROR | SWT.CLOSE); box.setText("Unable to save disk image!"); box.setMessage( "Unable to save '" + disks[0].getFilename() + "'.\n\n" + "AppleCommander was unable to save the disk\n" + "image. The system error given was '" + errorMessage + "'\n\n" + "Sorry!"); box.open(); } /** * Create the keyboard handler for the directory pane. * These are keys that are only active in the directory * viewer. See createToolbarCommandHandler for the general application * keyboard handler. * @see #createToolbarCommandHandler */ private Listener createDirectoryKeyboardHandler() { return new Listener() { public void handleEvent(Event event) { if (event.type == SWT.KeyUp) { TreeItem[] treeItem = null; if ((event.stateMask & SWT.CTRL) != 0) { switch (event.character) { case '-': treeItem = directoryTree.getSelection(); setDirectoryExpandedStates(treeItem[0], false); break; case '+': treeItem = directoryTree.getSelection(); setDirectoryExpandedStates(treeItem[0], true); break; } } else { // assume no control and no alt switch (event.character) { case '-': treeItem = directoryTree.getSelection(); treeItem[0].setExpanded(false); break; case '+': treeItem = directoryTree.getSelection(); treeItem[0].setExpanded(true); break; } } } } }; } /** * Open up the view file window for the currently selected file. */ protected void viewFile() { FileEntry fileEntry = getSelectedFileEntry(); if (fileEntry.isDeleted()) { MessageBox box = new MessageBox(shell, SWT.ICON_ERROR | SWT.OK); box.setText("Unable to view a deleted file!"); box.setMessage("Sorry, you cannot view a deleted file."); box.open(); } else if (fileEntry.isDirectory()) { TreeItem item = findDirectoryItem(directoryTree.getSelection()[0].getItems(), fileEntry.getFilename(), 1, 0); if (item != null) { directoryTree.showItem(item); directoryTree.setSelection(new TreeItem[] { item }); changeCurrentFormat(currentFormat); // minor hack } } else { // Assuming a normal file! FileViewerWindow window = new FileViewerWindow(shell, fileEntry, imageManager); window.open(); } } /** * Locate a named item in the directory tree. */ protected TreeItem findDirectoryItem(TreeItem[] treeItems, String name, int maxDepth, int currentDepth) { if (maxDepth == currentDepth) return null; for (int i=0; ionly active in the file * viewer. See createToolbarCommandHandler for the general application * keyboard handler. * @see #createToolbarCommandHandler */ private Listener createFileKeyboardHandler() { return new Listener() { public void handleEvent(Event event) { FileEntry fileEntry = getSelectedFileEntry(); if (fileEntry != null && event.type == SWT.KeyUp && (event.stateMask & SWT.CTRL) != 0) { switch (event.character) { case CTRL_C: // Compile Wizard if (compileToolItem.isEnabled()) { compileFileWizard(); } break; case CTRL_D: // Delete file if (deleteToolItem.isEnabled()) { deleteFile(); } break; case CTRL_E: // Export Wizard exportFileWizard(); break; case CTRL_V: // View file viewFile(); break; } } } }; } /** * The toolbar command handler contains the global toolbar * actions. This does not include file-specific actions. * The intent is that the listener is then added to multiple * visual components (i.e., the file listing as well as the * directory listing). */ private Listener createToolbarCommandHandler() { return new Listener() { public void handleEvent(Event event) { if (event.type == SWT.KeyUp) { if ((event.stateMask & SWT.CTRL) != 0) { // CTRL key held if ((event.stateMask & SWT.SHIFT) != 0) { // SHIFT key held switch (event.character) { case CTRL_S: // Save As... saveAs(); break; } } else { switch (event.character) { case CTRL_I: // Import Wizard importFiles(); break; case CTRL_P: // Print... print(); break; case CTRL_S: // Save if (saveToolItem.isEnabled()) { save(); } break; } } } else { // No CTRL key switch (event.keyCode) { case SWT.F2: // Standard file display changeCurrentFormat(FormattedDisk.FILE_DISPLAY_STANDARD); break; case SWT.F3: // Native file display changeCurrentFormat(FormattedDisk.FILE_DISPLAY_NATIVE); break; case SWT.F4: // Detail file display changeCurrentFormat(FormattedDisk.FILE_DISPLAY_DETAIL); break; case SWT.F5: // Show deleted files showDeletedFiles = !showDeletedFilesToolItem.getSelection(); showDeletedFilesToolItem.setSelection(showDeletedFiles); fillFileTable(currentFileList); break; } } } } }; } /** * Get the currently selected FileEntry. Note that this * can return null if there are none selected. Also, if there * are multiple files selected, this is not complete. */ protected FileEntry getSelectedFileEntry() { FileEntry fileEntry = null; if (fileTable.getSelectionIndex() >= 0) { fileEntry = (FileEntry) fileTable.getItem(fileTable.getSelectionIndex()).getData(); } return fileEntry; } /** * Internal class that controls printing of a file listing. */ private class Printing implements Runnable { private Printer printer; private int y; private int x; private Rectangle clientArea; private GC gc; private List fileHeaders; private int[] columnWidths; private int[] columnPosition; private Font normalFont; private Font headerFont; private String filename; private int page = 1; private int dpiY; private int dpiX; public Printing(Printer printer) { this.printer = printer; } public void run() { if (printer.startJob(disks[0].getFilename())) { clientArea = printer.getClientArea(); dpiY = printer.getDPI().y; dpiX = printer.getDPI().x; // Setup 1" margin: Rectangle trim = printer.computeTrim(0, 0, 0, 0); clientArea.x = dpiX + trim.x; clientArea.y = dpiY + trim.y; clientArea.width -= (clientArea.x + trim.width); clientArea.height -= (clientArea.y + trim.height); // Set default values: y = clientArea.y; x = clientArea.x; gc = new GC(printer); int fontSize = 12; if (currentFormat == FormattedDisk.FILE_DISPLAY_NATIVE) { fontSize = 10; } else if (currentFormat == FormattedDisk.FILE_DISPLAY_DETAIL) { fontSize = 8; } normalFont = new Font(printer, "", fontSize, SWT.NORMAL); headerFont = new Font(printer, "", fontSize, SWT.BOLD); for (int i=0; i= header.getTitle().length()) ? header.getMaximumWidth() : header.getTitle().length(); totalWidth+= widths[i]; } columnWidths = new int[fileHeaders.size()]; columnPosition = new int[fileHeaders.size()]; int position = clientArea.x; for (int i=0; i (clientArea.y + clientArea.height)) { // filled a page printFooter(); printer.endPage(); y = clientArea.y; } } protected void printHeader() { Point point = gc.stringExtent(filename); gc.drawString(filename, clientArea.x + (clientArea.width - point.x)/2, y - dpiY + point.y); } protected void printFooter() { String text = "Page " + Integer.toString(page); Point point = gc.stringExtent(text); gc.drawString(text, clientArea.x + (clientArea.width - point.x)/2, clientArea.y + clientArea.height + dpiY - point.y); page++; } protected void printFiles(DirectoryEntry directory, int level) { Iterator iterator = directory.getFiles().iterator(); while (iterator.hasNext()) { FileEntry fileEntry = (FileEntry) iterator.next(); if (!fileEntry.isDeleted() || showDeletedFiles) { List columns = fileEntry.getFileColumnData(currentFormat); for (int i=0; i