You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1713 lines
44 KiB
1713 lines
44 KiB
/*++
|
|
|
|
Copyright (C) 1992-1999 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
legend.cpp
|
|
|
|
Abstract:
|
|
|
|
This file contains code creating the legend window, which is
|
|
a child of the graph windows. The legend window displays a
|
|
legend line for each line in the associated graph. It also
|
|
includes an area called the label, which are headers for those
|
|
lines.
|
|
|
|
--*/
|
|
|
|
//==========================================================================//
|
|
// Includes //
|
|
//==========================================================================//
|
|
|
|
|
|
#include "polyline.h"
|
|
#include <uxtheme.h>
|
|
#include <strsafe.h>
|
|
#include "grphitem.h"
|
|
#include "legend.h"
|
|
#include "winhelpr.h"
|
|
#include "owndraw.h"
|
|
#include "unihelpr.h"
|
|
|
|
#define eScaleValueSpace L">9999999999.0"
|
|
#define szGraphLegendClass L"PerfLegend"
|
|
#define szGraphLegendClassA "PerfLegend"
|
|
|
|
|
|
LRESULT APIENTRY HdrWndProc (HWND, UINT, WPARAM, LPARAM);
|
|
|
|
//==========================================================================//
|
|
// Constants //
|
|
//==========================================================================//
|
|
enum Orientation
|
|
{
|
|
LEFTORIENTATION = TA_LEFT,
|
|
CENTERORIENTATION = TA_CENTER,
|
|
RIGHTORIENTATION = TA_RIGHT
|
|
};
|
|
|
|
enum ColumnType
|
|
{
|
|
eLegendColorCol = 0,
|
|
eLegendScaleCol = 1,
|
|
eLegendCounterCol = 2,
|
|
eLegendInstanceCol = 3,
|
|
eLegendParentCol = 4,
|
|
eLegendObjectCol = 5,
|
|
eLegendSystemCol = 6,
|
|
eLegendExtraCol = 7 // If control wider than combined columns
|
|
};
|
|
|
|
enum SortType
|
|
{
|
|
NO_SORT,
|
|
INCREASING_SORT,
|
|
DECREASING_SORT
|
|
};
|
|
|
|
enum WindowType
|
|
{
|
|
LIST_WND = 1000,
|
|
HDR_WND
|
|
};
|
|
|
|
#define NULL_WIDTH -1
|
|
|
|
#define dwGraphLegendClassStyle (CS_HREDRAW | CS_VREDRAW)
|
|
#define iGraphLegendClassExtra (0)
|
|
#define iGraphLegendWindowExtra (sizeof (PLEGEND))
|
|
#define dwGraphLegendWindowStyle (WS_CHILD | WS_VISIBLE)
|
|
|
|
#define ThreeDPad 2
|
|
#define iMaxVisibleItems 8
|
|
|
|
#define dwGraphLegendItemsWindowClass L"ListBox"
|
|
#define dwGraphLegendItemsWindowStyle \
|
|
(LBS_NOTIFY | LBS_NOINTEGRALHEIGHT | LBS_OWNERDRAWFIXED | \
|
|
WS_VISIBLE | WS_CHILD | WS_VSCROLL)
|
|
|
|
#define WM_DELAYED_SELECT WM_USER + 100
|
|
|
|
#define LegendBottomMargin() (ThreeDPad)
|
|
#define LegendLeftMargin() (ThreeDPad)
|
|
#define LegendHorzMargin() (10)
|
|
|
|
typedef struct {
|
|
PCGraphItem pGItem;
|
|
LPCWSTR pszKey;
|
|
} SORT_ITEM, *PSORT_ITEM;
|
|
|
|
//==========================================================================//
|
|
// Local Variables //
|
|
//==========================================================================//
|
|
static INT xBorderWidth = GetSystemMetrics(SM_CXBORDER);
|
|
static INT yBorderHeight = GetSystemMetrics(SM_CYBORDER);
|
|
|
|
#define MAX_COL_HEADER_LEN 32
|
|
#define MAX_COL_CHARS 64
|
|
|
|
static WCHAR aszColHeader[iLegendNumCols][MAX_COL_HEADER_LEN];
|
|
|
|
//
|
|
// Sorting function
|
|
//
|
|
INT __cdecl
|
|
LegendSortFunc(
|
|
const void *elem1,
|
|
const void *elem2
|
|
)
|
|
{
|
|
return lstrcmp(((PSORT_ITEM)elem1)->pszKey, ((PSORT_ITEM)elem2)->pszKey);
|
|
}
|
|
|
|
|
|
//
|
|
// Constructor
|
|
//
|
|
CLegend::CLegend ( void )
|
|
: m_pCtrl ( NULL ),
|
|
m_hWnd ( NULL ),
|
|
m_hWndHeader ( NULL ),
|
|
m_DefaultWndProc ( NULL ),
|
|
m_hWndItems ( NULL ),
|
|
m_hFontItems ( NULL ),
|
|
m_hFontLabels ( NULL ),
|
|
m_iNumItemsVisible ( 0 ),
|
|
m_pCurrentItem ( NULL ),
|
|
m_iSortDir ( NO_SORT ),
|
|
m_parrColWidthFraction( NULL )
|
|
{
|
|
|
|
m_fMetafile = FALSE;
|
|
|
|
m_aCols[0].xWidth = -1;
|
|
}
|
|
|
|
//
|
|
// Destructor
|
|
//
|
|
CLegend::~CLegend (void )
|
|
{
|
|
// Restore default window proc
|
|
// so we don't get called post-mortum
|
|
if (m_hWndHeader != NULL) {
|
|
SetWindowLongPtr(m_hWndHeader, GWLP_WNDPROC, (INT_PTR)m_DefaultWndProc);
|
|
}
|
|
|
|
if (m_hWnd != NULL) {
|
|
DestroyWindow(m_hWnd);
|
|
}
|
|
|
|
if ( NULL != m_parrColWidthFraction ) {
|
|
delete m_parrColWidthFraction;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Initialization
|
|
//
|
|
BOOL CLegend::Init ( PSYSMONCTRL pCtrl, HWND hWndParent )
|
|
{
|
|
INT iCol ;
|
|
HD_ITEM hdi;
|
|
HDC hDC;
|
|
BOOL fComputeWidths;
|
|
WNDCLASS wc ;
|
|
LONG lExStyles;
|
|
// Save pointer to parent control
|
|
m_pCtrl = pCtrl;
|
|
|
|
BEGIN_CRITICAL_SECTION
|
|
|
|
// Register window class once
|
|
if (pstrRegisteredClasses[LEGEND_WNDCLASS] == NULL) {
|
|
|
|
wc.style = dwGraphLegendClassStyle ;
|
|
wc.lpfnWndProc = GraphLegendWndProc ;
|
|
wc.hInstance = g_hInstance ;
|
|
wc.cbClsExtra = iGraphLegendClassExtra ;
|
|
wc.cbWndExtra = iGraphLegendWindowExtra ;
|
|
wc.hIcon = NULL ;
|
|
wc.hCursor = LoadCursor (NULL, IDC_ARROW) ;
|
|
wc.hbrBackground = NULL ;
|
|
wc.lpszMenuName = NULL ;
|
|
wc.lpszClassName = szGraphLegendClass ;
|
|
|
|
if (RegisterClass (&wc)) {
|
|
pstrRegisteredClasses[LEGEND_WNDCLASS] = szGraphLegendClass;
|
|
}
|
|
|
|
// Ensure controls are initialized
|
|
InitCommonControls();
|
|
|
|
// Load the column header strings just once also
|
|
for (iCol=0; iCol<iLegendNumCols; iCol++) {
|
|
|
|
LoadString(g_hInstance, (IDS_LEGEND_BASE + iCol), aszColHeader[iCol], MAX_COL_HEADER_LEN);
|
|
}
|
|
}
|
|
|
|
END_CRITICAL_SECTION
|
|
|
|
if (pstrRegisteredClasses[LEGEND_WNDCLASS] == NULL)
|
|
return FALSE;
|
|
|
|
// Create our window
|
|
m_hWnd = CreateWindow (szGraphLegendClass, // class
|
|
NULL, // caption
|
|
dwGraphLegendWindowStyle, // window style
|
|
0, 0, // position
|
|
0, 0, // size
|
|
hWndParent, // parent window
|
|
NULL, // menu
|
|
g_hInstance, // program instance
|
|
(LPVOID) this ); // user-supplied data
|
|
|
|
if (m_hWnd == NULL)
|
|
return FALSE;
|
|
|
|
// Turn off layout mirroring if it is enabled
|
|
lExStyles = GetWindowLong(m_hWnd, GWL_EXSTYLE);
|
|
|
|
if ( 0 != ( lExStyles & WS_EX_LAYOUTRTL ) ) {
|
|
lExStyles &= ~WS_EX_LAYOUTRTL;
|
|
SetWindowLong(m_hWnd, GWL_EXSTYLE, lExStyles);
|
|
}
|
|
|
|
// Turn off XP window theme for the owner drawn list header and cells.
|
|
SetWindowTheme (m_hWnd, L" ", L" ");
|
|
|
|
m_hWndHeader = CreateWindow(WC_HEADER,
|
|
NULL,
|
|
WS_CHILD | WS_BORDER | HDS_BUTTONS | HDS_HORZ,
|
|
0, 0, 0, 0,
|
|
m_hWnd,
|
|
(HMENU)HDR_WND,
|
|
g_hInstance,
|
|
(LPVOID) NULL);
|
|
|
|
if (m_hWndHeader == NULL)
|
|
return FALSE;
|
|
|
|
// Turn off XP window theme for the owner drawn list header and cells.
|
|
SetWindowTheme (m_hWndHeader, L" ", L" ");
|
|
|
|
// Insert our own window procedure for special processing
|
|
m_DefaultWndProc = (WNDPROC)SetWindowLongPtr(m_hWndHeader, GWLP_WNDPROC, (INT_PTR)HdrWndProc);
|
|
|
|
// Create Legend Items Listbox
|
|
m_hWndItems = CreateWindow (L"ListBox", // window class
|
|
NULL, // window caption
|
|
dwGraphLegendItemsWindowStyle, // window style
|
|
0, 0, 0, 0, // window size and pos
|
|
m_hWnd, // parent window
|
|
(HMENU)LIST_WND, // child ID
|
|
g_hInstance, // program instance
|
|
(LPVOID) TRUE) ; // user-supplied data
|
|
|
|
if (m_hWndItems == NULL)
|
|
return FALSE;
|
|
|
|
// Turn off XP window theme for the owner drawn list header and cells.
|
|
SetWindowTheme (m_hWndItems, L" ", L" ");
|
|
|
|
// Set up DC for text measurements
|
|
hDC = GetDC (m_hWndHeader);
|
|
if ( NULL != hDC ) {
|
|
// Compute initial sizes based on font
|
|
ChangeFont(hDC);
|
|
}
|
|
|
|
// Set column widths and header labels
|
|
m_aCols[0].xPos = 0;
|
|
|
|
fComputeWidths = (m_aCols[0].xWidth == -1);
|
|
|
|
for (iCol = 0; iCol < iLegendNumCols; iCol++)
|
|
{
|
|
// If width not loaded, calculate one based on label
|
|
if ( fComputeWidths && NULL != hDC ) {
|
|
m_aCols[iCol].xWidth = TextWidth (hDC, aszColHeader[iCol]) + 2 * LegendHorzMargin () ;
|
|
}
|
|
|
|
m_aCols[iCol].iOrientation = LEFTORIENTATION;
|
|
|
|
if (iCol > 0) {
|
|
m_aCols[iCol].xPos = m_aCols[iCol-1].xPos + m_aCols[iCol-1].xWidth;
|
|
}
|
|
|
|
hdi.mask = HDI_FORMAT | HDI_WIDTH;
|
|
hdi.pszText = NULL;
|
|
hdi.cxy = m_aCols[iCol].xWidth;
|
|
hdi.fmt = HDF_OWNERDRAW | HDF_LEFT;
|
|
|
|
Header_InsertItem(m_hWndHeader, iCol, &hdi);
|
|
}
|
|
|
|
if ( NULL != hDC ) {
|
|
ReleaseDC ( m_hWndHeader, hDC );
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
HRESULT CLegend::LoadFromStream(LPSTREAM pIStream)
|
|
{
|
|
HRESULT hr;
|
|
ULONG bc;
|
|
INT iCol;
|
|
LEGEND_DATA LegendData;
|
|
HD_ITEM hdi;
|
|
|
|
|
|
hr = pIStream->Read(&LegendData, sizeof(LegendData), &bc);
|
|
if (FAILED(hr))
|
|
return hr;
|
|
|
|
if (bc != sizeof(LegendData))
|
|
return E_FAIL;
|
|
|
|
hdi.mask = HDI_WIDTH;
|
|
|
|
for (iCol=0; iCol<iLegendNumCols; iCol++) {
|
|
m_aCols[iCol].xWidth = LegendData.xColWidth[iCol];
|
|
|
|
if (iCol > 0) {
|
|
m_aCols[iCol].xPos = m_aCols[iCol-1].xPos + m_aCols[iCol-1].xWidth;
|
|
}
|
|
|
|
hdi.cxy = m_aCols[iCol].xWidth;
|
|
Header_SetItem(m_hWndHeader, iCol, &hdi);
|
|
}
|
|
|
|
m_iSortCol = LegendData.iSortCol;
|
|
m_iSortDir = LegendData.iSortDir;
|
|
|
|
return NOERROR;
|
|
}
|
|
|
|
|
|
HRESULT
|
|
CLegend::SaveToStream(LPSTREAM pIStream)
|
|
{
|
|
HRESULT hr;
|
|
INT iCol;
|
|
LEGEND_DATA LegendData;
|
|
|
|
for (iCol=0; iCol<iLegendNumCols; iCol++) {
|
|
LegendData.xColWidth[iCol] = m_aCols[iCol].xWidth;
|
|
}
|
|
|
|
LegendData.iSortCol = m_iSortCol;
|
|
LegendData.iSortDir = m_iSortDir;
|
|
|
|
hr = pIStream->Write(&LegendData, sizeof(LegendData), NULL);
|
|
if (FAILED(hr))
|
|
return hr;
|
|
|
|
return NOERROR;
|
|
}
|
|
|
|
HRESULT
|
|
CLegend::LoadFromPropertyBag (
|
|
IPropertyBag* pIPropBag,
|
|
IErrorLog* pIErrorLog )
|
|
{
|
|
HRESULT hr = S_OK;
|
|
LPWSTR pszData = NULL;
|
|
INT iBufSizeCurrent = 0;
|
|
INT iBufSize;
|
|
INT iDataIndex;
|
|
DOUBLE dValue = 0;
|
|
WCHAR* pNextData;
|
|
WCHAR* pDataEnd;
|
|
|
|
|
|
hr = IntegerFromPropertyBag ( pIPropBag, pIErrorLog, L"LegendSortDirection", m_iSortDir );
|
|
hr = IntegerFromPropertyBag ( pIPropBag, pIErrorLog, L"LegendSortColumn", m_iSortCol );
|
|
|
|
iBufSize = iBufSizeCurrent;
|
|
|
|
hr = StringFromPropertyBag (
|
|
pIPropBag,
|
|
pIErrorLog,
|
|
L"LegendColumnWidths",
|
|
pszData,
|
|
iBufSize );
|
|
|
|
if ( SUCCEEDED(hr) &&
|
|
iBufSize > iBufSizeCurrent ) {
|
|
// Width data exists.
|
|
if ( NULL != pszData ) {
|
|
delete [] pszData;
|
|
pszData = NULL;
|
|
}
|
|
|
|
pszData = new WCHAR[ iBufSize ];
|
|
|
|
if ( NULL == pszData ) {
|
|
hr = E_OUTOFMEMORY;
|
|
} else {
|
|
*pszData = L'\0';
|
|
|
|
iBufSizeCurrent = iBufSize;
|
|
|
|
hr = StringFromPropertyBag (
|
|
pIPropBag,
|
|
pIErrorLog,
|
|
L"LegendColumnWidths",
|
|
pszData,
|
|
iBufSize );
|
|
}
|
|
|
|
if ( SUCCEEDED(hr) ) {
|
|
m_parrColWidthFraction = new DOUBLE[iLegendNumCols];
|
|
|
|
if ( NULL != m_parrColWidthFraction ) {
|
|
|
|
pNextData = pszData;
|
|
pDataEnd = pszData + lstrlen(pszData);
|
|
|
|
for ( iDataIndex = 0; SUCCEEDED(hr) && iDataIndex < iLegendNumCols; iDataIndex++ ) {
|
|
if ( pNextData < pDataEnd ) {
|
|
hr = GetNextValue ( pNextData, dValue );
|
|
if ( SUCCEEDED(hr) ) {
|
|
m_parrColWidthFraction[iDataIndex] = dValue;
|
|
}
|
|
} else {
|
|
hr = E_FAIL;
|
|
}
|
|
}
|
|
if ( FAILED(hr) ) {
|
|
//
|
|
// If failed to get at least one column value, then use default values.
|
|
//
|
|
delete [] m_parrColWidthFraction;
|
|
m_parrColWidthFraction = NULL;
|
|
}
|
|
} else {
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
}
|
|
}
|
|
if (pszData != NULL) {
|
|
delete [] pszData;
|
|
}
|
|
|
|
return NOERROR;
|
|
}
|
|
|
|
HRESULT
|
|
CLegend::SaveToPropertyBag (
|
|
IPropertyBag* pIPropBag,
|
|
BOOL /* fClearDirty */,
|
|
BOOL /* fSaveAllProps */ )
|
|
{
|
|
HRESULT hr = NOERROR;
|
|
WCHAR szData[MAX_COL_CHARS*iLegendNumCols];
|
|
WCHAR* pszTemp;
|
|
INT iIndex;
|
|
VARIANT vValue;
|
|
INT xWidth;
|
|
|
|
xWidth = m_Rect.right - m_Rect.left - 2 * LegendLeftMargin();
|
|
|
|
// Continue even if error, using defaults in those cases.
|
|
szData[0] = L'\0';
|
|
|
|
for ( iIndex = 0; SUCCEEDED(hr) && iIndex < iLegendNumCols; iIndex++ ) {
|
|
DOUBLE dFractionWidth;
|
|
|
|
dFractionWidth = ( (DOUBLE)m_aCols[iIndex].xWidth ) / xWidth;
|
|
|
|
if ( iIndex > 0 ) {
|
|
StringCchCat (szData, MAX_COL_CHARS * iLegendNumCols, L"\t" );
|
|
}
|
|
|
|
VariantInit( &vValue );
|
|
vValue.vt = VT_R8;
|
|
vValue.dblVal = dFractionWidth;
|
|
hr = VariantChangeTypeEx( &vValue, &vValue, LCID_SCRIPT, VARIANT_NOUSEROVERRIDE, VT_BSTR );
|
|
|
|
pszTemp = vValue.bstrVal;
|
|
|
|
StringCchCat(szData, MAX_COL_CHARS * iLegendNumCols, pszTemp );
|
|
|
|
VariantClear( &vValue );
|
|
}
|
|
|
|
if ( SUCCEEDED( hr ) ){
|
|
hr = StringToPropertyBag ( pIPropBag, L"LegendColumnWidths", szData );
|
|
}
|
|
|
|
hr = IntegerToPropertyBag ( pIPropBag, L"LegendSortDirection", m_iSortCol );
|
|
|
|
hr = IntegerToPropertyBag ( pIPropBag, L"LegendSortColumn", m_iSortDir );
|
|
|
|
return NOERROR;
|
|
}
|
|
|
|
|
|
//
|
|
// Get list index of item
|
|
//
|
|
INT CLegend::GetItemIndex(PCGraphItem pGItem)
|
|
{
|
|
INT nItems;
|
|
INT i;
|
|
|
|
nItems = LBNumItems(m_hWndItems);
|
|
|
|
for (i=0; i<nItems; i++)
|
|
{
|
|
if (pGItem == (PCGraphItem)LBData(m_hWndItems, i))
|
|
return i;
|
|
}
|
|
|
|
return LB_ERR;
|
|
}
|
|
|
|
//
|
|
// Select list item
|
|
//
|
|
BOOL CLegend::SelectItem(PCGraphItem pGItem)
|
|
{
|
|
INT iIndex;
|
|
|
|
// Don't reselect the current selection
|
|
// This is our parent echoing the change
|
|
if (pGItem == m_pCurrentItem)
|
|
return TRUE;
|
|
|
|
iIndex = GetItemIndex(pGItem);
|
|
|
|
if (iIndex == LB_ERR)
|
|
return FALSE;
|
|
|
|
LBSetSelection (m_hWndItems, iIndex) ;
|
|
m_pCurrentItem = pGItem;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
//
|
|
// Add new item to legend
|
|
//
|
|
BOOL CLegend::AddItem (PCGraphItem pItem)
|
|
{
|
|
INT iHigh,iLow,iMid = 0;
|
|
INT iComp;
|
|
LPCWSTR pszItemKey = NULL;
|
|
LPCWSTR pszItemKey2 = NULL;
|
|
PCGraphItem pListItem;
|
|
BOOL bSorted = TRUE;
|
|
|
|
if (m_iSortDir == NO_SORT) {
|
|
bSorted = FALSE;
|
|
}
|
|
else {
|
|
|
|
//
|
|
// If we need to sort, we must sort based upon a sortable
|
|
// column. So check to make sure we have a sortable column.
|
|
// If we don't have a sortable column, just add the item
|
|
//
|
|
pszItemKey = GetSortKey(pItem);
|
|
|
|
if (pszItemKey == NULL) {
|
|
bSorted = FALSE;
|
|
}
|
|
}
|
|
|
|
if (bSorted == TRUE) {
|
|
//
|
|
// Binary search search for insertion point
|
|
//
|
|
iLow = 0;
|
|
iHigh = LBNumItems(m_hWndItems);
|
|
iMid = (iHigh + iLow) / 2;
|
|
|
|
while (iLow < iHigh) {
|
|
|
|
pListItem = (PCGraphItem)LBData(m_hWndItems, iMid);
|
|
|
|
pszItemKey2 = GetSortKey(pListItem);
|
|
//
|
|
// pszItemKey2 should not be NULL if we come this point.
|
|
// But if somehow it is NULL, then add the item
|
|
//
|
|
if (pszItemKey2 == NULL) {
|
|
bSorted = FALSE;
|
|
break;
|
|
}
|
|
|
|
iComp = lstrcmp(pszItemKey, pszItemKey2);
|
|
if (m_iSortDir == DECREASING_SORT) {
|
|
iComp = -iComp;
|
|
}
|
|
|
|
if (iComp > 0) {
|
|
iLow = iMid + 1;
|
|
}
|
|
else {
|
|
iHigh = iMid;
|
|
}
|
|
|
|
iMid = (iHigh + iLow) / 2;
|
|
}
|
|
}
|
|
|
|
if (bSorted == TRUE) {
|
|
LBInsert (m_hWndItems, iMid, pItem) ;
|
|
}
|
|
else {
|
|
LBAdd(m_hWndItems, pItem);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
//
|
|
// Delete item from legend
|
|
//
|
|
void CLegend::DeleteItem (PCGraphItem pItem)
|
|
{
|
|
INT iIndex ;
|
|
|
|
// Calling procedure checks for NULL pItem
|
|
assert ( NULL != pItem );
|
|
iIndex = GetItemIndex (pItem) ;
|
|
|
|
if (iIndex != LB_ERR) {
|
|
|
|
LBDelete (m_hWndItems, iIndex) ;
|
|
|
|
// If deleted the current item
|
|
// select the next one (or prev if no next)
|
|
if (pItem == m_pCurrentItem) {
|
|
|
|
if (iIndex == LBNumItems(m_hWndItems))
|
|
iIndex--;
|
|
|
|
if (iIndex >= 0)
|
|
m_pCurrentItem = (PCGraphItem)LBData(m_hWndItems, iIndex);
|
|
else
|
|
m_pCurrentItem = NULL;
|
|
|
|
LBSetSelection (m_hWndItems, iIndex) ;
|
|
m_pCtrl->SelectCounter(m_pCurrentItem);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// Clear all items from legend
|
|
//
|
|
void CLegend::Clear ( void )
|
|
{
|
|
LBReset (m_hWndItems) ;
|
|
m_pCurrentItem = NULL ;
|
|
}
|
|
|
|
//
|
|
// Get currently selected item
|
|
//
|
|
PCGraphItem CLegend::CurrentItem ( void )
|
|
{
|
|
return (m_pCurrentItem) ;
|
|
}
|
|
|
|
//
|
|
// Get legend window
|
|
//
|
|
HWND CLegend::Window ( void )
|
|
{
|
|
return m_hWnd;
|
|
}
|
|
|
|
//
|
|
// Draw the header for a column
|
|
//
|
|
|
|
void
|
|
CLegend::DrawColHeader(
|
|
INT iCol,
|
|
HDC hDC,
|
|
HDC hAttribDC,
|
|
RECT& rRect,
|
|
BOOL bItemState )
|
|
{
|
|
HFONT hFontPrev;
|
|
RECT rc = rRect;
|
|
|
|
if ( m_fMetafile ) {
|
|
if ( eAppear3D == m_pCtrl->Appearance() ) {
|
|
DrawEdge(hDC, &rc, EDGE_RAISED, BF_RECT);
|
|
} else {
|
|
Rectangle (hDC, rc.left, rc.top,
|
|
rc.right, rc.bottom );
|
|
}
|
|
}
|
|
|
|
|
|
if ( iCol < iLegendNumCols ) {
|
|
|
|
rc.top += yBorderHeight + 1; // Extra pixel so that tops of letters don't get clipped.
|
|
rc.bottom -= yBorderHeight;
|
|
rc.left += 6 * xBorderWidth;
|
|
rc.right -= 6 * xBorderWidth;
|
|
|
|
if ( bItemState )
|
|
OffsetRect(&rc, xBorderWidth, yBorderHeight);
|
|
|
|
SetTextColor (hDC, m_pCtrl->clrFgnd()) ;
|
|
SetBkColor(hDC, m_pCtrl->clrBackCtl()) ;
|
|
SetTextAlign (hDC, m_aCols[iCol].iOrientation) ;
|
|
hFontPrev = (HFONT)SelectFont(hDC, m_pCtrl->Font());
|
|
|
|
FitTextOut (
|
|
hDC,
|
|
hAttribDC,
|
|
0,
|
|
&rc,
|
|
aszColHeader[iCol],
|
|
lstrlen(aszColHeader[iCol]),
|
|
m_aCols[iCol].iOrientation, FALSE );
|
|
|
|
SelectFont (hDC, hFontPrev);
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// Draw the headers for all columns
|
|
//
|
|
void
|
|
CLegend::DrawHeader(
|
|
HDC hDC,
|
|
HDC hAttribDC,
|
|
RECT& /* rUpdateRect */ )
|
|
{
|
|
INT iCol;
|
|
RECT rectCol;
|
|
INT iSumColWidths;
|
|
|
|
iSumColWidths = 0;
|
|
|
|
for ( iCol = 0; iCol < iLegendNumCols; iCol++ ) {
|
|
INT iColWidth;
|
|
|
|
Header_GetItemRect( m_hWndHeader, iCol, &rectCol );
|
|
|
|
iColWidth = rectCol.right - rectCol.left;
|
|
|
|
if ( 0 < iColWidth ) {
|
|
|
|
iSumColWidths += iColWidth;
|
|
|
|
OffsetRect ( &rectCol, m_Rect.left, m_Rect.top );
|
|
|
|
// Don't draw past the legend bounds.
|
|
if ( rectCol.bottom > m_Rect.bottom ) {
|
|
break;
|
|
} else if ( rectCol.left >= m_Rect.right ) {
|
|
break;
|
|
} else if ( m_Rect.right < rectCol.right ) {
|
|
rectCol.right = m_Rect.right;
|
|
}
|
|
|
|
DrawColHeader( iCol, hDC, hAttribDC, rectCol, FALSE );
|
|
}
|
|
}
|
|
|
|
// Handle extra width past last column
|
|
|
|
if ( iSumColWidths < ( m_Rect.right - m_Rect.left ) ) {
|
|
rectCol.left = m_Rect.left + iSumColWidths;
|
|
rectCol.right = m_Rect.right;
|
|
|
|
DrawColHeader( iLegendNumCols, hDC, hAttribDC, rectCol, FALSE );
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// Draw the color column for a legend item
|
|
//
|
|
void
|
|
CLegend::DrawColorCol (
|
|
PCGraphItem pItem,
|
|
INT iCol,
|
|
HDC hDC,
|
|
HDC hAttribDC,
|
|
INT yPos)
|
|
{
|
|
RECT rect ;
|
|
HRGN hRgnOld;
|
|
INT iRgn;
|
|
INT yMiddle;
|
|
|
|
if ( 0 < m_aCols[iCol].xWidth ) {
|
|
|
|
rect.left = m_aCols[iCol].xPos + LegendLeftMargin () ;
|
|
rect.top = yPos + 1 ;
|
|
rect.right = rect.left + m_aCols[iCol].xWidth - 2 * LegendLeftMargin () ;
|
|
rect.bottom = yPos + m_yItemHeight - 1 ;
|
|
|
|
if( m_fMetafile ) {
|
|
OffsetRect ( &rect, m_Rect.left, m_Rect.top );
|
|
|
|
// Handle clipping.
|
|
if ( rect.bottom > m_Rect.bottom ) {
|
|
return;
|
|
} else if ( rect.left >= m_Rect.right ) {
|
|
return;
|
|
} else if ( m_Rect.right < rect.right ) {
|
|
rect.right = m_Rect.right;
|
|
}
|
|
}
|
|
|
|
yMiddle = (rect.top + rect.bottom) / 2;
|
|
|
|
if ( m_fMetafile ) {
|
|
Line (hDC, pItem->Pen(),
|
|
rect.left + 1, yMiddle, rect.right - 1, yMiddle) ;
|
|
} else {
|
|
if ( NULL != hAttribDC && NULL != hDC ) {
|
|
hRgnOld = CreateRectRgn(0,0,0,0);
|
|
if ( NULL != hRgnOld ) {
|
|
iRgn = GetClipRgn(hAttribDC, hRgnOld);
|
|
if ( -1 != iRgn ) {
|
|
if ( ERROR != IntersectClipRect (hDC, rect.left + 1, rect.top + 1,
|
|
rect.right - 1, rect.bottom - 1) ) {
|
|
Line (hDC, pItem->Pen(),
|
|
rect.left + 1, yMiddle, rect.right - 1, yMiddle) ;
|
|
}
|
|
// Old clip region is for the ListBox item window, so can't
|
|
// use this for printing.
|
|
if ( 1 == iRgn ) {
|
|
SelectClipRgn(hDC, hRgnOld);
|
|
}
|
|
}
|
|
DeleteObject(hRgnOld);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
CLegend::DrawCol (
|
|
INT iCol,
|
|
HDC hDC,
|
|
HDC hAttribDC,
|
|
INT yPos,
|
|
LPCWSTR lpszValue)
|
|
/*
|
|
Effect: Draw the value lpszValue for the column iCol on hDC.
|
|
|
|
Assert: The foreground and background text colors of hDC are
|
|
properly set.
|
|
*/
|
|
{
|
|
static WCHAR szMissing[4] = L"---";
|
|
|
|
RECT rect ;
|
|
INT xPos ;
|
|
BOOL bNeedEllipses = FALSE;
|
|
INT cChars = 0;
|
|
WCHAR achBuf[MAX_COL_CHARS + sizeof(ELLIPSES)/sizeof(WCHAR) + 1];
|
|
|
|
if ( 0 < m_aCols[iCol].xWidth ) {
|
|
rect.left = m_aCols[iCol].xPos + LegendLeftMargin() ;
|
|
rect.top = yPos ;
|
|
rect.right = rect.left + m_aCols[iCol].xWidth - 3 * LegendLeftMargin() ;
|
|
rect.bottom = yPos + m_yItemHeight ;
|
|
|
|
if( m_fMetafile ) {
|
|
OffsetRect ( &rect, m_Rect.left, m_Rect.top );
|
|
|
|
// Don't draw past the legend bounds.
|
|
|
|
if ( rect.bottom > m_Rect.bottom ) {
|
|
return;
|
|
} else if ( rect.left >= m_Rect.right ) {
|
|
return;
|
|
} else if ( m_Rect.right < rect.right ) {
|
|
rect.right = m_Rect.right;
|
|
}
|
|
|
|
DrawEdge(hDC, &rect, BDR_SUNKENOUTER, BF_RECT);
|
|
}
|
|
|
|
switch (m_aCols[iCol].iOrientation)
|
|
{ // switch
|
|
case LEFTORIENTATION:
|
|
SetTextAlign (hDC, TA_LEFT) ;
|
|
xPos = rect.left ;
|
|
break ;
|
|
|
|
case CENTERORIENTATION:
|
|
SetTextAlign (hDC, TA_CENTER) ;
|
|
xPos = (rect.left + rect.right) / 2 ;
|
|
break ;
|
|
|
|
case RIGHTORIENTATION:
|
|
SetTextAlign (hDC, TA_RIGHT) ;
|
|
xPos = rect.right ;
|
|
break ;
|
|
|
|
default:
|
|
xPos = rect.left ;
|
|
break ;
|
|
} // switch
|
|
|
|
if (lpszValue[0] == 0)
|
|
lpszValue = szMissing;
|
|
|
|
bNeedEllipses = NeedEllipses (
|
|
hAttribDC,
|
|
lpszValue,
|
|
lstrlen(lpszValue),
|
|
rect.right - rect.left,
|
|
m_xEllipses,
|
|
&cChars );
|
|
|
|
|
|
if ( bNeedEllipses ) {
|
|
cChars = min(cChars,MAX_COL_CHARS);
|
|
wcsncpy(achBuf, lpszValue, cChars);
|
|
achBuf[cChars] = L'\0';
|
|
|
|
StringCchCat(achBuf,
|
|
MAX_COL_CHARS + sizeof(ELLIPSES)/sizeof(WCHAR) + 1,
|
|
ELLIPSES);
|
|
|
|
lpszValue = achBuf;
|
|
cChars = lstrlen(lpszValue);
|
|
}
|
|
|
|
ExtTextOut (hDC, xPos, rect.top + yBorderHeight, ETO_OPAQUE | ETO_CLIPPED,
|
|
&rect, lpszValue, cChars, NULL) ;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Draw one legend line
|
|
//
|
|
void
|
|
CLegend::DrawItem (
|
|
PCGraphItem pItem,
|
|
INT yPos,
|
|
HDC hDC,
|
|
HDC hAttribDC)
|
|
{
|
|
|
|
LPWSTR pszName = NULL;
|
|
WCHAR szBuf[MAX_PATH + 1];
|
|
INT iMinWidth = 3;
|
|
INT iPrecision = 3;
|
|
|
|
szBuf[0] = L'\0';
|
|
|
|
// Draw Color
|
|
DrawColorCol (pItem, eLegendColorCol, hDC, hAttribDC, yPos) ;
|
|
|
|
// Draw Scale
|
|
|
|
#if PDH_MIN_SCALE != -7
|
|
// display a message if the scale format string gets out of sync with
|
|
// the PDH limits
|
|
#pragma message ("\nLEGEND.CPP: the scale format statement does not match the PDH\n")
|
|
#endif
|
|
|
|
if ( pItem->Scale() < (FLOAT) 1.0 ) {
|
|
iMinWidth = 7;
|
|
iPrecision = 7;
|
|
} else {
|
|
iMinWidth = 3;
|
|
iPrecision = 3;
|
|
}
|
|
|
|
|
|
FormatNumber ( pItem->Scale(), szBuf, MAX_PATH, iMinWidth, iPrecision );
|
|
|
|
SetTextAlign (hDC, TA_TOP) ;
|
|
DrawCol ( eLegendScaleCol, hDC, hAttribDC, yPos, szBuf) ;
|
|
|
|
// Draw Counter
|
|
DrawCol ( eLegendCounterCol, hDC, hAttribDC, yPos, pItem->Counter()->Name()) ;
|
|
|
|
// Draw Instance
|
|
pszName = pItem->Instance()->GetInstanceName();
|
|
DrawCol ( eLegendInstanceCol, hDC, hAttribDC, yPos, pszName) ;
|
|
|
|
// Draw Parent
|
|
pszName = pItem->Instance()->GetParentName();
|
|
DrawCol (eLegendParentCol, hDC, hAttribDC, yPos, pszName) ;
|
|
|
|
// Draw Object
|
|
DrawCol (eLegendObjectCol, hDC, hAttribDC, yPos, pItem->Object()->Name()) ;
|
|
|
|
// Draw System
|
|
DrawCol (eLegendSystemCol, hDC, hAttribDC, yPos, pItem->Machine()->Name()) ;
|
|
}
|
|
|
|
//
|
|
// Resize parts of legend
|
|
//
|
|
void CLegend::SizeComponents (LPRECT pRect)
|
|
{
|
|
INT xWidth;
|
|
INT yHeight;
|
|
|
|
m_Rect = *pRect;
|
|
|
|
xWidth = pRect->right - pRect->left;
|
|
yHeight = pRect->bottom - pRect->top;
|
|
|
|
// If no space, hide window and leave
|
|
if (xWidth == 0 || yHeight == 0) {
|
|
WindowShow(m_hWnd, FALSE);
|
|
return;
|
|
}
|
|
|
|
// If loaded from property bag, set column sizes.
|
|
if ( NULL != m_parrColWidthFraction ) {
|
|
INT iColTotalWidth;
|
|
INT iCol;
|
|
HD_ITEM hdi;
|
|
|
|
hdi.mask = HDI_WIDTH;
|
|
|
|
iColTotalWidth = xWidth - 2 * LegendLeftMargin();
|
|
|
|
for ( iCol = 0; iCol < iLegendNumCols; iCol++ ) {
|
|
m_aCols[iCol].xWidth = (INT)(m_parrColWidthFraction[iCol] * iColTotalWidth);
|
|
hdi.cxy = m_aCols[iCol].xWidth;
|
|
Header_SetItem(m_hWndHeader, iCol, &hdi);
|
|
}
|
|
|
|
AdjustColumnWidths ();
|
|
|
|
delete m_parrColWidthFraction;
|
|
m_parrColWidthFraction = NULL;
|
|
}
|
|
|
|
// Show window to assigned position
|
|
MoveWindow(m_hWnd, pRect->left, pRect->top, xWidth, yHeight, FALSE);
|
|
WindowShow(m_hWnd, TRUE);
|
|
|
|
// Set the size, position, and visibility of the header control.
|
|
SetWindowPos(m_hWndHeader, HWND_TOP, 0, 0, xWidth, m_yHeaderHeight, SWP_SHOWWINDOW);
|
|
|
|
// Resize legend items window
|
|
MoveWindow (m_hWndItems,
|
|
LegendLeftMargin (), m_yHeaderHeight + ThreeDPad,
|
|
xWidth - 2 * LegendLeftMargin (),
|
|
yHeight - m_yHeaderHeight - ThreeDPad - LegendBottomMargin(),
|
|
TRUE) ;
|
|
}
|
|
|
|
//
|
|
// Repaint legend area
|
|
//
|
|
|
|
void CLegend::OnPaint ( void )
|
|
{ // OnPaint
|
|
HDC hDC ;
|
|
RECT rectFrame;
|
|
PAINTSTRUCT ps ;
|
|
|
|
hDC = BeginPaint (m_hWnd, &ps) ;
|
|
|
|
if ( eAppear3D == m_pCtrl->Appearance() ) {
|
|
// Draw 3D border
|
|
GetClientRect(m_hWnd, &rectFrame);
|
|
//rectFrame.bottom -= ThreeDPad;
|
|
//rectFrame.right -= ThreeDPad;
|
|
DrawEdge(hDC, &rectFrame, BDR_SUNKENOUTER, BF_RECT);
|
|
}
|
|
|
|
if (LBNumItems (m_hWndItems) == 0) {
|
|
WindowInvalidate(m_hWndItems) ;
|
|
}
|
|
|
|
|
|
EndPaint (m_hWnd, &ps) ;
|
|
} // OnPaint
|
|
|
|
//
|
|
// Handle user drawn header
|
|
//
|
|
|
|
void CLegend::OnDrawHeader(LPDRAWITEMSTRUCT lpDI)
|
|
{
|
|
INT iCol = DIIndex(lpDI);
|
|
HDC hDC = lpDI->hDC;
|
|
RECT rc = lpDI->rcItem;
|
|
BOOL bItemState = lpDI->itemState;
|
|
|
|
// The screen DC is used for the attribute DC.
|
|
DrawColHeader( iCol, hDC, hDC, rc, bItemState );
|
|
}
|
|
|
|
//
|
|
// Handle user drawn item message
|
|
//
|
|
void CLegend::OnDrawItem (LPDRAWITEMSTRUCT lpDI)
|
|
{
|
|
HFONT hFontPrevious ;
|
|
HDC hDC ;
|
|
PCGraphItem pItem ;
|
|
INT iLBIndex ;
|
|
COLORREF preBkColor = m_pCtrl->clrBackCtl();
|
|
COLORREF preTextColor = m_pCtrl->clrFgnd();
|
|
BOOL ResetColor = FALSE ;
|
|
|
|
hDC = lpDI->hDC ;
|
|
iLBIndex = DIIndex (lpDI) ;
|
|
|
|
if (iLBIndex == -1)
|
|
pItem = NULL ;
|
|
else
|
|
pItem = (PCGraphItem) LBData (m_hWndItems, iLBIndex) ;
|
|
|
|
// If only a focus change, flip focus rect and leave
|
|
if (lpDI->itemAction == ODA_FOCUS) {
|
|
DrawFocusRect (hDC, &(lpDI->rcItem)) ;
|
|
return;
|
|
}
|
|
|
|
// If item is selected use highlight colors
|
|
if (DISelected (lpDI) || pItem == NULL) {
|
|
preTextColor = SetTextColor (hDC, GetSysColor (COLOR_HIGHLIGHTTEXT)) ;
|
|
preBkColor = SetBkColor (hDC, GetSysColor (COLOR_HIGHLIGHT)) ;
|
|
ResetColor = TRUE;
|
|
} // Else set BkColor to BackColorLegend selected by the user.
|
|
|
|
// Clear area
|
|
ExtTextOut (hDC, lpDI->rcItem.left, lpDI->rcItem.top,
|
|
ETO_OPAQUE, &(lpDI->rcItem), NULL, 0, NULL ) ;
|
|
|
|
// Draw Legend Item
|
|
if (pItem) {
|
|
hFontPrevious = SelectFont (hDC, m_pCtrl->Font()) ;
|
|
// The screen DC is used as the attribute DC
|
|
DrawItem (pItem, lpDI->rcItem.top, hDC, hDC) ;
|
|
SelectFont (hDC, hFontPrevious) ;
|
|
}
|
|
|
|
// Draw Focus rect
|
|
if (DIFocus (lpDI))
|
|
DrawFocusRect (hDC, &(lpDI->rcItem)) ;
|
|
|
|
// Restore original colors
|
|
if (ResetColor == TRUE) {
|
|
SetTextColor (hDC, preTextColor) ;
|
|
SetBkColor (hDC, preBkColor) ;
|
|
}
|
|
}
|
|
|
|
void CLegend::OnMeasureItem (LPMEASUREITEMSTRUCT lpMI) {
|
|
lpMI->itemHeight = m_yItemHeight ;
|
|
} // OnMeasureItem
|
|
|
|
|
|
|
|
void CLegend::OnDblClick ( void )
|
|
{
|
|
m_pCtrl->DblClickCounter ( m_pCurrentItem );
|
|
}
|
|
|
|
|
|
//
|
|
// Handle selection change message
|
|
//
|
|
|
|
void CLegend::OnSelectionChanged ( void )
|
|
{
|
|
INT iIndex ;
|
|
PCGraphItem pGItem;
|
|
|
|
// Get the new selection
|
|
iIndex = LBSelection (m_hWndItems) ;
|
|
pGItem = (PCGraphItem) LBData (m_hWndItems, iIndex) ;
|
|
|
|
// if it's bad, reselect the current one
|
|
// else request parent control to select new item
|
|
if (pGItem == (PCGraphItem)LB_ERR) {
|
|
SelectItem(m_pCurrentItem);
|
|
}
|
|
else {
|
|
m_pCurrentItem = pGItem;
|
|
m_pCtrl->SelectCounter(pGItem);
|
|
}
|
|
}
|
|
|
|
void CLegend::AdjustColumnWidths (
|
|
INT iCol
|
|
)
|
|
{
|
|
INT i;
|
|
|
|
// Adjust positions of following columns
|
|
for (i=iCol+1; i < iLegendNumCols; i++) {
|
|
m_aCols[i].xPos = m_aCols[i - 1].xPos + m_aCols[i - 1].xWidth ;
|
|
}
|
|
}
|
|
|
|
void CLegend::OnColumnWidthChanged (
|
|
HD_NOTIFY *phdn
|
|
)
|
|
{
|
|
INT iCol = phdn->iItem;
|
|
INT xWidth = phdn->pitem->cxy;
|
|
|
|
// Update column width
|
|
m_aCols[iCol].xWidth = xWidth;
|
|
|
|
AdjustColumnWidths ( iCol );
|
|
|
|
// Force update
|
|
WindowInvalidate(m_hWndItems) ;
|
|
}
|
|
|
|
|
|
LPCWSTR CLegend::GetSortKey (
|
|
PCGraphItem pItem
|
|
)
|
|
{
|
|
static WCHAR chNullName = 0;
|
|
|
|
switch (m_iSortCol) {
|
|
|
|
case eLegendCounterCol:
|
|
return pItem->Counter()->Name();
|
|
|
|
case eLegendInstanceCol:
|
|
if (pItem->Instance()->HasParent())
|
|
return wcschr(pItem->Instance()->Name(), L'/') + 1;
|
|
else
|
|
return pItem->Instance()->Name();
|
|
|
|
case eLegendParentCol:
|
|
if (pItem->Instance()->HasParent())
|
|
return pItem->Instance()->Name();
|
|
else
|
|
return &chNullName;
|
|
|
|
case eLegendObjectCol:
|
|
return pItem->Object()->Name();
|
|
|
|
case eLegendSystemCol:
|
|
return pItem->Machine()->Name();
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
void
|
|
CLegend::OnColumnClicked (
|
|
HD_NOTIFY *phdn
|
|
)
|
|
{
|
|
INT i;
|
|
INT iCol = phdn->iItem;
|
|
INT nItems = LBNumItems (m_hWndItems);
|
|
PSORT_ITEM pSortItem;
|
|
PSORT_ITEM pSortItems;
|
|
BOOL bResort = FALSE;
|
|
|
|
if (nItems <= 0)
|
|
return;
|
|
|
|
// Can't sort on color or scale factor
|
|
if (iCol == eLegendColorCol || iCol == eLegendScaleCol) {
|
|
m_iSortDir = NO_SORT;
|
|
return;
|
|
}
|
|
|
|
// If repeat click, reverse sort direction
|
|
if (iCol == m_iSortCol) {
|
|
bResort = TRUE;
|
|
m_iSortDir = (m_iSortDir == INCREASING_SORT) ?
|
|
DECREASING_SORT : INCREASING_SORT;
|
|
} else {
|
|
m_iSortCol = iCol;
|
|
m_iSortDir = INCREASING_SORT;
|
|
}
|
|
|
|
// Allocate array for sorting
|
|
pSortItems = new SORT_ITEM [nItems];
|
|
if (pSortItems == NULL) {
|
|
return;
|
|
}
|
|
|
|
// Build array of GraphItem/Key pairs
|
|
pSortItem = pSortItems;
|
|
for (i=0; i<nItems; i++,pSortItem++) {
|
|
|
|
pSortItem->pGItem = (PCGraphItem)LBData(m_hWndItems, i);
|
|
pSortItem->pszKey = GetSortKey(pSortItem->pGItem);
|
|
}
|
|
|
|
// For resort, just reload in reverse order.
|
|
if ( !bResort ) {
|
|
// Sort by key value
|
|
qsort( (PVOID)pSortItems, nItems, sizeof(SORT_ITEM), &LegendSortFunc );
|
|
}
|
|
|
|
// Disable drawing while rebuilding list
|
|
LBSetRedraw(m_hWndItems, FALSE);
|
|
|
|
// Clear list box
|
|
LBReset (m_hWndItems) ;
|
|
|
|
// Reload in sorted order
|
|
if ( !bResort && m_iSortDir == INCREASING_SORT) {
|
|
for (i=0; i<nItems; i++) {
|
|
LBAdd (m_hWndItems, pSortItems[i].pGItem);
|
|
}
|
|
} else {
|
|
for (i=nItems - 1; i>=0; i--) {
|
|
LBAdd (m_hWndItems, pSortItems[i].pGItem);
|
|
}
|
|
}
|
|
|
|
LBSetRedraw(m_hWndItems, TRUE);
|
|
|
|
delete [] pSortItems;
|
|
}
|
|
|
|
//
|
|
// Window procedure
|
|
//
|
|
LRESULT APIENTRY GraphLegendWndProc (HWND hWnd, UINT uiMsg, WPARAM wParam,
|
|
LPARAM lParam)
|
|
{
|
|
CLegend *pLegend;
|
|
BOOL bCallDefProc ;
|
|
LRESULT lReturnValue ;
|
|
RECT rect;
|
|
|
|
pLegend = (PLEGEND)GetWindowLongPtr(hWnd,0);
|
|
|
|
bCallDefProc = FALSE ;
|
|
lReturnValue = 0L ;
|
|
|
|
switch (uiMsg)
|
|
{
|
|
case WM_CREATE:
|
|
pLegend = (PLEGEND)((CREATESTRUCT*)lParam)->lpCreateParams;
|
|
SetWindowLongPtr(hWnd,0,(INT_PTR)pLegend);
|
|
break;
|
|
|
|
case WM_DESTROY:
|
|
pLegend->m_hWnd = NULL;
|
|
break ;
|
|
|
|
case WM_LBUTTONDBLCLK:
|
|
case WM_LBUTTONDOWN:
|
|
SendMessage(GetParent(hWnd), uiMsg, wParam, lParam);
|
|
break;
|
|
|
|
case WM_COMMAND:
|
|
switch (HIWORD (wParam))
|
|
{ // switch
|
|
case LBN_DBLCLK:
|
|
pLegend->OnDblClick () ;
|
|
break ;
|
|
|
|
case LBN_SELCHANGE:
|
|
pLegend->OnSelectionChanged () ;
|
|
break ;
|
|
|
|
case LBN_SETFOCUS:
|
|
pLegend->m_pCtrl->Activate();
|
|
break;
|
|
|
|
default:
|
|
bCallDefProc = TRUE ;
|
|
} // switch
|
|
break ;
|
|
|
|
case WM_NOTIFY:
|
|
switch (((LPNMHDR)lParam)->code)
|
|
{
|
|
case HDN_ENDTRACK:
|
|
pLegend->OnColumnWidthChanged((HD_NOTIFY*) lParam);
|
|
break;
|
|
|
|
case HDN_ITEMCLICK:
|
|
pLegend->OnColumnClicked((HD_NOTIFY*) lParam);
|
|
pLegend->m_pCtrl->Activate();
|
|
break;
|
|
}
|
|
|
|
return FALSE;
|
|
break;
|
|
|
|
case WM_DRAWITEM:
|
|
switch (((LPDRAWITEMSTRUCT)lParam)->CtlID) {
|
|
|
|
case LIST_WND:
|
|
pLegend->OnDrawItem((LPDRAWITEMSTRUCT) lParam) ;
|
|
break;
|
|
|
|
case HDR_WND:
|
|
pLegend->OnDrawHeader((LPDRAWITEMSTRUCT) lParam) ;
|
|
break;
|
|
}
|
|
|
|
break ;
|
|
|
|
case WM_MEASUREITEM:
|
|
pLegend->OnMeasureItem ((LPMEASUREITEMSTRUCT) lParam) ;
|
|
break ;
|
|
|
|
case WM_DELETEITEM:
|
|
break ;
|
|
|
|
case WM_ERASEBKGND:
|
|
GetClientRect(hWnd, &rect);
|
|
Fill((HDC)wParam, pLegend->m_pCtrl->clrBackCtl(), &rect);
|
|
return TRUE;
|
|
|
|
case WM_PAINT:
|
|
pLegend->OnPaint () ;
|
|
break ;
|
|
|
|
case WM_SETFOCUS:
|
|
SetFocus(pLegend->m_hWndItems);
|
|
break ;
|
|
|
|
default:
|
|
bCallDefProc = TRUE ;
|
|
}
|
|
|
|
|
|
if (bCallDefProc)
|
|
lReturnValue = DefWindowProc (hWnd, uiMsg, wParam, lParam) ;
|
|
|
|
return (lReturnValue);
|
|
}
|
|
|
|
LRESULT APIENTRY
|
|
HdrWndProc (
|
|
HWND hWnd,
|
|
UINT uiMsg,
|
|
WPARAM wParam,
|
|
LPARAM lParam
|
|
)
|
|
{
|
|
RECT rect;
|
|
CLegend *pLegend;
|
|
|
|
// Parent window carries the Legend object pointer
|
|
pLegend = (PLEGEND)GetWindowLongPtr(GetParent(hWnd),0);
|
|
|
|
if (uiMsg == WM_ERASEBKGND) {
|
|
GetClientRect(hWnd, &rect);
|
|
Fill((HDC)wParam, pLegend->m_pCtrl->clrBackCtl(), &rect);
|
|
return TRUE;
|
|
}
|
|
|
|
// Do the default processing
|
|
#ifdef STRICT
|
|
return CallWindowProc(pLegend->m_DefaultWndProc, hWnd, uiMsg, wParam, lParam);
|
|
#else
|
|
return CallWindowProc((FARPROC)pLegend->m_DefaultWndProc, hWnd, uiMsg, wParam, lParam);
|
|
#endif
|
|
}
|
|
|
|
|
|
//
|
|
// Compute minimum width of legend
|
|
//
|
|
INT CLegend::MinWidth ( void )
|
|
{
|
|
return 0 ;
|
|
}
|
|
|
|
//
|
|
// Compute minimum height of legend
|
|
//
|
|
INT CLegend::MinHeight ( INT yMaxHeight )
|
|
{
|
|
INT yHeight = m_yHeaderHeight + m_yItemHeight + 2*ThreeDPad
|
|
+ LegendBottomMargin();
|
|
|
|
return (yMaxHeight >= yHeight) ? yHeight : 0;
|
|
}
|
|
|
|
|
|
//
|
|
// Compute prefered height of legend
|
|
//
|
|
INT CLegend::Height (INT yMaxHeight)
|
|
{
|
|
INT nItems;
|
|
INT nPrefItems;
|
|
|
|
// Determine preferred number of items to show
|
|
nPrefItems = PinInclusive(LBNumItems(m_hWndItems), 1, iMaxVisibleItems);
|
|
|
|
// Determine number of items that will fit
|
|
nItems = (yMaxHeight - m_yHeaderHeight - 2*ThreeDPad - LegendBottomMargin())
|
|
/ m_yItemHeight;
|
|
|
|
// Use smaller number
|
|
nItems = min(nItems, nPrefItems);
|
|
|
|
// If no items will fit, return zero
|
|
if (nItems == 0)
|
|
return 0;
|
|
|
|
// Return height of legend with nItems
|
|
return m_yHeaderHeight + 2*ThreeDPad + nItems * m_yItemHeight
|
|
+ LegendBottomMargin();
|
|
}
|
|
|
|
|
|
|
|
#ifdef KEEP_PRINT
|
|
|
|
void CLegend::Print (HDC hDC, RECT rectLegend)
|
|
{
|
|
INT yItemHeight ;
|
|
HFONT hFontItems ;
|
|
PCGraphItem pItem ;
|
|
INT iIndex ;
|
|
INT iIndexNum ;
|
|
|
|
yItemHeight = m_yItemHeight ;
|
|
hFontItems = m_hFontItems ;
|
|
|
|
m_hFontItems = hFontPrinterScales ;
|
|
SelectFont (hDC, m_hFontItems) ;
|
|
|
|
m_yItemHeight = FontHeight (hDC, TRUE) ;
|
|
|
|
iIndexNum = LBNumItems (m_hWndItems);
|
|
for (iIndex = 0; iIndex < iIndexNum; iIndex++)
|
|
{
|
|
pItem = (PCGraphItem) LBData (m_hWndItems, iIndex) ;
|
|
DrawItem (pItem, iIndex * m_yItemHeight, hDC) ;
|
|
}
|
|
|
|
m_hFontItems = hFontItems ;
|
|
m_yItemHeight = yItemHeight ;
|
|
|
|
SelectBrush (hDC, GetStockObject (HOLLOW_BRUSH)) ;
|
|
SelectPen (hDC, GetStockObject (BLACK_PEN)) ;
|
|
Rectangle (hDC, 0, 0,
|
|
rectLegend.right - rectLegend.left,
|
|
rectLegend.bottom - rectLegend.top) ;
|
|
}
|
|
#endif
|
|
|
|
|
|
void
|
|
CLegend::ChangeFont(
|
|
HDC hDC
|
|
)
|
|
{
|
|
HD_LAYOUT hdl;
|
|
WINDOWPOS wp;
|
|
RECT rectLayout;
|
|
|
|
// Assign font to header
|
|
SetFont(m_hWndHeader, m_pCtrl->Font());
|
|
|
|
// Get prefered height of header control
|
|
// (use arbitrary rect for allowed area)
|
|
rectLayout.left = 0;
|
|
rectLayout.top = 0;
|
|
rectLayout.right = 32000;
|
|
rectLayout.bottom = 32000;
|
|
|
|
wp.cy = 0;
|
|
hdl.prc = &rectLayout;
|
|
hdl.pwpos = ℘
|
|
Header_Layout(m_hWndHeader, &hdl);
|
|
m_yHeaderHeight = wp.cy + 2 * yBorderHeight;
|
|
|
|
// Set up DC for font measurements
|
|
SelectFont (hDC, m_pCtrl->Font()) ;
|
|
|
|
// Compute height of legend line
|
|
SelectFont (hDC, m_hFontItems) ;
|
|
m_yItemHeight = FontHeight (hDC, TRUE) + 2 * yBorderHeight;
|
|
|
|
LBSetItemHeight(m_hWndItems, m_yItemHeight);
|
|
|
|
// Compute width of "..."
|
|
m_xEllipses = TextWidth (hDC, ELLIPSES) ;
|
|
}
|
|
|
|
|
|
|
|
void
|
|
CLegend::Render(
|
|
HDC hDC,
|
|
HDC hAttribDC,
|
|
BOOL /*fMetafile*/,
|
|
BOOL /*fEntire*/,
|
|
LPRECT prcUpdate )
|
|
{
|
|
PCGraphItem pItem ;
|
|
INT iIndex ;
|
|
INT iIndexNum ;
|
|
RECT rectPaint;
|
|
HFONT hFontPrevious;
|
|
|
|
// if no space assigned, return
|
|
if (m_Rect.top == m_Rect.bottom)
|
|
return;
|
|
|
|
// if no painting needed, return
|
|
if (!IntersectRect(&rectPaint, &m_Rect, prcUpdate))
|
|
return;
|
|
|
|
m_fMetafile = TRUE;
|
|
|
|
hFontPrevious = SelectFont (hDC, m_pCtrl->Font()) ;
|
|
|
|
DrawHeader ( hDC, hAttribDC, rectPaint );
|
|
|
|
SelectFont (hDC, hFontPrevious) ;
|
|
|
|
iIndexNum = LBNumItems (m_hWndItems);
|
|
|
|
hFontPrevious = SelectFont (hDC, m_pCtrl->Font()) ;
|
|
|
|
for (iIndex = 0; iIndex < iIndexNum; iIndex++) {
|
|
pItem = (PCGraphItem) LBData (m_hWndItems, iIndex) ;
|
|
DrawItem (
|
|
pItem,
|
|
m_yHeaderHeight + ( iIndex * m_yItemHeight ),
|
|
hDC,
|
|
hAttribDC) ;
|
|
}
|
|
|
|
SelectFont (hDC, hFontPrevious) ;
|
|
|
|
m_fMetafile = FALSE;
|
|
|
|
|
|
SelectBrush (hDC, GetStockObject (HOLLOW_BRUSH)) ;
|
|
SelectPen (hDC, GetStockObject (BLACK_PEN)) ;
|
|
|
|
if ( eAppear3D == m_pCtrl->Appearance() ) {
|
|
// Draw 3D border
|
|
DrawEdge(hDC, &m_Rect, BDR_SUNKENOUTER, BF_RECT);
|
|
}
|
|
}
|
|
|
|
HRESULT
|
|
CLegend::GetNextValue (
|
|
WCHAR*& pszNext,
|
|
DOUBLE& rdValue )
|
|
{
|
|
HRESULT hr = NOERROR;
|
|
WCHAR szValue[MAX_COL_CHARS + 1];
|
|
INT iLen;
|
|
|
|
VARIANT vValue;
|
|
|
|
rdValue = -1.0;
|
|
|
|
iLen = wcscspn (pszNext, L"\t");
|
|
|
|
//
|
|
// Change tab character to null.
|
|
//
|
|
pszNext[iLen] = L'\0';
|
|
|
|
hr = StringCchCopy ( szValue, MAX_COL_CHARS + 1, pszNext );
|
|
|
|
if ( SUCCEEDED ( hr ) ) {
|
|
|
|
VariantInit( &vValue );
|
|
vValue.vt = VT_BSTR;
|
|
|
|
vValue.bstrVal = SysAllocString ( szValue );
|
|
hr = VariantChangeTypeEx( &vValue, &vValue, LCID_SCRIPT, VARIANT_NOUSEROVERRIDE, VT_R8 );
|
|
|
|
if ( SUCCEEDED(hr) ) {
|
|
rdValue = vValue.dblVal;
|
|
}
|
|
|
|
VariantClear( &vValue );
|
|
}
|
|
pszNext += iLen + 1 ;
|
|
|
|
return hr;
|
|
}
|