/* * 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 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.ArrayList; 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; import com.webcodepro.applecommander.compiler.ApplesoftCompiler; import com.webcodepro.applecommander.storage.DirectoryEntry; import com.webcodepro.applecommander.storage.Disk; import com.webcodepro.applecommander.storage.DiskException; 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.FormattedDisk.FileColumnHeader; import com.webcodepro.applecommander.storage.filters.AppleWorksDataBaseFileFilter; import com.webcodepro.applecommander.storage.filters.AppleWorksSpreadSheetFileFilter; import com.webcodepro.applecommander.storage.filters.AppleWorksWordProcessorFileFilter; import com.webcodepro.applecommander.storage.filters.ApplesoftFileFilter; import com.webcodepro.applecommander.storage.filters.AssemblySourceFileFilter; import com.webcodepro.applecommander.storage.filters.BinaryFileFilter; import com.webcodepro.applecommander.storage.filters.BusinessBASICFileFilter; import com.webcodepro.applecommander.storage.filters.GraphicsFileFilter; import com.webcodepro.applecommander.storage.filters.IntegerBasicFileFilter; import com.webcodepro.applecommander.storage.filters.PascalTextFileFilter; import com.webcodepro.applecommander.storage.filters.TextFileFilter; import com.webcodepro.applecommander.storage.filters.GutenbergFileFilter; import com.webcodepro.applecommander.storage.os.prodos.ProdosDiskSizeDoesNotMatchException; import com.webcodepro.applecommander.storage.os.prodos.ProdosFormatDisk; import com.webcodepro.applecommander.storage.physical.ByteArrayImageLayout; import com.webcodepro.applecommander.storage.physical.DosOrder; import com.webcodepro.applecommander.storage.physical.ImageOrder; import com.webcodepro.applecommander.storage.physical.NibbleOrder; import com.webcodepro.applecommander.storage.physical.ProdosOrder; import com.webcodepro.applecommander.ui.ImportSpecification; import com.webcodepro.applecommander.ui.UiBundle; import com.webcodepro.applecommander.ui.UserPreferences; import com.webcodepro.applecommander.ui.swt.util.DropDownSelectionListener; import com.webcodepro.applecommander.ui.swt.util.ImageManager; import com.webcodepro.applecommander.ui.swt.util.SwtUtil; import com.webcodepro.applecommander.ui.swt.wizard.compilefile.CompileWizard; import com.webcodepro.applecommander.ui.swt.wizard.exportfile.ExportWizard; import com.webcodepro.applecommander.ui.swt.wizard.importfile.ImportWizard; import com.webcodepro.applecommander.util.AppleUtil; import com.webcodepro.applecommander.util.Host; import com.webcodepro.applecommander.util.StreamUtil; import com.webcodepro.applecommander.util.TextBundle; /** * Build the Disk File tab for the Disk Window. *

* Date created: Nov 17, 2002 9:46:53 PM * @author Rob Greene * * Changed at: Dec 1, 2017 * @author Lisias Toledo */ 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 ToolItem changeOrderToolItem; private Menu changeImageOrderMenu; private UserPreferences userPreferences = UserPreferences.getInstance(); private TextBundle textBundle = UiBundle.getInstance(); private FileFilter fileFilter; private GraphicsFileFilter graphicsFilter = new GraphicsFileFilter(); private AppleWorksWordProcessorFileFilter awpFilter = new AppleWorksWordProcessorFileFilter(); private GutenbergFileFilter gutenbergFilter = new GutenbergFileFilter(); 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(); changeOrderToolItem.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(textBundle.get("FilesTab")); //$NON-NLS-1$ 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) { try { changeCurrentFormat(getCurrentFormat()); // minor hack } catch (DiskException e) { // FIXME how to warn the User about this? } } /** * 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(textBundle.get("ExportGraphicsFormatMenuItem")); //$NON-NLS-1$ item.setEnabled(GraphicsFileFilter.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 + 2 * gc.stringExtent(textBundle.get("WidestCharacter")).x; //$NON-NLS-1$ } else { headerWidths[i] = gc.stringExtent( textBundle.get("WidestCharacter")).x //$NON-NLS-1$ * 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 fileList) { int[] weights = sashForm.getWeights(); if (formatChanged) { fileTable.dispose(); fileTable = new Table(sashForm, SWT.MULTI | SWT.FULL_SELECTION | SWT.BORDER); fileTable.addListener(SWT.KeyUp, createFileKeyboardHandler()); fileTable.addListener(SWT.KeyUp, createToolbarCommandHandler()); fileTable.setHeaderVisible(true); fileTable.setMenu(createFilePopupMenu()); fileTable.addSelectionListener(new SelectionListener() { /** * Single-click handler. */ public void widgetSelected(SelectionEvent event) { getImportToolItem().setEnabled(getDisk(0).canCreateFile() && getDisk(0).canWriteFileData()); if (getFileTable().getSelectionCount() > 0) { FileEntry fileEntry = getSelectedFileEntry(); getExportToolItem().setEnabled(getDisk(0).canReadFileData()); getDeleteToolItem().setEnabled(getDisk(0).canDeleteFile()); getCompileToolItem().setEnabled(fileEntry != null && fileEntry.canCompile()); getViewFileToolItem().setEnabled(true); } else { getExportToolItem().setEnabled(false); getDeleteToolItem().setEnabled(false); getCompileToolItem().setEnabled(false); getViewFileToolItem().setEnabled(false); } } /** * Double-click handler. */ public void widgetDefaultSelected(SelectionEvent event) { try { viewFile(null); } catch (DiskException e) { // FIXME how to warn the User about this? } } }); TableColumn column = null; List headers = disks[0].getFileColumnHeaders(currentFormat); int[] widths = (int[])columnWidths.get(new Integer(currentFormat)); for (int i=0; i 1) ? textBundle.get("DeletePromptMultipleFiles") : //$NON-NLS-1$ textBundle.get("DeletePromptSingleFile") //$NON-NLS-1$ + textBundle.get("DeletePromptTrailer"); //$NON-NLS-1$ int answer = SwtUtil.showYesNoDialog(shell, textBundle.get("DeletePromptTitle"), //$NON-NLS-1$ message); if (answer == SWT.YES) { for (int i=0; i fileList = directory.getFiles(); formatChanged = (currentFormat != newFormat); if (formatChanged || !fileList.equals(currentFileList)) { preserveColumnWidths(); // must be done before assigning newFormat currentFormat = newFormat; fillFileTable(fileList); // Ensure that the control buttons are set appropriately. // Primarly required for keyboard interface. standardFormatToolItem.setSelection( currentFormat == FormattedDisk.FILE_DISPLAY_STANDARD); nativeFormatToolItem.setSelection( currentFormat == FormattedDisk.FILE_DISPLAY_NATIVE); detailFormatToolItem.setSelection( currentFormat == FormattedDisk.FILE_DISPLAY_DETAIL); } } /** * Handle SaveAs. */ protected void saveAs() { FileDialog fileDialog = new FileDialog(shell, SWT.SAVE); fileDialog.setFilterPath(userPreferences.getSaveDirectory()); fileDialog.setFileName(Host.getFileName(disks[0].getFilename())); fileDialog.setText(textBundle.get("SaveDiskImageAsPrompt")); //$NON-NLS-1$ String fullpath = fileDialog.open(); userPreferences.setSaveDirectory(fileDialog.getFilterPath()); if (fullpath == null) { return; // user pressed cancel } try { disks[0].saveAs(fullpath); diskWindow.setStandardWindowTitle(); saveToolItem.setEnabled(disks[0].hasChanged()); } catch (IOException ex) { showSaveError(ex); } } /** * Handle save. * If this is the first time a disk has been saved (a new image), * default to the SaveAs behavior. */ protected void save() { try { if (disks[0].isNewImage()) { saveAs(); // no directory -> 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(textBundle.get("SaveDiskImageErrorTitle")); //$NON-NLS-1$ box.setMessage(textBundle.format("SaveDiskImageErrorMessage", //$NON-NLS-1$ new Object[] { getDisk(0).getFilename(), errorMessage })); 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 = getDirectoryTree().getSelection(); setDirectoryExpandedStates(treeItem[0], false); break; case '+': treeItem = getDirectoryTree().getSelection(); setDirectoryExpandedStates(treeItem[0], true); break; } } else { // assume no control and no alt switch (event.character) { case '-': treeItem = getDirectoryTree().getSelection(); treeItem[0].setExpanded(false); break; case '+': treeItem = getDirectoryTree().getSelection(); treeItem[0].setExpanded(true); break; } } } } }; } /** * Open up the view file window for the currently selected file. * @throws DiskException */ protected void viewFile(Class fileFilterClass) throws DiskException { FileEntry fileEntry = getSelectedFileEntry(); if (fileEntry.isDeleted()) { SwtUtil.showErrorDialog(shell, textBundle.get("DeleteFileErrorTitle"), //$NON-NLS-1$ textBundle.get("DeleteFileErrorMessage")); //$NON-NLS-1$ } 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 = null; FileFilter fileFilter = null; try { fileFilter = (FileFilter) fileFilterClass.newInstance(); } catch (NullPointerException ex) { // This is expected } catch (InstantiationException e) { SwtUtil.showSystemErrorDialog(shell, e); } catch (IllegalAccessException e) { SwtUtil.showSystemErrorDialog(shell, e); } if (fileFilter != null) { window = new FileViewerWindow(shell, fileEntry, imageManager, fileFilter); } else { 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) try { switch (event.character) { case CTRL_C: // Compile Wizard if (getCompileToolItem().isEnabled()) { compileFileWizard(); } break; case CTRL_D: // Delete file if (getDeleteToolItem().isEnabled()) { deleteFile(); } break; case CTRL_E: // Export Wizard exportFileWizard(); break; case CTRL_V: // View file viewFile(null); break; } } catch (DiskException e) { // FIXME how to warn the User about this? } } }; } /** * 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 try { switch (event.character) { case CTRL_I: // Import Wizard importFiles(); break; case CTRL_P: // Print... print(); break; case CTRL_S: // Save if (getSaveToolItem().isEnabled()) { save(); } break; } } catch (DiskException e) { // FIXME how to warn the User about this? } } else { // No CTRL key if ((event.stateMask & SWT.ALT) != SWT.ALT) // Ignore ALT key combinations like alt-F4! try { 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 setShowDeletedFiles(!getShowDeletedFilesToolItem().getSelection()); getShowDeletedFilesToolItem().setSelection(isShowDeletedFiles()); fillFileTable(getCurrentFileList()); break; } } catch (DiskException e) { // FIXME how to warn the User about this? } } } } }; } /** * 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[] printColumnWidths; private int[] printColumnPosition; 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(getDisk(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 (getCurrentFormat() == FormattedDisk.FILE_DISPLAY_NATIVE) { fontSize = 10; } else if (getCurrentFormat() == FormattedDisk.FILE_DISPLAY_DETAIL) { fontSize = 8; } normalFont = new Font(printer, new String(), fontSize, SWT.NORMAL); headerFont = new Font(printer, new String(), fontSize, SWT.BOLD); for (int i=0; i= header.getTitle().length()) ? header.getMaximumWidth() : header.getTitle().length(); totalWidth+= widths[i]; } printColumnWidths = new int[fileHeaders.size()]; printColumnPosition = 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() { TextBundle textBundle = UiBundle.getInstance(); String text = textBundle.format("PageNumberText", Integer.toString(page)); //$NON-NLS-1$ 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) throws DiskException { Iterator iterator = directory.getFiles().iterator(); while (iterator.hasNext()) { FileEntry fileEntry = (FileEntry) iterator.next(); if (!fileEntry.isDeleted() || isShowDeletedFiles()) { List columns = fileEntry.getFileColumnData(getCurrentFormat()); for (int i=0; i getCurrentFileList() { return currentFileList; } protected ToolItem getSaveToolItem() { return saveToolItem; } protected ToolItem getShowDeletedFilesToolItem() { return showDeletedFilesToolItem; } }