home *** CD-ROM | disk | FTP | other *** search
/ Visual Basic Source Code / Visual Basic Source Code.iso / vbsource / metkit / catfish.cpp < prev    next >
Encoding:
C/C++ Source or Header  |  1997-06-07  |  42.4 KB  |  1,418 lines

  1. //    Copyright (C) 1996, 1997 Meta Four Software.  All rights reserved.
  2. //
  3. //  Main CatFish application sample code
  4. //
  5. //! rev="$Id: catfish.cpp,v 1.5 1997/05/27 00:06:05 jcw Rel $"
  6.  
  7. #include "stdafx.h"
  8. #include "catfish.h"
  9. #include "setupdlg.h"
  10.  
  11.     #include <dos.h>    // _dos_findfirst in GetCatalogDate
  12.     
  13. #ifdef _DEBUG
  14. #undef THIS_FILE
  15. static char BASED_CODE THIS_FILE[] = __FILE__;
  16. #endif
  17.  
  18. #pragma warning(disable: 4702) // MSVC 1.52 gets confused: unreachable code
  19.     
  20. /////////////////////////////////////////////////////////////////////////////
  21. // MSDN Q100770: Using Accelerator Keys When Modal Dialog Box Main Window
  22.  
  23.     HWND    ghDlg;          // Handle to main dialog box
  24.     HACCEL  ghAccelTable;   // Handle to accelerator table
  25.  
  26.     CTheApp ThisApp;
  27.  
  28. /////////////////////////////////////////////////////////////////////////////
  29. // Use a simple version of localized date, time, and number formating.
  30.  
  31.     static CString  sShortDate  = "MM/dd/yy";   // "d.M.yyyy", etc
  32.     static bool     iTime       = false;        // true if 24h format
  33.     static bool     iTLZero     = true;         // true if hour has 2 digits
  34.     static char     sThousand   = ',';          // thousands separator
  35.     static char     sTime       = ':';          // time separator
  36.  
  37.     static void SetInternationalSettings()
  38.     {
  39.         iTime = GetProfileInt("intl", "iTime", 0) != 0;
  40.         iTLZero = GetProfileInt("intl", "iTLZero", 1) != 0;
  41.         
  42.         char buf [30];
  43.         
  44.         if (GetProfileString("intl", "sShortDate", "MM/dd/yy", buf, sizeof buf))
  45.             sShortDate = buf;
  46.         
  47.         if (GetProfileString("intl", "sThousand", ",", buf, sizeof buf))
  48.             sThousand = *buf;
  49.         
  50.         if (GetProfileString("intl", "sTime", ":", buf, sizeof buf))
  51.             sTime = *buf;
  52.     }
  53.         
  54. /////////////////////////////////////////////////////////////////////////////
  55. // Convert a number to comma-separated format, grouped in units of three.
  56. // Optionally prefix with spaces (assuming two spaces is width of one digit).
  57. // Finally, the zero value can be changed to a '-' upon request.
  58. //
  59. // Note:    In many places, the code is simplified by the assumption that
  60. //          every digit has exactly the same width as two space characters.
  61. //          This works for the selected font (MS Sans Serif, font size 8).
  62. //          It allows us to present a nice columnar interface without having
  63. //          to figure out each of the string position in pixels. There are
  64. //          several more assumptions like this (e.g. "k   " is like "Mb").
  65.  
  66.     static CString CommaNum(DWORD num, int groups =0, BOOL zero =TRUE)
  67.     {
  68.         CString s;
  69.         s.Format("%lu", num);
  70.         
  71.         int g = 0;
  72.         int n = s.GetLength();
  73.         while (n > 3)
  74.         {
  75.             n -= 3;
  76.             s = s.Left(n) + sThousand + s.Mid(n);
  77.             ++g;
  78.         }
  79.         
  80.         if (--groups >= 0)
  81.         {
  82.             int w = ((3 - n) % 3) * 2;
  83.             if (g < groups)
  84.                 w += 7 * (groups - g);
  85.  
  86.             s = CString (' ', w) + s;
  87.         }
  88.         
  89.         if (!zero && (s == "0" || s.Right(2) == " 0"))
  90.             s = s.Left(s.GetLength() - 1) + " -";
  91.             
  92.         return s;
  93.     }
  94.     
  95. /////////////////////////////////////////////////////////////////////////////
  96. // Convert a DOS date and TIME words to short format strings.
  97. // Lets be nice to a lot of people and adopt their local conventions.
  98.  
  99.     static CString ShortDate(WORD date)
  100.     {
  101.         if (date == 0)
  102.             return "";  // will be ok as long as the date is last item
  103.             
  104.         int w = 0;
  105.         
  106.         char buf [10];
  107.         char* q = buf;
  108.         
  109.             // decode the short date, deal with 1- and 2-digit fields
  110.         const char* p = sShortDate;
  111.         while (*p)
  112.         {
  113.             int i;
  114.             
  115.             switch (*p++)
  116.             {            
  117.                 default:    *q++ = *(p-1);
  118.                             continue;
  119.                 
  120.                 case 'd':   i = date & 0x1F;
  121.                             break;
  122.                             
  123.                 case 'M':   i = (date >> 5) & 0x0F;
  124.                             break;
  125.                             
  126.                 case 'y':   i = ((date >> 9) + 80) % 100;
  127.                             break; // 4-digit years are treated as 2-digit
  128.                             
  129.             }
  130.             
  131.             if (i < 10 && *p != *(p-1))
  132.                 ++w;
  133.             else
  134.                 *q++ = (char) (i / 10 + '0');
  135.             
  136.             *q++ = (char) (i % 10 + '0');
  137.  
  138.             while (*p == *(p-1))
  139.                 ++p;
  140.         }
  141.         
  142.             // centering is easy, since one digit is as wide as two spaces
  143.         CString t (' ', 2 * w);
  144.             // alignment depends on whether the year is first or last 
  145.         if (sShortDate[0] == 'y')
  146.             return CString (buf, q - buf) + t;
  147.         
  148.         return t + CString (buf, q - buf);
  149.     }
  150.     
  151.     static CString ShortTime(WORD time)
  152.     {
  153.         int h = time >> 11;
  154.         int m = (time >> 5) & 0x3F;
  155.         char ampm = "ap" [h / 12];
  156.         
  157.         if (!iTime)
  158.             h = (h + 11) % 12 + 1; // dec, then inc, so 0 becomes 12
  159.             
  160.         CString s;
  161.         s.Format("%02d%c%02d", h, sTime, m);
  162.         
  163.         if (!iTime)
  164.             s += ampm;
  165.         
  166.         if (!iTLZero && s[0] == '0')
  167.             s = "  " + s.Mid(1); // replace leading zero with two spaces
  168.             
  169.         return s;
  170.     }
  171.     
  172. /////////////////////////////////////////////////////////////////////////////
  173. // Make a string fit in the specified number of pixels on given device.
  174. // Characters at the end are replaced by an ellipsis to make the string fit.
  175. // There is some trickery in here to optimize this very common calculation.
  176.  
  177.     static BOOL FitString(CDC* dc, CString& text, int width)
  178.     {
  179.         CSize sz = dc->GetTextExtent(text, text.GetLength());
  180.         if (sz.cx <= width)
  181.             return TRUE;    // make the most common case fast
  182.         
  183.             // Assumption: "...xyz" is just as wide as "xyz..." 
  184.         CString s = "..." + text;
  185.         
  186.         int n = s.GetLength();
  187.         while (--n > 3)
  188.         {            
  189.             sz = dc->GetTextExtent(text, n);
  190.             if (sz.cx <= width)
  191.                 break;
  192.         }
  193.              
  194.         text = text.Left(n - 3) + "...";
  195.         return FALSE;
  196.     }
  197.     
  198. /////////////////////////////////////////////////////////////////////////////
  199. // Disables redraw and clears listbox, will reset normal state in destructor
  200.  
  201.     class ListBoxFreezer
  202.     {
  203.     public:
  204.         ListBoxFreezer (CListBox& lb)
  205.             : list (lb)
  206.         {
  207.             list.SetRedraw(FALSE);
  208.             list.ResetContent();
  209.         }
  210.         
  211.         ~ListBoxFreezer ()
  212.         {
  213.             list.SetRedraw(TRUE);
  214.             list.Invalidate();
  215.         }
  216.     
  217.     private:
  218.         CListBox& list;
  219.     };
  220.     
  221. /////////////////////////////////////////////////////////////////////////////
  222. // Return file date in display format, or an empty string if file not present
  223.  
  224. CString GetCatalogDate(CString& catName)
  225. {
  226.     CString s = catName;
  227.     s += FILE_TYPE;
  228.  
  229. #ifndef _WIN32
  230.     _find_t fbuf;
  231.     if (_dos_findfirst(s, _A_NORMAL, &fbuf) != 0)
  232.         return "";
  233.     
  234.         // pick up the name as it is stored on disk (properly capitalized)
  235.     s = fbuf.name;
  236.     ASSERT(s.Right(4).CompareNoCase(FILE_TYPE) == 0);
  237.     catName = s.Left(s.GetLength() - 4);
  238.         
  239.     return ShortDate((WORD) fbuf.wr_date) + "  "
  240.             + ShortTime((WORD) fbuf.wr_time);
  241. #endif
  242.     return "?";
  243. }
  244.  
  245. /////////////////////////////////////////////////////////////////////////////
  246. // The one and only application object
  247.  
  248. CTheApp::CTheApp ()
  249.     : CWinApp ("CatFish")
  250. {    
  251. }
  252.  
  253. BOOL CTheApp::InitInstance()
  254. {
  255.     SetDialogBkColor();
  256.     SetInternationalSettings();
  257.  
  258.     ghAccelTable = LoadAccelerators(AfxGetInstanceHandle(),
  259.                                     MAKEINTRESOURCE(IDD_MAIN_DIALOG));
  260.  
  261.         // the following is required to let a dialog box have an icon   
  262.     static WNDCLASS wndclass;
  263.     if (!wndclass.lpfnWndProc)
  264.     {
  265.         wndclass.lpfnWndProc    = DefDlgProc;
  266.         wndclass.cbWndExtra     = DLGWINDOWEXTRA ;
  267.         wndclass.hInstance      = m_hInstance;
  268.         wndclass.hIcon          = LoadIcon(AFX_IDI_STD_FRAME);
  269.         wndclass.lpszClassName  = "CATFISHCLASS";
  270.         
  271.         RegisterClass(&wndclass);
  272.     }
  273.     
  274.         // enter a modal loop right now 
  275.     CMainDlgWindow mainDlg;
  276.     m_pMainWnd = &mainDlg;
  277.     
  278.     mainDlg.Execute(stricmp(m_lpCmdLine, "/f") == 0);
  279.     
  280.         // and then return false to skip the main application run loop
  281.     return FALSE;
  282. }
  283.  
  284. BOOL CTheApp::ProcessMessageFilter(int code, LPMSG lpMsg)
  285. {
  286.     if (code < 0)
  287.         CWinApp::ProcessMessageFilter(code, lpMsg);
  288.          
  289.     if (ghDlg && ghAccelTable)
  290.     {
  291.         if (::TranslateAccelerator(ghDlg, ghAccelTable, lpMsg))
  292.             return(TRUE);
  293.     }
  294.          
  295.     return CWinApp::ProcessMessageFilter(code, lpMsg);
  296. }
  297.  
  298. /////////////////////////////////////////////////////////////////////////////
  299.  
  300. BEGIN_MESSAGE_MAP(CMainDlgWindow, CDialog)
  301.     //{{AFX_MSG_MAP(CMainDlgWindow)
  302.     ON_WM_CLOSE()
  303.     ON_WM_DRAWITEM()
  304.     ON_LBN_SELCHANGE(IDC_CAT_LIST, OnSelchangeCatList)
  305.     ON_LBN_SELCHANGE(IDC_TREE_LIST, OnSelchangeTreeList)
  306.     ON_LBN_DBLCLK(IDC_TREE_LIST, OnDblclkTreeList)
  307.     ON_LBN_SELCHANGE(IDC_FILE_LIST, OnSelchangeFileList)
  308.     ON_BN_CLICKED(IDC_FIND_BTN, OnFindBtn)
  309.     ON_BN_CLICKED(IDC_SETUP_BTN, OnSetupBtn)
  310.     ON_LBN_DBLCLK(IDC_FILE_LIST, OnDblclkFileList)
  311.     ON_COMMAND(ID_FIND_NEXT, OnFindNext)
  312.     ON_COMMAND(ID_FIND_PREV, OnFindPrev)
  313.     ON_COMMAND(ID_SORT_BY_NAME, OnSortByName)
  314.     ON_COMMAND(ID_SORT_BY_SIZE, OnSortBySize)
  315.     ON_COMMAND(ID_SORT_BY_DATE, OnSortByDate)
  316.     ON_COMMAND(ID_SORT_REVERSE, OnSortReverse)
  317.     ON_WM_DESTROY()
  318.     ON_WM_LBUTTONDOWN()
  319.     ON_COMMAND(ID_APP_ABOUT, OnAppAbout)
  320.     ON_COMMAND(ID_FILE_EXPORT, OnFileExport)
  321.     ON_COMMAND(ID_FILE_REFRESH, OnFileRefresh)
  322.     ON_WM_CHAR()
  323.     ON_COMMAND(ID_FIND_CMD, OnFindBtn)
  324.     ON_COMMAND(ID_FILE_SETUP, OnSetupBtn)
  325.     ON_LBN_DBLCLK(IDC_CAT_LIST, OnSetupBtn)
  326.     ON_COMMAND(ID_EDIT_FIND, OnFindBtn)
  327.     //}}AFX_MSG_MAP
  328.     ON_COMMAND(ID_FILE_EXIT, OnClose)
  329.     ON_COMMAND(ID_HELP, OnHelp)
  330. END_MESSAGE_MAP()
  331.  
  332. /////////////////////////////////////////////////////////////////////////////
  333.  
  334. CMainDlgWindow::CMainDlgWindow ()
  335.     : CDialog (IDD_MAIN_DIALOG),
  336.       m_storage (0), m_fileDir (-1), m_treeDir (-1), m_dc (0),
  337.       m_sortProp (&pName), m_sortReverse (false), m_startFind (false)
  338. {
  339.     //{{AFX_DATA_INIT(CMainDlgWindow)
  340.     //}}AFX_DATA_INIT
  341. }
  342.  
  343. CMainDlgWindow::~CMainDlgWindow ()
  344. {
  345.     delete m_storage;
  346. }
  347.  
  348. int CMainDlgWindow::Execute(bool find)
  349. {
  350.     m_startFind = find;
  351.     
  352.     return DoModal();
  353. }
  354.  
  355. void CMainDlgWindow::DoDataExchange(CDataExchange* pDX)
  356. {
  357.     CDialog::DoDataExchange(pDX);
  358.     //{{AFX_DATA_MAP(CMainDlgWindow)
  359.     DDX_Control(pDX, IDC_FIND_BTN, m_findBtn);
  360.     DDX_Control(pDX, IDC_PATH_FRAME, m_pathFrame);
  361.     DDX_Control(pDX, IDC_TREE_LIST, m_treeList);
  362.     DDX_Control(pDX, IDC_FILE_LIST, m_fileList);
  363.     DDX_Control(pDX, IDC_CAT_LIST, m_catList);
  364.     DDX_Control(pDX, IDC_MSG_TEXT, m_msgText);
  365.     DDX_Control(pDX, IDC_INFO_TEXT, m_infoText);
  366.     DDX_Control(pDX, IDC_TREE_PATH, m_treePath);
  367.     //}}AFX_DATA_MAP
  368. }
  369.  
  370. void CMainDlgWindow::OnCancel()
  371. {
  372.     ::MessageBeep(0);                  // don't go away on ESC key
  373. }
  374.  
  375. void CMainDlgWindow::OnClose()
  376. {
  377.     EndDialog(IDOK);
  378. }
  379.  
  380. void CMainDlgWindow::OnDestroy()
  381. {
  382.     CDialog::OnDestroy();
  383.     
  384.     SetCatalog("");
  385. }
  386.  
  387. BOOL CMainDlgWindow::OnInitDialog()
  388. {
  389.     CDialog::OnInitDialog();
  390.  
  391.     ghDlg = m_hWnd;
  392.     
  393.         // create a small font for several of the dialog box items
  394.     LOGFONT lf;
  395.     memset(&lf, 0, sizeof(LOGFONT));
  396.     lf.lfHeight = -8;
  397.     strcpy(lf.lfFaceName, "MS Sans Serif");
  398.     m_font.CreateFontIndirect(&lf);
  399.     
  400.     m_msgText.SetFont(&m_font, FALSE);
  401.     m_infoText.SetFont(&m_font, FALSE);
  402.     m_catList.SetFont(&m_font, FALSE);
  403.     m_treeList.SetFont(&m_font, FALSE);
  404.     m_fileList.SetFont(&m_font, FALSE);
  405.     
  406.         // determine the character height and set owner-draw lists accordingly
  407.     {
  408.         CClientDC dc (this);
  409.         CFont* oldFont = dc.SelectObject(&m_font);
  410.             
  411.         TEXTMETRIC tm;
  412.         VERIFY(dc.GetTextMetrics(&tm));
  413.                
  414.         dc.SelectObject(oldFont);
  415.             
  416.         m_catList.SetItemHeight(0, tm.tmHeight);
  417.         m_treeList.SetItemHeight(0, tm.tmHeight);
  418.         m_fileList.SetItemHeight(0, tm.tmHeight);
  419.     }
  420.     
  421.         // fill the list of catalogs
  422.     m_catList.Dir(0, "*" FILE_TYPE);
  423.     
  424.         // default file sort order is by filename
  425.     SortFileList(pName);
  426.  
  427.         // show contents now, before potential slow catalog loading starts
  428.     ShowWindow(ThisApp.m_nCmdShow);
  429.     UpdateWindow(); 
  430.     
  431.     m_catList.SetCurSel(0);
  432.     OnSelchangeCatList();
  433.  
  434.     m_infoText.SetWindowText("http://purl.net/meta4/metakit");
  435.     
  436.     if (m_catList.GetCount() == 0)
  437.         OnHelp();
  438.     
  439.     if (m_startFind)
  440.         OnFindBtn();
  441.         
  442.     return TRUE;    // return TRUE  unless you set the focus to a control
  443. }
  444.  
  445.     // notification handler for owner-draw listboxes
  446. void CMainDlgWindow::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct)
  447. {
  448.     int n = (int) lpDrawItemStruct->itemID;
  449.     if (n == -1)
  450.        return;
  451.     
  452.     m_item = lpDrawItemStruct;
  453.     m_dc = CDC::FromHandle(m_item->hDC);
  454.     
  455.     if (m_item->itemAction == ODA_FOCUS)
  456.     {
  457.         m_dc->DrawFocusRect(&m_item->rcItem);
  458.         return;
  459.     }
  460.     
  461.     if (m_item->itemState & ODS_SELECTED)
  462.     {
  463.         m_dc->SetBkColor(GetSysColor(COLOR_HIGHLIGHT));
  464.         m_dc->SetTextColor(GetSysColor(COLOR_HIGHLIGHTTEXT));
  465.     }
  466.     else
  467.     {
  468.         m_dc->SetBkColor(GetSysColor(COLOR_WINDOW));
  469.         m_dc->SetTextColor(GetSysColor(COLOR_WINDOWTEXT));
  470.     }
  471.  
  472.     switch (nIDCtl)
  473.     {
  474.         case IDC_CAT_LIST:      OnDrawCatItem(n); break;
  475.         case IDC_TREE_LIST:     OnDrawDirItem(n); break;
  476.         case IDC_FILE_LIST:     OnDrawFileItem(n); break;
  477.     }
  478.     
  479.     if ((m_item->itemState & ODS_FOCUS) && m_item->itemAction != ODA_SELECT)
  480.         m_dc->DrawFocusRect(&m_item->rcItem);
  481. }
  482.  
  483.     // common code to draw a text string in a listbox item
  484. void CMainDlgWindow::DrawItemText(const CString& text, int off)
  485. {
  486.     RECT& rect = m_item->rcItem;
  487.     
  488.     m_dc->ExtTextOut(rect.left + off + 2, rect.top,
  489.                         off ? 0 : ETO_OPAQUE, &rect,
  490.                         text, text.GetLength(), 0);
  491. }
  492.  
  493.     // draw one item in the catalog listbox
  494. void CMainDlgWindow::OnDrawCatItem(int n)
  495. {
  496.     CString s;
  497.     m_catList.GetText(n, s);
  498.     
  499.     ASSERT(s.Right(4).CompareNoCase(FILE_TYPE) == 0);
  500.     s = s.Left(s.GetLength() - 4);
  501.     
  502.     CString date = GetCatalogDate(s);
  503.  
  504.     FitString(m_dc, s, 72);
  505.     DrawItemText(s);    
  506.     
  507.     DrawItemText(date, 72);
  508. }
  509.  
  510.     // draw one item in the tree listbox
  511. void CMainDlgWindow::OnDrawDirItem(int n)
  512. {
  513.     int dir = (int) m_treeList.GetItemData(n);
  514.     BOOL up = n == 0 || pParent (m_currCat[dir]) != m_treeDir;
  515.     
  516.     if (up && !(m_item->itemState & ODS_SELECTED))
  517.         m_dc->SetTextColor(GetSysColor(COLOR_GRAYTEXT));
  518.  
  519.     CString s = pName (m_currCat[dir]);
  520.     if (dir == 0)
  521.         s = "(root)";
  522.     
  523.     if (!up)
  524.         s = (m_dirCounts[dir] ? "+ " : "   ") + s;
  525.  
  526.     FitString(m_dc, s, 78);
  527.     DrawItemText(s);    
  528.     
  529.     DWORD t = m_kiloTotals[dir];
  530.     if (t <= 999999)
  531.         s = CommaNum(t, 2) + " k   ";
  532.     else
  533.         s = CommaNum((t + 999) / 1000, 2) + " Mb";
  534.         
  535.     s += CommaNum(m_dirCounts[dir], 2, FALSE).Mid(2) + "  "
  536.        + CommaNum(m_fileCounts[dir], 2, FALSE) + "    "
  537.        + ShortDate(m_lastDates[dir]);
  538.        
  539.     DrawItemText(s, 78);    
  540. }
  541.  
  542.     // draw one item in the file listbox
  543. void CMainDlgWindow::OnDrawFileItem(int n)
  544. {
  545.     c4_RowRef file = m_fileSort[n];
  546.     
  547.     CString s = pName (file);
  548.     FitString(m_dc, s, 85);
  549.     DrawItemText(s);
  550.     
  551.     s = CommaNum(pSize (file), 3) + "    " + ShortDate((WORD) pDate (file));
  552.     DrawItemText(s, 85);    
  553. }
  554.  
  555.     // pressing F1 leads to an brief help screen
  556. void CMainDlgWindow::OnHelp()
  557. {
  558.     CDialog dlg (IDD_WELCOME_DLG);
  559.     dlg.DoModal();
  560. }
  561.  
  562.     // there is of course also an about box
  563. void CMainDlgWindow::OnAppAbout()
  564. {
  565.     CDialog dlg (IDD_ABOUTBOX);
  566.     dlg.DoModal();
  567. }
  568.  
  569.     // find file entries
  570. void CMainDlgWindow::OnFindBtn()
  571. {
  572.     int n = m_catList.GetCurSel();
  573.     ASSERT(n != LB_ERR);
  574.  
  575.     CString s;
  576.     m_catList.GetText(n, s);      
  577.         
  578.     ASSERT(s.Right(4).CompareNoCase(FILE_TYPE) == 0);
  579.     s = s.Left(s.GetLength() - 4);
  580.     
  581.     s.MakeUpper();
  582.         
  583.     if (m_findDlg.Execute(s))
  584.         OnFindNext();
  585. }
  586.  
  587.     // setup catalogs
  588. void CMainDlgWindow::OnSetupBtn()
  589. {
  590.     DoSetup(false);
  591. }
  592.  
  593.     // update catalog
  594. void CMainDlgWindow::OnFileRefresh()
  595. {
  596.     int n = m_catList.GetCurSel();
  597.     if (n == LB_ERR)
  598.         return; // the easy way out, better would be to disable the menu item
  599.     
  600.     DoSetup(true);
  601. }
  602.  
  603.     // setup catalogs
  604. void CMainDlgWindow::DoSetup(bool now)
  605. {
  606.     CSetupDialog dlg;
  607.  
  608.     int n = m_catList.GetCurSel();
  609.     if (n != LB_ERR)
  610.     {                         
  611.         CString s;
  612.         m_catList.GetText(n, s);      
  613.         
  614.         ASSERT(s.Right(4).CompareNoCase(FILE_TYPE) == 0);
  615.         dlg.m_name = s.Left(s.GetLength() - 4);
  616.     }
  617.         
  618.     SetCatalog(""); // make sure no catalog is in use during setup
  619.     
  620.     dlg.Execute(now);
  621.     
  622.     {
  623.         ListBoxFreezer frozen (m_catList);
  624.     
  625.         m_catList.Dir(0, "*" FILE_TYPE);
  626.     
  627.             // attempt to maintain the current selection
  628.         if (m_catList.SelectString(-1, dlg.m_name) == LB_ERR)
  629.             m_catList.SetCurSel(0);
  630.     }
  631.     
  632.     OnSelchangeCatList();
  633. }
  634.  
  635.     // adjust the title to show which catalog is selected
  636. void CMainDlgWindow::ConstructTitle()
  637. {
  638.     CString s = m_currCatName;
  639.     ASSERT(s.Right(4).CompareNoCase(FILE_TYPE) == 0);
  640.     s = s.Left(s.GetLength() - 4);
  641.     
  642.     GetCatalogDate(s); // for side-effect: proper file name capitalization
  643.         
  644.     CString root = pName (m_currCat[0]);
  645.     if (!root.IsEmpty())
  646.         s += " - " + root;
  647.     
  648.     s = "CatFish - " + s;
  649.     
  650.     CString title;
  651.     GetWindowText(title);
  652.  
  653.     if (title != s)
  654.         SetWindowText(s);   
  655. }
  656.  
  657.     // select a catalog and update the dialog contents
  658. void CMainDlgWindow::SetCatalog(const char* catName)
  659. {
  660.     if (m_currCatName == catName)
  661.         return; // don't bother, the catalog is currently loaded
  662.     
  663.     SetTreeDir(-1);
  664.  
  665.         // An important side effect is that m_fileView is cleared before the 
  666.         // storage class is destroyed. Otherwise, the entire view would be
  667.         // loaded into memory since the underlying file is about to go away.
  668.     SetFileDir(-1);
  669.  
  670.     m_currCat = c4_View (); // see comment about m_fileView 
  671.     delete m_storage;
  672.     m_storage = 0;
  673.     
  674.     m_dirCounts.RemoveAll();
  675.     m_fileCounts.RemoveAll();
  676.     m_lastDates.RemoveAll();
  677.     m_kiloTotals.RemoveAll();
  678.  
  679.     m_currCatName = catName;
  680.     if (m_currCatName.IsEmpty())
  681.         return;                      
  682.     
  683.         // loading and calculations may take some time
  684.     HCURSOR oldCursor = SetCursor(LoadCursor(0, IDC_WAIT));
  685.     
  686.     m_storage = DEBUG_NEW c4_Storage (m_currCatName, false);
  687.     m_currCat = m_storage->View("dirs");
  688.     
  689.     ConstructTitle();    
  690.     
  691.     int n = m_currCat.GetSize();
  692.     m_dirCounts.InsertAt(0, 0, n);
  693.     m_fileCounts.InsertAt(0, 0, n);
  694.     m_lastDates.InsertAt(0, 0, n);
  695.     m_kiloTotals.InsertAt(0, 0, n);
  696.     
  697.         // this loop calculates all cumulative totals and dates,
  698.         // mathematicians call this the "transitive closure" ...
  699.     while (--n >= 0)
  700.     {
  701.         c4_RowRef dir = m_currCat[n];
  702.         
  703.         int date = 0;
  704.         DWORD total = 0;
  705.  
  706.         c4_View files = pFiles (dir);
  707.         
  708.         for (int i = 0; i < files.GetSize(); ++i)
  709.         {
  710.             c4_RowRef file = files[i];
  711.             
  712.             total += pSize (file);
  713.             if (date < pDate (file))
  714.                 date = (int) pDate (file);
  715.         }
  716.         
  717.         ASSERT(i == files.GetSize());
  718.         m_fileCounts[n] += (WORD) i;
  719.         m_kiloTotals[n] += (total + 1023) / 1024;
  720.         
  721.         if (m_lastDates[n] < (WORD) date)
  722.             m_lastDates[n] = (WORD) date;
  723.         
  724.         int parDir = pParent (dir);
  725.         if (parDir != n)
  726.         {
  727.             m_dirCounts[parDir] += m_dirCounts[n] + 1;
  728.             m_fileCounts[parDir] += m_fileCounts[n];
  729.             m_kiloTotals[parDir] += m_kiloTotals[n];    
  730.     
  731.             if (m_lastDates[parDir] < m_lastDates[n])
  732.                 m_lastDates[parDir] = m_lastDates[n];
  733.         }
  734.     }
  735.     
  736.     SetCursor(oldCursor);
  737.     
  738.     if (m_currCat.GetSize() > 0)
  739.         SetTreeDir(0);
  740. }
  741.  
  742.     // select a directory in the tree and update the dialog contents
  743. void CMainDlgWindow::SetTreeDir(int dirNum)
  744. {
  745.     if (dirNum != m_treeDir)
  746.     {
  747.         m_treeDir = dirNum;
  748.         
  749.         ListBoxFreezer frozen (m_treeList);
  750.     
  751.         if (dirNum >= 0)
  752.         {                  
  753.                 // select the appropriate subdirectories and sort them by name
  754.             c4_View selsort = m_currCat.Select(pParent [dirNum]).SortOn(pName);
  755.  
  756.             for (int j = 0; j < selsort.GetSize(); ++j)
  757.             {
  758.                     // map each entry back to the m_currCat view
  759.                 int ix = m_currCat.GetIndexOf(selsort[j]);
  760.                 ASSERT(ix >= 0);
  761.                 
  762.                     // don't add the root entry, it doesn't sort correctly
  763.                 if (ix > 0)
  764.                 {
  765.                     int k = m_treeList.AddString("");
  766.                     m_treeList.SetItemData(k, ix);
  767.                 }
  768.             }
  769.             
  770.                 // insert the parent directories in reverse order in front
  771.             for (;;)
  772.             {
  773.                 m_treeList.InsertString(0, "");
  774.                 m_treeList.SetItemData(0, dirNum);
  775.                 
  776.                 if (dirNum == m_treeDir)
  777.                     m_treeList.SetCurSel(0);
  778.                     
  779.                 if (dirNum <= 0)
  780.                     break;
  781.                     
  782.                 dirNum = (int) pParent (m_currCat[dirNum]);
  783.             }
  784.                                              
  785.                 // make sure the focus item is the same as the selection
  786.                 // InsertString moves the selection but not the focus...
  787.             m_treeList.SetCurSel(m_treeList.GetCurSel());
  788.         }
  789.     }
  790.     
  791.     SetFileDir(m_treeDir);
  792. }
  793.  
  794.     // select a list of files and update the dialog contents
  795. void CMainDlgWindow::SetFileDir(int dirNum)
  796. {
  797.     if (dirNum != m_fileDir)
  798.     {
  799.         m_fileDir = dirNum;
  800.     
  801.         ListBoxFreezer frozen (m_fileList);
  802.     
  803.         if (dirNum >= 0)
  804.         {               
  805.             m_fileView = pFiles (m_currCat[dirNum]);
  806.         
  807.             CString root = fFullPath(m_currCat, 0);           
  808.             CString path = fFullPath(m_currCat, dirNum);           
  809.             
  810.                 // remove common root prefix
  811.             path = path.Mid(root.GetLength());
  812.             if (path.IsEmpty())
  813.                 path = "(root)";
  814.             
  815.                 // fit the path, prefixing "..." to the head if necessary
  816.             CDC* dc = m_treePath.GetDC();
  817.             if (dc)
  818.             {
  819.                 CRect r;
  820.                 m_treePath.GetClientRect(&r);
  821.                 
  822.                 CString s = path;
  823.                     // loop uses FitString to measure, but not its result
  824.                 while (!FitString(dc, path, r.right))
  825.                 {              
  826.                     int n = s.SpanExcluding("\\/:").GetLength() + 1;
  827.                     if (n > 10)
  828.                         n = 10; // if the last segment is huge, chop it off
  829.                         
  830.                     path = "..." + s.Mid(n-1);
  831.                     s = s.Mid(n);
  832.                 }
  833.                 
  834.                 m_treePath.ReleaseDC(dc);
  835.             }
  836.             
  837.             m_treePath.SetWindowText(path);
  838.             
  839.             for (int i = 0; i < m_fileView.GetSize(); ++i)
  840.                 m_fileList.AddString("");
  841.         }
  842.         else
  843.         {
  844.             m_fileSort = c4_View ();
  845.             m_fileView = c4_View ();
  846.             m_treePath.SetWindowText("");
  847.         }
  848.         
  849.             // this sets up the appropriate m_fileSort view
  850.         SortFileList(*m_sortProp);
  851.     }
  852.     
  853.         // always reset the file list selection
  854.     m_fileList.SetCurSel(-1);
  855.     
  856.     OnSelchangeFileList();
  857. }
  858.  
  859.     // the catalog selection changed
  860. void CMainDlgWindow::OnSelchangeCatList()
  861. {
  862.     CString s;
  863.  
  864.     int n = m_catList.GetCurSel();
  865.     if (n != LB_ERR)
  866.         m_catList.GetText(n, s);
  867.         
  868.     SetCatalog(s);
  869. }
  870.  
  871.     // the directory selection changed
  872. void CMainDlgWindow::OnSelchangeTreeList()
  873. {
  874.     int n = m_treeList.GetCurSel();
  875.     
  876.     m_findBtn.EnableWindow(n >= 0);
  877.     
  878.     if (n >= 0)
  879.         n = (int) m_treeList.GetItemData(n);
  880.     
  881.     SetFileDir(n);
  882. }
  883.  
  884.     // descend into an entry in the directory tree
  885. void CMainDlgWindow::OnDblclkTreeList()
  886. {
  887.     int n = m_treeList.GetCurSel();
  888.     if (n >= 0)
  889.     {
  890.         n = (int) m_treeList.GetItemData(n);
  891.             
  892.             // don't allow descending into a dir with no subdirs
  893.         if (m_dirCounts[n] == 0)
  894.         {
  895.             MessageBeep(0);
  896.             return;
  897.         }
  898.     }
  899.     
  900.     SetTreeDir(n);
  901. }
  902.  
  903.     // the file selection changed
  904. void CMainDlgWindow::OnSelchangeFileList()
  905. {
  906.     CString s;
  907.     
  908.     int n = m_fileList.GetCurSel();
  909.     if (n >= 0)
  910.     {
  911.         c4_RowRef file = m_fileSort[n];
  912.         s = pName (file);
  913.     }
  914.     else if (m_fileDir >= 0)
  915.         s.Format("%d files", m_fileSort.GetSize());
  916.     
  917.     m_infoText.SetWindowText(s);
  918. }
  919.  
  920. void CMainDlgWindow::OnDblclkFileList()
  921. {
  922.     int n = m_fileList.GetCurSel();
  923.     if (n >= 0)
  924.     {
  925.         c4_RowRef file = m_fileSort[n];
  926.         CString s = pName (file);
  927.         
  928.         CString path = fFullPath(m_currCat, m_fileDir); // also the working dir
  929.          
  930.         if ((UINT) ShellExecute(m_hWnd, 0, path + s, 0, path, SW_SHOWNORMAL) >= 32)
  931.             return; // selected file succesfully launched
  932.     }
  933.  
  934.     MessageBeep(0);
  935. }
  936.  
  937.     // Adjust specified menu entry and label text to indicate current sort order
  938. void CMainDlgWindow::AdjustDisplay(CCmdUI& cui, int ix_, c4_Property& prop_,
  939.                                     int label_, const char* text_)
  940. {
  941.     bool match = m_sortProp == &prop_;
  942.     
  943.     cui.m_nIndex = ix_;
  944.     cui.SetRadio(match);
  945.  
  946.         // include "+" or "-" in the label corresponding to the current sort field
  947.     CString s = text_;
  948.     if (match)
  949.         s += m_sortReverse ? " (-)" : " (+)";
  950.         
  951.     CWnd* wnd = GetDlgItem(label_);
  952.     ASSERT(wnd);
  953.     
  954.     wnd->SetWindowText(s);
  955. }
  956.  
  957.     // Sort the file list and adjust menu items and label texts
  958. void CMainDlgWindow::SortFileList(c4_Property& prop_, bool toggle_)
  959. {
  960.     if (m_sortProp != &prop_)
  961.     {
  962.         m_sortProp = &prop_;
  963.         m_sortReverse = false;
  964.     }
  965.     else if (toggle_)
  966.         m_sortReverse = !m_sortReverse;
  967.     
  968.         // update all menu check marks here, since CCmdUI doesn't work in dialogs
  969.     CMenu* menu = GetMenu();
  970.     ASSERT(menu);
  971.  
  972.     menu = menu->GetSubMenu(0); // the "File" menu
  973.     ASSERT(menu);
  974.     
  975.     CMenu* sub = menu->GetSubMenu(3); // the "Sort Files" entry (ouch!)
  976.     ASSERT(sub);
  977.         
  978.         // use CCmdUI, not CheckMenuItem, because it can set nice bullet marks
  979.     CCmdUI cui;
  980.     cui.m_pMenu = sub;
  981.     cui.m_nIndexMax = sub->GetMenuItemCount();
  982.     ASSERT(cui.m_nIndexMax == 5); // name, size, date, <sep>, reverse
  983.     
  984.     AdjustDisplay(cui, 0, pName, IDC_NAME_LABEL, "File &name");    
  985.     AdjustDisplay(cui, 1, pSize, IDC_SIZE_LABEL, "Size");    
  986.     AdjustDisplay(cui, 2, pDate, IDC_DATE_LABEL, "Date");
  987.     
  988.         // the "Reverse" menu item uses a regular check mark
  989.     cui.m_nIndex = 4;
  990.     cui.SetCheck(m_sortReverse);
  991.  
  992.         // sorting may take some time
  993.     HCURSOR oldCursor = SetCursor(LoadCursor(0, IDC_WAIT));
  994.         
  995.         // figure out the index of the row that was selected, if any
  996.     int n = m_fileList.GetCurSel();
  997.     if (n >= 0)
  998.     {
  999.         n = m_fileView.GetIndexOf(m_fileSort [n]);
  1000.         ASSERT(n >= 0);
  1001.     }
  1002.        
  1003.         // define the sort order and make sure the list is redrawn
  1004.     if (m_sortReverse)
  1005.         m_fileSort = m_fileView.SortOnReverse(prop_, prop_);
  1006.     else
  1007.         m_fileSort = m_fileView.SortOn(prop_);
  1008.         
  1009.     m_fileList.Invalidate(); 
  1010.         
  1011.         // restore the selection to the original item
  1012.     if (n >= 0)
  1013.     {
  1014.         int m = m_fileSort.Find(m_fileView [n]); // where is that row now?
  1015.         ASSERT(m >= 0);
  1016.         
  1017.         m_fileList.SetCurSel(m);
  1018.     }
  1019.     
  1020.     SetCursor(oldCursor);
  1021. }
  1022.  
  1023. void CMainDlgWindow::OnSortByName()
  1024. {
  1025.     SortFileList(pName);
  1026. }
  1027.  
  1028. void CMainDlgWindow::OnSortBySize()
  1029. {
  1030.     SortFileList(pSize);
  1031. }
  1032.  
  1033. void CMainDlgWindow::OnSortByDate()
  1034. {
  1035.     SortFileList(pDate);
  1036. }
  1037.  
  1038. void CMainDlgWindow::OnSortReverse()
  1039. {
  1040.     SortFileList(*m_sortProp, true);
  1041. }
  1042.  
  1043. void CMainDlgWindow::OnLButtonDown(UINT nFlags, CPoint point)
  1044. {
  1045.         // catch mouse clicks on the header texts to alter the file sort order
  1046.     CWnd* wnd = ChildWindowFromPoint(point);
  1047.     if (wnd)
  1048.         switch (wnd->GetDlgCtrlID())
  1049.         {
  1050.             case IDC_NAME_LABEL:    SortFileList(pName, true); return;
  1051.             case IDC_SIZE_LABEL:    SortFileList(pSize, true); return;
  1052.             case IDC_DATE_LABEL:    SortFileList(pDate, true); return;
  1053.             
  1054.             case IDC_LOGO1:             // handle clicks on the fake logo
  1055.             case IDC_LOGO2:
  1056.             case IDC_LOGO3:         OnAppAbout(); return;
  1057.         }                     
  1058.     
  1059.     CDialog::OnLButtonDown(nFlags, point);
  1060. }
  1061.  
  1062. /////////////////////////////////////////////////////////////////////////////
  1063. // The following class maintains most of the state required to iterate
  1064. // over all entries to satisfy a find request. This is a pretty messy
  1065. // approach to be able to use this in both forward and backward modes.
  1066.         
  1067.     class CFindState
  1068.     {
  1069.     public:
  1070.         CFindState (CMainDlgWindow& dlg_)
  1071.             : _dlg (dlg_), findStorage (0)
  1072.         {
  1073.                 // searching may take some time
  1074.             oldCursor = SetCursor(LoadCursor(0, IDC_WAIT));
  1075.         }
  1076.         
  1077.         ~CFindState ()
  1078.         {                      
  1079.             SetCursor(oldCursor);
  1080.             
  1081.             findCat = c4_View();
  1082.             findList = c4_View();
  1083.             delete findStorage;
  1084.         }
  1085.         
  1086.         bool Initialize()
  1087.         {
  1088.             lastCat = _dlg.m_catList.GetCurSel();
  1089.             if (lastCat < 0 || _dlg.m_treeDir < 0 || _dlg.m_fileDir < 0)
  1090.             {
  1091.                 MessageBeep(0);
  1092.                 return false;
  1093.             }
  1094.         
  1095.             findCatName = _dlg.m_currCatName;
  1096.             findCat = _dlg.m_currCat;
  1097.             findList = _dlg.m_fileSort;
  1098.         
  1099.                 // prepare for iteration    
  1100.             lastDir = _dlg.m_fileDir;
  1101.             lastSel = _dlg.m_fileList.GetCurSel(); // can be -1
  1102.             
  1103.             return true;
  1104.         }
  1105.         
  1106.         void SetSort(const c4_View& view_)
  1107.         {
  1108.             ASSERT(_dlg.m_sortProp);
  1109.             c4_Property& prop = *_dlg.m_sortProp;
  1110.             
  1111.             if (_dlg.m_sortReverse)
  1112.                 findList = view_.SortOnReverse(prop, prop);
  1113.             else
  1114.                 findList = view_.SortOn(prop);
  1115.         }
  1116.         
  1117.         bool IsStartDir(int dir_, int cat_)
  1118.         {
  1119.             return dir_ == lastDir && cat_ == lastCat;
  1120.         }
  1121.         
  1122.         void Select(int sel_, int dir_)
  1123.         {
  1124.                 // adjust to the found catalog and directory
  1125.             _dlg.SetCatalog(findCatName);
  1126.             _dlg.SetTreeDir(dir_);
  1127.                 // then match the selection and update the status fields
  1128.             _dlg.m_fileList.SetCurSel(sel_);
  1129.             _dlg.OnSelchangeFileList();
  1130.  
  1131.             _dlg.m_fileList.SetFocus();     // so arrows work as expected
  1132.             _dlg.m_fileList.UpdateWindow(); // show before new find can start
  1133.         }
  1134.         
  1135.         void UseCatalog(int cat_)
  1136.         {
  1137.                 // show which catalog we're currently searching
  1138.             _dlg.m_catList.SetCurSel(cat_);
  1139.         
  1140.             findCat = c4_View();
  1141.             findList = c4_View();
  1142.             delete findStorage;
  1143.             findStorage = 0;
  1144.             
  1145.             _dlg.m_catList.GetText(cat_, findCatName);        
  1146.             findStorage = DEBUG_NEW c4_Storage (findCatName, false);
  1147.             findCat = findStorage->View("dirs");
  1148.         }
  1149.         
  1150.             // check if any key is pressed, this aborts a lengthy find
  1151.         bool WantsToQuit() const
  1152.         {
  1153.             MSG msg;
  1154.                 // careful, there may still be keyup's in the queue
  1155.             if (!::PeekMessage(&msg, NULL, WM_KEYDOWN, WM_KEYDOWN, PM_NOREMOVE))
  1156.                 return false;                                       
  1157.             
  1158.             while (::PeekMessage(&msg, NULL, WM_KEYFIRST, WM_KEYLAST, PM_REMOVE))
  1159.                 ; // flush all key events
  1160.                 
  1161.             return true; 
  1162.         }
  1163.         
  1164.         CMainDlgWindow& _dlg;
  1165.         int lastCat, lastDir, lastSel;
  1166.         c4_Storage* findStorage;
  1167.         CString findCatName;
  1168.         c4_View findCat, findList;
  1169.         HCURSOR oldCursor;
  1170.     };
  1171.     
  1172. /////////////////////////////////////////////////////////////////////////////
  1173. // Several aspects of the find code below affect search performance:
  1174. //
  1175. //  1.  Each catalog is opened without calculating any statistics
  1176. //  2.  Only match fields are accessed, for optimal use of on-demand loading
  1177. //  3.  Sorting is only performed *after* at least one entry has been found
  1178. //
  1179. // As a result, searching is quite fast (and THAT is an understatement).
  1180.  
  1181. void CMainDlgWindow::OnFindNext()
  1182. {
  1183.     CFindState state (*this);
  1184.     if (!state.Initialize())
  1185.         return;
  1186.  
  1187.         // prepare for iteration    
  1188.     int cat = state.lastCat;    
  1189.     int dir = state.lastDir;
  1190.     int sel = state.lastSel;
  1191.     
  1192.     bool mustSort = false; // avoid resorting when a sorted list is available
  1193.     bool first = true; // watch out for infinite loop if never found an entry
  1194.     
  1195.     for (;;) // loop over each catalog
  1196.     {
  1197.         int dirCount = state.findCat.GetSize();
  1198.         
  1199.         while (dir < dirCount) // loop over each subdirectory
  1200.         {
  1201.             c4_View files = pFiles (state.findCat [dir]);
  1202.             int selCount = files.GetSize();
  1203.             
  1204.                 // on first entry into dir, first scan for match in unsorted list
  1205.                 // this *drastically* improves performance if most dirs are a miss
  1206.             if (sel < 0 && m_findDlg.NeedsCompare())
  1207.             {
  1208.                 while (++sel < selCount) // loop over each file
  1209.                     if (m_findDlg.Match(files[sel]))
  1210.                     {
  1211.                         sel = -1; // something matches, prepare to search sorted
  1212.                         break;
  1213.                     }
  1214.                 
  1215.                 // at this point sel is either -1 or selCount
  1216.             }
  1217.             
  1218.                 // only sort if we're really going to use this to scan
  1219.             if (mustSort && sel < selCount)
  1220.                 state.SetSort(files);
  1221.             
  1222.             while (++sel < selCount) // loop over each file
  1223.                 if (m_findDlg.Match(state.findList [sel]))
  1224.                 {
  1225.                     if (!first && sel >= state.lastSel &&
  1226.                                     state.IsStartDir(dir, cat))
  1227.                         break; // oops, second time around in start dir, fail
  1228.                         
  1229.                     state.Select(sel, dir);
  1230.                     return;                    
  1231.                 }
  1232.             
  1233.                 // if we fell through start dir for the second time, then fail
  1234.                 // this scans for too many entries but works on empty start dir
  1235.             if (state.WantsToQuit() || !first && state.IsStartDir(dir, cat))
  1236.             {
  1237.                     // wrapped around, nothing found
  1238.                 m_catList.SetCurSel(state.lastCat);
  1239.                 MessageBeep(0);
  1240.                 return;
  1241.             }
  1242.             
  1243.             first = false;
  1244.             
  1245.             sel = -1;
  1246.             ++dir; // directories are scanned in breadth first order, hmmm...
  1247.             mustSort = true;
  1248.         }
  1249.         
  1250.         dir = 0;
  1251.         
  1252.         if (m_findDlg.m_singleCat)
  1253.             continue; // don't switch to another catalog file
  1254.                  
  1255.         if (++cat >= m_catList.GetCount())
  1256.             cat = 0;
  1257.         
  1258.         state.UseCatalog(cat);
  1259.     }
  1260. }
  1261.  
  1262. void CMainDlgWindow::OnFindPrev()
  1263. {
  1264.     CFindState state (*this);
  1265.     if (!state.Initialize())
  1266.         return;
  1267.  
  1268.         // prepare for iteration    
  1269.     int cat = state.lastCat;    
  1270.     int dir = state.lastDir;
  1271.     int sel = state.lastSel;
  1272.     
  1273.     bool mustSort = false; // avoid resorting when a sorted list is available
  1274.     bool first = true; // watch out for infinite loop if never found an entry
  1275.     
  1276.     for (;;) // loop over each catalog
  1277.     {
  1278.         if (dir < 0)
  1279.             dir = state.findCat.GetSize() - 1;
  1280.             
  1281.         while (dir >= 0) // loop over each subdirectory
  1282.         {
  1283.             c4_View files = pFiles (state.findCat [dir]);
  1284.             int selCount = files.GetSize();
  1285.             
  1286.             if (sel < 0)
  1287.                 sel = selCount;
  1288.                 
  1289.                 // on first entry into dir, first scan for match in unsorted list
  1290.                 // this *drastically* improves performance if most dirs are a miss
  1291.             if (sel >= selCount && m_findDlg.NeedsCompare())
  1292.             {
  1293.                 while (--sel >= 0) // loop over each file
  1294.                     if (m_findDlg.Match(files[sel]))
  1295.                     {
  1296.                         sel = selCount; // matches, prepare to search sorted
  1297.                         break;
  1298.                     }
  1299.                 
  1300.                 // at this point sel is either -1 or selCount
  1301.             }
  1302.             
  1303.                 // only sort if we're really going to use this to scan
  1304.             if (mustSort && sel >= 0)
  1305.                 state.SetSort(files);
  1306.             
  1307.             while (--sel >= 0) // loop over each file
  1308.                 if (m_findDlg.Match(state.findList[sel]))
  1309.                 {
  1310.                     if (!first && sel <= state.lastSel &&
  1311.                                     state.IsStartDir(dir, cat))
  1312.                         break; // oops, second time around in start dir, fail
  1313.                         
  1314.                     state.Select(sel, dir);
  1315.                     return;                    
  1316.                 }
  1317.             
  1318.                 // if we fell through start dir for the second time, then fail
  1319.                 // this scans for too many entries but works on empty start dir
  1320.             if (state.WantsToQuit() || !first && state.IsStartDir(dir, cat))
  1321.             {
  1322.                     // wrapped around, nothing found
  1323.                 m_catList.SetCurSel(state.lastCat);
  1324.                 MessageBeep(0);
  1325.                 return;
  1326.             }
  1327.             
  1328.             first = false;
  1329.             
  1330.             sel = -1;
  1331.             --dir; // directories are scanned in breadth first order, hmmm...
  1332.             mustSort = true;
  1333.         }
  1334.         
  1335.         ASSERT(dir == -1);
  1336.         
  1337.         if (m_findDlg.m_singleCat)
  1338.             continue; // don't switch to another catalog file
  1339.         
  1340.         if (cat == 0)
  1341.             cat = m_catList.GetCount();
  1342.         --cat;
  1343.         
  1344.         state.UseCatalog(cat);
  1345.     }
  1346. }
  1347.  
  1348. void CMainDlgWindow::OnFileExport()
  1349.     int n = m_catList.GetCurSel();
  1350.     if (n == LB_ERR)
  1351.         return; // the easy way out, better would be to disable the menu item
  1352.         
  1353.     CString s = m_currCatName;
  1354.     ASSERT(s.Right(4).CompareNoCase(FILE_TYPE) == 0);
  1355.     s = s.Left(s.GetLength() - 4);
  1356.     
  1357.     CFileDialog dlg (FALSE, "txt", s + ".txt",
  1358.                         OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT | OFN_NOCHANGEDIR);     
  1359.     
  1360.     if (dlg.DoModal() == IDOK)
  1361.     {
  1362.             // exporting may take some time
  1363.         HCURSOR oldCursor = SetCursor(LoadCursor(0, IDC_WAIT));
  1364.  
  1365.         CStdioFile output (dlg.GetPathName(), CFile::typeText |
  1366.                                 CFile::modeReadWrite | CFile::modeCreate);
  1367.         
  1368.         CWordArray pending;
  1369.         pending.Add(0);
  1370.         
  1371.         CString title;
  1372.         GetWindowText(title);
  1373.     
  1374.         output.WriteString(title);
  1375.  
  1376.         do
  1377.         {
  1378.             int index = pending[0];
  1379.             pending.RemoveAt(0);
  1380.  
  1381.             output.WriteString("\n\n " + fFullPath(m_currCat, index) + "\n");
  1382.             
  1383.             c4_View files = pFiles (m_currCat[index]); 
  1384.             for (int j = 0; j < files.GetSize(); ++j)
  1385.             {
  1386.                 c4_RowRef file = files[j];
  1387.                 output.WriteString("\n "
  1388.                     + ("  " + CommaNum(pSize (file), 3)).Right(13)
  1389.                     + "  " + ShortDate((WORD) pDate (file))
  1390.                     + "  " + (CString) pName (file));
  1391.             }
  1392.             
  1393.             c4_View children = m_currCat.Select(pParent [index]).SortOn(pName);
  1394.             
  1395.             int n = children.GetSize();
  1396.             if (n > 0)
  1397.             {
  1398.                 pending.InsertAt(0, 0, n);
  1399.                 for (int i = 0; i < n; ++i)               
  1400.                     pending[i] = (WORD) m_currCat.GetIndexOf(children[i]);
  1401.             }
  1402.  
  1403.                 // root has itself as parent, avoid a runaway loop  
  1404.             if (pending.GetSize() > 0 && pending[0] == 0)
  1405.                 pending.RemoveAt(0);
  1406.         
  1407.         } while (pending.GetSize() > 0); 
  1408.         
  1409.         output.WriteString("\n\nDone.\n");
  1410.         
  1411.         SetCursor(oldCursor);
  1412.     }
  1413. }
  1414.  
  1415. /////////////////////////////////////////////////////////////////////////////
  1416. // $Id: catfish.cpp,v 1.5 1997/05/27 00:06:05 jcw Rel $
  1417.