/* * 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 "ArchiveInfoDialog.h" #include "PrefsDialog.h" #include "EnterRegDialog.h" #include "OpenVolumeDialog.h" #include "Print.h" #include "HelpTopics.h" #include "../util/UtilLib.h" #include "resource.h" /* use MFC's fancy version of new for debugging */ //#define new DEBUG_NEW static const char* kWebSiteURL = "http://www.faddensoft.com/"; /* * 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. */ const char MainWindow::kOpenNuFX[] = "ShrinkIt Archives (.shk .sdk .bxy .sea .bse)|*.shk;*.sdk;*.bxy;*.sea;*.bse|"; const char MainWindow::kOpenBinaryII[] = "Binary II Archives (.bny .bqy .bxy)|*.bny;*.bqy;*.bxy|"; const char MainWindow::kOpenACU[] = "ACU Archives (.acu)|*.acu|"; const char MainWindow::kOpenDiskImage[] = "Disk Images (.shk .sdk .dsk .po .do .d13 .2mg .img .nib .nb2 .raw .hdv .dc .dc6 .ddd .app .fdi .iso .gz .zip)|" "*.shk;*.sdk;*.dsk;*.po;*.do;*.d13;*.2mg;*.img;*.nib;*.nb2;*.raw;*.hdv;*.dc;*.dc6;*.ddd;*.app;*.fdi;*.iso;*.gz;*.zip|"; const char MainWindow::kOpenAll[] = "All Files (*.*)|*.*|"; const char MainWindow::kOpenEnd[] = "|"; static const struct { //const char* extension; char extension[4]; FilterIndex idx; } gExtensionToIndex[] = { { "shk", kFilterIndexNuFX }, { "bxy", kFilterIndexNuFX }, { "bse", kFilterIndexNuFX }, { "sea", kFilterIndexNuFX }, { "bny", kFilterIndexBinaryII }, { "bqy", kFilterIndexBinaryII }, { "acu", kFilterIndexACU }, { "dsk", kFilterIndexDiskImage }, { "po", kFilterIndexDiskImage }, { "do", kFilterIndexDiskImage }, { "d13", kFilterIndexDiskImage }, { "2mg", kFilterIndexDiskImage }, { "img", kFilterIndexDiskImage }, { "sdk", kFilterIndexDiskImage }, { "raw", kFilterIndexDiskImage }, { "ddd", kFilterIndexDiskImage }, { "app", kFilterIndexDiskImage }, { "fdi", kFilterIndexDiskImage }, { "iso", kFilterIndexDiskImage }, { "gz", kFilterIndexDiskImage }, // assume disk image inside { "zip", kFilterIndexDiskImage }, // assume disk image inside }; const char* MainWindow::kModeNuFX = _T("nufx"); const char* MainWindow::kModeBinaryII = _T("bin2"); const char* MainWindow::kModeACU = _T("acu"); const char* MainWindow::kModeDiskImage = _T("disk"); /* * =========================================================================== * MainWindow * =========================================================================== */ static const UINT gFindReplaceID = RegisterWindowMessage(FINDMSGSTRING); BEGIN_MESSAGE_MAP(MainWindow, CFrameWnd) ON_WM_CREATE() 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) /* this is required to allow "Help" button to work in PropertySheets (!) */ // ON_COMMAND(ID_HELP, OnHelp) 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 char* kAppName = _T("CiderPress"); fpContentList = nil; fpOpenArchive = nil; //fpSelSet = nil; fpActionProgress = nil; fpProgressCounter = nil; fpFindDialog = nil; fFindDown = true; fFindMatchCase = false; fFindMatchWholeWord = false; fAbortPrinting = false; fhDevMode = nil; fhDevNames = nil; 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() { WMSG0("~MainWindow\n"); //WMSG0("MainWindow destructor\n"); CloseArchiveWOControls(); int cc; cc = ::WinHelp(m_hWnd, ::AfxGetApp()->m_pszHelpFilePath, HELP_QUIT, 0); WMSG1("Turning off WinHelp returned %d\n", cc); // free stuff used by print dialog ::GlobalFree(fhDevMode); ::GlobalFree(fhDevNames); fPreferences.SaveToRegistry(); WMSG0("MainWindow destructor complete\n"); } /* * Override the pre-create function to tweak the window style. */ BOOL MainWindow::PreCreateWindow(CREATESTRUCT& cs) { BOOL res = CFrameWnd::PreCreateWindow(cs); cs.dwExStyle &= ~(WS_EX_CLIENTEDGE); return res; } /* * Override GetClientRect so we can factor in the status and tool bars. */ 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; //WMSG2("HEIGHTS = %d/%d\n", toolBarHeight, statusBarHeight); CFrameWnd::GetClientRect(lpRect); lpRect->top += toolBarHeight; lpRect->bottom -= statusBarHeight; } /* * Do some idle processing. */ 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 != nil) { /* 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) { WMSG0("Resetting column 0 width\n"); 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 != nil) { 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); } } } } /* * Handle command-line arguments. * * Usage: * CiderPress [[-temparc] [-mode {nufx,bin2,disk}] [-dispname name] filename] */ void MainWindow::ProcessCommandLine(void) { /* * Get the command line and break it down into an argument vector. */ const char* cmdLine = ::GetCommandLine(); if (cmdLine == nil || strlen(cmdLine) == 0) return; char* mangle = strdup(cmdLine); if (mangle == nil) return; WMSG1("Mangling '%s'\n", mangle); char* argv[8]; int argc = 8; VectorizeString(mangle, argv, &argc); WMSG0("Args:\n"); for (int i = 0; i < argc; i++) { WMSG2(" %d '%s'\n", i, argv[i]); } /* * Figure out what the arguments are. */ const char* filename = nil; const char* dispName = nil; int filterIndex = kFilterIndexGeneric; bool temp = false; for (i = 1; i < argc; i++) { if (argv[i][0] == '-') { if (strcasecmp(argv[i], "-mode") == 0) { if (i == argc-1) { WMSG0("WARNING: -mode specified without mode\n"); } else i++; if (strcasecmp(argv[i], kModeNuFX) == 0) filterIndex = kFilterIndexNuFX; else if (strcasecmp(argv[i], kModeBinaryII) == 0) filterIndex = kFilterIndexBinaryII; else if (strcasecmp(argv[i], kModeACU) == 0) filterIndex = kFilterIndexACU; else if (strcasecmp(argv[i], kModeDiskImage) == 0) filterIndex = kFilterIndexDiskImage; else { WMSG1("WARNING: unrecognized mode '%s'\n", argv[i]); } } else if (strcasecmp(argv[i], "-dispname") == 0) { if (i == argc-1) { WMSG0("WARNING: -dispname specified without name\n"); } else i++; dispName = argv[i]; } else if (strcasecmp(argv[i], "-temparc") == 0) { temp = true; } else if (strcasecmp(argv[i], "-install") == 0) { // see MyApp::InitInstance WMSG0("Got '-install' flag, doing nothing\n"); } else if (strcasecmp(argv[i], "-uninstall") == 0) { // see MyApp::InitInstance WMSG0("Got '-uninstall' flag, doing nothing\n"); } else { WMSG1("WARNING: unrecognized flag '%s'\n", argv[i]); } } else { /* must be the filename */ if (i != argc-1) { WMSG1("WARNING: ignoring extra arguments (e.g. '%s')\n", argv[i+1]); } filename = argv[i]; break; } } if (argc != 1 && filename == nil) { WMSG0("WARNING: args specified but no filename found\n"); } WMSG0("Argument handling:\n"); WMSG3(" index=%d temp=%d filename='%s'\n", filterIndex, temp, filename == nil ? "(nil)" : filename); if (filename != nil) { 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, false) == 0) { /* success, update title bar */ if (temp) fOpenArchivePathName = path.GetFileName(); else fOpenArchivePathName = filename; if (dispName != nil) fOpenArchivePathName = dispName; SetCPTitle(fOpenArchivePathName, fpOpenArchive); } /* if it's a temporary file, arrange to have it deleted before exit */ if (temp) { int len = strlen(filename); if (len > 4 && strcasecmp(filename + (len-4), ".tmp") == 0) { fDeleteList.Add(filename); } else { WMSG1("NOT adding '%s' to DeleteList -- does not end in '.tmp'\n", filename); } } } free(mangle); } /* * =================================== * Command handlers * =================================== */ const int kProgressPane = 1; /* * OnCreate handler. Used to add a toolbar and status bar. */ int MainWindow::OnCreate(LPCREATESTRUCT lpcs) { WMSG0("Now in OnCreate!\n"); 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, ""); return 0; } /* * 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. */ LONG MainWindow::OnLateInit(UINT, LONG) { CString result; CString appName; CString niftyListFile; appName.LoadString(IDS_MB_APP_NAME); WMSG0("----- late init begins -----\n"); /* * 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(); /* * 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; WMSG1("CheckRegistration returned %d\n", 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); WMSG0("FORCING REG\n"); #if 0 if (EnterRegDialog::GetRegInfo(this) != 0) { result = ""; goto fail; } #endif SetCPTitle(); // update title bar with new reg info break; case MyRegistry::kRegFailed: ASSERT(!result.IsEmpty()); goto fail; default: ASSERT(false); CString confused; confused.Format("Registration check failed. %s", (LPCTSTR) result); result = confused; goto fail; } /* * 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; } /* * The system wants to know if we're okay with shutting down. * * Return TRUE if it's okay to shut down, FALSE otherwise. */ BOOL MainWindow::OnQueryEndSession(void) { WMSG0("Got QueryEndSession\n"); return TRUE; } /* * Notification of shutdown (or not). */ void MainWindow::OnEndSession(BOOL bEnding) { WMSG1("Got EndSession (bEnding=%d)\n", bEnding); if (bEnding) { CloseArchiveWOControls(); fPreferences.SaveToRegistry(); } } /* * 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. */ void MainWindow::OnSize(UINT nType, int cx, int cy) { CFrameWnd::OnSize(nType, cx, cy); ResizeClientArea(); } void MainWindow::ResizeClientArea(void) { CRect sizeRect; GetClientRect(&sizeRect); if (fpContentList != NULL) fpContentList->MoveWindow(sizeRect); else Invalidate(false); } /* * Restrict the minimum window size to something reasonable. */ void MainWindow::OnGetMinMaxInfo(MINMAXINFO* pMMI) { pMMI->ptMinTrackSize.x = 256; pMMI->ptMinTrackSize.y = 192; } /* * Repaint the main window. */ void MainWindow::OnPaint(void) { 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 == nil) { 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) { WMSG0("MOUSE WHEEL\n"); return FALSE; WPARAM wparam; LPARAM lparam; wparam = nFlags | (zDelta << 16); lparam = pt.x | (pt.y << 16); if (fpContentList != nil) fpContentList->SendMessage(WM_MOUSEWHEEL, wparam, lparam); return CWnd::OnMouseWheel(nFlags, zDelta, pt); // return TRUE; } #endif /* * Make sure open controls keep the input focus. */ void MainWindow::OnSetFocus(CWnd* /*pOldWnd*/) { if (fpContentList != nil) { WMSG0("Returning focus to ContentList\n"); fpContentList->SetFocus(); } } /* * User hit F1. We don't currently have context-sensitive help on the main page. */ BOOL MainWindow::OnHelpInfo(HELPINFO* /*lpHelpInfo*/) { //WinHelp(0, HELP_FINDER); WinHelp(HELP_TOPIC_WELCOME, HELP_CONTEXT); return TRUE; // dunno what this means } #if 0 /* * Catch-all Help handler, necessary to allow CPropertySheet to display a * "Help" button. (WTF?) */ LONG MainWindow::OnHelp(UINT wParam, LONG lParam) { HELPINFO* lpHelpInfo = (HELPINFO*) lParam; DWORD context = lpHelpInfo->iCtrlId; WMSG1("MainWindow OnHelp (context=%d)\n", context); WinHelp(context, HELP_CONTEXTPOPUP); return TRUE; // yes, we handled it } #endif /* * Handle Edit->Preferences by popping up a property sheet. */ void MainWindow::OnEditPreferences(void) { PrefsSheet ps; ColumnLayout* pColLayout = fPreferences.GetColumnLayout(); /* pull any user header tweaks out of list so we can configure prefs */ if (fpContentList != nil) 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.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); } /* * Apply a change from the preferences sheet. */ void MainWindow::ApplyNow(PrefsSheet* pPS) { bool mustReload = false; //WMSG0("APPLY CHANGES\n"); ColumnLayout* pColLayout = fPreferences.GetColumnLayout(); if (pPS->fGeneralPage.fDefaultsPushed) { /* reset all sizes to defaults, then factor in checkboxes */ WMSG0(" Resetting all widths to defaults\n"); /* 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 */ WMSG1(" Column %d restored\n", i); pColLayout->SetColumnWidth(i, ColumnLayout::kWidthDefaulted); } else if (pColLayout->GetColumnWidth(i) != 0 && !pPS->fGeneralPage.fColumn[i]) { /* disable column */ WMSG1(" Column %d hidden\n", i); pColLayout->SetColumnWidth(i, 0); } } if (fpContentList != nil) 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)) { WMSG1("DOS filename coercion pref now %d\n", pPS->fGeneralPage.fCoerceDOSFilenames); fPreferences.SetPrefBool(kPrCoerceDOSFilenames, pPS->fGeneralPage.fCoerceDOSFilenames != 0); mustReload = true; } if (fPreferences.GetPrefBool(kPrSpacesToUnder) != (pPS->fGeneralPage.fSpacesToUnder != 0)) { WMSG1("Spaces-to-underscores now %d\n", 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 != nil) { WMSG0("NEW ASSOCIATIONS!\n"); for (int assoc = 0; assoc < gMyApp.fRegistry.GetNumFileAssocs(); assoc++) { gMyApp.fRegistry.SetFileAssoc(assoc, pPS->fGeneralPage.fOurAssociations[assoc]); } /* delete them so, if they hit "apply" again, we only update once */ delete[] pPS->fGeneralPage.fOurAssociations; pPS->fGeneralPage.fOurAssociations = nil; } 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(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); WMSG1("--- Temp path now '%s'\n", 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 != nil) fpOpenArchive->PreferencesChanged(); if (mustReload) { WMSG0("Preferences apply requesting GA/CL reload\n"); if (fpOpenArchive != nil) fpOpenArchive->Reload(); if (fpContentList != nil) fpContentList->Reload(); } /* export to registry */ fPreferences.SaveToRegistry(); //Invalidate(); } /* * Handle IDM_EDIT_FIND. */ void MainWindow::OnEditFind(void) { DWORD flags = 0; if (fpFindDialog != nil) 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 != nil); } /* * Handle activity in the modeless "find" dialog. */ LRESULT MainWindow::OnFindDialogMessage(WPARAM wParam, LPARAM lParam) { assert(fpFindDialog != nil); fFindDown = (fpFindDialog->SearchDown() != 0); fFindMatchCase = (fpFindDialog->MatchCase() != 0); fFindMatchWholeWord = (fpFindDialog->MatchWholeWord() != 0); if (fpFindDialog->IsTerminating()) { fpFindDialog = nil; return 0; } if (fpFindDialog->FindNext()) { fFindLastStr = fpFindDialog->GetFindString(); fpContentList->FindNext(fFindLastStr, fFindDown, fFindMatchCase, fFindMatchWholeWord); } else { WMSG0("Unexpected find dialog activity\n"); } return 0; } /* * Handle IDM_SORT_*. * * The "sort" enu item should really only be active if we have a file open. */ void MainWindow::OnEditSort(UINT id) { WMSG1("EDIT SORT %d\n", id); ASSERT(id >= IDM_SORT_PATHNAME && id <= IDM_SORT_ORIGINAL); fPreferences.GetColumnLayout()->SetSortColumn(id - IDM_SORT_PATHNAME); fPreferences.GetColumnLayout()->SetAscending(true); if (fpContentList != nil) fpContentList->NewSortOrder(); } void MainWindow::OnUpdateEditSort(CCmdUI* pCmdUI) { unsigned int column = fPreferences.GetColumnLayout()->GetSortColumn(); pCmdUI->SetCheck(pCmdUI->m_nID - IDM_SORT_PATHNAME == column); } /* * Open the help file. */ void MainWindow::OnHelpContents(void) { WinHelp(0, HELP_FINDER); } /* * Go to the faddenSoft web site. */ void MainWindow::OnHelpWebSite(void) { int err; err = (int) ::ShellExecute(m_hWnd, _T("open"), kWebSiteURL, NULL, NULL, SW_SHOWNORMAL); if (err <= 32) { CString msg; if (err == ERROR_FILE_NOT_FOUND) { msg = "Windows call failed: web browser not found. (Sometimes" " it mistakenly reports this when IE is not the default" " browser.)"; ShowFailureMsg(this, msg, IDS_FAILED); } else { msg.Format("Unable to launch web browser (err=%d).", err); ShowFailureMsg(this, msg, IDS_FAILED); } } } /* * Show ordering info (ka-ching!). */ void MainWindow::OnHelpOrdering(void) { WinHelp(HELP_TOPIC_ORDERING_INFO, HELP_CONTEXT); } /* * Pop up the About box. */ void MainWindow::OnHelpAbout(void) { int result; AboutDialog dlg(this); result = dlg.DoModal(); WMSG1("HelpAbout returned %d\n", result); /* * User could've changed registration. If we're showing the registered * user name in the title bar, update it. */ if (fpOpenArchive == nil) SetCPTitle(); } /* * Create a new SHK archive, using a "save as" dialog to select the name. */ void MainWindow::OnFileNewArchive(void) { CString filename, saveFolder, errStr; GenericArchive* pOpenArchive; CString errMsg; CFileDialog dlg(FALSE, _T("shk"), NULL, OFN_OVERWRITEPROMPT|OFN_NOREADONLYRETURN|OFN_HIDEREADONLY, "ShrinkIt Archives (*.shk)|*.shk||", this); dlg.m_ofn.lpstrTitle = "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(); WMSG1("NEW FILE '%s'\n", 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, nil); if (!errStr.IsEmpty()) { CString failed; failed.LoadString(IDS_FAILED); MessageBox(errStr, failed, MB_ICONERROR); delete pOpenArchive; } else { SwitchContentList(pOpenArchive); fOpenArchivePathName = dlg.GetPathName(); SetCPTitle(fOpenArchivePathName, fpOpenArchive); } bail: WMSG0("--- OnFileNewArchive done\n"); } /* * Handle request to open an archive or disk image. */ void MainWindow::OnFileOpen(void) { CString openFilters; CString saveFolder; /* set up filters; the order is significant */ openFilters = kOpenNuFX; openFilters += kOpenBinaryII; openFilters += kOpenACU; openFilters += kOpenDiskImage; openFilters += kOpenAll; openFilters += kOpenEnd; CFileDialog dlg(TRUE, "shk", NULL, OFN_FILEMUSTEXIST, openFilters, this); dlg.m_ofn.nFilterIndex = fPreferences.GetPrefLong(kPrLastOpenFilterIndex); 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); DoOpenArchive(dlg.GetPathName(), dlg.GetFileExt(), dlg.m_ofn.nFilterIndex, dlg.GetReadOnlyPref() != 0); bail: WMSG0("--- OnFileOpen done\n"); } /* * Handle request to open a raw disk volume. */ void MainWindow::OnFileOpenVolume(void) { WMSG0("--- OnFileOpenVolume\n"); 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); } /* * Open an archive. */ void MainWindow::DoOpenArchive(const char* pathName, const char* ext, int filterIndex, bool readOnly) { if (LoadArchive(pathName, ext, filterIndex, readOnly, false) == 0) { /* success, update title bar */ fOpenArchivePathName = pathName; SetCPTitle(fOpenArchivePathName, fpOpenArchive); } else { /* some failures will close an open archive */ //if (fpOpenArchive == nil) // SetCPTitle(); } } /* * Save any pending changes. * * This may be called directly from tools, so don't assume that the * conditions checked for in OnUpdateFileSave hold here. */ void MainWindow::OnFileReopen(void) { ReopenArchive(); } void MainWindow::OnUpdateFileReopen(CCmdUI* pCmdUI) { pCmdUI->Enable(fpOpenArchive != nil); } /* * Save any pending changes. * * This may be called directly from tools, so don't assume that the * conditions checked for in OnUpdateFileSave hold here. */ void MainWindow::OnFileSave(void) { CString errMsg; if (fpOpenArchive == nil) 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 != nil && fpOpenArchive->IsModified()); } /* * Close current archive or disk image. */ void MainWindow::OnFileClose(void) { CloseArchive(); //SetCPTitle(); WMSG0("--- OnFileClose done\n"); } void MainWindow::OnUpdateFileClose(CCmdUI* pCmdUI) { pCmdUI->Enable(fpOpenArchive != nil); } /* * Show detailed information on the current archive. */ void MainWindow::OnFileArchiveInfo(void) { ArchiveInfoDialog* pDlg = nil; ASSERT(fpOpenArchive != nil); 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; default: WMSG1("Unexpected archive type %d\n", fpOpenArchive->GetArchiveKind()); ASSERT(false); return; }; pDlg->DoModal(); delete pDlg; } void MainWindow::OnUpdateFileArchiveInfo(CCmdUI* pCmdUI) { pCmdUI->Enable(fpContentList != nil); } /* * Print the contents of the current archive. */ void MainWindow::OnFilePrint(void) { PrintListing(fpContentList); } void MainWindow::OnUpdateFilePrint(CCmdUI* pCmdUI) { pCmdUI->Enable(fpContentList != nil && fpContentList->GetItemCount() > 0); } /* * Print a ContentList. */ 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; msg.LoadString(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; } /* * Handle Exit item by sending a close request. */ void MainWindow::OnFileExit(void) { SendMessage(WM_CLOSE, 0, 0); } /* * Select everything in the content list. */ void MainWindow::OnEditSelectAll(void) { ASSERT(fpContentList != nil); fpContentList->SelectAll(); } void MainWindow::OnUpdateEditSelectAll(CCmdUI* pCmdUI) { pCmdUI->Enable(fpContentList != nil); } /* * Invert the content list selection. */ void MainWindow::OnEditInvertSelection(void) { ASSERT(fpContentList != nil); fpContentList->InvertSelection(); } void MainWindow::OnUpdateEditInvertSelection(CCmdUI* pCmdUI) { pCmdUI->Enable(fpContentList != nil); } /* * Get the one selected item from the current display. Primarily useful * for the double-click handler, but also used for "action" menu items * that insist on operating on a single menu item (edit prefs, create subdir). * * Returns nil if the item couldn't be found or if more than one item was * selected. */ GenericEntry* MainWindow::GetSelectedItem(ContentList* pContentList) { if (pContentList->GetSelectedCount() != 1) return nil; POSITION posn; posn = pContentList->GetFirstSelectedItemPosition(); if (posn == nil) { ASSERT(false); return nil; } int num = pContentList->GetNextSelectedItem(/*ref*/ posn); GenericEntry* pEntry = (GenericEntry*) pContentList->GetItemData(num); if (pEntry == nil) { WMSG1(" Glitch: couldn't find entry %d\n", num); ASSERT(false); } return pEntry; } /* * Handle a double-click. * * Individual items get special treatment, multiple items just get handed off * to the file viewer. */ void MainWindow::HandleDoubleClick(void) { bool handled = false; ASSERT(fpContentList != nil); if (fpContentList->GetSelectedCount() == 0) { /* nothing selected, they double-clicked outside first column */ WMSG0("Double-click but nothing selected\n"); 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 == nil) return; WMSG1(" Double-click GOT '%s'\n", pEntry->GetPathName()); const char* ext; long fileType, auxType; ext = FindExtension(pEntry->GetPathName(), 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 != nil && MatchSemicolonList(extViewerExts, ext+1)) { WMSG1(" Launching external viewer for '%s'\n", ext); TmpExtractForExternal(pEntry); handled = true; } else if (pEntry->GetRecordKind() == GenericEntry::kRecordKindFile) { if ((ext != nil && ( stricmp(ext, ".shk") == 0 || stricmp(ext, ".sdk") == 0 || stricmp(ext, ".bxy") == 0 )) || (fileType == 0xe0 && auxType == 0x8002)) { WMSG0(" Guessing NuFX\n"); TmpExtractAndOpen(pEntry, GenericEntry::kDataThread, kModeNuFX); handled = true; } else if ((ext != nil && ( stricmp(ext, ".bny") == 0 || stricmp(ext, ".bqy") == 0 )) || (fileType == 0xe0 && auxType == 0x8000)) { WMSG0(" Guessing Binary II\n"); TmpExtractAndOpen(pEntry, GenericEntry::kDataThread, kModeBinaryII); handled = true; } else if ((ext != nil && ( stricmp(ext, ".acu") == 0 )) || (fileType == 0xe0 && auxType == 0x8001)) { WMSG0(" Guessing ACU\n"); TmpExtractAndOpen(pEntry, GenericEntry::kDataThread, kModeACU); handled = true; } else if (fileType == 0x64496d67 && auxType == 0x64437079 && pEntry->GetUncompressedLen() == 819284) { /* type is dImg, creator is dCpy, length is 800K + DC stuff */ WMSG0(" Looks like a disk image\n"); TmpExtractAndOpen(pEntry, GenericEntry::kDataThread, kModeDiskImage); handled = true; } } else if (pEntry->GetRecordKind() == GenericEntry::kRecordKindDisk) { WMSG0(" Opening archived disk image\n"); TmpExtractAndOpen(pEntry, GenericEntry::kDiskImageThread, kModeDiskImage); handled = true; } if (!handled) { // standard viewer HandleView(); } /* set "/t" temp flag and delete afterward, warning user (?) */ } /* * Extract a record to the temp folder and open it with a new instance of * CiderPress. We might want to extract disk images as 2MG files to take * the mystery out of opening them, but since they're coming out of a * ShrinkIt archive they're pretty un-mysterious anyway. * * We tell the new instance to open it read-only, and flag it for * deletion on exit. * * Returns 0 on success, nonzero error status on failure. */ int MainWindow::TmpExtractAndOpen(GenericEntry* pEntry, int threadKind, const char* 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('"', '_'); char nameBuf[MAX_PATH]; UINT unique; unique = GetTempFileName(fPreferences.GetPrefString(kPrTempPath), "CPfile", 0, nameBuf); if (unique == 0) { DWORD dwerr = ::GetLastError(); WMSG2("GetTempFileName failed on '%s' (err=%ld)\n", 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 = fopen(nameBuf, "wb"); if (fp != nil) { WMSG2("Extracting to '%s' (unique=%d)\n", nameBuf, unique); result = pEntry->ExtractThreadToFile(threadKind, fp, GenericEntry::kConvertEOLOff, GenericEntry::kConvertHAOff, &errMsg); fclose(fp); if (result == IDOK) { /* success */ CString parameters; parameters.Format("-mode %s -dispname \"%s\" -temparc \"%s\"", modeStr, dispName, nameBuf); int err; err = (int) ::ShellExecute(m_hWnd, _T("open"), gMyApp.GetExeFileName(), parameters, NULL, SW_SHOWNORMAL); if (err <= 32) { CString msg; msg.Format("Unable to launch CiderPress (err=%d).", err); ShowFailureMsg(this, msg, IDS_FAILED); } else { /* during dev, "missing DLL" causes false-positive success */ WMSG0("Successfully launched CiderPress\n"); mustDelete = false; // up to newly-launched app } } else { ShowFailureMsg(this, errMsg, IDS_FAILED); } } else { CString msg; msg.Format("Unable to open temp file '%s'.", nameBuf); ::ShowFailureMsg(this, msg, IDS_FAILED); } if (mustDelete) { WMSG1("Deleting '%s'\n", nameBuf); unlink(nameBuf); } return 0; } /* * Extract a record to the temp folder and open it with an external viewer. * The file must be created with the correct extension so ShellExecute * does the right thing. * * The files will be added to the "delete on exit" list, so that they will * be cleaned up when CiderPress exits (assuming the external viewer no longer * has them open). * * The GetTempFileName function creates a uniquely-named temp file. We * create a file that has that name plus an extension. To ensure that we * don't try to use the same temp filename twice, we have to hold off on * deleting the unused .tmp files until we're ready to delete the * corresponding .gif (or whatever) files. Thus, each invocation of this * function creates two files and two entries in the delete-on-exit set. * * Returns 0 on success, nonzero error status on failure. */ int MainWindow::TmpExtractForExternal(GenericEntry* pEntry) { const char* ext; ext = FindExtension(pEntry->GetPathName(), pEntry->GetFssep()); char nameBuf[MAX_PATH]; UINT unique; unique = GetTempFileName(fPreferences.GetPrefString(kPrTempPath), "CPfile", 0, nameBuf); if (unique == 0) { DWORD dwerr = ::GetLastError(); WMSG2("GetTempFileName failed on '%s' (err=%ld)\n", fPreferences.GetPrefString(kPrTempPath), dwerr); return dwerr; } fDeleteList.Add(nameBuf); // file is created by GetTempFileName strcat(nameBuf, ext); /* * Open the temp file and extract the data into it. */ CString errMsg; int result; FILE* fp; fp = fopen(nameBuf, "wb"); if (fp != nil) { fDeleteList.Add(nameBuf); // second file created by fopen WMSG2("Extracting to '%s' (unique=%d)\n", 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, _T("open"), nameBuf, NULL, NULL, SW_SHOWNORMAL); if (err <= 32) { CString msg; msg.Format("Unable to launch external viewer (err=%d).", err); ShowFailureMsg(this, msg, IDS_FAILED); } else { WMSG0("Successfully launched external viewer\n"); } } else { ShowFailureMsg(this, errMsg, IDS_FAILED); } } else { CString msg; msg.Format("Unable to open temp file '%s'.", 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 != nil); idx = fpContentList->GetRightClickItem(); ASSERT(idx != -1); WMSG1("OnRtClkDefault %d\n", 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 != nil) fpActionProgress->SetProgress(0); else fStatusBar.SetPaneText(kProgressPane, "--%"); //WMSG0(" Complete: BEGIN\n"); /* redraw stuff with the changes */ (void) PeekAndPump(); } int MainWindow::SetProgressUpdate(int percent, const char* oldName, const char* newName) { int status = IDOK; if (fpActionProgress != nil) { status = fpActionProgress->SetProgress(percent); if (oldName != nil) fpActionProgress->SetArcName(oldName); if (newName != nil) fpActionProgress->SetFileName(newName); } else { char buf[8]; sprintf(buf, "%d%%", percent); fStatusBar.SetPaneText(kProgressPane, buf); //WMSG1(" Complete: %s\n", buf); } if (!PeekAndPump()) { WMSG0("SetProgressUpdate: shutdown?!\n"); } //EventPause(10); // DEBUG DEBUG return status; } void MainWindow::SetProgressEnd(void) { if (fpActionProgress != nil) fpActionProgress->SetProgress(100); else fStatusBar.SetPaneText(kProgressPane, ""); // EventPause(100); // DEBUG DEBUG //WMSG0(" Complete: END\n"); } /* * Set a number in the "progress counter". Useful for loading large archives * where we're not sure how much stuff is left, so showing a percentage is * hard. * * Pass in -1 to erase the counter. * * Returns "true" if we'd like things to continue. */ bool MainWindow::SetProgressCounter(const char* str, long val) { /* if the main window is enabled, user could activate menus */ ASSERT(!IsWindowEnabled()); if (fpProgressCounter != nil) { //WMSG2("SetProgressCounter '%s' %d\n", str, val); CString msg; if (str != nil) fpProgressCounter->SetCounterFormat(str); fpProgressCounter->SetCount((int) val); } else { if (val < 0) { fStatusBar.SetPaneText(kProgressPane, ""); } else { CString tmpStr; tmpStr.Format("%ld", val); fStatusBar.SetPaneText(kProgressPane, tmpStr); } } if (!PeekAndPump()) { WMSG0("SetProgressCounter: shutdown?!\n"); } //EventPause(10); // DEBUG DEBUG if (fpProgressCounter != nil) return !fpProgressCounter->GetCancel(); else return true; } /* * Allow events to flow through the message queue whenever the * progress meter gets updated. This will allow us to redraw with * reasonable frequency. * * Calling this can result in other code being called, such as Windows * message handlers, which can lead to reentrancy problems. Make sure * you're adequately semaphored before calling here. * * Returns TRUE if all is well, FALSE if we're trying to quit. */ 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; } /* * Go to sleep for a little bit, waking up 100x per second to check * the idle loop. */ void MainWindow::EventPause(int duration) { int count = duration / 10; for (int i = 0; i < count; i++) { PeekAndPump(); ::Sleep(10); } } /* * Printer abort procedure; allows us to abort a print job. The DC * SetAbortProc() function calls here periodically. The return value from * this function determine whether or not printing halts. * * This checks a global "print cancel" variable, which is set by our print * cancel button dialog. * * If this returns TRUE, printing continues; FALSE, and printing aborts. */ /*static*/ BOOL CALLBACK MainWindow::PrintAbortProc(HDC hDC, int nCode) { MainWindow* pMain = (MainWindow*)::AfxGetMainWnd(); pMain->PeekAndPump(); if (pMain->GetAbortPrinting()) { WMSG0("PrintAbortProc returning FALSE (abort printing)\n"); return FALSE; } WMSG0(" PrintAbortProc returning TRUE (continue printing)\n"); return TRUE; } /* * =================================== * Support functions * =================================== */ /* * Draw what looks like an empty client area. */ 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); } /* * Load an archive, using the appropriate GenericArchive subclass. If * "createFile" is "true", a new archive file will be created (and must * not already exist!). * * "filename" is the full path to the file, "extension" is the * filetype component of the name (without the leading '.'), "filterIndex" * is the offset into the set of filename filters used in the standard * file dialog, "readOnly" reflects the state of the stdfile dialog * checkbox, and "createFile" is set to true by the "New Archive" command. * * Returns 0 on success, nonzero on failure. */ int MainWindow::LoadArchive(const char* fileName, const char* extension, int filterIndex, bool readOnly, bool createFile) { GenericArchive::OpenResult openResult; int result = -1; GenericArchive* pOpenArchive = nil; int origFilterIndex = filterIndex; CString errStr, appName; appName.LoadString(IDS_MB_APP_NAME); WMSG3("LoadArchive: '%s' ro=%d idx=%d\n", fileName, readOnly, filterIndex); /* close any existing archive to avoid weirdness from re-open */ CloseArchive(); /* * If they used the "All Files (*.*)" filter, we have to guess based * on the file type. * * IDEA: change the current "filterIndex ==" stuff to a type-specific * model, then do type-scanning here. Code later on takes the type * and opens it. That way we can do the trivial "it must be" handling * up here, and maybe do a little "open it up and see" stuff as well. * In general, though, if we don't recognize the extension, it's * probably a disk image. */ if (filterIndex == kFilterIndexGeneric) { int i; for (i = 0; i < NELEM(gExtensionToIndex); i++) { if (strcasecmp(extension, gExtensionToIndex[i].extension) == 0) { filterIndex = gExtensionToIndex[i].idx; break; } } if (i == NELEM(gExtensionToIndex)) filterIndex = kFilterIndexDiskImage; } try_again: if (filterIndex == kFilterIndexBinaryII) { /* try Binary II and nothing else */ ASSERT(!createFile); WMSG0(" Trying Binary II\n"); pOpenArchive = new BnyArchive; openResult = pOpenArchive->Open(fileName, readOnly, &errStr); if (openResult != GenericArchive::kResultSuccess) { if (!errStr.IsEmpty()) ShowFailureMsg(this, errStr, IDS_FAILED); result = -1; goto bail; } } else if (filterIndex == kFilterIndexACU) { /* try ACU and nothing else */ ASSERT(!createFile); WMSG0(" Trying ACU\n"); pOpenArchive = new AcuArchive; openResult = pOpenArchive->Open(fileName, readOnly, &errStr); if (openResult != GenericArchive::kResultSuccess) { if (!errStr.IsEmpty()) ShowFailureMsg(this, errStr, IDS_FAILED); result = -1; goto bail; } } else if (filterIndex == kFilterIndexDiskImage) { /* try various disk image formats */ ASSERT(!createFile); WMSG0(" Trying disk images\n"); pOpenArchive = new DiskArchive; openResult = pOpenArchive->Open(fileName, readOnly, &errStr); if (openResult == GenericArchive::kResultCancel) { result = -1; goto bail; } else if (openResult == GenericArchive::kResultFileArchive) { delete pOpenArchive; pOpenArchive = nil; if (strcasecmp(extension, "zip") == 0) { errStr = "ZIP archives with multiple files are not supported."; MessageBox(errStr, appName, MB_OK|MB_ICONINFORMATION); result = -1; goto bail; } else { /* assume some variation of a ShrinkIt archive */ // msg.LoadString(IDS_OPEN_AS_NUFX); <-- with MB_OKCANCEL filterIndex = kFilterIndexNuFX; goto try_again; } } else if (openResult != GenericArchive::kResultSuccess) { if (filterIndex != origFilterIndex) { /* * Kluge: assume we guessed disk image and were wrong. */ errStr = "File doesn't appear to be a valid archive" " or disk image."; } if (!errStr.IsEmpty()) ShowFailureMsg(this, errStr, IDS_FAILED); result = -1; goto bail; } } else if (filterIndex == kFilterIndexNuFX) { /* try NuFX (including its embedded-in-BNY form) */ WMSG0(" Trying NuFX\n"); pOpenArchive = new NufxArchive; openResult = pOpenArchive->Open(fileName, readOnly, &errStr); if (openResult != GenericArchive::kResultSuccess) { if (!errStr.IsEmpty()) ShowFailureMsg(this, errStr, IDS_FAILED); result = -1; goto bail; } } else { ASSERT(FALSE); result = -1; goto bail; } SwitchContentList(pOpenArchive); pOpenArchive = nil; result = 0; bail: if (pOpenArchive != nil) { ASSERT(result != 0); delete pOpenArchive; } return result; } /* * Open a raw disk volume. Useful for ProDOS-formatted 1.44MB floppy disks * and CFFA flash cards. * * Assume it's a disk image -- it'd be a weird place for a ShrinkIt archive. * CFFA cards can actually hold multiple volumes, but that's all taken care * of inside the diskimg DLL. * * Returns 0 on success, nonzero on failure. */ int MainWindow::DoOpenVolume(CString drive, bool readOnly) { int result = -1; ASSERT(drive.GetLength() > 0); CString errStr; //char filename[4] = "_:\\"; //filename[0] = driveLetter; WMSG2("FileOpenVolume '%s' %d\n", (const char*)drive, readOnly); /* close existing archive */ CloseArchive(); GenericArchive* pOpenArchive = nil; 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 = nil; fOpenArchivePathName = drive; result = 0; fOpenArchivePathName = drive; SetCPTitle(fOpenArchivePathName, fpOpenArchive); bail: if (pOpenArchive != nil) { ASSERT(result != 0); delete pOpenArchive; } return result; } /* * Close and re-open the current archive. */ void MainWindow::ReopenArchive(void) { if (fpOpenArchive == nil) { ASSERT(false); return; } /* clear the flag, regardless of success or failure */ fNeedReopen = false; GenericArchive* pOpenArchive = nil; 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 */ WMSG3("Reopening '%s' ro=%d kind=%d\n", 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; } WMSG0(" Reopen was successful\n"); SwitchContentList(pOpenArchive); pOpenArchive = nil; SetCPTitle(pathName, fpOpenArchive); bail: delete pOpenArchive; } /* * Determine whether "path" matches the pathname of the currently open archive. */ bool MainWindow::IsOpenPathName(const char* path) { if (fpOpenArchive == nil) return false; if (stricmp(path, fpOpenArchive->GetPathName()) == 0) return true; return false; } /* * Switch the content list to a new archive, closing the previous if one * was already open. */ void MainWindow::SwitchContentList(GenericArchive* pOpenArchive) { assert(pOpenArchive != nil); /* * 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 != nil) CloseArchive(); ASSERT(fpOpenArchive == nil); ASSERT(fpContentList == nil); /* * 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; } /* * Close the existing archive file, but don't try to shut down the child * windows. This should really only be used from the destructor. */ void MainWindow::CloseArchiveWOControls(void) { if (fpOpenArchive != nil) { //fpOpenArchive->Close(); WMSG0("Deleting OpenArchive\n"); delete fpOpenArchive; fpOpenArchive = nil; } } /* * Close the existing archive file, and throw out the control we're * using to display it. */ void MainWindow::CloseArchive(void) { CWaitCursor waitc; // closing large compressed archive can be slow // destroy the ContentList if (fpContentList != nil) { WMSG0("Destroying ContentList\n"); fpContentList->DestroyWindow(); // auto-cleanup invokes "delete" fpContentList = nil; } // destroy the GenericArchive CloseArchiveWOControls(); // reset the title bar SetCPTitle(); } /* * Set the title bar on the main window. * * "pathname" is often different from pOpenArchive->GetPathName(), especially * when we were launched from another instance of CiderPress and handed a * temp file whose name we're trying to conceal. */ void MainWindow::SetCPTitle(const char* pathname, GenericArchive* pOpenArchive) { ASSERT(pathname != nil); CString title; CString archiveDescription; CString appName; appName.LoadString(IDS_MB_APP_NAME); pOpenArchive->GetDescription(&archiveDescription); title.Format(_T("%s - %s (%s)"), appName, pathname, archiveDescription); if (fpOpenArchive->IsReadOnly()) { CString readOnly; readOnly.LoadString(IDS_READONLY); title += _T(" "); title += readOnly; } SetWindowText(title); } /* * Set the title bar to something boring when nothing is open. */ void MainWindow::SetCPTitle(void) { CString appName, regName, title; CString user, company, reg, versions, expire; #if 0 if (gMyApp.fRegistry.GetRegistration(&user, &company, ®, &versions, &expire) == 0) { if (reg.IsEmpty()) { regName += _T(" (unregistered)"); } else { regName += _T(" (registered to "); regName += user; regName += _T(")"); // include company? } } #endif appName.LoadString(IDS_MB_APP_NAME); title = appName + regName; SetWindowText(title); } /* * Come up with a title to put at the top of a printout. This is essentially * the same as the window title, but without some flags (e.g. "read-only"). */ CString MainWindow::GetPrintTitle(void) { CString title; CString archiveDescription; CString appName; if (fpOpenArchive == nil) { ASSERT(false); return title; } appName.LoadString(IDS_MB_APP_NAME); fpOpenArchive->GetDescription(&archiveDescription); title.Format(_T("%s - %s (%s)"), appName, fOpenArchivePathName, archiveDescription); return title; } /* * After successful completion of a command, make a happy noise (but only * if we're configured to do so). */ void MainWindow::SuccessBeep(void) { const Preferences* pPreferences = GET_PREFERENCES(); if (pPreferences->GetPrefBool(kPrBeepOnSuccess)) { WMSG0("\n"); ::MessageBeep(MB_OK); } } /* * If something fails, make noise if we're configured for loudness. */ void MainWindow::FailureBeep(void) { const Preferences* pPreferences = GET_PREFERENCES(); if (pPreferences->GetPrefBool(kPrBeepOnSuccess)) { WMSG0("\n"); ::MessageBeep(MB_ICONEXCLAMATION); // maybe MB_ICONHAND? } } /* * Remove a file. Returns a helpful error string on failure. * * The absence of the file is not considered an error. */ CString MainWindow::RemoveFile(const char* fileName) { CString errMsg; int cc; cc = unlink(fileName); if (cc < 0 && errno != ENOENT) { int err = errno; WMSG2("Failed removing file '%s', errno=%d\n", fileName, err); errMsg.Format("Unable to remove '%s': %s.", fileName, strerror(err)); if (err == EACCES) errMsg += "\n\n(Make sure the file isn't open.)"; } return errMsg; } /* * Configure a ReformatHolder based on the current preferences. */ /*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)); } /* * Convert a DiskImg format spec into a ReformatHolder SourceFormat. */ /*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; }