/*
* Copyright (c) 2003-2009 Rony Shapiro <ronys@users.sourceforge.net>.
* All rights reserved. Use of the code is allowed under the
* Artistic License 2.0 terms, as specified in the LICENSE file
* distributed with this code, or available from
* http://www.opensource.org/licenses/artistic-license-2.0.php
*/

#include "../stdafx.h"
#include "PWFilterLC.h"
#include "PWFiltersDlg.h"

#include "SetHistoryFiltersDlg.h"
#include "SetPolicyFiltersDlg.h"
#include "corelib/PWSFilters.h"

#include "../resource3.h"
#include "corelib/corelib.h"

#include <algorithm>

using namespace std;

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

CPWFilterLC::CPWFilterLC()
  : m_pPWF(NULL), m_iType(0), m_pfilters(NULL), m_bInitDone(false),
   m_fwidth(-1), m_lwidth(-1), m_rowheight(-1),
   m_bSetFieldActive(false), m_bSetLogicActive(false),
   m_pComboBox(NULL), m_iItem(-1), m_numfilters(0), m_pFont(NULL),
   m_pchTip(NULL), m_pwchTip(NULL),
  // Following 4 variables only used if "m_iType == DFTYPE_MAIN"
  m_bPWHIST_Set(false), m_bPOLICY_Set(false),
  m_GoodHistory(false), m_GoodPolicy(false)
{
  m_crGrayText   = ::GetSysColor(COLOR_GRAYTEXT);
  m_crWindow     = ::GetSysColor(COLOR_WINDOW);
  m_crWindowText = ::GetSysColor(COLOR_WINDOWTEXT);
  m_crButtonFace = ::GetSysColor(COLOR_BTNFACE);
  m_crRedText    = RGB(168, 0, 0);
}

CPWFilterLC::~CPWFilterLC()
{
  // Do not delete filters, as they need to passed back to the caller

  m_pCheckImageList->DeleteImageList();
  delete m_pCheckImageList;

  m_pImageList->DeleteImageList();
  delete m_pImageList;

  delete m_pFont;
  delete m_pComboBox;
  delete m_pchTip;
  delete m_pwchTip;
}

BEGIN_MESSAGE_MAP(CPWFilterLC, CListCtrl)
  //{{AFX_MSG_MAP(CPWFilterLC)
  ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTW, 0, 0xFFFF, OnToolTipText)
  ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTA, 0, 0xFFFF, OnToolTipText)
  ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, OnCustomDraw)
  ON_WM_LBUTTONDOWN()
  //}}AFX_MSG_MAP
END_MESSAGE_MAP()

void CPWFilterLC::Init(CWnd * pParent, st_filters *pfilters, const int &filtertype)
{
  m_pPWF = static_cast<CPWFiltersDlg *>(pParent);
  m_iType = filtertype;
  m_pfilters = pfilters;
  EnableToolTips(TRUE);

  const COLORREF crTransparent = RGB(192, 192, 192);
  CBitmap bitmap;
  BITMAP bm;
  bitmap.LoadBitmap(IDB_CHECKED);
  bitmap.GetBitmap(&bm); // should be 13 x 13

  m_pCheckImageList = new CImageList;
  BOOL status = m_pCheckImageList->Create(bm.bmWidth, bm.bmHeight,
                                     ILC_MASK | ILC_COLOR, 2, 0);
  ASSERT(status != 0);

  m_pCheckImageList->Add(&bitmap, crTransparent);
  bitmap.DeleteObject();
  bitmap.LoadBitmap(IDB_UNCHECKED);
  m_pCheckImageList->Add(&bitmap, crTransparent);
  bitmap.DeleteObject();

  // Set CHeaderDtrl control ID
  m_pHeaderCtrl = GetHeaderCtrl();
  m_pHeaderCtrl->SetDlgCtrlID(IDC_FILTERLC_HEADER);
  CString cs_text;

  cs_text = _T(" # ");
  InsertColumn(FLC_FILTER_NUMBER, cs_text);
  cs_text = _T(" ? ");
  InsertColumn(FLC_ENABLE_BUTTON, cs_text);
  cs_text = _T(" + ");
  InsertColumn(FLC_ADD_BUTTON, cs_text);
  cs_text = _T(" - ");
  InsertColumn(FLC_REM_BUTTON, cs_text);
  cs_text.LoadString(IDS_AND_OR);
  InsertColumn(FLC_LGC_COMBOBOX, cs_text);
  cs_text.LoadString(IDS_SELECTFIELD);
  InsertColumn(FLC_FLD_COMBOBOX, cs_text);
  cs_text.LoadString(IDS_CRITERIA_DESC);
  InsertColumn(FLC_CRITERIA_TEXT, cs_text);

  for (int i = 0; i <= FLC_CRITERIA_TEXT; i++) {
    SetColumnWidth(i, LVSCW_AUTOSIZE_USEHEADER);
  }

  // Make +/- columns same size ('+' is bigger than '-')
  SetColumnWidth(FLC_REM_BUTTON, GetColumnWidth(FLC_ADD_BUTTON));

  // Make checkbox, and button headers centered (and so their contents in the ListCtrl)
  LVCOLUMN lvc;
  lvc.mask = LVCF_FMT;
  lvc.fmt = LVCFMT_CENTER;
  SetColumn(FLC_FILTER_NUMBER, &lvc);
  SetColumn(FLC_ENABLE_BUTTON, &lvc);
  SetColumn(FLC_ADD_BUTTON, &lvc);
  SetColumn(FLC_REM_BUTTON, &lvc);

  switch (m_iType) {
    case DFTYPE_MAIN:
      // Set maximum number of filters per type and their own Control IDs
      m_FLD_ComboID = IDC_MFILTER_FLD_COMBOBOX;
      m_LGC_ComboID = IDC_MFILTER_LGC_COMBOBOX;
      m_pvfdata = &(m_pfilters->vMfldata);
      m_pnumactive = &(m_pfilters->num_Mactive);
      m_GoodHistory = m_pfilters->num_Hactive > 0;
      m_GoodPolicy = m_pfilters->num_Pactive > 0;
      break;
    case DFTYPE_PWHISTORY:
      // Set maximum number of filters per type and their own Control IDs
      m_FLD_ComboID = IDC_HFILTER_FLD_COMBOBOX;
      m_LGC_ComboID = IDC_HFILTER_LGC_COMBOBOX;
      m_pvfdata = &(m_pfilters->vHfldata);
      m_pnumactive = &(m_pfilters->num_Hactive);
      break;
    case DFTYPE_PWPOLICY:
      // Set maximum number of filters per type and their own Control IDs
      m_FLD_ComboID = IDC_PFILTER_FLD_COMBOBOX;
      m_LGC_ComboID = IDC_PFILTER_LGC_COMBOBOX;
      m_pvfdata = &(m_pfilters->vPfldata);
      m_pnumactive = &(m_pfilters->num_Pactive);
      break;
    default:
      ASSERT(0);
  }

  SetUpComboBoxData();

  // Add dummy item to set column widths
  InsertItem(0, _T("."));  // FLC_ENABLE_BUTTON
  SetItemText(0, FLC_ADD_BUTTON, _T("."));
  SetItemText(0, FLC_REM_BUTTON, _T("."));
  SetItemText(0, FLC_LGC_COMBOBOX, _T("."));
  SetItemText(0, FLC_FLD_COMBOBOX, _T("."));
  SetItemText(0, FLC_CRITERIA_TEXT, _T("."));
  SetItemData(0, FLC_CRITERIA_REDTXT | FLC_FLD_CBX_ENABLED);
  m_iItem = 0;

  st_FilterRow newfilter;
  m_pvfdata->push_back(newfilter);

  // Set up widths of columns with comboboxes - once for each combo
  DrawComboBox(FLC_FLD_COMBOBOX, -1);
  if (m_pComboBox) {
    m_pComboBox->DestroyWindow();
    delete m_pComboBox;
    vWCFcbx_data.clear();
    m_pComboBox = NULL;
  }
  DrawComboBox(FLC_LGC_COMBOBOX, -1);
  if (m_pComboBox) {
    m_pComboBox->DestroyWindow();
    delete m_pComboBox;
    vWCFcbx_data.clear();
    m_pComboBox = NULL;
  }

  // Remove dummy item
  DeleteItem(0);
  m_pvfdata->pop_back();

  // If existing filters, set them up in the dialog
  DeleteFilters();
  (*m_pnumactive) = 0;
  int i(0);
  
  DWORD_PTR dwData;
  vFilterRows::iterator Flt_iter;
  for (Flt_iter = m_pvfdata->begin(); Flt_iter != m_pvfdata->end(); Flt_iter++) {
    st_FilterRow &st_fldata = *Flt_iter;

    AddFilter_Controls();

    if (st_fldata.bFilterActive)
      (*m_pnumactive)++;

    dwData = 0;
    if (st_fldata.bFilterActive)
      dwData |= FLC_FILTER_ENABLED;
    else
      dwData &= ~FLC_FILTER_ENABLED;

    if (st_fldata.ftype == FT_PWHIST)
      m_bPWHIST_Set = true;
    if (st_fldata.ftype == FT_POLICY)
      m_bPOLICY_Set = true;

    if (st_fldata.ftype != FT_INVALID) {
      dwData |= FLC_FLD_CBX_SET | FLC_FLD_CBX_ENABLED;
      SetField(i);
      SetLogic(i);
      vlast_ft[i] = st_fldata.ftype;
      vlast_mt[i] = st_fldata.mtype;
      vcbxChanged[i] = false;
      std::vector<st_Fcbxdata>::iterator Fcbxdata_iter;
      Fcbxdata_iter = std::find_if(vFcbx_data.begin(), vFcbx_data.end(), 
                                   equal_ftype(st_fldata.ftype));
      if (Fcbxdata_iter != vFcbx_data.end())
        SetItemText(i, FLC_FLD_COMBOBOX, (*Fcbxdata_iter).cs_text);

      CString cs_criteria;
      if (st_fldata.ftype != FT_PWHIST && st_fldata.ftype != FT_POLICY &&
          st_fldata.rule == PWSMatch::MR_INVALID) {
        cs_criteria.LoadString(IDS_NOTDEFINED);
        dwData |= FLC_CRITERIA_REDTXT;
        vCriteriaSet[i] = false;
        st_fldata.bFilterComplete = false;
      } else {
        cs_criteria = PWSFilters::GetFilterDescription(st_fldata).c_str();
        dwData &= ~FLC_CRITERIA_REDTXT;
        vCriteriaSet[i] = true;
        st_fldata.bFilterComplete = true;
      }
      SetItemText(i, FLC_CRITERIA_TEXT, cs_criteria);
      SetColumnWidth(FLC_CRITERIA_TEXT, LVSCW_AUTOSIZE_USEHEADER);
    }

    if (i == 0)
      cs_text = _T("");
    else
      cs_text = vLcbx_data[st_fldata.ltype == LC_AND ? 0 : 1].cs_text;
 
    SetItemText(i, FLC_LGC_COMBOBOX, cs_text);
    SetItemData(i, dwData);

    i++;
  }

  // Always show one filter!
  if (m_pvfdata->size() == 0) {
    AddFilter_Controls();
    SetItemText(i, FLC_LGC_COMBOBOX, _T(""));

    // Add a new filter to vector
    st_FilterRow newfilter;
    m_pvfdata->push_back(newfilter);
    (*m_pnumactive)++;
  }

  ResetAndOr();

  if (m_iType == DFTYPE_MAIN)
    m_pPWF->EnableDisableApply();

  m_bInitDone = true;
}

BOOL CPWFilterLC::OnCommand(WPARAM wParam, LPARAM lParam)
{
  // Since we are dynamically adding controls, we need to handle their
  // messages (i.e. user clicking on them) ourselves and not via "ON_BN_CLICKED"
  WORD wNotify  = HIWORD(wParam); // notification message
  UINT nID      = (UINT)LOWORD(wParam); // control identifier

  if (nID == m_FLD_ComboID || nID == m_LGC_ComboID) {
    switch (wNotify) {
      case CBN_SELENDOK:
        if (nID == m_FLD_ComboID) {
          m_bSetFieldActive = true;
          SetField(m_iItem);
        } else {
          m_bSetLogicActive = true;
          SetLogic(m_iItem);
        }
        return TRUE;
      case CBN_CLOSEUP:
      case CBN_KILLFOCUS:
        if (!m_bSetFieldActive && !m_bSetLogicActive)
          CloseKillCombo();
        return TRUE;
      case CBN_DROPDOWN:
        if (!m_bSetFieldActive && !m_bSetLogicActive)
          DropDownCombo(nID);
        return TRUE;
      case CBN_SELENDCANCEL:
        if (nID == m_FLD_ComboID && !m_bSetFieldActive)
          CancelField(m_iItem);
        else if (nID == m_LGC_ComboID && !m_bSetLogicActive)
          CancelLogic(m_iItem);
        return TRUE;
    }
  }
  return CListCtrl::OnCommand(wParam, lParam);
}

int CPWFilterLC::AddFilter()
{
  int newID = AddFilter_Controls();

  // Check one was added
  if (newID < 0)
    return (-1);

  // Add a new filter to vector
  st_FilterRow newfilter;
  if (newID == 0)
    newfilter.ltype = LC_INVALID;
  else
    newfilter.ltype = LC_AND;

  m_pvfdata->push_back(newfilter);
  // Increase active number
  (*m_pnumactive)++;

  // Only if adding in the middle do we need to move things up and
  // reset the 'new' one
  if (m_iItem != newID - 1) {
    // Now move up any after us (only states - not actual pointers)
    // and reset the one immediately after use
    MoveUp(m_iItem);
  }

  ResetAndOr();

  m_pPWF->UpdateStatusText();

  return newID;
}

void CPWFilterLC::RemoveFilter()
{
  ASSERT(m_iItem >= 0);

  // Main Filters dialog need to handle History/Policy before calling this
  if (m_iType == DFTYPE_MAIN) {
    st_FilterRow &st_fldata = m_pvfdata->at(m_iItem);
    if (m_bPWHIST_Set && st_fldata.ftype == FT_PWHIST) {
      // We are deleting the History so put option back in other Comboboxes
      m_bPWHIST_Set = false;
    }
    else if (m_bPOLICY_Set && st_fldata.ftype == FT_POLICY) {
      // We are deleting the Policy so put option back in other Comboboxes
      m_bPOLICY_Set = false;
    }
  }

  if (m_iItem == 0 && m_pvfdata->size() == 1) {
    // Don't remove the one and only filter - just reset it
    ResetFilter(0);

    // Set active counter & number of filters
    (*m_pnumactive) = 1;
    m_numfilters = 1;
  }
  else if ((m_iItem + 1) == (int)m_pvfdata->size()) {
    // Last entry - is relatively easy!
    // Decrement active filters if necessary
    if (m_pvfdata->at(m_iItem).bFilterActive)
      (*m_pnumactive)--;

    // Decrement number of filters
    m_numfilters--;

    // Remove Control windows, controls and filterdata
    RemoveLast();
  } else {
    // Now remove one that is "not the only one" nor "the last one".
    if (m_pvfdata->at(m_iItem).bFilterActive)
      (*m_pnumactive)--;

    // Move all the information down one and then delete the last
    // Decrement active filters if necessary
    MoveDown();

    // Decrement number of filters
    m_numfilters--;

    // Now remove the end one
    RemoveLast();
  }
  ResetAndOr();

  m_pPWF->UpdateStatusText();
}

void CPWFilterLC::ResetFilter(const int num)
{
  // Mustn't remove first and ONLY one - just reinitialise the controls.
  // Enable filter, disable GetCriterion button, set AND, unset Combobox selection
  DWORD_PTR dwData;
  dwData = FLC_CRITERIA_REDTXT | FLC_FILTER_ENABLED;
  SetItemData(num, dwData);
  CString cs_text(MAKEINTRESOURCE(IDS_PICKFIELD));
  SetItemText(num, FLC_FLD_COMBOBOX, cs_text);

  vlast_ft[num] = FT_INVALID;
  vlast_mt[num] = PWSMatch::MT_INVALID;
  vcbxChanged[num] = true;
  vCriteriaSet[num] = false;
  vAddPresent[num] = false;

  // Make sure entry is pristine
  m_pvfdata->at(num).Empty();
  if (num == 0) {
    m_pvfdata->at(num).ltype = LC_INVALID;
    SetItemText(num, FLC_LGC_COMBOBOX, _T(""));
  } else {
    m_pvfdata->at(num).ltype = LC_AND;
    SetItemText(num, FLC_LGC_COMBOBOX, vLcbx_data[0].cs_text);
  }

  // Update criterion text and window text
  CString cs_criteria(MAKEINTRESOURCE(IDS_NOTDEFINED));
  SetItemText(num, FLC_CRITERIA_TEXT, cs_criteria);
  SetColumnWidth(FLC_CRITERIA_TEXT, LVSCW_AUTOSIZE_USEHEADER);
}

void CPWFilterLC::RemoveLast()
{
  int num = GetItemCount();
  ASSERT(num > 0);

  vlast_ft.pop_back();
  vlast_mt.pop_back();
  vcbxChanged.pop_back();
  vCriteriaSet.pop_back();
  vAddPresent.pop_back();

  // Remove filter
  m_pvfdata->pop_back();

  // Remove ListCtrl entry
  DeleteItem(num - 1);
}

int CPWFilterLC::AddFilter_Controls()
{
  // First add filter at the end of current filters
  CString cs_text;
  CRect rect;

  int newID = m_numfilters;

  cs_text.Format(_T("%d"), newID + 1);
  InsertItem(newID, cs_text);  // FLC_FILTER_NUMBER
  SetItemText(newID, FLC_ENABLE_BUTTON, _T(""));
  SetItemText(newID, FLC_ADD_BUTTON, _T("+"));
  SetItemText(newID, FLC_REM_BUTTON, _T("-"));
  cs_text.LoadString(IDSC_AND);
  SetItemText(newID, FLC_LGC_COMBOBOX, cs_text);
  cs_text.LoadString(IDS_PICKFIELD);
  SetItemText(newID, FLC_FLD_COMBOBOX, cs_text);

  // set status in cell
  cs_text.LoadString(IDS_NOTDEFINED);
  SetItemText(newID, FLC_CRITERIA_TEXT, cs_text);
  SetColumnWidth(FLC_CRITERIA_TEXT, LVSCW_AUTOSIZE_USEHEADER);
  SetItemData(newID, FLC_CRITERIA_REDTXT | FLC_FILTER_ENABLED);

  vlast_ft.push_back(FT_INVALID);
  vlast_mt.push_back(PWSMatch::MT_INVALID);
  vcbxChanged.push_back(true);
  vCriteriaSet.push_back(false);
  vAddPresent.push_back(false);

  // Increase total number
  m_numfilters++;

  return newID;
}

void CPWFilterLC::MoveDown()
{
  // Move  "states" down. i.e. deleting '1' means
  //  '0' unchanged; '2'->'1'; '3'->'2'; '4'->'3' etc.
  DWORD_PTR dwData;
  CString cs_text;

  for (int i = m_iItem; i < m_numfilters - 1; i++) {
    cs_text.Format(_T("%d"), i + 1);
    SetItemText(i, FLC_FILTER_NUMBER, cs_text);

    cs_text = GetItemText(i + 1, FLC_FLD_COMBOBOX);
    SetItemText(i, FLC_FLD_COMBOBOX, cs_text);

    cs_text = GetItemText(i + 1, FLC_LGC_COMBOBOX);
    SetItemText(i, FLC_LGC_COMBOBOX, cs_text);

    cs_text = GetItemText(i + 1, FLC_CRITERIA_TEXT);
    SetItemText(i, FLC_CRITERIA_TEXT, cs_text);
    SetColumnWidth(FLC_CRITERIA_TEXT, LVSCW_AUTOSIZE_USEHEADER);

    // Data field contains flags for enabled state and text colour
    dwData = GetItemData(i + 1);
    SetItemData(i, dwData);

    vlast_ft[i] = vlast_ft[i + 1];
    vlast_mt[i] = vlast_mt[i + 1];
    vcbxChanged[i] = vcbxChanged[i + 1];
    vCriteriaSet[i] = vCriteriaSet[i + 1];
    vAddPresent[i] = vAddPresent[i + 1];
    m_pvfdata->at(i) = m_pvfdata->at(i + 1);
  }
}

void CPWFilterLC::MoveUp(const int nAfter)
{
  // Move up. i.e. adding after '1' means
  //  '0' & '1' unchanged; '5'->'6'; '4'->'5'; '3'->'4';
  //  '2' then reset.

  // Move "states" up one; can't change pointers
  CString cs_text;
  DWORD_PTR dwData;

  for (int i = m_numfilters - 2; i > nAfter; i--) {
    cs_text.Format(_T("%d"), i + 2);
    SetItemText(i + 1, FLC_FILTER_NUMBER, cs_text);

    cs_text = GetItemText(i, FLC_FLD_COMBOBOX);
    SetItemText(i + 1, FLC_FLD_COMBOBOX, cs_text);

    cs_text = GetItemText(i + 1, FLC_LGC_COMBOBOX);
    SetItemText(i, FLC_LGC_COMBOBOX, cs_text);

    cs_text = GetItemText(i, FLC_CRITERIA_TEXT);
    SetItemText(i + 1, FLC_CRITERIA_TEXT, cs_text);
    SetColumnWidth(FLC_CRITERIA_TEXT, LVSCW_AUTOSIZE_USEHEADER);

    // Data field contains flags for enabled state and text colour
    dwData = GetItemData(i);
    SetItemData(i + 1, dwData);

    vlast_ft[i + 1] = vlast_ft[i];
    vlast_mt[i + 1] = vlast_mt[i];
    vcbxChanged[i + 1] = vcbxChanged[i];
    vCriteriaSet[i + 1] = vCriteriaSet[i];
    vAddPresent[i + 1] = vAddPresent[i];
    m_pvfdata->at(i + 1) = m_pvfdata->at(i);
  }

  // Now reset the 'new' one.
  ResetFilter(nAfter + 1);
}

void CPWFilterLC::ResetAndOr()
{
  // Ensure And/Or enabled
  DWORD_PTR dwData;
  for (int i = 1; i < GetItemCount(); i++) {
    dwData = GetItemData(i);
    dwData |= FLC_LGC_CBX_ENABLED;
    SetItemData(i, dwData);
  }

  // Except for the first one
  SetItemText(0, FLC_LGC_COMBOBOX, _T(""));
  dwData = GetItemData(0);
  dwData &= ~FLC_LGC_CBX_ENABLED;
  SetItemData(0, dwData);
}

void CPWFilterLC::DeleteFilters()
{
  if (vlast_ft.size() == 0)
    return;

  // Clear the other vectors
  vlast_ft.clear();
  vlast_mt.clear();
  vcbxChanged.clear();
  vCriteriaSet.clear();
  vAddPresent.clear();

  // Clear filter data
  m_pvfdata->clear();

  // Zero counters
  *m_pnumactive = 0;
  m_numfilters = 0;
}

FieldType CPWFilterLC::EnableCriteria()
{
  // User clicked "Enable/Disable" filter checkbox
  ASSERT(m_iItem >= 0);

  // Get offset into vector of controls
  st_FilterRow &st_fldata = m_pvfdata->at(m_iItem);

  // Did they enable or disable the filter?
  bool bIsChecked = !st_fldata.bFilterActive;
  st_fldata.bFilterActive = bIsChecked;

  // Check, when activating me, if there are any active filters before us
  // If not, we won't show the logic enable the And/Or logic value
  // This is a real pain!
  int j(0);
  for (int i = 0; i < (int)m_pvfdata->size(); i++) {
    if (m_pvfdata->at(i).bFilterActive) {
      DWORD_PTR dwData2 = GetItemData(i);
      dwData2 &= ~FLC_LGC_CBX_ENABLED;
      SetItemData(i, dwData2);
      j = i;
      break;
    }
  }
  for (int i = j + 1; i < (int)m_pvfdata->size(); i++) {
    if (m_pvfdata->at(i).bFilterActive) {
      DWORD_PTR dwData2 = GetItemData(i);
      dwData2 |= FLC_LGC_CBX_ENABLED;
      SetItemData(i, dwData2);
    }
  }

  DWORD_PTR dwData = GetItemData(m_iItem);
  FieldType ft = st_fldata.ftype;

  // If user (had) selected an entry, get its field type
  // If not, don't let them try and select a criterion
  if ((ft != FT_INVALID) && bIsChecked)
    dwData |= FLC_FLD_CBX_ENABLED;
  else
    dwData &= ~FLC_FLD_CBX_ENABLED;

  // Set state of this filter and adjust active count
  if (bIsChecked) {
    // Now active
    dwData |= FLC_FILTER_ENABLED;
    st_fldata.bFilterActive = true;
    (*m_pnumactive)++;
  } else {
    // Now inactive
    dwData &= ~FLC_FILTER_ENABLED;
    st_fldata.bFilterActive = false;
    (*m_pnumactive)--;
  }

  SetItemData(m_iItem, dwData);

  CString cs_text(MAKEINTRESOURCE(IDS_PICKFIELD));
  if (m_iType == DFTYPE_MAIN) {
    if (st_fldata.bFilterActive) {
      if (ft == FT_PWHIST) {
        if (!m_bPWHIST_Set)
          m_bPWHIST_Set = true;
        else {
          SetItemText(m_iItem, FLC_FLD_COMBOBOX, cs_text);
          st_fldata.ftype = FT_INVALID;
        }
      }
      if (ft == FT_POLICY) {
        if (!m_bPOLICY_Set)
          m_bPOLICY_Set = true;
        else {
          SetItemText(m_iItem, FLC_FLD_COMBOBOX, cs_text);
          st_fldata.ftype = FT_INVALID;
        }
      }
    } else {
      if (m_bPWHIST_Set && ft == FT_PWHIST)
        m_bPWHIST_Set = false;
      if (m_bPOLICY_Set && ft == FT_POLICY)
        m_bPOLICY_Set = false;
    }
    m_pPWF->EnableDisableApply();
  }

  // Update dialog window text
  m_pPWF->UpdateStatusText();

  return ft;
}

bool CPWFilterLC::SetField(const int iItem)
{
  // User has selected field
  bool retval(false);
  // Set focus to main window in case user does page up/down next
  // so that it changes the scoll bar not the value in this
  // ComboBox
  GetParent()->SetFocus();

  // Get offset into vector of controls
  st_FilterRow &st_fldata = m_pvfdata->at(iItem);

  FieldType ft(FT_INVALID);

  // m_pComboBox is NULL during inital setup
  if (m_pComboBox) {
    int iSelect = m_pComboBox->GetCurSel();
    if (iSelect != CB_ERR) {
      ft = (FieldType)m_pComboBox->GetItemData(iSelect);
    }
    st_fldata.ftype = ft;
  } else {
    ft = st_fldata.ftype;
  }

  CString cs_text(MAKEINTRESOURCE(IDS_PICKFIELD));
  if (ft != FT_INVALID) {
    std::vector<st_Fcbxdata>::iterator Fcbxdata_iter;
    Fcbxdata_iter = std::find_if(vWCFcbx_data.begin(), vWCFcbx_data.end(), equal_ftype(ft));
    if (Fcbxdata_iter != vWCFcbx_data.end())
      cs_text = (*Fcbxdata_iter).cs_text;
  }

  SetItemText(iItem, FLC_FLD_COMBOBOX, cs_text);

  DWORD_PTR dwData = GetItemData(iItem);
  if (ft == FT_INVALID) {
    // Oh no they haven't!
    dwData &= ~FLC_FLD_CBX_SET;
    SetItemData(iItem, dwData);
    goto delete_combo;
  } else {
    dwData |= (FLC_FLD_CBX_ENABLED | FLC_FLD_CBX_SET);
  }

  // Get related fieldtype
  PWSMatch::MatchType mt(PWSMatch::MT_INVALID);

  // Default - do not add 'is present'/'is not present' to the match dialog
  bool bAddPresent(false);

  // Now get associated match type
  switch (m_iType) {
    case DFTYPE_MAIN:
      switch (ft) {
        case FT_GROUPTITLE:
        case FT_TITLE:
          // Title MUST be present - no need to add that check
          mt = PWSMatch::MT_STRING;
          break;
        case FT_PASSWORD:
          // Password MUST be present - no need to add that check
          mt = PWSMatch::MT_PASSWORD;
          break;
        case FT_GROUP:
        case FT_USER:
        case FT_NOTES:
        case FT_URL:
        case FT_AUTOTYPE:
          bAddPresent = true;
          mt = PWSMatch::MT_STRING;
          break;
        case FT_CTIME:
        case FT_PMTIME:
        case FT_ATIME:
        case FT_XTIME:
        case FT_RMTIME:
          bAddPresent = true;
          mt = PWSMatch::MT_DATE;
          break;
        case FT_PWHIST:
          // 'Add Present' doesn't make sense here - see sub-dialog
          mt = PWSMatch::MT_PWHIST;
          break;
        case FT_POLICY:
          // 'Add Present' doesn't make sense here - see sub-dialog
          mt = PWSMatch::MT_POLICY;
          break;
        case FT_XTIME_INT:
          bAddPresent = true;
          mt = PWSMatch::MT_INTEGER;
          break;
        case FT_UNKNOWNFIELDS:
          // 'Add Present' not needed - bool match
          m_fbool.m_bt = CFilterBoolDlg::BT_PRESENT;
          mt = PWSMatch::MT_BOOL;
          break;
        case FT_ENTRYTYPE:
          // Entrytype is an entry attribute and MUST be present
          //  - no need to add that check
          mt = PWSMatch::MT_ENTRYTYPE;
          break;
        default:
          ASSERT(0);
      }
      break;
    case DFTYPE_PWHISTORY:
      switch (ft) {
        case HT_PRESENT:
          // 'Add Present' not needed - bool match
          m_fbool.m_bt = CFilterBoolDlg::BT_PRESENT;
          mt = PWSMatch::MT_BOOL;
          break;
        case HT_ACTIVE:
          // 'Add Present' not needed - bool match
          m_fbool.m_bt = CFilterBoolDlg::BT_ACTIVE;
          mt = PWSMatch::MT_BOOL;
          break;
        case HT_NUM:
        case HT_MAX:
          // 'Add Present' doesn't make sense here
          mt = PWSMatch::MT_INTEGER;
          break;
        case HT_CHANGEDATE:
          // 'Add Present' doesn't make sense here
          mt = PWSMatch::MT_DATE;
          break;
        case HT_PASSWORDS:
          // 'Add Present' doesn't make sense here
          mt = PWSMatch::MT_PASSWORD;
          break;
        default:
          ASSERT(0);
      }
      break;
    case DFTYPE_PWPOLICY:
      switch (ft) {
        case PT_PRESENT:
          // 'Add Present' not needed - bool match
          m_fbool.m_bt = CFilterBoolDlg::BT_PRESENT;
          mt = PWSMatch::MT_BOOL;
          break;
        case PT_EASYVISION:
        case PT_PRONOUNCEABLE:
        case PT_HEXADECIMAL:
          // 'Add Present' not needed - bool match
          m_fbool.m_bt = CFilterBoolDlg::BT_SET;
          mt = PWSMatch::MT_BOOL;
          break;
        case PT_LENGTH:
        case PT_LOWERCASE:
        case PT_UPPERCASE:
        case PT_DIGITS:
        case PT_SYMBOLS:
          // 'Add Present' doesn't make sense here
          mt = PWSMatch::MT_INTEGER;
          break;
        default:
          ASSERT(0);
      }
      break;
    default:
      ASSERT(0);
  }

  // Save if user has just re-selected previous selection - i.e. no change
  if (vlast_ft[iItem] != ft || vlast_mt[iItem] != mt) {
    vcbxChanged[iItem] = true;
    vCriteriaSet[iItem] = false;
    st_fldata.bFilterComplete = false;
  } else
    vcbxChanged[iItem] = false;

  vAddPresent[iItem] = bAddPresent;
  if (m_iType == DFTYPE_MAIN) {
    if (vlast_ft[iItem] == FT_PWHIST && ft != FT_PWHIST)
      m_bPWHIST_Set = false;
    else if (ft == FT_PWHIST)
      m_bPWHIST_Set = true;

    if (vlast_ft[iItem] == FT_POLICY && ft != FT_POLICY)
      m_bPOLICY_Set = false;
    else if (ft == FT_POLICY)
      m_bPOLICY_Set = true;
  }

  // Now we can update the last selected field
  vlast_ft[iItem] = ft;

  // Save last match type now
  vlast_mt[iItem] = mt;

  if (mt == PWSMatch::MT_INVALID) {
    dwData &= ~FLC_FLD_CBX_ENABLED;
    vCriteriaSet[iItem] = false;
    st_fldata.bFilterComplete = false;
  }

  // If criterion not set, update static text message to user
  if (vCriteriaSet[iItem] == false) {
    CString cs_criteria(MAKEINTRESOURCE(IDS_NOTDEFINED));
    dwData |= FLC_CRITERIA_REDTXT;
    SetItemText(iItem, FLC_CRITERIA_TEXT, cs_criteria);
    SetColumnWidth(FLC_CRITERIA_TEXT, LVSCW_AUTOSIZE_USEHEADER);
  }

  if (mt == PWSMatch::MT_INVALID) {
    SetItemData(iItem, dwData);
    goto delete_combo;
  }

  dwData |= FLC_FLD_CBX_ENABLED;
  SetItemData(iItem, dwData);
  st_fldata.mtype = mt;
  st_fldata.ftype = ft;
  retval = true;

delete_combo:
  if (m_pComboBox) {
    m_pComboBox->DestroyWindow();
    delete m_pComboBox;
    vWCFcbx_data.clear();
    m_pComboBox = NULL;
  }
  m_bSetFieldActive = false;
  return retval;
}

void CPWFilterLC::CancelField(const int iItem)
{
  // User has selected field

  // Set focus to main window in case user does page up/down next
  // so that it changes the scoll bar not the value in this
  // ComboBox
  GetParent()->SetFocus();

  // Get offset into vector of controls
  st_FilterRow &st_fldata = m_pvfdata->at(iItem);

  FieldType ft(st_fldata.ftype);

  CString cs_text(MAKEINTRESOURCE(IDS_PICKFIELD));
  if (ft != FT_INVALID) {
    std::vector<st_Fcbxdata>::iterator Fcbxdata_iter;
    Fcbxdata_iter = std::find_if(vWCFcbx_data.begin(), vWCFcbx_data.end(), equal_ftype(ft));
    if (Fcbxdata_iter != vWCFcbx_data.end())
      cs_text = (*Fcbxdata_iter).cs_text;
  }

  SetItemText(iItem, FLC_FLD_COMBOBOX, cs_text);

  // m_pComboBox is NULL during inital setup
  if (m_pComboBox) {
    m_pComboBox->DestroyWindow();
    delete m_pComboBox;
    vWCFcbx_data.clear();
    m_pComboBox = NULL;
  }
  return;
}

void CPWFilterLC::SetLogic(const int iItem)
{
  CString cs_text(_T(""));
  if (iItem == 0) {
    SetItemText(iItem, FLC_LGC_COMBOBOX, cs_text);
    goto delete_combo;
  }

  // Get offset into vector of controls
  st_FilterRow &st_fldata = m_pvfdata->at(iItem);

  LogicConnect lt(st_fldata.ltype);
  // m_pComboBox is NULL during inital setup
  if (m_pComboBox) {
    int iSelect = m_pComboBox->GetCurSel();
    if (iSelect != CB_ERR) {
      lt = (LogicConnect)m_pComboBox->GetItemData(iSelect);
    }
    m_pvfdata->at(iItem).ltype = lt;
  }

  cs_text = vLcbx_data[0].cs_text;
  if (lt != LC_INVALID) {
    std::vector<st_Lcbxdata>::iterator Lcbxdata_iter;
    Lcbxdata_iter = std::find_if(vLcbx_data.begin(), vLcbx_data.end(), equal_ltype(lt));
    if (Lcbxdata_iter != vLcbx_data.end())
      cs_text = (*Lcbxdata_iter).cs_text;
  }

  SetItemText(iItem, FLC_LGC_COMBOBOX, cs_text);

delete_combo:
  if (m_pComboBox) {
    m_pComboBox->DestroyWindow();
    delete m_pComboBox;
    vWCFcbx_data.clear();
    m_pComboBox = NULL;
  }
  m_bSetLogicActive = false;
}

void CPWFilterLC::CancelLogic(const int iItem)
{
  CString cs_text(_T(""));
  // Get offset into vector of controls
  if (iItem == 0) {
    SetItemText(iItem, FLC_LGC_COMBOBOX, cs_text);
    goto delete_combo;
  }

  st_FilterRow &st_fldata = m_pvfdata->at(iItem);

  LogicConnect lt(st_fldata.ltype);

  cs_text = vLcbx_data[0].cs_text;
  if (lt != LC_INVALID) {
    std::vector<st_Lcbxdata>::iterator Lcbxdata_iter;
    Lcbxdata_iter = std::find_if(vLcbx_data.begin(), vLcbx_data.end(), equal_ltype(lt));
    if (Lcbxdata_iter != vLcbx_data.end())
      cs_text = (*Lcbxdata_iter).cs_text;
  }

  SetItemText(iItem, FLC_LGC_COMBOBOX, cs_text);

delete_combo:
  // m_pComboBox is NULL during inital setup
  if (m_pComboBox) {
    m_pComboBox->DestroyWindow();
    delete m_pComboBox;
    vWCFcbx_data.clear();
    m_pComboBox = NULL;
  }
}

void CPWFilterLC::CloseKillCombo()
{
  // Remove combo
  if (m_pComboBox) {
    m_pComboBox->DestroyWindow();
    delete m_pComboBox;
    vWCFcbx_data.clear();
    m_pComboBox = NULL;
  }
}

void CPWFilterLC::DropDownCombo(const UINT nID)
{
  CString cs_text;
  int index(-1);

  if (nID == m_FLD_ComboID) {
    FieldType ft = m_pvfdata->at(m_iItem).ftype;
    cs_text.LoadString(IDS_PICKFIELD);
    if (ft != FT_INVALID) {
      std::vector<st_Fcbxdata>::iterator Fcbxdata_iter;
      Fcbxdata_iter = std::find_if(vWCFcbx_data.begin(), vWCFcbx_data.end(), equal_ftype(ft));
      if (Fcbxdata_iter != vWCFcbx_data.end()) {
        cs_text = (*Fcbxdata_iter).cs_text;
        index = Fcbxdata_iter - vWCFcbx_data.begin();
      }
    }
    SetItemText(m_iItem, FLC_FLD_COMBOBOX, cs_text);
  } else {
    LogicConnect lt = m_pvfdata->at(m_iItem).ltype;
    index = (lt == LC_AND) ? 0 : 1;
    cs_text.LoadString(lt == LC_AND ? IDSC_AND : IDSC_OR);
    SetItemText(m_iItem, FLC_LGC_COMBOBOX, cs_text);
  }
  m_pComboBox->SetCurSel(index);
}

bool CPWFilterLC::GetCriterion()
{
  // User has already enabled the filter and selected the field type
  // Now get the criteron
  ASSERT(m_iItem >= 0);

  // Get offset into vector of controls
  INT_PTR rc(IDCANCEL);

  st_FilterRow &st_fldata = m_pvfdata->at(m_iItem);

  // Get Text
  const CString cs_selected = GetItemText(m_iItem, FLC_LGC_COMBOBOX);

  // Save fieldtype (need it when we reset the filter data)
  const FieldType ft = st_fldata.ftype;

  bool b_good(false);
  LogicConnect ltype(st_fldata.ltype);

  // Now go display the correct match dialog
  switch (st_fldata.mtype) {
    case PWSMatch::MT_STRING:
      m_fstring.m_add_present = vAddPresent[m_iItem];
      m_fstring.m_title = cs_selected;
      if (!vcbxChanged[m_iItem] &&
          st_fldata.rule != PWSMatch::MR_INVALID) {
        m_fstring.m_rule = st_fldata.rule;
        m_fstring.m_string = st_fldata.fstring.c_str();
        m_fstring.m_case = st_fldata.fcase ? BST_CHECKED : BST_UNCHECKED;
      } else {
        m_fstring.m_rule = PWSMatch::MR_INVALID;
      }
      rc = m_fstring.DoModal();
      if (rc == IDOK) {
        st_fldata.Empty();
        st_fldata.bFilterActive = true;
        st_fldata.mtype = PWSMatch::MT_STRING;
        st_fldata.ftype = ft;
        st_fldata.rule = m_fstring.m_rule;
        st_fldata.fstring = m_fstring.m_string;
        if (st_fldata.rule == PWSMatch::MR_PRESENT ||
            st_fldata.rule == PWSMatch::MR_NOTPRESENT)
          st_fldata.fcase = false;
        else
          st_fldata.fcase = (m_fstring.m_case == BST_CHECKED);
        b_good = true;
      }
      break;
    case PWSMatch::MT_PASSWORD:
      m_fpswd.m_title = cs_selected;
      if (!vcbxChanged[m_iItem] &&
          st_fldata.rule != PWSMatch::MR_INVALID) {
        m_fpswd.m_rule = st_fldata.rule;
        m_fpswd.m_string = st_fldata.fstring.c_str();
        m_fpswd.m_case = st_fldata.fcase ? BST_CHECKED : BST_UNCHECKED;
        m_fpswd.m_num1 = st_fldata.fnum1;
      } else {
        m_fpswd.m_rule = PWSMatch::MR_INVALID;
      }
      rc = m_fpswd.DoModal();
      if (rc == IDOK) {
        st_fldata.Empty();
        st_fldata.bFilterActive = true;
        st_fldata.mtype = PWSMatch::MT_PASSWORD;
        st_fldata.ftype = ft;
        st_fldata.rule = m_fpswd.m_rule;
        st_fldata.fstring = LPCTSTR(m_fpswd.m_string);
        st_fldata.fcase = (m_fpswd.m_case == BST_CHECKED);
        if (st_fldata.rule != PWSMatch::MR_WILLEXPIRE)
          st_fldata.fnum1 = 0;
        else
          st_fldata.fnum1 = m_fpswd.m_num1;
        b_good = true;
      }
      break;
    case PWSMatch::MT_INTEGER:
      m_finteger.m_add_present = vAddPresent[m_iItem];
      m_finteger.m_title = cs_selected;
      if (!vcbxChanged[m_iItem] &&
          st_fldata.rule != PWSMatch::MR_INVALID) {
        m_finteger.m_rule = st_fldata.rule;
        m_finteger.m_num1 = st_fldata.fnum1;
        m_finteger.m_num2 = st_fldata.fnum2;
      } else {
        m_finteger.m_rule = PWSMatch::MR_INVALID;
      }
      rc = m_finteger.DoModal();
      if (rc == IDOK) {
        st_fldata.Empty();
        st_fldata.bFilterActive = true;
        st_fldata.mtype = PWSMatch::MT_INTEGER;
        st_fldata.ftype = ft;
        st_fldata.rule = m_finteger.m_rule;
        st_fldata.fnum1 = m_finteger.m_num1;
        st_fldata.fnum2 = m_finteger.m_num2;
        b_good = true;
      }
      break;
    case PWSMatch::MT_DATE:
      m_fdate.m_add_present = vAddPresent[m_iItem];
      m_fdate.m_title = cs_selected;
      if (!vcbxChanged[m_iItem] &&
          st_fldata.rule != PWSMatch::MR_INVALID) {
        m_fdate.m_rule = st_fldata.rule;
        m_fdate.m_time_t1 = st_fldata.fdate1;
        m_fdate.m_time_t2 = st_fldata.fdate2;
      } else {
        m_fdate.m_rule = PWSMatch::MR_INVALID;
      }
      rc = m_fdate.DoModal();
      if (rc == IDOK) {
        st_fldata.Empty();
        st_fldata.bFilterActive = true;
        st_fldata.mtype = PWSMatch::MT_DATE;
        st_fldata.ftype = ft;
        st_fldata.rule = m_fdate.m_rule;
        st_fldata.fdate1 = m_fdate.m_time_t1;
        st_fldata.fdate2 = m_fdate.m_time_t2;
        b_good = true;
      }
      break;
    case PWSMatch::MT_PWHIST:
      {
        st_filters filters(*m_pfilters);
        CSetHistoryFiltersDlg fhistory(this, &filters, m_pPWF->GetFiltername());
        rc = fhistory.DoModal();
        if (rc == IDOK) {
          st_fldata.Empty();
          st_fldata.bFilterActive = true;
          st_fldata.mtype = PWSMatch::MT_PWHIST;
          st_fldata.ftype = ft;
          m_pfilters->num_Hactive = filters.num_Hactive;
          m_pfilters->vHfldata = filters.vHfldata;
          m_bPWHIST_Set = true;
          m_pPWF->UpdateStatusText();
        }
        m_GoodHistory = m_pfilters->num_Hactive > 0;
        b_good = m_GoodHistory;
      }
      break;
    case PWSMatch::MT_POLICY:
      {
        st_filters filters(*m_pfilters);
        CSetPolicyFiltersDlg fpolicy(this, &filters, m_pPWF->GetFiltername());
        rc = fpolicy.DoModal();
        if (rc == IDOK) {
          st_fldata.Empty();
          st_fldata.bFilterActive = true;
          st_fldata.mtype = PWSMatch::MT_POLICY;
          st_fldata.ftype = ft;
          m_pfilters->num_Pactive = filters.num_Pactive;
          m_pfilters->vPfldata = filters.vPfldata;
          m_bPOLICY_Set = true;
          m_pPWF->UpdateStatusText();
        }
        m_GoodPolicy = m_pfilters->num_Pactive > 0;
        b_good = m_GoodPolicy;
      }
      break;
    case PWSMatch::MT_BOOL:
      m_fbool.m_title = cs_selected;
      if (!vcbxChanged[m_iItem] &&
          st_fldata.rule != PWSMatch::MR_INVALID) {
        m_fbool.m_rule = st_fldata.rule;
      } else {
        m_fbool.m_rule = PWSMatch::MR_INVALID;
      }
      rc = m_fbool.DoModal();
      if (rc == IDOK) {
        st_fldata.Empty();
        st_fldata.bFilterActive = true;
        st_fldata.mtype = PWSMatch::MT_BOOL;
        st_fldata.ftype = ft;
        st_fldata.rule = m_fbool.m_rule;
        b_good = true;
      }
      break;
    case PWSMatch::MT_ENTRYTYPE:
      m_fentry.m_title = cs_selected;
      if (!vcbxChanged[m_iItem] &&
          st_fldata.rule != PWSMatch::MR_INVALID) {
        m_fentry.m_rule = st_fldata.rule;
        m_fentry.m_etype = st_fldata.etype;
      } else {
        m_fentry.m_rule = PWSMatch::MR_INVALID;
      }
      rc = m_fentry.DoModal();
      if (rc == IDOK) {
        st_fldata.Empty();
        st_fldata.bFilterActive = true;
        st_fldata.mtype = PWSMatch::MT_ENTRYTYPE;
        st_fldata.ftype = ft;
        st_fldata.rule = m_fentry.m_rule;
        st_fldata.etype = m_fentry.m_etype;
        b_good = true;
      }
      break;
    default:
      ASSERT(0);
  }

  // Update static text if user has selected a new criterion
  if (rc == IDOK) {
    st_fldata.ltype = ltype;
    CString cs_criteria;
    DWORD_PTR dwData;
    dwData = GetItemData(m_iItem);
    if (b_good) {
      cs_criteria = PWSFilters::GetFilterDescription(st_fldata).c_str();
      dwData &= ~FLC_CRITERIA_REDTXT;
      vcbxChanged[m_iItem] = false;
      vCriteriaSet[m_iItem] = true;
      st_fldata.bFilterComplete = true;
    } else {
      cs_criteria.LoadString(IDS_NOTDEFINED);
      dwData |= FLC_CRITERIA_REDTXT;
    }
    SetItemData(m_iItem, dwData);
    SetItemText(m_iItem, FLC_CRITERIA_TEXT, cs_criteria);
    SetColumnWidth(FLC_CRITERIA_TEXT, LVSCW_AUTOSIZE_USEHEADER);
    m_pPWF->UpdateDialogMaxWidth();
  }

  return true;
}

void CPWFilterLC::SetUpComboBoxData()
{
  // Set up the Field selction Combobox

  // NOTE: The ComboBox strings are NOT sorted by design !
  if (vLcbx_data.size() == 0) {
    st_Lcbxdata stl;
    stl.cs_text.LoadString(IDSC_AND);
    stl.ltype = LC_AND;
    vLcbx_data.push_back(stl);

    stl.cs_text.LoadString(IDSC_OR);
    stl.ltype = LC_OR;
    vLcbx_data.push_back(stl);
  }

  if (vFcbx_data.size() == 0) {
    st_Fcbxdata stf;
    switch (m_iType) {
      case DFTYPE_MAIN:
        stf.cs_text.LoadString(IDSC_EXPHDRGROUP);
        stf.cs_text.TrimRight(_T('\t'));
        stf.ftype = FT_GROUP;
        vFcbx_data.push_back(stf);

        stf.cs_text.LoadString(IDSC_EXPHDRTITLE);
        stf.cs_text.TrimRight(_T('\t'));
        stf.ftype = FT_TITLE;
        vFcbx_data.push_back(stf);

        stf.cs_text.LoadString(IDSC_EXPHDRGROUPTITLE);
        stf.cs_text.TrimRight(_T('\t'));
        stf.ftype = FT_GROUPTITLE;
        vFcbx_data.push_back(stf);

        stf.cs_text.LoadString(IDSC_EXPHDRUSERNAME);
        stf.cs_text.TrimRight(_T('\t'));
        stf.ftype = FT_USER;
        vFcbx_data.push_back(stf);

        stf.cs_text.LoadString(IDSC_EXPHDRPASSWORD);
        stf.cs_text.TrimRight(_T('\t'));
        stf.ftype = FT_PASSWORD;
        vFcbx_data.push_back(stf);

        stf.cs_text.LoadString(IDSC_EXPHDRNOTES);
        stf.cs_text.TrimRight(_T('\t'));
        stf.ftype = FT_NOTES;
        vFcbx_data.push_back(stf);

        stf.cs_text.LoadString(IDSC_EXPHDRURL);
        stf.cs_text.TrimRight(_T('\t'));
        stf.ftype = FT_URL;
        vFcbx_data.push_back(stf);

        stf.cs_text.LoadString(IDSC_EXPHDRAUTOTYPE);
        stf.cs_text.TrimRight(_T('\t'));
        stf.ftype = FT_AUTOTYPE;
        vFcbx_data.push_back(stf);

        stf.cs_text.LoadString(IDSC_EXPHDRCTIME);
        stf.cs_text.TrimRight(_T('\t'));
        stf.ftype = FT_CTIME;
        vFcbx_data.push_back(stf);

        stf.cs_text.LoadString(IDSC_EXPHDRATIME);
        stf.cs_text.TrimRight(_T('\t'));
        stf.ftype = FT_ATIME;
        vFcbx_data.push_back(stf);

        stf.cs_text.LoadString(IDSC_EXPHDRPMTIME);
        stf.cs_text.TrimRight(_T('\t'));
        stf.ftype = FT_PMTIME;
        vFcbx_data.push_back(stf);

        stf.cs_text.LoadString(IDSC_EXPHDRXTIME);
        stf.cs_text.TrimRight(_T('\t'));
        stf.ftype = FT_XTIME;
        vFcbx_data.push_back(stf);

        stf.cs_text.LoadString(IDSC_EXPHDRXTIMEINT);
        stf.cs_text.TrimRight(_T('\t'));
        stf.ftype = FT_XTIME_INT;
        vFcbx_data.push_back(stf);

        stf.cs_text.LoadString(IDSC_EXPHDRRMTIME);
        stf.cs_text.TrimRight(_T('\t'));
        stf.ftype = FT_RMTIME;
        vFcbx_data.push_back(stf);

        stf.cs_text.LoadString(IDS_PASSWORDHISTORY);
        stf.cs_text.TrimRight(_T('\t'));
        stf.ftype = FT_PWHIST;
        vFcbx_data.push_back(stf);

        stf.cs_text.LoadString(IDSC_EXPHDRPWPOLICY);
        stf.cs_text.TrimRight(_T('\t'));
        stf.ftype = FT_POLICY;
        vFcbx_data.push_back(stf);

        stf.cs_text.LoadString(IDS_ENTRYTYPE);
        stf.cs_text.TrimRight(_T('\t'));
        stf.ftype = FT_ENTRYTYPE;
        vFcbx_data.push_back(stf);

        stf.cs_text.LoadString(IDS_UNKNOWNFIELDSFILTER);
        stf.cs_text.TrimRight(_T('\t'));
        stf.ftype = FT_UNKNOWNFIELDS;
        vFcbx_data.push_back(stf);
        break;
      case DFTYPE_PWHISTORY:
        stf.cs_text.LoadString(IDS_PRESENT);
        stf.cs_text.TrimRight(_T('\t'));
        stf.ftype = HT_PRESENT;
        vFcbx_data.push_back(stf);

        stf.cs_text.LoadString(IDS_HACTIVE);
        stf.cs_text.TrimRight(_T('\t'));
        stf.ftype = HT_ACTIVE;
        vFcbx_data.push_back(stf);

        stf.cs_text.LoadString(IDS_HNUM);
        stf.cs_text.TrimRight(_T('\t'));
        stf.ftype = HT_NUM;
        vFcbx_data.push_back(stf);

        stf.cs_text.LoadString(IDS_HMAX);
        stf.cs_text.TrimRight(_T('\t'));
        stf.ftype = HT_MAX;
        vFcbx_data.push_back(stf);

        stf.cs_text.LoadString(IDS_HDATE);
        stf.cs_text.TrimRight(_T('\t'));
        stf.ftype = HT_CHANGEDATE;
        vFcbx_data.push_back(stf);

        stf.cs_text.LoadString(IDS_HPSWD);
        stf.cs_text.TrimRight(_T('\t'));
        stf.ftype = HT_PASSWORDS;
        vFcbx_data.push_back(stf);
        break;
      case DFTYPE_PWPOLICY:
        stf.cs_text.LoadString(IDS_PRESENT);
        stf.cs_text.TrimRight(_T('\t'));
        stf.ftype = PT_PRESENT;
        vFcbx_data.push_back(stf);

        stf.cs_text.LoadString(IDS_PLENGTH);
        stf.cs_text.TrimRight(_T('\t'));
        stf.ftype = PT_LENGTH;
        vFcbx_data.push_back(stf);

        stf.cs_text.LoadString(IDS_PLOWER);
        stf.cs_text.TrimRight(_T('\t'));
        stf.ftype = PT_LOWERCASE;
        vFcbx_data.push_back(stf);

        stf.cs_text.LoadString(IDS_PUPPER);
        stf.cs_text.TrimRight(_T('\t'));
        stf.ftype = PT_UPPERCASE;
        vFcbx_data.push_back(stf);

        stf.cs_text.LoadString(IDS_PDIGITS);
        stf.cs_text.TrimRight(_T('\t'));
        stf.ftype = PT_DIGITS;
        vFcbx_data.push_back(stf);

        stf.cs_text.LoadString(IDS_PSYMBOL);
        stf.cs_text.TrimRight(_T('\t'));
        stf.ftype = PT_SYMBOLS;
        vFcbx_data.push_back(stf);

        stf.cs_text.LoadString(IDS_PHEXADECIMAL);
        stf.cs_text.TrimRight(_T('\t'));
        stf.ftype = PT_HEXADECIMAL;
        vFcbx_data.push_back(stf);

        stf.cs_text.LoadString(IDS_PEASYVISION);
        stf.cs_text.TrimRight(_T('\t'));
        stf.ftype = PT_EASYVISION;
        vFcbx_data.push_back(stf);

        stf.cs_text.LoadString(IDS_PPRONOUNCEABLE);
        stf.cs_text.TrimRight(_T('\t'));
        stf.ftype = PT_PRONOUNCEABLE;
        vFcbx_data.push_back(stf);
        break;
      default:
        ASSERT(0);
    }
  }
}

void CPWFilterLC::OnLButtonDown(UINT nFlags, CPoint point)
{
  int iItem = -1;
  int iSubItem = -1;

  LVHITTESTINFO lvhti;
  lvhti.pt = point;
  SubItemHitTest(&lvhti);

  if (lvhti.flags & LVHT_ONITEM) {
    iItem = lvhti.iItem;
    iSubItem = lvhti.iSubItem;
    m_iItem = iItem;
  }

  if ((iItem < 0)    || (iItem >= m_numfilters) ||
      (iSubItem < 0) || (iSubItem >= FLC_NUM_COLUMNS)) {
    CListCtrl::OnLButtonDown(nFlags, point);
  } else {
    FieldType ftype = m_pvfdata->at(m_iItem).ftype;
    DWORD_PTR dwData = GetItemData(m_iItem);
    bool bFilterActive = (dwData & FLC_FILTER_ENABLED) == FLC_FILTER_ENABLED;
    bool bLogicCBXEnabled = (dwData & FLC_LGC_CBX_ENABLED) == FLC_LGC_CBX_ENABLED;
    switch (iSubItem) {
      case FLC_FILTER_NUMBER:
        EnsureVisible(m_iItem, FALSE);
        SetItemState(m_iItem, 0, LVIS_SELECTED);
        break;
      case FLC_ENABLE_BUTTON:
        // Change what user sees - then implement the result
        EnableCriteria();
        break;
      case FLC_ADD_BUTTON:
        AddFilter();
        break;
      case FLC_REM_BUTTON:
        RemoveFilter();
        break;
      case FLC_LGC_COMBOBOX:
        if (bFilterActive && bLogicCBXEnabled && iItem > 0) {
          DrawComboBox(FLC_LGC_COMBOBOX, m_pvfdata->at(m_iItem).ltype == LC_OR ? 1 : 0);
          m_pComboBox->ShowDropDown(TRUE);
        }
        break;
      case FLC_FLD_COMBOBOX:
        if (bFilterActive) {
          DrawComboBox(FLC_FLD_COMBOBOX, (int)ftype);
          m_pComboBox->ShowDropDown(TRUE);
        }
        break;
      case FLC_CRITERIA_TEXT:
        dwData = GetItemData(m_iItem);
        if ((dwData & FLC_FILTER_ENABLED) &&
            (dwData & FLC_FLD_CBX_SET))
          GetCriterion();
        break;
      default:
        break;
    }
  }
  if (m_iItem >= 0)
    SetItemState(m_iItem, 0, LVIS_SELECTED | LVIS_DROPHILITED);
  Invalidate();
}

void CPWFilterLC::DrawComboBox(const int iSubItem, const int index)
{
  // Draw the drop down list
  CRect rect;
  GetSubItemRect(m_iItem, iSubItem, LVIR_BOUNDS, rect);

  m_pComboBox = new CComboBox;
  ASSERT(m_pComboBox);

  if (!m_pComboBox)
    return;

  UINT nID;
  if (iSubItem == FLC_FLD_COMBOBOX)
    nID = m_FLD_ComboID;
  else if (iSubItem == FLC_LGC_COMBOBOX)
    nID = m_LGC_ComboID;
  else {
    ASSERT(0);
    return;
  }

  rect.top -= 1;
  CRect m_rectComboBox(rect);
  m_rectComboBox.left += 1;

  DWORD dwStyle =  CBS_DROPDOWNLIST | WS_CHILD | WS_VISIBLE;
  BOOL bSuccess = m_pComboBox->Create(dwStyle, m_rectComboBox, this, nID);
  ASSERT(bSuccess);

  if (!bSuccess)
    return;

  if (!m_pFont) {
    CFont* pF = GetFont();
    ASSERT(pF);

    LOGFONT logFont;
    memset(&logFont, 0, sizeof(LOGFONT));

    pF->GetLogFont(&logFont);
    m_pFont = new CFont;
    m_pFont->CreateFontIndirect(&logFont);
  }
  m_pComboBox->SetFont(m_pFont);

  // add strings & data to ComboBox
  vWCFcbx_data = vFcbx_data;

  if (m_iType == DFTYPE_MAIN && iSubItem == FLC_FLD_COMBOBOX) {
    // Is PW History or Policy already selected somewhere else?
    //  if so remove them as an option on this filter
    st_FilterRow &st_fldata = m_pvfdata->at(m_iItem);
    if (m_bPWHIST_Set && st_fldata.ftype != FT_PWHIST)
      DeleteEntry(FT_PWHIST);
    if (m_bPOLICY_Set && st_fldata.ftype != FT_POLICY)
      DeleteEntry(FT_POLICY);
  }

  switch (iSubItem) {
    case FLC_FLD_COMBOBOX:
      for (int i = 0; i < (int)vWCFcbx_data.size(); i++) {
        m_pComboBox->AddString(vWCFcbx_data[i].cs_text);
        m_pComboBox->SetItemData(i, vWCFcbx_data[i].ftype);
      }
      break;
    case FLC_LGC_COMBOBOX:
      for (int i = 0; i < (int)vLcbx_data.size(); i++) {
        m_pComboBox->AddString(vLcbx_data[i].cs_text);
        m_pComboBox->SetItemData(i, (int)vLcbx_data[i].ltype);
      }
      break;
    default:
      ASSERT(0);
  }

  // Find widths and height - only the first time this is called
  // In fact, called twice during 'Init' processing just for this purpose
  // once for each Combobox
  if ((m_fwidth < 0 && iSubItem == FLC_FLD_COMBOBOX) ||
      (m_lwidth < 0 && iSubItem == FLC_LGC_COMBOBOX)) {
    // Find the longest string in the list box.
    CString str;
    CSize sz;
    int dx(0);
    TEXTMETRIC tm;
    CDC* pDC = m_pComboBox->GetDC();
    CFont* pFont = m_pComboBox->GetFont();

    // Select the ComboBox font, save the old font
    CFont* pOldFont = pDC->SelectObject(pFont);
    // Get the text metrics for avg char width
    pDC->GetTextMetrics(&tm);

    for (int i = 0; i < m_pComboBox->GetCount(); i++) {
      m_pComboBox->GetLBText(i, str);
      sz = pDC->GetTextExtent(str);

      // Add the average width to prevent clipping
      sz.cx += tm.tmAveCharWidth;

      if (sz.cx > dx)
        dx = sz.cx;
    }
    dx += 2 * ::GetSystemMetrics(SM_CXEDGE) + 5;

    // Select the old font back into the DC
    pDC->SelectObject(pOldFont);
    m_pComboBox->ReleaseDC(pDC);

    // Now set column widths
    if (iSubItem == FLC_FLD_COMBOBOX) {
      m_fwidth = max(dx, GetColumnWidth(FLC_FLD_COMBOBOX));
      SetColumnWidth(FLC_FLD_COMBOBOX, m_fwidth);
    } else {
      m_lwidth = max(dx, GetColumnWidth(FLC_LGC_COMBOBOX));
      SetColumnWidth(FLC_LGC_COMBOBOX, m_lwidth);
    }

    if (m_rowheight < 0) {
      // Set row height to take image by adding a dummy ImageList
      // Good trick - save making ComboBox "ownerdraw"
      CRect rect;
      m_pComboBox->GetClientRect(&rect);
      IMAGEINFO imageinfo;
      m_pCheckImageList->GetImageInfo(0, &imageinfo);
      m_rowheight = max(rect.Height(),
                        abs(imageinfo.rcImage.top - imageinfo.rcImage.bottom));

      m_pImageList = new CImageList;
      m_pImageList->Create(1, m_rowheight, ILC_COLOR4, 1, 1);
      SetImageList(m_pImageList, LVSIL_SMALL);
    }
  }

  // If the width of the list box is too small, adjust it so that every
  // item is completely visible.
  if (m_pComboBox->GetDroppedWidth() <
          ((iSubItem == FLC_FLD_COMBOBOX) ? m_fwidth : m_lwidth)) {
    m_pComboBox->SetDroppedWidth(iSubItem == FLC_FLD_COMBOBOX ? m_fwidth : m_lwidth);
  }

  // Try to ensure that dropdown list is big enough for all entries and
  // therefore no scrolling
  int n = m_pComboBox->GetCount();

  int ht = m_pComboBox->GetItemHeight(0);
  m_pComboBox->GetWindowRect(&rect);

  CSize sz;
  sz.cx = rect.Width();
  sz.cy = ht * (n + 2);

  if ((rect.top - sz.cy) < 0 || 
      (rect.bottom + sz.cy > ::GetSystemMetrics(SM_CYSCREEN))) {
    int ifit = max((rect.top / ht), (::GetSystemMetrics(SM_CYSCREEN) - rect.bottom) / ht);
    int ht2 = ht * ifit;
    sz.cy = min(ht2, sz.cy);
  }

  m_pComboBox->SetWindowPos(NULL, 0, 0, sz.cx, sz.cy, SWP_NOMOVE | SWP_NOZORDER);

  int nindex(index);
  if (nindex >= n || nindex < 0)
    nindex = -1;
  else {
    if (iSubItem == FLC_FLD_COMBOBOX) {
      std::vector<st_Fcbxdata>::iterator Fcbxdata_iter;
      Fcbxdata_iter = std::find_if(vWCFcbx_data.begin(), vWCFcbx_data.end(),
                                   equal_ftype((FieldType)index));
      if (Fcbxdata_iter == vWCFcbx_data.end())
        nindex = -1;
      else
        nindex = (int)(Fcbxdata_iter - vWCFcbx_data.begin());
    }
  }

  m_pComboBox->SetCurSel(nindex);
  m_pComboBox->ShowWindow(SW_SHOW);
  m_pComboBox->BringWindowToTop();
}

void CPWFilterLC::DeleteEntry(FieldType ftype)
{
  // User has selected Password History or Policy
  // Remove it from the Combobox data - Working Copy only!
  std::vector<st_Fcbxdata>::iterator Fcbxdata_iter;
  Fcbxdata_iter = std::find_if(vWCFcbx_data.begin(), vWCFcbx_data.end(),
                               equal_ftype(ftype));

  // Check if already deleted - should never happen!
  if (Fcbxdata_iter != vWCFcbx_data.end())
    vWCFcbx_data.erase(Fcbxdata_iter);
}

void CPWFilterLC::OnCustomDraw(NMHDR* pNotifyStruct, LRESULT* pResult)
{
  NMLVCUSTOMDRAW* pLVCD = reinterpret_cast<NMLVCUSTOMDRAW *>(pNotifyStruct);

  *pResult = CDRF_DODEFAULT;
  const int iItem = pLVCD->nmcd.dwItemSpec;
  const int iSubItem = pLVCD->iSubItem;
  const DWORD_PTR dwData = pLVCD->nmcd.lItemlParam;
  bool bFilterActive = (dwData & FLC_FILTER_ENABLED) == FLC_FILTER_ENABLED;
  bool bLogicCBXEnabled = (dwData & FLC_LGC_CBX_ENABLED) == FLC_LGC_CBX_ENABLED;

  int ix, iy;

  switch(pLVCD->nmcd.dwDrawStage) {
    case CDDS_PREPAINT:
      *pResult = CDRF_NOTIFYITEMDRAW;
      break;
    case CDDS_ITEMPREPAINT:
      *pResult = CDRF_NOTIFYSUBITEMDRAW;
      break;
    case CDDS_ITEMPREPAINT | CDDS_SUBITEM:
      {
        CRect rect;
        GetSubItemRect(iItem, iSubItem, LVIR_BOUNDS, rect);
        if (rect.top < 0) {
          *pResult = CDRF_SKIPDEFAULT;
          break;
        }
        if (iSubItem == 0) {
          CRect rect1;
          GetSubItemRect(iItem, 1, LVIR_BOUNDS, rect1);
          rect.right = rect1.left;
        }
        pLVCD->clrText = m_crWindowText;
        pLVCD->clrTextBk = GetTextBkColor();
        CDC* pDC = CDC::FromHandle(pLVCD->nmcd.hdc);
        CRect inner_rect(rect), first_rect(rect);
        inner_rect.DeflateRect(2, 2);
        switch (iSubItem) {
          case FLC_FILTER_NUMBER:
            first_rect.DeflateRect(1, 1);
            pDC->FillSolidRect(&first_rect, m_crButtonFace);
            DrawSubItemText(iItem, iSubItem, pDC, 
                            m_iItem == iItem ? m_crRedText : m_crWindowText, 
                            m_crButtonFace, inner_rect, 
                            m_iItem == iItem, true);
            *pResult = CDRF_SKIPDEFAULT;
            break;
          case FLC_ENABLE_BUTTON:
            // Draw checked/unchecked image
            ix = inner_rect.CenterPoint().x;
            iy = inner_rect.CenterPoint().y;
            // The '7' below is ~ half the bitmap size of 13.
            inner_rect.SetRect(ix - 7, iy - 7, ix + 7, iy + 7);
            DrawImage(pDC, inner_rect, bFilterActive ? 0 : 1);
            *pResult = CDRF_SKIPDEFAULT;
            break;
          case FLC_ADD_BUTTON:
          case FLC_REM_BUTTON:
            // Always bold
            DrawSubItemText(iItem, iSubItem, pDC, m_crWindowText, m_crWindow,
                            inner_rect, true, false);
            *pResult = CDRF_SKIPDEFAULT;
            break;
          case FLC_LGC_COMBOBOX:
            // Greyed out if filter inactive or logic not set
            if (!bFilterActive || !bLogicCBXEnabled) {
              pLVCD->clrText = m_crGrayText;
            }
            break;
          case FLC_FLD_COMBOBOX:
            // Greyed out if filter inactive or field not set
            if (!bFilterActive) {
              pLVCD->clrText = m_crGrayText;
            }
            break;
          case FLC_CRITERIA_TEXT:
            // Greyed out if filter inactive
            if (!bFilterActive) {
              pLVCD->clrText = m_crGrayText;
            } else if (dwData & FLC_CRITERIA_REDTXT) {
              // Red text if criteria not set
              pLVCD->clrText = m_crRedText;
            }
            break;
          default:
            break;
        }
      }
      break;
    default:
      break;
  }
}

void CPWFilterLC::DrawSubItemText(int iItem, int iSubItem, CDC *pDC,
                                  COLORREF crText, COLORREF crBkgnd,
                                  CRect &rect, bool bBold, bool bOpaque)
{
  CRect rectText(rect);
  CString str = GetItemText(iItem, iSubItem);

  if (!str.IsEmpty()) {
    HDITEM hditem;
    hditem.mask = HDI_FORMAT;
    m_pHeaderCtrl->GetItem(iSubItem, &hditem);
    int nFmt = hditem.fmt & HDF_JUSTIFYMASK;
    UINT nFormat = DT_VCENTER | DT_SINGLELINE;
    if (nFmt == HDF_CENTER)
      nFormat |= DT_CENTER;
    else if (nFmt == HDF_LEFT)
      nFormat |= DT_LEFT;
    else
      nFormat |= DT_RIGHT;

    CFont *pOldFont = NULL;
    CFont boldfont;

    if (bBold) {
      CFont *font = pDC->GetCurrentFont();
      if (font) {
        LOGFONT lf;
        font->GetLogFont(&lf);
        lf.lfWeight = FW_BOLD;
        boldfont.CreateFontIndirect(&lf);
        pOldFont = pDC->SelectObject(&boldfont);
      }
    }
    pDC->SetBkMode(bOpaque ? OPAQUE : TRANSPARENT);
    pDC->SetTextColor(crText);
    pDC->SetBkColor(crBkgnd);
    pDC->DrawText(str, &rectText, nFormat);
    if (pOldFont)
      pDC->SelectObject(pOldFont);
  }
}

void CPWFilterLC::DrawImage(CDC *pDC, CRect &rect, int nImage)
{
  // Draw check image in given rectangle
  if (rect.IsRectEmpty() || nImage < 0) {
    return;
  }

  if (m_pCheckImageList) {
    SIZE sizeImage = {0, 0};
    IMAGEINFO info;

    if (m_pCheckImageList->GetImageInfo(nImage, &info)) {
      sizeImage.cx = info.rcImage.right - info.rcImage.left;
      sizeImage.cy = info.rcImage.bottom - info.rcImage.top;
    }

    if (nImage >= 0) {
      if (rect.Width() > 0) {
        POINT point;

        point.y = rect.CenterPoint().y - (sizeImage.cy >> 1);
        point.x = rect.left;

        SIZE size;
        size.cx = rect.Width() < sizeImage.cx ? rect.Width() : sizeImage.cx;
        size.cy = rect.Height() < sizeImage.cy ? rect.Height() : sizeImage.cy;
        m_pCheckImageList->DrawIndirect(pDC, nImage, point, size, CPoint(0, 0));
      }
    }
  }
}

INT_PTR CPWFilterLC::OnToolHitTest(CPoint point, TOOLINFO *pTI) const
{
  LVHITTESTINFO lvhti;
  lvhti.pt = point;

  ListView_SubItemHitTest(this->m_hWnd, &lvhti);
  int nSubItem = lvhti.iSubItem;

  // nFlags is 0 if the SubItemHitTest fails
  // Therefore, 0 & <anything> will equal false
  if (lvhti.flags & LVHT_ONITEMLABEL) {
    // get the client (area occupied by this control
    RECT rcClient;
    GetClientRect(&rcClient);

    // fill in the TOOLINFO structure
    pTI->hwnd = m_hWnd;
    pTI->uId = (UINT) (nSubItem + 1);
    pTI->lpszText = LPSTR_TEXTCALLBACK;
    pTI->rect = rcClient;

    return pTI->uId;  // By returning a unique value per listItem,
              // we ensure that when the mouse moves over another
              // list item, the tooltip will change
  } else {
    //Otherwise, we aren't interested, so let the message propagate
    return -1;
  }
}

BOOL CPWFilterLC::OnToolTipText(UINT /*id*/, NMHDR * pNMHDR, LRESULT * pResult)
{
  UINT nID = pNMHDR->idFrom;

  // check if this is the automatic tooltip of the control
  if (nID == 0) 
    return TRUE;  // do not allow display of automatic tooltip,
                  // or our tooltip will disappear

  // handle both ANSI and UNICODE versions of the message
  TOOLTIPTEXTA* pTTTA = (TOOLTIPTEXTA*)pNMHDR;
  TOOLTIPTEXTW* pTTTW = (TOOLTIPTEXTW*)pNMHDR;

  *pResult = 0;

  // get the mouse position
  const MSG* pMessage;
  pMessage = GetCurrentMessage();
  ASSERT(pMessage);
  CPoint pt;
  pt = pMessage->pt;    // get the point from the message
  ScreenToClient(&pt);  // convert the point's coords to be relative to this control

  // see if the point falls onto a list item
  LVHITTESTINFO lvhti;
  lvhti.pt = pt;
  
  SubItemHitTest(&lvhti);
  int nSubItem = lvhti.iSubItem;
  
  // nFlags is 0 if the SubItemHitTest fails
  // Therefore, 0 & <anything> will equal false
  if (lvhti.flags & LVHT_ONITEMLABEL) {
    // If it did fall on a list item,
    // and it was also hit one of the
    // item specific subitems we wish to show tooltips for
    
    switch (nSubItem) {
      case FLC_FILTER_NUMBER:
        nID = IDS_FLC_TOOLTIP0;
        break;
      case FLC_ENABLE_BUTTON:
        nID = IDS_FLC_TOOLTIP1;
        break;
      case FLC_ADD_BUTTON:
        nID = IDS_FLC_TOOLTIP2;
        break;
      case FLC_REM_BUTTON:
        nID = IDS_FLC_TOOLTIP3;
        break;
      case FLC_LGC_COMBOBOX:
        nID = IDS_FLC_TOOLTIP4;
        break;
      case FLC_FLD_COMBOBOX:
        nID = IDS_FLC_TOOLTIP5;
        break;
      case FLC_CRITERIA_TEXT:
        nID = IDS_FLC_TOOLTIP6;
        break;
      default:
        return FALSE;
    }
    // If there was a CString associated with the list item,
    // copy it's text (up to 80 characters worth, limitation 
    // of the TOOLTIPTEXT structure) into the TOOLTIPTEXT 
    // structure's szText member

    CString cs_TipText(MAKEINTRESOURCE(nID));

#define LONG_TOOLTIPS

#ifdef LONG_TOOLTIPS

#ifdef _UNICODE
  if (pNMHDR->code == TTN_NEEDTEXTA) {
    delete m_pchTip;

    m_pchTip = new char[cs_TipText.GetLength() + 1];
#if (_MSC_VER >= 1400)
    size_t num_converted;
    wcstombs_s(&num_converted, m_pchTip, cs_TipText.GetLength() + 1, cs_TipText,
               cs_TipText.GetLength() + 1);
#else
    wcstombs(m_pchTip, cs_TipText, cs_TipText.GetLength() + 1);
#endif
    pTTTA->lpszText = (LPSTR)m_pchTip;
  } else {
    delete m_pwchTip;

    m_pwchTip = new WCHAR[cs_TipText.GetLength() + 1];
    lstrcpyn(m_pwchTip, cs_TipText, cs_TipText.GetLength() + 1);
    pTTTW->lpszText = (LPWSTR)m_pwchTip;
  }

#else // Unicode

  if (pNMHDR->code == TTN_NEEDTEXTA) {
    delete m_pchTip;

    m_pchTip = new char[cs_TipText.GetLength() + 1];
    lstrcpyn(m_pchTip, cs_TipText, cs_TipText.GetLength()+1);
    pTTTA->lpszText = (LPSTR)m_pchTip;
  } else {
    delete m_pwchTip;

    m_pwchTip = new WCHAR[cs_TipText.GetLength() + 1];
#if _MSC_VER >= 1400
    size_t numconverted;
    mbstowcs_s(&numconverted, m_pwchTip, cs_TipText.GetLength() + 1, cs_TipText, 
               cs_TipText.GetLength() + 1);
#else
    mbstowcs(m_pwchTip, cs_TipText, cs_TipText.GetLength() + 1);
#endif
    pTTTW->lpszText = m_pwchTip;
  }

#endif // UNICODE/ASCII

#else // Short Tooltips!

#ifdef _UNICODE
    if (pNMHDR->code == TTN_NEEDTEXTA) {
      int n = WideCharToMultiByte(CP_ACP, 0, cs_TipText, -1,
                                  pTTTA->szText,
                                  sizeof(pTTTA->szText)/sizeof(pTTTA->szText[0]),
                                  NULL, NULL);
      if (n > 0)
        pTTTA->szText[n - 1] = 0;
    } else
#if _MSC_VER >= 1400
      _tcsncpy_s(pTTTW->szText, (sizeof(pTTTW->szText)/sizeof(pTTTW->szText[0])),
      cs_TipText, _TRUNCATE);
#else
      _tcsncpy(pTTTW->szText, cs_TipText, (sizeof(pTTTW->szText)/sizeof(pTTTW->szText[0])));
#endif

#else  // UNICODE

    if (pNMHDR->code == TTN_NEEDTEXTA)
#if _MSC_VER >= 1400
      _tcsncpy_s(pTTTA->szText, (sizeof(pTTTA->szText)/sizeof(pTTTA->szText[0])),
                 cs_TipText, _TRUNCATE);
#else
      _tcsncpy(pTTTA->szText, cs_TipText, (sizeof(pTTTA->szText)/sizeof(pTTTA->szText[0])));
#endif
    else {
      int n = MultiByteToWideChar(CP_ACP, 0, cs_TipText, -1, pTTTW->szText,
                                  sizeof(pTTTW->szText)/sizeof(pTTTW->szText[0]));
      if (n > 0)
        pTTTW->szText[n - 1] = 0;
    }
#endif // UNICODE/ASCII
#endif // Long/short tooltips

    return TRUE;   // we found a tool tip,
  }
  
  return FALSE;  // we didn't handle the message, let the 
                 // framework continue propagating the message
}

void CPWFilterLC::OnProcessKey(UINT nID)
{
  /*
    Ctrl+S      Select filter
    Up arrow    Select previous
    Down Arrow  Select next
    Ctrl+E      Enable selected filter
    Ctrl+F      Select Field
    Ctrl+C      Select Criteria
    Ctrl+L      Select Logic (And/Or)
    Delete      Delete selected filter
    Insert      Add after selected filter
  */

  POSITION pos;
  DWORD_PTR dwData(0);
  if (m_iItem >=0 && m_iItem < GetItemCount())
    dwData = GetItemData(m_iItem);

  switch (nID) {
    case ID_FLC_CRITERIA:
      if (m_iItem >= 0) {
        if ((dwData & FLC_FILTER_ENABLED) &&
            (dwData & FLC_FLD_CBX_SET) &&
            (dwData & FLC_FLD_CBX_ENABLED))
          GetCriterion();
      }
      break;
    case ID_FLC_ENABLE:
      if (m_iItem >= 0)
        EnableCriteria();
      break;
    case ID_FLC_FIELD:
      if (m_iItem >= 0) {
        if (dwData & FLC_FILTER_ENABLED) {
          m_bSetFieldActive = true;
          DrawComboBox(FLC_FLD_COMBOBOX, (int)m_pvfdata->at(m_iItem).ftype);
          m_pComboBox->ShowDropDown(TRUE);
          m_pComboBox->SetFocus();
        }
      }
      break;
    case ID_FLC_LOGIC:
      if (m_iItem > 0) {
        if ((dwData & FLC_FILTER_ENABLED) && 
            (dwData & FLC_LGC_CBX_ENABLED)) {
          m_bSetLogicActive = true;
          DrawComboBox(FLC_LGC_COMBOBOX, m_pvfdata->at(m_iItem).ltype == LC_OR ? 1 : 0);
          m_pComboBox->ShowDropDown(TRUE);
          m_pComboBox->SetFocus();
        }
      }
      break;
    case ID_FLC_SELECT:
      if (m_iItem < 0)
        m_iItem = 0;
      pos = GetFirstSelectedItemPosition();
      if (pos > 0)
        m_iItem = (int)pos - 1;
      EnsureVisible(m_iItem, FALSE);
      SetItemState(m_iItem, 0, LVIS_SELECTED);
      break;
    case ID_FLC_DELETE:
      if (m_iItem >= 0) {
        RemoveFilter();
        if (m_iItem > 0)
          m_iItem--;
      }
      break;
    case ID_FLC_INSERT:
      if (m_iItem >= 0) {
        m_iItem = AddFilter();
        if (m_iItem >= 0) {
          EnsureVisible(m_iItem, FALSE);
        }
      }
      break;
    case ID_FLC_PREVIOUS:
      if (m_bSetFieldActive || m_bSetLogicActive)
        return;

      if (m_iItem <= 0)
        m_iItem = 0;
      else
        m_iItem--;
      EnsureVisible(m_iItem, FALSE);
      break;
    case ID_FLC_NEXT:
      if (m_bSetFieldActive || m_bSetLogicActive)
        return;

      if (m_iItem >= GetItemCount() - 1)
        m_iItem = GetItemCount() - 1;
      else
        m_iItem++;
      EnsureVisible(m_iItem, FALSE);
      break;
    default:
      ASSERT(0);
  }
  Invalidate();
}
