/*****************************************************************************
 * Subset.cpp
 *
 * Copyright (C) Microsoft, 1989-1998
 * Feb 22, 1998
 *
 *  Modification History:
 *
 *  Ported to hhctrl.ocx from the old B2 code base.
 *
 *****************************************************************************/
#include "header.h"
#include "subset.h"
#include "resource.h"
#include "toc.h"
#include "cdefinss.h"
#include "verdef.h"
#include "secwin.h"

#define CmdID   LOWORD(wParam)
#define CmdCode HIWORD(wParam)
#define CmdHwnd (HWND)lParam
/*
 *  Defines, the EXPANDED define aids in intrepreting the proper bit in the
 *  FGT data structure depending on which LB we're operating on.
 */
#define EXPANDED(lb_id, ds) ((lb_id) ? ds->f_A_Open : ds->f_F_Open)
#define PRESENT(lb_id, ds) ((lb_id) ? ds->f_Available : ds->f_Filter)

// declare a static this pointer for our window procedures.
//
CDefineSS* CDefineSS::m_pThis;

//********************************************************************************
//
// CStructuralSubset Implementation - This is object representation of a subset.
//
//********************************************************************************


CSSList::CSSList()
{
   m_iSubSetCount = 0;
   m_pHeadSubSets = m_pFTS_SS = m_pF1_SS = m_pTOC_SS = m_pNew_SS = m_pEC_SS = NULL;
}

CSSList::~CSSList()
{
   CStructuralSubset *pSS, *pSSNext;

   pSS = m_pHeadSubSets;
   while ( pSS )
   {
      pSSNext = pSS->m_pNext;
      delete pSS;
      pSS = pSSNext;
   }
}

CStructuralSubset* CSSList::GetSubset(PSTR pszSSName)
{
   CStructuralSubset *pSS = m_pHeadSubSets;

   while ( pSS )
   {
      if (! lstrcmpi(pSS->GetName(), pszSSName) )
         return pSS;
      pSS = pSS->m_pNext;
   }
   return NULL;
}

void CSSList::DeleteSubset(CStructuralSubset* pSS, CStructuralSubset* pSSNew, /* == NULL */ HWND hWndUI /* == NULL */)
{
   CStructuralSubset *pSSCurrCB, *pSSl = m_pHeadSubSets;
   TCHAR szSel[MAX_SS_NAME_LEN];
   HWND hWndCB;
   INT_PTR i;
   CHHWinType* phh;

   if (! pSSNew )
      pSSNew = m_pEC_SS;

   if ( pSS->m_dwFlags & SS_READ_ONLY )
      return;

   if ( pSS == m_pFTS_SS )
      m_pFTS_SS = pSSNew;
   if ( pSS == m_pF1_SS )
      m_pF1_SS = pSSNew;
   if ( pSS == m_pTOC_SS )
      m_pTOC_SS = pSSNew;
   //
   // If we're given an hwnd, update the combo-box UI.
   //
   if ( hWndUI && (hWndCB = GetDlgItem(hWndUI, IDC_SS_PICKER)) )
   {
      //
      // Get the current CB selection and see if it's the one we're deleting ?
      //
      if ( ((i = SendMessage(hWndCB, CB_GETCURSEL, 0, 0L)) != -1) )
      {
		 GetDlgItemText(hWndUI, IDC_SS_PICKER, szSel, sizeof(szSel));

         pSSCurrCB = GetSubset(szSel);
         if ( pSSCurrCB == pSS )        // Yep, we're deleting the current one, select the new one.
         {
            SendMessage(hWndCB, CB_SELECTSTRING, -1, (LPARAM)pSSNew->GetName());
            if ( (phh = FindWindowIndex(hWndUI)) )
            {
               pSSNew->SelectAsTOCSubset(phh->m_phmData->m_pTitleCollection);
               phh->UpdateInformationTypes();                                   // This call re-draws the TOC.
            }
         }
      }
      if ( lstrcmpi(pSS->GetName(), pSSNew->GetName()) )
      {
         if ( (i = SendMessage(hWndCB, CB_FINDSTRING, -1, (LPARAM)pSS->GetName())) != -1 )
            SendMessage(hWndCB, CB_DELETESTRING, i, 0L);
      }
   }
   //
   // Take the delete victum out of the linked list.
   //
   while ( pSSl )
   {
      if ( pSSl->m_pNext == pSS )
      {
         pSSl->m_pNext = pSS->m_pNext;
         delete pSS;
         break;
      }
      pSSl = pSSl->m_pNext;
   }
   m_iSubSetCount--;
}

HRESULT CSSList::PersistSubsets(CExCollection* pCollection)
{
   LPCSTR lpszFQSSStore;
   HANDLE hFile;
   SSHEADER ssHeader;
   CStructuralSubset* pSS;
   CStructuralSubset* pSSCurr = NULL;
   PSS       pSSFile = NULL;
   unsigned long ulCnt;
   int iSSSize;
   int i = 0;

   if (! (pSS = m_pHeadSubSets) )
      return S_OK;
   //
   // Count'em
   //
   do
   {
      if ( pSS->IsTOC() )
         pSSCurr = pSS;
      if ( !pSS->IsEntire() && !pSS->IsEmpty() && !pSS->IsReadOnly() )
         i++;
   } while ( (pSS = GetNextSubset(pSS)) );
   //
   // Prepare a file.
   //
   if (! (lpszFQSSStore = pCollection->GetUserCHSLocalStoragePathnameByLanguage()) )
      return E_FAIL;
   hFile = CreateFile(lpszFQSSStore, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_WRITE_THROUGH, 0);
   if ( hFile == INVALID_HANDLE_VALUE )
      return E_FAIL;
   //
   // Fill in the header, write out the header.
   //
   // Need to store the persisted subset in the header since it may be a predefine.
   //
   ssHeader.dwSig      = SS_FILE_SIGNATURE;
   ssHeader.dwVer      = (VER_PRODUCTVERSION_DW & 0x0000);
   ssHeader.iSSCount   = i;
   ssHeader.dwFlags    = 0;
   if ( pSSCurr )
      lstrcpy(ssHeader.lpszCurrentSSName, pSSCurr->GetName());
   if (! WriteFile(hFile, &ssHeader, sizeof(SSHEADER), &ulCnt, NULL) )
   {
      MsgBox(IDS_PERSIST_SUBSET_ERR, MB_OK | MB_ICONHAND);
      return E_FAIL;
   }
   //
   // Write out the subsets.
   //
   pSS = m_pHeadSubSets;
   do
   {
      if ( pSS->IsEntire() || pSS->IsEmpty() || pSS->IsReadOnly() )
         continue;
      int iHashCnt = pSS->GetHashCount();
      iSSSize = (sizeof(SS) + ((iHashCnt - 1) * sizeof(DWORD)));
      if ( (pSSFile = (PSS)lcReAlloc(pSSFile, iSSSize)) )
      {
         pSSFile->iHashCount = iHashCnt;
         lstrcpy(pSSFile->lpszSSName, pSS->GetName());
         lstrcpy(pSSFile->lpszSSID, pSS->GetID());
         pSSFile->dwFlags = pSS->m_dwFlags;
         while ( --iHashCnt >= 0)
            pSSFile->dwHashes[iHashCnt] = pSS->EnumHashes(iHashCnt);
         if (! WriteFile(hFile, pSSFile, iSSSize, &ulCnt, NULL) )
         {
            MsgBox(IDS_PERSIST_SUBSET_ERR, MB_OK | MB_ICONHAND);
            lcFree(pSSFile);
            CloseHandle(hFile);
            return E_FAIL;
         }
      }
   } while ( (pSS = GetNextSubset(pSS)) );

   lcFree(pSSFile);
   CloseHandle(hFile);
   return S_OK;
}

HRESULT CSSList::RestoreSubsets(CExCollection* pCollection, PSTR pszRestoreSS)
{
   LPCSTR lpszFQSSStore;
   HANDLE hFile;
   SSHEADER ssHeader;
   PSS pSSFile;
   CStructuralSubset* pSS;
   unsigned long ulHashCnt, ulRead;
   HRESULT hr = E_FAIL;

   if (! (pSSFile = (PSS)lcMalloc(sizeof(SS) + sizeof(DWORD) * 50)) )
      return hr;
   lpszFQSSStore = pCollection->GetUserCHSLocalStoragePathnameByLanguage();
   hFile = CreateFile(lpszFQSSStore, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
   if ( hFile == INVALID_HANDLE_VALUE)
   {
      lpszFQSSStore = pCollection->GetUserCHSLocalStoragePathname();
      hFile = CreateFile(lpszFQSSStore, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
      if ( hFile == INVALID_HANDLE_VALUE)
      {
         lpszFQSSStore = pCollection->GetLocalStoragePathname(".chs");
         hFile = CreateFile(lpszFQSSStore, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
         if ( hFile == INVALID_HANDLE_VALUE )
         {
           lcFree(pSSFile);
           return hr;
         }
      }
   }
   //
   // Read the header.
   //
   if (! ReadFile(hFile, &ssHeader, sizeof(SSHEADER), &ulRead, NULL) )
      goto crap_out;
   if ( ssHeader.dwSig != SS_FILE_SIGNATURE )
      goto crap_out;
   //
   // Get the subsets.
   //
   while ( ssHeader.iSSCount-- )
   {
      if (! ReadFile(hFile, &ulHashCnt, sizeof(int), &ulRead, NULL) )
         goto crap_out;
      if ( ulHashCnt > 51 )
      {
         if (! (pSSFile = (PSS)lcReAlloc(pSSFile, sizeof(SS) + (sizeof(DWORD) * ulHashCnt))) )
            goto crap_out;
      }
      pSSFile->iHashCount = ulHashCnt;
      unsigned long ulAmt = ((sizeof(SS) - sizeof(int)) + (sizeof(DWORD) * (ulHashCnt - 1)));
      if (! ReadFile(hFile, (((BYTE*)pSSFile) + sizeof(int)), ulAmt, &ulRead, NULL) )
         goto crap_out;
      //
      // Now create a CStructuralSubset from the data.
      //
      pSS = new CStructuralSubset(pSSFile->lpszSSName);
      pSS->m_dwFlags = pSSFile->dwFlags;
      pSS->m_dwFlags &= ~(SS_FTS | SS_TOC | SS_F1);       // Insure selection bits are reset.
      lstrcpy(pSS->m_szSSID, pSSFile->lpszSSID);
      while ( ulHashCnt )
         pSS->AddHash(pSSFile->dwHashes[--ulHashCnt]);
      //
      // Add the subset to the list appropiatly.
      //
      AddSubset(pSS);
      if (! lstrcmpi(ssHeader.lpszCurrentSSName, pSS->GetName() ) )
      {
         SetFTS(pSS);
         SetTOC(pSS);
         SetF1(pSS);
      }
   }
   if ( pszRestoreSS )
      lstrcpy(pszRestoreSS, ssHeader.lpszCurrentSSName);
   hr = S_OK;
crap_out:
   lcFree(pSSFile);
   CloseHandle(hFile);
   return hr;
}

HRESULT CSSList::ReadPreDefinedSubsets(CExCollection* pCollection, PSTR pszRestoreSS)
{
   SSHEADER ssHeader;
   PSS pSSFile;
   CStructuralSubset* pSS;
   CSubFileSystem* pSubFS;
   unsigned long ulHashCnt, ulRead;
   HRESULT hr = E_FAIL;

   if (! (pSSFile = (PSS)lcMalloc(sizeof(SS) + sizeof(DWORD) * 50)) )
      return hr;

   if (! pCollection || !pCollection->GetMasterTitle() )
      return hr;

   pSubFS = new CSubFileSystem(pCollection->GetMasterTitle()->GetTitleIdxFileSystem());
   if ( !SUCCEEDED(pSubFS->OpenSub("predef.chs")) )
      goto crap_out;
   //
   // Read the header.
   //
   if ( !SUCCEEDED(pSubFS->ReadSub(&ssHeader, sizeof(SSHEADER), &ulRead)) )
      goto crap_out;
   if ( ssHeader.dwSig != SS_FILE_SIGNATURE )
      goto crap_out;
   //
   // Get the subsets.
   //
   while ( ssHeader.iSSCount-- )
   {
      if ( !SUCCEEDED(pSubFS->ReadSub(&ulHashCnt, sizeof(int), &ulRead)) )
         goto crap_out;
      if ( ulHashCnt > 51 )
      {
         if (! (pSSFile = (PSS)lcReAlloc(pSSFile, sizeof(SS) + (sizeof(DWORD) * ulHashCnt))) )
            goto crap_out;
      }
      pSSFile->iHashCount = ulHashCnt;
      unsigned long ulAmt = ((sizeof(SS) - sizeof(int)) + (sizeof(DWORD) * (ulHashCnt - 1)));
      if ( !SUCCEEDED(pSubFS->ReadSub((((BYTE*)pSSFile) + sizeof(int)), ulAmt, &ulRead)) )
         goto crap_out;
      //
      // Now create a CStructuralSubset from the data.
      //
      pSS = new CStructuralSubset(pSSFile->lpszSSName);
      pSS->m_dwFlags = pSSFile->dwFlags;
      pSS->m_dwFlags &= ~(SS_FTS | SS_TOC | SS_F1);       // Insure selection bits are reset.
      pSS->m_dwFlags |= SS_READ_ONLY;
      lstrcpy(pSS->m_szSSID, pSSFile->lpszSSID);
      while ( ulHashCnt )
         pSS->AddHash(pSSFile->dwHashes[--ulHashCnt]);
      //
      // Add the subset to the list appropiatly.
      //
      AddSubset(pSS);
      if (! lstrcmpi(pszRestoreSS, pSS->GetName() ) )
      {
         SetFTS(pSS);
         SetTOC(pSS);
         SetF1(pSS);
      }
   }
   hr = S_OK;
crap_out:
   lcFree(pSSFile);
   delete pSubFS;
   return hr;
}

HRESULT CSSList::AddSubset(CStructuralSubset* pSS, HWND hWndUI /* == NULL */)
{
   CStructuralSubset *pSSl;
   HWND hWndCB;

   if (! m_pHeadSubSets )
      m_pHeadSubSets = pSS;
   else
   {
      pSSl = m_pHeadSubSets;
      while ( pSSl->m_pNext )
         pSSl = pSSl->m_pNext;

      pSSl->m_pNext = pSS;
      pSS->m_pNext = NULL;
   }
   m_iSubSetCount++;
   if ( hWndUI && (hWndCB = GetDlgItem(hWndUI, IDC_SS_PICKER)) )
   {
      if ( SendMessage(hWndCB, CB_FINDSTRING, -1, (LPARAM)pSS->GetName()) == -1 )
         SendMessage(hWndCB, CB_ADDSTRING, 0, (LPARAM)pSS->GetName());
   }
   return S_OK;
}

void CSSList::Set(CStructuralSubset* pSSNew, CStructuralSubset** pSSOld, DWORD dwFlags)
{
   if (! pSSNew )
      return;

   pSSNew->m_dwFlags |= dwFlags;
   if ( *pSSOld && (pSSNew != *pSSOld) )
      (*pSSOld)->m_dwFlags &= ~dwFlags;
   *pSSOld = pSSNew;
}

//********************************************************************************
//
// CStructuralSubset Implementation - This is object representation of a subset.
//
//********************************************************************************

CStructuralSubset::CStructuralSubset(PCSTR pszSubSetName)
{
   TCHAR* psz;
   int i = 0;

   m_szSSID[0] = '\0';

   if ( pszSubSetName )
   {
      if ( (psz = StrChr(pszSubSetName, '|')) )  // If the subset name contains an identifier, process it.
      {
         psz = AnsiNext(psz);
         lstrcpyn(m_szSubSetName, psz, MAX_SS_NAME_LEN);
         while ( pszSubSetName[i] != '|' && (i < MAX_SS_NAME_LEN) )
         {
            m_szSSID[i] = pszSubSetName[i];
            i++;
         }
         m_szSSID[i] = '\0';     // terminate.
      }
      else
         lstrcpyn(m_szSubSetName, pszSubSetName, MAX_SS_NAME_LEN);
   }
   else
   {
      m_szSubSetName[0] = '\0';
      m_szSSID[0] = '\0';
   }
   m_pdwHashes = NULL;
   m_iAllocatedCount = m_iHashCount = 0;
   m_dwFlags = 0;
   m_pNext = NULL;
}

CStructuralSubset::~CStructuralSubset()
{
   if ( m_pdwHashes )
      lcFree(m_pdwHashes);
}

HASH CStructuralSubset::EnumHashes(int pos)
{
   if ( !m_iHashCount || (pos >= m_iHashCount) || (pos < 0) )
      return 0;
   return m_pdwHashes[pos];
}

void CStructuralSubset::AddHash(DWORD dwHash)
{
   if ( (m_iHashCount &&  (!(m_iHashCount % 12))) || (m_iAllocatedCount == 0) )
   {
      m_iAllocatedCount += 12;
      m_pdwHashes = (HASH*)lcReAlloc(m_pdwHashes, sizeof(HASH) * m_iAllocatedCount);
   }
   m_pdwHashes[m_iHashCount] = dwHash;
   m_iHashCount++;
}

BOOL CStructuralSubset::IsTitleInSubset(CExTitle* pTitle)
{
   int i;

   for (i = 0; i < m_iHashCount; i++)
   {
      if ( m_pdwHashes[i] == pTitle->m_dwHash )
         return TRUE;
   }
   return FALSE;
}

//
// Function selects the subset as the current TOC filter by setting the f_IsVisable bit accordingly.
// NOTE: we don't bother selecting the bits for entire contents, this is special cased for performance reasons.
//
void CStructuralSubset::SelectAsTOCSubset(CExCollection* pCollection)
{
   CFolder*  pFgt;    // pointer to filtered group tree.
   UINT  i = 0;
   HASH  Hash;

   if ( IsEntire() || IsEmpty() )
      return;

   pFgt = pCollection->m_Collection.GetVisableRootFolder();
   //
   //  De-select all node's filter bits and select the proper
   //  tree nodes based on the hash list!
   //
   while ( pFgt )
   {
      MarkNode(pFgt, 0);        // Remove nodes by marking as not visable.  // RemoveNodeFromFilter(pFgt);
      pFgt = pFgt->pNext;
   }
   while ( Hash = EnumHashes(i++) )
   {
      if ( (pFgt = pCollection->m_pCSlt->HashToCFolder(Hash)) )
         MarkNode(pFgt, 1);     // Add nodes by marking as "visable".       // AddNode2Filter(pFgt);
   }
}

void CStructuralSubset::MarkNode(CFolder* pFgti, BOOL bVisable)
{
   CFolder*   pFgt;
   CFolder*   pFgtLast;

   // First, mark the node and any children of the node.
   //
   pFgt = pFgti;
   if ( pFgt->pKid )
   {
      while ( pFgt )
      {
         do
         {
            pFgt->f_IsVisable = bVisable;
            pFgtLast = pFgt;
            pFgt = pFgt->pKid;

         } while ( pFgt );
         pFgt = pFgtLast;
         while ( pFgt && (! (pFgt->pNext)) )
         {
            if ( pFgt->pParent != pFgti )
               pFgt = pFgt->pParent;
            else
               break;
         }
         if ( pFgt )
            pFgt = pFgt->pNext;
      }
   }
   // Next, assure any parents of the node are properly marked.
   //
   pFgt = pFgti;
   pFgt->f_IsVisable = bVisable;

   while ( (pFgt = pFgt->pParent) )
      pFgt->f_IsVisable = bVisable;
}

//****************************************************************************
//
// CDefineSS Implementation - This is the guy that does all the UI.
//
//*****************************************************************************

CDefineSS::CDefineSS(CExCollection* pCollection)
{
   m_bShouldSave = FALSE;
   m_pSS = NULL;
   m_pCollection = pCollection;
   m_hIL = 0;
   m_hWndParent = 0;
}

CDefineSS::~CDefineSS()
{
}

/****************************************************************************
 * IDefineFilter()
 *
 * Internal entry point for filter manipulation dialog.
 *
 * ENTRY:
 *   none.
 *
 * EXIT:
 *   none.
 *
 ****************************************************************************/
CStructuralSubset* CDefineSS::DefineSubset(HWND hWnd, CStructuralSubset* pSS)
{
   m_pSS = pSS;

   if (! m_hIL )
   {
      m_hIL = ImageList_LoadImage(_Module.GetResourceInstance(), MAKEINTRESOURCE(IDBMP_CNT_IMAGE_LIST), CWIDTH_IMAGE_LIST,
                                  0, 0x00FF00FF, IMAGE_BITMAP, 0);
      if (! m_hIL )
         return NULL;
   }
   //
   // Create the model dialog.
   //
   m_pThis = this;
   m_hWndParent = hWnd;
	if(g_bWinNT5)
	   return (CStructuralSubset*)DialogBoxW(_Module.GetResourceInstance(), MAKEINTRESOURCEW(DLG_FILTERS), hWnd, FilterDlgProc);
	else
	   return (CStructuralSubset*)DialogBox(_Module.GetResourceInstance(), MAKEINTRESOURCE(DLG_FILTERS), hWnd, FilterDlgProc);
}

/*****************************************************************************
 * MyFilterLBFunc()
 *
 * ListBox subclasser. Used for our filter construction LB's. Needed so
 * we can do proper mouse move processing for diddiling the cursor and
 * implementing the silly collpase feature.
 *
 * ENTRY:
 *  The usual window procedure parameters.
 *
 * EXIT:
 *  a long LRESULT.
 *
 *****************************************************************************/
LRESULT CDefineSS::MyFilterLBFunc(HWND hWnd, UINT uiMsg, WPARAM wParam, LPARAM lParam)
{
   int    iTop, iHeight, iItem;
   POINT  pt;
   CFolder*   pFgt;

   switch (uiMsg)
   {
      case WM_SETCURSOR:
         m_pThis->m_giXpos = -1;
         iTop = (int)SendMessage(hWnd, LB_GETTOPINDEX, 0, 0L);
         iHeight = (int)SendMessage(hWnd, LB_GETITEMHEIGHT, 0, 0L);
         GetCursorPos(&pt);
         ScreenToClient(hWnd, &pt);
         //
         // Compute index of the item the cursor is currently hovering over
         // and then get the item data for that item so we can determine it's
         // level and diddle the cursor appropiatly.
         //
         iItem = ((pt.y / iHeight) + iTop);
         if ( (pFgt = (CFolder*)SendMessage(hWnd, LB_GETITEMDATA, iItem, (LPARAM)0)) != (CFolder*)LB_ERR )
         {
            if ( pFgt->iLevel >= 1 )
            {
               if ( pt.x < (pFgt->iLevel * m_pThis->m_giIndentSpacing) )
               {
                  SetCursor(LoadCursor(_Module.GetResourceInstance(),MAKEINTRESOURCE(IDC_COLLAPSE)));
                  m_pThis->m_giXpos = pt.x;
                  return(TRUE);
               }
            }
         }
         break;

      case WM_KEYDOWN:
        PostMessage(hWnd, WM_SETCURSOR, 0, 0L);
        break;

      // HACKHACK - we have a hidden button called IDOK that
      // we use to trap VK_RETURNs so we can send these to
      // the list boxes or other controls
      case WM_SETFOCUS:
        m_pThis->SetDefaultButton( GetParent(hWnd), IDOK, FALSE );
        break;

   }
   return(CallWindowProc(m_pThis->m_lpfnOrigLBProc, hWnd, uiMsg, wParam, lParam));
}

/*****************************************************************************
 * FilterDlgProc()
 *
 * Filter dialog handler
 *
 * ENTRY:
 *   Standard windows callback params.
 *
 * EXIT:
 *   BOOL - TRUE if we handled it. FALSE if we didn't.
 *
 ****************************************************************************/
INT_PTR CDefineSS::FilterDlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
   int    i;
   CFolder* pfgt;

   switch(msg)
   {
      case WM_INITDIALOG: {
         //
         // Set the this pointers for our LB message hook procedures. Also, set the procedure hooks
         // for our LB's so we can do cursor changes, tree collapse and proper keyboard interface.
         //
         SendMessage(GetDlgItem(hDlg,IDF_AVAIL), WM_SETFONT, (WPARAM)m_pThis->m_pCollection->m_phmData->GetContentFont(), 0);
         SendMessage(GetDlgItem(hDlg,IDF_RANGE), WM_SETFONT, (WPARAM)m_pThis->m_pCollection->m_phmData->GetContentFont(), 0);
         SendMessage(GetDlgItem(hDlg,IDF_RANGES), WM_SETFONT, (WPARAM)m_pThis->m_pCollection->m_phmData->GetContentFont(), 0);
//         SendMessage(GetDlgItem(hDlg,IDF_RANGES), WM_SETFONT, (WPARAM)_Resource.GetUIFont(), 0);
         m_pThis->m_lpfnOrigLBProc = (WNDPROC)SetWindowLongPtr(GetDlgItem(hDlg,IDF_RANGE), GWLP_WNDPROC, (LONG_PTR)MyFilterLBFunc);
         SetWindowLongPtr(GetDlgItem(hDlg,IDF_AVAIL), GWLP_WNDPROC, (LONG_PTR)MyFilterLBFunc);
         SendDlgItemMessage(hDlg, IDF_SAVE_EC, EM_LIMITTEXT, MAX_SS_NAME_LEN - 1, 0L);
         SendMessage(GetDlgItem(hDlg,IDF_SAVE_EC), WM_SETFONT, (WPARAM)m_pThis->m_pCollection->m_phmData->GetContentFont(), 0);
         m_pThis->InitDialog(hDlg, NULL);
         m_pThis->m_bShouldSave = FALSE;
         // set the Close button as the default button
         m_pThis->SetDefaultButton( hDlg, IDCANCEL, FALSE );
         m_pThis->m_giXpos = -1;
         break;
      }

      case WM_CHARTOITEM: // swallow these
        return( -2 );

      case WM_VKEYTOITEM:
         switch (CmdID)
         {
             case 0xBD: // '-'
             case VK_SUBTRACT:
                pfgt=(CFolder*)SendMessage(CmdHwnd, LB_GETITEMDATA, CmdCode, (LPARAM)0);
                m_pThis->ExpandContract(CmdHwnd, pfgt, FALSE, (HWND)CmdHwnd == GetDlgItem(hDlg,IDF_AVAIL));
                return(-2);

             case VK_HOME:
                if (CmdID == VK_HOME && !(GetKeyState(VK_CONTROL) & 0x8000 ))
                  return( -1 );

                // otherwise fall through

             case VK_LEFT:
                if ( ((CmdID == VK_HOME)||(CmdID == VK_LEFT)) && (GetKeyState(VK_CONTROL) & 0x8000 ))
                {
                    // Close everyone
                    pfgt= (CFolder*)SendMessage(CmdHwnd, LB_GETITEMDATA, 0, (LPARAM)0);
                    while (pfgt->pNext)
                    {

                      m_pThis->ExpandContract(CmdHwnd, pfgt, FALSE, (HWND)CmdHwnd == GetDlgItem(hDlg,IDF_AVAIL));
                      pfgt = pfgt->pNext;
                    }
                }
                else
                {
                    pfgt= (CFolder*)SendMessage(CmdHwnd, LB_GETITEMDATA, CmdCode, (LPARAM)0);
                    m_pThis->ExpandContract(CmdHwnd, pfgt, FALSE, (HWND)CmdHwnd == GetDlgItem(hDlg,IDF_AVAIL));
                }
                return(-2);

             case VK_BACK: { // go to parent
                CFolder* pFgtCaret;
                int caret;

                if ( (caret = (int)SendMessage(CmdHwnd,LB_GETCARETINDEX,0,0L)) == -1 )
                   return -2;
                if (! (pFgtCaret=(CFolder*)SendMessage(CmdHwnd, LB_GETITEMDATA, caret, (LPARAM)0)) )
                   return -2;
                pFgtCaret = pFgtCaret->pParent;
                if( pFgtCaret )
                {
                   SendMessage(CmdHwnd, LB_SETSEL, FALSE, MAKELPARAM(caret,0));
                   if ( (caret = (int)SendMessage(CmdHwnd,LB_FINDSTRINGEXACT,0,(LPARAM)pFgtCaret)) == -1 )
                      caret = 0;
                   SendMessage(CmdHwnd, LB_SETCARETINDEX, caret, 0L);
                   SendMessage(CmdHwnd, LB_SETSEL, TRUE, MAKELPARAM(caret,0));
                }
                return(-2);
             }

             case 0xBB: // '+'
             case VK_ADD:
                pfgt=(CFolder*)SendMessage(CmdHwnd, LB_GETITEMDATA, CmdCode, (LPARAM)0);
                m_pThis->ExpandContract(CmdHwnd, pfgt, TRUE, (HWND)CmdHwnd == GetDlgItem(hDlg,IDF_AVAIL) );
                return(-2);

             case VK_RIGHT: {
                INT  ci;

                pfgt=(CFolder*)SendMessage(CmdHwnd, LB_GETITEMDATA, CmdCode, (LPARAM)0);
                m_pThis->ExpandContract(CmdHwnd, pfgt, TRUE, (HWND)CmdHwnd == GetDlgItem(hDlg,IDF_AVAIL));

                // select the first child if it has one
                if ( CmdID == VK_RIGHT && pfgt->pKid && (ci = (INT)SendMessage(CmdHwnd, LB_GETCARETINDEX, 0, 0L)) != LB_ERR ) {
                  SendMessage(CmdHwnd, LB_SETSEL, FALSE, MAKELPARAM(ci,0));
                  SendMessage(CmdHwnd, LB_SETCARETINDEX, ci++, MAKELPARAM(0,0));
                  SendMessage(CmdHwnd, LB_SETSEL, TRUE, MAKELPARAM(ci,0));
                }

                return(-2);
             }

             case VK_RETURN:
                SendMessage( CmdHwnd, WM_COMMAND, ((HWND)CmdHwnd == GetDlgItem(hDlg,IDF_AVAIL))?IDF_AVAIL:IDF_RANGE,
                           MAKELPARAM(CmdHwnd,LBN_DBLCLK));
                return(-2);

         }
         return(-1);  // Tell dlgmgr we didn't handle this key stroke.

      case WM_NCLBUTTONDOWN:
          SendDlgItemMessage(hDlg,IDF_RANGES,CB_SHOWDROPDOWN,FALSE,0L);
          return(FALSE);

      case WM_DRAWITEM:
          // draw one of our listbox items
          m_pThis->DrawFBItem(hDlg, (LPDRAWITEMSTRUCT)lParam, (UINT)wParam);
          break;

      case WM_MEASUREITEM:
          // report how big our items are
          m_pThis->MeasureFBItem((LPMEASUREITEMSTRUCT)lParam);
          break;

      case WM_COMMAND:
          if ( m_pThis->FilterDlgCommand(hDlg, wParam, lParam) )
          {
             i = m_pThis->FillFilterTree(GetDlgItem(hDlg,IDF_RANGE), FALSE, FALSE);
             EnableWindow(GetDlgItem(hDlg,IDF_REMOVE), (i != 0));
             EnableWindow(GetDlgItem(hDlg,IDF_REMOVEALL), (i != 0));
             i = m_pThis->FillFilterTree(GetDlgItem(hDlg,IDF_AVAIL), TRUE, FALSE);
             EnableWindow(GetDlgItem(hDlg,IDF_ADD), (i != 0));
             EnableWindow(GetDlgItem(hDlg,IDF_ADDALL), (i != 0));
             m_pThis->SetSaveStatus(hDlg);
          }
          else
             return FALSE;
          break;

      case WM_CLOSE:
          // Closing the Dialog behaves the same as Cancel
          PostMessage(hDlg, WM_COMMAND, IDCANCEL, 0L);
          break;

      case WM_DESTROY:
          break;

      default:
          return FALSE; // say we didn't handle it
          break;
   }
   return TRUE; // say we did handle it
}

/*****************************************************************************
 * MeasureFBItem()
 *
 * Set appropiate size entries in the measure item struc according to the
 * items we'll be rendering in the filter listboxes.
 *
 * ENTRY:
 *   hDlg - Handle to the filter dialog.
 *   lpMIS - Pointer to the LPMEASUREITEMSTRUCT
 *
 * EXIT:
 *   None.
 *
 *****************************************************************************/
void CDefineSS::MeasureFBItem(LPMEASUREITEMSTRUCT lpMIS)
{
    POINT         pt;
    HDC           hDC;
    HFONT         hOldFont;
    TEXTMETRIC    tm;

    ImageList_GetIconSize(m_hIL, (int*)&pt.x, (int*)&pt.y);
    hDC = GetDC(NULL);
    hOldFont = (HFONT)SelectObject(hDC, m_pCollection->m_phmData->GetContentFont());
    GetTextMetrics(hDC, &tm);
    SelectObject(hDC, hOldFont);
    ReleaseDC(NULL, hDC);

    if ( pt.y > tm.tmHeight )
       lpMIS->itemHeight = pt.y;
    else
       lpMIS->itemHeight = tm.tmHeight;

    m_giIndentSpacing = pt.x;
    m_iFontHeight = tm.tmHeight;
    m_iGlyphX = pt.x;
    m_iGlyphY = pt.y;
}

/*****************************************************************************
 * InitDialog()
 *
 * Do all initialization of the range definition dialog for a particular
 * situation, Add, Delete or Change.
 *
 * ENTRY:
 *   hDlg     - The handle to the dialog.
 *   szRange  - Pointer to new range or NULL if initing the range combo-box.
 *
 * EXIT:
 *   BOOL - TRUE on success, FALSE on failure.
 *
 ****************************************************************************/
BOOL CDefineSS::InitDialog(HWND hDlg, LPSTR szRange)
{
   int     iSel = 0;
   int     i = 0;
   int     iFilterCnt = 0;
   BOOL    bDef = FALSE;
   CStructuralSubset* pSS = NULL;

   SendDlgItemMessage(hDlg,IDF_RANGES,CB_SETEXTENDEDUI,TRUE,0L);

   if (! m_pCollection->m_pSSList )
      return(FALSE);

   if (! szRange  )
   {
      SendDlgItemMessage(hDlg, IDF_RANGES, CB_RESETCONTENT, 0, 0L);
      while ( (pSS = m_pCollection->m_pSSList->GetNextSubset(pSS)) )
      {
         i = (int) SendDlgItemMessage(hDlg, IDF_RANGES, CB_INSERTSTRING, (WPARAM) -1, (LPARAM)(LPSTR)pSS->GetName());
         SendDlgItemMessage(hDlg, IDF_RANGES, CB_SETITEMDATA, i, (LPARAM)((DWORD_PTR)pSS));
      }
   }
   else
   {
      if ( (iSel = (int)SendDlgItemMessage(hDlg, IDF_RANGES, CB_FINDSTRINGEXACT,0, (LPARAM)szRange)) != LB_ERR )
         pSS = (CStructuralSubset*)SendDlgItemMessage(hDlg, IDF_RANGES, CB_GETITEMDATA, iSel,0L);
      SendDlgItemMessage(hDlg, IDF_RANGES, CB_SETCURSEL, iSel, 0L);
      bDef = TRUE;
   }
   //
   // Select the current filter. If it's "entire CD" select "New".
   //
   if (! bDef || ! pSS)
   {
      pSS = m_pCollection->m_pSSList->GetNew();
      SendDlgItemMessage(hDlg, IDF_RANGES, CB_SELECTSTRING, (WPARAM)-1, (LPARAM)pSS->GetName());
   }
   //
   // Init the listboxes!
   //
   SetRangeToTree(pSS);
   //
   //  Init filter LB and remove options.
   //
   i = FillFilterTree(GetDlgItem(hDlg,IDF_RANGE), FALSE, FALSE);
   EnableWindow(GetDlgItem(hDlg,IDF_REMOVE), (i != 0));
   EnableWindow(GetDlgItem(hDlg,IDF_REMOVEALL), (i != 0));
   //
   //  Init available LB and add options.
   //
   i = FillFilterTree(GetDlgItem(hDlg,IDF_AVAIL), TRUE, FALSE);
   EnableWindow(GetDlgItem(hDlg,IDF_ADD), (i != 0));
   EnableWindow(GetDlgItem(hDlg,IDF_ADDALL), (i != 0));
   //
   // deal with buttons
   //
   if ( pSS->IsReadOnly() )
   {
      EnableWindow(GetDlgItem(hDlg, IDF_DELETE), FALSE);
      SetWindowText(GetDlgItem(hDlg,IDF_SAVE_EC),GetStringResource(IDS_UNTITLED_SUBSET));
   }
   else
   {
      EnableWindow(GetDlgItem(hDlg, IDF_DELETE), TRUE);
      SetWindowText(GetDlgItem(hDlg,IDF_SAVE_EC), pSS->GetName());
   }
   EnableWindow(GetDlgItem(hDlg,IDF_SAVE), FALSE);
   return(TRUE);
}

/*****************************************************************************
 * DoesNodeHaveANext()
 *
 * aids DrawFBItem() in painting the vertical connecting codes in the filter
 * list boxes.
 *
 * ENTRY:
 *   LbId - ListBox ID.
 *   n    - What level are we painting.
 *   pFgt - Pointer to the node we're painting.
 *
 * EXIT:
 *   BOOL - TRUE if a next will appear on the given level. FALSE otherwise.
 *
 ****************************************************************************/
BOOL CDefineSS::DoesNodeHaveANext(UINT LbId, int n, CFolder* pFgt)
{
   while ( pFgt->iLevel > (n + 1) )
      pFgt = pFgt->pParent;

   while ( pFgt->pNext )
   {
      pFgt = pFgt->pNext;
      if ( (PRESENT((LbId == IDF_AVAIL), pFgt)) )
         return(TRUE);
   }
   return(FALSE);
}

/*****************************************************************************
 * DrawFBItem()
 *
 * Function is responsible for painting the items in the owner-draw list boxes
 * that are used in the filter contents dialog.
 *
 * ENTRY:
 *   hDlg - Handle to the dialog.
 *   lpDI - Pointer to the draw item struct. Contains a pFgt to the item
 *          we're painting.
 *   Lbid - ListBox identifier.
 *
 * EXIT:
 *   None.
 *
 ****************************************************************************/
void CDefineSS::DrawFBItem(HWND hDlg, LPDRAWITEMSTRUCT lpDI, UINT LbId)
{
   RECT     rc;
   int      n;
   WORD     wTopText;
   HDC      hDC;
   CFolder* pFgt;
   INT      xBitmap = 0;
   int      iHeight;
   BOOL     bHasNext;
   char*    lpszText;
   char     szScratch[MAX_PATH];
   static int bNoReEnter = 0;

   if (lpDI->itemID == 0xFFFF)
       return;
   pFgt = (CFolder*)lpDI->itemData;
   if (!pFgt || lpDI->itemData == -1 )
      return;

   bNoReEnter++;
   if ( bNoReEnter > 1 )
   {
      bNoReEnter--;
      return;
   }

   hDC = lpDI->hDC;
   rc = lpDI->rcItem;
   iHeight = rc.bottom - rc.top;
   wTopText = (WORD)((rc.top + ((iHeight) / 2)) - ((m_iFontHeight - 2) / 2));
   /*
    *  Paint the proper lines based on the level and position of the item.
    */
   if ( pFgt->iLevel )
   {
      for ( n = 0; n < pFgt->iLevel; n++ )
      {
         // Draw the verticle line indicating level. Remember to not leave a
         // "tail".
         //
         bHasNext = DoesNodeHaveANext(LbId, n, pFgt);
         if ( bHasNext )
            QRect(hDC, rc.left + m_iGlyphX / 2, rc.top,     // Full verticle line.
                  1, iHeight, COLOR_WINDOWTEXT);
         else if ( n == (pFgt->iLevel - 1) )
            QRect(hDC, rc.left + m_iGlyphX / 2, rc.top,     // Half vertical line.
                  1, (iHeight / 2), COLOR_WINDOWTEXT);

         rc.left += m_iGlyphX;
      }
      // Draw the horizontal connector line.
      //
      QRect(hDC, (rc.left - m_iGlyphX / 2), (rc.top + (m_iGlyphY / 2)), ((m_iGlyphX / 2)), 1, COLOR_WINDOWTEXT);
   }
   //
   // draw the little book.
   //
   if ( EXPANDED((LbId == IDF_AVAIL), pFgt) )  // Is the book open or closed ?
      xBitmap++;                               // It's an open book.
   ImageList_Draw(m_hIL, xBitmap, hDC, rc.left+1, rc.top, ILD_NORMAL);
   rc.left += m_iGlyphX + 2;
   if ( lpDI->itemState & ODS_SELECTED )
   {
       SetBkColor(hDC, GetSysColor(COLOR_HIGHLIGHT));
       SetTextColor(hDC, GetSysColor(COLOR_HIGHLIGHTTEXT));
   }
   else
   {
      SetBkColor(hDC, GetSysColor(COLOR_WINDOW));
      SetTextColor(hDC, GetSysColor(COLOR_WINDOWTEXT));
   }
   //
   // Now paint the text.
   //
   if ( pFgt->f_HasHash && pFgt->pExTitle )
   {
      pFgt->pExTitle->GetTopicLocation(0, szScratch, sizeof(szScratch));
      lpszText = szScratch;
   }
   else
      lpszText = pFgt->Title;
   //
   // Set LB Horizontal extent based upon the width of the string.
   //
   int iWidth, len = lstrlen(lpszText);
   SIZE size;

   if ( lpDI->itemAction == ODA_DRAWENTIRE )
   {
      GetTextExtentPoint32(hDC, lpszText, len, &size);
      size.cx += rc.left + 4;
      iWidth = (int)SendDlgItemMessage(hDlg, LbId, LB_GETHORIZONTALEXTENT, 0, 0L);
      if ( size.cx > iWidth )
         SendDlgItemMessage(hDlg, LbId, LB_SETHORIZONTALEXTENT, (WPARAM)size.cx, 0L);
   }
   //
   // Paint the string.
   //
   ExtTextOut(hDC, (rc.left + 2), wTopText, ETO_OPAQUE | ETO_CLIPPED, &rc, lpszText, len, NULL);
   if (lpDI->itemState & ODS_FOCUS)
       DrawFocusRect(hDC,&rc);

   bNoReEnter--;
}

/****************************************************************************
 * SetRangeToTree()
 *
 * Function will set the filter tree up according the the subset specified
 * by the given CStructuraSubset pointer.
 *
 * ENTRY:
 *   CStructuralSubset* A NULL object is the equivelent to RF_NONE.
 *                      Use of this feature will cause all nodes to be
 *                      removed from the filter.
 *
 * EXIT:
 *  BOOL - TRUE for success. FALSE for failure!
 *
 ****************************************************************************/
BOOL CDefineSS::SetRangeToTree(CStructuralSubset* pSS)
{
   CFolder*  pFgt;    // pointer to filtered group tree.
   UINT  i = 0;
   HASH  Hash;

   pFgt = m_pCollection->m_Collection.GetVisableRootFolder();
   if ( pSS )
   {
      if ( pSS->m_dwFlags & SS_ENTIRE_CONTENTS )
      {
         while ( pFgt )
         {
            AddNode2Filter(pFgt);
            pFgt = pFgt->pNext;
         }
      }
      else
      {
         //
         //  De-select all nodes filter bits and select the proper
         //  tree nodes based on the hash list!
         //
         while ( pFgt )
         {
            RemoveNodeFromFilter(pFgt);
            pFgt = pFgt->pNext;
         }
         while ( Hash = pSS->EnumHashes(i++) )
         {
            if ( (pFgt = m_pCollection->m_pCSlt->HashToCFolder(Hash)) )
               AddNode2Filter(pFgt);
         }
      }
      return(TRUE);
   }
   else
   {
      while ( pFgt )
      {
         RemoveNodeFromFilter(pFgt);
         pFgt = pFgt->pNext;
      }
   }
   return(FALSE);
}

/****************************************************************************
 * GetRangeFromTree()
 *
 * Function will extract a range from the filter LB and return a handle to
 * the range.
 *
 * ENTRY:
 *  sz - Range name.
 *
 * EXIT:
 *
 ****************************************************************************/
CStructuralSubset* CDefineSS::GetRangeFromTree(LPSTR sz)
{
   CFolder*    pFgt;
   CFolder*    pFgtLast;
   int     i = 0;
   CStructuralSubset* pSS;
   CExTitle* pExTitle;

   if (! (pSS = new CStructuralSubset(sz)) )
      return NULL;  // Young girls are chaining themselves to the axels of big mac trucks.

   // Walk root level nodes and gather up the prefix hashes from each node
   // and all it's kids if the filter bit is set.
   //
   pFgt = m_pCollection->m_Collection.GetVisableRootFolder();
   while ( pFgt )
   {
      do
      {
         pFgtLast = pFgt;
         if ( pFgt->f_Filter && !pFgt->f_Available && pFgt->f_HasHash )
         {
            pSS->AddHash(pFgt->pExTitle->m_dwHash);
            //
            // Any merged .CHM's ?
            //
            pExTitle = pFgt->pExTitle->m_pKid;
            while ( pExTitle )
            {
               pSS->AddHash(pExTitle->m_dwHash);
               pExTitle = pExTitle->m_pNextKid;
            }
            i++;
            break;
         }
         pFgt = pFgt->pKid;

      } while ( pFgt );

      pFgt = pFgtLast;
      while ( pFgt && (! (pFgt->pNext)) )
         pFgt = pFgt->pParent;
      if ( pFgt )
         pFgt = pFgt->pNext;
   }
   // Lastly, setup the range entry and add it to the range list.
   //
   if (! i )
   {
      delete pSS;
      return(NULL);
   }
   return pSS;
}

/*****************************************************************************
 * SetSaveStatus()
 *
 * If you don't want'em saving then disable the save button!
 *
 * ENTRY:
 *  hDlg - Handle to the define contents dialog.
 *
 * EXIT:
 *  None.
 *
 ****************************************************************************/
void CDefineSS::SetSaveStatus(HWND hDlg)
{
   char     sz[MAX_SS_NAME_LEN];
   int      i;
   CStructuralSubset* pSS;
   HWND     hWnd = GetDlgItem(hDlg, IDF_SAVE);

   GetWindowText(GetDlgItem(hDlg, IDF_SAVE_EC), sz, sizeof(sz));
   i = (int)SendDlgItemMessage(hDlg, IDF_RANGES, CB_GETCURSEL, 0, 0L);
   pSS = (CStructuralSubset*)SendDlgItemMessage(hDlg,IDF_RANGES,CB_GETITEMDATA,i,0L);

   if ( ((pSS->m_dwFlags & SS_READ_ONLY) && (! lstrcmpi(pSS->GetName(), sz))) ||
      (! SendDlgItemMessage(hDlg, IDF_RANGE, LB_GETCOUNT, 0, 0L)) ||
      (! lstrlen(sz)) || !m_bShouldSave )
      EnableWindow(hWnd, FALSE);
   else
      EnableWindow(hWnd, TRUE);
}

/*****************************************************************************
 * ShouldSave()
 *
 * Alert the user that they may want to save the changes they've made to
 * the filter.
 *
 * ENTRY:
 *   hDlg - Handle to the filter definition dialog.
 *
 * EXIT:
 *   INT - IDYES, IDNO or IDCANCEL. 0 if saving is not necessary.
 *
 ****************************************************************************/
INT CDefineSS::ShouldSave(HWND hDlg)
{
   char    sz[MAX_SS_NAME_LEN];

   if ( IsWindowEnabled(GetDlgItem(hDlg, IDF_SAVE)) )
   {
      GetWindowText(GetDlgItem(hDlg, IDF_SAVE_EC), sz, sizeof(sz));
      return MsgBox(IDS_SAVE_FILTER, sz, MB_YESNOCANCEL | MB_ICONQUESTION);
   }
   return(0);
}

/****************************************************************************
 * FilterDlgCommand()
 *
 * Function handles all WM_COMMAND messages from the filter dialog.
 *
 * ENTRY:
 *   hDlg   - Handle to the dialog.
 *   wParam - Message dependent.
 *   lParam - Message dependent.
 *
 * EXIT:
 *   Returns TRUE is range list boxes need updating. False otherwise.
 *
 ***************************************************************************/
BOOL CDefineSS::FilterDlgCommand(HWND hDlg, WPARAM wParam, LPARAM lParam)
{
   int         i, n;
   CFolder*    pFgt;
   char        sz[MAX_SS_NAME_LEN];
   static      il;
   static HWND hWndCombo = NULL;

   switch (CmdID)
   {
      // HACKHACK - this is an invisible button that we use to route double-clicks
      // to other controls
      case IDOK:
      {
        HWND hWndCurrent, hWndFocus;
        int id = 0;
        // find out which control we are on
        if( (hWndFocus = GetFocus()) )
        {
          hWndCurrent = GetDlgItem(hDlg,IDF_AVAIL);
          if( hWndFocus == hWndCurrent )
            id = IDF_AVAIL;
          hWndCurrent = GetDlgItem(hDlg,IDF_RANGE);
          if( hWndFocus == hWndCurrent )
            id = IDF_RANGE;
          hWndCurrent = GetDlgItem(hDlg,IDF_RANGES);
          if( hWndFocus == hWndCurrent )
            id = IDF_RANGES;

          SendMessage( hDlg, WM_COMMAND, MAKEWPARAM(id,LBN_DBLCLK), 0L);
        }
        return( TRUE );
      }

      case IDF_SAVE:
         if ( SaveFilter(hDlg) )
         {
            SetDefaultButton( hDlg, IDCANCEL, TRUE );
            i = (int)SendDlgItemMessage(hDlg, IDF_RANGES, CB_GETCURSEL, 0, 0L);
            m_pSS = (CStructuralSubset*)SendDlgItemMessage(hDlg, IDF_RANGES, CB_GETITEMDATA, i, 0L);
            EnableWindow(GetDlgItem(hDlg, IDF_DELETE), TRUE);
            m_bShouldSave = FALSE;
         }
         else
            SetFocus(GetDlgItem(hDlg, IDF_RANGES));
         break;

      case IDCANCEL:
         // if a combo box is open then close it and don't close the dialog
         if( hWndCombo )
         {
           SendMessage( hWndCombo, CB_SHOWDROPDOWN, FALSE, 0L );
           return FALSE;
         }
         //
         // Has the range been modified ? Might it need to be saved.
         //
         if ( m_bShouldSave )
         {
            i = ShouldSave(hDlg);
            if ( i == IDYES )
            {
               if (! SaveFilter(hDlg) )
               {
                  SetFocus(GetDlgItem(hDlg, IDF_RANGES));
                  break;
               }
               i = (int)SendDlgItemMessage(hDlg, IDF_RANGES, CB_GETCURSEL, 0, 0L);
               m_pSS = (CStructuralSubset*)SendDlgItemMessage(hDlg, IDF_RANGES, CB_GETITEMDATA, i, 0L);
               m_bShouldSave = FALSE;
            }
            else if ( i == IDCANCEL )
               break;
         }
         EndDialog(hDlg, (INT_PTR)m_pSS);
         break;

      case IDF_DELETE:
         if ( (i = (int)SendDlgItemMessage(hDlg, IDF_RANGES, CB_GETCURSEL, 0, 0L)) != CB_ERR )
         {
            int n;
            m_pSS = (CStructuralSubset*)SendDlgItemMessage(hDlg, IDF_RANGES, CB_GETITEMDATA, i, 0L);
            if ( MsgBox(IDS_CONFIRM_SSDELETE, m_pSS->GetName(), MB_YESNO | MB_ICONQUESTION) == IDYES )
            {
               if ( (i = (int)SendDlgItemMessage(hDlg, IDF_RANGES, CB_FINDSTRINGEXACT, 0, (LPARAM)m_pSS->GetName())) != LB_ERR)
                  SendDlgItemMessage(hDlg, IDF_RANGES, CB_DELETESTRING, i, 0L);
               m_pCollection->m_pSSList->DeleteSubset(m_pSS, NULL, m_hWndParent);
               n = (int)SendDlgItemMessage(hDlg, IDF_RANGES, CB_GETCOUNT, 0, 0L);
               n--;
               i = min(n,i);
               SendDlgItemMessage(hDlg, IDF_RANGES, CB_SETCURSEL, i, 0L);
               i = (int)SendDlgItemMessage(hDlg, IDF_RANGES, CB_GETCURSEL, 0, 0L);
               m_pSS = (CStructuralSubset*)SendDlgItemMessage(hDlg, IDF_RANGES, CB_GETITEMDATA, i, 0L);
               InitDialog(hDlg, m_pSS->GetName());
               m_bShouldSave = FALSE;
               return(FALSE);
            }
         }
         break;

      case IDF_SAVE_EC:
         switch (CmdCode)
         {
            case EN_UPDATE:
               m_bShouldSave = TRUE;
               SetSaveStatus(hDlg);
               SetDefaultButton( hDlg, IDF_SAVE, FALSE );
               break;

            case EN_CHANGE:
               m_bShouldSave = TRUE;
               break;
         }
         break;

      case IDF_RANGES:
         switch (CmdCode)
         {
            case CBN_DROPDOWN:
               il = (int)SendDlgItemMessage(hDlg,IDF_RANGES,CB_GETCURSEL,0,0L);
               break;

            case CBN_SELCHANGE:
               i = (int)SendDlgItemMessage(hDlg,IDF_RANGES,CB_GETCURSEL,0,0L);
               SendDlgItemMessage(hDlg,IDF_RANGES,CB_GETLBTEXT,i,(LPARAM)sz);
               if ( m_bShouldSave )
               {
                  n = ShouldSave(hDlg);
                  if ( n == IDCANCEL )
                     goto cancel_it;
                  else if ( n == IDYES )
                  {
                     if (! SaveFilter(hDlg) )
                        goto cancel_it;
                  }
                  //SetDefaultButton( hDlg, IDCANCEL, TRUE );
               }
               i = (int)SendDlgItemMessage(hDlg, IDF_RANGES, CB_GETCURSEL, 0, 0L);
               m_pSS = (CStructuralSubset*)SendDlgItemMessage(hDlg, IDF_RANGES, CB_GETITEMDATA, i, 0L);
               m_bShouldSave = FALSE;
               InitDialog(hDlg, sz);
               return(FALSE);
cancel_it:
               SendDlgItemMessage(hDlg,IDF_RANGES,CB_SETCURSEL,(WPARAM)il,0L);
         }
         break;

      case IDHELP:
         break;


      case IDF_NEW:
         m_bShouldSave = TRUE;
         SetSaveStatus(hDlg);
         break;

      case IDF_REMOVE:
         n = (int)SendDlgItemMessage(hDlg, IDF_RANGE, LB_GETCOUNT, 0, 0L);
         for (i = 0; i < n; i++)
         {
             if (SendDlgItemMessage(hDlg, IDF_RANGE, LB_GETSEL, i, 0L))
             {
                 pFgt=(CFolder*)SendDlgItemMessage(hDlg,IDF_RANGE,LB_GETITEMDATA,i, (LPARAM)0);
                 RemoveNodeFromFilter(pFgt);
                 SendDlgItemMessage(hDlg, IDF_RANGE, LB_SETSEL, 0, (LPARAM)i);
                 m_bShouldSave = TRUE;
             }
         }
         SetFocus(GetDlgItem(hDlg, IDF_RANGE));
         return(TRUE);
         break;

      case IDF_ADDALL:
         //
         // Enumerate root level nodes Adding each!
         //
         pFgt = m_pCollection->m_Collection.GetVisableRootFolder();
         while ( pFgt )
         {
            AddNode2Filter(pFgt);
            pFgt = pFgt->pNext;
         }
         m_bShouldSave = TRUE;
         SetFocus(GetDlgItem(hDlg, IDF_RANGE));
         return(TRUE);
         break;

      case IDF_ADD:
         n = (int)SendDlgItemMessage(hDlg, IDF_AVAIL, LB_GETCOUNT, 0, 0L);
         for (i = 0; i < n; i++)
         {
             if (SendDlgItemMessage(hDlg, IDF_AVAIL, LB_GETSEL, i, 0L))
             {
                 pFgt=(CFolder*)SendDlgItemMessage(hDlg,IDF_AVAIL,LB_GETITEMDATA,i, (LPARAM)0);
                 AddNode2Filter(pFgt);
                 SendDlgItemMessage(hDlg, IDF_AVAIL, LB_SETSEL, 0, (LPARAM)i);
                 m_bShouldSave = TRUE;
             }
         }
         SetFocus(GetDlgItem(hDlg, IDF_AVAIL));
         return(TRUE);
         break;

      case IDF_REMOVEALL:
         //
         // Enumerate root level nodes removing each!
         //
         pFgt = m_pCollection->m_Collection.GetVisableRootFolder();
         while ( pFgt )
         {
            RemoveNodeFromFilter(pFgt);
            pFgt = pFgt->pNext;
         }
         m_bShouldSave = TRUE;
         SetFocus(GetDlgItem(hDlg, IDF_AVAIL));
         return(TRUE);
         break;

      case IDF_RANGE:
         switch (CmdCode)
         {
            case LBN_DBLCLK:
               n = 0;
               goto expcont;
         }
         break;

      case IDF_AVAIL:
         switch (CmdCode)
         {
            case LBN_DBLCLK:
               n = 1;
expcont:
               i = (int)SendDlgItemMessage(hDlg,CmdID,LB_GETCARETINDEX,0,0L);
               if (i == (int)LB_ERR)
                   break;

               pFgt=(CFolder*)SendDlgItemMessage(hDlg,CmdID,LB_GETITEMDATA,i, (LPARAM)0);
               if ( !pFgt || pFgt == (CFolder*)-1 )
                  break;
               //
               // giXpos is set in my LB hook proc. If it's != -1 then this
               // double click is intended as a collapsing dbl click and giXpos
               // will indicate the x position within the LB where the double
               // click occured. Sound reasonable ?
               //
               if ( m_giXpos > 0 )
               {
                  int iLevel;

                  iLevel = m_giXpos / m_giIndentSpacing;
                  while ( (iLevel != pFgt->iLevel) && pFgt->pParent )
                     pFgt = pFgt->pParent;
               }
               ExpandContract(GetDlgItem(hDlg,CmdID), pFgt, -1, n);
               break;

            default:
               break;

         }
         break;
   }

   switch( CmdCode )
   {
      case CBN_DROPDOWN:
         hWndCombo = (HWND) lParam;
         SetDefaultButton( hDlg, IDCANCEL, FALSE );
         break;

      case CBN_CLOSEUP:
         hWndCombo = NULL;
         SetDefaultButton( hDlg, IDCANCEL, FALSE );
         break;

   }

   return(FALSE);
}

/*****************************************************************************
 * SaveFilter()
 *
 * Function saves a filter to the filter list, insuring a read-only filter
 * is not over-written.
 *
 * ENTRY:
 *   hDlg - Handle to the filter definition dialog.
 *
 * EXIT:
 *   BOOL - TRUE Indicates the filter was saved. FALSE indicates filter
 *          was not saved.
 *
 *****************************************************************************/
BOOL CDefineSS::SaveFilter(HWND hDlg)
{
   char    szGivenRN[MAX_SS_NAME_LEN];
   int     i = 0;
   CStructuralSubset* pSS = NULL;
   CStructuralSubset* pSSNew;

   GetDlgItemText(hDlg, IDF_SAVE_EC, szGivenRN, sizeof(szGivenRN));
   //
   // Extract range from the filter LB.
   //
   if (! (pSSNew = GetRangeFromTree(szGivenRN)) )
      return(FALSE);
   //
   // Insure we're not trashing a read only ramge.
   //
   while ( (pSS = m_pCollection->m_pSSList->GetNextSubset(pSS)) )
   {
      if (! lstrcmpi(pSS->GetName(), pSSNew->GetName()) )
      {
         if ( pSS->m_dwFlags & SS_READ_ONLY )
         {
            MsgBox(IDS_BAD_RANGE_NAME, MB_OK | MB_ICONEXCLAMATION);
            return(FALSE);
         }
         // Sure you want to over-write the existing rnage ?
         //
         if ( MsgBox(IDS_OVERWRITE, MB_YESNO | MB_ICONQUESTION) == IDNO )
            return(FALSE);
         if ( (i = (int)SendDlgItemMessage(hDlg, IDF_RANGES, CB_FINDSTRINGEXACT, 0, (LPARAM)pSS->GetName())) != LB_ERR)
            SendDlgItemMessage(hDlg, IDF_RANGES, CB_DELETESTRING, i, 0L);
         m_pCollection->m_pSSList->DeleteSubset(pSS, pSSNew, m_hWndParent);
         break;
      }
   }
   // Add new subset to the list and update UI...
   //
   m_pCollection->m_pSSList->AddSubset(pSSNew, m_hWndParent);
   SendDlgItemMessage(hDlg, IDF_RANGES, CB_INSERTSTRING, 0, (LPARAM)pSSNew->GetName());
   SendDlgItemMessage(hDlg, IDF_RANGES, CB_SETITEMDATA, 0, (LPARAM)pSSNew);
   SendDlgItemMessage(hDlg, IDF_RANGES, CB_SETCURSEL, 0, 0L);
   EnableWindow(GetDlgItem(hDlg,IDF_SAVE), FALSE);
   return(TRUE);
}

/*****************************************************************************
 * RemoveNodeFromFilter()
 *
 * Function removes the node and all it's kids from the filter.
 *
 * ENTRY:
 *   pFgt - Poiner to the node to be removed.
 *
 * EXIT:
 *  None.
 *
 *****************************************************************************/
void CDefineSS::RemoveNodeFromFilter(CFolder* pFgti)
{
   CFolder*   pFgt;
   CFolder*   pFgtLast;

   // First, de-select the node and any children of the node taking care
   // to set the f_Available bit from each!
   //
   pFgt = pFgti;
   if ( pFgt->pKid )
   {
      while ( pFgt )
      {
         do
         {
            pFgt->f_Available = 1;
            pFgt->f_Filter = 0;
            pFgtLast = pFgt;
            pFgt = pFgt->pKid;

         } while ( pFgt );
         pFgt = pFgtLast;
         while ( pFgt && (! (pFgt->pNext)) )
         {
            if ( pFgt->pParent != pFgti )
               pFgt = pFgt->pParent;
            else
               break;
         }
         if ( pFgt )
            pFgt = pFgt->pNext;
      }
   }
   // Next, assure any parents of the node are marked as available. We also
   // have to take care to see that a parent who has no children who are
   // part of the filter are removed from the filter!
   //
   pFgt = pFgti;
   pFgt->f_Filter = 0;
   pFgt->f_Available = 1;

   while ( pFgt = pFgt->pParent )
   {
      pFgt->f_Available = 1;
      SetFilterStatus(pFgt);
   }
}

/*****************************************************************************
 * SetFilterStatus()
 *
 * If ALL children of the given node are marked as available, that nodes
 * filter bit will be set to zero.
 *
 * ENTRY:
 *   pFgt - Pointer to the node to start from.
 *
 * EXIT:
 *
 ****************************************************************************/
void CDefineSS::SetFilterStatus(CFolder* pFgt)
{
   CFolder* pFgtl = pFgt->pKid;
   CFolder* pFgtLast;

   while ( pFgtl )
   {
      do
      {
         if (! pFgtl->f_Available )
            return;
         pFgtLast = pFgtl;
         pFgtl = pFgtl->pKid;
      } while ( pFgtl );
      pFgtl = pFgtLast;
      while ( pFgtl && (! (pFgtl->pNext)) && (pFgtl->pParent != pFgt) )
         pFgtl = pFgtl->pParent;
      if ( pFgtl )
         pFgtl = pFgtl->pNext;
   }
   pFgt->f_Filter = 0;
}

/*****************************************************************************
 * AddNode2Filter()
 *
 * Function adds a node to the filter assuring that necessary parents are
 * added, and necessary nodes are removed from the available listbox.
 *
 * ENTRY:
 *   pFgti = Node to be added.
 *
 * EXIT:
 *   None.
 *
 *****************************************************************************/
void CDefineSS::AddNode2Filter(CFolder* pFgti)
{
   CFolder*   pFgt;
   CFolder*   pFgtLast;

   // First, select the node and any children of the node taking care
   // to remove the f_Available bit from each!
   //
   pFgt = pFgti;
   while ( pFgt )
   {
      do
      {
         pFgt->f_Available = 0;
         pFgt->f_Filter = 1;

         pFgtLast = pFgt;
         pFgt = pFgt->pKid;

      } while ( pFgt );
      pFgt = pFgtLast;
      if ( pFgt == pFgti )
         break;
      while ( pFgt && (! (pFgt->pNext)) )
      {
         if ( pFgt->pParent != pFgti )
            pFgt = pFgt->pParent;
         else
            break;
      }
      if ( pFgt )
         pFgt = pFgt->pNext;
   }
   // Next, assure parents are selected.
   //
   pFgt = pFgti;
   if ( pFgt->pParent )
   {
      while ( pFgt = pFgt->pParent )
      {
         pFgt->f_Filter = 1;
         pFgt->f_F_Open = 1;
         SetAvailableStatus(pFgt);   // Should it still be available ?
      }
   }
}

/*****************************************************************************
 * SetAvailabilityStatus()
 *
 * If ALL children of the given node are selected as part of a filter, that
 * nodes availability bit will be set to zero.
 *
 * ENTRY:
 *   pFgt - Pointer to the node to start from.
 *
 * EXIT:
 *
 ****************************************************************************/
void CDefineSS::SetAvailableStatus(CFolder* pFgt)
{
   CFolder* pFgtl = pFgt->pKid;
   CFolder* pFgtLast;

   while ( pFgtl )
   {
      do
      {
         if (! pFgtl->f_Filter )
            return;
         pFgtLast = pFgtl;
         pFgtl = pFgtl->pKid;
      } while ( pFgtl );
      pFgtl = pFgtLast;
      while ( pFgtl && (! (pFgtl->pNext)) && (pFgtl->pParent != pFgt) )
         pFgtl = pFgtl->pParent;
      if ( pFgtl )
         pFgtl = pFgtl->pNext;
   }
   pFgt->f_Available = 0;
}

/******************************************************************************
 * ExpandContract()
 *
 * Function set's appropiate bits in the appropiate nodes to cause
 * a parent node to expand or contract when FillRangeTree() is called.
 *
 * ENTRY:
 *   hwndLB - Handle to the listbox that contains the node were operating on.
 *
 *   pFgt   - Pointer to the node were expanding or contracting.
 *
 *   sel    - Identifies action: TRUE for Expansion
 *                               FALSE for Contraction
 *                               -1 for double-click which really means that
 *                               we toggle the current state for the appropiate
 *                               LB identified by the "which" arg.
 *
 *   which  - Which ListBox are we working with: TRUE  == Available LB.
 *                                               FALSE == Range LB.
 *
 * EXIT:
 *  WORD - Returns the AX from FillRangeTree which happens to be the number
 *         of items placed in the LB.
 *
 *****************************************************************************/
WORD CDefineSS::ExpandContract(HWND hwndLB, CFolder* pFgt, int sel, BOOL which)
{
   if (sel != 0 && sel != 1)
   {
      if (! pFgt->pKid )
         return(0);
      //
      // This is the double click case.
      //
      if ( which ) {
         if( pFgt->f_A_Open )
           pFgt->f_A_Open = FALSE;
         else
           pFgt->f_A_Open = TRUE;
      }
      else
         pFgt->f_F_Open = !pFgt->f_F_Open;
   }
   else
   {
      // This is the +/- case
      //
      // Contraction works a little differently than expansion.
      // For contraction, if the selected node is not expanded, we close
      // it's parent. If it is expanded, we close it.
      //
      if ( !sel )  // if we're doing contraction...
      {
         if ( ! (which ? pFgt->f_A_Open : pFgt->f_F_Open) ) // if node closed...
         {
            if ( pFgt->pParent )
               pFgt = pFgt->pParent;
         }
      }

      if (! pFgt->pKid && pFgt->pParent)
         pFgt = pFgt->pParent;

      if ( which )
         pFgt->f_A_Open = sel;
      else
         pFgt->f_F_Open = sel;
   }
   return (WORD)FillFilterTree(hwndLB, which, sel);
}

/*****************************************************************************
 * FillFilterTree()
 *
 * Function actually makes the send message calls to cause a repaint of
 * the listbox identified by hwndLB.
 *
 * ENTRY:
 *    hwndLB     - Identifies the listbox were working with (it's handle)
 *    fAvailable - Which ListBox are we working with: TRUE  == Available LB.
 *                                                    FALSE == Range LB.
 *    fScrollup  - If TRUE and a parents children will appear off the bottom
 *                 of the LB we'll scroll the parent to the top of the LB.
 *
 * EXIT:
 *    UINT - Returns the number of items placed in the LB.
 *
 *****************************************************************************/
UINT CDefineSS::FillFilterTree(HWND hwndLB, BOOL fAvailable, BOOL fScrollup)
{
   UINT uiRet = 0;
   int  n, nVis, top, caret;
   CFolder* pFgtTop, *pFgtCaret, *pFgt, *plFgt;
   RECT rc;

   /*
    *  Compute the number of visible lines, get the top and caret indicies.
    */
   GetClientRect(hwndLB,&rc);
   n = (int)SendMessage(hwndLB,LB_GETITEMHEIGHT,0,0L);
   nVis = rc.bottom / n;
   SendMessage(hwndLB,WM_SETREDRAW,FALSE,0L);  // Don't re-paint!

   caret = (int)SendMessage(hwndLB,LB_GETCARETINDEX,0,0L);
   top = (int)SendMessage(hwndLB,LB_GETTOPINDEX,0,0L);
   /*
    *  Get the items living at the top and the caret position of the listbox.
    */
   if (top == (int)LB_ERR)
       pFgtTop = NULL;
   else
   {
       if ( (pFgtTop=(CFolder*)SendMessage(hwndLB, LB_GETITEMDATA, top, (LPARAM)0)) == (CFolder*)LB_ERR )
          pFgtTop = NULL;
   }

   if (caret == (int)LB_ERR)
       pFgtCaret = NULL;
   else
   {
       if ( (pFgtCaret=(CFolder*)SendMessage(hwndLB, LB_GETITEMDATA, caret, (LPARAM)0)) == (CFolder*)LB_ERR )
          pFgtCaret = NULL;
   }
   /*
    *  If the top entry is a child and it's not expanded, set it's parent
    *  as the top. ie it's been collapsed.
    */
   if ( pFgtTop && pFgtTop->pParent && !EXPANDED(fAvailable, pFgtTop->pParent) )
       pFgtTop = pFgtTop->pParent;
   /*
    *  If the caret position is on a child and it's not expanded, set it's
    *  parent as the caret position. ie it's been collapsed.
    */
   if ( pFgtCaret && pFgtCaret->pParent && !EXPANDED(fAvailable, pFgtCaret->pParent) )
       pFgtCaret = pFgtCaret->pParent;
   /*
    *  Insure we have not set the top or caret position to a non selected
    *  node.
    */
   if ( pFgtTop && !PRESENT(fAvailable,pFgtTop) )
   {
      plFgt = pFgtTop;
      do
      {
         do
         {
            pFgtTop = pFgtTop->pNext;
         } while ( pFgtTop && !PRESENT(fAvailable,pFgtTop) );

         if (! pFgtTop )
         {
            pFgtTop = plFgt->pParent;
            plFgt = pFgtTop;
         }
         else
            break;
      } while ( pFgtTop );
   }
   if ( pFgtCaret && !PRESENT(fAvailable,pFgtCaret) )
   {
      plFgt = pFgtCaret;
      do
      {
         do
         {
            pFgtCaret = pFgtCaret->pNext;
         } while ( pFgtCaret && !PRESENT(fAvailable,pFgtCaret) );

         if (! pFgtCaret )
         {
            pFgtCaret = plFgt->pParent;
            plFgt = pFgtCaret;
         }
         else
            break;
      } while ( pFgtCaret );
   }
   SendMessage(hwndLB,LB_RESETCONTENT,FALSE,0L);
   /*
    *  Reset horizontal extent.
    */
   SendMessage(hwndLB, LB_SETHORIZONTALEXTENT, 0, 0L);
   /*
    *  Now, add the appropiate items to the list box.
    */
   if ( fAvailable )
      uiRet = AvailableWalk(hwndLB);
   else
      uiRet = FilteredWalk(hwndLB);
   /*
    *  Lastly, insure a parent is scrolled to the top of the LB if it's kids
    *  have run off of the bottom of the LB.
    */
    if (pFgtTop)
        top = (int)SendMessage(hwndLB,LB_FINDSTRINGEXACT,0,(LPARAM)pFgtTop);

    if (pFgtCaret)
        caret = (int)SendMessage(hwndLB,LB_FINDSTRINGEXACT,0,(LPARAM)pFgtCaret);

    if (!pFgtTop || top == (int)LB_ERR)
        top = 0;

    if (!pFgtCaret || caret == (int)LB_ERR)
        caret = 0;

    if ( fScrollup && pFgtCaret && pFgtCaret->pKid )
    {
        if (caret < top)
            top = caret;

        for (pFgt = pFgtCaret->pKid, n = 1; pFgt; pFgt = pFgt->pNext)
            ++n;

        if (n >= nVis)
            top = caret;
        else if (caret + n > top + nVis)
            top = caret - nVis + n;
    }
    SendMessage(hwndLB, LB_SETTOPINDEX, top, 0L);
    SendMessage(hwndLB, LB_SETCARETINDEX, caret, 0L);
    SendMessage(hwndLB, LB_SETSEL, TRUE, MAKELPARAM(caret,0));

    SendMessage(hwndLB, WM_SETREDRAW, TRUE, 0L);
    InvalidateRect(hwndLB, NULL, TRUE);
    return(uiRet);
}

/*****************************************************************************
 * FilteredWalk()
 *
 * Helper function called only from FillFilterTree(). This bit of code walks
 * the filter group tree and adds the selected nodes to the listbox given.
 *
 * ENTRY:
 *   hwndLB - Handle to the list box to which items are to be added.
 *
 * EXIT:
 *   uint - Returns the number of items added to the listbox.
 *
 ****************************************************************************/
UINT CDefineSS::FilteredWalk(HWND hwndLB)
{
   CFolder*  pFgt;
   CFolder*  pFgtLast;
   UINT  i = 0;

   pFgt = m_pCollection->m_Collection.GetVisableRootFolder();
   /*
    *  Ok, now we actually do all the walking of the filter linked list to
    *  re-generate the filter listbox contnets. This must be done in the
    *  order in which the items appear in the LB. ie. Depthwise.
    */
   while ( pFgt )
   {
      do
      {
         if ( pFgt->f_Filter && !pFgt->f_IsOrphan )
         {
            SendMessage(hwndLB, LB_ADDSTRING, 0, (LPARAM)pFgt);
            i++;
         }
         pFgtLast = pFgt;
         if ( pFgt->f_F_Open )
            pFgt = pFgt->pKid;
         else
            break;
      } while ( pFgt );
      pFgt = pFgtLast;
      while ( pFgt && (! (pFgt->pNext)) )
         pFgt = pFgt->pParent;
      if ( pFgt )
         pFgt = pFgt->pNext;
   }
   return(i);
}

/*****************************************************************************
 * AvailableWalk()
 *
 * Helper function called only from FillFilterTree(). This bit of code walks
 * the filter group tree and adds the selected nodes to the listbox given.
 *
 * ENTRY:
 *   hwndLB - Handle to the list box to which items are to be added.
 *
 * EXIT:
 *   uint - Returns the number of items added to the listbox.
 *
 ****************************************************************************/
UINT CDefineSS::AvailableWalk(HWND hwndLB)
{
   CFolder*  pFgt;
   CFolder*  pFgtLast;
   UINT  i = 0;

   pFgt = m_pCollection->m_Collection.GetVisableRootFolder();
   /*
    *  Ok, now we actually do all the walking of the filter linked list to
    *  re-generate the filter listbox contnets. This must be done in the
    *  order in which the items appear in the LB. ie. Depthwise.
    */
   while ( pFgt )
   {
      do
      {
         if ( pFgt->f_Available && !pFgt->f_IsOrphan )
         {
            SendMessage(hwndLB, LB_ADDSTRING, 0, (LPARAM)pFgt);
            i++;
         }
         pFgtLast = pFgt;
         if ( pFgt->f_A_Open )
            pFgt = pFgt->pKid;
         else
            break;
      } while ( pFgt );
      pFgt = pFgtLast;
      while ( pFgt && (! (pFgt->pNext)) )
         pFgt = pFgt->pParent;
      if ( pFgt )
         pFgt = pFgt->pNext;
   }
   return(i);
}

//
// Change a dialog's default push button
//
BOOL CDefineSS::SetDefaultButton( HWND hDlg, int iID, BOOL fFocus )
{
  BOOL bReturn = FALSE;

  if( hDlg ) {

    // Get the current default push button and reset it.
    ResetDefaultButton( hDlg );

    // Update the default push button's control ID.
    SendMessage( hDlg, DM_SETDEFID, (WPARAM) iID, 0L );

    // Set the new style.
    bReturn = (BOOL) SendDlgItemMessage( hDlg, iID, BM_SETSTYLE, (WPARAM) BS_DEFPUSHBUTTON, (LPARAM) TRUE );

    // Set the focus to the new default push button.
    if( fFocus )
      bReturn &= (SetFocus( GetDlgItem( hDlg, iID ) ) != NULL);

  }
  return( bReturn );
}

//
// Clear the current default button status from the default push button
//
void CDefineSS::ResetDefaultButton( HWND hDlg )
{
  DWORD dwResult;

  if( hDlg ) {

    // Get the current default push button.
    dwResult = (DWORD) SendMessage( hDlg, DM_GETDEFID, 0, 0L );

    // Reset the current default push button to a regular button.
    if( HIWORD(dwResult) == DC_HASDEFID )
      SendDlgItemMessage( hDlg, LOWORD(dwResult), BM_SETSTYLE, (WPARAM) BS_PUSHBUTTON, (LPARAM) TRUE );

  }

  return;
}