/*
 * CiderPress
 * Copyright (C) 2007, 2008 by faddenSoft, LLC.  All Rights Reserved.
 * See the file LICENSE for distribution terms.
 */
/*
 * Main window management.
 */
#include "stdafx.h"
#include "Main.h"
#include "MyApp.h"
#include "AboutDialog.h"
#include "NufxArchive.h"
#include "DiskArchive.h"
#include "BNYArchive.h"
#include "ACUArchive.h"
#include "AppleSingleArchive.h"
#include "ArchiveInfoDialog.h"
#include "PrefsDialog.h"
#include "EnterRegDialog.h"
#include "OpenVolumeDialog.h"
#include "Print.h"
#include "../util/UtilLib.h"
#include "resource.h"

/* use MFC's fancy version of new for debugging */
//#define new DEBUG_NEW

static const WCHAR kWebSiteURL[] = L"http://www.a2ciderpress.com/";

/* custom class name for main frame */
static const WCHAR kMainWindowClassName[] = L"faddenSoft.CiderPress.4";


/*
 * Filters for the "open file" command.  In some cases a file may be opened
 * in more than one format, so it's necessary to keep track of what the
 * file filter was set to when the file was opened.
 *
 * With Vista-style dialogs, the second part of the string (the filespec)
 * will sometimes be included in the pop-up.  Sometimes not.  It's
 * deterministic but I haven't been able to figure out what the pattern is --
 * it's not simply length of a given filter or of the entire string, or based
 * on the presence of certain characters.  The filter works correctly, so it
 * doesn't seem to be malformed.  It's just ugly to have the open dialog
 * popup show an enormous, redundant filter string.
 *
 * I tried substituting '\0' for '|' and placing the string directly into
 * the dialog; no change.
 *
 * CFileDialog::ApplyOFNToShellDialog in {VisualStudio}\VC\atlmfc\src\mfc\dlgfile.cpp
 * appears to be doing the parsing.  Single-stepping through the code shows
 * that it's working fine, so something later on is choosing to merge
 * pszName and pszSpec when generating the pop-up menu.
 *
 * The good news is that if I exclude the list of extensions from the name
 * section, the popup will (so far) always includes the spec.  The bad news
 * is that this means we won't display the list of extensions on WinXP, which
 * uses the older style of dialog.  We could switch from public constants to
 * a function that generates the filter based on a bit mask and the current
 * OS version, but that might be more trouble than it's worth.
 */
const WCHAR MainWindow::kOpenNuFX[] =
    L"ShrinkIt Archives|*.shk;*.sdk;*.bxy;*.sea;*.bse|";
const WCHAR MainWindow::kOpenBinaryII[] =
    L"Binary II Archives|*.bny;*.bqy;*.bxy|";
const WCHAR MainWindow::kOpenACU[] =
    L"ACU Archives|*.acu|";
const WCHAR MainWindow::kOpenAppleSingle[] =
    L"AppleSingle files|*.as|";
const WCHAR MainWindow::kOpenDiskImage[] =
    L"Disk Images|"
    L"*.shk;*.sdk;*.dsk;*.po;*.do;*.d13;*.2mg;*.img;*.nib;*.nb2;*.raw;*.hdv;*.dc;*.dc6;*.ddd;*.app;*.fdi;*.iso;*.gz;*.zip|";
const WCHAR MainWindow::kOpenAll[] =
    L"All Files|*.*|";
const WCHAR MainWindow::kOpenEnd[] =
    L"|";

/*
 * Used when guessing archive type from extension when no "-mode" argument
 * was specified.
 *
 * This does *not* apply to files double-clicked from the content list; see
 * HandleDoubleClick() for that.
 */
static const struct {
    WCHAR extension[4];
    FilterIndex idx;
} gExtensionToIndex[] = {
    { L"shk",   kFilterIndexDiskImage },    // DiskImage probably better than NuFX
    { L"bxy",   kFilterIndexNuFX },
    { L"bse",   kFilterIndexNuFX },
    { L"sea",   kFilterIndexNuFX },
    { L"bny",   kFilterIndexBinaryII },
    { L"bqy",   kFilterIndexBinaryII },
    { L"acu",   kFilterIndexACU },
    { L"as",    kFilterIndexAppleSingle },
    { L"sdk",   kFilterIndexDiskImage },
    { L"dsk",   kFilterIndexDiskImage },
    { L"po",    kFilterIndexDiskImage },
    { L"do",    kFilterIndexDiskImage },
    { L"d13",   kFilterIndexDiskImage },
    { L"2mg",   kFilterIndexDiskImage },
    { L"img",   kFilterIndexDiskImage },
    { L"nib",   kFilterIndexDiskImage },
    { L"nb2",   kFilterIndexDiskImage },
    { L"raw",   kFilterIndexDiskImage },
    { L"hdv",   kFilterIndexDiskImage },
    { L"dc",    kFilterIndexDiskImage },
    { L"dc6",   kFilterIndexDiskImage },
    { L"ddd",   kFilterIndexDiskImage },
    { L"app",   kFilterIndexDiskImage },
    { L"fdi",   kFilterIndexDiskImage },
    { L"iso",   kFilterIndexDiskImage },
    { L"gz",    kFilterIndexDiskImage },    // assume disk image inside
    { L"zip",   kFilterIndexDiskImage },    // assume disk image inside
};

const WCHAR MainWindow::kModeNuFX[] = L"nufx";
const WCHAR MainWindow::kModeBinaryII[] = L"bin2";
const WCHAR MainWindow::kModeACU[] = L"acu";
const WCHAR MainWindow::kModeAppleSingle[] = L"as";
const WCHAR MainWindow::kModeDiskImage[] = L"disk";


/*
 * ===========================================================================
 *      MainWindow
 * ===========================================================================
 */

static const UINT gFindReplaceID = RegisterWindowMessage(FINDMSGSTRING);

BEGIN_MESSAGE_MAP(MainWindow, CFrameWnd)
    ON_WM_CREATE()
    ON_WM_CLOSE()
    ON_MESSAGE(WMU_LATE_INIT, OnLateInit)
    //ON_MESSAGE(WMU_CLOSE_MAIN_DIALOG, OnCloseMainDialog)
    ON_WM_SIZE()
    ON_WM_GETMINMAXINFO()
    ON_WM_PAINT()
    //ON_WM_MOUSEWHEEL()
    ON_WM_SETFOCUS()
    ON_WM_HELPINFO()
    ON_WM_QUERYENDSESSION()
    ON_WM_ENDSESSION()
    ON_REGISTERED_MESSAGE(gFindReplaceID, OnFindDialogMessage)
    ON_COMMAND(          IDM_FILE_NEW_ARCHIVE, OnFileNewArchive)
    ON_COMMAND(          IDM_FILE_OPEN, OnFileOpen)
    ON_COMMAND(          IDM_FILE_OPEN_VOLUME, OnFileOpenVolume)
    ON_UPDATE_COMMAND_UI(IDM_FILE_OPEN_VOLUME, OnUpdateFileOpenVolume)
    ON_COMMAND(          IDM_FILE_REOPEN, OnFileReopen)
    ON_UPDATE_COMMAND_UI(IDM_FILE_REOPEN, OnUpdateFileReopen)
    ON_COMMAND(          IDM_FILE_SAVE, OnFileSave)
    ON_UPDATE_COMMAND_UI(IDM_FILE_SAVE, OnUpdateFileSave)
    ON_COMMAND(          IDM_FILE_CLOSE, OnFileClose)
    ON_UPDATE_COMMAND_UI(IDM_FILE_CLOSE, OnUpdateFileClose)
    ON_COMMAND(          IDM_FILE_ARCHIVEINFO, OnFileArchiveInfo)
    ON_UPDATE_COMMAND_UI(IDM_FILE_ARCHIVEINFO, OnUpdateFileArchiveInfo)
    ON_COMMAND(          IDM_FILE_PRINT, OnFilePrint)
    ON_UPDATE_COMMAND_UI(IDM_FILE_PRINT, OnUpdateFilePrint)
    ON_COMMAND(          IDM_FILE_EXIT, OnFileExit)
    ON_COMMAND(          IDM_EDIT_COPY, OnEditCopy)
    ON_UPDATE_COMMAND_UI(IDM_EDIT_COPY, OnUpdateEditCopy)
    ON_COMMAND(          IDM_EDIT_PASTE, OnEditPaste)
    ON_UPDATE_COMMAND_UI(IDM_EDIT_PASTE, OnUpdateEditPaste)
    ON_COMMAND(          IDM_EDIT_PASTE_SPECIAL, OnEditPasteSpecial)
    ON_UPDATE_COMMAND_UI(IDM_EDIT_PASTE_SPECIAL, OnUpdateEditPasteSpecial)
    ON_COMMAND(          IDM_EDIT_FIND, OnEditFind)
    ON_UPDATE_COMMAND_UI(IDM_EDIT_FIND, OnUpdateEditFind)
    ON_COMMAND(          IDM_EDIT_SELECT_ALL, OnEditSelectAll)
    ON_UPDATE_COMMAND_UI(IDM_EDIT_SELECT_ALL, OnUpdateEditSelectAll)
    ON_COMMAND(          IDM_EDIT_INVERT_SELECTION, OnEditInvertSelection)
    ON_UPDATE_COMMAND_UI(IDM_EDIT_INVERT_SELECTION, OnUpdateEditInvertSelection)
    ON_COMMAND(          IDM_EDIT_PREFERENCES, OnEditPreferences)
    ON_COMMAND_RANGE(    IDM_SORT_PATHNAME, IDM_SORT_ORIGINAL, OnEditSort)
    ON_UPDATE_COMMAND_UI_RANGE(IDM_SORT_PATHNAME, IDM_SORT_ORIGINAL, OnUpdateEditSort)
    ON_COMMAND(          IDM_ACTIONS_VIEW, OnActionsView)
    ON_UPDATE_COMMAND_UI(IDM_ACTIONS_VIEW, OnUpdateActionsView)
    ON_COMMAND(          IDM_ACTIONS_ADD_FILES, OnActionsAddFiles)
    ON_UPDATE_COMMAND_UI(IDM_ACTIONS_ADD_FILES, OnUpdateActionsAddFiles)
    ON_COMMAND(          IDM_ACTIONS_ADD_DISKS, OnActionsAddDisks)
    ON_UPDATE_COMMAND_UI(IDM_ACTIONS_ADD_DISKS, OnUpdateActionsAddDisks)
    ON_COMMAND(          IDM_ACTIONS_CREATE_SUBDIR, OnActionsCreateSubdir)
    ON_UPDATE_COMMAND_UI(IDM_ACTIONS_CREATE_SUBDIR, OnUpdateActionsCreateSubdir)
    ON_COMMAND(          IDM_ACTIONS_EXTRACT, OnActionsExtract)
    ON_UPDATE_COMMAND_UI(IDM_ACTIONS_EXTRACT, OnUpdateActionsExtract)
    ON_COMMAND(          IDM_ACTIONS_TEST, OnActionsTest)
    ON_UPDATE_COMMAND_UI(IDM_ACTIONS_TEST, OnUpdateActionsTest)
    ON_COMMAND(          IDM_ACTIONS_DELETE, OnActionsDelete)
    ON_UPDATE_COMMAND_UI(IDM_ACTIONS_DELETE, OnUpdateActionsDelete)
    ON_COMMAND(          IDM_ACTIONS_RENAME, OnActionsRename)
    ON_UPDATE_COMMAND_UI(IDM_ACTIONS_RENAME, OnUpdateActionsRename)
    ON_COMMAND(          IDM_ACTIONS_RECOMPRESS, OnActionsRecompress)
    ON_UPDATE_COMMAND_UI(IDM_ACTIONS_RECOMPRESS, OnUpdateActionsRecompress)
    ON_COMMAND(          IDM_ACTIONS_OPENASDISK, OnActionsOpenAsDisk)
    ON_UPDATE_COMMAND_UI(IDM_ACTIONS_OPENASDISK, OnUpdateActionsOpenAsDisk)
    ON_COMMAND(          IDM_ACTIONS_EDIT_COMMENT, OnActionsEditComment)
    ON_UPDATE_COMMAND_UI(IDM_ACTIONS_EDIT_COMMENT, OnUpdateActionsEditComment)
    ON_COMMAND(          IDM_ACTIONS_EDIT_PROPS, OnActionsEditProps)
    ON_UPDATE_COMMAND_UI(IDM_ACTIONS_EDIT_PROPS, OnUpdateActionsEditProps)
    ON_COMMAND(          IDM_ACTIONS_RENAME_VOLUME, OnActionsRenameVolume)
    ON_UPDATE_COMMAND_UI(IDM_ACTIONS_RENAME_VOLUME, OnUpdateActionsRenameVolume)
    ON_COMMAND(          IDM_ACTIONS_CONV_DISK, OnActionsConvDisk)
    ON_UPDATE_COMMAND_UI(IDM_ACTIONS_CONV_DISK, OnUpdateActionsConvDisk)
    ON_COMMAND(          IDM_ACTIONS_CONV_FILE, OnActionsConvFile)
    ON_UPDATE_COMMAND_UI(IDM_ACTIONS_CONV_FILE, OnUpdateActionsConvFile)
    ON_COMMAND(          IDM_ACTIONS_CONV_TOWAV, OnActionsConvToWav)
    ON_UPDATE_COMMAND_UI(IDM_ACTIONS_CONV_TOWAV, OnUpdateActionsConvToWav)
    ON_COMMAND(          IDM_ACTIONS_CONV_FROMWAV, OnActionsConvFromWav)
    ON_UPDATE_COMMAND_UI(IDM_ACTIONS_CONV_FROMWAV, OnUpdateActionsConvFromWav)
    ON_COMMAND(          IDM_ACTIONS_IMPORT_BAS, OnActionsImportBAS)
    ON_UPDATE_COMMAND_UI(IDM_ACTIONS_IMPORT_BAS, OnUpdateActionsImportBAS)
    ON_COMMAND(          IDM_TOOLS_DISKEDIT, OnToolsDiskEdit)
    ON_COMMAND(          IDM_TOOLS_IMAGECREATOR, OnToolsDiskImageCreator)
    ON_COMMAND(          IDM_TOOLS_DISKCONV, OnToolsDiskConv)
    ON_COMMAND(          IDM_TOOLS_BULKDISKCONV, OnToolsBulkDiskConv)
    ON_COMMAND(          IDM_TOOLS_SST_MERGE, OnToolsSSTMerge)
    ON_COMMAND(          IDM_TOOLS_VOLUMECOPIER_VOLUME, OnToolsVolumeCopierVolume)
    ON_COMMAND(          IDM_TOOLS_VOLUMECOPIER_FILE, OnToolsVolumeCopierFile)
    ON_COMMAND(          IDM_TOOLS_EOLSCANNER, OnToolsEOLScanner)
    ON_COMMAND(          IDM_TOOLS_TWOIMGPROPS, OnToolsTwoImgProps)
    ON_COMMAND(          IDM_HELP_CONTENTS, OnHelpContents)
    ON_COMMAND(          IDM_HELP_WEBSITE, OnHelpWebSite)
    ON_COMMAND(          IDM_HELP_ORDERING, OnHelpOrdering)
    ON_COMMAND(          IDM_HELP_ABOUT, OnHelpAbout)
//  ON_COMMAND(          IDM_RTCLK_DEFAULT, OnRtClkDefault)

    ON_COMMAND(ID_HELP_FINDER, CFrameWnd::OnHelpFinder)
    ON_COMMAND(ID_HELP, CFrameWnd::OnHelp)
    ON_COMMAND(ID_CONTEXT_HELP, CFrameWnd::OnContextHelp)
    ON_COMMAND(ID_DEFAULT_HELP, CFrameWnd::OnHelpFinder)
END_MESSAGE_MAP()

/*
 * MainWindow constructor.  Creates the main window and sets
 * its properties.
 */
MainWindow::MainWindow()
{
    static const WCHAR kAppName[] = L"CiderPress";

    fpContentList = NULL;
    fpOpenArchive = NULL;
    //fpSelSet = NULL;
    fpActionProgress = NULL;
    fpProgressCounter = NULL;
    fpFindDialog = NULL;

    fFindDown = true;
    fFindMatchCase = false;
    fFindMatchWholeWord = false;

    fAbortPrinting = false;
    fhDevMode = NULL;
    fhDevNames = NULL;
    fNeedReopen = false;

    CString wndClass = AfxRegisterWndClass(
        CS_DBLCLKS /*| CS_HREDRAW | CS_VREDRAW*/,
        gMyApp.LoadStandardCursor(IDC_ARROW),
        NULL /*(HBRUSH) (COLOR_WINDOW + 1)*/,
        gMyApp.LoadIcon(IDR_MAINFRAME) );

    Create(wndClass, kAppName, WS_OVERLAPPEDWINDOW /*| WS_CLIPCHILDREN*/,
        rectDefault, NULL, MAKEINTRESOURCE(IDR_MAINFRAME));

    LoadAccelTable(MAKEINTRESOURCE(IDR_MAINFRAME));

    // initialize some OLE garbage
    AfxOleInit();

    // required by MFC if Rich Edit controls are used
    AfxInitRichEdit();

    // required??
    //AfxEnableControlContainer();

    SetCPTitle();

    int cc = PostMessage(WMU_LATE_INIT, 0, 0);
    ASSERT(cc != 0);
}

/*
 * MainWindow destructor.  Close the archive if one is open, but don't try
 * to shut down any controls in child windows.  By this point, Windows has
 * already snuffed them.
 */
MainWindow::~MainWindow()
{
    LOGI("~MainWindow");

    //LOGI("MainWindow destructor");
    CloseArchiveWOControls();

    //int cc;
    //cc = ::WinHelp(m_hWnd, ::AfxGetApp()->m_pszHelpFilePath, HELP_QUIT, 0);
    //LOGI("Turning off WinHelp returned %d", cc);
    ::HtmlHelp(NULL, NULL, HH_CLOSE_ALL, 0);

    // free stuff used by print dialog
    ::GlobalFree(fhDevMode);
    ::GlobalFree(fhDevNames);

    fPreferences.SaveToRegistry();
    LOGI("MainWindow destructor complete");
}

void MainWindow::OnClose()
{
    SaveWinPlacement();
    CFrameWnd::OnClose();
}

BOOL MainWindow::PreCreateWindow(CREATESTRUCT& cs)
{
    BOOL res = CFrameWnd::PreCreateWindow(cs);

    cs.dwExStyle &= ~(WS_EX_CLIENTEDGE);

    // This changes the window class name to a value that the installer can
    // detect.  This allows us to prevent installation while CiderPress is
    // running.  (If we don't do that, the installation will offer to reboot
    // the computer to complete installation.)
    WNDCLASS wndCls;
    if (!GetClassInfo(AfxGetInstanceHandle(), cs.lpszClass, &wndCls)) {
        LOGW("GetClassInfo failed");
    } else {
        cs.lpszClass = kMainWindowClassName;
        wndCls.lpszClassName = kMainWindowClassName;
        if (!AfxRegisterClass(&wndCls)) {
            LOGW("AfxRegisterClass failed");
        }
    }

    return res;
}

void MainWindow::GetClientRect(LPRECT lpRect) const
{
    CRect sizeRect;
    int toolBarHeight, statusBarHeight;

    fToolBar.GetWindowRect(&sizeRect);
    toolBarHeight = sizeRect.bottom - sizeRect.top;
    fStatusBar.GetWindowRect(&sizeRect);
    statusBarHeight = sizeRect.bottom - sizeRect.top;

    //LOGI("HEIGHTS = %d/%d", toolBarHeight, statusBarHeight);
    CFrameWnd::GetClientRect(lpRect);
    lpRect->top += toolBarHeight;
    lpRect->bottom -= statusBarHeight;
}

void MainWindow::DoIdle(void)
{
    /*
     * Make sure that the filename field in the content list is always
     * visible, since that what the user clicks on to select things.  Would
     * be nice to have a way to prevent it, but for now we'll just shove
     * things back where they're supposed to be.
     */
    if (fpContentList != NULL) {
        /* get the current column 0 width, with current user adjustments */
        fpContentList->ExportColumnWidths();
        int width = fPreferences.GetColumnLayout()->GetColumnWidth(0);

        if (width >= 0 && width < ColumnLayout::kMinCol0Width) {
            /* column is too small, but don't change it until user lets mouse up */
            if (::GetAsyncKeyState(VK_LBUTTON) >= 0) {
                LOGI("Resetting column 0 width");
                fPreferences.GetColumnLayout()->SetColumnWidth(0,
                    ColumnLayout::kMinCol0Width);
                fpContentList->NewColumnWidths();
            }
        }
    }

    /*
     * Put an asterisk at the end of the title if we have an open archive
     * and it has pending modifications.  Remove it if nothing is pending.
     */
    if (fpOpenArchive != NULL) {
        CString title;
        int len;

        GetWindowText(/*ref*/ title);
        len = title.GetLength();
        if (len > 0 && title.GetAt(len-1) == '*') {
            if (!fpOpenArchive->IsModified()) {
                /* remove the asterisk and the preceeding space */
                title.Delete(len-2, 2);
                SetWindowText(title);
            }
        } else {
            if (fpOpenArchive->IsModified()) {
                /* add an asterisk */
                title += " *";
                SetWindowText(title);
            }
        }
    }
}

void MainWindow::ProcessCommandLine(void)
{
    /*
     * Get the command line and break it down into an argument vector.
     *
     * Usage:
     *  CiderPress [[-temparc] [-mode {nufx,bin2,disk}] [-dispname name] filename]
     */
    const WCHAR* cmdLine = ::GetCommandLine();
    if (cmdLine == NULL || wcslen(cmdLine) == 0)
        return;

    WCHAR* mangle = wcsdup(cmdLine);
    if (mangle == NULL)
        return;

    LOGI("Mangling '%ls'", mangle);
    WCHAR* argv[8];
    int argc = 8;
    VectorizeString(mangle, argv, &argc);

    LOGI("Args:");
    for (int i = 0; i < argc; i++) {
        LOGI("  %d '%ls'", i, argv[i]);
    }

    /*
     * Figure out what the arguments are.
     */
    const WCHAR* filename = NULL;
    const WCHAR* dispName = NULL;
    FilterIndex filterIndex = kFilterIndexGeneric;
    bool temp = false;

    for (int i = 1; i < argc; i++) {
        if (argv[i][0] == '-') {
            if (wcsicmp(argv[i], L"-mode") == 0) {
                if (i == argc-1) {
                    LOGI("WARNING: -mode specified without mode");
                } else
                    i++;
                if (wcsicmp(argv[i], kModeNuFX) == 0)
                    filterIndex = kFilterIndexNuFX;
                else if (wcsicmp(argv[i], kModeBinaryII) == 0)
                    filterIndex = kFilterIndexBinaryII;
                else if (wcsicmp(argv[i], kModeACU) == 0)
                    filterIndex = kFilterIndexACU;
                else if (wcsicmp(argv[i], kModeAppleSingle) == 0)
                    filterIndex = kFilterIndexAppleSingle;
                else if (wcsicmp(argv[i], kModeDiskImage) == 0)
                    filterIndex = kFilterIndexDiskImage;
                else {
                    LOGI("WARNING: unrecognized mode '%ls'", argv[i]);
                }
            } else if (wcsicmp(argv[i], L"-dispname") == 0) {
                if (i == argc-1) {
                    LOGI("WARNING: -dispname specified without name");
                } else
                    i++;
                dispName = argv[i];
            } else if (wcsicmp(argv[i], L"-temparc") == 0) {
                temp = true;
            } else if (wcsicmp(argv[i], L"-install") == 0) {
                // see MyApp::InitInstance
                LOGI("Got '-install' flag, doing nothing");
            } else if (wcsicmp(argv[i], L"-uninstall") == 0) {
                // see MyApp::InitInstance
                LOGI("Got '-uninstall' flag, doing nothing");
            } else {
                LOGI("WARNING: unrecognized flag '%ls'", argv[i]);
            }
        } else {
            /* must be the filename */
            if (i != argc-1) {
                LOGI("WARNING: ignoring extra arguments (e.g. '%ls')",
                    argv[i+1]);
            }
            filename = argv[i];
            break;
        }
    }
    if (argc != 1 && filename == NULL) {
        LOGI("WARNING: args specified but no filename found");
    }

    LOGI("Argument handling:");
    LOGI(" index=%d temp=%d filename='%ls'",
        filterIndex, temp, filename == NULL ? L"(null)" : filename);

    if (filename != NULL) {
        PathName path(filename);
        CString ext = path.GetExtension();

        // drop the leading '.' from the extension
        if (ext.Left(1) == ".")
            ext.Delete(0, 1);

        /* load the archive, mandating read-only if it's a temporary file */
        if (LoadArchive(filename, ext, filterIndex, temp) == 0) {
            /* success, update title bar */
            if (temp)
                fOpenArchivePathName = path.GetFileName();
            else
                fOpenArchivePathName = filename;
            if (dispName != NULL)
                fOpenArchivePathName = dispName;
            SetCPTitle(fOpenArchivePathName, fpOpenArchive);
        }

        /* if it's a temporary file, arrange to have it deleted before exit */
        if (temp) {
            int len = wcslen(filename);

            if (len > 4 && wcsicmp(filename + (len-4), L".tmp") == 0) {
                fDeleteList.Add(filename);
            } else {
                LOGI("NOT adding '%ls' to DeleteList -- does not end in '.tmp'",
                    filename);
            }
        }
    }

    free(mangle);
}


/*
 * ===================================
 *      Command handlers
 * ===================================
 */

const int kProgressPane = 1;

int MainWindow::OnCreate(LPCREATESTRUCT lpcs)
{
    // add a toolbar and status bar

    LOGI("Now in OnCreate!");
    if (CFrameWnd::OnCreate(lpcs) == -1)
        return -1;

    /*
     * Create the tool bar.
     */
#if 0
    static UINT buttonList[] = {
        IDM_FILE_OPEN,
        IDM_FILE_NEW_ARCHIVE,
        // spacer
        IDM_FILE_PRINT,
    };
#endif
    fToolBar.Create(this, WS_CHILD | WS_VISIBLE | CBRS_TOP |
        CBRS_TOOLTIPS | CBRS_FLYBY);
    fToolBar.LoadToolBar(IDR_TOOLBAR1);

    /*
     * Create the status bar.
     */
    static UINT indicators[] = { ID_SEPARATOR, ID_INDICATOR_COMPLETE };
    fStatusBar.Create(this);
    fStatusBar.SetIndicators(indicators, NELEM(indicators));
    //fStatusBar.SetPaneInfo(0, ID_SEPARATOR, SBPS_NOBORDERS | SBPS_STRETCH, 0);

    fStatusBar.SetPaneText(kProgressPane, L"");

    RestoreWinPlacement();

    return 0;
}

LONG MainWindow::OnLateInit(UINT, LONG)
{
    /*
     * Catch a message sent to inspire us to perform one-time initializations of
     * preferences and libraries.
     *
     * We're doing this the long way around because we want to be able to
     * put up a dialog box if the version is bad.  If we tried to handle this
     * in the constructor we'd be acting before the window was fully created.
     */
    CString result;
    CString appName;
    CString niftyListFile;

    CheckedLoadString(&appName, IDS_MB_APP_NAME);

    LOGI("----- late init begins -----");

    /*
     * Handle all other messages.  This gives the framework a chance to dim
     * all of the toolbar buttons.  This is especially useful when opening
     * a file from the command line that doesn't exist, causing an error
     * dialog and blocking main window messages.
     */
    PeekAndPump();

    /*
     * Initialize libraries.  This includes a version check.
     */
    result = NufxArchive::AppInit();
    if (!result.IsEmpty())
        goto fail;
    result = DiskArchive::AppInit();
    if (!result.IsEmpty())
        goto fail;
    result = BnyArchive::AppInit();
    if (!result.IsEmpty())
        goto fail;

    niftyListFile = gMyApp.GetExeBaseName();
    niftyListFile += "NList.Data";
    if (!NiftyList::AppInit(niftyListFile)) {
        CString file2 = niftyListFile + ".TXT";
        if (!NiftyList::AppInit(file2)) {
            CString msg;
            msg.Format(IDS_NLIST_DATA_FAILED, niftyListFile, file2);
            MessageBox(msg, appName, MB_OK);
        }
    }

    /*
     * Read preferences from registry.
     */
    fPreferences.LoadFromRegistry();

#if 0
    /*
     * Check to see if we're registered; if we're not, and we've expired, it's
     * time to bail out.
     */
    MyRegistry::RegStatus regStatus;
    //regStatus = gMyApp.fRegistry.CheckRegistration(&result);
    regStatus = MyRegistry::kRegValid;
    LOGI("CheckRegistration returned %d", regStatus);
    switch (regStatus) {
    case MyRegistry::kRegNotSet:
    case MyRegistry::kRegValid:
        ASSERT(result.IsEmpty());
        break;
    case MyRegistry::kRegExpired:
    case MyRegistry::kRegInvalid:
        MessageBox(result, appName, MB_OK|MB_ICONINFORMATION);
        LOGI("FORCING REG");
        if (EnterRegDialog::GetRegInfo(this) != 0) {
            result = "";
            goto fail;
        }
        SetCPTitle();       // update title bar with new reg info
        break;
    case MyRegistry::kRegFailed:
        ASSERT(!result.IsEmpty());
        goto fail;
    default:
        ASSERT(false);
        CString confused;
        confused.Format(L"Registration check failed. %ls", (LPCWSTR) result);
        result = confused;
        goto fail;
    }
#endif

    /*
     * Process command-line options, possibly loading an archive.
     */
    ProcessCommandLine();

    return 0;

fail:
    if (!result.IsEmpty())
        ShowFailureMsg(this, result, IDS_FAILED);
    int cc = PostMessage(WM_CLOSE, 0, 0);
    ASSERT(cc != 0);

    return 0;
}

BOOL MainWindow::OnQueryEndSession(void)
{
    // The system wants to know if we're okay with shutting down.
    LOGI("Got QueryEndSession");
    return TRUE;
}

void MainWindow::OnEndSession(BOOL bEnding)
{
    LOGI("Got EndSession (bEnding=%d)", bEnding);

    if (bEnding) {
        CloseArchiveWOControls();

        fPreferences.SaveToRegistry();
    }
}

void MainWindow::OnSize(UINT nType, int cx, int cy)
{
    /*
     * The main window is resizing.  We don't automatically redraw on resize,
     * so we will need to update the client region.  If it's filled with a
     * control, the control's resize & redraw function will take care of it.
     * If not, we need to explicitly invalidate the client region so the
     * window will repaint itself.
     */
    CFrameWnd::OnSize(nType, cx, cy);
    ResizeClientArea();
}

void MainWindow::ResizeClientArea(void)
{
    CRect sizeRect;
    
    GetClientRect(&sizeRect);
    if (fpContentList != NULL)
        fpContentList->MoveWindow(sizeRect);
    else
        Invalidate(false);
}

void MainWindow::OnGetMinMaxInfo(MINMAXINFO* pMMI)
{
    // Restrict the minimum window size to something reasonable.
    pMMI->ptMinTrackSize.x = 256;
    pMMI->ptMinTrackSize.y = 192;
}

void MainWindow::OnPaint(void)
{
    // Repaint the main window.
    CPaintDC dc(this);
    CRect clientRect;

    GetClientRect(&clientRect);

    /*
     * If there's no control in the window, fill in the client area with
     * what looks like an empty MDI client rect.
     */
    if (fpContentList == NULL) {
        DrawEmptyClientArea(&dc, clientRect);
    }

#if 0
    CPen pen(PS_SOLID, 1, RGB(255, 0, 0));  // red pen, 1 pixel wide
    CPen* pOldPen = dc.SelectObject(&pen);

    dc.MoveTo(clientRect.left, clientRect.top);
    dc.LineTo(clientRect.right-1, clientRect.top);
    dc.LineTo(clientRect.right, clientRect.bottom);
    dc.LineTo(clientRect.left, clientRect.bottom-1);
    dc.LineTo(clientRect.left, clientRect.top);

    dc.SelectObject(pOldPen);
#endif
}

#if 0
afx_msg BOOL MainWindow::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
{
    LOGI("MOUSE WHEEL");
    return FALSE;

    WPARAM wparam;
    LPARAM lparam;

    wparam = nFlags | (zDelta << 16);
    lparam = pt.x | (pt.y << 16);
    if (fpContentList != NULL)
        fpContentList->SendMessage(WM_MOUSEWHEEL, wparam, lparam);
    return CWnd::OnMouseWheel(nFlags, zDelta, pt);
//  return TRUE;
}
#endif

void MainWindow::OnSetFocus(CWnd* /*pOldWnd*/)
{
    // Make sure open controls keep the input focus.
    if (fpContentList != NULL) {
        LOGD("Returning focus to ContentList");
        fpContentList->SetFocus();
    }
}

BOOL MainWindow::OnHelpInfo(HELPINFO* /*lpHelpInfo*/)
{
    LOGD("MainWindow::OnHelpInfo");
    MyApp::HandleHelp(this, HELP_TOPIC_WELCOME);
    return TRUE;
}

void MainWindow::OnEditPreferences(void)
{
    // Handle Edit->Preferences by popping up a property sheet.

    PrefsSheet ps;
    ColumnLayout* pColLayout = fPreferences.GetColumnLayout();

    /* pull any user header tweaks out of list so we can configure prefs */
    if (fpContentList != NULL)
        fpContentList->ExportColumnWidths();

    /* set up PrefsGeneralPage */
    for (int i = 0; i < kNumVisibleColumns; i++) {
        ps.fGeneralPage.fColumn[i] = (pColLayout->GetColumnWidth(i) != 0);
    }
    ps.fGeneralPage.fMimicShrinkIt = fPreferences.GetPrefBool(kPrMimicShrinkIt);
    ps.fGeneralPage.fBadMacSHK = fPreferences.GetPrefBool(kPrBadMacSHK);
    ps.fGeneralPage.fReduceSHKErrorChecks = fPreferences.GetPrefBool(kPrReduceSHKErrorChecks);
    ps.fGeneralPage.fCoerceDOSFilenames = fPreferences.GetPrefBool(kPrCoerceDOSFilenames);
    ps.fGeneralPage.fSpacesToUnder = fPreferences.GetPrefBool(kPrSpacesToUnder);
    ps.fGeneralPage.fPasteJunkPaths = fPreferences.GetPrefBool(kPrPasteJunkPaths);
    ps.fGeneralPage.fBeepOnSuccess = fPreferences.GetPrefBool(kPrBeepOnSuccess);

    /* set up PrefsDiskImagePage */
    ps.fDiskImagePage.fQueryImageFormat = fPreferences.GetPrefBool(kPrQueryImageFormat);
    ps.fDiskImagePage.fOpenVolumeRO = fPreferences.GetPrefBool(kPrOpenVolumeRO);
    ps.fDiskImagePage.fOpenVolumePhys0 = fPreferences.GetPrefBool(kPrOpenVolumePhys0);
    ps.fDiskImagePage.fProDOSAllowLower = fPreferences.GetPrefBool(kPrProDOSAllowLower);
    ps.fDiskImagePage.fProDOSUseSparse = fPreferences.GetPrefBool(kPrProDOSUseSparse);

    /* set up PrefsCompressionPage */
    ps.fCompressionPage.fCompressType = fPreferences.GetPrefLong(kPrCompressionType);

    /* set up PrefsFviewPage */
    ps.fFviewPage.fMaxViewFileSizeKB =
        (fPreferences.GetPrefLong(kPrMaxViewFileSize) + 1023) / 1024;
    ps.fFviewPage.fNoWrapText = fPreferences.GetPrefBool(kPrNoWrapText);

    ps.fFviewPage.fHighlightHexDump = fPreferences.GetPrefBool(kPrHighlightHexDump);
    ps.fFviewPage.fHighlightBASIC = fPreferences.GetPrefBool(kPrHighlightBASIC);
    ps.fFviewPage.fConvDisasmOneByteBrkCop = fPreferences.GetPrefBool(kPrDisasmOneByteBrkCop);
    ps.fFviewPage.fConvMouseTextToASCII = fPreferences.GetPrefBool(kPrConvMouseTextToASCII);
    ps.fFviewPage.fConvHiResBlackWhite = fPreferences.GetPrefBool(kPrConvHiResBlackWhite);
    ps.fFviewPage.fConvDHRAlgorithm = fPreferences.GetPrefLong(kPrConvDHRAlgorithm);
    ps.fFviewPage.fRelaxGfxTypeCheck = fPreferences.GetPrefBool(kPrRelaxGfxTypeCheck);
//  ps.fFviewPage.fEOLConvRaw = fPreferences.GetPrefBool(kPrEOLConvRaw);
//  ps.fFviewPage.fConvHighASCII = fPreferences.GetPrefBool(kPrConvHighASCII);
    ps.fFviewPage.fConvTextEOL_HA = fPreferences.GetPrefBool(kPrConvTextEOL_HA);
    ps.fFviewPage.fConvCPMText = fPreferences.GetPrefBool(kPrConvCPMText);
    ps.fFviewPage.fConvPascalText = fPreferences.GetPrefBool(kPrConvPascalText);
    ps.fFviewPage.fConvPascalCode = fPreferences.GetPrefBool(kPrConvPascalCode);
    ps.fFviewPage.fConvApplesoft = fPreferences.GetPrefBool(kPrConvApplesoft);
    ps.fFviewPage.fConvInteger = fPreferences.GetPrefBool(kPrConvInteger);
    ps.fFviewPage.fConvBusiness = fPreferences.GetPrefBool(kPrConvBusiness);
    ps.fFviewPage.fConvGWP = fPreferences.GetPrefBool(kPrConvGWP);
    ps.fFviewPage.fConvText8 = fPreferences.GetPrefBool(kPrConvText8);
    ps.fFviewPage.fConvAWP = fPreferences.GetPrefBool(kPrConvAWP);
    ps.fFviewPage.fConvADB = fPreferences.GetPrefBool(kPrConvADB);
    ps.fFviewPage.fConvASP = fPreferences.GetPrefBool(kPrConvASP);
    ps.fFviewPage.fConvSCAssem = fPreferences.GetPrefBool(kPrConvSCAssem);
    ps.fFviewPage.fConvDisasm = fPreferences.GetPrefBool(kPrConvDisasm);
    ps.fFviewPage.fConvHiRes = fPreferences.GetPrefBool(kPrConvHiRes);
    ps.fFviewPage.fConvDHR = fPreferences.GetPrefBool(kPrConvDHR);
    ps.fFviewPage.fConvSHR = fPreferences.GetPrefBool(kPrConvSHR);
    ps.fFviewPage.fConvPrintShop = fPreferences.GetPrefBool(kPrConvPrintShop);
    ps.fFviewPage.fConvMacPaint = fPreferences.GetPrefBool(kPrConvMacPaint);
    ps.fFviewPage.fConvProDOSFolder = fPreferences.GetPrefBool(kPrConvProDOSFolder);
    ps.fFviewPage.fConvResources = fPreferences.GetPrefBool(kPrConvResources);

    /* set up PrefsFilesPage */
    ps.fFilesPage.fTempPath = fPreferences.GetPrefString(kPrTempPath);
    ps.fFilesPage.fExtViewerExts = fPreferences.GetPrefString(kPrExtViewerExts);

    if (ps.DoModal() == IDOK)
        ApplyNow(&ps);
}

void MainWindow::ApplyNow(PrefsSheet* pPS)
{
    // Apply a change from the preferences sheet.
    LOGV("APPLY CHANGES");

    bool mustReload = false;

    ColumnLayout* pColLayout = fPreferences.GetColumnLayout();

    if (pPS->fGeneralPage.fDefaultsPushed) {
        /* reset all sizes to defaults, then factor in checkboxes */
        LOGI(" Resetting all widths to defaults");

        /* copy defaults over */
        for (int i = 0; i < kNumVisibleColumns; i++)
            pColLayout->SetColumnWidth(i, ColumnLayout::kWidthDefaulted);
    }

    /* handle column checkboxes */
    for (int i = 0; i < kNumVisibleColumns; i++) {
        if (pColLayout->GetColumnWidth(i) == 0 &&
            pPS->fGeneralPage.fColumn[i])
        {
            /* restore column */
            LOGI(" Column %d restored", i);
            pColLayout->SetColumnWidth(i, ColumnLayout::kWidthDefaulted);
        } else if (pColLayout->GetColumnWidth(i) != 0 &&
            !pPS->fGeneralPage.fColumn[i])
        {
            /* disable column */
            LOGI(" Column %d hidden", i);
            pColLayout->SetColumnWidth(i, 0);
        }
    }
    if (fpContentList != NULL)
        fpContentList->NewColumnWidths();
    fPreferences.SetPrefBool(kPrMimicShrinkIt,
        pPS->fGeneralPage.fMimicShrinkIt != 0);
    fPreferences.SetPrefBool(kPrBadMacSHK, pPS->fGeneralPage.fBadMacSHK != 0);
    fPreferences.SetPrefBool(kPrReduceSHKErrorChecks,
        pPS->fGeneralPage.fReduceSHKErrorChecks != 0);
    if (fPreferences.GetPrefBool(kPrCoerceDOSFilenames)!=
        (pPS->fGeneralPage.fCoerceDOSFilenames != 0))
    {
        LOGI("DOS filename coercion pref now %d",
            pPS->fGeneralPage.fCoerceDOSFilenames);
        fPreferences.SetPrefBool(kPrCoerceDOSFilenames,
            pPS->fGeneralPage.fCoerceDOSFilenames != 0);
        mustReload = true;
    }
    if (fPreferences.GetPrefBool(kPrSpacesToUnder) !=
        (pPS->fGeneralPage.fSpacesToUnder != 0))
    {
        LOGI("Spaces-to-underscores now %d", pPS->fGeneralPage.fSpacesToUnder);
        fPreferences.SetPrefBool(kPrSpacesToUnder, pPS->fGeneralPage.fSpacesToUnder != 0);
        mustReload = true;
    }
    fPreferences.SetPrefBool(kPrPasteJunkPaths, pPS->fGeneralPage.fPasteJunkPaths != 0);
    fPreferences.SetPrefBool(kPrBeepOnSuccess, pPS->fGeneralPage.fBeepOnSuccess != 0);

    if (pPS->fGeneralPage.fOurAssociations != NULL) {
        LOGI("NEW ASSOCIATIONS!");

#ifdef CAN_UPDATE_FILE_ASSOC
        for (int assoc = 0; assoc < gMyApp.fRegistry.GetNumFileAssocs(); assoc++)
        {
            gMyApp.fRegistry.SetFileAssoc(assoc,
                pPS->fGeneralPage.fOurAssociations[assoc]);
        }
#endif

        /* delete them so, if they hit "apply" again, we only update once */
        delete[] pPS->fGeneralPage.fOurAssociations;
        pPS->fGeneralPage.fOurAssociations = NULL;

        LOGV("Sending association-change notification to Windows shell");
        ::SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL);
    }

    fPreferences.SetPrefBool(kPrQueryImageFormat, pPS->fDiskImagePage.fQueryImageFormat != 0);
    fPreferences.SetPrefBool(kPrOpenVolumeRO, pPS->fDiskImagePage.fOpenVolumeRO != 0);
    fPreferences.SetPrefBool(kPrOpenVolumePhys0, pPS->fDiskImagePage.fOpenVolumePhys0 != 0);
    fPreferences.SetPrefBool(kPrProDOSAllowLower, pPS->fDiskImagePage.fProDOSAllowLower != 0);
    fPreferences.SetPrefBool(kPrProDOSUseSparse, pPS->fDiskImagePage.fProDOSUseSparse != 0);

    fPreferences.SetPrefLong(kPrCompressionType, pPS->fCompressionPage.fCompressType);

    fPreferences.SetPrefLong(kPrMaxViewFileSize, pPS->fFviewPage.fMaxViewFileSizeKB * 1024);
    fPreferences.SetPrefBool(kPrNoWrapText, pPS->fFviewPage.fNoWrapText != 0);

    fPreferences.SetPrefBool(kPrHighlightHexDump, pPS->fFviewPage.fHighlightHexDump != 0);
    fPreferences.SetPrefBool(kPrHighlightBASIC, pPS->fFviewPage.fHighlightBASIC != 0);
    fPreferences.SetPrefBool(kPrDisasmOneByteBrkCop, pPS->fFviewPage.fConvDisasmOneByteBrkCop != 0);
    fPreferences.SetPrefBool(kPrConvMouseTextToASCII, pPS->fFviewPage.fConvMouseTextToASCII != 0);
    fPreferences.SetPrefBool(kPrConvHiResBlackWhite, pPS->fFviewPage.fConvHiResBlackWhite != 0);
    fPreferences.SetPrefLong(kPrConvDHRAlgorithm, pPS->fFviewPage.fConvDHRAlgorithm);
    fPreferences.SetPrefBool(kPrRelaxGfxTypeCheck, pPS->fFviewPage.fRelaxGfxTypeCheck != 0);
//  fPreferences.SetPrefBool(kPrEOLConvRaw, pPS->fFviewPage.fEOLConvRaw != 0);
//  fPreferences.SetPrefBool(kPrConvHighASCII, pPS->fFviewPage.fConvHighASCII != 0);
    fPreferences.SetPrefBool(kPrConvTextEOL_HA, pPS->fFviewPage.fConvTextEOL_HA != 0);
    fPreferences.SetPrefBool(kPrConvCPMText, pPS->fFviewPage.fConvCPMText != 0);
    fPreferences.SetPrefBool(kPrConvPascalText, pPS->fFviewPage.fConvPascalText != 0);
    fPreferences.SetPrefBool(kPrConvPascalCode, pPS->fFviewPage.fConvPascalCode != 0);
    fPreferences.SetPrefBool(kPrConvApplesoft, pPS->fFviewPage.fConvApplesoft != 0);
    fPreferences.SetPrefBool(kPrConvInteger, pPS->fFviewPage.fConvInteger != 0);
    fPreferences.SetPrefBool(kPrConvBusiness, pPS->fFviewPage.fConvBusiness != 0);
    fPreferences.SetPrefBool(kPrConvGWP, pPS->fFviewPage.fConvGWP != 0);
    fPreferences.SetPrefBool(kPrConvText8, pPS->fFviewPage.fConvText8 != 0);
    fPreferences.SetPrefBool(kPrConvAWP, pPS->fFviewPage.fConvAWP != 0);
    fPreferences.SetPrefBool(kPrConvADB, pPS->fFviewPage.fConvADB != 0);
    fPreferences.SetPrefBool(kPrConvASP, pPS->fFviewPage.fConvASP != 0);
    fPreferences.SetPrefBool(kPrConvSCAssem, pPS->fFviewPage.fConvSCAssem != 0);
    fPreferences.SetPrefBool(kPrConvDisasm, pPS->fFviewPage.fConvDisasm != 0);
    fPreferences.SetPrefBool(kPrConvHiRes, pPS->fFviewPage.fConvHiRes != 0);
    fPreferences.SetPrefBool(kPrConvDHR, pPS->fFviewPage.fConvDHR != 0);
    fPreferences.SetPrefBool(kPrConvSHR, pPS->fFviewPage.fConvSHR != 0);
    fPreferences.SetPrefBool(kPrConvPrintShop, pPS->fFviewPage.fConvPrintShop != 0);
    fPreferences.SetPrefBool(kPrConvMacPaint, pPS->fFviewPage.fConvMacPaint != 0);
    fPreferences.SetPrefBool(kPrConvProDOSFolder, pPS->fFviewPage.fConvProDOSFolder != 0);
    fPreferences.SetPrefBool(kPrConvResources, pPS->fFviewPage.fConvResources != 0);

    fPreferences.SetPrefString(kPrTempPath, pPS->fFilesPage.fTempPath);
    LOGI("--- Temp path now '%ls'", fPreferences.GetPrefString(kPrTempPath));
    fPreferences.SetPrefString(kPrExtViewerExts, pPS->fFilesPage.fExtViewerExts);


//  if ((pPS->fGeneralPage.fShowToolbarText != 0) != fPreferences.GetShowToolbarText()) {
//      fPreferences.SetShowToolbarText(pPS->fGeneralPage.fShowToolbarText != 0);
//      //SetToolbarTextMode();
//      ResizeClientArea();
//  }

    /* allow open archive to track changes to preferences */
    if (fpOpenArchive != NULL)
        fpOpenArchive->PreferencesChanged();

    if (mustReload) {
        LOGI("Preferences apply requesting GA/CL reload");
        if (fpOpenArchive != NULL)
            fpOpenArchive->Reload();
        if (fpContentList != NULL)
            fpContentList->Reload();
    }

    /* export to registry */
    fPreferences.SaveToRegistry();

    //Invalidate();
}

void MainWindow::OnEditFind(void)
{
    // Handle IDM_EDIT_FIND.

    DWORD flags = 0;

    if (fpFindDialog != NULL)
        return;

    if (fFindDown)
        flags |= FR_DOWN;
    if (fFindMatchCase)
        flags |= FR_MATCHCASE;
    if (fFindMatchWholeWord)
        flags |= FR_WHOLEWORD;

    fpFindDialog = new CFindReplaceDialog;

    fpFindDialog->Create(TRUE,      // "find" only
                         fFindLastStr,  // default string to search for
                         NULL,      // default string to replace
                         flags,     // flags
                         this);     // parent
}

void MainWindow::OnUpdateEditFind(CCmdUI* pCmdUI)
{
    pCmdUI->Enable(fpOpenArchive != NULL);
}

LRESULT MainWindow::OnFindDialogMessage(WPARAM wParam, LPARAM lParam)
{
    // Handle activity in the modeless "find" dialog.

    assert(fpFindDialog != NULL);

    fFindDown = (fpFindDialog->SearchDown() != 0);
    fFindMatchCase = (fpFindDialog->MatchCase() != 0);
    fFindMatchWholeWord = (fpFindDialog->MatchWholeWord() != 0);

    if (fpFindDialog->IsTerminating()) {
        fpFindDialog = NULL;
        return 0;
    }

    if (fpFindDialog->FindNext()) {
        fFindLastStr = fpFindDialog->GetFindString();
        fpContentList->FindNext(fFindLastStr, fFindDown, fFindMatchCase,
            fFindMatchWholeWord);
    } else {
        LOGI("Unexpected find dialog activity");
    }

    return 0;
}

void MainWindow::OnEditSort(UINT id)
{
    // Handle IDM_SORT_*.
    // The "sort" menu item should really only be active if we have a file open.
    LOGD("EDIT SORT %d", id);

    ASSERT(id >= IDM_SORT_PATHNAME && id <= IDM_SORT_ORIGINAL);
    fPreferences.GetColumnLayout()->SetSortColumn(id - IDM_SORT_PATHNAME);
    fPreferences.GetColumnLayout()->SetAscending(true);
    if (fpContentList != NULL)
        fpContentList->NewSortOrder();
}

void MainWindow::OnUpdateEditSort(CCmdUI* pCmdUI)
{
    unsigned int column = fPreferences.GetColumnLayout()->GetSortColumn();

    pCmdUI->SetCheck(pCmdUI->m_nID - IDM_SORT_PATHNAME == column);
}

void MainWindow::OnHelpContents(void)
{
    MyApp::HandleHelp(this, HELP_TOPIC_WELCOME);
    //WinHelp(0, HELP_FINDER);
}

void MainWindow::OnHelpWebSite(void)
{
    int err;

    err = (int) ::ShellExecute(m_hWnd, L"open", kWebSiteURL, NULL, NULL,
                    SW_SHOWNORMAL);
    if (err <= 32) {
        CString msg;
        if (err == ERROR_FILE_NOT_FOUND) {
            msg = L"Windows call failed: web browser not found.  (Sometimes"
                  L" it mistakenly reports this when IE is not the default"
                  L" browser.)";
            ShowFailureMsg(this, msg, IDS_FAILED);
        } else {
            msg.Format(L"Unable to launch web browser (err=%d).", err);
            ShowFailureMsg(this, msg, IDS_FAILED);
        }
    }
}

void MainWindow::OnHelpOrdering(void)
{
    // How to order... ka-ching!
    // TODO: no longer used?
    LOGW("OnHelpOrdering -- not implemented");
    //WinHelp(HELP_TOPIC_ORDERING_INFO, HELP_CONTEXT);
}

void MainWindow::OnHelpAbout(void)
{
    int result;

    AboutDialog dlg(this);

    result = dlg.DoModal();
    LOGV("HelpAbout returned %d", result);

    /*
     * User could've changed registration.  If we're showing the registered
     * user name in the title bar, update it.
     */
    if (fpOpenArchive == NULL)
        SetCPTitle();
}

void MainWindow::OnFileNewArchive(void)
{
    // Create a new SHK archive, using a "save as" dialog to select the name.

    CString filename, saveFolder, errStr;
    GenericArchive* pOpenArchive;
    CString errMsg;

    CFileDialog dlg(FALSE, L"shk", NULL,
        OFN_OVERWRITEPROMPT|OFN_NOREADONLYRETURN|OFN_HIDEREADONLY,
        L"ShrinkIt Archives (*.shk)|*.shk||", this);

    dlg.m_ofn.lpstrTitle = L"New Archive";
    dlg.m_ofn.lpstrInitialDir = fPreferences.GetPrefString(kPrOpenArchiveFolder);

    if (dlg.DoModal() != IDOK)
        goto bail;

    saveFolder = dlg.m_ofn.lpstrFile;
    saveFolder = saveFolder.Left(dlg.m_ofn.nFileOffset);
    fPreferences.SetPrefString(kPrOpenArchiveFolder, saveFolder);

    filename = dlg.GetPathName();
    LOGI("NEW FILE '%ls'", (LPCWSTR) filename);

    /* remove file if it already exists */
    errMsg = RemoveFile(filename);
    if (!errMsg.IsEmpty()) {
        ShowFailureMsg(this, errMsg, IDS_FAILED);
        goto bail;
    }

    pOpenArchive = new NufxArchive;
    errStr = pOpenArchive->New(filename, NULL);
    if (!errStr.IsEmpty()) {
        CString failed;
        CheckedLoadString(&failed, IDS_FAILED);
        MessageBox(errStr, failed, MB_ICONERROR);

        delete pOpenArchive;
    } else {
        SwitchContentList(pOpenArchive);
        fOpenArchivePathName = dlg.GetPathName();
        SetCPTitle(fOpenArchivePathName, fpOpenArchive);
    }

bail:
    LOGD("--- OnFileNewArchive done");
}

void MainWindow::OnFileOpen(void)
{
    // Handle request to open an archive or disk image.

    CString openFilters;
    CString saveFolder;

    /* set up filters; the order must match enum FilterIndex */
    openFilters = kOpenNuFX;
    openFilters += kOpenBinaryII;
    openFilters += kOpenACU;
    openFilters += kOpenAppleSingle;
    openFilters += kOpenDiskImage;
    openFilters += kOpenAll;
    openFilters += kOpenEnd;
    CFileDialog dlg(TRUE, L"shk", NULL,
        OFN_FILEMUSTEXIST, openFilters, this);

    DWORD savedIndex = fPreferences.GetPrefLong(kPrLastOpenFilterIndex);
    if (savedIndex < kFilterIndexFIRST || savedIndex > kFilterIndexMAX) {
        // default to *.* if not set (zero) or out of range
        savedIndex = kFilterIndexGeneric;
    }
    dlg.m_ofn.nFilterIndex = savedIndex;
    dlg.m_ofn.lpstrInitialDir = fPreferences.GetPrefString(kPrOpenArchiveFolder);

    if (dlg.DoModal() != IDOK)
        goto bail;

    fPreferences.SetPrefLong(kPrLastOpenFilterIndex, dlg.m_ofn.nFilterIndex);
    saveFolder = dlg.m_ofn.lpstrFile;
    saveFolder = saveFolder.Left(dlg.m_ofn.nFileOffset);
    fPreferences.SetPrefString(kPrOpenArchiveFolder, saveFolder);

    FilterIndex filterIndex = (FilterIndex) dlg.m_ofn.nFilterIndex;
    if (filterIndex < kFilterIndexFIRST || filterIndex > kFilterIndexMAX) {
        ASSERT(false);
        LOGW("invalid filterIndex %d", filterIndex);
        filterIndex = kFilterIndexGeneric;
    }
    DoOpenArchive(dlg.GetPathName(), dlg.GetFileExt(),
        filterIndex, dlg.GetReadOnlyPref() != 0);

bail:
    LOGD("--- OnFileOpen done");
}

void MainWindow::OnFileOpenVolume(void)
{
    // Handle request to open a raw disk volume.
    LOGD("--- OnFileOpenVolume");

    int result;

    OpenVolumeDialog dlg(this);

    result = dlg.DoModal();
    if (result != IDOK)
        goto bail;

    //DiskImg::SetAllowWritePhys0(fPreferences.GetPrefBool(kPrOpenVolumePhys0));
    DoOpenVolume(dlg.fChosenDrive, dlg.fReadOnly != 0);

bail:
    return;
}

void MainWindow::OnUpdateFileOpenVolume(CCmdUI* pCmdUI)
{
    // don't really need this function
    pCmdUI->Enable(TRUE);
}

void MainWindow::DoOpenArchive(const WCHAR* pathName, const WCHAR* ext,
    FilterIndex filterIndex, bool readOnly)
{
    if (LoadArchive(pathName, ext, filterIndex, readOnly) == 0) {
        /* success, update title bar */
        fOpenArchivePathName = pathName;
        SetCPTitle(fOpenArchivePathName, fpOpenArchive);
    } else {
        /* some failures will close an open archive */
        //if (fpOpenArchive == NULL)
        //  SetCPTitle();
    }
}

void MainWindow::OnFileReopen(void)
{
    ReopenArchive();
}

void MainWindow::OnUpdateFileReopen(CCmdUI* pCmdUI)
{
    pCmdUI->Enable(fpOpenArchive != NULL);
}

void MainWindow::OnFileSave(void)
{
    /*
     * This may be called directly from tools, so don't assume that the
     * conditions checked for in OnUpdateFileSave hold here.
     */
    CString errMsg;

    if (fpOpenArchive == NULL)
        return;

    {
        CWaitCursor waitc;
        errMsg = fpOpenArchive->Flush();
    }
    if (!errMsg.IsEmpty())
        ShowFailureMsg(this, errMsg, IDS_FAILED);

    // update the title bar
    DoIdle();
}

void MainWindow::OnUpdateFileSave(CCmdUI* pCmdUI)
{
    pCmdUI->Enable(fpOpenArchive != NULL && fpOpenArchive->IsModified());
}

void MainWindow::OnFileClose(void)
{
    CloseArchive();
    //SetCPTitle();
    LOGD("--- OnFileClose done");
}

void MainWindow::OnUpdateFileClose(CCmdUI* pCmdUI)
{
    pCmdUI->Enable(fpOpenArchive != NULL);
}

void MainWindow::OnFileArchiveInfo(void)
{
    ArchiveInfoDialog* pDlg = NULL;
    ASSERT(fpOpenArchive != NULL);

    switch (fpOpenArchive->GetArchiveKind()) {
    case GenericArchive::kArchiveNuFX:
        pDlg = new NufxArchiveInfoDialog((NufxArchive*) fpOpenArchive, this);
        break;
    case GenericArchive::kArchiveDiskImage:
        pDlg = new DiskArchiveInfoDialog((DiskArchive*) fpOpenArchive, this);
        break;
    case GenericArchive::kArchiveBNY:
        pDlg = new BnyArchiveInfoDialog((BnyArchive*) fpOpenArchive, this);
        break;
    case GenericArchive::kArchiveACU:
        pDlg = new AcuArchiveInfoDialog((AcuArchive*) fpOpenArchive, this);
        break;
    case GenericArchive::kArchiveAppleSingle:
        pDlg = new AppleSingleArchiveInfoDialog((AppleSingleArchive*) fpOpenArchive, this);
        break;
    default:
        LOGW("Unexpected archive type %d", fpOpenArchive->GetArchiveKind());
        ASSERT(false);
        return;
    };

    pDlg->DoModal();

    delete pDlg;
}

void MainWindow::OnUpdateFileArchiveInfo(CCmdUI* pCmdUI)
{
    pCmdUI->Enable(fpContentList != NULL);
}

void MainWindow::OnFilePrint(void)
{
    PrintListing(fpContentList);
}

void MainWindow::OnUpdateFilePrint(CCmdUI* pCmdUI)
{
    pCmdUI->Enable(fpContentList != NULL && fpContentList->GetItemCount() > 0);
}

void MainWindow::PrintListing(const ContentList* pContentList)
{
    CPrintDialog dlg(FALSE);    // use CPrintDialogEx for Win2K? CPageSetUpDialog?
    PrintContentList pcl;
    CDC dc;
    int itemCount, numPages;

    itemCount = pContentList->GetItemCount();
    numPages = (itemCount + (pcl.GetLinesPerPage()-1)) / pcl.GetLinesPerPage();

    dlg.m_pd.nFromPage = dlg.m_pd.nMinPage = 1;
    dlg.m_pd.nToPage = dlg.m_pd.nMaxPage = numPages;

    dlg.m_pd.hDevMode = fhDevMode;
    dlg.m_pd.hDevNames = fhDevNames;
    dlg.m_pd.Flags |= PD_USEDEVMODECOPIESANDCOLLATE;
    dlg.m_pd.Flags &= ~(PD_NOPAGENUMS);
    if (dlg.DoModal() != IDOK)
        return;
    if (dc.Attach(dlg.GetPrinterDC()) != TRUE) {
        CString msg;
        CheckedLoadString(&msg, IDS_PRINTER_NOT_USABLE);
        ShowFailureMsg(this, msg, IDS_FAILED);
        return;
    }

    pcl.Setup(&dc, this);
    if (dlg.m_pd.Flags & PD_PAGENUMS)
        pcl.Print(pContentList, dlg.m_pd.nFromPage, dlg.m_pd.nToPage);
    else
        pcl.Print(pContentList);

    fhDevMode = dlg.m_pd.hDevMode;
    fhDevNames = dlg.m_pd.hDevNames;
}

void MainWindow::OnFileExit(void)
{
    // Handle Exit item by sending a close request.
    SendMessage(WM_CLOSE, 0, 0);
}

void MainWindow::OnEditSelectAll(void)
{
    ASSERT(fpContentList != NULL);
    fpContentList->SelectAll();
}

void MainWindow::OnUpdateEditSelectAll(CCmdUI* pCmdUI)
{
    pCmdUI->Enable(fpContentList != NULL);
}

void MainWindow::OnEditInvertSelection(void)
{
    ASSERT(fpContentList != NULL);
    fpContentList->InvertSelection();
}

void MainWindow::OnUpdateEditInvertSelection(CCmdUI* pCmdUI)
{
    pCmdUI->Enable(fpContentList != NULL);
}

GenericEntry* MainWindow::GetSelectedItem(ContentList* pContentList)
{
    if (pContentList->GetSelectedCount() != 1)
        return NULL;

    POSITION posn;
    posn = pContentList->GetFirstSelectedItemPosition();
    if (posn == NULL) {
        ASSERT(false);
        return NULL;
    }
    int num = pContentList->GetNextSelectedItem(/*ref*/ posn);
    GenericEntry* pEntry = (GenericEntry*) pContentList->GetItemData(num);
    if (pEntry == NULL) {
        LOGW(" Glitch: couldn't find entry %d", num);
        ASSERT(false);
    }

    return pEntry;
}

void MainWindow::HandleDoubleClick(void)
{
    const uint32_t kTypeDimg = 0x64496d67;
    const uint32_t kCreatorDcpy = 0x64437079;
    bool handled = false;

    ASSERT(fpContentList != NULL);
    if (fpContentList->GetSelectedCount() == 0) {
        /* nothing selected, they double-clicked outside first column */
        LOGI("Double-click but nothing selected");
        return;
    }
    if (fpContentList->GetSelectedCount() != 1) {
        /* multiple items, just bring up viewer */
        HandleView();
        return;
    }

    /*
     * Find the GenericEntry that corresponds to this item.
     */
    GenericEntry* pEntry = GetSelectedItem(fpContentList);
    if (pEntry == NULL)
        return;

    LOGI(" Double-click got '%ls'", (LPCWSTR) pEntry->GetPathNameUNI());
    const WCHAR* ext;
    long fileType, auxType;

    ext = PathName::FindExtension(pEntry->GetPathNameUNI(), pEntry->GetFssep());
    fileType = pEntry->GetFileType();
    auxType = pEntry->GetAuxType();

    /*  // unit tests for MatchSemicolonList
    MatchSemicolonList("gif; jpeg; jpg", "jpeg");
    MatchSemicolonList("gif; jpeg; jpg", "jpg");
    MatchSemicolonList("gif; jpeg; jpg", "gif");
    MatchSemicolonList("gif;jpeg;jpg", "gif;");
    MatchSemicolonList("gif; jpeg; jpg", "jpe");
    MatchSemicolonList("gif; jpeg; jpg", "jpegx");
    MatchSemicolonList("gif; jpeg; jpg", "jp");
    MatchSemicolonList("gif; jpeg; jpg", "jpgx");
    MatchSemicolonList("gif; jpeg; jpg", "if");
    MatchSemicolonList("gif; jpeg; jpg", "gifs");
    MatchSemicolonList("gif, jpeg; jpg", "jpeg");
    MatchSemicolonList("", "jpeg");
    MatchSemicolonList(";", "jpeg");
    MatchSemicolonList("gif, jpeg; jpg", "");
    */

    /*
     * Figure out what to do with it.
     */
    CString extViewerExts;
    extViewerExts = fPreferences.GetPrefString(kPrExtViewerExts);
    if (ext != NULL && MatchSemicolonList(extViewerExts, ext+1)) {
        LOGI(" Launching external viewer for '%ls'", ext);
        TmpExtractForExternal(pEntry);
        handled = true;
    } else if (pEntry->GetRecordKind() == GenericEntry::kRecordKindFile) {
        if ((ext != NULL && (
                wcsicmp(ext, L".shk") == 0 ||
                wcsicmp(ext, L".sdk") == 0 ||
                wcsicmp(ext, L".bxy") == 0)) ||
            (fileType == 0xe0 && auxType == 0x8002))
        {
            LOGI(" Guessing NuFX");
            TmpExtractAndOpen(pEntry, GenericEntry::kDataThread, kModeNuFX);
            handled = true;
        } else
        if ((ext != NULL && (
                wcsicmp(ext, L".bny") == 0 ||
                wcsicmp(ext, L".bqy") == 0)) ||
            (fileType == 0xe0 && auxType == 0x8000))
        {
            LOGI(" Guessing Binary II");
            TmpExtractAndOpen(pEntry, GenericEntry::kDataThread, kModeBinaryII);
            handled = true;
        } else
        if ((ext != NULL && (
                wcsicmp(ext, L".acu") == 0)) ||
            (fileType == 0xe0 && auxType == 0x8001))
        {
            LOGI(" Guessing ACU");
            TmpExtractAndOpen(pEntry, GenericEntry::kDataThread, kModeACU);
            handled = true;
        } else
        if ((ext != NULL && (
                wcsicmp(ext, L".as") == 0)) ||
            (fileType == 0xe0 && auxType == 0x0001))
        {
            LOGI(" Guessing AppleSingle");
            TmpExtractAndOpen(pEntry, GenericEntry::kDataThread, kModeAppleSingle);
            handled = true;
        } else
        if (fileType == kTypeDimg && auxType == kCreatorDcpy &&
            pEntry->GetDataForkLen() == 819284)
        {
            /* type is dImg, creator is dCpy, length is 800K + DC stuff */
            LOGI(" Looks like a DiskCopy disk image");
            TmpExtractAndOpen(pEntry, GenericEntry::kDataThread, kModeDiskImage);
            handled = true;
        }
    } else if (pEntry->GetRecordKind() == GenericEntry::kRecordKindForkedFile) {
        /*
         * On Mac HFS volumes these can have resource forks (which we ignore).
         * We could probably just generally ignore resource forks for all of
         * the above files, rather than pulling this one out separately, but
         * this is the only one that could reasonably have a resource fork.
         */
        if (fileType == kTypeDimg && auxType == kCreatorDcpy &&
            pEntry->GetDataForkLen() == 819284)
        {
            /* type is dImg, creator is dCpy, length is 800K + DC stuff */
            LOGI(" Looks like a (forked) DiskCopy disk image");
            TmpExtractAndOpen(pEntry, GenericEntry::kDataThread, kModeDiskImage);
            handled = true;
        }
    } else if (pEntry->GetRecordKind() == GenericEntry::kRecordKindDisk) {
        LOGI(" Opening archived disk image");
        TmpExtractAndOpen(pEntry, GenericEntry::kDiskImageThread, kModeDiskImage);
        handled = true;
    }

    if (!handled) {
        // standard viewer
        HandleView();
    }

    /* set "/t" temp flag and delete afterward, warning user (?) */
}

int MainWindow::TmpExtractAndOpen(GenericEntry* pEntry, int threadKind,
    const WCHAR* modeStr)
{
    CString dispName;
    bool mustDelete = false;

    /*
     * Get the name to display in the title bar.  Double quotes will
     * screw it up, so we have to replace them.  (We could escape them,
     * but then we'd also have to escape the escape char.)
     */
    dispName = pEntry->GetFileName();
    dispName.Replace('"', '_');

    WCHAR nameBuf[MAX_PATH];
    UINT unique;
    unique = GetTempFileName(fPreferences.GetPrefString(kPrTempPath),
                L"CPfile", 0, nameBuf);
    if (unique == 0) {
        DWORD dwerr = ::GetLastError();
        LOGI("GetTempFileName failed on '%ls' (err=%ld)",
            fPreferences.GetPrefString(kPrTempPath), dwerr);
        return dwerr;
    }
    mustDelete = true;

    /*
     * Open the temp file and extract the data into it.
     */
    CString errMsg;
    int result;
    FILE* fp;

    fp = _wfopen(nameBuf, L"wb");
    if (fp != NULL) {
        LOGD("Extracting to '%ls' (unique=%d)", nameBuf, unique);
        result = pEntry->ExtractThreadToFile(threadKind, fp,
                    GenericEntry::kConvertEOLOff, GenericEntry::kConvertHAOff,
                    &errMsg);
        fclose(fp);
        if (result == IDOK) {
            /* success */
            CString parameters;

            parameters.Format(L"-mode %ls -dispname \"%ls\" -temparc \"%ls\"",
                modeStr, (LPCWSTR) dispName, nameBuf);
            int err;

            err = (int) ::ShellExecute(m_hWnd, L"open",
                    gMyApp.GetExeFileName(), parameters, NULL,
                            SW_SHOWNORMAL);
            if (err <= 32) {
                CString msg;
                msg.Format(L"Unable to launch CiderPress (err=%d).", err);
                ShowFailureMsg(this, msg, IDS_FAILED);
            } else {
                /* during dev, "missing DLL" causes false-positive success */
                LOGI("Successfully launched CiderPress, mode=%ls", modeStr);
                mustDelete = false;     // up to newly-launched app
            }
        } else {
            ShowFailureMsg(this, errMsg, IDS_FAILED);
        }
    } else {
        CString msg;
        msg.Format(L"Unable to open temp file '%ls'.", nameBuf);
        ::ShowFailureMsg(this, msg, IDS_FAILED);
    }

    if (mustDelete) {
        LOGI("Deleting '%ls'", nameBuf);
        _wunlink(nameBuf);
    }

    return 0;
}

int MainWindow::TmpExtractForExternal(GenericEntry* pEntry)
{
    const WCHAR* ext;

    ext = PathName::FindExtension(pEntry->GetPathNameUNI(), pEntry->GetFssep());

    WCHAR nameBuf[MAX_PATH];
    UINT unique;
    unique = GetTempFileName(fPreferences.GetPrefString(kPrTempPath),
                L"CPfile", 0, nameBuf);
    if (unique == 0) {
        DWORD dwerr = ::GetLastError();
        LOGI("GetTempFileName failed on '%ls' (err=%ld)",
            fPreferences.GetPrefString(kPrTempPath), dwerr);
        return dwerr;
    }
    fDeleteList.Add(nameBuf);       // file is created by GetTempFileName

    wcscat(nameBuf, ext);

    /*
     * Open the temp file and extract the data into it.
     */
    CString errMsg;
    int result;
    FILE* fp;

    fp = _wfopen(nameBuf, L"wb");
    if (fp != NULL) {
        fDeleteList.Add(nameBuf);   // second file created by fopen
        LOGI("Extracting to '%ls' (unique=%d)", nameBuf, unique);
        result = pEntry->ExtractThreadToFile(GenericEntry::kDataThread, fp,
                    GenericEntry::kConvertEOLOff, GenericEntry::kConvertHAOff,
                    &errMsg);
        fclose(fp);
        if (result == IDOK) {
            /* success */
            int err;

            err = (int) ::ShellExecute(m_hWnd, L"open", nameBuf, NULL,
                            NULL, SW_SHOWNORMAL);
            if (err <= 32) {
                CString msg;
                msg.Format(L"Unable to launch external viewer (err=%d).", err);
                ShowFailureMsg(this, msg, IDS_FAILED);
            } else {
                LOGI("Successfully launched external viewer");
            }
        } else {
            ShowFailureMsg(this, errMsg, IDS_FAILED);
        }
    } else {
        CString msg;
        msg.Format(L"Unable to open temp file '%ls'.", nameBuf);
        ShowFailureMsg(this, msg, IDS_FAILED);
    }

    return 0;
}

#if 0
/*
 * Handle a "default action" selection from the right-click menu.  The
 * action only applies to the record that was clicked on, so we need to
 * retrieve that from the control.
 */
void MainWindow::OnRtClkDefault(void)
{
    int idx;

    ASSERT(fpContentList != NULL);

    idx = fpContentList->GetRightClickItem();
    ASSERT(idx != -1);
    LOGI("OnRtClkDefault %d", idx);

    fpContentList->ClearRightClickItem();
}
#endif


/*
 * ===================================
 *      Progress meter
 * ===================================
 */

/*
 * There are two different mechanisms for reporting progress: ActionProgress
 * dialogs (for adding/extracting files) and a small box in the lower
 * right-hand corner (for opening archives).  These functions will set
 * the progress in the active action progress dialog if it exists, or
 * will set the percentage in the window frame if not.
 */

void MainWindow::SetProgressBegin(void)
{
    if (fpActionProgress != NULL)
        fpActionProgress->SetProgress(0);
    else
        fStatusBar.SetPaneText(kProgressPane, L"--%");
    //LOGI("  Complete: BEGIN");

    /* redraw stuff with the changes */
    (void) PeekAndPump();
}

int MainWindow::SetProgressUpdate(int percent, const WCHAR* oldName,
    const WCHAR* newName)
{
    int status = IDOK;

    if (fpActionProgress != NULL) {
        status = fpActionProgress->SetProgress(percent);
        if (oldName != NULL)
            fpActionProgress->SetArcName(oldName);
        if (newName != NULL)
            fpActionProgress->SetFileName(newName);
    } else {
        WCHAR buf[8];
        wsprintf(buf, L"%d%%", percent);
        fStatusBar.SetPaneText(kProgressPane, buf);
        //LOGI("  Complete: %ls", buf);
    }

    if (!PeekAndPump()) {
        LOGI("SetProgressUpdate: shutdown?!");
    }

    //EventPause(10);       // DEBUG DEBUG
    return status;
}

void MainWindow::SetProgressEnd(void)
{
    if (fpActionProgress != NULL)
        fpActionProgress->SetProgress(100);
    else
        fStatusBar.SetPaneText(kProgressPane, L"");
//  EventPause(100);        // DEBUG DEBUG
    //LOGI("  Complete: END");
}

bool MainWindow::SetProgressCounter(const WCHAR* str, long val)
{
    /* if the main window is enabled, user could activate menus */
    ASSERT(!IsWindowEnabled());

    if (fpProgressCounter != NULL) {
        //LOGI("SetProgressCounter '%ls' %d", str, val);
        CString msg;

        if (str != NULL)
            fpProgressCounter->SetCounterFormat(str);
        fpProgressCounter->SetCount((int) val);
    } else {
        if (val < 0) {
            fStatusBar.SetPaneText(kProgressPane, L"");
        } else {
            CString tmpStr;
            tmpStr.Format(L"%ld", val);
            fStatusBar.SetPaneText(kProgressPane, tmpStr);
        }
    }

    if (!PeekAndPump()) {
        LOGI("SetProgressCounter: shutdown?!");
    }
    //EventPause(10);       // DEBUG DEBUG

    if (fpProgressCounter != NULL)
        return !fpProgressCounter->GetCancel();
    else
        return true;
}

BOOL MainWindow::PeekAndPump(void)
{
    MSG msg;

    while (::PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) {
        if (!AfxGetApp()->PumpMessage()) {
            ::PostQuitMessage(0);
            return FALSE;
        }
    }

    LONG lIdle = 0;
    while (AfxGetApp()->OnIdle(lIdle++))
        ;
    return TRUE;
}

void MainWindow::EventPause(int duration)
{
    int count = duration / 10;

    for (int i = 0; i < count; i++) {
        PeekAndPump();
        ::Sleep(10);
    }
}

/*static*/ BOOL CALLBACK MainWindow::PrintAbortProc(HDC hDC, int nCode)
{
    MainWindow* pMain = (MainWindow*)::AfxGetMainWnd();

    pMain->PeekAndPump();
    if (pMain->GetAbortPrinting()) {
        LOGI("PrintAbortProc returning FALSE (abort printing)");
        return FALSE;
    }
    LOGI("  PrintAbortProc returning TRUE (continue printing)");
    return TRUE;
}

/*
 * ===================================
 *      Support functions
 * ===================================
 */

static const WCHAR kMainWinSection[] = L"mainwin";
static const WCHAR kMainWin_Left[] = L"left";
static const WCHAR kMainWin_Top[] = L"top";
static const WCHAR kMainWin_Right[] = L"right";
static const WCHAR kMainWin_Bottom[] = L"bottom";
static const WCHAR kMainWin_Cmd[] = L"cmd";

void MainWindow::SaveWinPlacement()
{
    // Capture the current window position.  At the point where the destructor
    // fires, too much has been shut down, so we need to do it here.
    WINDOWPLACEMENT wndpl;
    wndpl.length = sizeof(wndpl);
    if (!GetWindowPlacement(&wndpl)) {
        LOGW("Unable to get window placement");
        return;
    }

    LOGD("Window is at ltrb=%ld,%ld %ldx%ld showCmd=%d",
        wndpl.rcNormalPosition.left,
        wndpl.rcNormalPosition.top,
        wndpl.rcNormalPosition.right - wndpl.rcNormalPosition.left,
        wndpl.rcNormalPosition.bottom - wndpl.rcNormalPosition.top,
        wndpl.showCmd);
    gMyApp.WriteProfileInt(kMainWinSection,
        kMainWin_Left, wndpl.rcNormalPosition.left);
    gMyApp.WriteProfileInt(kMainWinSection,
        kMainWin_Top, wndpl.rcNormalPosition.top);
    gMyApp.WriteProfileInt(kMainWinSection,
        kMainWin_Right, wndpl.rcNormalPosition.right);
    gMyApp.WriteProfileInt(kMainWinSection,
        kMainWin_Bottom, wndpl.rcNormalPosition.bottom);
    gMyApp.WriteProfileInt(kMainWinSection,
        kMainWin_Cmd, wndpl.showCmd);
}

void MainWindow::RestoreWinPlacement()
{
    const int kNoVal = -32000;

    RECT normPos;
    normPos.left = gMyApp.GetProfileInt(kMainWinSection, kMainWin_Left, kNoVal);
    normPos.top = gMyApp.GetProfileInt(kMainWinSection, kMainWin_Top, kNoVal);
    normPos.right = gMyApp.GetProfileInt(kMainWinSection, kMainWin_Right, kNoVal);
    normPos.bottom = gMyApp.GetProfileInt(kMainWinSection, kMainWin_Bottom, kNoVal);
    int cmd = gMyApp.GetProfileInt(kMainWinSection, kMainWin_Cmd, kNoVal);

    if (normPos.left == kNoVal || normPos.top == kNoVal ||
            normPos.right == kNoVal || normPos.bottom == kNoVal || cmd == kNoVal) {
        LOGI("Previous window placement not found.");
        return;
    }

    POINT nopt;
    nopt.x = nopt.y = -1;

    // We need to set the placement for "normal", and then deal with
    // maximized windows by having the MyApp ShowWindow() call pass the
    // appropriate command.  If we pass the correct normal rect here with
    // the "maximized" command, we end up with a non-maximized window of
    // maximum size.  (If done correctly, you can maximize, quit, restart,
    // and un-maximize back to the original size.)
    WINDOWPLACEMENT wndpl;
    wndpl.length = sizeof(wndpl);
    wndpl.flags = 0;
    wndpl.showCmd = SW_SHOWNORMAL;
    wndpl.ptMinPosition = nopt;
    wndpl.ptMaxPosition = nopt;
    wndpl.rcNormalPosition = normPos;

    LOGD("Restoring previous placement, cmd=%d", wndpl.showCmd);
    SetWindowPlacement(&wndpl);

    // Stomp on MyApp's show command.  We don't want to start minimized,
    // so switch to "normal" if that's set.
    gMyApp.m_nCmdShow = (cmd == SW_SHOWMINIMIZED ? SW_SHOWNORMAL : cmd);
}

void MainWindow::DrawEmptyClientArea(CDC* pDC, const CRect& clientRect)
{
    CBrush brush;
    brush.CreateSolidBrush(::GetSysColor(COLOR_APPWORKSPACE));  // dk gray
    CBrush* pOldBrush = pDC->SelectObject(&brush);
    pDC->FillRect(&clientRect, &brush);
    pDC->SelectObject(pOldBrush);

    CPen penWH(PS_SOLID, 1, ::GetSysColor(COLOR_3DHIGHLIGHT));  // white
    CPen penLG(PS_SOLID, 1, ::GetSysColor(COLOR_3DLIGHT));      // lt gray
    CPen penDG(PS_SOLID, 1, ::GetSysColor(COLOR_3DSHADOW));     // dk gray
    CPen penBL(PS_SOLID, 1, ::GetSysColor(COLOR_3DDKSHADOW));   // near-black
    CPen* pOldPen = pDC->SelectObject(&penWH);
    //pDC->SelectObject(&penWH);
    pDC->MoveTo(clientRect.right-1, clientRect.top);
    pDC->LineTo(clientRect.right-1, clientRect.bottom-1);
    pDC->LineTo(clientRect.left-1, clientRect.bottom-1);
    pDC->SelectObject(&penBL);
    pDC->MoveTo(clientRect.right-3, clientRect.top+1);
    pDC->LineTo(clientRect.left+1, clientRect.top+1);
    pDC->LineTo(clientRect.left+1, clientRect.bottom-2);
    pDC->SelectObject(&penLG);
    pDC->MoveTo(clientRect.right-2, clientRect.top+1);
    pDC->LineTo(clientRect.right-2, clientRect.bottom-2);
    pDC->LineTo(clientRect.left, clientRect.bottom-2);
    pDC->SelectObject(&penDG);
    pDC->MoveTo(clientRect.right-2, clientRect.top);
    pDC->LineTo(clientRect.left, clientRect.top);
    pDC->LineTo(clientRect.left, clientRect.bottom-1);

    pDC->SelectObject(pOldPen);
}

GenericArchive* MainWindow::CreateArchiveInstance(FilterIndex filterIndex) const
{
    GenericArchive* pOpenArchive = NULL;

    switch (filterIndex) {
    case kFilterIndexNuFX:          pOpenArchive = new NufxArchive;         break;
    case kFilterIndexBinaryII:      pOpenArchive = new BnyArchive;          break;
    case kFilterIndexACU:           pOpenArchive = new AcuArchive;          break;
    case kFilterIndexAppleSingle:   pOpenArchive = new AppleSingleArchive;  break;
    case kFilterIndexDiskImage:     pOpenArchive = new DiskArchive;         break;
    default:                        ASSERT(false);                          break;
    }

    return pOpenArchive;
}

int MainWindow::LoadArchive(const WCHAR* fileName, const WCHAR* extension,
    FilterIndex filterIndex, bool readOnly)
{
    static const WCHAR kFileArchive[] = L"This appears to be a file archive.";
    GenericArchive::OpenResult openResult;
    const FilterIndex origFilterIndex = filterIndex;
    GenericArchive* pOpenArchive = NULL;

    LOGI("LoadArchive: '%ls' ro=%d idx=%d", fileName, readOnly, filterIndex);

    /* close any existing archive to avoid weirdness from re-open */
    CloseArchive();

    /*
     * If they used the "All Files (*.*)" filter, try guess based
     * on the file extension.
     */
    if (filterIndex == kFilterIndexGeneric) {
        int i;

        for (i = 0; i < NELEM(gExtensionToIndex); i++) {
            if (wcsicmp(extension, gExtensionToIndex[i].extension) == 0) {
                filterIndex = gExtensionToIndex[i].idx;
                break;
            }
        }

        if (i == NELEM(gExtensionToIndex)) {
            // no match found, use "disk image" as initial guess
            filterIndex = kFilterIndexDiskImage;
        }
    }

    /*
     * Try to open the file according to the specified filter index.  If
     * it works, we're done.  Trying this first ensures that you can choose
     * to open, say, a .SDK file as either ShrinkIt or Disk Image.
     *
     * It's possible to cancel the file open if you have "confirm disk image
     * format set" and the file is a disk image.  In that case we want to
     * return with an error result, but without showing an error dialog.
     */
    CString firstErrStr;
    pOpenArchive = CreateArchiveInstance(filterIndex);
    LOGD("First try: %ls", (LPCWSTR) pOpenArchive->GetDescription());
    openResult = pOpenArchive->Open(fileName, readOnly, &firstErrStr);
    if (openResult == GenericArchive::kResultSuccess) {
        // success!
        SwitchContentList(pOpenArchive);
        return 0;
    } else if (openResult == GenericArchive::kResultFileArchive) {
        if (wcsicmp(extension, L"zip") == 0) {
            // we could probably just return in this case
            firstErrStr = L"Zip archives with multiple files are not supported";
        } else {
            firstErrStr = kFileArchive;
        }
    } else if (openResult == GenericArchive::kResultCancel) {
        LOGD("canceled");
        delete pOpenArchive;
        return -1;
    }
    delete pOpenArchive;

    /*
     * That didn't work.  Try the others.
     */
    for (int i = kFilterIndexFIRST; i <= kFilterIndexLASTNG; i++) {
        if (i == filterIndex) continue;

        pOpenArchive = CreateArchiveInstance((FilterIndex) i);
        LOGD("Now trying: %ls", (LPCWSTR) pOpenArchive->GetDescription());
        CString dummyErrStr;
        openResult = pOpenArchive->Open(fileName, readOnly, &dummyErrStr);
        if (openResult == GenericArchive::kResultSuccess) {
            // success!
            SwitchContentList(pOpenArchive);
            return 0;
        } else if (openResult == GenericArchive::kResultCancel) {
            LOGD("cancelled");
            delete pOpenArchive;
            return -1;
        } else if (i == kFilterIndexNuFX && firstErrStr == kFileArchive) {
            // For .shk files we first check to see if it's a disk image,
            // then we try by file.  If it's a damaged file archive, we
            // really want to present the file archive failure message, not
            // "that looks like a file archive".  So we tweak the result a
            // little here.
            //
            // This doesn't catch all the cases where the NufxLib error
            // message is more useful than the DiskImg error message, but
            // it's hard to accurately determine what the most-accurate
            // message is in some cases.
            firstErrStr = dummyErrStr;
        }
        delete pOpenArchive;
    }

    /*
     * Nothing worked.  Show the original error message so that it matches
     * up with the chosen filter index -- if they seemed to be trying to
     * open an AppleSingle, don't tell them we failed because the file isn't
     * a disk image.
     *
     * If they were using the Generic filter, they'll get the message from
     * the disk image library.  It might make more sense to just say "we
     * couldn't figure out what this was", but most of the time people are
     * trying to open disk images anyway.
     */
    if (firstErrStr.IsEmpty()) {
        // not expected; put up a generic message if it happens
        firstErrStr = L"Unable to determine what kind of file this is.";
    }
    ShowFailureMsg(this, firstErrStr, IDS_FAILED);
    return -1;
}

int MainWindow::DoOpenVolume(CString drive, bool readOnly)
{
    int result = -1;

    ASSERT(drive.GetLength() > 0);

    CString errStr;
    //char filename[4] = "_:\\";
    //filename[0] = driveLetter;

    LOGI("FileOpenVolume '%ls' %d", (LPCWSTR)drive, readOnly);

    /* close existing archive */
    CloseArchive();

    GenericArchive* pOpenArchive = NULL;
    pOpenArchive = new DiskArchive;
    {
        CWaitCursor waitc;
        GenericArchive::OpenResult openResult;

        openResult = pOpenArchive->Open(drive, readOnly, &errStr);
        if (openResult == GenericArchive::kResultCancel) {
            // this bubbles out of the format confirmation dialog
            goto bail;
        } else if (openResult != GenericArchive::kResultSuccess) {
            if (!errStr.IsEmpty())
                ShowFailureMsg(this, errStr, IDS_FAILED);
            goto bail;
        }
    }

    // success!
    SwitchContentList(pOpenArchive);
    pOpenArchive = NULL;
    fOpenArchivePathName = drive;
    result = 0;

    fOpenArchivePathName = drive;
    SetCPTitle(fOpenArchivePathName, fpOpenArchive);

bail:
    if (pOpenArchive != NULL) {
        ASSERT(result != 0);
        delete pOpenArchive;
    }
    return result;
}

void MainWindow::ReopenArchive(void)
{
    if (fpOpenArchive == NULL) {
        ASSERT(false);
        return;
    }

    /* clear the flag, regardless of success or failure */
    fNeedReopen = false;

    GenericArchive* pOpenArchive = NULL;
    CString pathName = fpOpenArchive->GetPathName();
    bool readOnly = fpOpenArchive->IsReadOnly();
    GenericArchive::ArchiveKind archiveKind = fpOpenArchive->GetArchiveKind();
    GenericArchive::OpenResult openResult;
    CString errStr;

    /* if the open fails we *don't* want to leave the previous content up */
    LOGI("Reopening '%ls' ro=%d kind=%d",
        (LPCWSTR) pathName, readOnly, archiveKind);
    CloseArchive();

    switch (archiveKind) {
    case GenericArchive::kArchiveDiskImage:
        pOpenArchive = new DiskArchive;
        break;
    case GenericArchive::kArchiveNuFX:
        pOpenArchive = new NufxArchive;
        break;
    case GenericArchive::kArchiveBNY:
        pOpenArchive = new BnyArchive;
        break;
    default:
        ASSERT(false);
        return;
    }

    openResult = pOpenArchive->Open(pathName, readOnly, &errStr);
    if (openResult == GenericArchive::kResultCancel) {
        // this bubbles out of the format confirmation dialog
        goto bail;
    } else if (openResult != GenericArchive::kResultSuccess) {
        if (!errStr.IsEmpty())
            ShowFailureMsg(this, errStr, IDS_FAILED);
        goto bail;
    }

    LOGI(" Reopen was successful");
    SwitchContentList(pOpenArchive);
    pOpenArchive = NULL;
    SetCPTitle(pathName, fpOpenArchive);

bail:
    delete pOpenArchive;
}

bool MainWindow::IsOpenPathName(const WCHAR* path)
{
    if (fpOpenArchive == NULL)
        return false;

    if (wcsicmp(path, fpOpenArchive->GetPathName()) == 0)
        return true;

    return false;
}

void MainWindow::SwitchContentList(GenericArchive* pOpenArchive)
{
    assert(pOpenArchive != NULL);

    /*
     * We've got an archive opened successfully.  If we already had one
     * open, shut it.  (This assumes that closing an archive is a simple
     * matter of closing files and freeing storage.  If we needed to do
     * something that might fail, like flush changes, we should've done
     * that before getting this far to avoid confusion.)
     */
    if (fpOpenArchive != NULL)
        CloseArchive();

    ASSERT(fpOpenArchive == NULL);
    ASSERT(fpContentList == NULL);

    /*
     * Without this we get an assertion failure in CImageList::Attach if we
     * call here from ReopenArchive.  I think Windows needs to do some
     * cleanup, though I don't understand how the reopen case differs from
     * the usual case.  Maybe there's more stuff pending in the "reopen"
     * case?  In any event, this seems to work, which is all you can hope
     * for from MFC.  It does, however, make the screen flash, which it
     * didn't do before.
     *
     * UPDATE: this tripped once while I was debugging, even with this.  The
     * PeekAndPump function does force the idle loop to run, so I'm not sure
     * why it failed, unless the debugger somehow affected the idle
     * processing.  Yuck.
     *
     * The screen flash bugged me so I took it back out.  And the assert
     * didn't hit.  I really, really love Windows.
     */
    //PeekAndPump();


    fpContentList = new ContentList(pOpenArchive,
                                    fPreferences.GetColumnLayout());
    
    CRect sizeRect;
    GetClientRect(&sizeRect);
    fpContentList->Create(WS_CHILD | WS_VISIBLE | WS_VSCROLL,
        sizeRect, this, IDC_CONTENT_LIST);

    fpOpenArchive = pOpenArchive;
}

void MainWindow::CloseArchiveWOControls(void)
{
    if (fpOpenArchive != NULL) {
        //fpOpenArchive->Close();
        LOGI("Deleting OpenArchive");
        delete fpOpenArchive;
        fpOpenArchive = NULL;
    }
}

void MainWindow::CloseArchive(void)
{
    CWaitCursor waitc;  // closing large compressed archive can be slow

    // destroy the ContentList
    if (fpContentList != NULL) {
        LOGI("Destroying ContentList");
        fpContentList->DestroyWindow(); // auto-cleanup invokes "delete"
        fpContentList = NULL;
    }

    // destroy the GenericArchive
    CloseArchiveWOControls();

    // reset the title bar
    SetCPTitle();
}

void MainWindow::SetCPTitle(const WCHAR* pathname, GenericArchive* pOpenArchive)
{
    ASSERT(pathname != NULL);
    CString title;
    CString appName;

    CheckedLoadString(&appName, IDS_MB_APP_NAME);

    CString archiveDescription(pOpenArchive->GetDescription());
    title.Format(L"%ls - %ls (%ls)", (LPCWSTR) appName, pathname,
        (LPCWSTR) archiveDescription);

    if (fpOpenArchive->IsReadOnly()) {
        CString readOnly;
        CheckedLoadString(&readOnly, IDS_READONLY);
        title += L" ";
        title += readOnly;
    }

    SetWindowText(title);
}

void MainWindow::SetCPTitle(void)
{
    CString appName, regName, title;
    CString user, company, reg, versions, expire;

#if 0
    if (gMyApp.fRegistry.GetRegistration(&user, &company, &reg, &versions,
            &expire) == 0)
    {
        if (reg.IsEmpty()) {
            regName += _T("  (unregistered)");
        } else {
            regName += _T("  (registered to ");
            regName += user;
            regName += _T(")");
            // include company?
        }
    }
#endif

    CheckedLoadString(&appName, IDS_MB_APP_NAME);
    title = appName + regName;
    SetWindowText(title);
}

CString MainWindow::GetPrintTitle(void)
{
    CString title;

    if (fpOpenArchive == NULL) {
        ASSERT(false);
        return title;
    }

    CString appName;
    CheckedLoadString(&appName, IDS_MB_APP_NAME);

    CString archiveDescription(fpOpenArchive->GetDescription());
    title.Format(L"%ls - %ls (%ls)", (LPCWSTR) appName,
        (LPCWSTR) fOpenArchivePathName, (LPCWSTR) archiveDescription);

    return title;
}

void MainWindow::SuccessBeep(void)
{
    const Preferences* pPreferences = GET_PREFERENCES();

    if (pPreferences->GetPrefBool(kPrBeepOnSuccess)) {
        LOGI("<happy-beep>");
        ::MessageBeep(MB_OK);
    }
}

void MainWindow::FailureBeep(void)
{
    const Preferences* pPreferences = GET_PREFERENCES();

    if (pPreferences->GetPrefBool(kPrBeepOnSuccess)) {
        LOGI("<failure-beep>");
        ::MessageBeep(MB_ICONEXCLAMATION);  // maybe MB_ICONHAND?
    }
}

CString MainWindow::RemoveFile(const WCHAR* fileName)
{
    CString errMsg;

    int cc;
    cc = _wunlink(fileName);
    if (cc < 0 && errno != ENOENT) {
        int err = errno;
        LOGI("Failed removing file '%ls', errno=%d", fileName, err);
        errMsg.Format(L"Unable to remove '%ls': %hs.",
            fileName, strerror(err));
        if (err == EACCES)
            errMsg += L"\n\n(Make sure the file isn't open.)";
    }

    return errMsg;
}

/*static*/ void MainWindow::ConfigureReformatFromPreferences(ReformatHolder* pReformat)
{
    const Preferences* pPreferences = GET_PREFERENCES();

    pReformat->SetReformatAllowed(ReformatHolder::kReformatRaw, true);
    pReformat->SetReformatAllowed(ReformatHolder::kReformatHexDump, true);

    pReformat->SetReformatAllowed(ReformatHolder::kReformatTextEOL_HA,
        pPreferences->GetPrefBool(kPrConvTextEOL_HA));
    pReformat->SetReformatAllowed(ReformatHolder::kReformatResourceFork,
        pPreferences->GetPrefBool(kPrConvResources));
    pReformat->SetReformatAllowed(ReformatHolder::kReformatProDOSDirectory,
        pPreferences->GetPrefBool(kPrConvProDOSFolder));
    pReformat->SetReformatAllowed(ReformatHolder::kReformatPascalText,
        pPreferences->GetPrefBool(kPrConvPascalText));
    pReformat->SetReformatAllowed(ReformatHolder::kReformatPascalCode,
        pPreferences->GetPrefBool(kPrConvPascalCode));
    pReformat->SetReformatAllowed(ReformatHolder::kReformatCPMText,
        pPreferences->GetPrefBool(kPrConvCPMText));
    pReformat->SetReformatAllowed(ReformatHolder::kReformatApplesoft,
        pPreferences->GetPrefBool(kPrConvApplesoft));
    pReformat->SetReformatAllowed(ReformatHolder::kReformatApplesoft_Hilite,
        pPreferences->GetPrefBool(kPrConvApplesoft));
    pReformat->SetReformatAllowed(ReformatHolder::kReformatInteger,
        pPreferences->GetPrefBool(kPrConvInteger));
    pReformat->SetReformatAllowed(ReformatHolder::kReformatInteger_Hilite,
        pPreferences->GetPrefBool(kPrConvInteger));
    pReformat->SetReformatAllowed(ReformatHolder::kReformatBusiness,
        pPreferences->GetPrefBool(kPrConvBusiness));
    pReformat->SetReformatAllowed(ReformatHolder::kReformatBusiness_Hilite,
        pPreferences->GetPrefBool(kPrConvBusiness));
    pReformat->SetReformatAllowed(ReformatHolder::kReformatSCAssem,
        pPreferences->GetPrefBool(kPrConvSCAssem));
    pReformat->SetReformatAllowed(ReformatHolder::kReformatMerlin,
        pPreferences->GetPrefBool(kPrConvSCAssem));
    pReformat->SetReformatAllowed(ReformatHolder::kReformatLISA2,
        pPreferences->GetPrefBool(kPrConvSCAssem));
    pReformat->SetReformatAllowed(ReformatHolder::kReformatLISA3,
        pPreferences->GetPrefBool(kPrConvSCAssem));
    pReformat->SetReformatAllowed(ReformatHolder::kReformatLISA4,
        pPreferences->GetPrefBool(kPrConvSCAssem));
    pReformat->SetReformatAllowed(ReformatHolder::kReformatMonitor8,
        pPreferences->GetPrefBool(kPrConvDisasm));
    pReformat->SetReformatAllowed(ReformatHolder::kReformatDisasmMerlin8,
        pPreferences->GetPrefBool(kPrConvDisasm));
    pReformat->SetReformatAllowed(ReformatHolder::kReformatMonitor16Long,
        pPreferences->GetPrefBool(kPrConvDisasm));
    pReformat->SetReformatAllowed(ReformatHolder::kReformatMonitor16Short,
        pPreferences->GetPrefBool(kPrConvDisasm));
    pReformat->SetReformatAllowed(ReformatHolder::kReformatDisasmOrcam16,
        pPreferences->GetPrefBool(kPrConvDisasm));
    pReformat->SetReformatAllowed(ReformatHolder::kReformatAWGS_WP,
        pPreferences->GetPrefBool(kPrConvGWP));
    pReformat->SetReformatAllowed(ReformatHolder::kReformatTeach,
        pPreferences->GetPrefBool(kPrConvGWP));
    pReformat->SetReformatAllowed(ReformatHolder::kReformatGWP,
        pPreferences->GetPrefBool(kPrConvGWP));
    pReformat->SetReformatAllowed(ReformatHolder::kReformatMagicWindow,
        pPreferences->GetPrefBool(kPrConvText8));
    pReformat->SetReformatAllowed(ReformatHolder::kReformatGutenberg,
        pPreferences->GetPrefBool(kPrConvGutenberg));
    pReformat->SetReformatAllowed(ReformatHolder::kReformatAWP,
        pPreferences->GetPrefBool(kPrConvAWP));
    pReformat->SetReformatAllowed(ReformatHolder::kReformatAWP,
        pPreferences->GetPrefBool(kPrConvAWP));
    pReformat->SetReformatAllowed(ReformatHolder::kReformatADB,
        pPreferences->GetPrefBool(kPrConvADB));
    pReformat->SetReformatAllowed(ReformatHolder::kReformatASP,
        pPreferences->GetPrefBool(kPrConvASP));
    pReformat->SetReformatAllowed(ReformatHolder::kReformatHiRes,
        pPreferences->GetPrefBool(kPrConvHiRes));
    pReformat->SetReformatAllowed(ReformatHolder::kReformatHiRes_BW,
        pPreferences->GetPrefBool(kPrConvHiRes));
    pReformat->SetReformatAllowed(ReformatHolder::kReformatDHR_Latched,
        pPreferences->GetPrefBool(kPrConvDHR));
    pReformat->SetReformatAllowed(ReformatHolder::kReformatDHR_BW,
        pPreferences->GetPrefBool(kPrConvDHR));
    pReformat->SetReformatAllowed(ReformatHolder::kReformatDHR_Plain140,
        pPreferences->GetPrefBool(kPrConvDHR));
    pReformat->SetReformatAllowed(ReformatHolder::kReformatDHR_Window,
        pPreferences->GetPrefBool(kPrConvDHR));
    pReformat->SetReformatAllowed(ReformatHolder::kReformatSHR_PIC,
        pPreferences->GetPrefBool(kPrConvSHR));
    pReformat->SetReformatAllowed(ReformatHolder::kReformatSHR_JEQ,
        pPreferences->GetPrefBool(kPrConvSHR));
    pReformat->SetReformatAllowed(ReformatHolder::kReformatSHR_Paintworks,
        pPreferences->GetPrefBool(kPrConvSHR));
    pReformat->SetReformatAllowed(ReformatHolder::kReformatSHR_Packed,
        pPreferences->GetPrefBool(kPrConvSHR));
    pReformat->SetReformatAllowed(ReformatHolder::kReformatSHR_APF,
        pPreferences->GetPrefBool(kPrConvSHR));
    pReformat->SetReformatAllowed(ReformatHolder::kReformatSHR_3200,
        pPreferences->GetPrefBool(kPrConvSHR));
    pReformat->SetReformatAllowed(ReformatHolder::kReformatSHR_3201,
        pPreferences->GetPrefBool(kPrConvSHR));
    pReformat->SetReformatAllowed(ReformatHolder::kReformatSHR_DG256,
        pPreferences->GetPrefBool(kPrConvSHR));
    pReformat->SetReformatAllowed(ReformatHolder::kReformatSHR_DG3200,
        pPreferences->GetPrefBool(kPrConvSHR));
    pReformat->SetReformatAllowed(ReformatHolder::kReformatPrintShop,
        pPreferences->GetPrefBool(kPrConvPrintShop));
    pReformat->SetReformatAllowed(ReformatHolder::kReformatMacPaint,
        pPreferences->GetPrefBool(kPrConvMacPaint));

    pReformat->SetOption(ReformatHolder::kOptHiliteHexDump,
        pPreferences->GetPrefBool(kPrHighlightHexDump));
    pReformat->SetOption(ReformatHolder::kOptHiliteBASIC,
        pPreferences->GetPrefBool(kPrHighlightBASIC));
    pReformat->SetOption(ReformatHolder::kOptHiResBW,
        pPreferences->GetPrefBool(kPrConvHiResBlackWhite));
    pReformat->SetOption(ReformatHolder::kOptDHRAlgorithm,
        pPreferences->GetPrefLong(kPrConvDHRAlgorithm));
    pReformat->SetOption(ReformatHolder::kOptRelaxGfxTypeCheck,
        pPreferences->GetPrefBool(kPrRelaxGfxTypeCheck));
    pReformat->SetOption(ReformatHolder::kOptOneByteBrkCop,
        pPreferences->GetPrefBool(kPrDisasmOneByteBrkCop));
    pReformat->SetOption(ReformatHolder::kOptMouseTextToASCII,
        pPreferences->GetPrefBool(kPrConvMouseTextToASCII));
}

/*static*/ ReformatHolder::SourceFormat MainWindow::ReformatterSourceFormat(
    DiskImg::FSFormat format)
{
    /*
     * Gutenberg both UsesDOSFileStructure and is formatted with 
     * kFormatGutenberg, so check for the latter first.
     */
    if (format == DiskImg::kFormatGutenberg)
        return ReformatHolder::kSourceFormatGutenberg;
    else if (DiskImg::UsesDOSFileStructure(format))
        return ReformatHolder::kSourceFormatDOS;
    else if (format == DiskImg::kFormatCPM)
        return ReformatHolder::kSourceFormatCPM;
    else
        return ReformatHolder::kSourceFormatGeneric;
}
