Click here to Skip to main content
15,897,518 members
Articles / Desktop Programming / MFC

Report control - with category

Rate me:
Please Sign up or sign in to vote.
4.66/5 (18 votes)
14 Oct 2005CPOL 102.4K   3.1K   78  
A report control - with category.
/**$F
//	File:		ReportCtrl.cpp
//	Version:	3.0.2
//
//	Author:		Maarten Hoeben
//	E-mail:		hamster@xs4all.nl
//
//	Implementation of the CReportCtrl and associated classes.
//
//	This code may be used in compiled form in any way you desire. This
//	file may be redistributed unmodified by any means PROVIDING it is
//	not sold for profit without the authors written consent, and
//	providing that this notice and the authors name and all copyright
//	notices remains intact.
//
//	An email letting me know how you are using it would be nice as well.
//
//	This file is provided "as is" with no expressed or implied warranty.
//	The author accepts no liability for any damage/loss of business that
//	this product may cause.
//
//	Version history
//
//	1.0.1	- Initial release.
//	1.1.0	- Changed copyright notice.
//			- Added RVN_LAYOUTCHANGED notification message.
//			- Fixed SB_THUMBPOSITION and SB_THUMBTRACK problems.
//			- Removed IDC_HEADERCTRL and IDC_REPORTCTRL definitions.
//			  Now hard coded because conflicts with some implementations.
//			- Added SortAllColumns(), contributed by Roger Parkinson.
//			- Added Chris Hambleton's suggestion to sort on image.
//			  indices when the (sub)item does not have text.
//			- Fixed DeleteAllItems(), as suggested by Paul Leung.
//			- Fixed DeleteAllItems() focus problem, as suggested by Dmitry ??.
//			- Fixed DeleteItem(), as noted by Eugenio Ciceri.
//			- Added fixes and suggestions of Serge Weinstock:
//				- Fixed GetNextSelectedItem().
//				- Fixed "no items to show" position.
//				- Added support for WS_BORDER style.
//				- Added RVN_ITEMDELETED notification.
//			- Added mouse-wheel support.
//			- Added extended horizontal grid style and changed regular
//			  horizontal grid style.
//			- Fixed selection inversion using space key.
//			- Fixed focus selection using ctrl key.
//			- Fixed focus on deleted items.
//			- Added RVS_OWNERDATA style.
//			- Changed notification structure to facilitate
//			  RVS_OWNERDATA style.
//			- Changed hit test info structure to facilitate
//			  RVS_FOCUSSUBITEMS style.
//			- Added RVS_FOCUSSUBITEMS style to enable focus on individual
//			  subitems to facilitate subitem editing.
//			- Added dialog control ID to WPARAM of notification messages.
//			- Added subitem editing functionality.
//			- Added CReportEditCtrl edit control.
//			- Added CReportComboCtrl edit control.
//			- Added support for RVIM_TEXT on empty strings (required for
//			  editing).
//			- Added RVIS_READONLY state.
//			- Added rect member to RVHITTESTINFO structure.
//			- Added RVS_EXPANDSUBITEMS style.
//			- Added CReportTipCtrl, based on code by Zafir Anjum.
//			- Added background image support, based on code contributed by
//			  Ernest Laurentin.
//	1.1.1	- Added RVN_KEYDOWN notification.
//			- Fixed DeleteItem as suggested by Thomas Freudenberg
//			  (Luca; this time I tested the function :-).
//			- Removed use of GetBuffer() in CReportData::GetSubItem()
//			  to improve performance.
//			- Improved sorting performance by using quick sort instead
//			  of bubble sort (QuickSort based on Martin Ziacek's
//			  QArray implementation).
//			- Added item to row mapping for improved lookup performance.
//			- Fixed GetItemForRow to retrieve ITEM in OWNERDATA style.
//			- Added item/row manipulation functions.
//			- Fixed column reordering and storage for invisible
//			  controls.
//			- Added GetItemHeight function.
//			- Added NMRVHEADER notification structure.
//			- Replaced RVN_COLUMNCLICK with RVN_HEADERCLICK. This
//			  new notification uses the new NMRVHEADER structure.
//			- Added MeasureItem function.
//			- Added RVN_DIVIDERDBLCLICK notification and code that
//			  implements optimal column sizing.
//			- Added right mouse button handling and RVN_ITEMRCLICK
//			  notification.
//			- Added item cache to optimize lookup of ITEM structures
//			  in RVS_OWNERDATA style.
//			- Fixed a bug related to creation of the control from
//			  from dialog templates.
//			- Fixed double RVN_ENDITEMEDIT messages in edit controls.
//			- Fixed edit cancellation on style changes.
//			- Fixed control keys in edit controls.
//			- Added AddItem operations.
//			- Added FindItem operation.
//			- Changed text string parameter of InsertItem to LPCTSTR.
//			- Changed behavior of edit row and RVS_FOCUSSUBITEMS style.
//			  The focus on the edit row is now always on individual columns,
//			  while the focus on other rows is only on individual columns
//			  if the RVS_FOCUSSUBITEMS style is used.
//			- Fixed a bug related to editing of edit row while not visible.
//			- Added BeginEdit functions to edit controls.
//			- Changed 'Column' handling function names in order to stop
//			  confusion about what is a column and what is a subitem.
//			  A column is a visible (or active) subitem.
//			- Added GetSubItemWidth and SetSubItemWidth functions, courtesy
//			  of Jonathan Kotas.
//			- Removed restriction to sort on columns only.
//			- Added Focus manipulation functions, suggested by Attila Hajdrik.
//			- Added subitem persistent style, as suggested by Attila Hajdrik.
//			- Fixed assertion on column invisible deactivation.
//			- Fixed several bugs related to subitem dynamic (de)activation and
//			  column manipulation.
//			- Added RVP_SORTTOOLTIP property to allow changing "Sort by"
//			  tooltip.
//			- Fixed pResult assignment in OnHdnItemClick.
//			- Added ResortItems function.
//			- Removed restriction for at least one visible subitem.
//			- Added GetActiveSubItemCount().
//			- Added flat style to CReportSubItemListCtrl
//			  (use WS_EX_STATICEDGE).
//			- Added subitem disable to CReportSubItemListCtrl.
//			- Added UndefineAllSubItems, as suggested by Brian Pollack.
//			- Added RVCF_SUBITEM_NOFOCUS, as suggested by Attila Hajdrik.
//			- Added RVP_NOTIFYMASK property for setting a mask on what
//			  notifications must be generated, as suggested by A. Hajdrik.
//			- Made Notify function virtual.
//			- Fixed OnVScroll thumb track for item count > 32767.
//			- Improved SelectRows performance by using list instead of
//			  iteration through all items to clear the previous selection.
//			- Fixed several glitches related to subitem activation.
//			- Added IsItemVisible().
//			- Fixed scrollbar assertion in SetItemCount().
//			- Improved performance of CReportData.
//			- Added PreviewHeight function to calculate the height of
//			  preview text.
//			- Made several implementation function virtual.
//			- Minor performance improvement by replacing printf with prepared
//			  fixed in New.
//	1.1.2	- Fixed MoveUp/MoveDown related selection bug.
//			- Fixed focus on item sort as suggested by Matija Jerkovic.
//			- Fixed GetProfile and improved GetItem as suggested by
//			  Eugenio Ciceri.
//			- Several fixes to CReportView as suggested by Eugenio Ciceri.
//			- Fixed ClearSelection, added InvertSelection SelectAll and
//			  SetSelection variants (SelectMatch and SelectLessEq) as
//			  contributed by Eugenio Ciceri.
//			- Fixed SelectRows as suggested by Matija Jerkovic.
//			- Fixed InsertItem and DeleteItem as suggested by Eugenio Ciceri.
//			- Fixed DeleteAllItems.
//			- Fixed grid property, thanks to Roland Kopetzky.
//			- Applied fixes to edit row as suggested by Alex.
//			- Added support for disabled style.
//			- Fixed obstructed bottom selected row redraw problem.
//			- Added GetColor, SetColor and DeleteAllColors methods.
//			- Fixed (ctrl+)page-up assertion in empty reports.
//			- Added UpdateWindow on strategic places to improve
//			  drawing on slower or heavy loaded systems.
//			- Fixed assertion in HitTest on edit row.
//			- Added "browse" button option to CReportEditCtrl.
//			- Added RVN_BUTTONCLICK notification.
//			- Added RVIS_OWNERDRAW state and RVN_ITEMDRAW notification
//			  to support owner drawn subitems.
//			- Changed default font to default GUI font.
//			- Added second PreviewHeight function to calculate the height of
//			  preview text from the text and an optional rectangle.
//			- Fixed a selection bug in SetItemCount().
//			- Fixed another selection bug in DeleteItem().
//			- Fixed FindItem for LPARAM as suggested by Armin Z�rcher.
//	2.0.0	- Made scrolling with left or right cursor key dependent on
//			  focus subitems style.
//			- Fixed resource leak in empty list, as suggested by Florent
//			  Odelain.
//			- Fixed ResortItems and DeactivateSubItem bugs as suggested by
//			  Rafael Lombardi Santos.
//			- Fixed HitTest on reordered columns, reported by Trevor Ash.
//			- Fixed item rect returned in hitinfo structure for scrolled
//			  controls. This fixes incorrect positioning of tip windows.
//			- Added CReportHeaderCtrl to access CFlatHeaderCtrl's protected
//			  members.
//			- Added first column indent functions, to support hierarchy
//			  GUI elements.
//			- Fixed HitTest to retrieve correct item data.
//			- Added parameter to sort callback.
//			- Removed SortAllSubItems, because implementation was not
//			  maintained and buggy.
//			- Fixed IsItemVisible and added GetTopIndex(), PageUp() and
//			  PageDown() as suggested by Alina Kozlovsky.
//			- Added tree control features, both in preparation of group
//			  view mode as well as a standalone feature.
//			- Fixed subitem text drawing following uninitialized subitems.
//			- Changed CompareItems callback and functions to support
//			  separate subitems to enable tree view and group view sorting.
//			- Extended CompareItems to sort on checkboxes as suggested by
//			  Peter Lagerhem.
//			- Fixed offset of subitem tip for items with images or checks
//			  and text and related hit testing issues.
//			- Fixed NOHEADER style.
//			- Changed selected items and tree boxes in tree view mode visuals
//			  to match common control look and feel.
//			- Fixed default height setting for font size updates with images.
//			- Fixed cosmetic bug with RVS_SHOWHGRID style, as suggested by Matrix.
//			- Fixed SelectAll() for empty control as suggested by Dmitry Sazonov.
//			- Added GetStyle().
//			- Added RVN_HEADERRCLICK to support popup menus on header.
//			- Fixed tooltip double click and ALT key relay events by removing
//			  mouse capture.
//	2.0.1	- Fixed bug in DeleteItem for tree control mode.
//			- Fixed item expansion for single subitem hierarchy items.
//			- Added GetExpandedItemText to allow expanded items to show different
//			  text from the subitem text.
//			- Added support for radio button and disabled check marks and 
//			  radio buttons.
//			- Added RVP_ENABLEFLATCHECKMARK property, to control the visual style
//			  of check marks or radio buttons.
//			- Added support for check mark and radio button image list.
//			- Changed CurrentFocus() to GetCurrentFocus().
//			- Changed OnKillFocus to recognize all child windows.
//			- Extended MeasureItem function.
//			- Fixed GetNextItem() for RVTI_ROOT.
//			- Added support for SetRedraw suggested by Phil J Pearson.
//			- Optimized InsertItem performance by skipping GetRowFromItem
//			  when the focus is not on an reorderable row, as suggested by
//			  Phil J Pearson.
//			- Added disabled background function to CReportSubItemListCtrl.
//			- Added pre-create style passing to CReportView.
//			- Adjusted edit box position.
//			- Fixed a bug related to keydown messages in unfocused state.
//			- Fixed tip redraw problem and tip background color mismatch.
//			- Fixed kill tip on WM_KILLFOCUS, finally fixing click and double
//			  click on expanded subitems.
//			- Added GetSelectedItems method.
//			- Fixed a selection bug in ClearSelection.
//			- Made GetReportCtrlPtr virtual and changed CReportView to use
//			  overridden function to get a pointer to the embedded CReportCtrl.
//			  This allows control derived from CReportCtrl to be embedded in 
//			  CReportView.
//			- Added UpdateWindow to SetRedraw when re-enabling redrawing.
//			- Removed legacy definitions from header file.
//			- Added blended image support through state bits, focus and selection
//			  through the RVP_ENABLEIMAGEBLENDING and RVP_BLENDCOLOR properties.
//			- Added support for overlay images.
//			- Added Win2K tip fading to CReportTipCtrl.
//			- Fixed nFormat subitem member update in OnHdnItemChanged, as suggested
//			  by Sven Ritter.
//			- Added selective item cache flush to SetItemCount.
//			- Added GetItemString() function to retrieve an item as a string.
//			- Added clipboard Copy support and clipboard separator and indent
//			  properties.
//			- Fixed OnRvnEndItemEdit to not loose lParam, as suggested by
//			  Paul Hurley.
//			- Added GetSortSettings() to retrieve sorting settings.
//			- Made SelectRows virtual to allow owner data multiple selection
//			  management.
//			- Fixed a bug in DeleteItem for trees.
//			- Fixed various mouse button, keyboard selection/focus issues.
//	3.0.0	- Compile with either VC-6 or VC-7
//			- Support grouping by categories
//			- Support graphical tree display
//			- Fixed various scroll problems
//			- Fixed auto sizing the columms
//			- Fixed header match window width
//			- Support time
//			- Time for category displays Today, Tomorrow etc
//			- Use a stable sort
//			- Added Popup menu to select category		
//			- Shift key can be used to resize a column without modifying header size
//  3.0.1   - Support UNICODE
//  3.0.2   - Fix bug in control in dialog
*/
#include "stdafx.h"
#include "ReportCtrl.h"

#include "MemDC.h"
#include ".\reportctrl.h"

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

static TCHAR                            g_cSeparator = _T('|');
static TCHAR                            g_cCBSeparator = _T('\t');
static CString                          g_strCBIndent = _T("  ");
static TCHAR                            g_szNoData[REPORTDATA_MAX_NODATA];
static lpfnUpdateLayeredWindow          g_lpfnUpdateLayeredWindow = NULL;
static lpfnSetLayeredWindowAttributes   g_lpfnSetLayeredWindowAttributes = NULL;

// CCategoryTime
CCategoryTime::CCategoryTime(LPTSTR lptstr)
{
    ParseDateTime(lptstr);
}

CString CCategoryTime::NameCategory(COleDateTime baseTime, bool b_Both)
{
    if (GetStatus() != COleDateTime::valid)
        return _T("");

    COleDateTimeSpan    oneDay(1, 0, 0, 0);
    COleDateTime        ct(baseTime.GetYear(), baseTime.GetMonth(), baseTime.GetDay(), 0, 0, 0);
    if (*this > baseTime)
    {
        ct += oneDay;
        if (*this < ct)
            return _T("Today");
        if (*this < ct + oneDay)
            return _T("Tomorrow");
        ct += (7 - baseTime.GetDayOfWeek()) * oneDay;
        if (*this < ct)
            return b_Both ? _T("Later this week") : _T("This Week");
        ct += 7 * oneDay;
        if (*this < ct)
            return _T("Next week");
        ct.SetDate(baseTime.GetYear() + (baseTime.GetMonth() == 12), baseTime.GetMonth() % 12 + 1, 1);
        if (*this < ct)
            return b_Both ? _T("Later this Month") : _T("This Month");
        ct.SetDate(baseTime.GetYear() + (baseTime.GetMonth() >= 11), (baseTime.GetMonth() + 1) % 12 + 1, 1);
        if (*this < ct)
            return _T("Next Month");
        ct.SetDate(baseTime.GetYear() + 1, 1, 1);
        if (*this < ct)
            return b_Both ? _T("Later this year") : _T("This year");
        ct.SetDate(baseTime.GetYear() + 2, 1, 1);
        if (*this < ct)
            return _T("Next year");
        return _T("Later");
    }

    if (ct <= *this)
        return _T("Today");
    if (ct - oneDay <= *this)
        return _T("Yesterday");
    ct -= (baseTime.GetDayOfWeek() - 1) * oneDay;
    if (ct <= *this)
        return b_Both ? _T("Earlier this week") : _T("This Week");
    ct -= 7 * oneDay;
    if (ct <= *this)
        return _T("Last Week");
    ct.SetDate(baseTime.GetYear(), baseTime.GetMonth(), 1);
    if (ct <= *this)
        return b_Both ? _T("Earlier this Month") : _T("This Month");
    if (baseTime.GetMonth() == 1)
        ct.SetDate(baseTime.GetYear() - 1, 12, 1);
    else
        ct.SetDate(baseTime.GetYear(), baseTime.GetMonth() - 1, 1);
    if (ct <= *this)
        return _T("Last month");
    ct.SetDate(ct.GetYear() - 1, 1, 1);
    if (ct <= *this)
        return _T("Last Year");
    return _T("Earlier");
}

BOOL CCategoryTime::SameCategoryAs(CCategoryTime other, COleDateTime baseTime, bool b_Both)
{
    if (GetStatus() == COleDateTime::valid && other.GetStatus() == COleDateTime::valid)
        return other.NameCategory(baseTime, b_Both) == NameCategory(baseTime, b_Both);
    else
        return FALSE;
}

// CReportData
CReportData::CReportData()
{
}

CReportData::~CReportData()
{
}

BOOL CReportData::New(INT iSubItems)
{
    LPTSTR  lpsz = GetBuffer(iSubItems * REPORTDATA_MAX_NODATA);
    lpsz[0] = 0;
    for (INT i = 0; i < iSubItems; i++)
        lpsz = _tcscat(lpsz, g_szNoData);
    ReleaseBuffer();
    return TRUE;
}

BOOL CReportData::GetSubItem(INT iSubItem, LPINT lpiImage, LPINT lpiOverlay, LPINT lpiCheck, LPINT lpiColor,
                             LPTSTR lpszText, LPINT lpiTextMax)
{
    INT i, iPos, iText;
    for (i = 0, iPos = 0; i < iSubItem && iPos >= 0; i++, iPos++)
        iPos = Find(g_cSeparator, iPos);
    if (iPos < 0)
        return FALSE;

    LPCTSTR lpsz = GetBuffer(0);
    lpsz = &lpsz[iPos];
    VERIFY(_stscanf(lpsz, _T("(%d,%d,%d,%d,%d)"), lpiImage, lpiOverlay, lpiCheck, lpiColor, &iText));

    if (iText < 0)
        *lpiTextMax = -1;

    if (*lpiImage == -1 && *lpiOverlay == -1 && *lpiCheck == -1 && *lpiColor == -1 && *lpiTextMax == -1)
        return FALSE;

    if (iText < 0)
        return TRUE;

    lpsz = _tcspbrk(lpsz, _T(")")) + 1;
    if (lpsz && lpszText)
    {
        for (INT iTextSize = 0; iTextSize < (*lpiTextMax) - 1 && *lpsz != g_cSeparator; iTextSize++)
            lpszText[iTextSize] = *lpsz++;

        lpszText[iTextSize] = 0;
    }

    return TRUE;
}

BOOL CReportData::SetSubItem(INT iSubItem, INT iImage, INT iOverlay, INT iCheck, INT iColor, LPCTSTR lpszText)
{
    if (!InsertSubItem(iSubItem, iImage, iOverlay, iCheck, iColor, lpszText))
        return FALSE;

    if (!DeleteSubItem(iSubItem + 1))
        return FALSE;

    return TRUE;
}

BOOL CReportData::InsertSubItem(INT iSubItem, INT iImage, INT iOverlay, INT iCheck, INT iColor, LPCTSTR lpszText)
{
    INT i, iPos, iText;

    for (i = 0, iPos = 0; i < iSubItem && iPos >= 0; i++, iPos++)
        iPos = Find(g_cSeparator, iPos);

    if (iPos < 0)
        return FALSE;

    if (lpszText == NULL)
    {
        lpszText = _T("");
        iText = -1;
    }
    else
        iText = _tcslen(lpszText);

    TCHAR   sz[32 + REPORTCTRL_MAX_TEXT];
    _stprintf(sz, _T("(%d,%d,%d,%d,%d)%s%c"), iImage, iOverlay, iCheck, iColor, iText, lpszText, g_cSeparator);

    Insert(iPos, sz);
    return TRUE;
}

BOOL CReportData::DeleteSubItem(INT iSubItem)
{
    INT i, iPos1, iPos2;

    for (i = 0, iPos1 = 0; i < iSubItem && iPos1 >= 0; i++, iPos1++)
        iPos1 = Find(g_cSeparator, iPos1);

    if (iPos1 < 0)
        return FALSE;

    iPos2 = Find(g_cSeparator, iPos1);
    if (iPos2++ < 0)
        return FALSE;

    Delete(iPos1, iPos2 - iPos1);
    return TRUE;
}

// CReportView
IMPLEMENT_DYNCREATE(CReportView, CView)
CReportView::CReportView()
{
    m_bCreated = FALSE;
    m_nExtra = 0;
}

CReportView::~CReportView()
{
}

BOOL CReportView::PreCreateWindow(CREATESTRUCT &cs)
{
    BOOL    bResult = CView::PreCreateWindow(cs);

    m_wndReportCtrl.m_dwStyle = cs.style & 0xFFFF;
    return bResult;
}

void CReportView::OnInitialUpdate()
{
    CView::OnInitialUpdate();

    if (!m_bCreated && GetReportCtrlPtr() == &m_wndReportCtrl)
    {
        m_wndReportCtrl.m_dwStyle |= m_nExtra | WS_CHILD | WS_TABSTOP | WS_VISIBLE;

        CRect   rect;
        GetClientRect(rect);

        if (m_wndReportCtrl.Create(m_wndReportCtrl.m_dwStyle, rect, this, 0) == NULL)
            AfxThrowMemoryException();
    }

    m_bCreated = TRUE;
}

BEGIN_MESSAGE_MAP(CReportView, CView)
//{{AFX_MSG_MAP(CReportView)
    ON_WM_SIZE()
    ON_WM_SETFOCUS()
    ON_WM_ERASEBKGND()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

// CReportView drawing
void CReportView::OnDraw(CDC *pDC)
{
    UNREFERENCED_PARAMETER(pDC);
}

// CReportView diagnostics
#ifdef _DEBUG
void CReportView::AssertValid() const
{
    CView::AssertValid();
}

void CReportView::Dump(CDumpContext &dc) const
{
    CView::Dump(dc);
}
#endif // DEBUG

// CReportView attributes
CReportCtrl &CReportView::GetReportCtrl()
{
    ASSERT(GetReportCtrlPtr() == &m_wndReportCtrl);                     // Don't use this function

    // GetReportCtrlPtr is overridden.
    return m_wndReportCtrl;
}

CReportCtrl *CReportView::GetReportCtrlPtr()
{
    return &m_wndReportCtrl;
}

// CReportView implementation
BOOL CReportView::OnEraseBkgnd(CDC *pDC)
{
    UNREFERENCED_PARAMETER(pDC);

    return TRUE;
}

void CReportView::OnSize(UINT nType, int cx, int cy)
{
    CView::OnSize(nType, cx, cy);

    if (m_bCreated && GetReportCtrlPtr()->GetSafeHwnd() != NULL)
    {
        CRect   rect;
        GetClientRect(rect);
        GetReportCtrlPtr()->MoveWindow(rect);
    }
}

void CReportView::OnSetFocus(CWnd *pOldWnd)
{
    UNREFERENCED_PARAMETER(pOldWnd);
    if (m_bCreated && GetReportCtrlPtr()->GetSafeHwnd() != NULL)
        GetReportCtrlPtr()->SetFocus();
}

// CReportCtrl
IMPLEMENT_DYNCREATE(CReportCtrl, CWnd)
CReportCtrl::CReportCtrl()
{
    WNDCLASS    wndclass;
    HINSTANCE   hInst = AfxGetInstanceHandle();

    if (!(::GetClassInfo(hInst, REPORTCTRL_CLASSNAME, &wndclass)))
    {
        wndclass.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
        wndclass.lpfnWndProc = ::DefWindowProc;
        wndclass.cbClsExtra = wndclass.cbWndExtra = 0;
        wndclass.hInstance = hInst;
        wndclass.hIcon = NULL;
        wndclass.hCursor = LoadCursor(hInst, IDC_ARROW);
        wndclass.hbrBackground = (HBRUSH) COLOR_WINDOW;
        wndclass.lpszMenuName = NULL;
        wndclass.lpszClassName = REPORTCTRL_CLASSNAME;

        if (!AfxRegisterClass(&wndclass))
            AfxThrowResourceException();
    }

    m_iScrollWindow = 0;
    m_bSubclassFromCreate = FALSE;

    m_iTreeDepth = 0;
    m_bDoubleBuffer = TRUE;
    m_bRedrawFlag = TRUE;
    m_iSpacing = 6;
    m_nRowsPerWheelNotch = GetMouseScrollLines();

    m_dwStyle = 0;

    m_bPreview = TRUE;
    m_bHorzScrollBar = TRUE;

    m_font.CreateStockObject(DEFAULT_GUI_FONT);

    m_pImageList = NULL;
    m_pCheckList = NULL;
    m_sizeImage.cx = 0;
    m_sizeImage.cy = 0;
    m_sizeCheck.cx = 8;
    m_sizeCheck.cy = 8;

    m_arrayColors.SetSize(0, 8);

    m_iGridStyle = PS_SOLID;
    m_nFrameControlStyle = DFCS_FLAT;
    m_nBlendStyle = RVP_BLEND_NONE;
    m_crBlendColor = RGB(0xff, 0xff, 0xff);

    m_strNoItems = _T("There are no items to show in this view.");
    m_strSortBy = _T("Sort by: %s");

    _stprintf(g_szNoData, _T("(-1,-1,-1,-1,-1)%c"), g_cSeparator);

    m_iDefaultWidth = 200;
    m_iDefaultHeight = 10;
    m_iVirtualWidth = 0;
    m_iVirtualHeight = 0;

    m_arraySubItems.SetSize(0, 8);
    m_arrayItems.SetSize(0, 128);

    m_tiRoot.bOpen = TRUE;

    m_bEditValid = FALSE;

    m_bUseItemCacheMap = TRUE;
    for (UINT n = 0; n < REPORTCTRL_MAX_CACHE; n++)
        m_aciCache[n].iItem = RVI_INVALID;

    m_bProcessKey = FALSE;

    m_bUpdateItemMap = FALSE;

    m_bColumnsReordered = FALSE;
    m_bWidthChanged = FALSE;
    m_arrayColumns.SetSize(0, 8);

    m_bFocus = FALSE;
    m_iFocusRow = RVI_EDIT;
    m_iFocusColumn = -1;
    m_iSelectRow = 0;
    m_arrayRows.SetSize(0, 128);

    m_bIndentColumn = FALSE;
    m_bIndentGrey = FALSE;
    m_iIndentColumn = 0;
    m_iIndentColumnPending = -1;

    m_iEditItem = RVI_INVALID;
    m_iEditSubItem = -1;
    m_hEditWnd = NULL;

    m_lprsilc = NULL;
    m_lpfnrvc = NULL;
    m_lParamCompare = 0;

    m_uNotifyMask = RVNM_ALL;

    m_bCategoryAscending = FALSE;
    m_iCategoryColumn = -1;
    m_iCategorySubItem = -1;
    m_iCategoryImage = -1;
    m_wndCategoryHdr = NULL;
    m_bSorting = FALSE;
    m_bFrameFocus = FALSE;
}

CReportCtrl::~CReportCtrl()
{
    m_wndHeader.DestroyWindow();
    if (m_wndCategoryHdr)
        delete m_wndCategoryHdr;

    m_wndTip.DestroyWindow();

    if (m_palette.m_hObject)
        m_palette.DeleteObject();
    if (m_bitmap.m_hObject != NULL)
        m_bitmap.DeleteObject();

    if (m_font.m_hObject)
        m_font.DeleteObject();
    if (m_fontBold.m_hObject)
        m_fontBold.DeleteObject();
}

BOOL CReportCtrl::Create()
{
    CRect   rect(0, 0, 0, 0);
    DWORD   dwStyle = HDS_HORZ | HDS_BUTTONS | HDS_FULLDRAG | HDS_DRAGDROP | CCS_TOP;
    if (!m_wndHeader.Create(dwStyle, rect, this, 0))
        return FALSE;

    if (!m_wndTip.Create(this))
        return FALSE;

    CWnd    *pWnd = GetParent();
    if (pWnd)
    {
        CFont   *pFont = pWnd->GetFont();
        if (pFont)
        {
            LOGFONT lf;
            pFont->GetLogFont(&lf);

            m_font.DeleteObject();
            m_font.CreateFontIndirect(&lf);
        }
    }

    OnSetFont((WPARAM) ((HFONT) m_font), FALSE);
    m_wndHeader.SetFont(&m_font, FALSE);

    m_dwStyle = CWnd::GetStyle();
    Layout();

    GetSysColors();
    return TRUE;
}

BOOL CReportCtrl::Create(DWORD dwStyle, const RECT &rect, CWnd *pParentWnd, UINT nID, CCreateContext *pContext)
{
    m_bSubclassFromCreate = TRUE;

    if (!CWnd::Create(REPORTCTRL_CLASSNAME, NULL, dwStyle, rect, pParentWnd, nID, pContext))
        return FALSE;

    return Create();
}

void CReportCtrl::PreSubclassWindow()
{
    CWnd::PreSubclassWindow();

    if (!m_bSubclassFromCreate)
        if (!Create())
            AfxThrowMemoryException();
}

void CReportCtrl::OnDestroy()
{
    DeleteAllItems();
    CWnd::OnDestroy();
}

BEGIN_MESSAGE_MAP(CReportCtrl, CWnd)
//{{AFX_MSG_MAP(CReportCtrl)
    ON_WM_DESTROY()
    ON_WM_SIZE()
    ON_WM_ERASEBKGND()
    ON_WM_PAINT()
    ON_WM_SYSCOLORCHANGE()
    ON_WM_SETTINGCHANGE()
    ON_NOTIFY(HDN_ITEMCHANGED, 0, OnHdnItemChanged)
    ON_NOTIFY(HDN_ITEMCHANGINGA, 0, OnHdnItemchanging)
    ON_NOTIFY(HDN_ITEMCHANGINGW, 0, OnHdnItemchanging)
    ON_NOTIFY(HDN_ITEMCLICK, 0, OnHdnItemClick)
    ON_NOTIFY(HDN_BEGINDRAG, 0, OnHdnBeginDrag)
    ON_NOTIFY(HDN_ENDDRAG, 0, OnHdnEndDrag)
    ON_NOTIFY(HDN_DIVIDERDBLCLICK, 0, OnHdnDividerDblClick)
    ON_NOTIFY(RVN_ENDITEMEDIT, 0, OnRvnEndItemEdit)
    ON_WM_HSCROLL()
    ON_WM_VSCROLL()
    ON_WM_LBUTTONDOWN()
    ON_WM_SETCURSOR()
    ON_WM_KEYDOWN()
    ON_WM_GETDLGCODE()
    ON_WM_LBUTTONDBLCLK()
    ON_WM_QUERYNEWPALETTE()
    ON_WM_PALETTECHANGED()
    ON_WM_SETFOCUS()
    ON_WM_KILLFOCUS()
    ON_WM_NCCALCSIZE()
    ON_WM_NCPAINT()
    ON_WM_MOUSEWHEEL()
    ON_WM_CHAR()
    ON_WM_MOUSEMOVE()
    ON_WM_RBUTTONDOWN()
    ON_WM_WINDOWPOSCHANGING()
//}}AFX_MSG_MAP
    ON_MESSAGE(WM_SETFONT, OnSetFont)
    ON_MESSAGE(WM_GETFONT, OnGetFont)
END_MESSAGE_MAP()

// CReportCtrl attributes
DWORD CReportCtrl::GetStyle() const
{
    return (CWnd::GetStyle() & 0xFFFF0000) | m_dwStyle;
}

BOOL CReportCtrl::ModifyProperty(WPARAM wParam, LPARAM lParam)
{
    switch (wParam)
    {
    case RVP_SPACING:
        m_iSpacing = (INT) lParam;
        break;
    case RVP_CHECK:
        m_sizeCheck.cx = LOWORD(lParam);
        m_sizeCheck.cy = HIWORD(lParam);
        break;
    case RVP_NOITEMTEXT:
        m_strNoItems = (LPCTSTR) lParam;
        break;
    case RVP_GRIDSTYLE:
        switch (lParam)
        {
        case RVP_GRIDSTYLE_DOT:
            m_iGridStyle = PS_DOT;
            break;
        case RVP_GRIDSTYLE_DASH:
            m_iGridStyle = PS_DASH;
            break;
        case RVP_GRIDSTYLE_SOLID:
            m_iGridStyle = PS_SOLID;
            break;
        default:
            return FALSE;
        }
        break;
    case RVP_SORTTOOLTIP:
        m_strSortBy = (LPCTSTR) lParam;
        break;
    case RVP_NOTIFYMASK:
        m_uNotifyMask = (ULONG) lParam;
        break;
    case RVP_ENABLEITEMCACHEMAP:
        m_bUseItemCacheMap = (BOOL) lParam;
        break;
    case RVP_SEPARATOR:
        g_cSeparator = (TCHAR) lParam;
        _stprintf(g_szNoData, _T("(-1,-1,-1,-1)%c"), g_cSeparator);
        break;
    case RVP_ENABLEPREVIEW:
        m_bPreview = (BOOL) lParam;
        break;
    case RVP_ENABLEHORZSCROLLBAR:
        m_bHorzScrollBar = (BOOL) lParam;
        break;
    case RVP_FRAMECONTROLSTYLE:
        switch (lParam)
        {
        case RVP_FCSTYLE_FLAT:
            m_nFrameControlStyle = DFCS_FLAT;
            break;
        case RVP_FCSTYLE_MONO:
            m_nFrameControlStyle = DFCS_MONO;
            break;
        case RVP_FCSTYLE_NORMAL:
            m_nFrameControlStyle = 0;
            break;
        default:
            return FALSE;
        }
        break;
    case RVP_ENABLEIMAGEBLENDING:
        m_nBlendStyle = (UINT) lParam;
        break;
    case RVP_BLENDCOLOR:
        m_crBlendColor = (COLORREF) lParam;
        break;
    case RVP_CBSEPARATOR:
        g_cCBSeparator = (TCHAR) lParam;
        break;
    case RVP_CBINDENT:
        g_strCBIndent = (LPCTSTR) lParam;
        break;
    default:
        return FALSE;
    }

    Layout();

    return TRUE;
}

INT CReportCtrl::GetSubItemCount()
{
    return m_arraySubItems.GetSize();
}

CString CReportCtrl::GetSubItemText(INT iSubItem)
{
    CString str;
    if (iSubItem != -1)
    {
        ASSERT(iSubItem >= 0);
        ASSERT(iSubItem < m_arraySubItems.GetSize());                   // Specify a valid subitem

        SUBITEM &subitem = m_arraySubItems[iSubItem];
        if (subitem.nFormat & RVCF_TEXT)
            str = subitem.strText.GetBuffer(0);
    }

    return str;
}

void CReportCtrl::GetSubItem(INT iSubItem, LPHDITEM phdi)
{
    ASSERT(iSubItem >= 0);
    ASSERT(iSubItem < m_arraySubItems.GetSize());                       // Specify a valid subitem

    SUBITEM &subitem = m_arraySubItems[iSubItem];

    phdi->mask = HDI_FORMAT | HDI_WIDTH | HDI_LPARAM;
    phdi->fmt = subitem.nFormat & RVCF_MASK;
    phdi->cxy = subitem.iWidth;
    phdi->lParam = (LPARAM) iSubItem;

    if (subitem.nFormat & RVCF_IMAGE)
    {
        phdi->mask |= HDI_IMAGE;
        phdi->iImage = subitem.iImage;
    }

    if (subitem.nFormat & RVCF_TEXT)
    {
        phdi->mask |= HDI_TEXT;
        phdi->pszText = subitem.strText.GetBuffer(0);
    }
}

void CReportCtrl::GetSubItemEx(INT iSubItem, HDITEMEX *phditemex)
{
    if (iSubItem != -1)
    {
        ASSERT(iSubItem >= 0);
        ASSERT(iSubItem < m_arraySubItems.GetSize());                   // Specify a valid subitem

        SUBITEM &subitem = m_arraySubItems[iSubItem];

        phditemex->nStyle = (subitem.nFormat & RVCF_EX_MASK) >> 16;
        phditemex->iMinWidth = subitem.iMinWidth;
        phditemex->iMaxWidth = subitem.iMaxWidth;
        if (phditemex->nStyle & HDF_EX_TOOLTIP)
            phditemex->strToolTip = subitem.strText;
    }
}

INT CReportCtrl::ActivateSubItem(INT iSubItem, INT iColumn)
{
    ASSERT(iSubItem >= 0);
    ASSERT(iSubItem < m_arraySubItems.GetSize());                       // Specify a valid subitem

    SUBITEM &subitem = m_arraySubItems[iSubItem];
    INT     iResult = -1;
    ExpandCategories(0);
    if (GetColumnFromSubItem(iSubItem) < 0)
    {
        UnindentFirstColumn();

        try
        {
            HDITEM  hdi;
            hdi.mask = HDI_FORMAT | HDI_WIDTH | HDI_LPARAM | HDI_ORDER;
            hdi.fmt = subitem.nFormat & RVCF_MASK;
            hdi.cxy = subitem.iWidth;
            hdi.iOrder = iColumn;
            hdi.lParam = (LPARAM) iSubItem;

            if (subitem.nFormat & RVCF_IMAGE)
            {
                hdi.mask |= HDI_IMAGE;
                hdi.iImage = subitem.iImage;
            }

            if (subitem.nFormat & RVCF_TEXT)
            {
                hdi.mask |= HDI_TEXT;
                hdi.pszText = subitem.strText.GetBuffer(0);
            }

            iResult = m_wndHeader.InsertItem(iSubItem, &hdi);
            if (iResult >= 0)
            {
                m_iVirtualWidth += subitem.iWidth;

                HDITEMEX    hditemex;
                hditemex.nStyle = (subitem.nFormat & RVCF_EX_MASK) >> 16;
                hditemex.iMinWidth = subitem.iMinWidth;
                hditemex.iMaxWidth = subitem.iMaxWidth;

                if (hditemex.nStyle & HDF_EX_TOOLTIP)
                    hditemex.strToolTip.Format(m_strSortBy, subitem.strText);

                m_wndHeader.SetItemEx(iResult, &hditemex);

                hdi.mask = HDI_WIDTH;
                m_wndHeader.GetItem(iResult, &hdi);
                subitem.iWidth = hdi.cxy;

                ReorderColumns();

                BOOL    bAscending;
                INT     iSortItem = m_wndHeader.GetSortColumn(&bAscending);
                if (iSubItem <= iSortItem)
                    m_wndHeader.SetSortColumn(1 + iSortItem, bAscending);
                if (iSubItem == m_iCategorySubItem)
                    m_iCategorySubItem = -1;
                m_bWidthChanged = TRUE;
            }

            IndentFirstColumn();
        }
        catch(CMemoryException * e)
        {
            e->Delete();
            if (iResult >= 0)
                m_wndHeader.DeleteItem(iResult);
        }
    }

    if (m_wndCategoryHdr != NULL)
    {
        m_wndCategoryHdr->NewCategory();
        m_wndCategoryHdr->Show();
    }

    if (m_dwStyle & RVS_TREEVIEW)
        BuildTree();
    return iResult;
}

BOOL CReportCtrl::DeactivateSubItem(INT iSubItem)
{
    ASSERT(iSubItem >= 0);
    ASSERT(iSubItem < m_arraySubItems.GetSize());                       // Specify a valid subitem

    INT iColumn = GetColumnFromSubItem(iSubItem);
    if (iColumn < 0)
        return FALSE;

    UnindentFirstColumn();

    BOOL    bAscending;
    INT     iSortItem = m_wndHeader.GetSortColumn(&bAscending);
    INT     iSortColumn = -1, iColumns = m_arrayColumns.GetSize();
    for (int i = 0; i < iColumns; i++)
        if (iSortItem == m_arrayColumns[i])
            iSortColumn = i;

    HDITEM  hdi;
    hdi.mask = HDI_WIDTH;
    m_wndHeader.GetItem(m_arrayColumns[iColumn], &hdi);

    BOOL    bResult = m_wndHeader.DeleteItem(m_arrayColumns[iColumn]);

    m_iVirtualWidth -= hdi.cxy;
    ReorderColumns();

    if (iSortColumn > 0)
    {
        if (iSortColumn == iColumn)
            m_wndHeader.SetSortColumn(-1, TRUE);
        else
            m_wndHeader.SetSortColumn(m_arrayColumns[iSortColumn - (iColumn <= iSortColumn)], bAscending);
    }

    IndentFirstColumn();
    m_bWidthChanged = TRUE;
    Layout();
    return bResult;
}

BOOL CReportCtrl::DeactivateAllSubItems()
{
    INT iSubItems = m_arraySubItems.GetSize();
    for (INT iSubItem = 0; iSubItem < iSubItems; iSubItem++)
    {
        if (IsActiveSubItem(iSubItem))
        {
            if (!DeactivateSubItem(iSubItem))
                return FALSE;
        }
    }

    return TRUE;
}

BOOL CReportCtrl::IsActiveSubItem(INT iSubItem)
{
    INT iColumn = GetColumnFromSubItem(iSubItem);
    return iColumn < 0 ? FALSE : TRUE;
}

INT CReportCtrl::GetActiveSubItemCount()
{
    return m_arrayColumns.GetSize();
}

INT CReportCtrl::GetSubItemWidth(INT iSubItem)
{
    INT iSubItems = m_arraySubItems.GetSize();
    ASSERT(iSubItem <= iSubItems);

    try
    {
        INT iWidth = m_arraySubItems[iSubItem].iWidth;

        if (m_bIndentColumn && GetColumnFromSubItem(iSubItem) == 0)
            iWidth -= m_iIndentColumn;

        return iWidth;
    }
    catch(CMemoryException * e)
    {
        e->Delete();
        return -1;
    }
}

BOOL CReportCtrl::SetSubItemWidth(INT iSubItem, INT iWidth)
{
    BOOL    bResult;

    UnindentFirstColumn();
    bResult = SetSubItemWidthImpl(iSubItem, iWidth);
    IndentFirstColumn();
    return bResult;
}

INT CReportCtrl::GetItemIndex(HTREEITEM hItem)
{
    ASSERT(m_dwStyle & RVS_TREEVIEW);
    return ((LPTREEITEM) hItem)->iItem;
}

HTREEITEM CReportCtrl::GetItemHandle(INT iItem)
{
    ASSERT(m_dwStyle & RVS_TREEVIEW);
    return iItem >= RVI_FIRST ? (HTREEITEM) m_arrayItems[iItem].lptiItem : NULL;
}

void CReportCtrl::SetTreeSubItem(HTREEITEM hItem, INT iSubItem)
{
    ((LPTREEITEM) hItem)->iSubItem = iSubItem;
}

HTREEITEM CReportCtrl::GetNextItem(HTREEITEM hItem, Direction nCode)
{
    LPTREEITEM  lpti = (LPTREEITEM) hItem;

    if (hItem == RVTI_ROOT)
        lpti = &m_tiRoot;

    switch (nCode)
    {
    case RVGN_ROOT:
        while (lpti->lptiParent != &m_tiRoot)
            lpti = lpti->lptiParent;
        break;
    case RVGN_NEXT:
        lpti = lpti->lptiSibling;
        break;
    case RVGN_PREVIOUS:
        if (lpti->lptiParent->lptiChildren != lpti)
        {
            LPTREEITEM  lptiSibling = lpti->lptiParent->lptiChildren;
            ASSERT(lptiSibling != NULL);

            while (lptiSibling->lptiSibling != lpti)
            {
                lptiSibling = lptiSibling->lptiSibling;
                ASSERT(lptiSibling != NULL);
            }

            lpti = lptiSibling;
        }
        else
            lpti = NULL;
        break;
    case RVGN_PARENT:
        lpti = lpti->lptiParent != &m_tiRoot ? lpti->lptiParent : NULL;
        break;
    case RVGN_CHILD:
        lpti = lpti->lptiChildren;
        break;
    case RVGN_FOCUSED:
        {
            INT iFocusItem = GetItemForRow(m_iFocusRow);
            if (iFocusItem >= RVI_FIRST)
                lpti = m_arrayItems[iFocusItem].lptiItem;
            else
                lpti = NULL;
        }
        break;
    default:
        lpti = NULL;
        break;
    }

    return (HTREEITEM) lpti;
}

BOOL CReportCtrl::GetItem(LPRVITEM lprvi)
{
    ASSERT(lprvi->iItem > RVI_INVALID);
    ASSERT(lprvi->iItem < GetItemCount());                              // Specify item
    ASSERT(lprvi->iSubItem < m_arraySubItems.GetSize());                // Specify subitem

    if
    (
        (lprvi->iItem <= RVI_INVALID)
    ||  (lprvi->iItem >= GetItemCount())
    ||  (lprvi->iSubItem >= m_arraySubItems.GetSize())
    ) return FALSE;

    UINT    nMask = lprvi->nMask;
    INT     iTextMax = lprvi->iTextMax;
    ITEM    &item = GetItemStruct(lprvi->iItem, lprvi->iSubItem, nMask);

    lprvi->nMask = 0;
    lprvi->iBkColor = item.iBkColor;
    if (lprvi->iBkColor >= 0)
        lprvi->nMask |= RVIM_BKCOLOR;

    lprvi->nPreview = item.nPreview;
    if (lprvi->nPreview > 0)
        lprvi->nMask |= RVIM_PREVIEW;

    lprvi->iIndent = item.iIndent;
    if (lprvi->iIndent >= 0)
        lprvi->nMask |= RVIM_INDENT;

    lprvi->lpszText = nMask & RVIM_TEXT ? lprvi->lpszText : NULL;
    item.rdData.GetSubItem(lprvi->iSubItem, &lprvi->iImage, &lprvi->iOverlay, &lprvi->iCheck, &lprvi->iTextColor,
                           lprvi->lpszText, &iTextMax);
    if (lprvi->lpszText && iTextMax >= 0)
        lprvi->nMask |= RVIM_TEXT;

    if (lprvi->iImage >= 0)
        lprvi->nMask |= RVIM_IMAGE;
    if (lprvi->iOverlay >= 0)
        lprvi->nMask |= RVIM_OVERLAY;
    if (lprvi->iCheck >= 0)
        lprvi->nMask |= RVIM_CHECK;
    if (lprvi->iTextColor >= 0)
        lprvi->nMask |= RVIM_TEXTCOLOR;

    lprvi->nMask |= RVIM_STATE | RVIM_LPARAM;
    lprvi->nState = item.nState;
    lprvi->lParam = item.lParam;

    return TRUE;
}

BOOL CReportCtrl::SetItem(LPRVITEM lprvi)
{
    ASSERT(!(lprvi->iItem != RVI_EDIT && m_dwStyle & RVS_OWNERDATA));   // Not supported when using this style
    if (m_dwStyle & RVS_OWNERDATA)
        return FALSE;

    m_wndTip.Hide();

    TCHAR   szText[REPORTCTRL_MAX_TEXT];

    RVITEM  rvi;
    rvi.iItem = lprvi->iItem;
    rvi.iSubItem = lprvi->iSubItem;
    rvi.lpszText = szText;
    rvi.iTextMax = REPORTCTRL_MAX_TEXT;
    rvi.nMask = RVIM_TEXT | RVIM_STATE;
    VERIFY(GetItem(&rvi));

    if (lprvi->nMask & RVIM_TEXT)
    {
        if (lprvi->lpszText != NULL)
        {
            _tcsncpy(rvi.lpszText, lprvi->lpszText, REPORTCTRL_MAX_TEXT - 1);
            rvi.lpszText[REPORTCTRL_MAX_TEXT - 1] = 0;
        }
        else
            rvi.lpszText = NULL;
    }
    else if (!(rvi.nMask & RVIM_TEXT))
        rvi.lpszText = NULL;

    if (lprvi->nMask & RVIM_TEXTCOLOR)
        rvi.iTextColor = lprvi->iTextColor;

    if (lprvi->nMask & RVIM_IMAGE)
    {
        ASSERT(m_pImageList != NULL && lprvi->iImage < m_pImageList->GetImageCount());
        rvi.iImage = lprvi->iImage;
    }

    if (lprvi->nMask & RVIM_OVERLAY)
    {
        ASSERT(m_pImageList != NULL && lprvi->iOverlay < m_pImageList->GetImageCount());
        rvi.iOverlay = lprvi->iOverlay;
    }

    if (lprvi->nMask & RVIM_CHECK)
        rvi.iCheck = lprvi->iCheck;

    if (lprvi->nMask & RVIM_BKCOLOR)
        rvi.iBkColor = lprvi->iBkColor;

    ASSERT(!(lprvi->iItem == RVI_EDIT && lprvi->nMask & RVIM_PREVIEW)); // Preview not supported on edit row
    if (lprvi->nMask & RVIM_PREVIEW)
        rvi.nPreview = lprvi->nPreview;

    if (lprvi->nMask & RVIM_INDENT)
        rvi.iIndent = lprvi->iIndent;

    // Note: focus, selection and tree-GUI elements cannot be changed through this
    // function
    if (lprvi->nMask & RVIM_STATE)
    {
        rvi.nState &= RVIS_FOCUSED | RVIS_SELECTED | RVIS_TREEMASK;
        rvi.nState |= lprvi->nState &~(RVIS_FOCUSED | RVIS_SELECTED | RVIS_TREEMASK);
    }

    if (lprvi->nMask & RVIM_LPARAM)
        rvi.lParam = lprvi->lParam;

    ITEM    &item = GetItemStruct(rvi.iItem, rvi.iSubItem);

    VERIFY(item.rdData.InsertSubItem(rvi.iSubItem, rvi.iImage, rvi.iOverlay, rvi.iCheck, rvi.iTextColor, rvi.lpszText));
    VERIFY(item.rdData.DeleteSubItem(rvi.iSubItem + 1));

    item.iBkColor = rvi.iBkColor;
    item.nPreview = rvi.nPreview;
    item.iIndent = rvi.iIndent;
    item.nState = rvi.nState;
    item.lParam = rvi.lParam;
    SetItemStruct(rvi.iItem, item);

    ScrollWindow(SB_VERT, GetScrollPos32(SB_VERT));

    if (m_bRedrawFlag == TRUE)
        RedrawItems(rvi.iItem);
    return TRUE;
}

INT CReportCtrl::GetItemText(INT iItem, INT iSubItem, LPTSTR lpszText, INT iLen)
{
    RVITEM  rvi;
    rvi.nMask = RVIM_TEXT;
    rvi.iItem = iItem;
    rvi.iSubItem = iSubItem;
    rvi.lpszText = lpszText;
    rvi.iTextMax = iLen;
    return GetItem(&rvi) ? _tcslen(rvi.lpszText) : 0;
}

CString CReportCtrl::GetItemText(INT iItem, INT iSubItem)
{
    CString str;
    TCHAR   szText[REPORTCTRL_MAX_TEXT];

    if (GetItemText(iItem, iSubItem, szText, REPORTCTRL_MAX_TEXT))
        str = szText;

    return str;
}

BOOL CReportCtrl::SetItemText(INT iItem, INT iSubItem, LPCTSTR lpszText)
{
    RVITEM  rvi;
    rvi.nMask = RVIM_TEXT;
    rvi.iItem = iItem;
    rvi.iSubItem = iSubItem;
    rvi.lpszText = (LPTSTR) lpszText;
    return SetItem(&rvi);
}

INT CReportCtrl::GetItemImage(INT iItem, INT iSubItem)
{
    RVITEM  rvi;
    rvi.nMask = RVIM_IMAGE;
    rvi.iItem = iItem;
    rvi.iSubItem = iSubItem;
    return GetItem(&rvi) ? rvi.iImage : -1;
}

BOOL CReportCtrl::SetItemImage(INT iItem, INT iSubItem, INT iImage)
{
    RVITEM  rvi;
    rvi.nMask = RVIM_IMAGE;
    rvi.iItem = iItem;
    rvi.iSubItem = iSubItem;
    rvi.iImage = iImage;
    return SetItem(&rvi);
}

INT CReportCtrl::GetItemCheck(INT iItem, INT iSubItem)
{
    RVITEM  rvi;
    rvi.nMask = RVIM_CHECK;
    rvi.iItem = iItem;
    rvi.iSubItem = iSubItem;
    return GetItem(&rvi) ? rvi.iCheck : -1;
}

BOOL CReportCtrl::SetItemCheck(INT iItem, INT iSubItem, INT iCheck)
{
    RVITEM  rvi;
    rvi.nMask = RVIM_CHECK;
    rvi.iItem = iItem;
    rvi.iSubItem = iSubItem;
    rvi.iCheck = iCheck;
    return SetItem(&rvi);
}

DWORD CReportCtrl::GetItemData(INT iItem)
{
    RVITEM  rvi;
    rvi.nMask = RVIM_LPARAM;
    rvi.iItem = iItem;
    return GetItem(&rvi) ? rvi.lParam : 0;
}

BOOL CReportCtrl::SetItemData(INT iItem, DWORD dwData)
{
    RVITEM  rvi;
    rvi.nMask = RVIM_LPARAM;
    rvi.iItem = iItem;
    rvi.lParam = dwData;
    return SetItem(&rvi);
}

BOOL CReportCtrl::GetItemRect(INT iItem, INT iSubItem, LPRECT lpRect, UINT nCode)
{
    INT iRow = GetRowFromItem(iItem);
    INT iColumn = GetColumnFromSubItem(iSubItem);

    if (nCode == RVIR_TEXTNOCOLUMNS)
        iColumn = 0;

    if (!GetRowRect(iRow, iColumn, lpRect, nCode))
        return FALSE;

    RVITEM  rvi;
    rvi.iItem = iItem;
    rvi.iSubItem = iSubItem;
    GetItem(&rvi);

    if (nCode != RVIR_BOUNDS && iColumn == 0 && rvi.nMask & RVIM_INDENT && rvi.iIndent >= 0)
    {
        lpRect->left += rvi.iIndent * m_iDefaultHeight;

        if (lpRect->left > lpRect->right)
            return FALSE;
    }

    switch (nCode)
    {
    case RVIR_BOUNDS:
        return TRUE;
    case RVIR_IMAGE:
        if (!(rvi.nMask & RVIM_IMAGE))
            return FALSE;

        lpRect->left += m_iSpacing;
        if (lpRect->left + m_sizeImage.cx > lpRect->right - m_iSpacing)
            return FALSE;

        lpRect->right = lpRect->left + m_sizeImage.cx;
        return TRUE;
    case RVIR_CHECK:
        if (!(rvi.nMask & RVIM_CHECK))
            return FALSE;

        lpRect->left += m_iSpacing + (rvi.nMask & RVIM_IMAGE ? m_sizeImage.cx + m_iSpacing : 0);
        if (lpRect->left + m_sizeCheck.cx > lpRect->right - m_iSpacing)
            return FALSE;

        lpRect->right = lpRect->left + m_sizeCheck.cx;
        return TRUE;
    case RVIR_TEXT:
    case RVIR_TEXTNOCOLUMNS:
        lpRect->left += m_iSpacing + (rvi.nMask & RVIM_IMAGE ? m_sizeImage.cx + m_iSpacing : 0) + (rvi.nMask & RVIM_CHECK ? m_sizeCheck.cx + m_iSpacing : 0);

        if (lpRect->left > lpRect->right - m_iSpacing)
            return FALSE;

        lpRect->right -= m_iSpacing;
        return TRUE;
    default:
        return FALSE;
    }
}

BOOL CReportCtrl::MeasureItem(INT iItem, INT iSubItem, LPRECT lpRect, BOOL bTextOnly)
{
    lpRect->top = 0;
    lpRect->bottom = m_iDefaultHeight;
    lpRect->left = 0;
    lpRect->right = m_iDefaultWidth;

    if (iSubItem != -1)
    {
        TCHAR   szText[REPORTCTRL_MAX_TEXT];

        RVITEM  rvi;
        rvi.nMask = RVIM_TEXT;
        rvi.iItem = iItem;
        rvi.iSubItem = iSubItem;
        rvi.lpszText = szText;
        rvi.iTextMax = REPORTCTRL_MAX_TEXT;
        GetItem(&rvi);

        lpRect->right = m_iSpacing;
        if (bTextOnly == FALSE)
        {
            lpRect->right += (rvi.nMask & RVIM_IMAGE ? m_sizeImage.cx + m_iSpacing : 0) + (rvi.nMask & RVIM_CHECK ? m_sizeCheck.cx + m_iSpacing : 0);
        }

        if (rvi.nMask & RVIM_TEXT)
        {
            CClientDC   dc(this);
            CFont       *pFontDC = dc.SelectObject(rvi.nState & RVIS_BOLD ? &m_fontBold : &m_font);
            lpRect->right += dc.GetTextExtent(rvi.lpszText, _tcslen(rvi.lpszText)).cx + m_iSpacing;
            dc.SelectObject(pFontDC);
        }
    }

    return TRUE;
}

INT CReportCtrl::GetItemHeight()
{
    return m_iDefaultHeight;
}

void CReportCtrl::SetItemHeight(INT iHeight)
{
    m_iDefaultHeight = iHeight;

    Layout();
}

CString CReportCtrl::GetHeadingForSortedCategory()
{
    BOOL        bSortAccending;
    INT         iSubItem = GetCategorySubItem(&bSortAccending);
    HDITEMEX    hditemex;
    GetSubItemEx(iSubItem, &hditemex);
    return (hditemex.nStyle & HDF_EX_HASDATE) ? (bSortAccending ? _T("Earliest on Top") : _T("Latest on Top")) : (bSortAccending ? _T("Lowest on Top") : _T("Highest on Top"));
}

void CReportCtrl::GetCategoryMenuItem(LPCATITEM lpCatItem)
{
    INT iSubItem = lpCatItem->iSubItem;
    if (iSubItem >= 0)
    {
        if (iSubItem == m_iCategorySubItem || GetColumnFromSubItem(iSubItem) >= 0)
        {
            HDITEM  hdi;
            lpCatItem->mask = iSubItem == 0 && m_dwStyle & RVS_TREEVIEW ? 0 : CATEGORY_ENABLED;
            GetSubItem(iSubItem, &hdi);
            lpCatItem->strText = GetSubItemText(iSubItem);;
            if (lpCatItem->strText.GetLength() == 0)
            {
                HDITEMEX    hditemex;
                GetSubItemEx(iSubItem, &hditemex);
                lpCatItem->strText += hditemex.strToolTip;
            }

            if (hdi.mask & HDI_IMAGE)
            {
                lpCatItem->iImage = hdi.iImage;
                lpCatItem->mask |= CATEGORY_IMAGE;
            }
        }
    }
    else
    {
        lpCatItem->mask = CATEGORY_ENABLED;
        lpCatItem->strText = _T("None");
    }
}

CString CReportCtrl::GetItemString(INT iItem, TCHAR cSeparator)
{
    CString str;
    TCHAR   szText[REPORTCTRL_MAX_TEXT + 1];
    INT     iColumn, iColumns = m_arrayColumns.GetSize();

    for (iColumn = 0; iColumn < iColumns; iColumn++)
    {
        HDITEM  hdi;
        hdi.mask = HDI_WIDTH | HDI_LPARAM;
        m_wndHeader.GetItem(m_arrayColumns[iColumn], &hdi);

        RVITEM  rvi;
        rvi.iItem = iItem;
        rvi.iSubItem = hdi.lParam;

        rvi.nMask = RVIM_TEXT;
        rvi.lpszText = szText;
        rvi.iTextMax = REPORTCTRL_MAX_TEXT;

        VERIFY(GetItem(&rvi));

        if (rvi.nMask & RVIM_TEXT)
        {
            LPTSTR  lpsz = rvi.lpszText;
            do
            {
                lpsz = _tcschr(lpsz, cSeparator);
                if (lpsz != NULL)
                    *lpsz = _T(' ');
            } while (lpsz != NULL);
        }
        else if (rvi.nMask & RVIM_IMAGE)
        {
            _stprintf(szText, _T("%d"), rvi.iImage);
        }
        else if (rvi.nMask & RVIM_CHECK)
        {
            _stprintf(szText, _T("%d"), rvi.iCheck);
        }
        else
            szText[0] = 0;

        if (m_dwStyle & RVS_TREEVIEW && iColumn == 0 && rvi.nMask & RVIM_INDENT)
        {
            for (INT i = 1; i < rvi.iIndent; i++)
                str += g_strCBIndent;

            HTREEITEM   hItem = GetItemHandle(iItem);
            ASSERT(hItem != NULL);

            if (HasChildren(hItem))
                str += GetNextItem(hItem, RVGN_CHILD) != NULL ? _T('-') : _T('+');
            else
                str += _T(' ');
        }

        str += rvi.lpszText;

        str += iColumn < iColumns - 1 ? cSeparator : _T('\n');
    }

    return str;
}

INT CReportCtrl::GetVisibleCount(BOOL bUnobstructed)
{
    return GetVisibleRows(bUnobstructed);
}

INT CReportCtrl::GetTopIndex()
{
    return GetScrollPos32(SB_VERT);
}

CCategoryHdr *CReportCtrl::GetCategoryHdrCtrl()
{
    if (m_wndCategoryHdr && m_wndCategoryHdr->GetSafeHwnd() != NULL)
        return m_wndCategoryHdr;
    else
        return NULL;
}

void CReportCtrl::SetAutosizeHeaders(BOOL bAutoSize)
{
    GetHeaderCtrl()->ModifyProperty(FH_PROPERTY_ENABLEAUTOSIZE, bAutoSize);
    if (m_wndCategoryHdr)
    {
        m_wndCategoryHdr->ModifyProperty(FH_PROPERTY_ENABLEAUTOSIZE, bAutoSize);
        m_wndCategoryHdr->NewCategory();
    }

    Invalidate();
}

INT CReportCtrl::GetCategoryHdrHeight()
{
    return (m_wndCategoryHdr != NULL && m_wndCategoryHdr->m_iState == SW_SHOW) ? m_wndCategoryHdr->GetHeight() : 0;
}

BOOL CReportCtrl::IsItemVisible(INT iItem, BOOL bUnobstructed)
{
    INT iFirst, iLast, iRow;

    iRow = GetRowFromItem(iItem);
    iFirst = GetScrollPos32(SB_VERT);
    GetVisibleRows(bUnobstructed, &iFirst, &iLast);

    return (iRow >= iFirst && iRow <= iLast) ? TRUE : FALSE;
}

void CReportCtrl::PageUp()
{
    INT iPos = GetTopIndex() - GetVisibleCount();
    ScrollWindow(SB_VERT, iPos < 0 ? 0 : iPos);
}

void CReportCtrl::PageDown()
{
    INT iPos = GetTopIndex() + GetVisibleCount();
    ScrollWindow(SB_VERT, iPos);
}

INT CReportCtrl::GetItemCount()
{
    return m_dwStyle & RVS_OWNERDATA ? m_iVirtualHeight : m_arrayItems.GetSize();
}

INT CReportCtrl::GetItemCount(HTREEITEM hParent, BOOL bRecurse)
{
    INT         iCount = 0;

    LPTREEITEM  lptiSibling = hParent == RVTI_ROOT ? m_tiRoot.lptiChildren : ((LPTREEITEM) hParent)->lptiChildren;

    while (lptiSibling != NULL)
    {
        if (bRecurse == TRUE && lptiSibling->lptiChildren != NULL)
            iCount += GetItemCount((HTREEITEM) lptiSibling, bRecurse);

        lptiSibling = lptiSibling->lptiSibling;
        iCount++;
    }

    return iCount;
}

void CReportCtrl::SetItemCount(INT iCount)
{
    ASSERT(m_dwStyle & RVS_OWNERDATA);                      // Only supported when using RVS_OWNERDATA style
    ASSERT(iCount >= 0);

    INT i;

    m_iVirtualHeight = iCount;
    m_iFocusRow = m_iFocusRow >= m_iVirtualHeight ? m_iVirtualHeight - 1 : m_iFocusRow;

    CList<INT, INT> list;
    while (!m_listSelection.IsEmpty())
    {
        i = m_listSelection.GetHead();
        if (i < iCount)
            list.AddTail(i);

        m_listSelection.RemoveHead();
    }

    m_listSelection.AddTail(&list);

    for (i = 0; i < REPORTCTRL_MAX_CACHE; i++)
    {
        if (m_aciCache[i].iItem >= iCount)
            m_aciCache[i].iItem = RVI_INVALID;
    }

    RedrawRows(RVI_EDIT, RVI_LAST, TRUE);

    INT iPos = GetScrollPos32(SB_VERT);
    ScrollWindow(SB_VERT, iPos > iCount ? iCount : iPos);
}

INT CReportCtrl::GetFirstSelectedItem()
{
    return GetNextSelectedItem(RVI_INVALID);
}

INT CReportCtrl::GetNextSelectedItem(INT iItem)
{
    RVITEM  rvi;
    rvi.nMask = RVIM_STATE;

    INT iItems = GetItemCount();

    for (rvi.iItem = iItem + 1; rvi.iItem < iItems; rvi.iItem++)
        if (GetItem(&rvi) && rvi.nState & RVIS_SELECTED)
            return rvi.iItem;

    return RVI_INVALID;
}

INT CReportCtrl::GetSelectedItems(LPINT lpiItems, INT iMax)
{
    INT iItem = GetFirstSelectedItem(), iItems = 0;

    while (iItem != RVI_INVALID)
    {
        if (lpiItems != NULL && iItems < iMax)
            lpiItems[iItems] = iItem;

        iItem = GetNextSelectedItem(iItem);
        iItems++;
    }

    return iItems;
}

INT CReportCtrl::GetSelectedItemCount()
{
    INT iItem = GetFirstSelectedItem(), iItems = 0;
    while (iItem != RVI_INVALID)
    {
        iItem = GetNextSelectedItem(iItem);
        iItems++;
    }

    return iItems;
}

void CReportCtrl::ClearSelection()
{
    POSITION    pos = m_listSelection.GetHeadPosition();
    while (pos != NULL)
    {
        INT iItem = m_listSelection.GetAt(pos);
        INT iRow = GetRowFromItem(iItem);

        SetState(iRow, GetState(iRow) &~RVIS_SELECTED, RVIS_SELECTED);
        RedrawRows(iRow);

        m_listSelection.GetNext(pos);
    }

    m_listSelection.RemoveAll();
}

void CReportCtrl::InvertSelection()
{
    INT iRows = m_arrayRows.GetSize();
    INT iFocusRow = m_iFocusRow;
    SelectRows(RVI_FIRST, iRows - 1, TRUE, TRUE, TRUE, FALSE);
    SelectRows(iFocusRow, iFocusRow, FALSE, FALSE, FALSE, FALSE);
}

void CReportCtrl::SelectAll()
{
    if (m_arrayRows.GetSize())
    {
        ClearSelection();
        InvertSelection();
    }
}

void CReportCtrl::SetSelection(INT iItem, BOOL bKeepSelection, BOOL bNotify)
{
    ASSERT(iItem > RVI_INVALID && iItem < GetItemCount());

    INT iRow = GetRowFromItem(iItem);
    SelectRows(iRow, iRow, TRUE, bKeepSelection, FALSE, bNotify);
}

void CReportCtrl::SetSelection(LPINT lpiItems, INT iCount, BOOL bKeepSelection, BOOL bNotify)
{
    INT i, iRow;

    for (i = 0; i < iCount; i++)
    {
        ASSERT(lpiItems[i] > RVI_INVALID && lpiItems[i] < GetItemCount());

        iRow = GetRowFromItem(lpiItems[i]);
        SelectRows(iRow, iRow, TRUE, bKeepSelection | (i != 0), FALSE, bNotify);
    }
}

void CReportCtrl::SetSelection(INT iSubItem, CString &strPattern, BOOL bKeepSelection, BOOL bNotify)
{
    INT iRows = m_arrayRows.GetSize();

    if (bKeepSelection == FALSE)
        ClearSelection();

    for (INT iRow = 0; iRow < iRows; iRow++)
    {
        INT iItem = GetItemForRow(iRow);
        if (MatchString(GetItemText(iItem, iSubItem), strPattern))
            SelectRows(iRow, iRow, TRUE, TRUE, FALSE, bNotify);
    }
}

void CReportCtrl::SetSelectionLessEq(INT iSubItem, INT iMatch, BOOL bKeepSelection, BOOL bNotify)
{
    RVITEM  rvi;
    INT     iRows = m_arrayRows.GetSize();

    if (bKeepSelection == FALSE)
        ClearSelection();

    rvi.iSubItem = iSubItem;
    rvi.nMask = RVIM_LPARAM;

    for (INT iRow = 0; iRow < iRows; iRow++)
    {
        rvi.iItem = GetItemForRow(iRow);
        if (GetItem(&rvi))
        {
            if (rvi.lParam <= iMatch)
                SelectRows(iRow, iRow, TRUE, TRUE, FALSE, bNotify);
        }
    }
}

void CReportCtrl::SetSelection(HTREEITEM hItem, BOOL bKeepSelection, BOOL bNotify)
{
    SetSelection(((LPTREEITEM) hItem)->iItem, bKeepSelection, bNotify);
}

void CReportCtrl::SetSelection(HTREEITEM *lphItems, INT iCount, BOOL bKeepSelection, BOOL bNotify)
{
    INT i, iItem, iRow;

    for (i = 0; i < iCount; i++)
    {
        iItem = ((LPTREEITEM) lphItems[i])->iItem;
        ASSERT(iItem > RVI_INVALID && iItem < GetItemCount());

        iRow = GetRowFromItem(iItem);
        SelectRows(iRow, iRow, TRUE, bKeepSelection | (i != 0), FALSE, bNotify);
    }
}

INT CReportCtrl::GetItemRow(INT iItem)
{
    ASSERT(!(m_dwStyle & RVS_OWNERDATA));
    return GetRowFromItem(iItem);
}

CArray<INT, INT> *CReportCtrl::GetItemRowArray()
{
    ASSERT(!(m_dwStyle & RVS_OWNERDATA));
    return &m_arrayRows;
}

BOOL CReportCtrl::SetItemRowArray(CArray<INT, INT> &arrayRows)
{
    if (m_dwStyle & RVS_OWNERDATA)
        return FALSE;

    ASSERT(m_arrayRows.GetSize() == arrayRows.GetSize());   // Must match
    if (m_arrayRows.GetSize() != arrayRows.GetSize())
        return FALSE;

    INT iFocusItem = GetItemForRow(m_iFocusRow);

    m_arrayRows.Copy(arrayRows);
    m_bUpdateItemMap = TRUE;

    m_iFocusRow = GetRowFromItem(iFocusItem);

    Invalidate();
    return TRUE;
}

BOOL CReportCtrl::MoveUp(INT iRow)
{
    if (m_dwStyle & RVS_OWNERDATA)
        return FALSE;

    if (iRow <= RVI_FIRST)
        return FALSE;

    ASSERT(iRow < m_arrayRows.GetSize());

    INT iTemp = m_arrayRows[iRow - 1];
    m_arrayRows[iRow - 1] = m_arrayRows[iRow];
    m_arrayRows[iRow] = iTemp;

    if (m_iFocusRow == iRow)
        m_iFocusRow--;

    Invalidate();

    m_bUpdateItemMap = TRUE;
    return TRUE;
}

BOOL CReportCtrl::MoveDown(INT iRow)
{
    if (m_dwStyle & RVS_OWNERDATA)
        return FALSE;

    if (iRow >= m_arrayRows.GetSize() - 1)
        return FALSE;

    ASSERT(iRow >= RVI_FIRST);

    INT iTemp = m_arrayRows[iRow + 1];
    m_arrayRows[iRow + 1] = m_arrayRows[iRow];
    m_arrayRows[iRow] = iTemp;

    if (m_iFocusRow == iRow)
        m_iFocusRow++;

    Invalidate();

    m_bUpdateItemMap = TRUE;
    return TRUE;
}

BOOL CReportCtrl::SetImageList(CImageList *pImageList, CImageList *pCheckList)
{
    BOOL        bResult = TRUE;
    IMAGEINFO   info;

    m_pImageList = pImageList;
    m_wndHeader.SetImageList(pImageList);

    if (pImageList != NULL)
    {
        if (pImageList->GetImageInfo(0, &info))
        {
            m_sizeImage.cx = info.rcImage.right - info.rcImage.left;
            m_sizeImage.cy = info.rcImage.bottom - info.rcImage.top;

            m_iDefaultHeight = m_sizeImage.cy + 1 > m_iDefaultHeight ? m_sizeImage.cy + 1 : m_iDefaultHeight;
        }
        else
            bResult = FALSE;
    }

    m_pCheckList = pCheckList;

    if (pCheckList != NULL)
    {
        if (pCheckList->GetImageInfo(0, &info))
        {
            m_sizeCheck.cx = info.rcImage.right - info.rcImage.left;
            m_sizeCheck.cy = info.rcImage.bottom - info.rcImage.top;

            m_iDefaultHeight = m_sizeCheck.cy + 1 > m_iDefaultHeight ? m_sizeCheck.cy + 1 : m_iDefaultHeight;
        }
        else
            bResult = FALSE;
    }

    Invalidate();
    return bResult;
}

CImageList *CReportCtrl::GetImageList(void)
{
    return m_pImageList;
}

BOOL CReportCtrl::SetBkImage(UINT nIDResource)
{
    return SetBkImage((LPCTSTR) nIDResource);
}

BOOL CReportCtrl::SetBkImage(LPCTSTR lpszResourceName)
{
    if (m_bitmap.m_hObject != NULL)
        m_bitmap.DeleteObject();

    HBITMAP hBitmap = (HBITMAP)::LoadImage(AfxGetInstanceHandle(), lpszResourceName, IMAGE_BITMAP, 0, 0,
                                           LR_CREATEDIBSECTION);

    Invalidate();

    if (hBitmap == NULL)
        return FALSE;

    m_bitmap.Attach(hBitmap);

    BITMAP  bitmap;
    m_bitmap.GetBitmap(&bitmap);
    m_sizeBitmap.cx = bitmap.bmWidth;
    m_sizeBitmap.cy = bitmap.bmHeight;

    BOOL    bResult = CreatePalette();

    Invalidate();
    return bResult;
}

COLORREF CReportCtrl::GetColor(INT iIndex)
{
    return m_arrayColors[iIndex];
}

BOOL CReportCtrl::SetColor(INT iIndex, COLORREF crColor)
{
    if (iIndex < 0 || iIndex >= m_arrayColors.GetSize())
        return FALSE;

    m_arrayColors.SetAt(iIndex, crColor);

    BOOL    bResult = CreatePalette();

    Invalidate();
    return bResult;
}

BOOL CReportCtrl::HasFocus()
{
    return m_bFocus;
}

INT CReportCtrl::GetCurrentFocus(LPINT lpiColumn)
{
    if (lpiColumn != NULL)
        *lpiColumn = m_iFocusColumn;

    return m_iFocusRow;
}

BOOL CReportCtrl::AdjustFocus(INT iRow, INT iColumn)
{
    INT iSubItem = GetSubItemFromColumn(iColumn);
    if (iSubItem >= 0 && m_arraySubItems[iSubItem].nFormat & RVCF_SUBITEM_NOFOCUS)
        return FALSE;

    if (iRow < RVI_INVALID || iRow > m_iVirtualHeight - 1 || iColumn < -1 || iColumn > m_arrayColumns.GetSize() - 1)
        return FALSE;

    if (m_iFocusRow > RVI_INVALID && iRow != m_iFocusRow)
    {
        SetState(m_iFocusRow, 0, RVIS_FOCUSED);
        RedrawRows(m_iFocusRow);
    }

    m_iFocusColumn = iColumn;
    m_iFocusRow = iRow;
    if (iRow > RVI_INVALID)
    {
        SetState(iRow, RVIS_FOCUSED, RVIS_FOCUSED);
        RedrawRows(m_iFocusRow);
    }

    return TRUE;
}

BOOL CReportCtrl::HasChildren(HTREEITEM hItem)
{
    ASSERT(m_dwStyle & RVS_TREEVIEW);
    return ((LPTREEITEM) hItem)->lptiChildren != NULL ? TRUE : FALSE;
}

BOOL CReportCtrl::IsChild(HTREEITEM hParent, HTREEITEM hItem)
{
    ASSERT(m_dwStyle & RVS_TREEVIEW);
    return ((LPTREEITEM) hItem)->lptiParent == (LPTREEITEM) hParent ? TRUE : FALSE;
}

BOOL CReportCtrl::IsDescendent(HTREEITEM hParent, HTREEITEM hItem)
{
    ASSERT(m_dwStyle & RVS_TREEVIEW);

    LPTREEITEM  lptiAncestor = ((LPTREEITEM) hItem)->lptiParent;

    while (lptiAncestor != NULL)
    {
        if (lptiAncestor == (LPTREEITEM) hParent)
            return TRUE;

        lptiAncestor = lptiAncestor->lptiParent;
    }

    return FALSE;
}

CReportHeaderCtrl *CReportCtrl::GetHeaderCtrl()
{
    return m_wndHeader.GetSafeHwnd() != NULL ? &m_wndHeader : NULL;
}

BOOL CReportCtrl::SetReportSubItemListCtrl(CReportSubItemListCtrl *lprsilc)
{
    if (m_lprsilc != NULL)
    {
        m_wndHeader.ModifyProperty(FH_PROPERTY_DROPTARGET, NULL);
        m_lprsilc->SetReportCtrl(NULL);
    }

    m_lprsilc = lprsilc;
    if (m_lprsilc != NULL)
    {
        m_wndHeader.ModifyProperty(FH_PROPERTY_DROPTARGET, (LPARAM) m_lprsilc->m_hWnd);
        m_lprsilc->SetReportCtrl(this);
    }

    return TRUE;
}

CReportSubItemListCtrl *CReportCtrl::GetReportSubItemListCtrl()
{
    return m_lprsilc;
}

BOOL CReportCtrl::SetSortCallback(LPFNRVCOMPARE lpfnrvc, LPARAM lParam)
{
    m_lpfnrvc = lpfnrvc;
    m_lParamCompare = lParam;
    return TRUE;
}

LPFNRVCOMPARE CReportCtrl::GetSortCallback()
{
    return m_lpfnrvc;
}

BOOL CReportCtrl::GetSortSettings(INT *lpiSubItem, BOOL *lpbAscending)
{
    INT iColumn = m_wndHeader.GetSortColumn(lpbAscending);
    if (iColumn >= 0)
    {
        *lpiSubItem = GetSubItemFromColumn(iColumn);
        return TRUE;
    }

    *lpiSubItem = -1;
    return FALSE;
}

BOOL CReportCtrl::WriteProfile(LPCTSTR lpszSection, LPCTSTR lpszEntry, LPCTSTR lpszData)
{
    CString str, strProfile;

    if (lpszData)
        strProfile = lpszData;
    else
    {
        UnindentFirstColumn();

        if (m_bColumnsReordered)
            ReorderColumns();

        INT i;
        INT iSubItems = m_arraySubItems.GetSize();
        INT iColumns = m_arrayColumns.GetSize();

        strProfile.Format(_T("(%d,%d)"), iSubItems, iColumns);

        for (i = 0; i < iSubItems; i++)
        {
            str.Format(_T(" %d"), m_arraySubItems[i].iWidth);
            strProfile += str;
        }

        for (i = 0; i < iColumns; i++)
        {
            HDITEM  hdi;
            hdi.mask = HDI_LPARAM;
            m_wndHeader.GetItem(m_arrayColumns[i], &hdi);

            str.Format(_T(" %d"), hdi.lParam);
            strProfile += str;
        }

        IndentFirstColumn();
    }

    CWinApp *pApp = AfxGetApp();
    return pApp->WriteProfileString(lpszSection, lpszEntry, strProfile);
}

BOOL CReportCtrl::GetProfile(LPCTSTR lpszSection, LPCTSTR lpszEntry, LPTSTR lpszDefault)
{
    CString str, strProfile;

    CWinApp *pApp = AfxGetApp();
    strProfile = pApp->GetProfileString(lpszSection, lpszEntry);
    if (strProfile.IsEmpty())
        return FALSE;
    if (lpszDefault)
    {
        ASSERT(int(_tcslen(lpszDefault)) == strProfile.GetLength());
        if (int(_tcslen(lpszDefault)) != strProfile.GetLength())
            return FALSE;
        _tcscpy(lpszDefault, strProfile);
    }
    else
    {
        UnindentFirstColumn();

        LPTSTR  lpsz = strProfile.GetBuffer(0);

        INT     i;
        INT     iSubItems;
        INT     iColumns;

        iSubItems = _tcstol(++lpsz, &lpsz, 10);
        iColumns = _tcstol(++lpsz, &lpsz, 10);

        if (iSubItems != m_arraySubItems.GetSize())
            return FALSE;

        if (iColumns == 0)
            return FALSE;

        for (i = 0; i < iSubItems; i++)
            m_arraySubItems[i].iWidth = _tcstol(++lpsz, &lpsz, 10);

        for (i = 0; i < iColumns; i++)
            ActivateSubItem(_tcstol(++lpsz, &lpsz, 10), i);

        ReorderColumns();
    }

    return TRUE;
}

// CReportCtrl operations
BOOL CReportCtrl::ModifyStyle(DWORD dwRemove, DWORD dwAdd, UINT nFlags)
{
    DWORD   dwStyle = CWnd::GetStyle();

    ASSERT(!(dwRemove & (RVS_OWNERDATA | RVS_TREEVIEW)));   // Can't dynamically remove this styles
    ASSERT(!(dwAdd & (RVS_OWNERDATA | RVS_TREEVIEW)));      // Can't dynamically add this styles

    if (m_hEditWnd != NULL)
        EndEdit();

    if (CWnd::ModifyStyle(dwRemove, dwAdd, nFlags))
    {
        if (!(dwStyle & RVS_SHOWHGRID) && dwAdd & RVS_SHOWHGRID)
            m_iDefaultHeight++;

        if (dwStyle & RVS_SHOWHGRID && dwRemove & RVS_SHOWHGRID)
            m_iDefaultHeight--;

        if (dwRemove & RVS_FOCUSSUBITEMS)
            m_iFocusColumn = -1;

        m_dwStyle = CWnd::GetStyle();

        Layout();
        return TRUE;
    }

    return FALSE;
}

INT CReportCtrl::DefineSubItem(INT iSubItem, LPRVSUBITEM lprvs, BOOL bUpdateList)
{
    INT i;
    INT iSubItems = m_arraySubItems.GetSize();

    ASSERT(iSubItem <= iSubItems);
    ASSERT(lprvs->lpszText != NULL);                        // Must supply (descriptive) text for subitem selector
    ASSERT(_tcslen(lprvs->lpszText) < FLATHEADER_TEXT_MAX);

    try
    {
        SUBITEM subitem;

        subitem.nFormat = lprvs->nFormat;
        subitem.iWidth = lprvs->iWidth < 0 ? m_iDefaultWidth : lprvs->iWidth;
        subitem.iMinWidth = lprvs->iMinWidth;
        subitem.iMaxWidth = lprvs->iMaxWidth;
        subitem.iImage = lprvs->nFormat & RVCF_IMAGE ? lprvs->iImage : 0;
        subitem.strText = lprvs->lpszText;

        m_arraySubItems.InsertAt(iSubItem, subitem);

        HDITEM  hdi;
        hdi.mask = HDI_LPARAM;

        INT iHeaderItems = m_arrayColumns.GetSize();
        for (i = 0; i < iHeaderItems; i++)
        {
            if (m_wndHeader.GetItem(m_arrayColumns[i], &hdi))
                if (hdi.lParam >= iSubItem)
                {
                    hdi.lParam++;
                    m_wndHeader.SetItem(m_arrayColumns[i], &hdi);
                }
        }

        VERIFY(m_itemEdit.rdData.InsertSubItem(iSubItem, -1, -1, -1, -1, NULL));

        if (!(m_dwStyle & RVS_OWNERDATA))
        {
            INT iItems = m_arrayItems.GetSize();
            for (i = 0; i < iItems; i++)
                VERIFY(m_arrayItems[i].rdData.InsertSubItem(iSubItem, -1, -1, -1, -1, NULL));
        }

        if (bUpdateList && m_lprsilc != NULL)
            m_lprsilc->UpdateList();

        return iSubItem;
    }
    catch(CMemoryException * e)
    {
        e->Delete();
        return -1;
    }
}

BOOL CReportCtrl::RedefineSubItem(INT iSubItem, LPRVSUBITEM lprvs, BOOL bUpdateList)
{
    INT iSubItems = m_arraySubItems.GetSize();

    ASSERT(iSubItem <= iSubItems);
    ASSERT(lprvs->lpszText != NULL);                        // Must supply (descriptive) text for subitem selector
    ASSERT(_tcslen(lprvs->lpszText) < FLATHEADER_TEXT_MAX);

    try
    {
        SUBITEM subitem;

        subitem.nFormat = lprvs->nFormat;
        subitem.iWidth = lprvs->iWidth < 0 ? m_iDefaultWidth : lprvs->iWidth;
        subitem.iMinWidth = lprvs->iMinWidth;
        subitem.iMaxWidth = lprvs->iMaxWidth;
        subitem.iImage = lprvs->nFormat & RVCF_IMAGE ? lprvs->iImage : 0;
        subitem.strText = lprvs->lpszText;

        m_arraySubItems.SetAt(iSubItem, subitem);

        if (bUpdateList && m_lprsilc != NULL)
            m_lprsilc->UpdateList();

        return SetSubItemWidth(iSubItem, lprvs->iWidth);
    }
    catch(CMemoryException * e)
    {
        e->Delete();
        return FALSE;
    }
}

BOOL CReportCtrl::UndefineSubItem(INT iSubItem)
{
    ASSERT(iSubItem >= 0);
    ASSERT(iSubItem < m_arraySubItems.GetSize());           // Specify a valid subitem

    VERIFY(!DeactivateSubItem(iSubItem));
    m_arraySubItems.RemoveAt(iSubItem);

    HDITEM  hdi;
    hdi.mask = HDI_LPARAM;

    INT i;
    INT iHeaderItems = m_arrayColumns.GetSize();
    for (i = 0; i < iHeaderItems; i++)
    {
        if (m_wndHeader.GetItem(m_arrayColumns[i], &hdi))
            if (hdi.lParam > iSubItem)
            {
                hdi.lParam--;
                m_wndHeader.SetItem(m_arrayColumns[i], &hdi);
            }
    }

    VERIFY(m_itemEdit.rdData.DeleteSubItem(iSubItem));

    if (!(m_dwStyle & RVS_OWNERDATA))
    {
        INT iItems = m_arrayItems.GetSize();
        for (i = 0; i < iItems; i++)
            VERIFY(m_arrayItems[i].rdData.DeleteSubItem(iSubItem));
    }

    if (m_lprsilc != NULL)
        m_lprsilc->UpdateList();

    return TRUE;
}

void CReportCtrl::UndefineAllSubItems()
{
    while (m_arraySubItems.GetSize() > 0)
        UndefineSubItem(0);
}

INT CReportCtrl::AddItem(LPCTSTR lpszText, INT iImage, INT iCheck, INT iTextColor)
{
    INT iItems = m_arrayItems.GetSize();
    return InsertItem(iItems, lpszText, iImage, iCheck, iTextColor);
}

INT CReportCtrl::AddItem(LPRVITEM lprvi)
{
    INT iItems = m_arrayItems.GetSize();

    if (lprvi == NULL)
    {
        INT     iSubItem = 0, iSubItems = m_arraySubItems.GetSize();

        RVITEM  rvi;
        TCHAR   szText[REPORTCTRL_MAX_TEXT];

        rvi.iItem = RVI_EDIT;
        rvi.iSubItem = 0;
        rvi.nMask = RVIM_TEXT;
        rvi.lpszText = szText;
        rvi.iTextMax = REPORTCTRL_MAX_TEXT;
        VERIFY(GetItem(&rvi));

        rvi.iItem = iItems;

        INT iItem = InsertItem(&rvi);
        if (iItem != RVI_INVALID)
        {
            for (iSubItem = 1; iSubItem < iSubItems; iSubItem++)
            {
                rvi.iItem = RVI_EDIT;
                rvi.iSubItem = iSubItem;
                rvi.nMask = RVIM_TEXT;
                rvi.lpszText = szText;
                rvi.iTextMax = REPORTCTRL_MAX_TEXT;
                VERIFY(GetItem(&rvi));

                rvi.iItem = iItem;
                VERIFY(SetItem(&rvi));
            }
        }

        return iItem;
    }
    else
        return InsertItem(lprvi);
}

INT CReportCtrl::InsertItem(INT iItem, LPCTSTR lpszText, INT iImage, INT iCheck, INT iTextColor)
{
    RVITEM  rvi;
    rvi.nMask = RVIM_TEXT;
    rvi.iItem = iItem;
    rvi.iSubItem = 0;
    rvi.lpszText = (LPTSTR) lpszText;

    rvi.iTextColor = iTextColor;
    rvi.iImage = iImage;
    rvi.iCheck = iCheck;

    if (iTextColor >= 0)
        rvi.nMask |= RVIM_TEXTCOLOR;

    if (iImage >= 0)
        rvi.nMask |= RVIM_IMAGE;

    if (iCheck >= 0)
        rvi.nMask |= RVIM_CHECK;

    return InsertItem(&rvi);
}

INT CReportCtrl::InsertItem(LPRVITEM lprvi)
{
    if (m_dwStyle & RVS_OWNERDATA || m_dwStyle & RVS_TREEVIEW)
        return RVI_INVALID;                             // Use set SetItemCount() when using OWNERDATA style

    // Use tree item variants when using TREEVIEW style
    INT iItem = InsertItemImpl(lprvi);
    if (iItem != RVI_INVALID)
    {
        m_bUpdateItemMap = TRUE;
        m_iVirtualHeight++;

        if (m_iFocusRow >= RVI_FIRST && m_iFocusRow >= GetRowFromItem(iItem))
            m_iFocusRow++;
    }

    return iItem;
}

BOOL CReportCtrl::DeleteItem(INT iItem)
{
    if (m_dwStyle & RVS_OWNERDATA || m_dwStyle & RVS_TREEVIEW)
        return RVI_INVALID;                             // Use set SetItemCount() when using OWNERDATA style

    // Use tree item variants when using TREEVIEW style
    if (iItem < RVI_FIRST)
        return FALSE;

    ASSERT(iItem <= m_arrayItems.GetSize());

    INT     iRow = GetRowFromItem(iItem);
    BOOL    bResult = DeleteItemImpl(iItem);

    m_bUpdateItemMap = TRUE;
    ASSERT(m_iVirtualHeight > 0);
    m_iVirtualHeight--;

    if (m_iFocusRow >= iRow)
        m_iFocusRow--;

    m_iFocusRow = m_iFocusRow >= m_iVirtualHeight ? m_iVirtualHeight - 1 : m_iFocusRow;

    INT iRows = m_arrayRows.GetSize();
    INT iFirst = GetScrollPos32(SB_VERT), iLast;
    GetVisibleRows(TRUE, &iFirst, &iLast);

    if (iRow <= iFirst || iLast >= iRows - 1)
        GetVisibleRows(TRUE, &iFirst, &iLast, TRUE);

    ScrollWindow(SB_VERT, iFirst > RVI_INVALID ? iFirst : 0);

    return bResult;
}

HTREEITEM CReportCtrl::InsertItem(LPCTSTR lpszText, INT iImage, INT iCheck, INT iTextColor, HTREEITEM hParent,
                                  HTREEITEM hInsertAfter, INT iSubItem)
{
    RVITEM  rvi;
    rvi.nMask = RVIM_TEXT;
    rvi.lpszText = (LPTSTR) lpszText;

    rvi.iTextColor = iTextColor;
    rvi.iImage = iImage;
    rvi.iCheck = iCheck;

    if (iTextColor >= 0)
        rvi.nMask |= RVIM_TEXTCOLOR;

    if (iImage >= 0)
        rvi.nMask |= RVIM_IMAGE;

    if (iCheck >= 0)
        rvi.nMask |= RVIM_CHECK;

    return InsertItem(&rvi, hParent, hInsertAfter, iSubItem);
}

HTREEITEM CReportCtrl::InsertItem(LPRVITEM lprvi, HTREEITEM hParent, HTREEITEM hInsertAfter, INT iSubItem)
{
    if (!(m_dwStyle & RVS_TREEVIEW))
        return NULL;                                    // TREEVIEW style must be set to use this style.

    LPTREEITEM  lptiFocus = GetTreeFocus();

    LPTREEITEM  lpti = new TREEITEM;
    if (lpti != NULL)
    {
        lprvi->iItem = m_arrayItems.GetSize();
        lprvi->iSubItem = 0;

        lpti->iItem = InsertItemImpl(lprvi);
        lpti->iSubItem = iSubItem;

        if (lpti->iItem != RVI_INVALID)
        {
            LPTREEITEM  lptiParent = hParent == RVTI_ROOT ? &m_tiRoot : (LPTREEITEM) hParent;

            InsertTree(lprvi->iSubItem, lptiParent, (LPTREEITEM) hInsertAfter, lpti, TRUE);

            m_arrayItems[lpti->iItem].lptiItem = lpti;
            lpti->iIndent = hParent == RVTI_ROOT ? 1 : lptiParent->iIndent + 1;
            lpti->lptiParent = lptiParent;
        }
        else
        {
            delete lpti;
            lpti = NULL;
        }
    }

    BuildTree();
    SetTreeFocus(lptiFocus);

    return (HTREEITEM) lpti;
}

BOOL CReportCtrl::DeleteItem(HTREEITEM hItem)
{
    if (!(m_dwStyle & RVS_TREEVIEW))
        return FALSE;                                   // TREEVIEW style must be set to use this style.

    LPTREEITEM  lptiFocus = GetTreeFocus();

    if (lptiFocus != NULL && (IsDescendent(hItem, (HTREEITEM) lptiFocus) || (HTREEITEM) lptiFocus == hItem))
    {
        if (lptiFocus->lptiSibling != NULL)
            lptiFocus = lptiFocus->lptiSibling;
        else if (lptiFocus->lptiParent != &m_tiRoot)
            lptiFocus = lptiFocus->lptiParent;
        else
            lptiFocus = NULL;
    }

    BOOL    bResult = DeleteItemImpl((LPTREEITEM) hItem, FALSE);

    BuildTree();
    SetTreeFocus(lptiFocus);

    return bResult;
}

BOOL CReportCtrl::DeleteAllItems()
{
    BOOL    bResult = TRUE;

    if (m_dwStyle & RVS_OWNERDATA)                      // Use set SetItemCount() when using this style
        return FALSE;

    if (m_dwStyle & RVS_TREEVIEW)
    {
        bResult = DeleteItemImpl(&m_tiRoot, TRUE);
    }
    else
    {
        RVITEM  rvi;
        for (int iItem = 0; iItem < m_arrayItems.GetSize(); iItem++)
        {
            rvi.nMask = RVIM_LPARAM;
            rvi.iItem = iItem;
            GetItem(&rvi);
            Notify(RVN_ITEMDELETED, iItem, 0, 0, rvi.lParam);
        }
    }

    m_arrayRows.RemoveAll();
    m_arrayItems.RemoveAll();
    m_listSelection.RemoveAll();

    m_bUpdateItemMap = TRUE;
    m_iVirtualHeight = 0;
    m_iFocusRow = RVI_INVALID;

    ScrollWindow(SB_VERT, 0);
    return bResult;
}

void CReportCtrl::RedrawItems(INT iFirst, INT iLast)
{
    CRect   rect;

    if (iFirst == iLast || iLast == RVI_INVALID)
    {
        GetItemRect(iFirst, -1, rect);

        if (m_bPreview)
            rect.bottom += GetItemStruct(iFirst, -1, RVIM_PREVIEW).nPreview;

        rect.OffsetRect(GetScrollPos32(SB_HORZ), 0);
    }
    else
        rect = m_rectReport;

    InvalidateRect(rect);
}

BOOL CReportCtrl::EnsureVisible(INT iItem, BOOL bUnobstructed)
{
    INT iFirst = GetScrollPos32(SB_VERT), iLast;
    INT iRow = GetRowFromItem(iItem);
    if (iRow < RVI_FIRST)
        return FALSE;

    INT iVirtualRow = GetVirtualRow(iRow);
    GetVisibleRows(bUnobstructed, &iFirst, &iLast);

    if (iVirtualRow < iFirst)
        ScrollWindow(SB_VERT, iRow);

    if (iVirtualRow > iLast)
    {
        iLast = iRow;
        GetVisibleRows(bUnobstructed, &iFirst, &iLast, TRUE);
        ScrollWindow(SB_VERT, iFirst);
    }

    return TRUE;
}

BOOL CReportCtrl::EnsureVisible(HTREEITEM hItem, BOOL bUnobstructed)
{
    LPTREEITEM  lpti = (LPTREEITEM) hItem;
    while (lpti->lptiParent != &m_tiRoot)
    {
        Expand((HTREEITEM) lpti->lptiParent, RVE_EXPAND);
        lpti = lpti->lptiParent;
    }

    lpti = (LPTREEITEM) hItem;
    return EnsureVisible(lpti->iItem, bUnobstructed);
}

INT CReportCtrl::InsertColor(INT iIndex, COLORREF crColor)
{
    try
    {
        m_arrayColors.InsertAt(iIndex, crColor);
        if (CreatePalette())
            return iIndex;

        return -1;
    }
    catch(CMemoryException * e)
    {
        e->Delete();
        return -1;
    }
}

BOOL CReportCtrl::DeleteColor(INT iIndex)
{
    if (iIndex >= m_arrayColors.GetSize())
        return FALSE;

    m_arrayColors.RemoveAt(iIndex);
    CreatePalette();

    return TRUE;
}

void CReportCtrl::DeleteAllColors()
{
    m_arrayColors.RemoveAll();
    CreatePalette();
}

INT CReportCtrl::HitTest(LPRVHITTESTINFO lprvhti)
{
    ASSERT(lprvhti);

    lprvhti->nFlags = 0;
    lprvhti->iItem = RVI_INVALID;
    lprvhti->iSubItem = -1;

    lprvhti->hItem = NULL;

    lprvhti->iRow = RVI_INVALID;
    lprvhti->iColumn = -1;

    CRect   rectItem = m_rectEdit;
    rectItem.bottom -= GetSystemMetrics(SM_CYFIXEDFRAME) * 2;

    if (rectItem.PtInRect(lprvhti->point) || m_rectReport.PtInRect(lprvhti->point))
    {
        INT     iHPos = GetScrollPos32(SB_HORZ);
        INT     iFirst = GetScrollPos32(SB_VERT), iLast;

        BOOL    bCheckItem = FALSE;

        rectItem.left = -iHPos;
        rectItem.right = rectItem.left + m_iVirtualWidth;

        if (rectItem.PtInRect(lprvhti->point))
        {
            iFirst = RVI_EDIT;
            lprvhti->iItem = RVI_EDIT;
            lprvhti->nFlags |= RVHT_ONITEMEDIT;
            bCheckItem = TRUE;
        }
        else if (!bCheckItem && GetVisibleRows(FALSE, &iFirst, &iLast))
        {
            ITEM    item;
/*$off*/
        rectItem.SetRect(
          m_rectReport.left-iHPos,                  m_rectReport.top,
          m_rectReport.left-iHPos+m_iVirtualWidth,  m_rectReport.top
          );
/*$on*/
            int iRows = m_dwStyle & RVS_OWNERDATA ? m_iVirtualHeight : m_arrayRows.GetSize();
            for (INT iTop = iFirst; iFirst <= iLast && iFirst < iRows; iFirst++)
            {
                int iRow = GetRowFromVirtualRow(iFirst);
                GetItemFromRow(iRow, item);
                rectItem.bottom = rectItem.top + m_iDefaultHeight;
                if (GetCategoryForRow(iRow, iFirst == iTop) >= 0)
                {
                    if (rectItem.PtInRect(lprvhti->point))
                    {
                        lprvhti->nFlags = ((lprvhti->point.x <= (m_iCategoryImage >= 0 ? m_sizeImage.cx : m_iDefaultHeight))) ? RVHT_ONCATIMAGE : RVHT_ONCATITEM;
                        lprvhti->iItem = GetItemForRow(iRow);
                        return lprvhti->iItem;
                    }

                    if (!(m_dwStyle & RVS_OWNERDATA) && (RVIS_HIDDEN & GetState(iRow)))
                    {
                        rectItem.top = rectItem.bottom;
                        if (iLast + 1 < iRows)
                            iLast++;
                        continue;
                    }
                    else
                    {
                        rectItem.top = rectItem.bottom;
                        rectItem.bottom += m_iDefaultHeight + (m_bPreview ? item.nPreview : 0);
                    }
                }
                else
                {
                    if ((!(m_dwStyle & RVS_OWNERDATA) && (RVIS_HIDDEN & GetState(iRow))))
                    {
                        if (iLast + 1 < iRows)
                            iLast++;
                        continue;
                    }
                    else
                        rectItem.bottom += (m_bPreview ? item.nPreview : 0);
                }

                if (rectItem.PtInRect(lprvhti->point))
                    break;
                rectItem.top = rectItem.bottom;
            }

            if (iFirst <= iLast)
            {
                lprvhti->iItem = GetItemForRow(GetRowFromVirtualRow(iFirst));
                lprvhti->iRow = GetRowFromItem(lprvhti->iItem);
                if (m_dwStyle & RVS_TREEVIEW && lprvhti->iItem >= RVI_FIRST)
                    lprvhti->hItem = (HTREEITEM) m_arrayItems[lprvhti->iItem].lptiItem;
                if (m_bPreview && item.nPreview)
                {
                    CRect   rectPreview(rectItem);
                    rectPreview.top += m_iDefaultHeight;

                    if (rectPreview.PtInRect(lprvhti->point))
                    {
                        lprvhti->nFlags |= RVHT_ONITEMPREVIEW;
                        return lprvhti->iItem;
                    }
                }

                bCheckItem = TRUE;
            }
        }

        if (bCheckItem == TRUE)
        {
            bCheckItem = FALSE;
            if (m_dwStyle & RVS_TREEVIEW && m_arrayItems[lprvhti->iItem].lptiItem->iSubItem >= 0)
            {
                LPTREEITEM  lpti = m_arrayItems[lprvhti->iItem].lptiItem;

                lprvhti->iSubItem = lpti->iSubItem;
                lprvhti->iColumn = 0;

                rectItem.OffsetRect(iHPos, 0);

                lprvhti->rect = rectItem;
                lprvhti->rect.right = lprvhti->rect.left + m_iVirtualWidth;
                lprvhti->rect.bottom = rectItem.top + m_iDefaultHeight;

                bCheckItem = TRUE;
            }
            else
            {
                INT     iHeaderItem;
                INT     iHeaderItems = m_arrayColumns.GetSize();

                HDITEM  hdi;
                hdi.mask = HDI_WIDTH | HDI_LPARAM;

                rectItem.right = rectItem.left;

                for (iHeaderItem = 0; iHeaderItem < iHeaderItems; iHeaderItem++)
                {
                    CRect   rect;
                    m_wndHeader.GetItemRect(iHeaderItem, &rect);

                    CPoint  point = lprvhti->point;
                    point.y = (rect.bottom - rect.top) / 2;
                    point.x += iHPos;

                    if (point.x >= rect.left && point.x < rect.right)
                    {
                        rectItem.left = rect.left;
                        rectItem.right = rect.right;
                        break;
                    }
                }

                if (iHeaderItem < iHeaderItems)
                {
                    m_wndHeader.GetItem(iHeaderItem, &hdi);
                    lprvhti->iSubItem = hdi.lParam;
                    lprvhti->iColumn = GetColumnFromSubItem(lprvhti->iSubItem);
                    bCheckItem = TRUE;
                }
            }

            if (bCheckItem == TRUE)
            {
                RVITEM  rvi;
                rvi.iItem = GetItemForRow(GetRowFromVirtualRow(iFirst));
                rvi.iSubItem = lprvhti->iSubItem;
                GetItem(&rvi);
                if (lprvhti->iColumn == 0 && rvi.nMask & RVIM_INDENT && rvi.iIndent >= 0)
                {
                    rectItem.left += rvi.iIndent * m_iDefaultHeight;
                    if (rvi.nState & RVIS_TREEMASK)
                    {
                        if (lprvhti->point.x >= rectItem.left - m_iDefaultHeight && lprvhti->point.x < rectItem.left)
                        {
                            lprvhti->rect = rectItem;
                            lprvhti->rect.left -= iHPos + m_iDefaultHeight;
                            lprvhti->rect.right -= lprvhti->rect.left;
                            lprvhti->rect.bottom = rectItem.top + m_iDefaultHeight;

                            lprvhti->nFlags |= RVHT_ONITEMTREEBOX;
                            return lprvhti->iItem;
                        }
                    }
                }

                rectItem.OffsetRect(-iHPos, 0);
                lprvhti->rect = rectItem;
                lprvhti->rect.bottom = rectItem.top + m_iDefaultHeight;
                rectItem.right = rectItem.left + m_iSpacing;
                if (rvi.nMask & RVIM_IMAGE)
                {
                    rectItem.right += m_sizeImage.cx + m_iSpacing;
                    if (lprvhti->point.x < rectItem.right)
                    {
                        lprvhti->nFlags |= RVHT_ONITEMIMAGE;
                        return lprvhti->iItem;
                    }
                }

                if (rvi.nMask & RVIM_CHECK)
                {
                    rectItem.right += m_sizeCheck.cx + m_iSpacing;
                    if (lprvhti->point.x < rectItem.right)
                    {
                        lprvhti->nFlags |= RVHT_ONITEMCHECK;
                        return lprvhti->iItem;
                    }
                }

                if (lprvhti->point.x >= rectItem.right && lprvhti->point.x < lprvhti->rect.right - m_iSpacing)
                {
                    lprvhti->nFlags |= RVHT_ONITEMTEXT;
                    return lprvhti->iItem;
                }

                lprvhti->nFlags = RVHT_NOWHERE;
            }
            else
                lprvhti->nFlags = RVHT_NOWHERE;
        }
        else
            lprvhti->nFlags = RVHT_NOWHERE;
    }
    else
    {
        lprvhti->nFlags |= lprvhti->point.y < m_rectReport.top ? RVHT_ABOVE : 0;
        lprvhti->nFlags |= lprvhti->point.y > m_rectReport.bottom ? RVHT_BELOW : 0;
        lprvhti->nFlags |= lprvhti->point.x < m_rectReport.left ? RVHT_TOLEFT : 0;
        lprvhti->nFlags |= lprvhti->point.x > m_rectReport.right ? RVHT_TORIGHT : 0;
    }

    return lprvhti->iItem;
}

BOOL CReportCtrl::ResortItems()
{
    INT     iColumn;
    INT     iSubItem;
    BOOL    bAscending;

    iColumn = m_wndHeader.GetSortColumn(&bAscending);
    iSubItem = GetSubItemFromColumn(iColumn);
    return SortItems(iSubItem, bAscending);
}

BOOL CReportCtrl::SortItems(INT iSubItem, BOOL bAscending)
{
    CWaitCursor wait;
    m_cBaseTime = COleDateTime::GetCurrentTime();
    if (m_dwStyle & RVS_OWNERDATA)
        return FALSE;                                   // Can't sort if data is managed by owner

    BOOL    bCategoryAscending;
    INT     iCategorySubItem = GetCategorySubItem(&bCategoryAscending);
    if (iSubItem < 0 && iCategorySubItem < 0)
        return FALSE;

    INT iColumn = GetColumnFromSubItem(iSubItem);
    m_bEarlier = m_bLater = false;
    if (iColumn >= 0)
        m_wndHeader.SetSortColumn(m_arrayColumns[iColumn], bAscending);

    m_bSorting = TRUE;
    if (m_dwStyle & RVS_TREEVIEW)
    {
        LPTREEITEM  lptiFocus = GetTreeFocus();

        if (iSubItem >= 0)
            TreeSort(iSubItem, &m_tiRoot, bAscending, TRUE);
        BuildTree();

        // TODO: extend tree sort to do both in one pass
        if (iCategorySubItem >= 0)
            TreeSort(iCategorySubItem, &m_tiRoot, bCategoryAscending, TRUE);
        BuildTree();
        SetTreeFocus(lptiFocus);
    }
    else
    {
        INT iRows = m_arrayRows.GetSize();
        if (!iRows)
            return FALSE;

        INT iFocusItem = GetItemForRow(m_iFocusRow);
        if (iSubItem >= 0)
            ShellSort(iSubItem, 0, iRows - 1, bAscending);
        if (iCategorySubItem >= 0)
            ShellSort(iCategorySubItem, 0, iRows - 1, bCategoryAscending);

        m_bUpdateItemMap = TRUE;

        if (iFocusItem >= 0)
        {
            m_iFocusRow = GetRowFromItem(iFocusItem);

            INT iFirst = m_iFocusRow, iLast;
            GetVisibleRows(TRUE, &iFirst, &iLast);
            GetVisibleRows(TRUE, &iFirst, &iLast, TRUE);
            ScrollWindow(SB_VERT, iFirst);
        }
    }

    m_bSorting = FALSE;
    m_bUpdateItemMap = TRUE;
    Invalidate();
    return TRUE;
}

INT CReportCtrl::CompareItems(INT iItem1, INT iSubItem1, INT iItem2, INT iSubItem2, BOOL bCategory)
{
    ASSERT(!(m_dwStyle & RVS_OWNERDATA));               // Can't sort if data is managed by owner

    if (m_lpfnrvc == NULL)
    {
        RVITEM  rvi1, rvi2;

        TCHAR   szText1[REPORTCTRL_MAX_TEXT], szText2[REPORTCTRL_MAX_TEXT];

        rvi1.nMask = RVIM_TEXT;
        rvi1.iItem = iItem1;
        rvi1.iSubItem = iSubItem1;
        rvi1.lpszText = szText1;
        rvi1.iTextMax = REPORTCTRL_MAX_TEXT;
        VERIFY(GetItem(&rvi1));

        rvi2.nMask = RVIM_TEXT;
        rvi2.iItem = iItem2;
        rvi2.iSubItem = iSubItem2;
        rvi2.lpszText = szText2;
        rvi2.iTextMax = REPORTCTRL_MAX_TEXT;
        VERIFY(GetItem(&rvi2));

        if (!(rvi1.nMask & RVIM_TEXT || rvi2.nMask & RVIM_TEXT))
        {
            if ((rvi1.nMask & RVIM_CHECK || rvi2.nMask & RVIM_CHECK))
            {
                if (rvi1.iCheck < rvi2.iCheck)
                    return -1;
                else if (rvi1.iCheck > rvi2.iCheck)
                    return 1;
                else
                    return 0;
            }

            if (rvi1.iImage < rvi2.iImage)
                return -1;
            else if (rvi1.iImage > rvi2.iImage)
                return 1;
            else
                return 0;
        }
        else
        {
            CString str1 = szText1;
            CString str2 = szText2;
            SUBITEM &subitem1 = m_arraySubItems[iSubItem1];
            SUBITEM &subitem2 = m_arraySubItems[iSubItem2];
            ASSERT(subitem1.nFormat == subitem2.nFormat);

            // See if we're comparing dates, not simply strings
            if (subitem1.nFormat & RVCF_EX_HASDATE)
            {
                if
                (
                    bCategory
                &&  CCategoryTime(szText1).SameCategoryAs(CCategoryTime(szText2), m_cBaseTime, m_bEarlier & m_bLater)
                ) return 0;

                COleDateTime    date1;
                BOOL            bRes = date1.ParseDateTime(szText1);

                // If the timestamp cannot be successfully parsed, consider it a text string
                if (!bRes)
                    return _tcscmp(szText1, szText2);

                COleDateTime    date2;
                bRes = date2.ParseDateTime(szText2);

                // If the timestamp cannot be successfully parsed, consider it a text string
                if (!bRes)
                    return _tcscmp(szText1, szText2);

                // Now compare to date classes
                m_bEarlier |= date1 < m_cBaseTime || date2 < m_cBaseTime;
                m_bLater |= date1 > m_cBaseTime || date2 > m_cBaseTime;
                if (date1 < date2)
                    return -1;
                else if (date2 < date1)
                    return 1;
                else
                    return 0;
            }

            int nComp;

            // compare the two strings, notice: in this case, "xxxx10" comes after "xxxx2"
            {
                CString tmpStr1, tmpStr2;
                #define numbers _T("0123456789")
                int     index = str1.FindOneOf(numbers);
                if (index != -1)
                    tmpStr1 = str1.Right(str1.GetLength() - index);
                index = str2.FindOneOf(numbers);
                if (index != -1)
                    tmpStr2 = str2.Right(str2.GetLength() - index);

                tmpStr1 = tmpStr1.SpanIncluding(numbers);
                tmpStr2 = tmpStr2.SpanIncluding(numbers);

                if ((tmpStr1 == _T("")) && (tmpStr2 == _T("")))
                    nComp = str1.CompareNoCase(str2);
                else
                {
                    int num1 = _ttoi(tmpStr1);
                    int num2 = _ttoi(tmpStr2);

                    tmpStr1 = str1.SpanExcluding(numbers);
                    tmpStr2 = str2.SpanExcluding(numbers);

                    if (tmpStr1 == tmpStr2)
                    {
                        if (num1 > num2)
                            nComp = 1;
                        else if (num1 < num2)
                            nComp = -1;
                        else
                            nComp = str1.CompareNoCase(str2);
                    }
                    else
                        nComp = str1.CompareNoCase(str2);
                }
            }

            return nComp;
        }
    }
    else
        return m_lpfnrvc(iItem1, iSubItem1, iItem2, iSubItem2, m_lParamCompare);
}

INT CReportCtrl::FindItem(LPRVFINDINFO lprvfi, INT iSubItem, INT iStart)
{
    if (m_dwStyle & RVS_OWNERDATA)
        return FALSE;                                   // Can't find data if data is managed by owner

    INT     iItems = m_arrayItems.GetSize();
    INT     iItem = iStart + (lprvfi->nFlags & RVFI_UP ? -1 : 1);

    RVITEM  rvi;
    TCHAR   szText[REPORTCTRL_MAX_TEXT];
    rvi.lpszText = szText;
    rvi.iTextMax = REPORTCTRL_MAX_TEXT;

    BOOL    bMatch;

    while (iItem != iStart)
    {
        if (iItem >= iItems)
        {
            if (lprvfi->nFlags & RVFI_WRAP)
                iItem = RVI_EDIT;
            else
                break;
        }

        if (iItem < RVI_EDIT)
        {
            if (lprvfi->nFlags & RVFI_WRAP)
                iItem = iItems - 1;
            else
                break;
        }

        rvi.nMask = RVIM_TEXT | RVIM_IMAGE | RVIM_CHECK | RVIM_LPARAM;
        rvi.iItem = iItem;
        rvi.iSubItem = iSubItem;
        VERIFY(GetItem(&rvi));

        bMatch = TRUE;
        if (lprvfi->nFlags & RVFI_TEXT && rvi.nMask & RVIM_TEXT)
        {
            INT iMatch;
            if (lprvfi->nFlags & RVFI_PARTIAL)
                iMatch = _tcsncmp(lprvfi->lpszText, rvi.lpszText, _tcslen(lprvfi->lpszText));
            else
                iMatch = _tcscmp(lprvfi->lpszText, rvi.lpszText);

            bMatch &= !iMatch ? TRUE : FALSE;
        }
        else if (lprvfi->nFlags & RVFI_TEXT)
            bMatch = FALSE;

        if (lprvfi->nFlags & RVFI_IMAGE && rvi.nMask & RVIM_IMAGE)
            bMatch &= lprvfi->iImage == rvi.iImage ? TRUE : FALSE;
        else if (lprvfi->nFlags & RVFI_IMAGE)
            bMatch = FALSE;

        if (lprvfi->nFlags & RVFI_CHECK && rvi.nMask & RVIM_CHECK)
            bMatch &= lprvfi->iCheck == rvi.iCheck ? TRUE : FALSE;
        else if (lprvfi->nFlags & RVFI_CHECK)
            bMatch = FALSE;

        if (lprvfi->nFlags & RVFI_LPARAM && rvi.nMask & RVIM_LPARAM)
            bMatch &= lprvfi->lParam == rvi.lParam ? TRUE : FALSE;
        else if (lprvfi->nFlags & RVFI_LPARAM)
            bMatch = FALSE;

        if (bMatch)
            return iItem;

        iItem += lprvfi->nFlags & RVFI_UP ? -1 : 1;
    }

    return RVI_INVALID;
}

BOOL CReportCtrl::Expand(HTREEITEM hItem, UINT nCode)
{
    if (!(m_dwStyle & RVS_TREEVIEW))
        return FALSE;

    LPTREEITEM  lpti = (LPTREEITEM) hItem;
    switch (nCode)
    {
    case RVE_COLLAPSE:
        lpti->bOpen = FALSE;
        break;
    case RVE_EXPAND:
        lpti->bOpen = TRUE;
        break;
    case RVE_TOGGLE:
        lpti->bOpen = !lpti->bOpen;
        break;
    default:
        return FALSE;
    }

    INT iFocusItem = RVI_INVALID;
    if (m_iFocusRow != RVI_INVALID)
    {
        SetState(m_iFocusRow, 0, RVIS_FOCUSED);
        iFocusItem = GetItemForRow(m_iFocusRow);
    }

    BuildTree();

    if (iFocusItem >= RVI_FIRST)
    {
        LPTREEITEM  lptiAncestor = GetVisibleAncestor(m_arrayItems[iFocusItem].lptiItem);
        ASSERT(lptiAncestor != &m_tiRoot && lptiAncestor != NULL);

        m_iFocusRow = GetRowFromItem(lptiAncestor->iItem);
        ASSERT(m_iFocusRow != RVI_INVALID);

        SetState(m_iFocusRow, RVIS_FOCUSED, RVIS_FOCUSED);
        DeselectDescendents(lptiAncestor);
    }

    INT iFirst = GetScrollPos32(SB_VERT), iLast;
    GetVisibleRows(TRUE, &iFirst, &iLast);

    if (m_iFocusRow <= iFirst || iLast >= m_iFocusRow - 1)
        GetVisibleRows(TRUE, &iFirst, &iLast, TRUE);

    ScrollWindow(SB_VERT, iFirst > RVI_INVALID ? iFirst : 0);
    return TRUE;
}

BOOL CReportCtrl::ExpandAll(HTREEITEM hItem, UINT nCode, INT iLevels)
{
    if (!(m_dwStyle & RVS_TREEVIEW))
        return FALSE;

    LPTREEITEM  lpti;

    if (hItem == RVTI_ROOT)
    {
        lpti = &m_tiRoot;
    }
    else
    {
        Expand(hItem, nCode);

        lpti = (LPTREEITEM) hItem;
    }

    return ExpandAllImpl(hItem, nCode, iLevels, 0);
}

void CReportCtrl::FlushCache(INT iItem)
{
    if (!(m_dwStyle & RVS_OWNERDATA || m_bUseItemCacheMap))
        return;

    CacheDelete(iItem);
}

void CReportCtrl::Copy()
{
    CString str;

    if (OpenClipboard() == FALSE || ::EmptyClipboard() == FALSE)
        return;

    HGLOBAL             hMem = NULL, h;

    CArray<INT, INT>    arraySelection;

    SelectionToSortedArray(arraySelection);

    INT     i, iSize = arraySelection.GetSize();
    UINT    nData = 0;

    for (i = 0; i < iSize; i++)
    {
        INT iRow = arraySelection[i];
        INT iItem = GetItemForRow(iRow);
        ASSERT(iItem > RVI_INVALID);

        str = GetItemString(iItem, g_cCBSeparator);

        ASSERT(str.GetLength() > 0);

        if (hMem == NULL)
            h = ::GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE | GMEM_ZEROINIT, str.GetLength() * (sizeof(TCHAR) + 1));
        else
            h = ::GlobalReAlloc(hMem, nData + str.GetLength() * (sizeof(TCHAR) + 1), GMEM_MOVEABLE | GMEM_DDESHARE);

        if (h == NULL)
            break;

        hMem = h;

        LPBYTE  lpx = (LPBYTE)::GlobalLock(hMem);
        ASSERT(lpx != NULL);

        memcpy(lpx + nData, (LPCTSTR) str, str.GetLength() * sizeof(TCHAR));

        ::GlobalUnlock(hMem);

        nData += str.GetLength() * sizeof(TCHAR);
        ((LPTSTR) lpx)[nData / sizeof(TCHAR)] = 0;
    }

    if (hMem != NULL)
        ::SetClipboardData(CF_TEXT, hMem);

    ::CloseClipboard();
}

void CReportCtrl::Cut()
{
}

void CReportCtrl::Paste()
{
}

// CReportCtrl misc
INT CReportCtrl::PreviewHeight(CFont *pFont, UINT nLines)
{
    INT iHeight = -1;

    ASSERT(pFont != NULL);

    CDC *pDC = GetDC();
    if (pDC)
    {
        CFont       *pDCFont = pDC->SelectObject(pFont);
        TEXTMETRIC  tm;
        pDC->GetTextMetrics(&tm);
        pDC->SelectObject(pDCFont);
        ReleaseDC(pDC);

        iHeight = (tm.tmHeight + tm.tmExternalLeading) * nLines;
    }

    return iHeight;
}

INT CReportCtrl::PreviewHeight(CFont *pFont, LPCTSTR lpszText, LPRECT lpRect)
{
    INT iHeight = -1;

    ASSERT(pFont != NULL);

    CDC *pDC = GetDC();
    if (pDC)
    {
        CRect   rect(GetSubItemWidth(0) + 10, 0, m_iVirtualWidth, 0);
        if (lpRect != NULL)
            rect = *lpRect;

        CFont   *pDCFont = pDC->SelectObject(pFont);
        (pDCFont);
        iHeight = pDC->DrawText(lpszText, -1, rect, DT_NOPREFIX | DT_LEFT | DT_CALCRECT | DT_EXPANDTABS | DT_WORDBREAK | DT_NOCLIP);
        ReleaseDC(pDC);
    }

    return iHeight;
}

// CReportCtrl implementation
void CReportCtrl::GetSysColors()
{
    m_crBackground = ::GetSysColor(COLOR_WINDOW);
    m_crBkSelected = ::GetSysColor(COLOR_HIGHLIGHT);
    m_crBkSelectedNoFocus = ::GetSysColor(COLOR_BTNFACE);
    m_crText = ::GetSysColor(COLOR_WINDOWTEXT);
    m_crTextSelected = ::GetSysColor(COLOR_HIGHLIGHTTEXT);
    m_crTextSelectedNoFocus = ::GetSysColor(COLOR_WINDOWTEXT);
    m_crGrid = ::GetSysColor(COLOR_BTNFACE);
    m_cr3DFace = ::GetSysColor(COLOR_3DFACE);
    m_cr3DLight = ::GetSysColor(COLOR_3DLIGHT);
    m_cr3DShadow = ::GetSysColor(COLOR_3DSHADOW);
    m_cr3DHiLight = ::GetSysColor(COLOR_3DHILIGHT);
    m_cr3DDkShadow = ::GetSysColor(COLOR_3DDKSHADOW);
}

UINT CReportCtrl::GetMouseScrollLines()
{
    UINT    nScrollLines = 3;                           // Reasonable default
    HKEY    hKey;

    if (RegOpenKeyEx(HKEY_CURRENT_USER, _T("Control Panel\\Desktop"), 0, KEY_QUERY_VALUE, &hKey) == ERROR_SUCCESS)
    {
        TCHAR   szData[128];
        DWORD   dwKeyDataType;
        DWORD   dwDataBufSize = sizeof(szData);

        if
        (RegQueryValueEx(hKey, _T("WheelScrollLines"), NULL, &dwKeyDataType, (LPBYTE) & szData, &dwDataBufSize) == ERROR_SUCCESS) nScrollLines = _tcstoul(szData, NULL, 10);

        RegCloseKey(hKey);
    }

    return nScrollLines;
}

BOOL CReportCtrl::CreatePalette()
{
    if (m_palette.m_hObject)
        m_palette.DeleteObject();

    INT                 iUserColors = m_arrayColors.GetSize();
    INT                 iBitmapColors = 0;

    DIBSECTION          ds;
    BITMAPINFOHEADER    &bmInfo = ds.dsBmih;

    if ((HBITMAP) m_bitmap != NULL)
    {
        m_bitmap.GetObject(sizeof(ds), &ds);
        iBitmapColors = bmInfo.biClrUsed ? bmInfo.biClrUsed : 1 << bmInfo.biBitCount;
    }

    INT iColors = iUserColors + iBitmapColors;

    if (!iColors)
        return FALSE;

    CClientDC   cdc(NULL);
    if (iColors <= 256)
    {
        UINT        nSize = sizeof(LOGPALETTE) + (sizeof(PALETTEENTRY) * iColors);
        LOGPALETTE  *pLP = (LOGPALETTE *) new BYTE[nSize];

        pLP->palVersion = 0x300;
        pLP->palNumEntries = (WORD) (iColors);

        INT i;
        for (i = 0; i < iUserColors; i++)
        {
            pLP->palPalEntry[i].peRed = GetRValue(m_arrayColors[i]);
            pLP->palPalEntry[i].peGreen = GetGValue(m_arrayColors[i]);
            pLP->palPalEntry[i].peBlue = GetBValue(m_arrayColors[i]);
            pLP->palPalEntry[i].peFlags = 0;
        }

        if (iBitmapColors > 0)
        {
            // Create the palette
            RGBQUAD *pRGB = new RGBQUAD[iBitmapColors];

            CDC     dc;
            dc.CreateCompatibleDC(&cdc);

            dc.SelectObject(&m_bitmap);
            ::GetDIBColorTable(dc, 0, iColors, pRGB);

            for (INT i = 0; i < iBitmapColors; i++)
            {
                pLP->palPalEntry[iUserColors + i].peRed = pRGB[i].rgbRed;
                pLP->palPalEntry[iUserColors + i].peGreen = pRGB[i].rgbGreen;
                pLP->palPalEntry[iUserColors + i].peBlue = pRGB[i].rgbBlue;
                pLP->palPalEntry[iUserColors + i].peFlags = 0;
            }

            delete[] pRGB;
        }

        m_palette.CreatePalette(pLP);

        delete[] pLP;
    }
    else
        m_palette.CreateHalftonePalette(&cdc);

    return TRUE;
}

BOOL CReportCtrl::NotifyHdr(LPNMRVHEADER lpnmrvhdr, UINT nCode, INT iHdrItem)
{
    lpnmrvhdr->hdr.hwndFrom = GetSafeHwnd();
    lpnmrvhdr->hdr.idFrom = GetDlgCtrlID();
    lpnmrvhdr->hdr.code = nCode;

    HDITEM  hdi;
    hdi.mask = HDI_LPARAM;
    hdi.lParam = -1;
    m_wndHeader.GetItem(iHdrItem, &hdi);

    lpnmrvhdr->iSubItem = hdi.lParam;

    return Notify((LPNMREPORTVIEW) lpnmrvhdr);
}

BOOL CReportCtrl::Notify(UINT nCode, INT iItem, INT iSubItem, UINT nState, LPARAM lParam)
{
    NMREPORTVIEW    nmrv;
    nmrv.hdr.hwndFrom = GetSafeHwnd();
    nmrv.hdr.idFrom = GetDlgCtrlID();
    nmrv.hdr.code = nCode;

    nmrv.iItem = iItem;
    nmrv.iSubItem = iSubItem;

    if (m_dwStyle & RVS_TREEVIEW && iItem >= RVI_FIRST)
        nmrv.hItem = (HTREEITEM) m_arrayItems[iItem].lptiItem;

    nmrv.nState = nState;

    nmrv.lParam = lParam;

    return Notify(&nmrv);
}

BOOL CReportCtrl::Notify(UINT nCode, UINT nKeys, LPRVHITTESTINFO lprvhti)
{
    NMREPORTVIEW    nmrv;
    nmrv.hdr.hwndFrom = GetSafeHwnd();
    nmrv.hdr.idFrom = GetDlgCtrlID();
    nmrv.hdr.code = nCode;

    nmrv.nKeys = nKeys;
    nmrv.point = lprvhti->point;
    nmrv.nFlags = lprvhti->nFlags;

    nmrv.iItem = lprvhti->iItem;
    nmrv.iSubItem = lprvhti->iSubItem;

    if (m_dwStyle & RVS_TREEVIEW && lprvhti->iItem >= RVI_FIRST)
        nmrv.hItem = (HTREEITEM) m_arrayItems[lprvhti->iItem].lptiItem;

    if (lprvhti->iItem >= 0 && (!(m_dwStyle & RVS_OWNERDATA)))
        nmrv.lParam = GetItemStruct(lprvhti->iItem, -1).lParam;

    return Notify(&nmrv);
}

BOOL CReportCtrl::Notify(LPNMREPORTVIEW lpnmrv)
{
    INT iCode = 0 - (lpnmrv->hdr.code - RVN_FIRST);

    if (m_uNotifyMask & (1 << iCode))
    {
        CWnd    *pWnd = GetParent();
        if (pWnd)
            return pWnd->SendMessage(WM_NOTIFY, GetDlgCtrlID(), (LPARAM) lpnmrv);
    }

    return FALSE;
}

BOOL CReportCtrl::PreTranslateMessage(MSG *pMsg)
{
    if ((pMsg->message == WM_KEYDOWN) && (pMsg->wParam == VK_RETURN))
    {
        INT iFirst = GetScrollPos32(SB_VERT), iLast;
        GetVisibleRows(TRUE, &iFirst, &iLast);

        NMREPORTVIEW    nmrv;
        nmrv.hdr.hwndFrom = GetSafeHwnd();
        nmrv.hdr.idFrom = GetDlgCtrlID();
        nmrv.hdr.code = RVN_KEYDOWN;

        nmrv.nKeys = VK_RETURN;

        nmrv.iItem = GetItemForRow(m_iFocusRow);
        nmrv.iSubItem = GetSubItemFromColumn(m_iFocusColumn);

        if (nmrv.iItem != RVI_INVALID && (!(m_dwStyle & RVS_OWNERDATA)))
            nmrv.lParam = GetItemStruct(nmrv.iItem, -1).lParam;

        m_bProcessKey = FALSE;

        if (Notify(&nmrv))
            return true;
    }

    return CWnd::PreTranslateMessage(pMsg);
}

void CReportCtrl::Layout()
{
    CRect   rect;
    GetClientRect(rect);
    Layout(rect.Width(), rect.Height());
}

void CReportCtrl::Layout(INT cx, INT cy)
{
    HDLAYOUT    hdl;
    WINDOWPOS   wpos;

    hdl.prc = &m_rectReport;
    hdl.pwpos = &wpos;

    m_rectReport.SetRect(0, 0, cx, cy);

    if (IsWindow(m_wndHeader.GetSafeHwnd()))
    {
        m_wndTip.Hide();
        VERIFY(m_wndHeader.SendMessage(HDM_LAYOUT, 0, (LPARAM) & hdl));

        int iCategoryHdrHeight = GetCategoryHdrHeight();
        if (m_dwStyle & RVS_NOHEADER)
            m_rectHeader.SetRect(wpos.x, 0, wpos.x + wpos.cx, 0);
        else
            m_rectHeader.SetRect(wpos.x, wpos.y, wpos.x + wpos.cx, wpos.y + wpos.cy);

        CCategoryHdr    *ac = GetCategoryHdrCtrl();
        if (ac != NULL)
            VERIFY(ac->SendMessage(HDM_LAYOUT, 0, (LPARAM) & hdl));

        m_rectHeader.OffsetRect(0, iCategoryHdrHeight);
        m_rectReport.top = iCategoryHdrHeight + m_rectHeader.Height();
        m_rectTop = m_rectHeader;

        if (m_dwStyle & RVS_SHOWEDITROW)
        {
            UINT    nFrameHeight = GetSystemMetrics(SM_CYFIXEDFRAME);
            m_rectTop.bottom += m_iDefaultHeight + 2 * nFrameHeight;
            m_rectReport.top += m_iDefaultHeight + 2 * nFrameHeight;

            m_rectEdit = m_rectTop;
            m_rectEdit.top = m_rectHeader.bottom;
        }
        else
            m_rectEdit.SetRectEmpty();

        ScrollWindow(SB_VERT, GetScrollPos32(SB_VERT));
        ScrollWindow(SB_HORZ, GetScrollPos32(SB_HORZ));
    }
}

CReportCtrl::LPITEM CReportCtrl::CacheLookup(INT iItem)
{
    if (iItem == RVI_EDIT)
        return m_bEditValid ? &m_itemEdit : NULL;

    UINT    nIndex = iItem % REPORTCTRL_MAX_CACHE;

    if (m_aciCache[nIndex].iItem == iItem)
        return &m_aciCache[nIndex].item;

    return NULL;
}

CReportCtrl::LPITEM CReportCtrl::CacheAdd(INT iItem)
{
    if (iItem == RVI_EDIT)
    {
        m_bEditValid = TRUE;
        return &m_itemEdit;
    }

    UINT    nIndex = iItem % REPORTCTRL_MAX_CACHE;

    m_aciCache[nIndex].iItem = iItem;
    return &m_aciCache[nIndex].item;
}

void CReportCtrl::CacheDelete(INT iItem)
{
    if (iItem == RVI_INVALID)
    {
        for (UINT n = 0; n < REPORTCTRL_MAX_CACHE; n++)
            m_aciCache[n].iItem = RVI_INVALID;
    }
    else if (iItem == RVI_EDIT)
    {
        m_bEditValid = FALSE;
    }
    else
    {
        UINT    nIndex = iItem % REPORTCTRL_MAX_CACHE;

        if (m_aciCache[nIndex].iItem == iItem)
            m_aciCache[nIndex].iItem = RVI_INVALID;
    }
}

void CReportCtrl::BuildTree()
{
    if (m_dwStyle & RVS_TREEVIEW)
    {
        m_iTreeDepth = 0;
        m_iVirtualHeight = 0;
        m_arrayRows.RemoveAll();
        BuildTree(&m_tiRoot);
        if (!m_bSorting && m_arraySubItems[0].nFormat & RVCF_EX_AUTOWIDTH)
        {
            int oldWidth = GetSubItemWidth(0);
            int iWidth = max(m_iDefaultHeight * m_iTreeDepth, m_arraySubItems[0].iMinWidth);
            if (abs(iWidth - oldWidth) >= m_iDefaultHeight / 2)
            {
                SetSubItemWidth(0, iWidth);
                ScrollWindow(SB_HORZ, GetScrollPos32(SB_HORZ));
                Notify(RVN_INDENTCHANGED);
            }
        }
    }

    m_bUpdateItemMap = TRUE;
}

void CReportCtrl::BuildTree(LPTREEITEM lpti)
{
    LPTREEITEM  lptiSibling = lpti->lptiChildren;
    BOOL        bAsTreeColumn = AsTreeColumn();

    while (lptiSibling != NULL)
    {
        INT iRow = m_arrayRows.Add(lptiSibling->iItem);
        m_iVirtualHeight++;

        ITEM    &item = GetItemStruct(lptiSibling->iItem, lptiSibling->iItem, RVIM_STATE | RVIM_INDENT);
        item.nState &= ~RVIS_TREEMASK;
        item.iIndent = bAsTreeColumn ? lptiSibling->iIndent : 0;
        if (item.iIndent > m_iTreeDepth)
            m_iTreeDepth = item.iIndent;
        if (lptiSibling->lptiChildren != NULL)
        {
            item.nState |= lptiSibling->bOpen ? RVIS_TREEOPENED : RVIS_TREECLOSED;
            SetItemStruct(lptiSibling->iItem, item);
            SetState(iRow, item.nState & RVIS_TREEMASK, RVIS_TREEMASK);

            if (lptiSibling->bOpen)
                BuildTree((LPTREEITEM) lptiSibling);
        }

        lptiSibling = lptiSibling->lptiSibling;
    }
}

void CReportCtrl::InsertTree(INT iSubItem, LPTREEITEM lptiParent, LPTREEITEM lptiInsertAfter, LPTREEITEM lpti,
                             BOOL bAscending)
{
    LPTREEITEM  lptiSibling = lptiParent->lptiChildren;

    if (lptiSibling == NULL)
    {
        ASSERT(lptiParent != NULL);
        lptiParent->lptiChildren = lpti;
    }
    else
    {
        switch ((ULONG) lptiInsertAfter)
        {
        case RVTI_FIRST:
            lpti->lptiSibling = lptiSibling;
            lptiParent->lptiChildren = lpti;
            break;
        case RVTI_LAST:
            while (lptiSibling->lptiSibling != NULL)
                lptiSibling = lptiSibling->lptiSibling;
            lptiSibling->lptiSibling = lpti;
            break;
        case RVTI_SORT:
            {
                INT iSubItem1 = lpti->iSubItem < 0 ? iSubItem : lpti->iSubItem;
                INT iSubItem2 = lptiSibling->iSubItem < 0 ? iSubItem : lptiSibling->iSubItem;
                if
                (
                    (bAscending && CompareItems(lpti->iItem, iSubItem1, lptiSibling->iItem, iSubItem2) < 0)
                ||  (!bAscending && CompareItems(lpti->iItem, iSubItem1, lptiSibling->iItem, iSubItem2) > 0)
                )
                {
                    lpti->lptiSibling = lptiSibling;
                    lptiParent->lptiChildren = lpti;
                    break;
                }
                else
                {
                    while (lptiSibling->lptiSibling != NULL)
                    {
                        iSubItem1 = lpti->iSubItem < 0 ? iSubItem : lpti->iSubItem;
                        iSubItem2 = lptiSibling->iSubItem < 0 ? iSubItem : lptiSibling->iSubItem;
/*$off*/
              if(
                (bAscending && CompareItems(lpti->iItem, iSubItem1, lptiSibling->lptiSibling->iItem, iSubItem2) < 0) ||
                (!bAscending && CompareItems(lpti->iItem, iSubItem1, lptiSibling->lptiSibling->iItem, iSubItem2) > 0)
                )
                break;
/*$on*/
                        lptiSibling = lptiSibling->lptiSibling;
                    }

                    lptiInsertAfter = lptiSibling;
                }
            }

        // Intentional
        default:
            lpti->lptiSibling = lptiInsertAfter->lptiSibling;
            lptiInsertAfter->lptiSibling = lpti;
            break;
        }
    }
}

BOOL CReportCtrl::ExpandAllImpl(HTREEITEM hRoot, UINT nCode, INT iLevels, INT iDeep)
{
    HTREEITEM   hItem = GetNextItem(hRoot, RVGN_CHILD);

    iDeep++;
    if (iLevels >= 0 && iDeep <= iLevels)
        return TRUE;

    while (hItem != NULL)
    {
        Expand(hItem, nCode);
        if (ExpandAllImpl(hItem, nCode, iLevels, iDeep) == FALSE)
            return FALSE;

        hItem = GetNextItem(hItem, RVGN_NEXT);
    }

    return TRUE;
}

INT CReportCtrl::InsertItemImpl(LPRVITEM lprvi)
{
    ASSERT(lprvi->iItem >= 0);                          // Use SetItem to set Edit row item
    ASSERT(lprvi->iItem <= m_arrayItems.GetSize());
    ASSERT(lprvi->iSubItem < m_arraySubItems.GetSize());

    INT     iRow = -1;
    BOOL    bInserted1 = FALSE, bInserted2 = FALSE;

    try
    {
        ITEM    item;
        item.rdData.New(m_arraySubItems.GetSize());

        m_arrayItems.InsertAt(lprvi->iItem, item);
        bInserted1 = TRUE;
        VERIFY(SetItem(lprvi));

        if (!(m_dwStyle & RVS_TREEVIEW))
        {
            iRow = lprvi->iItem;
            m_arrayRows.InsertAt(iRow, INT_MIN);
            bInserted2 = TRUE;

            INT iRows = m_arrayRows.GetSize();
            for (INT i = 0; i < iRows; i++)
                if (m_arrayRows[i] >= iRow)
                    m_arrayRows[i]++;

            m_arrayRows[iRow] = lprvi->iItem;
        }

        ScrollWindow(SB_VERT, GetScrollPos32(SB_VERT));

        CList<INT, INT> list;

        POSITION        pos = m_listSelection.GetHeadPosition();
        while (pos != NULL)
        {
            INT iSelectedItem = m_listSelection.GetAt(pos);

            if (iSelectedItem >= lprvi->iItem)
                list.AddTail(iSelectedItem + 1);
            else
                list.AddTail(iSelectedItem);

            m_listSelection.GetNext(pos);
        }

        m_listSelection.RemoveAll();
        m_listSelection.AddTail(&list);

        return lprvi->iItem;
    }
    catch(CMemoryException * e)
    {
        if (bInserted1)
            m_arrayItems.RemoveAt(lprvi->iItem);
        if (bInserted2)
            m_arrayRows.RemoveAt(iRow);

        e->Delete();
        return RVI_INVALID;
    }
}

BOOL CReportCtrl::DeleteItemImpl(INT iItem)
{
    RVITEM  rvi;
    rvi.nMask = RVIM_LPARAM;
    rvi.iItem = iItem;
    GetItem(&rvi);
    Notify(RVN_ITEMDELETED, iItem, 0, 0, rvi.lParam);

    INT i, iItems = m_arrayItems.GetSize();

    if (m_dwStyle & RVS_TREEVIEW)
    {
        for (i = 0; i < iItems; i++)
        {
            LPTREEITEM  lpti = m_arrayItems[i].lptiItem;

            ASSERT(i == lpti->iItem);
            if (lpti->iItem > iItem)
                lpti->iItem--;
        }
    }

    m_arrayItems.RemoveAt(iItem);

    if (!(m_dwStyle & RVS_TREEVIEW))
    {
        for (i = 0; i < m_arrayRows.GetSize(); i++)
            if (m_arrayRows[i] == iItem)
                m_arrayRows.RemoveAt(i);

        INT iRows = m_arrayRows.GetSize();
        for (i = 0; i < iRows; i++)
            if (m_arrayRows[i] > iItem)
                m_arrayRows[i]--;
    }

    POSITION    pos = m_listSelection.Find(iItem);
    if (pos != NULL)
        m_listSelection.RemoveAt(pos);

    CList<INT, INT> list;
    pos = m_listSelection.GetHeadPosition();
    while (pos != NULL)
    {
        INT iSelectedItem = m_listSelection.GetAt(pos);

        if (iSelectedItem >= iItem)
            list.AddTail(iSelectedItem - 1);
        else
            list.AddTail(iSelectedItem);

        m_listSelection.GetNext(pos);
    }

    m_listSelection.RemoveAll();
    m_listSelection.AddTail(&list);

    return TRUE;
}

BOOL CReportCtrl::DeleteItemImpl(LPTREEITEM lpti, BOOL bChildrenOnly)
{
    LPTREEITEM  lptiSibling = lpti->lptiChildren;

    while (lptiSibling != NULL)
    {
        if (lptiSibling->lptiChildren != NULL)
            DeleteItemImpl(lptiSibling, TRUE);

        DeleteItemImpl(lptiSibling->iItem);

        LPTREEITEM  lptiChild = lptiSibling;
        lptiSibling = lptiSibling->lptiSibling;
        delete lptiChild;
    }

    lpti->lptiChildren = NULL;

    if (!bChildrenOnly)
    {
        DeleteItemImpl(lpti->iItem);

        lptiSibling = (LPTREEITEM) GetNextItem((HTREEITEM) lpti, RVGN_PREVIOUS);

        if (lptiSibling != NULL)
            lptiSibling->lptiSibling = lpti->lptiSibling;
        else if (lpti->lptiParent->lptiChildren == lpti)
            lpti->lptiParent->lptiChildren = lpti->lptiSibling;

        delete lpti;
    }

    return TRUE;
}

CReportCtrl::ITEM & CReportCtrl::GetItemStruct(INT iItem, INT iSubItem, UINT nMask)
{
    if (m_dwStyle & RVS_OWNERDATA)
    {
        INT     iSubItems = m_arraySubItems.GetSize();

        TCHAR   szText[REPORTCTRL_MAX_TEXT + 1];
        memset(szText, 0, sizeof(szText));

        LPITEM  lpItem = CacheLookup(iItem);
        if (m_bUseItemCacheMap && lpItem != NULL)
        {
            if (iSubItem < 0)
                return *lpItem;

            INT iImage, iOverlay, iCheck, iColor, iTextMax;
            if (lpItem->rdData.GetSubItem(iSubItem, &iImage, &iOverlay, &iCheck, &iColor, szText, &iTextMax))
                return *lpItem;
        }
        else
        {
            VERIFY((lpItem = CacheAdd(iItem)) != 0);
            lpItem->rdData.New(iSubItems);
        }
        VERIFY(LoadItemData(lpItem, iItem, iSubItem, nMask));
        return *lpItem;
    }
    return iItem == RVI_EDIT ? m_itemEdit : m_arrayItems[iItem];
}
void CReportCtrl::SetItemStruct(INT iItem, ITEM &item)
{
    if (m_dwStyle & RVS_OWNERDATA)
        return;

    if (iItem == RVI_EDIT)
        m_itemEdit = item;
    else
        m_arrayItems[iItem] = item;
}

BOOL CReportCtrl::LoadItemData(LPITEM lpItem, INT iItem, INT iSubItem, UINT nMask)
{
    CWnd    *pWnd = GetParent();
    if (pWnd)
    {
        TCHAR   szText[REPORTCTRL_MAX_TEXT + 1];
        memset(szText, 0, sizeof(szText));

        NMRVITEMCALLBACK    nmrvic;
        nmrvic.hdr.hwndFrom = GetSafeHwnd();
        nmrvic.hdr.idFrom = GetDlgCtrlID();
        nmrvic.hdr.code = RVN_ITEMCALLBACK;

        nmrvic.item.iItem = iItem;
        nmrvic.item.iSubItem = iSubItem;
        nmrvic.item.nMask = nMask;

        if (iSubItem >= 0)
        {
            nmrvic.item.nMask |= RVIM_TEXT;
            nmrvic.item.lpszText = szText;
            nmrvic.item.iTextMax = REPORTCTRL_MAX_TEXT;
        }

        Notify((LPNMREPORTVIEW) & nmrvic);

        INT iColor = nmrvic.item.nMask & RVIM_TEXTCOLOR ? nmrvic.item.iTextColor : -1;
        INT iImage = nmrvic.item.nMask & RVIM_IMAGE ? nmrvic.item.iImage : -1;
        INT iOverlay = nmrvic.item.nMask & RVIM_OVERLAY ? nmrvic.item.iOverlay : -1;
        INT iCheck = nmrvic.item.nMask & RVIM_CHECK ? nmrvic.item.iCheck : -1;

        if (iSubItem >= 0)
            lpItem->rdData.SetSubItem(iSubItem, iImage, iOverlay, iCheck, iColor, szText);

        lpItem->iBkColor = nmrvic.item.nMask & RVIM_BKCOLOR ? nmrvic.item.iBkColor : -1;
        lpItem->nPreview = nmrvic.item.nMask & RVIM_PREVIEW ? nmrvic.item.nPreview : 0;
        lpItem->nState = nmrvic.item.nMask & RVIM_STATE ? nmrvic.item.nState : 0;
        lpItem->iIndent = nmrvic.item.nMask & RVIM_INDENT ? nmrvic.item.iIndent : -1;

        return TRUE;
    }

    return FALSE;
}

INT CReportCtrl::GetItemForRow(INT iRow)
{
    if (m_dwStyle & RVS_OWNERDATA)
        return iRow;

    if (iRow == RVI_INVALID)
        return RVI_INVALID;

    return iRow == RVI_EDIT ? RVI_EDIT : m_arrayRows[iRow];
}

INT CReportCtrl::GetItemFromRow(INT iRow, ITEM &item)
{
    if (iRow == RVI_INVALID)
        return RVI_INVALID;

    INT iItem = iRow;
    if (!(m_dwStyle & RVS_OWNERDATA))
        iItem = GetItemForRow(iItem);

    item = GetItemStruct(iItem, -1);
    return iItem;
}

INT CReportCtrl::GetRowFromItem(INT iItem)
{
    if (iItem == RVI_EDIT || m_dwStyle & RVS_OWNERDATA)
        return iItem;

    INT iRows = m_arrayRows.GetSize(), iRow;
    if (iRows > 0)
    {
        if (m_bUpdateItemMap)
        {
            for (iRow = 0; iRow < iRows; iRow++)
                m_mapItemToRow[m_arrayRows[iRow]] = iRow;
            m_bUpdateItemMap = FALSE;
            MarkCategories();
        }

        if (m_mapItemToRow.Lookup(iItem, iRow))
            return iRow;
    }

    return RVI_INVALID;
}

INT CReportCtrl::GetSubItemFromColumn(INT iColumn)
{
    if (iColumn < 0 || iColumn >= m_arrayColumns.GetSize())
        return -1;

    HDITEM  hdi;
    hdi.mask = HDI_WIDTH | HDI_LPARAM;
    m_wndHeader.GetItem(m_arrayColumns[iColumn], &hdi);

    return hdi.lParam;
}

BOOL CReportCtrl::AsTreeColumn()
{
    if (m_dwStyle & RVS_TREEVIEW && GetColumnFromSubItem(0) == 0)
        return TRUE;
    return FALSE;
}

INT CReportCtrl::GetColumnFromSubItem(INT iSubItem)
{
    if (iSubItem < 0)
        return -1;

    INT iColumn, iColumns = m_arrayColumns.GetSize();
    for (iColumn = 0; iColumn < iColumns; iColumn++)
    {
        HDITEM  hdi;
        hdi.mask = HDI_WIDTH | HDI_LPARAM;
        m_wndHeader.GetItem(m_arrayColumns[iColumn], &hdi);

        if (hdi.lParam == iSubItem)
            return iColumn;
    }

    return -1;
}

CReportCtrl::LPTREEITEM CReportCtrl::GetVisibleAncestor(LPTREEITEM lpti)
{
    BOOL        bExit = FALSE;
    LPTREEITEM  lptiAncestor = GetVisibleAncestor(lpti, &bExit);

    return bExit ? lptiAncestor : lpti;
}

CReportCtrl::LPTREEITEM CReportCtrl::GetVisibleAncestor(LPTREEITEM lpti, LPBOOL lpbExit)
{
    if (lpti->lptiParent == &m_tiRoot)
        return lpti;

    LPTREEITEM  lptiAncestor = GetVisibleAncestor(lpti->lptiParent, lpbExit);

    if (*lpbExit == TRUE)
        return lptiAncestor;

    if (lptiAncestor->bOpen == FALSE)
    {
        *lpbExit = TRUE;
        return lptiAncestor;
    }

    return lpti;
}

BOOL CReportCtrl::SetSubItemWidthImpl(INT iSubItem, INT iWidth)
{
    INT iSubItems = m_arraySubItems.GetSize();
    ASSERT(iSubItem <= iSubItems);

    try
    {
        iWidth = iWidth < 0 ? m_iDefaultWidth : iWidth;

        SUBITEM subitem;
        subitem = m_arraySubItems.GetAt(iSubItem);

        iWidth = iWidth < subitem.iMinWidth ? subitem.iMinWidth : iWidth;
        iWidth = iWidth > subitem.iMaxWidth && subitem.iMaxWidth > 0 ? subitem.iMaxWidth : iWidth;

        INT iOldWidth = subitem.iWidth;

        subitem.iWidth = iWidth;
        m_arraySubItems.SetAt(iSubItem, subitem);

        INT iColumn = GetColumnFromSubItem(iSubItem);
        if (iColumn < 0)
            return TRUE;

        HDITEM  hditem;
        hditem.mask = HDI_WIDTH;
        if (!m_wndHeader.GetItem(m_arrayColumns[iColumn], &hditem))
            return FALSE;

        m_iVirtualWidth += iWidth - iOldWidth;

        hditem.cxy = iWidth;
        m_wndHeader.SetItem(m_arrayColumns[iColumn], &hditem);

        return TRUE;
    }
    catch(CMemoryException * e)
    {
        e->Delete();
        return FALSE;
    }
}

void CReportCtrl::GetExpandedItemText(LPRVITEM lprvi)
{
    UNREFERENCED_PARAMETER(lprvi);
}

void CReportCtrl::SetState(INT iRow, UINT nState, UINT nMask)
{
    ASSERT(iRow >= RVI_EDIT);

    if (iRow == RVI_EDIT)
    {
        m_itemEdit.nState &= ~nMask;
        m_itemEdit.nState |= nState & nMask;
    }
    else if (!(m_dwStyle & RVS_OWNERDATA))
    {
        m_arrayItems[m_arrayRows[iRow]].nState &= ~nMask;
        m_arrayItems[m_arrayRows[iRow]].nState |= nState & nMask;
    }

    FlushCache(iRow);
}

UINT CReportCtrl::GetState(INT iRow)
{
    RVITEM  rvi;
    rvi.iItem = GetItemForRow(iRow);
    rvi.nMask = RVIM_STATE;
    GetItem(&rvi);

    ASSERT(rvi.nMask & RVIM_STATE);                     // Need to return state of the item
    return rvi.nState;
}

CReportCtrl::LPTREEITEM CReportCtrl::GetTreeFocus()
{
    INT iFocusItem = GetItemForRow(m_iFocusRow);

    if (iFocusItem >= RVI_EDIT)
        SetState(m_iFocusRow, 0, RVIS_FOCUSED);

    m_iFocusRow = RVI_INVALID;
    return iFocusItem >= RVI_FIRST ? m_arrayItems[iFocusItem].lptiItem : NULL;
}

void CReportCtrl::SetTreeFocus(LPTREEITEM lptiFocus)
{
    INT iFirst = GetScrollPos32(SB_VERT), iLast;

    if (lptiFocus != NULL)
    {
        m_iFocusRow = GetRowFromItem(lptiFocus->iItem);
        if (m_iFocusRow == RVI_INVALID)
        {
            ScrollWindow(SB_VERT, 0);
            return;
        }
    }
    else
        m_iFocusRow = RVI_EDIT;

    SetState(m_iFocusRow, RVIS_FOCUSED, RVIS_FOCUSED);

    GetVisibleRows(TRUE, &iFirst, &iLast);

    if (m_iFocusRow >= 0 && (m_iFocusRow <= iFirst || iLast >= m_iFocusRow - 1))
        GetVisibleRows(TRUE, &iFirst, &iLast, TRUE);

    ScrollWindow(SB_VERT, iFirst > RVI_INVALID ? iFirst : 0);
}

void CReportCtrl::DeselectDescendents(LPTREEITEM lptiParent)
{
    LPTREEITEM  lptiSibling = lptiParent->lptiChildren;

    while (lptiSibling != NULL)
    {
        if (lptiSibling->lptiChildren != NULL)
            DeselectDescendents(lptiSibling);

        POSITION    pos = m_listSelection.Find(lptiSibling->iItem);
        if (pos != NULL)
        {
            m_arrayItems[lptiSibling->iItem].nState &= ~RVIS_SELECTED;
            m_listSelection.RemoveAt(pos);
        }

        lptiSibling = lptiSibling->lptiSibling;
    }
}

void CReportCtrl::ReOrder()
{
    m_wndHeader.SetSortColumn(-1, FALSE);
    m_wndHeader.ReOrder();
    for (INT i = 0; i < m_arrayColumns.GetSize(); i++)
        m_arrayColumns[i] = i;
}

void CReportCtrl::ReorderColumns()
{
    LPINT   lpi;
    INT     iColumns = m_wndHeader.GetItemCount();

    if (iColumns)
    {
        m_iFocusColumn = m_iFocusColumn >= iColumns ? iColumns - 1 : m_iFocusColumn;

        INT iSubItem = m_arrayColumns.GetSize() ? GetSubItemFromColumn(m_iFocusColumn) : -1;

        m_arrayColumns.SetSize(iColumns, 8);
        lpi = m_arrayColumns.GetData();
        if (!m_wndHeader.GetOrderArray(lpi, iColumns))
            return;

        if (iSubItem > 0)
            m_iFocusColumn = GetColumnFromSubItem(iSubItem);
        else
            m_iFocusColumn = -1;
    }
    else
    {
        m_arrayColumns.RemoveAll();
        m_iFocusColumn = -1;
    }

    if (m_lprsilc != NULL)
        m_lprsilc->UpdateList();

    m_bColumnsReordered = FALSE;
}

BOOL CReportCtrl::GetRowRect(INT iRow, INT iColumn, LPRECT lpRect, UINT nCode)
{
    // Select a visible sub-item
    if (iColumn < 0 && nCode != RVIR_BOUNDS)
        return FALSE;

    INT iFirst, iLast;

    if (iRow != RVI_EDIT)
    {
        iFirst = GetScrollPos32(SB_VERT);

        GetVisibleRows(FALSE, &iFirst, &iLast);

        // Select a visible item
        if (iFirst > iRow || iLast < iRow)
            return FALSE;

        *lpRect = m_rectReport;

        for (INT iTop = iFirst; iFirst < iRow; iFirst++)
        {
            lpRect->top += (GetCategoryForRow(iFirst, iFirst == iTop) >= 0 ? m_iDefaultHeight : 0);
            if ((m_dwStyle & RVS_OWNERDATA) || !(RVIS_HIDDEN & m_arrayItems[iFirst].nState))
                lpRect->top += m_iDefaultHeight + (m_bPreview ? GetItemStruct(GetItemForRow(iFirst), -1).nPreview : 0);
        }

        lpRect->top += (GetCategoryForRow(iFirst, iFirst == iTop) == -1 ? 0 : m_iDefaultHeight);
    }
    else
        *lpRect = m_rectEdit;

    lpRect->bottom = lpRect->top + m_iDefaultHeight;

    INT iPos = GetScrollPos32(SB_HORZ);

    lpRect->left -= iPos;
    lpRect->right -= iPos;

    INT iLeft = lpRect->left;

    if (iColumn < 0)
        return TRUE;

    HDITEM  hdi;
    hdi.mask = HDI_WIDTH | HDI_LPARAM;
    for (iFirst = 0; iFirst < iColumn; iFirst++)
    {
        m_wndHeader.GetItem(m_arrayColumns[iFirst], &hdi);
        lpRect->left += hdi.cxy;
    }

    m_wndHeader.GetItem(m_arrayColumns[iColumn], &hdi);

    if (nCode == RVIR_TEXTNOCOLUMNS)
        lpRect->right = iLeft + m_iVirtualWidth;
    else
        lpRect->right = lpRect->left + hdi.cxy;

    return TRUE;
}

INT CReportCtrl::GetVisibleRows(BOOL bUnobstructed, LPINT lpiFirst, LPINT lpiLast, BOOL bReverse)
{
    INT iHeight = m_rectReport.Height();
    INT iVirtualRows = 0;

    if (!bReverse)
    {
        INT iVirtualRow;
        if (lpiFirst)
            iVirtualRow = *lpiFirst;
        else
            iVirtualRow = GetScrollPos32(SB_VERT);
        for (INT iTop = iVirtualRow; iVirtualRow < m_iVirtualHeight && iHeight > 0; iVirtualRow++, iVirtualRows++)
        {
            INT iRow = GetRowFromVirtualRow(iVirtualRow);
            INT iItem = GetItemForRow(iRow);
            iHeight -= (GetCategoryForRow(iRow, iVirtualRow == iTop) == -1 ? 0 : m_iDefaultHeight);
            if (m_dwStyle & RVS_OWNERDATA || !(RVIS_HIDDEN & m_arrayItems[iItem].nState))
                iHeight -= m_iDefaultHeight + (m_bPreview ? GetItemStruct(iItem, -1).nPreview : 0);
            if (bUnobstructed && iHeight <= 0)
                break;
        }

        if (lpiLast)
            *lpiLast = iVirtualRow - 1;
    }
    else
    {
        ASSERT(lpiFirst);
        ASSERT(lpiLast);

        INT iFirst = *lpiFirst;
        for (*lpiFirst = *lpiLast; *lpiFirst >= 0 && iHeight > 0; *lpiFirst -= 1, iVirtualRows++)
        {
            INT iRow = GetRowFromVirtualRow(*lpiFirst);
            INT iItem = GetItemForRow(iRow);
            iHeight -= (GetCategoryForRow(iRow, *lpiFirst == iFirst) == -1 ? 0 : m_iDefaultHeight);
            if (m_dwStyle & RVS_OWNERDATA || !(RVIS_HIDDEN & m_arrayItems[iItem].nState))
                iHeight -= m_iDefaultHeight + (m_bPreview ? GetItemStruct(iItem, -1).nPreview : 0);
            if (bUnobstructed && iHeight <= 0)
                break;
        }

        *lpiFirst += 1;
    }

    return iVirtualRows;
}

void CReportCtrl::SelectRows(INT iFirst, INT iLast, BOOL bSelect, BOOL bKeepSelection, BOOL bInvert, BOOL bNotify)
{
    INT i;

    if (m_dwStyle & RVS_SINGLESELECT)
    {
        iLast = iFirst;
        bKeepSelection = FALSE;
    }

    if (m_iFocusRow > RVI_INVALID && iFirst != m_iFocusRow)
    {
        SetState(m_iFocusRow, 0, RVIS_FOCUSED);
        RedrawRows(m_iFocusRow);
    }

    m_iFocusRow = iFirst;
    SetState(iFirst, RVIS_FOCUSED, RVIS_FOCUSED);

    if (iFirst > iLast)
    {
        iLast += iFirst;
        iFirst = iLast - iFirst;
        iLast -= iFirst;
    }

    if (bSelect)
    {
        for (i = iFirst; i <= iLast; i++)
        {
            INT     iItem = GetItemForRow(i);
            UINT    nOldState, nNewState;

            nOldState = nNewState = GetState(i);

            if (bInvert)
                nNewState ^= RVIS_SELECTED;
            else
                nNewState |= RVIS_SELECTED;

            if (nNewState != nOldState)
            {
                if (bNotify && Notify(RVN_SELECTIONCHANGING, iItem, -1, nNewState))
                    continue;

                POSITION    pos = m_listSelection.Find(iItem);
                if (nNewState & RVIS_SELECTED)
                {
                    if (pos == NULL)
                        m_listSelection.AddTail(iItem);
                }
                else
                    m_listSelection.RemoveAt(pos);

                SetState(i, nNewState, RVIS_SELECTED);

                if (bNotify)
                    Notify(RVN_SELECTIONCHANGED, iItem, -1, nNewState);
            }

            EnsureVisible(iItem);
        }

        RedrawRows(iFirst, iLast);
    }
    else
        RedrawRows(iFirst);

    if (!bKeepSelection && bSelect)
    {
        CList<INT, INT> list;

        POSITION        pos = m_listSelection.GetHeadPosition();
        while (pos != NULL)
        {
            INT     iItem = m_listSelection.GetAt(pos);
            INT     i = GetRowFromItem(iItem);
            UINT    nOldState, nNewState;

            nOldState = nNewState = GetState(i);

            if ((i < iFirst || i > iLast) && nOldState & RVIS_SELECTED)
            {
                nNewState &= ~RVIS_SELECTED;

                if (nNewState != nOldState)
                {
                    if (bNotify && Notify(RVN_SELECTIONCHANGING, iItem, -1, nNewState))
                        continue;

                    SetState(i, nNewState, RVIS_SELECTED);

                    if (bNotify)
                        Notify(RVN_SELECTIONCHANGED, iItem, -1, nNewState);

                    RedrawRows(i);
                }
            }
            else
                list.AddTail(iItem);

            m_listSelection.GetNext(pos);
        }

        m_listSelection.RemoveAll();
        m_listSelection.AddTail(&list);
    }

    if (bNotify)
        Notify(RVN_SELECTIONDONECHANGING);
    UpdateWindow();
}

INT CReportCtrl::GetScrollPos32(INT iBar, BOOL bGetTrackPos)
{
    SCROLLINFO  si;
    si.cbSize = sizeof(SCROLLINFO);

    INT iMax = iBar == SB_HORZ ? m_iVirtualWidth : m_iVirtualHeight;

    if (bGetTrackPos)
    {
        if (GetScrollInfo(iBar, &si, SIF_TRACKPOS))
            return min(si.nTrackPos, iMax);
    }
    else
    {
        if (GetScrollInfo(iBar, &si, SIF_POS))
            return min(si.nPos, iMax);
    }

    return 0;
}

BOOL CReportCtrl::SetScrollPos32(INT iBar, INT iPos, BOOL bRedraw)
{
    SCROLLINFO  si;
    si.cbSize = sizeof(SCROLLINFO);
    si.fMask = SIF_POS;
    si.nPos = iPos;

    return SetScrollInfo(iBar, &si, bRedraw);
}

void CReportCtrl::ScrollWindow(INT iBar, INT iPos)
{
    if (m_iScrollWindow > 3 || m_rectReport.Width() <= 0 || m_rectReport.Height() <= 0)
        return;
    m_iScrollWindow++;                                  // Layout invokes ScrollWindow recursively
    m_wndTip.Hide();
    if (iBar == SB_HORZ)
    {
        SCROLLINFO  si;
        INT         iWidth = m_rectReport.Width();
        si.cbSize = sizeof(SCROLLINFO);
        si.fMask = SIF_PAGE | SIF_RANGE | SIF_POS;
        si.nPage = iWidth;
        si.nMin = 0;
        si.nMax = iWidth < m_iVirtualWidth ? m_iVirtualWidth - 2 : 0;
        si.nPos = iPos;
        SetScrollInfo(SB_HORZ, &si, TRUE);

        INT x = GetScrollPos32(SB_HORZ);
        INT cx = iWidth < m_iVirtualWidth ? m_iVirtualWidth : iWidth;
        if (int iCategoryHdrHeight = GetCategoryHdrHeight ())
            VERIFY(m_wndCategoryHdr->SetWindowPos(&wndTop, -x, 0, cx, iCategoryHdrHeight, SWP_SHOWWINDOW));
        VERIFY(m_wndHeader.SetWindowPos(&wndTop, -x, m_rectHeader.top, cx, m_rectHeader.Height(),
               m_dwStyle & RVS_NOHEADER ? SWP_HIDEWINDOW : SWP_SHOWWINDOW));
    }

    if (iBar == SB_VERT)
    {
        ASSERT(iPos >= 0 && iPos <= m_iVirtualHeight);

        INT iFirst = iPos, iLast;
        GetVisibleRows(TRUE, &iFirst, &iLast);

        SCROLLINFO  si;
        si.cbSize = sizeof(SCROLLINFO);
        si.fMask = SIF_PAGE | SIF_RANGE | SIF_POS;
        si.nPage = (iLast - iFirst + 1);
        si.nMin = 0;
        si.nMax = (iLast - iFirst + 1) < m_iVirtualHeight ? m_iVirtualHeight - 1 : 0;
        si.nPos = iFirst;
        iFirst = 0;
        if (GetVisibleRows(TRUE, &iFirst, &iLast) == m_iVirtualHeight)
            si.nPos = 0;
        SetScrollInfo(SB_VERT, &si, TRUE);
    }

    if (m_dwStyle & RVS_SHOWEDITROW)
        InvalidateRect(m_rectEdit);

    InvalidateRect(m_rectReport);
    --m_iScrollWindow;
}

void CReportCtrl::EnsureVisibleColumn(INT iColumn)
{
    if (iColumn > -1)
    {
        INT     iWidth = m_rectReport.Width();
        INT     iPos = GetScrollPos(SB_HORZ);
        INT     iOffset = 0;

        HDITEM  hdi;
        hdi.mask = HDI_WIDTH | HDI_LPARAM;

        for (INT i = 0; i < iColumn; i++)
        {
            m_wndHeader.GetItem(m_arrayColumns[i], &hdi);
            iOffset += hdi.cxy;
        }

        m_wndHeader.GetItem(m_arrayColumns[i], &hdi);

        if (iOffset + hdi.cxy > iPos + iWidth || iOffset < iPos)
            ScrollWindow(SB_HORZ, iOffset);
    }
}

void CReportCtrl::SetIndentFirstColumn(INT iIndent)
{
    UnindentFirstColumn();
    m_iIndentColumn = iIndent;
    IndentFirstColumn();
}

void CReportCtrl::IndentFirstColumn()
{
    if (m_arrayColumns.GetSize() > 0 && m_iIndentColumn > 0)
    {
        ASSERT(m_bIndentColumn == FALSE);

        INT iSubItem = GetSubItemFromColumn(0);
        ASSERT(iSubItem >= 0);

        SUBITEM &subitem = m_arraySubItems[iSubItem];
        subitem.iMinWidth += m_iIndentColumn;
        subitem.iMaxWidth += subitem.iMaxWidth > 0 ? m_iIndentColumn : 0;

        SetSubItemWidthImpl(iSubItem, subitem.iWidth + m_iIndentColumn);

        m_wndHeader.SetMinMax(m_wndHeader.OrderToIndex(0), subitem.iMinWidth, subitem.iMaxWidth);

        m_bIndentColumn = TRUE;
    }
}

void CReportCtrl::UnindentFirstColumn()
{
    if (m_bIndentColumn && m_iIndentColumn >= 0)
    {
        INT iSubItem = GetSubItemFromColumn(0);
        ASSERT(iSubItem >= 0);

        SUBITEM &subitem = m_arraySubItems[iSubItem];
        subitem.iMaxWidth -= subitem.iMaxWidth > 0 ? m_iIndentColumn : 0;
        subitem.iMinWidth -= m_iIndentColumn;

/*$off*/
      SetSubItemWidthImpl(
        iSubItem,
        (subitem.iMinWidth <= 0&& subitem.iWidth-m_iIndentColumn < 20)
        ?
        20:subitem.iWidth-m_iIndentColumn
        );

      m_iIndentColumnPending =
        m_iIndentColumnPending >= 0
        ?
        m_iIndentColumnPending:m_wndHeader.OrderToIndex(0);

      m_wndHeader.SetMinMax(
        m_iIndentColumnPending,
        subitem.iMinWidth,
        subitem.iMaxWidth
        );
/*$on*/
    }

    m_bIndentColumn = FALSE;
    m_iIndentColumnPending = -1;
}

void CReportCtrl::SetRedraw(BOOL bRedraw)
{
    CWnd::SetRedraw(bRedraw);
    m_bRedrawFlag = bRedraw;

    if (bRedraw == TRUE)
        UpdateWindow();
}

void CReportCtrl::RedrawRows(INT iFirst, INT iLast, BOOL bUpdate)
{
    CRect   rect;

    if (iFirst == iLast || iLast == RVI_INVALID)
    {
        if (iFirst == RVI_INVALID && iLast == RVI_INVALID)
            return;
        GetRowRect(iFirst, -1, rect);
        if (m_bPreview)
            rect.bottom += GetItemStruct(GetItemForRow(iFirst), -1, RVIM_PREVIEW).nPreview;
        rect.OffsetRect(GetScrollPos32(SB_HORZ), 0);
    }
    else
        rect = m_rectReport;

    InvalidateRect(rect);

    if (bUpdate)
        UpdateWindow();
}

void CReportCtrl::SelectionToSortedArray(CArray<INT, INT> &arrayRows)
{
    INT         iPos = 0, iSize;
    POSITION    pos = m_listSelection.GetHeadPosition();
    while (pos != NULL)
    {
        INT iRow = GetRowFromItem(m_listSelection.GetAt(pos));

        iSize = arrayRows.GetSize();
        if (iSize)
        {
            if (arrayRows[iPos] <= iRow)
            {
                while (iPos < iSize && arrayRows[iPos] <= iRow)
                    iPos++;
            }
            else
            {
                while (iPos > 0 && arrayRows[iPos - 1] > iRow)
                    iPos--;
            }
        }

        arrayRows.InsertAt(iPos, iRow);

        m_listSelection.GetNext(pos);
    }
}

void CReportCtrl::DrawCtrl(CDC *pDC)
{
    CRect   rectClip;
    if (pDC->GetClipBox(&rectClip) == ERROR)
        return;

    INT iHPos = GetScrollPos32(SB_HORZ);
    INT iVPos = GetScrollPos32(SB_VERT);

    DrawBkgnd(pDC, rectClip, m_dwStyle & WS_DISABLED ? m_cr3DFace : m_crBackground);

    CPen    pen(m_iGridStyle, 1, m_crGrid);
    CPen    *pPen = pDC->SelectObject(&pen);

    CFont   *pFont = pDC->SelectObject(GetFont());

    pDC->SetBkMode(TRANSPARENT);

    CRect   rect;
    CRect   rectRow
            (
                m_rectReport.left - iHPos,
                m_rectReport.top,
                m_rectReport.left - iHPos + m_iVirtualWidth,
                m_rectReport.top
            );

    GetClientRect(rect);
    rect.right = rect.left + m_iDefaultHeight;
    rect.OffsetRect(-iHPos, 0);
    if (m_iIndentColumn > 0 && m_bIndentGrey && rectClip.left < rect.right && rect.right > 0)
        pDC->FillSolidRect(rect, m_cr3DFace);

    TCHAR   szText[REPORTCTRL_MAX_TEXT];
    RVITEM  rvi;
    rvi.nMask = RVIM_TEXT;
    rvi.lpszText = szText;
    rvi.iTextMax = REPORTCTRL_MAX_TEXT;

    NMRVDRAWPREVIEW nmrvdp;
    nmrvdp.hdr.hwndFrom = GetSafeHwnd();
    nmrvdp.hdr.idFrom = GetDlgCtrlID();
    nmrvdp.hdr.code = RVN_ITEMDRAWPREVIEW;
    nmrvdp.hDC = pDC->m_hDC;

    INT iColumns = m_arrayColumns.GetSize();

    if (m_bColumnsReordered)
    {
        UnindentFirstColumn();
        ReorderColumns();
        IndentFirstColumn();
    }

    if (m_dwStyle & RVS_SHOWEDITROW)
    {
        GetClientRect(rect);
        rect.bottom = m_rectEdit.bottom;
        rect.top = rect.bottom - GetSystemMetrics(SM_CYFIXEDFRAME) * 2;
        pDC->FillSolidRect(rect, m_cr3DFace);
        pDC->Draw3dRect(rect, m_cr3DHiLight, m_cr3DDkShadow);

        rvi.iItem = RVI_EDIT;
        rvi.nMask = RVIM_TEXT;
        GetItem(&rvi);

        rect = m_rectEdit;
        rect.bottom -= GetSystemMetrics(SM_CYFIXEDFRAME) * 2;
        rect.left -= iHPos;

        DrawRow(pDC, rect, rectClip, RVI_EDIT, &rvi, FALSE);

        if (m_bFocus && m_hEditWnd == NULL && m_iFocusRow == RVI_EDIT && m_iFocusColumn < 0)
        {
            if (m_dwStyle & (RVS_SHOWHGRID | RVS_SHOWVGRID))
                rect.DeflateRect(1, 0, 1, 0);

            pDC->SetBkColor(m_dwStyle & WS_DISABLED ? m_cr3DFace : m_crBackground);
            pDC->SetTextColor(m_crText);
            pDC->DrawFocusRect(rect);
        }
    }

    if (!m_iVirtualHeight)
    {
        rectRow.top += 2;
        rectRow.bottom = rectRow.top + m_iDefaultHeight;

        GetClientRect(rect);
        rect.top = rectRow.top;
        rect.bottom = rectRow.bottom;

        if (rectRow.Width() < rect.Width())
            rect = rectRow;

        if (!m_strNoItems.IsEmpty())
        {
            pDC->SetTextColor(m_crText);
            pDC->DrawText(m_strNoItems, rect, DT_CENTER | DT_END_ELLIPSIS | DT_NOPREFIX);
        }
    }
    else
    {
        for
        (
            INT iVirtualRow = iVPos;
            iVirtualRow < m_iVirtualHeight && rectRow.top < rectClip.bottom + m_iDefaultHeight;
            iVirtualRow++
        )
        {
            int iRow = GetRowFromVirtualRow(iVirtualRow);
            rvi.iItem = GetItemForRow(iRow);
            rvi.nMask = RVIM_TEXT;
            rvi.lpszText = szText;
            VERIFY(GetItem(&rvi));

            INT iCategoryHeight = (GetCategoryForRow(iRow, iVirtualRow == iVPos) == -1 ? 0 : m_iDefaultHeight);
            rectRow.left = m_rectReport.left - iHPos;

            CRect   rectRowBefore(rectRow);
            if (!(rvi.nState & RVIS_HIDDEN))
                rectRow.bottom = rectRow.top + m_iDefaultHeight + (m_bPreview ? rvi.nPreview : 0);
            if (rectRow.bottom + iCategoryHeight >= rectClip.top)
            {
                if (rvi.iIndent >= 0)
                {
                    rectRow.left += rvi.iIndent * m_iDefaultHeight;
                    rectRow.right = max(rectRow.left, rectRow.right);
                }

                DrawRow(pDC, rectRow, rectClip, iVirtualRow, &rvi, iVirtualRow & 1);
                rectRow.OffsetRect(0, iCategoryHeight);
                if (rvi.nState & RVIS_HIDDEN)
                {
                    rectRow.bottom = rectRow.top;
                }
                else
                {
                    if (m_bPreview && rvi.nMask & RVIM_PREVIEW && rvi.nPreview)
                    {
                        nmrvdp.iItem = rvi.iItem;
                        nmrvdp.nState = rvi.nState;
                        nmrvdp.rect = rectRow;
                        nmrvdp.rect.top += m_iDefaultHeight;
                        nmrvdp.lParam = rvi.lParam;
                        Notify((LPNMREPORTVIEW) & nmrvdp);
                    }
                }

                if (m_dwStyle & RVS_SHOWHGRID)
                {
                    if (iCategoryHeight)
                    {
                        pDC->MoveTo(rectRow.left, rectRowBefore.bottom - 1 + iCategoryHeight);
                        pDC->LineTo(rectRow.right, rectRowBefore.bottom - 1 + iCategoryHeight);
                    }

                    pDC->MoveTo(rectRow.left, rectRow.bottom - 1);
                    pDC->LineTo(rectRow.right, rectRow.bottom - 1);
                }

                if
                (
                    m_bFocus
                &&  m_hEditWnd == NULL
                &&  m_iFocusRow == iVirtualRow
                &&  (!(m_dwStyle & RVS_FOCUSSUBITEMS) || m_iFocusColumn < 0)
                )
                {
                    if (m_dwStyle & (RVS_SHOWHGRID | RVS_SHOWVGRID))
                        rectRow.DeflateRect(1, 0, 1, 1);

                    pDC->SetBkColor(m_dwStyle & WS_DISABLED ? m_cr3DFace : m_crBackground);
                    pDC->SetTextColor(m_crText);
                    if (m_bFrameFocus)
                        pDC->DrawFocusRect(rectRow);
                    if (m_dwStyle & (RVS_SHOWHGRID | RVS_SHOWVGRID))
                        rectRow.InflateRect(1, 0, 1, 1);
                }
            }
            else
                rectRow.OffsetRect(0, (GetCategoryForRow(iVirtualRow, iVirtualRow == iVPos) == -1 ? 0 : m_iDefaultHeight));
            rectRow.top = rectRow.bottom;
        }

        if (AsTreeColumn())
        {
            for (; iVirtualRow < m_iVirtualHeight; iVirtualRow++)
            {
                rvi.iItem = GetItemForRow(GetRowFromVirtualRow(iVirtualRow));

                HTREEITEM   thisNode = GetItemHandle(rvi.iItem);
                ASSERT(thisNode);
                if (GetNextItem(thisNode, RVGN_PARENT) == NULL)
                    break;
                rvi.nMask = RVIM_TEXT;
                rvi.lpszText = szText;
                VERIFY(GetItem(&rvi));
                rectRow.left = m_rectReport.left - iHPos;
                rectRow.bottom = rectRow.top + m_iDefaultHeight + (m_bPreview ? rvi.nPreview : 0);
                if (rectRow.bottom >= rectClip.top)
                {
                    if (rvi.iIndent >= 0)
                    {
                        rectRow.left += rvi.iIndent * m_iDefaultHeight;
                        rectRow.right = max(rectRow.left, rectRow.right);
                    }

                    DrawRow(pDC, rectRow, rectClip, iVirtualRow, &rvi, iVirtualRow & 1);
                    if (m_bPreview && rvi.nMask & RVIM_PREVIEW && rvi.nPreview)
                    {
                        nmrvdp.iItem = rvi.iItem;
                        nmrvdp.nState = rvi.nState;
                        nmrvdp.rect = rectRow;
                        nmrvdp.rect.top += m_iDefaultHeight;
                        nmrvdp.lParam = rvi.lParam;
                        Notify((LPNMREPORTVIEW) & nmrvdp);
                    }
                }

                rectRow.top = rectRow.bottom;
            }
        }

        rectRow.top = m_rectReport.top;
        rectRow.bottom = m_rectReport.bottom < rectRow.bottom ? m_rectReport.bottom : rectRow.bottom;

        if (m_dwStyle & (RVS_SHOWHGRID | RVS_SHOWVGRID))
        {
            if (m_dwStyle & RVS_SHOWHGRIDEX)
            {
                pDC->MoveTo(rectRow.left, m_rectReport.top);
                pDC->LineTo(rectRow.left, m_rectReport.bottom);

                pDC->MoveTo(rectRow.right - 1, m_rectReport.top);
                pDC->LineTo(rectRow.right - 1, m_rectReport.bottom);
            }
            else
            {
                pDC->MoveTo(rectRow.left, rectRow.top);
                pDC->LineTo(rectRow.left, rectRow.bottom);

                pDC->MoveTo(rectRow.right - 1, rectRow.top);
                pDC->LineTo(rectRow.right - 1, rectRow.bottom);
            }
        }

        if (m_dwStyle & RVS_SHOWHGRID && m_dwStyle & RVS_SHOWHGRIDEX)
        {
            while (rectRow.bottom <= rectClip.bottom)
            {
                pDC->MoveTo(rectRow.left, rectRow.bottom - 1);
                pDC->LineTo(rectRow.right, rectRow.bottom - 1);

                rectRow.bottom += m_iDefaultHeight;
            }
        }

        if (m_dwStyle & RVS_SHOWVGRID)
        {
            for (INT iColumn = 0; iColumn < iColumns && rectRow.left < rectClip.right; iColumn++)
            {
                HDITEM  hdi;
                hdi.mask = HDI_WIDTH;
                m_wndHeader.GetItem(m_arrayColumns[iColumn], &hdi);

                rectRow.left += hdi.cxy;

                pDC->MoveTo(rectRow.left - 1, rectRow.top);
                pDC->LineTo(rectRow.left - 1, rectRow.bottom);
            }
        }
    }

    pDC->SelectObject(pFont);
    pDC->SelectObject(pPen);

    pen.DeleteObject();
}

void CReportCtrl::DrawRow(CDC *pDC, CRect rectRow, CRect rectClip, INT iVirtualRow, LPRVITEM lprvi, BOOL bAlternate)
{
    int iRow = GetRowFromVirtualRow(iVirtualRow);
    INT iColumns = m_arrayColumns.GetSize();
    INT iCategorySubItem = GetCategoryForRow(iRow, GetScrollPos32(SB_VERT) == iVirtualRow);
    ASSERT(!(m_dwStyle & RVS_SHOWCOLORALTERNATE && !m_arrayColors.GetSize()));
    ASSERT(lprvi->lpszText != NULL);

    LPTSTR      lpszText = lprvi->lpszText;
    COLORREF    crBackground = m_dwStyle & WS_DISABLED ? m_cr3DFace : m_crBackground;
    CRect       rectItem(rectRow.left, rectRow.top, rectRow.left, rectRow.top + m_iDefaultHeight);
    CRect       rectFill(rectRow);
    if (lprvi->nState & RVIS_BOLD)
        pDC->SelectObject(&m_fontBold);
    if (iCategorySubItem >= 0)
    {
        lprvi->iSubItem = iCategorySubItem;

        CFlatHeaderCtrl *hc = GetHeaderCtrl();
        lprvi->nMask |= RVIM_CATEGORY;
        rectItem.left = DrawRowBkgnd(pDC, rectItem, lprvi, GetRootRow(iRow), bAlternate);
        lprvi->nMask &= ~RVIM_CATEGORY;
        rectItem.right = hc->GetWidth() - rectItem.left;
        rectFill.OffsetRect(0, m_iDefaultHeight);
    }

    for (INT iColumn = 0; iColumn < iColumns && rectItem.left < rectClip.right;)
    {
        int     iDisplayRow = iRow;
        HDITEM  hdi;
        hdi.mask = HDI_WIDTH | HDI_LPARAM;
        if (iCategorySubItem >= 0)
        {
            hdi.lParam = (LPARAM) iCategorySubItem;
            iDisplayRow = GetRootRow(iRow);
        }
        else
        {
            if (lprvi->nState & RVIS_HIDDEN)
                break;
            lprvi->iItem = GetItemForRow(iDisplayRow);
            VERIFY(GetItem(lprvi));
            m_wndHeader.GetItem(m_arrayColumns[iColumn], &hdi);
            lprvi->iSubItem = -1;
            if (iColumn == 0)
            {
                DrawRowBkgnd(pDC, rectFill, lprvi, iDisplayRow, bAlternate);

                int itemIndex = GetItemForRow(iDisplayRow);
                lastRenderedPosition.SetAtGrow(itemIndex, rectFill.top);
            }

            rectItem.right = rectItem.left + hdi.cxy - (iColumn == 0 && lprvi->iIndent >= 0 ? lprvi->iIndent * m_iDefaultHeight : 0);
        }

        lprvi->iSubItem = hdi.lParam;
        if (rectItem.right > rectClip.left)
        {
            lprvi->nMask = RVIM_TEXT;
            lprvi->lpszText = lpszText;
            lprvi->iItem = GetItemForRow(iDisplayRow);
            VERIFY(GetItem(lprvi));

            COLORREF    crText = lprvi->nMask & RVIM_TEXTCOLOR ? m_arrayColors[lprvi->iTextColor] : m_crText;
            if (lprvi->nState & RVIS_SELECTED)
            {
                if (m_bFocus && iCategorySubItem < 0)
                    crText =
                        (
                            m_dwStyle & RVS_SHOWCOLORALWAYS
                        &&  lprvi->nMask & RVIM_TEXTCOLOR
                        ) ? crText : m_crTextSelected;
                else if (m_dwStyle & RVS_SHOWSELALWAYS)
                    crText = lprvi->nMask & RVIM_TEXTCOLOR
                    &&  m_dwStyle & RVS_SHOWCOLORALWAYS ? crText : m_crTextSelectedNoFocus;
            }

            if
            (
                m_iFocusRow == iDisplayRow
            &&  m_iFocusColumn == iColumn
            &&  (m_iFocusRow == RVI_EDIT || m_dwStyle & RVS_FOCUSSUBITEMS)
            )
            {
                pDC->FillSolidRect(rectItem, crBackground);
                crText = m_crText;
            }

            pDC->SetTextColor(crText);

            if (iCategorySubItem < 0 && iColumn == 0 && lprvi->iIndent >= 0)
            {
                if (AsTreeColumn())
                {
                    CRgn    oldClipRegion;
                    GetClipRgn(pDC->GetSafeHdc(), oldClipRegion);
                    m_wndHeader.GetItem(m_arrayColumns[iColumn], &hdi);
                    pDC->IntersectClipRect(rectClip.left, rectClip.top, hdi.cxy, rectClip.bottom);

                    HTREEITEM   thisNode = GetItemHandle(GetItemForRow(iDisplayRow));
                    ASSERT(thisNode);

                    HTREEITEM   parent = GetNextItem(thisNode, RVGN_PARENT);
                    if (parent)
                    {
                        // We need to draw a line up to our parent. (Or to our previous sibling, if any.)
                        int         startOfLine = lastRenderedPosition[GetItemIndex(parent)] - m_iDefaultHeight / 2;
                        HTREEITEM   previous = GetNextItem(thisNode, RVGN_PREVIOUS);
                        while (previous)
                        {
                            RVITEM  previousItem;
                            LPTSTR  szText = 0;
                            previousItem.iItem = GetItemIndex(previous);
                            previousItem.nMask = RVIM_TEXT;
                            previousItem.lpszText = szText;
                            VERIFY(GetItem(&previousItem));

                            // Is the previous node a sibling (at our own level?)
                            if (lprvi->iIndent == previousItem.iIndent)
                            {
                                // We want to draw our line up to this sibling.
                                startOfLine = lastRenderedPosition[GetItemIndex(previous)] - m_iDefaultHeight / 2;

                                // If the sibling has children, then we need to draw only to the bottom of their
                                // box.
                                if (previousItem.nState & RVIS_TREEMASK)
                                    startOfLine += 5;   // Magic number! Ungh!
                                break;
                            }

                            previous = GetNextItem(previous, RVGN_PREVIOUS);
                            if (previous == parent)
                                previous = 0;
                        }

                        // Draw a line up to our parent (or sibling)
                        pDC->MoveTo(rectItem.left - m_iDefaultHeight / 2, startOfLine + m_iDefaultHeight);
                        pDC->LineTo(rectItem.left - m_iDefaultHeight / 2, rectItem.top + m_iDefaultHeight / 2);
                        pDC->LineTo(rectItem.left - m_iDefaultHeight / 2, rectItem.top + m_iDefaultHeight / 2);

                        // Draw a line from our node to the right. Children might attach to this line
                        // later.
                        if ((lprvi->nState & RVIS_TREEOPENED))
                        {
                            pDC->MoveTo(rectItem.left - m_iDefaultHeight / 2,                   // should be width!
                                        rectItem.top + m_iDefaultHeight / 2);
                            pDC->LineTo(rectItem.left + m_iDefaultHeight / 2,                   // should be width!
                                        rectItem.top + m_iDefaultHeight / 2);
                        }
                        else
                        {
                            pDC->MoveTo(rectItem.left - m_iDefaultHeight / 2,                   // should be width!
                                        rectItem.top + m_iDefaultHeight / 2);
                            pDC->LineTo(rectItem.left, rectItem.top + m_iDefaultHeight / 2);
                        }
                    }
                    else
                    {
                        // Don't render the dash if there are no children.
                        if (lprvi->nState & RVIS_TREEMASK)
                        {
                            // There is no parent (this is a top-level node)
                            pDC->MoveTo(rectItem.left - m_iDefaultHeight / 2,                   // should be width!
                                        rectItem.top + m_iDefaultHeight / 2);
                            pDC->LineTo(rectItem.left + m_iDefaultHeight / 2,                   // should be width!
                                        rectItem.top + m_iDefaultHeight / 2);
                        }
                    }

                    pDC->SelectClipRgn(&oldClipRegion);
                }

                if (AsTreeColumn() && lprvi->nState & RVIS_TREEMASK)
                {
                    rectItem.left -= m_iDefaultHeight;
                    DrawTreeBox(pDC, rectItem, lprvi->nState & RVIS_TREEOPENED);
                    rectItem.left += m_iDefaultHeight;
                }

                if (!(m_dwStyle & RVS_OWNERDATA))
                {
                    LPTREEITEM  lpti = m_arrayItems[lprvi->iItem].lptiItem;
                    if (lpti != NULL)
                    {
                        if (lpti->iSubItem >= 0)
                        {
                            rectItem.right = rectRow.right;
                            lprvi->iSubItem = lpti->iSubItem;
                            lprvi->nMask = RVIM_TEXT;
                            lprvi->lpszText = lpszText;
                            VERIFY(GetItem(lprvi));
                            rectItem.DeflateRect(m_iSpacing, 0);
                            DrawItem(pDC, rectItem, lprvi);
                            rectItem.InflateRect(m_iSpacing, 0);
                            break;
                        }
                    }
                }
            }

            rectItem.DeflateRect(m_iSpacing, 0);
            if (iCategorySubItem >= 0)
                lprvi->nMask |= RVIM_CATEGORY;
            DrawItem(pDC, rectItem, lprvi);
            lprvi->nMask &= ~RVIM_CATEGORY;
            rectItem.InflateRect(m_iSpacing, 0);
            if
            (
                iCategorySubItem < 0
            &&  m_bFocus
            &&  m_hEditWnd == NULL
            &&  m_iFocusRow == iDisplayRow
            &&  m_iFocusColumn == iColumn
            &&  (m_iFocusRow == RVI_EDIT || m_dwStyle & RVS_FOCUSSUBITEMS)
            )
            {
                if (m_dwStyle & (RVS_SHOWHGRID | RVS_SHOWVGRID))
                    rectItem.DeflateRect(1, 0, 1, 1);
                pDC->SetBkColor(crBackground);
                pDC->SetTextColor(m_crText);
                pDC->DrawFocusRect(rectItem);
                if (m_dwStyle & (RVS_SHOWHGRID | RVS_SHOWVGRID))
                    rectItem.InflateRect(1, 0, 1, 1);
            }
        }

        if (iCategorySubItem >= 0)
        {
            RVITEM  rvi;
            rvi.nMask = RVIM_CATEGORY;
            rvi.iSubItem = iCategorySubItem;
            DrawRowFrgnd(pDC, rectItem, &rvi, iDisplayRow, bAlternate);
            iCategorySubItem = -1;
            rectItem.OffsetRect(0, rectItem.bottom - rectItem.top);
            rectItem.left = rectRow.left;
        }
        else
        {
            rectItem.left = rectItem.right;
            iColumn++;
            if (!(iColumn < iColumns && rectItem.left < rectClip.right))
                DrawRowFrgnd(pDC, rectItem, lprvi, iDisplayRow, bAlternate);
        }
    }

    if (lprvi->nState & RVIS_BOLD)
        pDC->SelectObject(GetFont());
}

void CReportCtrl::DrawRowFrgnd(CDC *pDC, CRect rect, LPRVITEM lprvi, INT iRow, BOOL bAlternate)
{
    UNREFERENCED_PARAMETER(iRow);
    UNREFERENCED_PARAMETER(bAlternate);
    if (lprvi->nMask & RVIM_CATEGORY)
    {
        CPen    *pPen = pDC->GetCurrentPen();
        CPen    penHighLight(PS_SOLID, 1, m_cr3DDkShadow);
        pDC->SelectObject(&penHighLight);
        pDC->MoveTo(0, rect.bottom - 1);
        pDC->LineTo(GetHeaderCtrl()->GetWidth(), rect.bottom - 1);
        pDC->SelectObject(pPen);
        penHighLight.DeleteObject();
    }
}

INT CReportCtrl::DrawRowBkgnd(CDC *pDC, CRect rect, LPRVITEM lprvi, INT iRow, BOOL bAlternate)
{
    if (lprvi->nMask & RVIM_CATEGORY)
    {
        BYTE    r = GetRValue(m_cr3DFace);
        BYTE    g = GetGValue(m_cr3DFace);
        BYTE    b = GetBValue(m_cr3DFace);
        BYTE    rb = GetRValue(m_crBackground);
        BYTE    gb = GetGValue(m_crBackground);
        BYTE    bb = GetBValue(m_crBackground);
        int     w = GetHeaderCtrl()->GetWidth();
        --rect.top;
        for (int i = 0, k = 64; i <= k; i++)
        {
            rect.left = max(0, int(float(w) / k * i) - 1);
            rect.right = min(w, 1 + int(rect.left + float(w) / k));
#define rgb(f, b)   int(float(f) + float(b - f) / (k / 2) * (i < (k / 2) ? ((k / 2) - i) : (i - (k / 2))))
            pDC->FillSolidRect(rect, RGB(rgb(r, rb), rgb(g, gb), rgb(b, bb)));
#undef rgb
        }

        rect.left = 0;
        if (m_iCategoryImage >= 0)
        {
            RVITEM  rvi;
            rvi.nMask = RVIM_IMAGE;
            rvi.iImage = m_iCategoryImage + (lprvi->nState & RVIS_HIDDEN ? 1 : 0);
            rect.right += m_sizeImage.cx;
            rect.left += DrawImage(pDC, rect, &rvi);
        }
        else
        {
            CPen    penErase(PS_SOLID, 1, m_cr3DDkShadow);
            CPen    *pPen = pDC->SelectObject(&penErase);
            CBrush  brushFace(m_cr3DFace);
            CBrush  *pBrush = pDC->SelectObject(&brushFace);
            CRect   r3dRect(CPoint(rect.left + 2, rect.top), CSize(m_iDefaultHeight, m_iDefaultHeight));
            r3dRect.DeflateRect(2, 2);
            pDC->RoundRect(r3dRect, CPoint(2, 2));
            r3dRect.DeflateRect(1, 1);
            pDC->FillSolidRect(CRect(r3dRect.left + 1, r3dRect.CenterPoint().y + 2, r3dRect.right - 3,
                               r3dRect.CenterPoint().y - 1), m_crBackground);
            if (lprvi->nState & RVIS_HIDDEN)
            {
                pDC->FillSolidRect(CRect(r3dRect.CenterPoint().x - 2, r3dRect.top + 2, r3dRect.CenterPoint().x + 1,
                                   r3dRect.bottom - 1), m_crBackground);
            }

            pDC->FillSolidRect(CRect(r3dRect.left + 2, r3dRect.CenterPoint().y - 1, r3dRect.right - 2,
                               r3dRect.CenterPoint().y + 1), m_cr3DShadow);
            if (lprvi->nState & RVIS_HIDDEN)
            {
                pDC->FillSolidRect(CRect(r3dRect.CenterPoint().x - 1, r3dRect.top + 2, r3dRect.CenterPoint().x + 1,
                                   r3dRect.bottom - 2), m_cr3DShadow);
            }

            rect.left += m_iDefaultHeight + 2;
            pDC->SelectObject(pPen);
            pDC->SelectObject(pBrush);
        }

        CFlatHeaderCtrl *hc = GetHeaderCtrl();
        rect.right = rect.left + hc ? hc->GetWidth() : 0;
    }
    else
    {
        COLORREF    crBackground = m_dwStyle & WS_DISABLED ? m_cr3DFace : m_crBackground;
        COLORREF    crItem;
        if (lprvi->iBkColor >= 0)
            crItem = m_arrayColors[lprvi->iBkColor];
        else
            crItem = (m_dwStyle & RVS_SHOWCOLORALTERNATE && bAlternate) ? m_arrayColors[0] : crBackground;

        if (lprvi->nState & RVIS_SELECTED)
        {
            if (m_bFocus)
                crItem = m_crBkSelected;
            else if (m_dwStyle & RVS_SHOWSELALWAYS)
                crItem = m_crBkSelectedNoFocus;
        }

        if (m_bitmap.m_hObject == NULL || crItem != crBackground || iRow == RVI_EDIT)
        {
            pDC->FillSolidRect(rect, crItem);
            pDC->SetBkColor(crItem);
        }
    }

    return rect.left;
}

void CReportCtrl::DrawItem(CDC *pDC, CRect rect, LPRVITEM lprvi)
{
    BOOL    bResult = FALSE;

    if (lprvi->nState & RVIS_OWNERDRAW)
    {
        NMRVITEMDRAW    nmrvid;
        nmrvid.hdr.hwndFrom = GetSafeHwnd();
        nmrvid.hdr.idFrom = GetDlgCtrlID();
        nmrvid.hdr.code = RVN_ITEMDRAW;

        nmrvid.lprvi = lprvi;

        nmrvid.hDC = pDC->GetSafeHdc();
        nmrvid.rect = rect;

        bResult = Notify((LPNMREPORTVIEW) & nmrvid);
    }

    if (!bResult)
    {
        INT iWidth = DrawImage(pDC, rect, lprvi);
        rect.left += iWidth ? iWidth + m_iSpacing : 0;
        if (lprvi->nMask & RVIM_IMAGE && !iWidth)
            return;
        iWidth = DrawCheck(pDC, rect, lprvi);
        rect.left += iWidth ? iWidth + m_iSpacing : 0;
        if (lprvi->nMask & RVIM_CHECK && !iWidth)
            return;
        DrawText(pDC, rect, lprvi);
    }
}

INT CReportCtrl::DrawTreeBox(CDC *pDC, CRect rectBox, BOOL bOpen)
{
    if (rectBox.right < rectBox.left)
        return rectBox.right;

    rectBox.right = rectBox.left + rectBox.Height();

    CPoint  point = rectBox.CenterPoint();

    CRect   rect;
    rect.SetRect(point, point);
    rect.InflateRect(4, 4, 5, 5);

    pDC->FillSolidRect(rect, m_cr3DShadow);
    rect.DeflateRect(1, 1);
    pDC->FillSolidRect(rect, m_crBackground);

    CPen    pen(PS_SOLID, 1, m_crText);
    CPen    *pPen = pDC->SelectObject(&pen);

    pDC->MoveTo(point.x - 2, point.y);
    pDC->LineTo(point.x + 3, point.y);

    if (!bOpen)
    {
        pDC->MoveTo(point.x, point.y - 2);
        pDC->LineTo(point.x, point.y + 3);
    }

    pDC->SelectObject(pPen);
    return rectBox.right;
}

INT CReportCtrl::DrawImage(CDC *pDC, CRect rect, LPRVITEM lprvi)
{
    CImageList  *pImageList = GetImageList();
    INT         iWidth = 0;

    if (lprvi->nMask & RVIM_IMAGE)
    {
        ASSERT(pImageList);
        ASSERT(lprvi->iImage >= 0 && lprvi->iImage < pImageList->GetImageCount());

        if (rect.Width() > 0)
        {
            POINT       point;
            UINT        nStyle = ILD_NORMAL;
            COLORREF    crColor = 0;

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

            switch (m_nBlendStyle)
            {
            case RVP_BLEND_FOCUS:
                nStyle = lprvi->nState & RVIS_FOCUSED ? ILD_FOCUS : ILD_NORMAL;
                crColor = m_crBackground;
                break;
            case RVP_BLEND_SELECT:
                nStyle = lprvi->nState & RVIS_SELECTED ? ILD_SELECTED : ILD_NORMAL;
                crColor = pDC->GetBkColor();
                break;
            case RVP_BLEND_ALL:
                nStyle = lprvi->nState & RVIS_FOCUSED ? ILD_FOCUS : ILD_NORMAL;
                crColor = m_crBackground;
                if (lprvi->nState & RVIS_SELECTED)
                {
                    nStyle = lprvi->nState & RVIS_SELECTED ? ILD_SELECTED : ILD_NORMAL;
                    crColor = pDC->GetBkColor();
                }
                break;
            default:
                if (lprvi->nState & RVIS_BLENDMASK)
                {
                    nStyle = lprvi->nState & RVIS_BLEND25 ? ILD_BLEND25 : ILD_BLEND50;
                    crColor = m_crBlendColor;
                }
                break;
            }

            SIZE    size;
            size.cx = rect.Width() < m_sizeImage.cx ? rect.Width() : m_sizeImage.cx;
            size.cy = m_sizeImage.cy;
            pImageList->DrawIndirect(pDC, lprvi->iImage, point, size, CPoint(0, 0), nStyle, SRCCOPY, CLR_DEFAULT,
                                     crColor);

            if (lprvi->nMask & RVIM_OVERLAY)
            {
                pImageList->DrawIndirect(pDC, lprvi->iOverlay, point, size, CPoint(0, 0), nStyle, SRCCOPY, CLR_DEFAULT,
                                         crColor);
            }

            iWidth = m_sizeImage.cx;
        }
    }
    else
        iWidth = m_arraySubItems[lprvi->iSubItem].nFormat & RVCF_SUBITEM_IMAGE ? m_sizeImage.cx : 0;

    return iWidth;
}

INT CReportCtrl::DrawCheck(CDC *pDC, CRect rect, LPRVITEM lprvi)
{
    INT iWidth = 0;

    if (lprvi->nMask & RVIM_CHECK)
    {
        if (rect.Width() > m_sizeCheck.cx)
        {
            if (m_pCheckList == NULL)
            {
                rect.top = rect.CenterPoint().y - (m_sizeCheck.cy >> 1);
                rect.left = rect.left;
                rect.bottom = rect.top + m_sizeCheck.cx;
                rect.right = rect.left + m_sizeCheck.cy;

                UINT    nState = m_nFrameControlStyle;
                BOOL    bDisable = FALSE;

                switch (lprvi->iCheck)
                {
                case 2:
                    bDisable = TRUE;

                // Intentional
                case 0:
                    nState |= DFCS_BUTTONCHECK;
                    break;
                case 3:
                    bDisable = TRUE;

                // Intentional
                case 1:
                    nState |= DFCS_BUTTONCHECK | DFCS_CHECKED;
                    break;
                case 6:
                    bDisable = TRUE;

                // Intentional
                case 4:
                    nState |= DFCS_BUTTONRADIO;
                    break;
                case 7:
                    bDisable = TRUE;

                // Intentional
                case 5:
                    nState |= DFCS_BUTTONRADIO | DFCS_CHECKED;
                    break;
                }

                pDC->DrawFrameControl(rect, DFC_BUTTON, nState);

                CPen    pen(PS_SOLID, 2, RGB(0xFF, 0, 0));
                CPen    *pPen = pDC->SelectObject(&pen);
                if (bDisable == TRUE)
                {
                    rect.DeflateRect(1, 1);
                    if (lprvi->iCheck >= 4)
                        rect.OffsetRect(1, 0);

                    pDC->MoveTo(rect.left, rect.top);
                    pDC->LineTo(rect.right - 1, rect.bottom - 1);
                    pDC->MoveTo(rect.right - 1, rect.top);
                    pDC->LineTo(rect.left, rect.bottom - 1);
                }

                pDC->SelectObject(pPen);
            }
            else
            {
                POINT   point;

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

                SIZE    size;
                size.cx = rect.Width() < m_sizeCheck.cx ? rect.Width() : m_sizeCheck.cx;
                size.cy = m_sizeCheck.cy;
                m_pCheckList->DrawIndirect(pDC, lprvi->iCheck, point, size, CPoint(0, 0));
            }

            iWidth = m_sizeCheck.cx;
        }
    }
    else
        iWidth = m_arraySubItems[lprvi->iSubItem].nFormat & RVCF_SUBITEM_CHECK ? m_sizeCheck.cx : 0;

    return iWidth;
}

INT CReportCtrl::DrawText(CDC *pDC, CRect rect, LPRVITEM lprvi)
{
    CSize   size;
    CString str = lprvi->lpszText;
    if (rect.Width() > 0 && lprvi->nMask & RVIM_TEXT)
    {
        INT     iExtra = 0;
        CFont   *pCurrentFont = NULL;
        if (lprvi->nMask & RVIM_CATEGORY)
        {
            if (m_arraySubItems[lprvi->iSubItem].nFormat & RVCF_EX_HASDATE)
            {
                str = CCategoryTime(lprvi->lpszText).NameCategory(m_cBaseTime, m_bEarlier & m_bLater);
                if (str.IsEmpty())
                    str = lprvi->lpszText;
            }

            pCurrentFont = pDC->SelectObject(&m_fontBold);
            iExtra = pDC->SetTextCharacterExtra(1);
        }

        size = pDC->GetTextExtent(lprvi->lpszText);
        switch (m_arraySubItems[lprvi->iSubItem].nFormat & HDF_JUSTIFYMASK)
        {
        case HDF_LEFT:
        case HDF_LEFT | HDF_RTLREADING:
            pDC->DrawText(str, -1, rect, DT_LEFT | DT_END_ELLIPSIS | DT_NOPREFIX | DT_SINGLELINE | DT_VCENTER);
            break;
        case HDF_CENTER:
        case HDF_CENTER | HDF_RTLREADING:
            pDC->DrawText(str, -1, rect, DT_CENTER | DT_END_ELLIPSIS | DT_NOPREFIX | DT_SINGLELINE | DT_VCENTER);
            break;
        case HDF_RIGHT:
        case HDF_RIGHT | HDF_RTLREADING:
            pDC->DrawText(str, -1, rect, DT_RIGHT | DT_END_ELLIPSIS | DT_NOPREFIX | DT_SINGLELINE | DT_VCENTER);
            break;
        }

        if (lprvi->nMask & RVIM_CATEGORY)
        {
            pDC->SetTextCharacterExtra(iExtra);
            pDC->SelectObject(pCurrentFont);
        }
    }

    size.cx = rect.Width() > size.cx ? size.cx : rect.Width();
    return size.cx > 0 ? size.cx : 0;
}

BOOL CReportCtrl::DrawBkgnd(CDC *pDC, CRect rect, COLORREF crBackground)
{
    if (m_bitmap.m_hObject != NULL)
    {
        CDC     dc;
        CRgn    rgnBitmap;
        CRect   rectBitmap;

        dc.CreateCompatibleDC(pDC);
        dc.SelectObject(&m_bitmap);

        rect.IntersectRect(rect, m_rectReport);

        INT iHPos = GetScrollPos32(SB_HORZ);
        rectBitmap.top = rect.top - rect.top % m_sizeBitmap.cy;
        rectBitmap.bottom = rect.top + m_sizeBitmap.cy;
        rectBitmap.left = rect.left - (rect.left + iHPos) % m_sizeBitmap.cx;
        rectBitmap.right = rect.left + m_sizeBitmap.cx;

        INT x, y;
        for (x = rectBitmap.left; x < rect.right; x += m_sizeBitmap.cx)
        {
            for (y = rectBitmap.top; y < rect.bottom; y += m_sizeBitmap.cy)
                pDC->BitBlt(x, y, m_sizeBitmap.cx, m_sizeBitmap.cy, &dc, 0, 0, SRCCOPY);
        }

        return TRUE;
    }

    pDC->FillSolidRect(rect, crBackground);
    return FALSE;
}

BOOL CReportCtrl::BeginEdit(INT iRow, INT iColumn, UINT nKey)
{
    if (iRow == RVI_INVALID || iColumn < 0)
        return FALSE;

    if (iRow == RVI_EDIT && !(m_dwStyle & RVS_SHOWEDITROW))
        return FALSE;

    TCHAR   szText[REPORTCTRL_MAX_TEXT];

    m_iEditItem = GetItemForRow(iRow);
    m_iEditSubItem = GetSubItemFromColumn(iColumn);

    // Make sure that the item to edit actually exists
    ASSERT(m_iEditItem != RVI_INVALID && m_iEditSubItem != -1);

    RVITEM  rvi;
    rvi.iItem = m_iEditItem;
    rvi.iSubItem = m_iEditSubItem;
    rvi.nMask = RVIM_TEXT | RVIM_STATE | RVIM_LPARAM;
    rvi.lpszText = szText;
    rvi.iTextMax = REPORTCTRL_MAX_TEXT;
    GetItem(&rvi);

    if (rvi.nState & RVIS_READONLY)
        return FALSE;

    NMRVITEMEDIT    nmrvie;
    memset(&nmrvie, 0, sizeof(nmrvie));

    nmrvie.hdr.hwndFrom = GetSafeHwnd();
    nmrvie.hdr.idFrom = GetDlgCtrlID();
    nmrvie.hdr.code = RVN_BEGINITEMEDIT;

    nmrvie.iItem = m_iEditItem;
    nmrvie.iSubItem = m_iEditSubItem;

    nmrvie.hWnd = NULL;
    if (!GetItemRect(m_iEditItem, m_iEditSubItem, &nmrvie.rect))
        return FALSE;

    nmrvie.nKey = nKey;
    if (rvi.nMask & RVIM_TEXT)
        nmrvie.lpszText = szText;

    nmrvie.lParam = rvi.lParam;

    BOOL    bResult = FALSE;
    if (GetParent() != NULL)
        bResult = Notify((LPNMREPORTVIEW) & nmrvie);

    if (!bResult)
    {
        if (!(rvi.nMask & RVIM_TEXT))
            return FALSE;

        if (!GetItemRect(m_iEditItem, m_iEditSubItem, &nmrvie.rect, RVIR_TEXT))
            return FALSE;

        nmrvie.rect.top += 2;

        CReportEditCtrl *pWnd = new CReportEditCtrl(m_iEditItem, m_iEditSubItem, nmrvie.bButton);
        if (pWnd == NULL)
            return FALSE;

        if (!pWnd->Create(WS_CHILD | WS_VISIBLE | ES_AUTOHSCROLL, nmrvie.rect, this, 0))
        {
            delete pWnd;
            return FALSE;
        }

        pWnd->SetFont(rvi.nMask & RVIM_STATE && rvi.nState & RVIS_BOLD ? &m_fontBold : &m_font);
        pWnd->SetWindowText(szText);
        pWnd->BeginEdit(nKey);

        m_hEditWnd = pWnd->GetSafeHwnd();
    }
    else
    {
        if (nmrvie.hWnd == NULL)
            return FALSE;

        m_hEditWnd = nmrvie.hWnd;
    }

    if (m_hEditWnd != NULL)
        ::SetFocus(m_hEditWnd);

    return TRUE;
}

void CReportCtrl::EndEdit(BOOL bUpdate, LPNMRVITEMEDIT lpnmrvie)
{
    if (!(m_dwStyle & RVS_OWNERDATA) && bUpdate)
    {
        if (lpnmrvie == NULL)
        {
            TCHAR   szText[REPORTCTRL_MAX_TEXT];

            INT     i = ::GetWindowText(m_hEditWnd, szText, REPORTCTRL_MAX_TEXT - 1);
            szText[REPORTCTRL_MAX_TEXT - 1] = 0;

            if (i || !::GetLastError())
                SetItemText(m_iEditItem, m_iEditSubItem, szText);
        }
        else
            SetItemText(m_iEditItem, m_iEditSubItem, lpnmrvie->lpszText);
    }

    if (IsWindow(m_hEditWnd))
        ::PostMessage(m_hEditWnd, WM_CLOSE, 0, 0);
    m_hEditWnd = NULL;

    CWnd    *pWnd = GetFocus();
    if (pWnd)
    {
        if (pWnd->GetSafeHwnd() != m_hWnd)
            m_bFocus = FALSE;
    }

    RedrawRows(m_iFocusRow);
}

void CReportCtrl::ShellSort(INT iSubItem, INT iLow, INT iHigh, BOOL bAscending)
{
    int g, i, j;
    int iRows = iHigh - iLow + 1;
    for (g = iRows / 2; g > 0; g /= 2)
    {
        for (i = g; i < iRows; ++i)
        {
            for (j = (i - g) + iLow; j >= iLow; j -= g)
            {
                if (bAscending)
                {
                    if (CompareItems(m_arrayRows[j], iSubItem, m_arrayRows[j + g], iSubItem) <= 0)
                        continue;
                }
                else if (CompareItems(m_arrayRows[j], iSubItem, m_arrayRows[j + g], iSubItem) >= 0)
                    continue;

                INT iTemp;
                iTemp = m_arrayRows[j + g];
                m_arrayRows[j + g] = m_arrayRows[j];
                m_arrayRows[j] = iTemp;
            }
        }
    }
}

void CReportCtrl::QuickSort(INT iSubItem, INT iLow, INT iHigh, BOOL bAscending)
{
    INT i, j;

    i = iHigh;
    j = iLow;

    INT iRow = m_arrayRows[((INT) ((iLow + iHigh) / 2))];

    do
    {
        if (bAscending)
        {
            while (CompareItems(m_arrayRows[j], iSubItem, iRow, iSubItem) < 0)
                j++;
            while (CompareItems(m_arrayRows[i], iSubItem, iRow, iSubItem) > 0)
                i--;
        }
        else
        {
            while (CompareItems(m_arrayRows[j], iSubItem, iRow, iSubItem) > 0)
                j++;
            while (CompareItems(m_arrayRows[i], iSubItem, iRow, iSubItem) < 0)
                i--;
        }

        if (i >= j)
        {
            if (i != j)
            {
                INT iTemp;

                iTemp = m_arrayRows[i];
                m_arrayRows[i] = m_arrayRows[j];
                m_arrayRows[j] = iTemp;
            }

            i--;
            j++;
        }
    } while (j <= i);

    if (iLow < i)
        QuickSort(iSubItem, iLow, i, bAscending);
    if (j < iHigh)
        QuickSort(iSubItem, j, iHigh, bAscending);
}

void CReportCtrl::TreeSort(INT iSubItem, LPTREEITEM lptiParent, BOOL bAscending, BOOL bTraverse)
{
    TREEITEM    ti;

    LPTREEITEM  lpti, lptiSibling = lptiParent->lptiChildren;
    while (lptiSibling != NULL)
    {
        lpti = lptiSibling;
        lptiSibling = lptiSibling->lptiSibling;

        lpti->lptiSibling = NULL;

        if (bTraverse && lpti->lptiChildren != NULL)
            TreeSort(iSubItem, lpti, bAscending);

        InsertTree(iSubItem, &ti, (LPTREEITEM) RVTI_SORT, lpti, bAscending);
    }

    lptiParent->lptiChildren = ti.lptiChildren;
}

// CReportCtrl message handlers
BOOL CReportCtrl::OnSetCursor(CWnd *pWnd, UINT nHitTest, UINT message)
{
    SetCursor(::LoadCursor(NULL, IDC_ARROW));
    return CWnd::OnSetCursor(pWnd, nHitTest, message);
}

void CReportCtrl::OnSetFocus(CWnd *pOldWnd)
{
    CWnd::OnSetFocus(pOldWnd);

    m_bFocus = TRUE;
    Invalidate();
}

void CReportCtrl::OnKillFocus(CWnd *pNewWnd)
{
    CWnd::OnKillFocus(pNewWnd);

    if (CWnd::IsChild(pNewWnd) == FALSE)
        m_bFocus = FALSE;

    if (m_bFocus == FALSE && pNewWnd != NULL && pNewWnd->GetSafeHwnd() != m_wndTip.GetSafeHwnd())
        m_wndTip.Hide();

    Invalidate();
}

void CReportCtrl::OnSysColorChange()
{
    CWnd::OnSysColorChange();
    GetSysColors();
}

void CReportCtrl::OnSettingChange(UINT uFlags, LPCTSTR lpszSection)
{
    CWnd::OnSettingChange(uFlags, lpszSection);

    m_nRowsPerWheelNotch = GetMouseScrollLines();
}

BOOL CReportCtrl::OnQueryNewPalette()
{
    Invalidate();
    return CWnd::OnQueryNewPalette();
}

void CReportCtrl::OnPaletteChanged(CWnd *pFocusWnd)
{
    CWnd::OnPaletteChanged(pFocusWnd);

    if (pFocusWnd->GetSafeHwnd() != GetSafeHwnd())
        Invalidate();
}

void CReportCtrl::OnSize(UINT nType, int cx, int cy)
{
    if (m_hEditWnd != NULL)
        EndEdit();

    CWnd::OnSize(nType, cx, cy);

    Layout();
}

void CReportCtrl::OnWindowPosChanging(WINDOWPOS FAR *lpwndpos)
{
    CWnd::OnWindowPosChanging(lpwndpos);

    if (!m_bHorzScrollBar)
    {
        ShowScrollBar(SB_HORZ, FALSE);
        ModifyStyle(WS_HSCROLL, 0, SWP_DRAWFRAME);
    }
}

LRESULT CReportCtrl::OnGetFont(WPARAM wParam, LPARAM lParam)
{
    UNREFERENCED_PARAMETER(wParam);
    UNREFERENCED_PARAMETER(lParam);

    return (LRESULT) m_font.m_hObject;
}

LRESULT CReportCtrl::OnSetFont(WPARAM wParam, LPARAM lParam)
{
    LRESULT lResult = Default();

    CFont   *pFont = CFont::FromHandle((HFONT) wParam);
    if (pFont)
    {
        LOGFONT lf;
        pFont->GetLogFont(&lf);

        m_font.DeleteObject();
        m_font.CreateFontIndirect(&lf);

        lf.lfWeight = FW_BOLD;
        m_fontBold.DeleteObject();
        m_fontBold.CreateFontIndirect(&lf);
    }

    CDC *pDC = GetDC();
    if (pDC)
    {
        CFont       *pFont = pDC->SelectObject(&m_font);
        TEXTMETRIC  tm;
        pDC->GetTextMetrics(&tm);
        pDC->SelectObject(pFont);
        ReleaseDC(pDC);

        INT iDefaultHeight;
        iDefaultHeight = tm.tmHeight + tm.tmExternalLeading + 2;
        iDefaultHeight += m_dwStyle & RVS_SHOWHGRID ? 1 : 0;

        if (m_pImageList != NULL)
            m_iDefaultHeight = max(iDefaultHeight, m_sizeImage.cy + 1);
        else
            m_iDefaultHeight = iDefaultHeight;

        m_sizeCheck.cx = m_iDefaultHeight - 2;
        m_sizeCheck.cy = m_iDefaultHeight - 2;
    }

    if (::IsWindow(GetSafeHwnd()) && lParam)
        Layout();

    return lResult;
}

BOOL CReportCtrl::OnEraseBkgnd(CDC *pDC)
{
    UNREFERENCED_PARAMETER(pDC);
    return TRUE;
}

void CReportCtrl::OnNcCalcSize(BOOL bCalcValidRects, NCCALCSIZE_PARAMS FAR *lpncsp)
{
    UNREFERENCED_PARAMETER(bCalcValidRects);

    LONG    lStyle = ::GetWindowLong(m_hWnd, GWL_STYLE);

    if (lStyle & WS_BORDER)
        ::InflateRect(&lpncsp->rgrc[0], -GetSystemMetrics(SM_CXEDGE), -GetSystemMetrics(SM_CYEDGE));

    Default();
}

void CReportCtrl::OnNcPaint()
{
    Default();

    LONG    lStyle = ::GetWindowLong(m_hWnd, GWL_STYLE);
    if (lStyle & WS_BORDER)
    {
        CWindowDC   dc(this);
        CRect       rcBounds;
        GetWindowRect(rcBounds);
        ScreenToClient(rcBounds);
        rcBounds.OffsetRect(-rcBounds.left, -rcBounds.top);
        dc.DrawEdge(rcBounds, EDGE_SUNKEN, BF_RECT);
    }
}

void CReportCtrl::OnPaint()
{
    if (m_bWidthChanged)
    {
        Notify(RVN_NEWWIDTH, -1, NULL);
        m_bWidthChanged = false;
    }

    if (m_wndCategoryHdr)
        m_wndCategoryHdr->MatchHeaders();

    CPaintDC    dc(this);

    CPalette    *pPalette = NULL;
    if (dc.GetDeviceCaps(RASTERCAPS) & RC_PALETTE)
    {
        pPalette = dc.SelectPalette(&m_palette, FALSE);
        dc.RealizePalette();
    }

    CRect   rExclude(m_rectHeader);
    rExclude.right = GetHeaderCtrl()->GetWidth() - rExclude.left;
    rExclude.top = 0;
    dc.ExcludeClipRect(&rExclude);

    if (m_bDoubleBuffer)
    {
        CMemDC  MemDC(&dc);
        DrawCtrl(&MemDC);

        if (m_hEditWnd != NULL)
        {
            RECT    rect;
            ::GetWindowRect(m_hEditWnd, &rect);
            ScreenToClient(&rect);
            dc.ExcludeClipRect(&rect);
        }
    }
    else
        DrawCtrl(&dc);

    if (pPalette && dc.GetDeviceCaps(RASTERCAPS) & RC_PALETTE)
        dc.SelectPalette(pPalette, FALSE);
}

void CReportCtrl::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar *pScrollBar)
{
    UNREFERENCED_PARAMETER(pScrollBar);

    INT iScrollPos = GetScrollPos32(SB_HORZ);

    if (m_hEditWnd != NULL)
        EndEdit();

    switch (nSBCode)
    {
    case SB_LINERIGHT:
        iScrollPos += min(m_iVirtualWidth - (m_rectReport.Width() + iScrollPos), m_rectReport.Width() >> 3);
        break;
    case SB_LINELEFT:
        iScrollPos -= min(iScrollPos, m_rectReport.Width() >> 3);
        break;
    case SB_PAGERIGHT:
        iScrollPos = min(m_iVirtualWidth, iScrollPos + m_rectReport.Width());
        break;
    case SB_PAGELEFT:
        iScrollPos = max(0, iScrollPos - m_rectReport.Width());
        break;
    case SB_THUMBPOSITION:
        iScrollPos = nPos;
        break;
    case SB_THUMBTRACK:
        iScrollPos = GetScrollPos32(SB_HORZ, true);
        if (!iScrollPos)
            return;
        break;
    case SB_RIGHT:
        iScrollPos = m_iVirtualWidth;
        break;
    case SB_LEFT:
        iScrollPos = 0;
        break;
    default:
        return;
    }

    ScrollWindow(SB_HORZ, iScrollPos);
}

void CReportCtrl::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar *pScrollBar)
{
    UNREFERENCED_PARAMETER(pScrollBar);
    UNREFERENCED_PARAMETER(nPos);

    INT iFirst = GetScrollPos32(SB_VERT), iLast;
    INT iItems = GetVisibleRows(TRUE, &iFirst, &iLast);

    if (m_hEditWnd != NULL)
        EndEdit();

    switch (nSBCode)
    {
    case SB_LINEUP:
        if (iFirst > 0)
            iFirst--;
        break;
    case SB_LINEDOWN:
        if (iFirst + iItems < m_iVirtualHeight)
            iFirst++;
        break;
    case SB_PAGEUP:
        GetVisibleRows(TRUE, &iLast, &iFirst, TRUE);
        iFirst = iLast - 1;
        iFirst = iFirst < 0 ? 0 : iFirst;
        break;
    case SB_PAGEDOWN:
        iFirst += iItems;
        GetVisibleRows(TRUE, &iFirst, &iLast);
        GetVisibleRows(TRUE, &iFirst, &iLast, TRUE);
        break;
    case SB_THUMBPOSITION:
        GetVisibleRows(TRUE, &iFirst, &iLast);
        GetVisibleRows(TRUE, &iFirst, &iLast, TRUE);
        break;
    case SB_THUMBTRACK:
        SetScrollPos32(SB_VERT, GetScrollPos32(SB_VERT, TRUE));
        iFirst = GetScrollPos32(SB_VERT);
        GetVisibleRows(TRUE, &iFirst, &iLast);
        GetVisibleRows(TRUE, &iFirst, &iLast, TRUE);
        break;
    case SB_TOP:
        iFirst = 0;
        break;
    case SB_BOTTOM:
        iLast = m_iVirtualHeight - 1;
        GetVisibleRows(TRUE, &iFirst, &iLast, TRUE);
        break;
    default:
        return;
    }

    ScrollWindow(SB_VERT, iFirst);
}

void CReportCtrl::OnLButtonDown(UINT nFlags, CPoint point)
{
    RVHITTESTINFO   rvhti;
    rvhti.point = point;

    HitTest(&rvhti);
    SetFocus();

    if (!(m_dwStyle & RVS_OWNERDATA))
    {
        if (rvhti.nFlags & RVHT_ONCATITEM)
        {
            Notify(RVN_CATEGORYCLICK, nFlags, &rvhti);
            return;
        }

        if (rvhti.nFlags & RVHT_ONCATIMAGE)
        {
            if (!Notify(RVN_CATEGORYCLICK, nFlags, &rvhti))
            {
                INT iNext = GetRowFromItem(rvhti.iItem);
                while (iNext > 0 && !(GetState(iNext) & RVIS_CATEGORY))
                    --iNext;

                UINT    nState = RVIS_HIDDEN & (RVIS_HIDDEN ^ GetState(iNext));
                SetState(iNext, nState, RVIS_HIDDEN);
                MarkCategories();
                ScrollWindow(SB_VERT, GetScrollPos32(SB_VERT));
                Invalidate();
            }

            return;
        }
    }

    if (AsTreeColumn() && rvhti.nFlags & RVHT_ONITEMTREEBOX)
    {
        if (!Notify(RVN_ITEMEXPANDING, rvhti.iItem, rvhti.iSubItem))
        {
            Expand((HTREEITEM) m_arrayItems[rvhti.iItem].lptiItem, RVE_TOGGLE);
            Notify(RVN_ITEMEXPANDED, rvhti.iItem, rvhti.iSubItem);
            return;
        }
    }

    INT iFocusRow = m_iFocusRow, iFocusColumn = m_iFocusColumn;

    INT iSubItem = GetSubItemFromColumn(rvhti.iColumn);
    if (m_dwStyle & RVS_FOCUSSUBITEMS && iSubItem >= 0 && !(m_arraySubItems[iSubItem].nFormat & RVCF_SUBITEM_NOFOCUS))
        m_iFocusColumn = rvhti.iColumn;
    else
        m_iFocusColumn = -1;

    if (rvhti.iItem != RVI_INVALID)
    {
        INT iRow = GetRowFromItem(rvhti.iItem);
        ASSERT(iRow != RVI_INVALID);

        switch (nFlags & (MK_CONTROL | MK_SHIFT))
        {
        case MK_CONTROL:
            SelectRows(iRow, iRow, TRUE, TRUE, TRUE);
            m_iSelectRow = iRow;
            break;
        case MK_SHIFT:
            SelectRows(iRow, m_iSelectRow, TRUE);
            break;
        case MK_CONTROL | MK_SHIFT:
            SelectRows(iRow, m_iSelectRow, TRUE, TRUE);
            m_iSelectRow = iRow;
            break;
        default:
            SelectRows(iRow, iRow, TRUE);
            m_iSelectRow = iRow;

            if
            (
                m_iFocusRow == iFocusRow
            &&  m_iFocusColumn == iFocusColumn
            &&  (m_iFocusRow == RVI_EDIT || m_dwStyle & RVS_FOCUSSUBITEMS)
            ) BeginEdit(m_iFocusRow, m_iFocusColumn, VK_LBUTTON);
            break;
        }
    }

    if (Notify(RVN_ITEMCLICK, nFlags, &rvhti))
    {
        CWnd::OnLButtonDown(nFlags, point);
        return;
    }
}

void CReportCtrl::OnRButtonDown(UINT nFlags, CPoint point)
{
    SetFocus();

    RVHITTESTINFO   rvhti;
    rvhti.point = point;

    HitTest(&rvhti);

    if (rvhti.nFlags & (RVHT_ONCATIMAGE | RVHT_ONCATITEM))
    {
        Notify(RVN_CATEGORYRCLICK, nFlags, &rvhti);
        return;
    }

    if (Notify(RVN_ITEMRCLICK, nFlags, &rvhti))
    {
        CWnd::OnRButtonDown(nFlags, point);
        return;
    }

    if (rvhti.iItem != RVI_INVALID)
    {
        INT iRow = GetRowFromItem(rvhti.iItem);
        ASSERT(iRow != RVI_INVALID);

        switch (nFlags & (MK_CONTROL | MK_SHIFT))
        {
        case MK_CONTROL:
            SelectRows(iRow, iRow, TRUE, TRUE, TRUE);
            m_iSelectRow = iRow;
            break;
        case MK_SHIFT:
            SelectRows(iRow, m_iSelectRow, TRUE);
            break;
        case MK_CONTROL | MK_SHIFT:
            SelectRows(iRow, m_iSelectRow, TRUE, TRUE);
            m_iSelectRow = iRow;
            break;
        default:
            if (GetState(iRow) & RVIS_SELECTED)
                SelectRows(iRow, iRow, TRUE, TRUE, FALSE);
            else
                SelectRows(iRow, iRow, TRUE, FALSE, TRUE);
            m_iSelectRow = iRow;
            break;
        }
    }
}

void CReportCtrl::OnLButtonDblClk(UINT nFlags, CPoint point)
{
    RVHITTESTINFO   rvhti;
    rvhti.point = point;
    HitTest(&rvhti);
    if (rvhti.nFlags & (RVHT_ONCATITEM | RVHT_ONCATIMAGE))
        return;
    if (Notify(RVN_ITEMDBCLICK, nFlags, &rvhti))
    {
        CWnd::OnLButtonDblClk(nFlags, point);
        return;
    }

    if (m_dwStyle & RVS_TREEVIEW)
    {
        if (rvhti.iItem >= RVI_FIRST && m_arrayItems[rvhti.iItem].lptiItem->lptiChildren != NULL)
        {
            Expand((HTREEITEM) m_arrayItems[rvhti.iItem].lptiItem, RVE_TOGGLE);
            Notify(RVN_ITEMEXPANDED, rvhti.iItem, rvhti.iSubItem);
        }
    }
}

UINT CReportCtrl::OnGetDlgCode()
{
    return DLGC_WANTARROWS;
}

void CReportCtrl::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
    INT iFirst = GetScrollPos32(SB_VERT), iLast;
    GetVisibleRows(TRUE, &iFirst, &iLast);

    NMREPORTVIEW    nmrv;
    nmrv.hdr.hwndFrom = GetSafeHwnd();
    nmrv.hdr.idFrom = GetDlgCtrlID();
    nmrv.hdr.code = RVN_KEYDOWN;

    nmrv.nKeys = nChar;
    nmrv.nFlags = nFlags;

    nmrv.iItem = GetItemForRow(m_iFocusRow);
    nmrv.iSubItem = GetSubItemFromColumn(m_iFocusColumn);

    if (nmrv.iItem != RVI_INVALID && (!(m_dwStyle & RVS_OWNERDATA)))
        nmrv.lParam = GetItemStruct(nmrv.iItem, -1).lParam;

    m_bProcessKey = FALSE;

    if (!Notify(&nmrv))
    {
        switch (nChar)
        {
        case VK_SPACE:
            SelectRows(m_iFocusRow, m_iFocusRow, TRUE, TRUE, IsCtrlDown() ? TRUE : FALSE);
            return;
        case VK_LEFT:
            if (m_dwStyle & RVS_TREEVIEW && m_iFocusColumn == -1)
            {
                INT iFocusItem = GetItemForRow(m_iFocusRow);
                if (iFocusItem >= RVI_FIRST)
                {
                    LPTREEITEM  lpti = m_arrayItems[iFocusItem].lptiItem;
                    if (lpti->lptiChildren != NULL && lpti->bOpen == TRUE)
                    {
                        Expand((HTREEITEM) lpti, RVE_COLLAPSE);
                        break;
                    }
                    else
                    {
                        if (lpti->lptiParent != &m_tiRoot)
                        {
                            lpti = lpti->lptiParent;

                            INT iFocusRow = GetRowFromItem(lpti->iItem);
                            ASSERT(iFocusRow != RVI_INVALID);

                            SelectRows(iFocusRow, iFocusRow);
                            EnsureVisible(lpti->iItem);
                            break;
                        }
                        else if (m_dwStyle & RVS_SHOWEDITROW)
                        {
                            SelectRows(RVI_EDIT, RVI_EDIT);
                            break;
                        }
                    }
                }
            }

            if (m_dwStyle & RVS_FOCUSSUBITEMS)
            {
                if (m_iFocusColumn > 0)
                {
                    m_iFocusColumn--;

                    while (m_iFocusColumn >= 0)
                    {
                        INT iSubItem = GetSubItemFromColumn(m_iFocusColumn);
                        if (iSubItem >= 0 && !(m_arraySubItems[iSubItem].nFormat & RVCF_SUBITEM_NOFOCUS))
                            break;

                        m_iFocusColumn--;
                    }
                }
                else
                    m_iFocusColumn = -1;

                EnsureVisibleColumn(m_iFocusColumn);

                iFirst = GetRowFromItem(m_iFocusRow);
                RedrawRows(iFirst);
            }
            else
            {
                INT iScrollPos = GetScrollPos32(SB_HORZ);
                INT iScroll = min(iScrollPos, m_rectReport.Width() >> 3);

                ScrollWindow(SB_HORZ, iScrollPos - iScroll);
            }

            return;
        case VK_RIGHT:
            if (m_dwStyle & RVS_TREEVIEW && m_iFocusColumn == -1)
            {
                INT iFocusItem = GetItemForRow(m_iFocusRow);
                if (iFocusItem >= RVI_FIRST)
                {
                    LPTREEITEM  lpti = m_arrayItems[iFocusItem].lptiItem;
                    if (lpti->lptiChildren != NULL)
                    {
                        if (lpti->bOpen == FALSE)
                        {
                            Expand((HTREEITEM) lpti, RVE_EXPAND);
                            break;
                        }
                        else
                        {
                            lpti = lpti->lptiChildren;

                            INT iFocusRow = GetRowFromItem(lpti->iItem);
                            ASSERT(iFocusRow != RVI_INVALID);

                            SelectRows(iFocusRow, iFocusRow);
                            EnsureVisible(lpti->iItem);
                            break;
                        }
                    }
                }
            }

            if (m_dwStyle & RVS_FOCUSSUBITEMS)
            {
                if (m_iFocusColumn < m_arrayColumns.GetSize() - 1)
                {
                    INT iFocusColumn = m_iFocusColumn;

                    m_iFocusColumn++;

                    while (m_iFocusColumn < m_arrayColumns.GetSize())
                    {
                        INT iSubItem = GetSubItemFromColumn(m_iFocusColumn);
                        if (iSubItem >= 0 && !(m_arraySubItems[iSubItem].nFormat & RVCF_SUBITEM_NOFOCUS))
                            break;

                        m_iFocusColumn++;

                        if (m_iFocusColumn == m_arrayColumns.GetSize())
                        {
                            m_iFocusColumn = iFocusColumn;
                            break;
                        }
                    }
                }
                else
                    m_iFocusColumn = m_arrayColumns.GetSize() - 1;

                EnsureVisibleColumn(m_iFocusColumn);

                iFirst = GetRowFromItem(m_iFocusRow);
                RedrawRows(iFirst);
            }
            else
            {
                INT iScrollPos = GetScrollPos32(SB_HORZ);
                INT iScroll = min(m_iVirtualWidth - (m_rectReport.Width() + iScrollPos), m_rectReport.Width() >> 3);

                ScrollWindow(SB_HORZ, iScrollPos + iScroll);
            }

            return;
        case VK_DOWN:
            if (m_iFocusRow < m_iVirtualHeight - 1)
            {
                if (m_iFocusRow == RVI_INVALID && !(m_dwStyle & RVS_SHOWEDITROW) && m_iVirtualHeight > 0)
                    m_iFocusRow = RVI_EDIT;

                if (IsCtrlDown())
                {
                    SelectRows(m_iFocusRow + 1, m_iFocusRow + 1, FALSE);
                    m_iSelectRow = m_iFocusRow;
                }
                else if (!IsShiftDown())
                {
                    SelectRows(m_iFocusRow + 1, m_iFocusRow + 1, TRUE);
                    m_iSelectRow = m_iFocusRow;
                }
                else
                    SelectRows(m_iFocusRow + 1, m_iSelectRow, TRUE);

                EnsureVisible(GetItemForRow(m_iFocusRow));
            }

            return;
        case VK_UP:
            if ((m_dwStyle & RVS_SHOWEDITROW && m_iFocusRow > RVI_EDIT) || m_iFocusRow > RVI_FIRST)
            {
                if (IsCtrlDown())
                {
                    SelectRows(m_iFocusRow - 1, m_iFocusRow - 1, FALSE);
                    m_iSelectRow = m_iFocusRow;
                }
                else if (!IsShiftDown())
                {
                    SelectRows(m_iFocusRow - 1, m_iFocusRow - 1, TRUE);
                    m_iSelectRow = m_iFocusRow;
                }
                else
                    SelectRows(m_iFocusRow - 1, m_iSelectRow, TRUE);

                EnsureVisible(GetItemForRow(m_iFocusRow));
            }

            return;
        case VK_NEXT:
            if (m_iFocusRow == iLast)
            {
                SendMessage(WM_VSCROLL, SB_PAGEDOWN, 0);

                iFirst = GetScrollPos32(SB_VERT);
                GetVisibleRows(TRUE, &iFirst, &iLast);
            }

            if (IsCtrlDown())
            {
                SelectRows(iLast, iLast, FALSE);
                m_iSelectRow = m_iFocusRow;
                return;
            }
            else if (!IsShiftDown())
            {
                iFirst = iLast;
                m_iSelectRow = iLast;
            }
            else
                iFirst = m_iSelectRow;

            SelectRows(iLast, iFirst, TRUE);
            return;
        case VK_PRIOR:
            if (m_iFocusRow == iFirst)
            {
                SendMessage(WM_VSCROLL, SB_PAGEUP, 0);

                iFirst = GetScrollPos32(SB_VERT);
            }

            iFirst = iLast < iFirst ? iLast : iFirst;

            if (IsCtrlDown())
            {
                SelectRows(iFirst, iFirst, FALSE);
                m_iSelectRow = m_iFocusRow;
                return;
            }
            else if (!IsShiftDown())
            {
                iLast = iFirst;
                m_iSelectRow = iFirst;
            }
            else
                iLast = m_iSelectRow;

            SelectRows(iFirst, iLast, TRUE);
            return;
        case VK_HOME:
            if (m_iFocusRow > 0)
            {
                SendMessage(WM_VSCROLL, SB_TOP, 0);

                if (!IsShiftDown())
                    m_iSelectRow = 0;

                SelectRows(0, m_iSelectRow, IsCtrlDown() ? FALSE : TRUE);
            }
            break;
        case VK_END:
            if (m_iFocusRow < m_iVirtualHeight)
            {
                SendMessage(WM_VSCROLL, SB_BOTTOM, 0);

                if (!IsShiftDown())
                    m_iSelectRow = m_iVirtualHeight - 1;

                SelectRows(m_iVirtualHeight - 1, m_iSelectRow, IsCtrlDown() ? FALSE : TRUE);
            }
            break;
        case VK_ESCAPE:
            break;
        default:
            m_bProcessKey = !IsCtrlDown() ? TRUE : FALSE;
            break;
        }
    }

    CWnd::OnKeyDown(nChar, nRepCnt, nFlags);
}

void CReportCtrl::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
{
    if (m_bProcessKey && m_iFocusColumn != -1 && (m_iFocusRow == RVI_EDIT || m_dwStyle & RVS_FOCUSSUBITEMS))
        if (BeginEdit(m_iFocusRow, m_iFocusColumn, nChar))
            return;

    CWnd::OnChar(nChar, nRepCnt, nFlags);
}

BOOL CReportCtrl::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
{
    int i;

    // A m_nRowsPerWheelNotch value less than 0 indicates that the mouse wheel
    // scrolls whole pages, not just lines.
    if (m_nRowsPerWheelNotch == -1)
    {
        int iPagesScrolled = zDelta / 120;

        if (iPagesScrolled > 0)
            for (i = 0; i < iPagesScrolled; i++)
                PostMessage(WM_VSCROLL, SB_PAGEUP, 0);
        else
            for (i = 0; i > iPagesScrolled; i--)
                PostMessage(WM_VSCROLL, SB_PAGEDOWN, 0);
    }
    else
    {
        int iRowsScrolled = (int) m_nRowsPerWheelNotch * zDelta / 120;

        if (iRowsScrolled > 0)
            for (i = 0; i < iRowsScrolled; i++)
                PostMessage(WM_VSCROLL, SB_LINEUP, 0);
        else
            for (i = 0; i > iRowsScrolled; i--)
                PostMessage(WM_VSCROLL, SB_LINEDOWN, 0);
    }

    return CWnd::OnMouseWheel(nFlags, zDelta, pt);
}

void CReportCtrl::OnMouseMove(UINT nFlags, CPoint point)
{
    BOOL    bHide = TRUE;

    if (m_bFocus && !nFlags && m_dwStyle & RVS_EXPANDSUBITEMS && m_hEditWnd == NULL)
    {
        RVHITTESTINFO   rvhti;
        rvhti.point = point;

        if (HitTest(&rvhti) != RVI_INVALID)
        {
            TCHAR   szText[REPORTCTRL_MAX_TEXT];

            RVITEM  rvi;
            rvi.iItem = rvhti.iItem;
            rvi.iSubItem = rvhti.iSubItem;
            rvi.lpszText = szText;
            rvi.iTextMax = REPORTCTRL_MAX_TEXT;
            rvi.nMask = RVIM_TEXT | RVIM_STATE;
            GetItem(&rvi);

            if (rvhti.nFlags & RVHT_ONITEMTEXT && rvi.nMask & RVIM_TEXT && _tcslen(rvi.lpszText))
            {
                CRect   rect;

                if (m_dwStyle & RVS_TREEVIEW && m_arrayItems[rvhti.iItem].lptiItem->iSubItem >= 0)
                    GetItemRect(rvhti.iItem, rvhti.iSubItem, rect, RVIR_TEXTNOCOLUMNS);
                else
                    GetItemRect(rvhti.iItem, rvhti.iSubItem, rect, RVIR_TEXT);

                GetExpandedItemText(&rvi);

                bHide = !m_wndTip.Show(rect, rvi.lpszText, rvi.nState & RVIS_BOLD ? &m_fontBold : &m_font);
            }
        }
    }

    if (m_wndTip.IsWindowVisible() && bHide)
        m_wndTip.Hide();

    CWnd::OnMouseMove(nFlags, point);
}

void CReportCtrl::OnHdnItemchanging(NMHDR *pNMHDR, LRESULT *pResult)
{
    CFlatHeaderCtrl *hc;
    if (pNMHDR->hwndFrom == (HWND) * GetHeaderCtrl())
        hc = GetHeaderCtrl();
    else
        hc = GetCategoryHdrCtrl();
    ASSERT(hc);
    *pResult = hc->ItemChanging(pNMHDR);
}

void CReportCtrl::OnHdnItemChanged(NMHDR *pNMHDR, LRESULT *pResult)
{
    LPHDITEM    lphdi = (LPHDITEM) ((LPNMHEADER) pNMHDR)->pitem;

    if (m_hEditWnd != NULL)
        EndEdit();

    if (pNMHDR->hwndFrom == (HWND) * GetHeaderCtrl())
    {
        HDITEM  hdi;
        hdi.mask = HDI_WIDTH | HDI_ORDER | HDI_LPARAM;
        m_wndHeader.GetItem(((LPNMHEADER) pNMHDR)->iItem, &hdi);

        if (lphdi->mask & HDI_FORMAT)
        {
            m_arraySubItems[hdi.lParam].nFormat = (m_arraySubItems[hdi.lParam].nFormat &~RVCF_JUSTIFYMASK) | (lphdi->fmt & HDF_JUSTIFYMASK);
        }

        if (lphdi->mask & HDI_WIDTH)
        {
            // AS: ref bug 14238 setting the minimal column width to 10 pixels
            if (lphdi->cxy < FHDR_MIN_COL_WIDTH)
            {
                hdi.cxy = FHDR_MIN_COL_WIDTH;
                lphdi->cxy = FHDR_MIN_COL_WIDTH;
                m_wndHeader.SetItem(((LPNMHEADER) pNMHDR)->iItem, &hdi);
            }

            m_iVirtualWidth += lphdi->cxy - m_arraySubItems[hdi.lParam].iWidth;
            ASSERT(m_iVirtualWidth >= 0);

            m_arraySubItems[hdi.lParam].iWidth = lphdi->cxy;
        }

        if (m_wndHeader.m_bAutoSizing == FALSE)
            ScrollWindow(SB_HORZ, GetScrollPos32(SB_HORZ));
        Notify(RVN_LAYOUTCHANGED, RVI_INVALID, (INT) hdi.lParam);
    }
    else
    {
        CCategoryHdr    *ac = GetCategoryHdrCtrl();
        if (ac)
        {
            HDITEM  hdi;
            hdi.mask = HDI_WIDTH | HDI_ORDER | HDI_LPARAM;
            ac->GetItem(((LPNMHEADER) pNMHDR)->iItem, &hdi);
            if (lphdi->mask & HDI_WIDTH)
            {
                if (lphdi->cxy < FHDR_MIN_COL_WIDTH)
                {
                    hdi.cxy = FHDR_MIN_COL_WIDTH;
                    lphdi->cxy = FHDR_MIN_COL_WIDTH;
                    ac->SetItem(((LPNMHEADER) pNMHDR)->iItem, &hdi);
                }
            }
        }
    }

    m_bWidthChanged = TRUE;
    *pResult = FALSE;
}

void CReportCtrl::OnHdnItemClick(NMHDR *pNMHDR, LRESULT *pResult)
{
    if (m_hEditWnd != NULL)
        EndEdit();

    if (pNMHDR->hwndFrom == (HWND) * GetHeaderCtrl())
    {
        NMRVHEADER  nmrvhdr;
        if (!NotifyHdr(&nmrvhdr, RVN_HEADERCLICK, ((LPNMHEADER) pNMHDR)->iItem))
        {
            if (!(m_dwStyle & (RVS_NOSORT | RVS_OWNERDATA)))
            {
                BOOL    bAscending;

                INT     iOld = m_wndHeader.GetSortColumn(&bAscending);
                INT     iNew = ((LPNMHEADER) pNMHDR)->iItem;

                if (iNew == iOld)
                    bAscending = !bAscending;
                else
                    bAscending = TRUE;

                SortItems(nmrvhdr.iSubItem, bAscending);
            }
        }
    }

    *pResult = FALSE;
}

void CReportCtrl::OnHdnBeginDrag(NMHDR *pNMHDR, LRESULT *pResult)
{
    LPNMHEADER  pnmhdr = LPNMHEADER(pNMHDR);
    if (m_hEditWnd != NULL)
        EndEdit();
    *pResult = pnmhdr->iItem == 0 && m_dwStyle & RVS_TREEVIEW ? TRUE : FALSE;
}

void CReportCtrl::OnHdnEndDrag(NMHDR *pNMHDR, LRESULT *pResult)
{
    LPNMHEADER  lpnmhdr = (LPNMHEADER) pNMHDR;

    if (pNMHDR->hwndFrom == (HWND) * GetHeaderCtrl())
    {
        INT iDropResult = m_wndHeader.GetDropResult();
        if (iDropResult == FHDR_DROPPED || iDropResult == FHDR_ONTARGET)
        {
            if (m_dwStyle & RVS_ALLOWCOLUMNREMOVAL)
                DeactivateSubItem(lpnmhdr->pitem->lParam);

            *pResult = TRUE;
        }
        else
        {
            m_bColumnsReordered = TRUE;
            m_iIndentColumnPending = m_wndHeader.OrderToIndex(0);
            Invalidate();

            *pResult = FALSE;
        }
    }
}

void CReportCtrl::OnHdnDividerDblClick(NMHDR *pNMHDR, LRESULT *pResult)
{
    if (m_hEditWnd != NULL)
        EndEdit();

    NMRVHEADER  nmrvhdr;
    if
    (
        !NotifyHdr(&nmrvhdr, RVN_DIVIDERDBLCLICK, ((LPNMHEADER) pNMHDR)->iItem)
    &&  !(m_dwStyle & RVS_OWNERDATA)
    &&  !(m_arraySubItems[nmrvhdr.iSubItem].nFormat & RVCF_EX_FIXEDWIDTH)
    )
    {
        INT iItems = m_arrayItems.GetSize();
        if (iItems > 0)
        {
            INT     iWidth = 0;
            RECT    rect;

            for (INT iItem = 0; iItem < iItems; iItem++)
            {
                if (MeasureItem(iItem, nmrvhdr.iSubItem, &rect))
                {
                    INT cx = rect.right - rect.left;
                    iWidth = cx > iWidth ? cx : iWidth;
                }
            }

            HDITEM  hdi;
            hdi.mask = HDI_WIDTH;
            hdi.cxy = iWidth;
            m_wndHeader.SetItem(((LPNMHEADER) pNMHDR)->iItem, &hdi);
        }
    }

    *pResult = FALSE;
}

void CReportCtrl::OnRvnEndItemEdit(NMHDR *pNMHDR, LRESULT *pResult)
{
    LPNMRVITEMEDIT  lpnmrvie = (LPNMRVITEMEDIT) pNMHDR;

    ASSERT(m_iEditItem == lpnmrvie->iItem);
    ASSERT(m_iEditSubItem == lpnmrvie->iSubItem);

    NMRVITEMEDIT    nmrvie;
    memset(&nmrvie, 0, sizeof(nmrvie));

    nmrvie.hdr.hwndFrom = GetSafeHwnd();
    nmrvie.hdr.idFrom = GetDlgCtrlID();
    nmrvie.hdr.code = RVN_ENDITEMEDIT;

    nmrvie.iItem = m_iEditItem;
    nmrvie.iSubItem = m_iEditSubItem;

    nmrvie.hWnd = lpnmrvie->hWnd;

    nmrvie.nKey = lpnmrvie->nKey;
    nmrvie.lpszText = lpnmrvie->lpszText;

    nmrvie.lParam = lpnmrvie->lParam;

    BOOL    bResult = FALSE;
    if (GetParent() != NULL)
        bResult = Notify((LPNMREPORTVIEW) & nmrvie);

    if (bResult)
        EndEdit(FALSE);
    else
        EndEdit(nmrvie.nKey != VK_ESCAPE ? TRUE : FALSE, &nmrvie);

    pResult = FALSE;
}

void CReportCtrl::SetCategoryHdr(CCategoryHdr *pWndCategoryHdr)
{
    ASSERT(m_wndCategoryHdr == NULL);

    m_wndCategoryHdr = pWndCategoryHdr == NULL ? new CCategoryHdr() : pWndCategoryHdr;
    if (m_wndCategoryHdr)
    {
        DWORD           dwStyle = HDS_HORZ | HDS_BUTTONS | HDS_FULLDRAG | CCS_TOP;
        CFlatHeaderCtrl *hc = GetHeaderCtrl();
        int             iHeight = hc ? hc->GetHeight() : 20;
        m_wndCategoryHdr->Create(dwStyle, CRect(0, 0, 100, iHeight + 2), this, 0);
        m_wndCategoryHdr->OnInitialUpdate();
        hc->ModifyProperty(FH_PROPERTY_ENABLEAUTOSIZE, hc->IsAutoSize());
        m_wndCategoryHdr->Show();
    }
}

void CReportCtrl::SetCategorySubItem(INT iSubItem, BOOL bCategoryAscending)
{
    ASSERT(iSubItem < m_arraySubItems.GetSize());
    ASSERT((m_dwStyle & RVS_TREEVIEW) || GetColumnFromSubItem(m_iCategorySubItem) < 0);
    ASSERT(!(m_dwStyle & RVS_OWNERDATA));
    if (m_dwStyle & RVS_OWNERDATA)
        return;

    if (m_iCategorySubItem == iSubItem && bCategoryAscending == m_bCategoryAscending)
        return;
    if (m_iCategorySubItem >= 0)
    {
        ActivateSubItem(m_iCategorySubItem, m_iCategoryColumn);
        m_iCategorySubItem = -1;
        m_iDefaultHeight--;
    }

    if (iSubItem >= 0)
    {
        m_iCategoryColumn = GetColumnFromSubItem(iSubItem);
        ASSERT(m_iCategoryColumn >= 0);
        m_iDefaultHeight++;
        if (!(m_dwStyle & RVS_TREEVIEW))
            DeactivateSubItem(iSubItem);
        m_bCategoryAscending = bCategoryAscending;
        m_iCategorySubItem = iSubItem;
        if (m_wndCategoryHdr == NULL)
            SetCategoryHdr();
    }

    if (m_wndCategoryHdr != NULL)
        m_wndCategoryHdr->Show();
    if (iSubItem >= 0)
        ResortItems();
    Layout();
    Invalidate();
    if (m_wndCategoryHdr != NULL)
        m_wndCategoryHdr->NewCategory();
    MarkCategories();
}

INT CReportCtrl::SetCategoryImage(INT image)
{
    INT iImage = m_iCategoryImage;
    m_iCategoryImage = image;
    return iImage;
}

INT CReportCtrl::GetCategorySubItem(BOOL *pbCategoryAscending)
{
    ASSERT((m_dwStyle & RVS_TREEVIEW) || GetColumnFromSubItem(m_iCategorySubItem) < 0);
    if (pbCategoryAscending)
        *pbCategoryAscending = m_bCategoryAscending;
    return m_iCategorySubItem;
}

INT CReportCtrl::GetRootRow(INT iRow)
{
    if (m_dwStyle & RVS_TREEVIEW)
    {
        while (iRow > 0)
        {
            HTREEITEM   thisNode = GetItemHandle(GetItemForRow(iRow));
            ASSERT(thisNode);

            HTREEITEM   parent = GetNextItem(thisNode, RVGN_PARENT);
            if (parent == NULL)
                break;
            iRow--;
        }
    }

    return iRow;
}

void CReportCtrl::ExpandCategories(UINT nState)
{
    ASSERT(nState == RVIS_HIDDEN || nState == 0);

    int iRows = m_arrayRows.GetSize();
    for (INT iRow = 0; iRow < iRows; iRow++)
        SetState(iRow, nState, RVIS_HIDDEN);
    if (m_dwStyle & RVS_TREEVIEW)
        BuildTree();
    m_bUpdateItemMap = TRUE;
}

void CReportCtrl::MarkCategories(void)
{
    if (m_iCategorySubItem < 0)
        return;

    INT     iMaxRows = m_arrayRows.GetSize();
    UINT    iHide = 0;
    m_iVirtualHeight = 0;
    for (int iRow = 0, iCategory = 0; iRow < iMaxRows; iRow++)
    {
        int iState = RVIS_CATEGORY;
        if (iRow != 0)
        {
            if (m_dwStyle & RVS_TREEVIEW)
            {
                HTREEITEM   thisNode = GetItemHandle(GetItemForRow(iRow));
                ASSERT(thisNode);
                if (GetNextItem(thisNode, RVGN_PARENT) != NULL)
                    iState = 0;
            }
        }

        if
        (
            iRow != 0
        &&  CompareItems(m_arrayRows[iRow], m_iCategorySubItem, m_arrayRows[iCategory], m_iCategorySubItem, TRUE) == 0
        ) iState = 0;
        if (iState)
        {
            iCategory = iRow;
            iHide = GetState(iRow) & RVIS_HIDDEN;
        }

        SetState(iRow, iState | iHide, RVIS_CATEGORY | RVIS_HIDDEN);
        if (iState || !(GetState(iRow) & RVIS_HIDDEN))
            ++m_iVirtualHeight;
    }
}

INT CReportCtrl::GetRowFromVirtualRow(INT iVirtualRow)
{
    if (m_dwStyle & RVS_OWNERDATA)
        return iVirtualRow;

    INT iMaxRows = m_arrayRows.GetSize();

    for (int iRow = 0; iVirtualRow > 0 && iRow + 1 < iMaxRows; iRow++)
    {
        int nState = m_arrayItems[m_arrayRows[iRow]].nState;
        if (nState & RVIS_CATEGORY || 0 == (nState & RVIS_HIDDEN))
            --iVirtualRow;
    }

    while (iRow + 1 < iMaxRows)
    {
        int nState = m_arrayItems[m_arrayRows[iRow]].nState;
        if (nState & RVIS_CATEGORY)
            break;
        if (nState & RVIS_HIDDEN)
            ++iRow;
        else
            break;
    }

    return iRow;
}

INT CReportCtrl::GetVirtualRow(INT iRow)
{
    if (m_dwStyle & RVS_OWNERDATA)
        return iRow;
    ASSERT(iRow < m_arrayRows.GetSize());

    for (int iVirtualRow = 0, i = 0; i <= iRow; i++)
    {
        int nState = m_arrayItems[m_arrayRows[i]].nState;
        if (nState & RVIS_CATEGORY || 0 == (nState & RVIS_HIDDEN))
            ++iVirtualRow;
    }

    return iVirtualRow;
}

INT CReportCtrl::GetCategoryForVirtualRow(INT iVirtualRow, BOOL bTop)
{
    return GetCategoryForRow(GetRowFromVirtualRow(iVirtualRow), bTop);
}

INT CReportCtrl::GetCategoryForRow(INT iRow, BOOL bTop)
{
    if (iRow >= 0 && m_iCategorySubItem >= 0)
    {
        if (bTop || iRow == 0)
            return m_iCategorySubItem;
        if (m_arrayItems[m_arrayRows[iRow]].nState & RVIS_CATEGORY)
            return m_iCategorySubItem;
    }

    return -1;
}

// CReportSubItemListCtrl
CReportSubItemListCtrl::CReportSubItemListCtrl()
{
    m_pReportCtrl = NULL;

    m_iSubItem = -1;
    m_pDragWnd = NULL;
}

CReportSubItemListCtrl::~CReportSubItemListCtrl()
{
}

BEGIN_MESSAGE_MAP(CReportSubItemListCtrl, CDragListBox)
//{{AFX_MSG_MAP(CReportSubItemListCtrl)
    ON_WM_ERASEBKGND()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

// CReportSubItemListCtrl attributes
BOOL CReportSubItemListCtrl::SetReportCtrl(CReportCtrl *pReportCtrl)
{
    if (pReportCtrl == NULL)
        return FALSE;

    ASSERT_KINDOF(CReportCtrl, pReportCtrl);

    m_pReportCtrl = pReportCtrl;
    ResetContent();

    return TRUE;
}

CReportCtrl *CReportSubItemListCtrl::GetReportCtrl()
{
    return m_pReportCtrl;
}

// CReportSubItemListCtrl operations
BOOL CReportSubItemListCtrl::UpdateList()
{
    INT iSubItem, iSubItems = m_pReportCtrl->m_arraySubItems.GetSize();

    ResetContent();

    for (iSubItem = 0; iSubItem < iSubItems; iSubItem++)
    {
        if (!m_pReportCtrl->IsActiveSubItem(iSubItem) && Include(iSubItem))
        {
            INT iItem = AddString(m_pReportCtrl->m_arraySubItems[iSubItem].strText);
            SetItemData(iItem, iSubItem);
        }
    }

    return TRUE;
}

BOOL CReportSubItemListCtrl::Include(INT iSubItem)
{
    UNREFERENCED_PARAMETER(iSubItem);

    return TRUE;
}

BOOL CReportSubItemListCtrl::Disable(INT iSubItem)
{
    UNREFERENCED_PARAMETER(iSubItem);

    return FALSE;
}

// CReportSubItemListCtrl message handlers
void CReportSubItemListCtrl::PreSubclassWindow()
{
    CDragListBox::PreSubclassWindow();

    SetItemHeight(0, GetItemHeight(0) + 2);
}

void CReportSubItemListCtrl::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
    CDC     *pDC = CDC::FromHandle(lpDrawItemStruct->hDC);

    CRect   rcItem(lpDrawItemStruct->rcItem);

    if (GetCount() > 0)
    {
        BOOL    bDisable = Disable(lpDrawItemStruct->itemData);

        if (GetExStyle() & WS_EX_STATICEDGE)
        {
            pDC->Draw3dRect(rcItem, m_pReportCtrl->m_cr3DHiLight, m_pReportCtrl->m_cr3DDkShadow);
            rcItem.DeflateRect(1, 1);
            pDC->FillSolidRect(rcItem, m_pReportCtrl->m_cr3DFace);
            rcItem.DeflateRect(1, 1);
        }
        else
        {
            pDC->DrawFrameControl(rcItem, DFC_BUTTON, DFCS_BUTTONPUSH);
            rcItem.DeflateRect(2, 2);
        }

        pDC->SetBkMode(TRANSPARENT);

        if (lpDrawItemStruct->itemState & ODS_SELECTED)
        {
            pDC->FillSolidRect(rcItem, m_pReportCtrl->m_cr3DDkShadow);
            pDC->SetTextColor(bDisable ? m_pReportCtrl->m_cr3DFace : m_pReportCtrl->m_cr3DHiLight);
        }
        else
        {
            if (bDisable)
            {
                pDC->SetTextColor(::GetSysColor(COLOR_3DHIGHLIGHT));

                CRect   rect = rcItem;

                rect.left += 1;
                rect.top += 1;
                rect.right += 1;
                rect.bottom += 1;

                pDC->DrawText(m_pReportCtrl->m_arraySubItems[lpDrawItemStruct->itemData].strText, -1, rect,
                              DT_SINGLELINE | DT_NOPREFIX | DT_NOCLIP | DT_VCENTER | DT_END_ELLIPSIS | DT_LEFT);

                pDC->SetTextColor(::GetSysColor(COLOR_3DSHADOW));
            }
            else
                pDC->SetTextColor(::GetSysColor(COLOR_WINDOWTEXT));
        }

        pDC->DrawText(m_pReportCtrl->m_arraySubItems[lpDrawItemStruct->itemData].strText, -1, rcItem,
                      DT_SINGLELINE | DT_NOPREFIX | DT_NOCLIP | DT_VCENTER | DT_END_ELLIPSIS | DT_LEFT);
    }
    else
        pDC->FillSolidRect(rcItem, ::GetSysColor(COLOR_WINDOW));
}

BOOL CReportSubItemListCtrl::BeginDrag(CPoint pt)
{
    if (GetCount() <= 0)
        return FALSE;

    INT iItem = ItemFromPt(pt);
    if (iItem >= 0)
    {
        m_iSubItem = GetItemData(iItem);

        if (Disable(m_iSubItem))
        {
            m_iSubItem = -1;
            return FALSE;
        }

        GetClientRect(m_rcDragWnd);
        m_rcDragWnd.bottom = m_rcDragWnd.top + GetItemHeight(0);

        _tcscpy(m_szSubItemText, m_pReportCtrl->m_arraySubItems[m_iSubItem].strText);

        m_hdiSubItem.mask = HDI_WIDTH | HDI_TEXT | HDI_FORMAT;
        m_hdiSubItem.cxy = m_rcDragWnd.Width();
        m_hdiSubItem.pszText = m_szSubItemText;
        m_hdiSubItem.cchTextMax = sizeof(m_szSubItemText);
        m_hdiSubItem.fmt = HDF_STRING | HDF_LEFT;

        m_pDragWnd = new CFHDragWnd;
        if (m_pDragWnd)
            m_pDragWnd->Create(m_rcDragWnd, &m_pReportCtrl->m_wndHeader, -2, &m_hdiSubItem);

        GetWindowRect(m_rcDropTarget1);
        m_pReportCtrl->m_wndHeader.GetWindowRect(m_rcDropTarget2);
    }

    m_iDropIndex = -1;

    return TRUE;
}

UINT CReportSubItemListCtrl::Dragging(CPoint pt)
{
    CPoint  point = pt;
    point.Offset(-(m_rcDragWnd.Width() >> 1), -(m_rcDragWnd.Height() >> 1));

    if (m_pDragWnd != NULL)
        m_pDragWnd->SetWindowPos(&wndTop, point.x, point.y, 0, 0, SWP_NOSIZE | SWP_SHOWWINDOW | SWP_NOACTIVATE);

    if (m_rcDropTarget1.PtInRect(pt))
        return DL_MOVECURSOR;

    m_iDropIndex = m_pReportCtrl->m_wndHeader.SendMessage(HDM_SETHOTDIVIDER, TRUE, MAKELONG(pt.x, pt.y));

    if (m_rcDropTarget2.PtInRect(pt))
        return DL_MOVECURSOR;

    return DL_STOPCURSOR;
}

void CReportSubItemListCtrl::CancelDrag(CPoint pt)
{
    UNREFERENCED_PARAMETER(pt);

    m_pReportCtrl->m_wndHeader.SendMessage(HDM_SETHOTDIVIDER, FALSE, -1);

    if (m_pDragWnd != NULL)
    {
        m_pDragWnd->DestroyWindow();
        m_pDragWnd = NULL;
    }

    m_iSubItem = -1;
}

void CReportSubItemListCtrl::Dropped(INT iSrcIndex, CPoint pt)
{
    UNREFERENCED_PARAMETER(pt);
    UNREFERENCED_PARAMETER(iSrcIndex);

    m_pReportCtrl->m_wndHeader.SendMessage(HDM_SETHOTDIVIDER, FALSE, -1);

    if (m_pDragWnd != NULL)
    {
        m_pDragWnd->DestroyWindow();
        m_pDragWnd = NULL;
    }

    if (m_iSubItem >= 0)
    {
        if (!m_pReportCtrl->GetActiveSubItemCount())
            m_pReportCtrl->ActivateSubItem(m_iSubItem);
        else if (m_iDropIndex >= 0)
            m_pReportCtrl->ActivateSubItem(m_iSubItem, m_iDropIndex);
    }

    m_iSubItem = -1;
}

BOOL CReportSubItemListCtrl::OnEraseBkgnd(CDC *pDC)
{
    if (!IsWindowEnabled())
    {
        CRect   rect;
        GetClientRect(rect);

        pDC->FillSolidRect(rect, ::GetSysColor(COLOR_BTNFACE));
        return TRUE;
    }
    else
        return CDragListBox::OnEraseBkgnd(pDC);
}

// CReportEditCtrl
CReportEditCtrl::CReportEditCtrl(INT iItem, INT iSubItem, BOOL bButton)
{
    m_bEndEdit = FALSE;

    m_iItem = iItem;
    m_iSubItem = iSubItem;

    m_bButton = bButton;

    m_nLastKey = VK_RETURN;
}

CReportEditCtrl::~CReportEditCtrl()
{
}

BOOL CReportEditCtrl::PreCreateWindow(CREATESTRUCT &cs)
{
    if (m_bButton)
    {
        if (cs.cx > cs.cy + 10)
            cs.cx -= cs.cy;
        else
            m_bButton = FALSE;
    }

    return CEdit::PreCreateWindow(cs);
}

int CReportEditCtrl::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    if (CEdit::OnCreate(lpCreateStruct) == -1)
        return -1;

    if (m_bButton)
    {
        CRect   rect
                (
                    lpCreateStruct->x + lpCreateStruct->cx,
                    lpCreateStruct->y,
                    lpCreateStruct->x + lpCreateStruct->cx + lpCreateStruct->cy,
                    lpCreateStruct->y + lpCreateStruct->cy
                );
        CWnd    *pParent = GetParent();
        ASSERT(pParent != NULL);

        if (!m_wndButton.Create(_T("..."), WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, rect, pParent, 0))
            return -1;

        m_wndButton.SetFont(pParent->GetFont());
    }

    return 0;
}

void CReportEditCtrl::PostNcDestroy()
{
    CEdit::PostNcDestroy();
    delete this;
}

BEGIN_MESSAGE_MAP(CReportEditCtrl, CEdit)
//{{AFX_MSG_MAP(CReportEditCtrl)
    ON_WM_KILLFOCUS()
    ON_WM_GETDLGCODE()
    ON_WM_CREATE()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

// CReportEditCtrl operations
void CReportEditCtrl::BeginEdit(UINT nKey)
{
    CString str;
    GetWindowText(str);

    switch (nKey)
    {
    case VK_LBUTTON:
    case VK_RETURN:
        SetSel(str.GetLength(), -1);
        break;
    case VK_BACK:
        SetSel(str.GetLength(), -1);
        SendMessage(WM_CHAR, nKey);
        break;
    case VK_TAB:
    case VK_DOWN:
    case VK_UP:
    case VK_RIGHT:
    case VK_LEFT:
    case VK_NEXT:
    case VK_PRIOR:
    case VK_HOME:
    case VK_SPACE:
    case VK_END:
        SetSel(0, -1);
        break;
    default:
        SetSel(0, -1);
        SendMessage(WM_CHAR, nKey);
        break;
    }
}

void CReportEditCtrl::EndEdit()
{
    CString str;

    if (m_bEndEdit)
        return;

    m_bEndEdit = TRUE;
    GetWindowText(str);

    NMRVITEMEDIT    nmrvie;
    memset(&nmrvie, 0, sizeof(nmrvie));

    nmrvie.hdr.hwndFrom = GetSafeHwnd();
    nmrvie.hdr.idFrom = GetDlgCtrlID();
    nmrvie.hdr.code = RVN_ENDITEMEDIT;

    nmrvie.iItem = m_iItem;
    nmrvie.iSubItem = m_iSubItem;

    nmrvie.bButton = m_bButton;

    nmrvie.hWnd = nmrvie.hdr.hwndFrom;

    nmrvie.nKey = m_nLastKey;
    nmrvie.lpszText = (LPTSTR) (LPCTSTR) str;

    CWnd    *pWnd = GetOwner();
    if (pWnd != NULL)
        pWnd->SendMessage(WM_NOTIFY, GetDlgCtrlID(), (LPARAM) & nmrvie);

    PostMessage(WM_CLOSE, 0, 0);
}

// CReportEditCtrl message handlers
void CReportEditCtrl::OnKillFocus(CWnd *pNewWnd)
{
    CWnd::OnKillFocus(pNewWnd);

    if (m_bButton)
    {
        if (pNewWnd->GetSafeHwnd() == m_wndButton.GetSafeHwnd())
        {
            BOOL            bResult = FALSE;
            CString         str;

            NMRVITEMEDIT    nmrvie;
            memset(&nmrvie, 0, sizeof(nmrvie));

            CReportCtrl *pWnd = DYNAMIC_DOWNCAST(CReportCtrl, GetOwner());
            if (pWnd != NULL)
            {
                GetWindowText(str);

                nmrvie.hdr.hwndFrom = pWnd->GetSafeHwnd();
                nmrvie.hdr.idFrom = pWnd->GetDlgCtrlID();
                nmrvie.hdr.code = RVN_BUTTONCLICK;

                nmrvie.iItem = m_iItem;
                nmrvie.iSubItem = m_iSubItem;

                nmrvie.bButton = m_bButton;

                nmrvie.hWnd = GetSafeHwnd();

                VERIFY((nmrvie.lpszText = str.GetBuffer(REPORTCTRL_MAX_TEXT)) != 0);

                bResult = pWnd->Notify((LPNMREPORTVIEW) & nmrvie);

                str.ReleaseBuffer();
            }

            if (bResult == TRUE)
                SetWindowText(nmrvie.lpszText);

            SetFocus();
            return;
        }
    }

    EndEdit();
}

UINT CReportEditCtrl::OnGetDlgCode()
{
    return DLGC_WANTALLKEYS;
}

BOOL CReportEditCtrl::PreTranslateMessage(MSG *pMsg)
{
    if
    (
        pMsg->message == WM_KEYDOWN
    &&  (pMsg->wParam == VK_TAB || pMsg->wParam == VK_RETURN || pMsg->wParam == VK_ESCAPE)
    )
    {
        m_nLastKey = pMsg->wParam;

        GetParent()->SetFocus();
        return TRUE;
    }

    if (pMsg->message == WM_SYSCHAR)
        return TRUE;

    return CEdit::PreTranslateMessage(pMsg);
}

// CReportComboCtrl
CReportComboCtrl::CReportComboCtrl(INT iItem, INT iSubItem)
{
    m_bEndEdit = FALSE;

    m_iItem = iItem;
    m_iSubItem = iSubItem;

    m_nLastKey = VK_RETURN;
}

CReportComboCtrl::~CReportComboCtrl()
{
}

void CReportComboCtrl::PostNcDestroy()
{
    CComboBox::PostNcDestroy();
    delete this;
}

BEGIN_MESSAGE_MAP(CReportComboCtrl, CComboBox)
//{{AFX_MSG_MAP(CReportComboCtrl)
    ON_WM_GETDLGCODE()
    ON_CONTROL_REFLECT(CBN_KILLFOCUS, OnKillfocus)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

// CReportComboCtrl operations
void CReportComboCtrl::BeginEdit(UINT nKey)
{
    CString str;
    GetWindowText(str);

    if (GetStyle() & CBS_DROPDOWN)
    {
        switch (nKey)
        {
        case VK_LBUTTON:
        case VK_RETURN:
            break;
        default:
            PostMessage(WM_CHAR, nKey);
            break;
        }
    }
}

void CReportComboCtrl::EndEdit()
{
    CString str;

    if (m_bEndEdit)
        return;

    m_bEndEdit = TRUE;
    GetWindowText(str);

    NMRVITEMEDIT    nmrvie;
    memset(&nmrvie, 0, sizeof(nmrvie));

    nmrvie.hdr.hwndFrom = GetSafeHwnd();
    nmrvie.hdr.idFrom = GetDlgCtrlID();
    nmrvie.hdr.code = RVN_ENDITEMEDIT;

    nmrvie.iItem = m_iItem;
    nmrvie.iSubItem = m_iSubItem;

    nmrvie.hWnd = nmrvie.hdr.hwndFrom;

    nmrvie.nKey = m_nLastKey;
    nmrvie.lpszText = (LPTSTR) (LPCTSTR) str;

    CWnd    *pWnd = GetOwner();
    if (pWnd != NULL)
        pWnd->SendMessage(WM_NOTIFY, GetDlgCtrlID(), (LPARAM) & nmrvie);

    PostMessage(WM_CLOSE, 0, 0);
}

// CReportComboCtrl message handlers
UINT CReportComboCtrl::OnGetDlgCode()
{
    return DLGC_WANTALLKEYS;
}

BOOL CReportComboCtrl::PreTranslateMessage(MSG *pMsg)
{
    if
    (
        pMsg->message == WM_KEYDOWN
    &&  (pMsg->wParam == VK_TAB || pMsg->wParam == VK_RETURN || pMsg->wParam == VK_ESCAPE)
    )
    {
        m_nLastKey = pMsg->wParam;

        GetParent()->SetFocus();
        return TRUE;
    }

    if (pMsg->message == WM_SYSCHAR)
        return TRUE;

    return CComboBox::PreTranslateMessage(pMsg);
}

void CReportComboCtrl::OnKillfocus()
{
    EndEdit();
}

// CReportTipCtrl
CReportTipCtrl::CReportTipCtrl()
{
    WNDCLASS    wndcls;
    HINSTANCE   hInst = AfxGetInstanceHandle();

    if (!(::GetClassInfo(hInst, REPORTTIPCTRL_CLASSNAME, &wndcls)))
    {
        wndcls.style = CS_SAVEBITS;
        wndcls.lpfnWndProc = ::DefWindowProc;
        wndcls.cbClsExtra = wndcls.cbWndExtra = 0;
        wndcls.hInstance = hInst;
        wndcls.hIcon = NULL;
        wndcls.hCursor = LoadCursor(hInst, IDC_ARROW);
        wndcls.hbrBackground = (HBRUSH) (COLOR_INFOBK + 1);
        wndcls.lpszMenuName = NULL;
        wndcls.lpszClassName = REPORTTIPCTRL_CLASSNAME;
        if (!AfxRegisterClass(&wndcls))
            AfxThrowResourceException();
    }

    m_bLayeredWindows = InitLayeredWindows();
    m_nAlpha = 0;

    m_dwLastLButtonDown = ULONG_MAX;
    m_dwDblClickMsecs = GetDoubleClickTime();

    m_pReportCtrl = NULL;
}

CReportTipCtrl::~CReportTipCtrl()
{
}

BEGIN_MESSAGE_MAP(CReportTipCtrl, CWnd)
//{{AFX_MSG_MAP(CReportTipCtrl)
    ON_WM_MOUSEMOVE()
    ON_WM_TIMER()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

// CReportTipCtrl message handlers
BOOL CReportTipCtrl::Create(CReportCtrl *pReportCtrl)
{
    ASSERT_VALID(pReportCtrl);

    m_pReportCtrl = pReportCtrl;

    return CreateEx(WS_EX_TOPMOST | WS_EX_TOOLWINDOW | (m_bLayeredWindows ? WS_EX_LAYERED : 0), // |WS_EX_LAYERED,
                    REPORTTIPCTRL_CLASSNAME, NULL, WS_BORDER | WS_POPUP, CRect(0, 0, 0, 0), pReportCtrl, 0, NULL);
}

BOOL CReportTipCtrl::Show(CRect rectText, LPCTSTR lpszText, CFont *pFont)
{
    BOOL    bResult = FALSE;

    ASSERT(::IsWindow(GetSafeHwnd()));

    if (rectText.IsRectEmpty() || GetFocus() == NULL)
    {
        Hide();
        return FALSE;
    }

    if (rectText != m_rectText)
    {
        Invalidate();
        UpdateWindow();

        m_rectTip.top = -1;
        m_rectTip.bottom = rectText.Height() + 1;
        m_rectTip.left = -1;
        m_rectTip.right = rectText.Width() + 1;
        m_rectTip.InflateRect(m_pReportCtrl->m_iSpacing, 0);
        m_pReportCtrl->ClientToScreen(rectText);

        CClientDC   dc(this);
        pFont = pFont == NULL ? m_pReportCtrl->GetFont() : pFont;

        CFont   *pFontDC = dc.SelectObject(pFont);

        CRect   rectDisplay = rectText;
        CSize   size = dc.GetTextExtent(lpszText, _tcslen(lpszText));
        rectDisplay.right = rectDisplay.left + size.cx;

        if (rectDisplay.right > rectText.right)
        {
            rectDisplay.InflateRect(m_pReportCtrl->m_iSpacing, 0);

            SetWindowPos(&wndTop, rectDisplay.left, rectDisplay.top, rectDisplay.Width(), rectDisplay.Height(),
                         SWP_NOOWNERZORDER | SWP_SHOWWINDOW | SWP_NOACTIVATE);

            dc.SetBkMode(TRANSPARENT);
            dc.TextOut(m_pReportCtrl->m_iSpacing - 1, 1, lpszText);

            if (m_bLayeredWindows)
            {
                UpdateWindow();

                if (m_nAlpha < 255)
                    SetTimer(1, REPORTTIPCTRL_FADETIME / (255 / REPORTTIPCTRL_FADESTEP), NULL);

                g_lpfnSetLayeredWindowAttributes(GetSafeHwnd(), RGB(0xFF, 0, 0xFF), (BYTE) (m_nAlpha), ULW_ALPHA);
            }

            bResult = TRUE;
        }
        else
            Hide();

        m_rectText = rectText;

        dc.SelectObject(pFontDC);
    }
    else
        Hide();

    return bResult;
}

void CReportTipCtrl::Hide()
{
    if (!::IsWindow(GetSafeHwnd()))
        return;

    if (m_bLayeredWindows)
    {
        m_nAlpha = 255;
        SetTimer(1, REPORTTIPCTRL_FADETIMEOUT, NULL);
    }

    if (IsWindowVisible())
        ShowWindow(SW_HIDE);
}

void CReportTipCtrl::OnMouseMove(UINT nFlags, CPoint point)
{
    if (!m_rectTip.PtInRect(point))
    {
        Hide();

        ClientToScreen(&point);

        CWnd    *pWnd = WindowFromPoint(point);
        if (pWnd == this)
            pWnd = m_pReportCtrl;

        INT iHitTest = (INT) pWnd->SendMessage(WM_NCHITTEST, 0, MAKELONG(point.x, point.y));

        if (iHitTest == HTCLIENT)
        {
            pWnd->ScreenToClient(&point);
            pWnd->PostMessage(WM_MOUSEMOVE, nFlags, MAKELONG(point.x, point.y));
        }
        else
        {
            pWnd->PostMessage(WM_NCMOUSEMOVE, iHitTest, MAKELONG(point.x, point.y));
        }
    }
}

BOOL CReportTipCtrl::PreTranslateMessage(MSG *pMsg)
{
    DWORD   dwTick = 0;
    BOOL    bDoubleClick = FALSE;

    CWnd    *pWnd;
    INT     iHitTest;

    switch (pMsg->message)
    {
    case WM_LBUTTONDOWN:
        dwTick = GetTickCount();
        bDoubleClick = ((dwTick - m_dwLastLButtonDown) <= m_dwDblClickMsecs);
        m_dwLastLButtonDown = dwTick;

    // Notice fall-through
    case WM_RBUTTONDOWN:
    case WM_MBUTTONDOWN:
        {
            POINTS  points = MAKEPOINTS(pMsg->lParam);
            POINT   point;
            point.x = points.x;
            point.y = points.y;

            ClientToScreen(&point);
            pWnd = WindowFromPoint(point);

            if (pWnd == this)
                pWnd = m_pReportCtrl;

            iHitTest = (INT) pWnd->SendMessage(WM_NCHITTEST, 0, MAKELONG(point.x, point.y));

            if (iHitTest == HTCLIENT)
            {
                pWnd->ScreenToClient(&point);
                pMsg->lParam = MAKELONG(point.x, point.y);
            }
            else
            {
                switch (pMsg->message)
                {
                case WM_LBUTTONDOWN:
                    pMsg->message = WM_NCLBUTTONDOWN;
                    break;
                case WM_RBUTTONDOWN:
                    pMsg->message = WM_NCRBUTTONDOWN;
                    break;
                case WM_MBUTTONDOWN:
                    pMsg->message = WM_NCMBUTTONDOWN;
                    break;
                }

                pMsg->wParam = iHitTest;
                pMsg->lParam = MAKELONG(point.x, point.y);
            }

            Hide();

            pWnd->PostMessage(bDoubleClick ? WM_LBUTTONDBLCLK : pMsg->message, pMsg->wParam, pMsg->lParam);
            return TRUE;
        }

    case WM_KEYDOWN:
    case WM_SYSKEYDOWN:
        Hide();
        m_pReportCtrl->PostMessage(pMsg->message, pMsg->wParam, pMsg->lParam);
        return TRUE;
    }

    if (GetFocus() == NULL)
    {
        Hide();
        return TRUE;
    }

    return CWnd::PreTranslateMessage(pMsg);
}

void CReportTipCtrl::OnTimer(UINT nIDEvent)
{
    if (nIDEvent == 1)
    {
        ASSERT(m_bLayeredWindows == TRUE);
        if (m_nAlpha < 255)
        {
            m_nAlpha += REPORTTIPCTRL_FADESTEP;
            if (m_nAlpha >= 255)
            {
                m_nAlpha = 255;
                KillTimer(1);
            }

            g_lpfnSetLayeredWindowAttributes(GetSafeHwnd(), RGB(0xFF, 0, 0xFF), (BYTE) (m_nAlpha), ULW_ALPHA);
        }
        else
        {
            m_nAlpha = 0;
            KillTimer(1);
        }
    }

    CWnd::OnTimer(nIDEvent);
}

// Global utilities
BOOL InitLayeredWindows()
{
    if (g_lpfnUpdateLayeredWindow == NULL)
    {
        HMODULE hUser32 = GetModuleHandle(_T("USER32.DLL"));

        g_lpfnUpdateLayeredWindow = (lpfnUpdateLayeredWindow) GetProcAddress(hUser32, "UpdateLayeredWindow");

        g_lpfnSetLayeredWindowAttributes = (lpfnSetLayeredWindowAttributes) GetProcAddress(hUser32,
                                                                                           "SetLayeredWindowAttributes");

        return g_lpfnUpdateLayeredWindow != NULL ? TRUE : FALSE;
    }

    return TRUE;
}

BOOL MatchString(const CString &strString, const CString &strPattern)
{
    CString strStr = strString;
    CString strPat = strPattern;
    strStr.MakeLower();
    strPat.MakeLower();

    BOOL    bStopSearch = FALSE;

    while (!strString.IsEmpty() && !strPat.IsEmpty() && !bStopSearch)
    {
        if (strPat[0] == _T('*'))
        {
            strPat = strPat.Right(strPat.GetLength() - 1);

            if (strPat.IsEmpty())
                return TRUE;

            CString strBuf = strPat;

            INT     iWild = strBuf.FindOneOf(_T("*?"));

            if (iWild != -1)
                strBuf = strBuf.Left(iWild);

            INT iFound = strStr.Find(strBuf);

            if (iFound == -1)
                return FALSE;

            if (iWild == -1)
                return TRUE;

            strStr = strStr.Right(strStr.GetLength() - iFound);
        }
        else
        {
            bStopSearch = strStr[0] != strPat[0] && strPat[0] != _T('?');

            if (!bStopSearch)
            {
                strStr = strStr.Right(strStr.GetLength() - 1);
                strPat = strPat.Right(strPat.GetLength() - 1);
            }
        }
    }

    bStopSearch = bStopSearch || !strStr.IsEmpty() && strPat.IsEmpty() || strStr.IsEmpty() && !strPat.IsEmpty();

    return !bStopSearch || bStopSearch && !strPat.IsEmpty() && strPat[0] == _T('*');
}

// CReportHeaderCtrl
CReportHeaderCtrl::CReportHeaderCtrl()
{
}

CReportHeaderCtrl::~CReportHeaderCtrl()
{
}

BEGIN_MESSAGE_MAP(CReportHeaderCtrl, CFlatHeaderCtrl)
//{{AFX_MSG_MAP(CReportHeaderCtrl)
    ON_WM_RBUTTONDOWN()
    ON_WM_MOUSEMOVE()
    ON_WM_LBUTTONUP()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

// CReportHeaderCtrl attributes
void CReportHeaderCtrl::SetMinMax(INT iPos, INT iMin, INT iMax)
{
    ASSERT(iPos < m_arrayHdrItemEx.GetSize());
    m_arrayHdrItemEx[iPos].iMinWidth = iMin;
    m_arrayHdrItemEx[iPos].iMaxWidth = iMax;
};

// CReportHeaderCtrl messages
void CReportHeaderCtrl::OnRButtonDown(UINT nFlags, CPoint point)
{
    CReportCtrl *pReportCtrl = DYNAMIC_DOWNCAST(CReportCtrl, GetParent());
    if (pReportCtrl != NULL)
    {
        HDHITTESTINFO   hdhti;
        hdhti.pt = point;

        INT         iItem = ::SendMessage(GetSafeHwnd(), HDM_HITTEST, 0, (LPARAM) (&hdhti));

        NMRVHEADER  nmrvhdr;
        if (pReportCtrl->NotifyHdr(&nmrvhdr, RVN_HEADERRCLICK, iItem))
            return;
    }

    CFlatHeaderCtrl::OnRButtonDown(nFlags, point);
}

void CReportHeaderCtrl::OnMouseMove(UINT nFlags, CPoint point)
{
    CFlatHeaderCtrl::OnMouseMove(nFlags, point);
    if (m_bResizing)
    {
        CReportCtrl *rc = DYNAMIC_DOWNCAST(CReportCtrl, GetParent());
        if (rc->GetCategoryHdrHeight() > 0)
        {
            CFlatHeaderCtrl *ac = rc->GetCategoryHdrCtrl();
            ac->SameWidth(this);
        }
    }
}

void CReportHeaderCtrl::OnLButtonUp(UINT nFlags, CPoint point)
{
    BOOL    bResizing = m_bResizing;
    CFlatHeaderCtrl::OnLButtonUp(nFlags, point);
    if (bResizing)
    {
        CReportCtrl *rc = DYNAMIC_DOWNCAST(CReportCtrl, GetParent());
        if (rc->GetCategoryHdrHeight() > 0)
        {
            CFlatHeaderCtrl *ac = rc->GetCategoryHdrCtrl();
            ac->SameWidth(this);
        }
    }
}

IMPLEMENT_DYNCREATE(CCategoryHdr, CFlatHeaderCtrl)
BEGIN_MESSAGE_MAP(CCategoryHdr, CFlatHeaderCtrl)
//{{AFX_MSG_MAP(CReportHeaderCtrl)
    ON_WM_LBUTTONUP()
    ON_WM_LBUTTONDOWN()
    ON_WM_MOUSEMOVE()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
CCategoryHdr::CCategoryHdr()
{
    m_bDynamic = false;
    m_iState = SW_HIDE;
}

void CCategoryHdr::Show()
{
    CReportCtrl *rc = DYNAMIC_DOWNCAST(CReportCtrl, GetParent());

    int         iState = -1;

    if (m_bDynamic && rc->GetCategorySubItem() < 0)
    {
        if (m_iState == SW_SHOW)
            iState = SW_HIDE;
    }
    else
    {
        if (m_iState == SW_HIDE)
            iState = SW_SHOW;
    }

    if (iState != -1)
    {
        m_iState = iState;
        ShowWindow(m_iState);
        rc->Layout();
        rc->ScrollWindow(SB_HORZ, rc->GetScrollPos32(SB_HORZ));
    }
}

void CCategoryHdr::OnInitialUpdate(void)
{
    CReportCtrl     *rc = DYNAMIC_DOWNCAST(CReportCtrl, GetParent());
    CFlatHeaderCtrl *hc = rc->GetHeaderCtrl();
    SetImageList(hc->GetImageList());

    HDITEM      hdi;
    HDITEMEX    hditemex;
    INT         item = 0;
    hdi.mask = HDI_TEXT | HDI_FORMAT | HDI_WIDTH;

    hdi.fmt = HDF_RIGHT | HDF_STRING;
    hdi.pszText = _T("Arranged By:");
    hdi.cxy = GetItemWidth(&hdi);
    InsertItem(item, &hdi);
    hditemex.nStyle = HDF_EX_NODIVIDER;
    hditemex.iMinWidth = hdi.cxy;
    hditemex.iMaxWidth = hdi.cxy;
    SetItemEx(item++, &hditemex);

    hditemex.iMaxWidth = 0;
    hditemex.iMinWidth = 20;
    hditemex.nStyle = 0;
    hdi.fmt = HDF_LEFT | HDF_STRING;
    hdi.cxy = 20;
    InsertItem(item, &hdi);
    SetItemEx(item++, &hditemex);

    hdi.fmt = HDF_CENTER | HDF_STRING;
    hdi.cxy = 10;
    hdi.pszText = _T("");
    hditemex.iMinWidth = 10;
    InsertItem(item, &hdi);
    SetItemEx(item++, &hditemex);
    NewCategory();
}

void CCategoryHdr::NewCategory(BOOL bSort)
{
    CReportCtrl *rc = DYNAMIC_DOWNCAST(CReportCtrl, GetParent());
    ASSERT(rc);

    BOOL    bSortAccending;
    INT     iSubItem = rc->GetCategorySubItem(&bSortAccending);

    HDITEM  hdiItem;
    if (!bSort)
    {
        hdiItem.mask = HDI_WIDTH;
        VERIFY(GetItem(0, &hdiItem));

        CString str;
        if (iSubItem >= 0)
        {
            rc->GetSubItem(iSubItem, &hdiItem);
            hdiItem.mask &= ~HDI_WIDTH;
            hdiItem.cxy = 0;
        }
        else
        {
            hdiItem.mask = HDI_TEXT | HDI_FORMAT;
            hdiItem.fmt = HDF_RIGHT | HDF_STRING;
            hdiItem.pszText = _T("");
        }

        SetItem(1, &hdiItem);
    }

    hdiItem.mask = HDI_WIDTH;
    GetItem(2, &hdiItem);
    hdiItem.mask = HDI_TEXT | HDI_FORMAT | HDI_WIDTH;
    hdiItem.fmt = (IsAutoSize() ? HDF_RIGHT : HDF_LEFT) | HDF_STRING;

    CString sText = rc->GetHeadingForSortedCategory();
    hdiItem.pszText = sText.GetBuffer(0);
    hdiItem.cxy = max(hdiItem.cxy, GetItemWidth(&hdiItem, TRUE));

    CRect   rWindow;
    GetWindowRect(&rWindow);
    hdiItem.cxy = rWindow.Width() / 2;
    if (iSubItem >= 0)
        SetSortColumn(2, bSortAccending);
    else
    {
        SetSortColumn(-1, FALSE);
        hdiItem.pszText = _T("");
    }

    SetItem(2, &hdiItem);
}

void CCategoryHdr::MatchHeaders()
{
    CReportCtrl *rc = DYNAMIC_DOWNCAST(CReportCtrl, GetParent());
    ASSERT(rc);

    CFlatHeaderCtrl *hc = rc->GetHeaderCtrl();
    ASSERT(hc);
    SameWidth(hc);
    hc->SameWidth(this, rc->GetStyle() & RVS_TREEVIEW ? 0 : -1);
}

void CCategoryHdr::OnMouseMove(UINT nFlags, CPoint point)
{
    CReportCtrl *rc = DYNAMIC_DOWNCAST(CReportCtrl, GetParent());
    if (m_bResizing)
    {
        CFlatHeaderCtrl *hc = rc->GetHeaderCtrl();
        CFlatHeaderCtrl::OnMouseMove(nFlags, point);
        hc->SameWidth(this);
        rc->m_bWidthChanged = TRUE;
        rc->Invalidate();
    }
}

void CCategoryHdr::MeasureMenuItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct)
{
    CReportCtrl             *rc = DYNAMIC_DOWNCAST(CReportCtrl, GetParent());
    CClientDC               dc(this);
    INT                     iSubItem = lpMeasureItemStruct->itemData;
    CReportCtrl::CATITEM    catItem;
    catItem.iSubItem = iSubItem;
    rc->GetCategoryMenuItem(&catItem);
    ASSERT(catItem.mask & CATEGORY_ENABLED);

    CFont   *pFont = dc.SelectObject(GetFont());
    CSize   size;
    size = dc.GetTextExtent(catItem.strText);
    lpMeasureItemStruct->itemWidth = size.cx;
    lpMeasureItemStruct->itemHeight = max(m_sizeImage.cy, size.cy) + 2;
    dc.SelectObject(pFont);
}

void CCategoryHdr::DrawMenuItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
    CDC                     *pDC = CDC::FromHandle(lpDrawItemStruct->hDC);
    CReportCtrl             *rc = DYNAMIC_DOWNCAST(CReportCtrl, GetParent());
    INT                     iSubItem = lpDrawItemStruct->itemData;
    CReportCtrl::CATITEM    catItem;
    catItem.iSubItem = iSubItem;
    rc->GetCategoryMenuItem(&catItem);
    ASSERT(catItem.mask & CATEGORY_ENABLED);

    CRect   rcItem(lpDrawItemStruct->rcItem);
    rcItem.DeflateRect(1, 1);
    if (lpDrawItemStruct->itemState & ODS_SELECTED)
        pDC->FillSolidRect(rcItem, m_ColorMenuHilight);
    if (catItem.mask & CATEGORY_IMAGE)
    {
        IMAGEINFO   info;
        CImageList  *pImageList = GetImageList();
        pImageList->GetImageInfo(catItem.iImage, &info);
        pImageList->DrawIndirect(pDC, catItem.iImage, rcItem.TopLeft(), CRect(info.rcImage).Size(), CPoint(0, 0));
    }

    rcItem.left += m_sizeImage.cx + 2;

    COLORREF    bkColor = pDC->GetBkColor();
    COLORREF    pnColor = pDC->GetTextColor();
    pDC->SetBkColor(lpDrawItemStruct->itemState & ODS_SELECTED ? m_ColorMenuHilight : m_ColorMenu);
    pDC->SetTextColor(m_crMenuText);
    pDC->DrawText(catItem.strText, -1, rcItem, DT_LEFT | DT_END_ELLIPSIS | DT_SINGLELINE | DT_VCENTER);
    pDC->SetTextColor(pnColor);
    pDC->SetBkColor(bkColor);
    rcItem.left -= m_sizeImage.cx + 2;
    if (lpDrawItemStruct->itemState & ODS_SELECTED)
    {
        CBrush  brushHighLight(RGB(0, 0, 0));
        rcItem.InflateRect(1, 1);
        pDC->FrameRect(rcItem, &brushHighLight);
    }
}

void CCategoryHdr::OnLButtonDown(UINT nFlags, CPoint point)
{
    CReportCtrl *rc = DYNAMIC_DOWNCAST(CReportCtrl, GetParent());
    CPoint      p(GetSystemMetrics(SM_CXDRAG), 0);
    CRect       rect;
    CRect       rect1;
    GetItemRect(0, &rect);
    GetItemRect(1, &rect1);
    GetDC()->DPtoLP(&p);
    rect.right = rect1.right - p.x - 1;
    if (PtInRect(&rect, point))
    {
        CCategoryMenu   menu(this, m_sizeImage.cx);
        menu.CreatePopupMenu();

        INT iCategorySubItem = rc->GetCategorySubItem();
        for (INT iSubItem = 0; iSubItem < rc->GetSubItemCount(); iSubItem++)
        {
            if (iSubItem == iCategorySubItem || rc->GetColumnFromSubItem(iSubItem) >= 0)
            {
                CReportCtrl::CATITEM    catItem;
                catItem.iSubItem = iSubItem;
                rc->GetCategoryMenuItem(&catItem);
                if (catItem.mask & CATEGORY_ENABLED)
                    menu.AppendMenu(MF_OWNERDRAW, (UINT_PTR) iSubItem, (LPCTSTR) iSubItem);
            }
        }

        menu.AppendMenu(MF_SEPARATOR);
        menu.AppendMenu(MF_OWNERDRAW, (UINT_PTR) - 1, (LPCTSTR) - 1);
        ClientToScreen(&point);
        menu.TrackPopupMenu(TPM_CENTERALIGN, point.x, point.y, this);
    }
    else
    {
        if (HHT_ONDIVIDER & m_hdhtiHotItem.flags || rc->GetCategorySubItem() >= 0)
            CFlatHeaderCtrl::OnLButtonDown(nFlags, point);
    }
}

void CCategoryHdr::OnLButtonUp(UINT nFlags, CPoint point)
{
    CReportCtrl *rc = DYNAMIC_DOWNCAST(CReportCtrl, GetParent());
    BOOL        bOnDivider = HHT_ONDIVIDER & m_hdhtiHotItem.flags;
    CFlatHeaderCtrl::OnLButtonUp(nFlags, point);

    MatchHeaders();
    rc->Layout();

    CRect   rect;
    GetItemRect(2, &rect);
    if (!bOnDivider && PtInRect(&rect, point) && rc->GetCategorySubItem() >= 0)
    {
        rc->m_bCategoryAscending = !rc->m_bCategoryAscending;
        NewCategory(TRUE);
        rc->ResortItems();
    }
}

BOOL CCategoryHdr::OnCommand(WPARAM wParam, LPARAM lParam)
{
    UNREFERENCED_PARAMETER(lParam);

    CReportCtrl *rc = DYNAMIC_DOWNCAST(CReportCtrl, GetParent());
    rc->SetCategorySubItem(wParam);
    return TRUE;
}

void CCategoryHdr::SetDynamic(BOOL bDynamic)
{
    m_bDynamic = bDynamic;
    Show();
    NewCategory();
}

BOOL CCategoryHdr::IsDynamic()
{
    return m_bDynamic;
}

IMPLEMENT_DYNCREATE(CCategoryMenu, CMenu)
CCategoryMenu::CCategoryMenu()
{
    ASSERT(0);
}

CCategoryMenu::CCategoryMenu(CCategoryHdr *pCCategoryHdr, int iHilite)
{
    VERIFY(m_rgn.CreateRectRgn(0, 0, 0, 0));
    m_iHilite = iHilite;
    m_pCCategoryHdr = pCCategoryHdr;
    m_ColorMenu = ::GetSysColor(COLOR_MENU);
    if (GetSysColorBrush(COLOR_MENUHILIGHT) != NULL)
        m_ColorMenuHilight = ::GetSysColor(COLOR_MENUHILIGHT);
    else
        m_ColorMenuHilight = ::GetSysColor(COLOR_3DFACE);
}

void CCategoryMenu::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
    CDC     *pDC = CDC::FromHandle(lpDrawItemStruct->hDC);
    CRect   rcItem(lpDrawItemStruct->rcItem);
    pDC->FillSolidRect(rcItem, m_ColorMenu);
    rcItem.right = m_iHilite + 1;

    CRect   rcHilite(rcItem);
    rcHilite.right = m_iHilite;
    pDC->FillSolidRect(rcItem, m_ColorMenuHilight);
    m_pCCategoryHdr->DrawMenuItem(lpDrawItemStruct);

    CRgn    rgn;
    CRect   box;
    rgn.CreateRectRgnIndirect(&rcItem);
    m_rgn.CombineRgn(&m_rgn, &rgn, RGN_OR);
    m_rgn.GetRgnBox(&box);
    rgn.SetRectRgn(&box);
    rgn.CombineRgn(&rgn, &m_rgn, RGN_DIFF);

    CBrush  brushFace(m_ColorMenuHilight);
    CBrush  *pBrush = pDC->SelectObject(&brushFace);
    pDC->PaintRgn(&rgn);
    pDC->SelectObject(&pBrush);
}

void CCategoryMenu::MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct)
{
    ASSERT(m_pCCategoryHdr);
    m_pCCategoryHdr->MeasureMenuItem(lpMeasureItemStruct);
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Canada Canada
I have been programming since 1960 in many places, in too many environments and languages. Retired and just doing this for a hobby.

Comments and Discussions