/*
* 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.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.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.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.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
*/
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 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) {
changeCurrentFormat(getCurrentFormat()); // 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(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 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) {
viewFile(null);
}
});
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 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.
*/
protected void viewFile(Class fileFilterClass) {
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) {
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;
}
}
}
};
}
/**
* 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 (getSaveToolItem().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
setShowDeletedFiles(!getShowDeletedFilesToolItem().getSelection());
getShowDeletedFilesToolItem().setSelection(isShowDeletedFiles());
fillFileTable(getCurrentFileList());
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[] 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) {
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