/*++ 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 #include #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 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 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; iColWrite(&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 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; ipGItem = (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=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; }