// PropertyGrid.cpp : implementation file
//
#include "stdafx.h"
#include "PropertyGrid.h"
#include <Commctrl.h>
#include <algorithm>
#include <windowsx.h>
#include "resource.h"
#include "HotKey.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
//using namespace MSXML2;
using namespace PropertyGrid;
/////////////////////////////////////////////////////////////////////////////
// CPropertyGrid
CPropertyGrid::CPropertyGrid()
{
// Just a simple image list
m_imgList.Create(16, 16, ILC_COLOR8 | ILC_MASK, 2, 1);
m_imgList.Add((HICON)::LoadImage(::AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_COLLAPSED), IMAGE_ICON, 16, 16, 0));
m_imgList.Add((HICON)::LoadImage(::AfxGetResourceHandle(), MAKEINTRESOURCE(IDI_EXPANDED), IMAGE_ICON, 16, 16, 0));
NONCLIENTMETRICS ncm = {0};
ncm.cbSize = sizeof(ncm);
SystemParametersInfo(SPI_GETNONCLIENTMETRICS, 0, &ncm, 0);
// Fonts for category and subcategory (bold one)
// and for element
LOGFONT lfBold = ncm.lfMessageFont;
lfBold.lfWeight = FW_BOLD;
m_hNormal = CreateFontIndirect(&(ncm.lfMessageFont));
m_hBold = CreateFontIndirect(&lfBold);
// Colors
m_crCategory = RGB(222, 211, 206);
m_crCategoryFont = RGB(132, 130, 132);
m_crCategoryTopBorder = RGB(255, 251, 239);
m_crSubcategory = RGB(222, 211, 206);
m_crSubcategoryFont = RGB(0, 0, 0);
m_crElement = RGB(255, 255, 255);
m_crElementFont = RGB(132, 130, 132);
m_crElementSelected = RGB(49, 109, 206);
m_crElementSelectedFont = RGB(255, 255, 255);
// Cursors
m_hArrow = ::LoadCursor(0, MAKEINTRESOURCE(IDC_ARROW));
m_hSize = ::LoadCursor(0, MAKEINTRESOURCE(IDC_SIZEWE));
InitializeGdiObjects();
m_bSizing = FALSE;
m_eiItem.pWindow = 0;
//
// Notification
//
m_pNotificationReciever = 0;
}
CPropertyGrid::~CPropertyGrid()
{
UninitializeGdiObjects();
// Fonts
::DeleteObject(m_hNormal);
::DeleteObject(m_hBold);
// Cursors
::DestroyCursor(m_hArrow);
::DestroyCursor(m_hSize);
}
BEGIN_MESSAGE_MAP(CPropertyGrid, CListCtrl)
//{{AFX_MSG_MAP(CPropertyGrid)
ON_NOTIFY_REFLECT(LVN_GETDISPINFO, OnGetdispinfo)
ON_NOTIFY_REFLECT(LVN_GETINFOTIP, OnGetinfotip)
ON_WM_LBUTTONDOWN()
ON_WM_MOUSEMOVE()
ON_WM_SETCURSOR()
ON_WM_LBUTTONUP()
ON_WM_SIZE()
ON_NOTIFY_REFLECT(NM_RETURN, OnReturn)
ON_WM_VSCROLL()
ON_WM_HSCROLL()
ON_WM_ERASEBKGND()
ON_WM_MEASUREITEM_REFLECT()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
/////////////////////////////////////////////////////////////////////////////
// CPropertyGrid message handlers
void CPropertyGrid::PreSubclassWindow()
{
CListCtrl::PreSubclassWindow();
CRect rcClient;
GetClientRect(rcClient);
// Modifying styles
ModifyStyle(0, LVS_REPORT | LVS_SHOWSELALWAYS | LVS_NOCOLUMNHEADER | LVS_OWNERDATA | LVS_OWNERDRAWFIXED);
SetExtendedStyle(LVS_EX_INFOTIP);
InsertColumn(0, 0, LVCFMT_LEFT, (rcClient.right - ::GetSystemMetrics(SM_CXHSCROLL)) / 2);
InsertColumn(1, 0, LVCFMT_LEFT, (rcClient.right - ::GetSystemMetrics(SM_CXHSCROLL))/ 2);
SetImageList(&m_imgList, LVSIL_SMALL);
// See "Customizing a Control's Appearance Using Custom Draw" topic
// in Platform SDK documentation
::SendMessage(*this, CCM_SETVERSION, (WPARAM)(DWORD)5, 0);
// Creating in-place things
m_ctrlEdit.Create(WS_CHILD | WS_VISIBLE /*| WS_BORDER */| ES_AUTOHSCROLL, CRect(0, 0, 0, 0),
this, IDC_PROPERTYGRID_EDIT);
SetWindowFont(m_ctrlEdit.GetSafeHwnd(), m_hNormal, TRUE);
m_ctrlDateTime.Create(WS_CHILD | WS_VISIBLE, CRect(0, 0, 0, 0), this, IDC_PROPERTYGRID_DATETIME);
SetWindowFont(m_ctrlDateTime.GetSafeHwnd(), m_hNormal, TRUE);
m_ctrlCombo.Create(WS_CHILD | WS_VISIBLE | CBS_AUTOHSCROLL | CBS_DROPDOWNLIST | WS_VSCROLL,
CRect(0, 0, 0, 0), this, IDC_PROPERTYGRID_COMBOBOX);
m_ctrlCombo.SetItemHeight(0, 10);
SetWindowFont(m_ctrlCombo.GetSafeHwnd(), m_hNormal, TRUE);
m_ctrlHotKey.Create(WS_CHILD | WS_VISIBLE, CRect(0, 0, 0, 0), this, IDC_PROPERTYGRID_HOTKEY);
SetWindowFont(m_ctrlHotKey.GetSafeHwnd(), m_hNormal, TRUE);
GetClientRect(m_rcClient);
m_nNameColumn = GetColumnWidth(0);
m_nValueColumn = GetColumnWidth(1);
SetWindowFont(*this, m_hNormal, TRUE);
//SetWindowPos(0, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOZORDER | SWP_NOSENDCHANGING | SWP_NOSIZE);
GetClientRect(m_rcClient);
SendMessage(WM_SIZE, 0, MAKELPARAM(m_rcClient.Width(), m_rcClient.Height()));
}
void CPropertyGrid::OnGetdispinfo(NMHDR* pNMHDR, LRESULT* pResult)
{
LV_DISPINFO* pDispInfo = (LV_DISPINFO*)pNMHDR;
LV_ITEM* pItem= &(pDispInfo)->item;
VCATEGORY::iterator vci;
LELEMENT::iterator lei;
if (pItem->mask & LVIF_TEXT)
{
switch(pItem->iSubItem)
{
case 0:
if(GetItem(pItem->iItem, vci, lei))
{
// Element
strcpy(pItem->pszText, lei->strName);
} // if
else
{
// Category
strcpy(pItem->pszText, vci->strName);
} // else
break;
case 1:
if(GetItem(pItem->iItem, vci, lei))
{
// Element
strcpy(pItem->pszText, lei->strValue);
} // if
break;
default:
break;
} // switch
} // if
*pResult = 0;
}
int CPropertyGrid::GetExpandedCount()
{
int n = 0;
VCATEGORY::iterator vci = m_vCategories.begin();
while(m_vCategories.end() != vci)
{
n += (vci->lFlags & CF_EXPANDED) ? (vci->lElements.size() + 1) : 1;
++vci;
} // while
return n;
}
int CPropertyGrid::GetItem(int nItem, VCATEGORY::iterator& vci, LELEMENT::iterator& lei)
{
vci = m_vCategories.begin();
while(m_vCategories.end() != vci)
{
if(0 == nItem) // stopped on a category
{
return 0;
} // if
if(vci->lFlags & CF_EXPANDED)
{
if(vci->lElements.size() >= nItem)
{
lei = vci->lElements.begin();
std::advance(lei, nItem - 1);
return 1;
} // if
else
nItem -= vci->lElements.size() + 1;
} // if
else
--nItem;
++vci;
} // while
return 0;
}
void CPropertyGrid::OnGetinfotip(NMHDR* pNMHDR, LRESULT* pResult)
{
NMLVGETINFOTIP *pLVGT = (NMLVGETINFOTIP*)pNMHDR;
VCATEGORY::iterator vci;
LELEMENT::iterator lei;
CString strTip;
if(GetItem(pLVGT->iItem, vci, lei))
{
// Element
strTip = lei->strTip;
} // if
else
{
// Category
strTip = vci->strTip;
} // else
// See NMLVGETINFOTIP description in Platform SDK documentation
if(!pLVGT->dwFlags)
{
#ifdef _UNICODE
wcsncat(pLVGT->pszText, strTip, pLVGT->cchTextMax - wcslen(pLVGT->pszText));
#else
strncat(pLVGT->pszText, strTip, pLVGT->cchTextMax - strlen(pLVGT->pszText));
#endif // _UNICODE
} // if
else
{
#ifdef _UNICODE
wcsncpy(pLVGT->pszText, strTip, pLVGT->cchTextMax);
#else
strncpy(pLVGT->pszText, strTip, pLVGT->cchTextMax);
#endif // _UNICODE
} // else
*pResult = 0;
}
void CPropertyGrid::OnLButtonDown(UINT nFlags, CPoint point)
{
if(m_bSize)
{
SetCapture();
m_bSizing = TRUE;
return;
} // if
AcceptEdit();
// Testing. If clicked on an icon (only!) expand or collapse category
VCATEGORY::iterator vci;
LELEMENT::iterator lei;
UINT uFlags;
int nItem = HitTest(point, &uFlags);
if(LVHT_ONITEMICON & uFlags)
{
if(!GetItem(nItem, vci, lei)) // Category
{
if(vci->lFlags & CF_EXPANDED)
{
vci->lFlags &= ~CF_EXPANDED;
vci->lFlags |= CF_COLLAPSED;
} // if
else if(vci->lFlags & CF_COLLAPSED)
{
vci->lFlags &= ~CF_COLLAPSED;
vci->lFlags |= CF_EXPANDED;
} // else if
if(m_pNotificationReciever)
{
if(m_pNotificationReciever->Ready())
m_pNotificationReciever->CategorySelected(vci->strName, vci->strTip);
} // if
SetItemCount(GetExpandedCount());
} // if
else // Element
{
if(m_pNotificationReciever)
{
if(m_pNotificationReciever->Ready())
m_pNotificationReciever->ElementSelected(lei->strName, lei->strTip);
} // if
} // else
} // if
// Testing subitem
LVHITTESTINFO hti;
hti.pt = point;
if(-1 != SubItemHitTest(&hti))
{
if(GetItem(hti.iItem, vci, lei) && (1 == hti.iSubItem)) // Element
{
BeginEdit(lei, hti.iItem);
} // if
} // if
CListCtrl::OnLButtonDown(nFlags, point);
}
void CPropertyGrid::OnMouseMove(UINT nFlags, CPoint point)
{
// Need to set a sizing cursor when required
if(!m_bSizing)
{
m_bSize = Between(point.x, GetColumnWidth(0), 3);
} // if
else
{
SetRedraw(FALSE);
int nWidth = GetColumnWidth(0) + GetColumnWidth(1);
if((point.x > 50) && (point.x < nWidth - 50))
{
SetColumnWidth(0, m_nNameColumn = point.x);
SetColumnWidth(1, m_nValueColumn = (nWidth - point.x));
} // if
SetRedraw();
} // else
CListCtrl::OnMouseMove(nFlags, point);
}
BOOL CPropertyGrid::Between(int nValue, int nSample, int nDelta)
{
return ((nValue >= nSample - nDelta) && (nValue <= nSample + nDelta));
}
BOOL CPropertyGrid::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
{
//return CListCtrl::OnSetCursor(pWnd, nHitTest, message);
return (0 != SetCursor(m_bSize ? m_hSize : m_hArrow));
}
void CPropertyGrid::OnLButtonUp(UINT nFlags, CPoint point)
{
if(m_bSizing)
{
ReleaseCapture();
m_bSizing = FALSE;
} // if
// If we're not editing anything
if(!m_eiItem.pWindow)
CListCtrl::OnLButtonUp(nFlags, point);
}
void CPropertyGrid::OnSize(UINT nType, int cx, int cy)
{
// Retain current ratio
AcceptEdit();
int nWidth = GetColumnWidth(0) + GetColumnWidth(1);
double dCol0 = double(GetColumnWidth(0)) / double(nWidth);
double dCol1 = double(GetColumnWidth(1)) / double(nWidth);
CListCtrl::OnSize(nType, cx, cy);
SetRedraw(FALSE);
SetColumnWidth(0, m_nNameColumn = int(dCol0 * cx));
SetColumnWidth(1, m_nValueColumn = int(dCol1 * cx));
GetClientRect(m_rcClient);
TRACE("Sizing\n");
SetRedraw();
}
BOOL CPropertyGrid::CancelEdit()
{
if(m_eiItem.pWindow)
{
VCATEGORY::iterator vci;
LELEMENT::iterator lei;
if(GetItem(m_eiItem.nItem, vci, lei))
{
lei->strValue = m_eiItem.strPrevious;
} // if
m_eiItem.pWindow->ShowWindow(SW_HIDE);
m_eiItem.pWindow = 0;
Invalidate();
return TRUE;
} // if
Invalidate();
return FALSE;
}
BOOL CPropertyGrid::AcceptEdit()
{
if(m_eiItem.pWindow)
{
VCATEGORY::iterator vci;
LELEMENT::iterator lei;
if(GetItem(m_eiItem.nItem, vci, lei))
{
// Special case for CHotKeyCtrl
CHotKeyCtrl *pctrlHotKey;
CComboBox *pctrlCombo;
if(pctrlHotKey = dynamic_cast<CHotKeyCtrl *>(m_eiItem.pWindow))
{
m_eiItem.pWindow->SetFocus();
lei->wHotKey = pctrlHotKey->GetHotKey();
lei->strValue = GetModifiersString(MapModifiers(HIBYTE(lei->wHotKey))) + TCHAR(LOBYTE(lei->wHotKey));
} // if
else if(pctrlCombo = dynamic_cast<CComboBox *>(m_eiItem.pWindow))
{
if(CB_ERR != pctrlCombo->GetCurSel())
lei->lValue = pctrlCombo->GetItemData(pctrlCombo->GetCurSel());
else
lei->lValue = -1;
m_eiItem.pWindow->GetWindowText(lei->strValue);
} // else if
else
{
m_eiItem.pWindow->GetWindowText(lei->strValue);
} // else
} // if
m_eiItem.pWindow->ShowWindow(SW_HIDE);
m_eiItem.pWindow = 0;
Invalidate();
return TRUE;
} // if
Invalidate();
return FALSE;
}
BOOL CPropertyGrid::BeginEdit(LELEMENT::iterator lei, int nItem)
{
CRect rc;
GetSubItemRect(nItem, 1, LVIR_BOUNDS, rc);
if(lei->lFlags & EF_EDITBOX)
{
rc.left += VALUE_TEXT_LEFT_INDENT - 3;
rc.top += VALUE_TEXT_TOP_INDENT;
m_ctrlEdit.MoveWindow(rc);
m_ctrlEdit.ShowWindow(SW_SHOW);
m_ctrlEdit.SetWindowText(lei->strValue);
m_ctrlEdit.SetSel(0, -1);
m_eiItem.nItem = nItem;
m_eiItem.pWindow = &m_ctrlEdit;
m_eiItem.strPrevious = lei->strValue;
m_ctrlEdit.SetFocus();
return TRUE;
} // if
else if(lei->lFlags & EF_DATETIME)
{
rc.InflateRect(1, 1, 1, 1);
m_ctrlDateTime.MoveWindow(rc);
m_ctrlDateTime.ShowWindow(SW_SHOW);
m_ctrlDateTime.SetFocus();
m_eiItem.nItem = nItem;
m_eiItem.pWindow = &m_ctrlDateTime;
m_eiItem.strPrevious = lei->strValue;
return TRUE;
} // if else
else if(lei->lFlags & EF_COMBOBOX)
{
rc.left += VALUE_TEXT_LEFT_INDENT - 3;
rc.top -= 1;
rc.bottom += COMBOBOX_DROPDOWN;
InitComboBox(*lei);
m_ctrlCombo.MoveWindow(rc);
m_ctrlCombo.SetWindowPos(this, 0, 0, m_nValueColumn, 14, SWP_NOMOVE | SWP_NOOWNERZORDER);
m_ctrlCombo.ShowWindow(SW_SHOW);
m_ctrlCombo.SelectString(-1, lei->strValue);
m_eiItem.nItem = nItem;
m_eiItem.pWindow = &m_ctrlCombo;
m_eiItem.strPrevious = lei->strValue;
m_ctrlCombo.SetFocus();
return TRUE;
} // else if
else if(lei->lFlags & EF_HOTKEY)
{
m_ctrlHotKey.MoveWindow(rc);
m_ctrlHotKey.ShowWindow(SW_SHOW);
m_ctrlHotKey.SetHotKey(LOBYTE(lei->wHotKey), HIBYTE(lei->wHotKey));
m_ctrlHotKey.SetFocus();
m_eiItem.nItem = nItem;
m_eiItem.pWindow = &m_ctrlHotKey;
m_eiItem.strPrevious = lei->strValue;
} // else if
return FALSE;
}
void CPropertyGrid::OnReturn(NMHDR* pNMHDR, LRESULT* pResult)
{
AcceptEdit();
*pResult = 0;
}
void CPropertyGrid::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
AcceptEdit();
CListCtrl::OnVScroll(nSBCode, nPos, pScrollBar);
}
void CPropertyGrid::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
AcceptEdit();
CListCtrl::OnHScroll(nSBCode, nPos, pScrollBar);
}
void CPropertyGrid::DrawItem(LPDRAWITEMSTRUCT lpDIS)
{
if(lpDIS->CtlType != ODT_LISTVIEW)
return;
VCATEGORY::iterator vci;
LELEMENT::iterator lei;
/* //
// Creating memory dc to speed up drawing
//
HDC hDC;
HBITMAP hBitmap, hBitmapOld;
//
// Coordinate transformations
//
POINT ptTL = { lpDIS->rcItem.left, lpDIS->rcItem.top };
POINT ptBR = { lpDIS->rcItem.right, lpDIS->rcItem.bottom };
//::LPtoDP(lpDIS->hDC, &ptTL, 1);
//::LPtoDP(lpDIS->hDC, &ptBR, 1);
int nW = ptBR.x - ptTL.x;
int nH = ptBR.y - ptTL.y;
//
// Creating
//
VERIFY((hDC = ::CreateCompatibleDC(lpDIS->hDC)) != NULL);
//VERIFY((hBitmap = ::CreateCompatibleBitmap(lpDIS->hDC, nW, nH)) != NULL);
VERIFY((hBitmap = ::CreateBitmap(nW, nH, 1, 16, 0)) != NULL);
VERIFY((hBitmapOld = (HBITMAP)::SelectObject(hDC, hBitmap)) != NULL);*/
//
// Draw what we need
//
if(GetItem(lpDIS->itemID, vci, lei))
{
DrawElementSection(lpDIS->hDC, lpDIS->rcItem, lei->strName, lei->strValue,
lpDIS->itemID == GetNextItem(-1, LVNI_SELECTED), lei->strValue != lei->strInitialValue,
lei->lFlags & EF_READONLY);
} // if
else
{
DrawCategorySection(lpDIS->hDC, lpDIS->rcItem, vci->strName,
lpDIS->itemID == GetNextItem(-1, LVNI_SELECTED), vci->lFlags & CF_EXPANDED);
} // else
//
// Transfer
//
/* RECT rc = lpDIS->rcItem;
TRACE("%d %d %d %d\n", rc.left, rc.top, rc.right, rc.bottom);
::BitBlt(lpDIS->hDC, 0, 0, nW, nH, hDC, rc.left, rc.top, SRCCOPY);
::SelectObject(hDC, hBitmapOld);
::DeleteObject(hBitmap);
::DeleteDC(hDC);
::ReleaseDC(*this, hDC);*/
}
BOOL CPropertyGrid::OnEraseBkgnd(CDC* pDC)
{
//UNUSED_ALWAYS(pDC);
//return CListCtrl::OnEraseBkgnd(pDC);
pDC->FillSolidRect(m_rcClient, m_crElement);
return TRUE;
}
CString CPropertyGrid::CompactString(HDC hDC, const CString &rString, int cx)
{
CString str(rString);
SIZE sz;
// If it already fits
::GetTextExtentPoint32(hDC, str, str.GetLength(), &sz);
if(sz.cx <= cx)
return str;
for(unsigned n = str.GetLength(); n >= 0; --n)
{
::GetTextExtentPoint32(hDC, str.Left(n) + _T("..."), CString(str.Left(n) + _T("...")).GetLength(), &sz);
if(sz.cx <= cx)
return str.Left(n) + _T("...");
} // for
return _T("");
}
void CPropertyGrid::DrawCategorySection(HDC hDC, const CRect &rcRect, const CString& strText, BOOL bSelected,
BOOL bExpanded)
{
HPEN hPen = ::CreatePen(PS_SOLID, 1, m_crCategory);
HBRUSH hBrushOld = (HBRUSH)::SelectObject(hDC, m_brCategory);
HPEN hPenOld = (HPEN)::SelectObject(hDC, hPen);
HFONT hFontOld = (HFONT)::SelectObject(hDC, m_hBold);
COLORREF crText = ::SetTextColor(hDC, m_crCategoryFont);
// Client-rectangle wide
::Rectangle(hDC, rcRect.left, rcRect.top, rcRect.right, rcRect.bottom);
// Draw a line above the section
::SelectObject(hDC, hPenOld);
hPen = ::CreatePen(PS_SOLID, 1, m_crCategoryTopBorder);
::SelectObject(hDC, hPen);
::MoveToEx(hDC, rcRect.left, rcRect.top, 0);
::LineTo(hDC, rcRect.right, rcRect.top);
::SelectObject(hDC, hPenOld);
// Space for icon
CString str = CompactString(hDC, strText, m_nNameColumn + m_nValueColumn - CATEGORY_TEXT_LEFT_INDENT);
::TextOut(hDC, rcRect.left + CATEGORY_TEXT_LEFT_INDENT, rcRect.top + CATEGORY_TEXT_TOP_INDENT,
str, str.GetLength());
// Selected
if(bSelected)
{
DrawFocusRect(hDC, rcRect, strText);
} // if
// Icon itself
::DrawIconEx(hDC, rcRect.left, rcRect.top,
m_imgList.ExtractIcon(bExpanded ? 1 : 0), 16, 16, 0, 0, DI_NORMAL);
::SelectObject(hDC, hBrushOld);
::SelectObject(hDC, hPenOld);
::SelectObject(hDC, hFontOld);
::DeleteObject(hPen);
::SetTextColor(hDC, crText);
}
void CPropertyGrid::DrawElementSection(HDC hDC, const CRect &rcRect, const CString &strText,
const CString &strValue, BOOL bSelected, BOOL bChanged, BOOL bReadOnly)
{
// Element and Value
HPEN hPen = ::CreatePen(PS_SOLID, 1, m_crCategory);
HBRUSH hBrushOld = (HBRUSH)::SelectObject(hDC, m_brCategory);
HPEN hPenOld = (HPEN)::SelectObject(hDC, hPen);
HFONT hFontOld = (HFONT)::SelectObject(hDC, m_hNormal);
// Rectangle to the right
::Rectangle(hDC, rcRect.left, rcRect.top, rcRect.left + 16, rcRect.bottom);
// Some general stuff
CString strN = CompactString(hDC, strText, m_nNameColumn - ELEMENT_TEXT_LEFT_INDENT);
CString strV = CompactString(hDC, strValue, m_nValueColumn - VALUE_TEXT_LEFT_INDENT);
// This applies to Element only
if(bSelected)
{
COLORREF crTextOld = ::SetTextColor(hDC, m_crElementSelectedFont);
HBRUSH hBrush = ::CreateSolidBrush(m_crElementSelected);
HBRUSH hBrushOld = (HBRUSH)::SelectObject(hDC, hBrush);
HPEN hPen = ::CreatePen(PS_SOLID, 1, m_crElementSelected);
HPEN hPenOld = (HPEN)::SelectObject(hDC, hPen);
// Selected
::Rectangle(hDC, rcRect.left + BORDER_WIDTH, rcRect.top + 1, rcRect.left + m_nNameColumn, rcRect.bottom);
::TextOut(hDC, rcRect.left + ELEMENT_TEXT_LEFT_INDENT, rcRect.top + ELEMENT_TEXT_TOP_INDENT,
strN, strN.GetLength());
::SelectObject(hDC, hBrushOld);
::SelectObject(hDC, hPenOld);
::SetTextColor(hDC, crTextOld);
::DeleteObject(hBrush);
::DeleteObject(hPen);
} // if
else
{
COLORREF crTextOld;
HBRUSH hBrush, hBrushOld;
HPEN hPen, hPenOld;
if(bReadOnly)
{
crTextOld = ::SetTextColor(hDC, m_crCategoryFont);
hBrush = ::CreateSolidBrush(m_crCategory);
hBrushOld = (HBRUSH)::SelectObject(hDC, hBrush);
hPen = ::CreatePen(PS_SOLID, 1, m_crCategory);
hPenOld = (HPEN)::SelectObject(hDC, hPen);
} // if
else
{
crTextOld = ::SetTextColor(hDC, m_crElementFont);
hBrush = ::CreateSolidBrush(m_crElement);
hBrushOld = (HBRUSH)::SelectObject(hDC, hBrush);
hPen = ::CreatePen(PS_SOLID, 1, m_crElement);
hPenOld = (HPEN)::SelectObject(hDC, hPen);
} // else
// Not selected
::Rectangle(hDC, rcRect.left + BORDER_WIDTH, rcRect.top + 1, rcRect.left + m_nNameColumn, rcRect.bottom);
::TextOut(hDC, rcRect.left + ELEMENT_TEXT_LEFT_INDENT, rcRect.top + ELEMENT_TEXT_TOP_INDENT,
strN, strN.GetLength());
::SelectObject(hDC, hBrushOld);
::SelectObject(hDC, hPenOld);
::SetTextColor(hDC, crTextOld);
::DeleteObject(hBrush);
::DeleteObject(hPen);
} // else
{
HBRUSH hBrush, hBrushOld;
HPEN hPen, hPenOld;
hBrush = ::CreateSolidBrush(m_crElement);
hBrushOld = (HBRUSH)::SelectObject(hDC, hBrush);
hPen = ::CreatePen(PS_SOLID, 1, m_crElement);
hPenOld = (HPEN)::SelectObject(hDC, hPen);
::Rectangle(hDC, rcRect.left + m_nNameColumn, rcRect.top + 1, rcRect.right, rcRect.bottom);
::SelectObject(hDC, hBrushOld);
::SelectObject(hDC, hPenOld);
::DeleteObject(hBrush);
::DeleteObject(hPen);
}
// And this applies only to values
if(bChanged)
{
HFONT hFontOld = (HFONT)::SelectObject(hDC, m_hBold);
::TextOut(hDC, rcRect.left + m_nNameColumn + VALUE_TEXT_LEFT_INDENT, rcRect.top + VALUE_TEXT_TOP_INDENT,
strV, strV.GetLength());
::SelectObject(hDC, hFontOld);
} // if
else
{
::TextOut(hDC, rcRect.left + m_nNameColumn + VALUE_TEXT_LEFT_INDENT, rcRect.top + VALUE_TEXT_TOP_INDENT,
strV, strV.GetLength());
} // else
::SelectObject(hDC, hBrushOld);
::SelectObject(hDC, hPenOld);
::DeleteObject(hPen);
DrawElementGrid(hDC, rcRect);
}
void CPropertyGrid::DrawElementGrid(HDC hDC, const CRect &rcRect)
{
HPEN hPen = ::CreatePen(PS_SOLID, 1, m_crCategory);
HPEN hPenOld = (HPEN)::SelectObject(hDC, hPen);
// Horizontal
::MoveToEx(hDC, rcRect.left, rcRect.bottom, 0);
::LineTo(hDC, rcRect.right, rcRect.bottom);
// Vertical
::MoveToEx(hDC, rcRect.left + m_nNameColumn, rcRect.top, 0);
::LineTo(hDC, rcRect.left + m_nNameColumn, rcRect.bottom);
::SelectObject(hDC, hPenOld);
::DeleteObject(hPen);
}
BOOL CPropertyGrid::LoadPropertyGridContents(MSXML2::IXMLDOMElementPtr pXML)
{
if(_bstr_t(_T("propertyGrid")) != pXML->nodeName)
return FALSE;
// Iterate through all children
MSXML2::IXMLDOMNodeListPtr pList;
MSXML2::IXMLDOMNode *pNode;
if(0 == (pList = pXML->childNodes))
return FALSE;
long lNodes;
lNodes = pList->length;
for(long l = 0; l < lNodes; ++l)
{
if(FAILED(pList->get_item(l, &pNode)))
return FALSE;
LoadCategory(pNode);
pNode->Release();
} // for
SetItemCount(GetExpandedCount());
return TRUE;
}
void CPropertyGrid::LoadCategory(MSXML2::IXMLDOMNodePtr pNode)
{
if(_bstr_t(_T("category")) != pNode->nodeName)
return;
MSXML2::IXMLDOMNamedNodeMapPtr pMap;
MSXML2::IXMLDOMNodePtr pAttribute;
CATEGORY ctg;
if(0 == (pMap = pNode->attributes))
return;
LoadCategory(pMap, ctg);
MSXML2::IXMLDOMNodeListPtr pNodes;
if(0 == (pNodes = pNode->childNodes))
return;
LoadElements(pNodes, ctg);
m_vCategories.push_back(ctg);
}
void CPropertyGrid::LoadCategory(MSXML2::IXMLDOMNamedNodeMapPtr pMap, CATEGORY &ctg)
{
// General stuff
ctg.lFlags = CF_EXPANDED;
ctg.lID = 0;
MSXML2::IXMLDOMNodePtr pAttribute;
// Category name
if(0 == (pAttribute = pMap->getNamedItem(_T("categoryName"))))
return;
_variant_t vt;
if(FAILED(pAttribute->get_nodeValue(&vt)))
return;
ctg.strName = (LPCTSTR)_bstr_t(vt);
// Category description
if(0 == (pAttribute = pMap->getNamedItem(_T("categoryDescription"))))
return;
if(FAILED(pAttribute->get_nodeValue(&vt)))
return;
ctg.strTip = (LPCTSTR)_bstr_t(vt);
}
void CPropertyGrid::LoadElements(MSXML2::IXMLDOMNodeListPtr pNodes, CATEGORY &ctg)
{
ELEMENT elm;
MSXML2::IXMLDOMNode *pNode;
long lNodes;
lNodes = pNodes->length;
for(long l = 0; l < lNodes; ++l)
{
if(FAILED(pNodes->get_item(l, &pNode)))
return;
if(LoadElement(pNode, elm))
ctg.lElements.push_back(elm);
pNode->Release();
} // for
}
BOOL CPropertyGrid::LoadElement(MSXML2::IXMLDOMNodePtr pNode, ELEMENT &elm)
{
if(_bstr_t(_T("element")) != pNode->nodeName)
return FALSE;
MSXML2::IXMLDOMNodePtr pAttribute;
MSXML2::IXMLDOMNamedNodeMapPtr pMap;
if(0 == (pMap = pNode->attributes))
return FALSE;
// Element name
if(0 == (pAttribute = pMap->getNamedItem(_T("elementName"))))
return FALSE;
_variant_t vt;
if(FAILED(pAttribute->get_nodeValue(&vt)))
return FALSE;
elm.strName = (LPCTSTR)_bstr_t(vt);
// Element description
if(0 == (pAttribute = pMap->getNamedItem(_T("elementDescription"))))
return FALSE;
if(FAILED(pAttribute->get_nodeValue(&vt)))
return FALSE;
elm.strTip = (LPCTSTR)_bstr_t(vt);
// Element type
if(0 == (pAttribute = pMap->getNamedItem(_T("elementType"))))
return FALSE;
if(FAILED(pAttribute->get_nodeValue(&vt)))
return FALSE;
CString str = (LPCTSTR)_bstr_t(vt);
if(_T("EditBox") == str)
{
elm.lFlags = EF_EDITBOX;
} // if
else if(_T("ComboBox") == str)
{
elm.lFlags = EF_COMBOBOX;
if(!LoadComboBoxEntries(pNode->childNodes, elm))
return FALSE;
} // else if
else if(_T("ReadOnly") == str)
{
elm.lFlags = EF_READONLY;
} // else if
else if(_T("HotKey") == str)
{
elm.lFlags = EF_HOTKEY;
} // else if
else
return FALSE;
// Element ID
if(0 == (pAttribute = pMap->getNamedItem(_T("elementID"))))
return FALSE;
if(FAILED(pAttribute->get_nodeValue(&vt)))
return FALSE;
elm.lID = (long)vt;
return TRUE;
}
CString CPropertyGrid::GetElementValue(long lID)
{
VCATEGORY::iterator vci;
LELEMENT::iterator lei;
for(vci = m_vCategories.begin(); m_vCategories.end() != vci; ++vci)
{
for(lei = vci->lElements.begin(); vci->lElements.end() != lei; ++lei)
{
if(lID == lei->lID)
return lei->strValue;
} // for
} // for
return _T("");
}
BOOL CPropertyGrid::SetEditBoxLong(long lID, long lValue)
{
VCATEGORY::iterator vci;
LELEMENT::iterator lei;
CString strText;
strText.Format("%d", lValue);
for(vci = m_vCategories.begin(); m_vCategories.end() != vci; ++vci)
{
for(lei = vci->lElements.begin(); vci->lElements.end() != lei; ++lei)
{
if((lID == lei->lID) && (lei->lFlags & EF_EDITBOX))
{
lei->strValue = lei->strInitialValue = strText;
return TRUE;
} // if
} // for
} // for
return FALSE;
}
long CPropertyGrid::GetEditBoxLong(long lID)
{
VCATEGORY::iterator vci;
LELEMENT::iterator lei;
for(vci = m_vCategories.begin(); m_vCategories.end() != vci; ++vci)
{
for(lei = vci->lElements.begin(); vci->lElements.end() != lei; ++lei)
{
if((lID == lei->lID) && (lei->lFlags & EF_EDITBOX))
{
CString strValue = lei->strValue;
return _ttol(strValue);
} // if
} // for
} // for
return -1;
}
long CPropertyGrid::GetComboBoxItemData(long lID)
{
VCATEGORY::iterator vci;
LELEMENT::iterator lei;
for(vci = m_vCategories.begin(); m_vCategories.end() != vci; ++vci)
{
for(lei = vci->lElements.begin(); vci->lElements.end() != lei; ++lei)
{
if((lID == lei->lID) && (lei->lFlags & EF_COMBOBOX))
return lei->lValue;
} // for
} // for
return -1;
}
BOOL CPropertyGrid::SetElementValue(long lID, const CString &strValue)
{
VCATEGORY::iterator vci;
LELEMENT::iterator lei;
for(vci = m_vCategories.begin(); m_vCategories.end() != vci; ++vci)
{
for(lei = vci->lElements.begin(); vci->lElements.end() != lei; ++lei)
{
if(lID == lei->lID)
{
lei->strValue = lei->strInitialValue = strValue;
return TRUE;
}
} // for
} // for
return FALSE;
}
BOOL CPropertyGrid::SetComboBoxValue(long lID, long lValue)
{
VCATEGORY::iterator vci;
LELEMENT::iterator lei;
for(vci = m_vCategories.begin(); m_vCategories.end() != vci; ++vci)
{
for(lei = vci->lElements.begin(); vci->lElements.end() != lei; ++lei)
{
if((lID == lei->lID) && (lei->lFlags & EF_COMBOBOX))
{
lei->strValue = lei->strInitialValue = FindComboBoxString(*lei, lValue);
lei->lValue = lValue;
return TRUE;
}
} // for
} // for
return FALSE;
}
BOOL CPropertyGrid::SetHotKey(long lID, WORD wHotKey)
{
VCATEGORY::iterator vci;
LELEMENT::iterator lei;
for(vci = m_vCategories.begin(); m_vCategories.end() != vci; ++vci)
{
for(lei = vci->lElements.begin(); vci->lElements.end() != lei; ++lei)
{
if((lID == lei->lID) && (lei->lFlags & EF_HOTKEY))
{
lei->wHotKey = wHotKey;
lei->strValue = lei->strInitialValue = GetModifiersString(MapModifiers(HIBYTE(wHotKey))) +
TCHAR(LOBYTE(wHotKey));
return TRUE;
}
} // for
} // for
return FALSE;
}
WORD CPropertyGrid::GetHotKey(long lID)
{
VCATEGORY::iterator vci;
LELEMENT::iterator lei;
for(vci = m_vCategories.begin(); m_vCategories.end() != vci; ++vci)
{
for(lei = vci->lElements.begin(); vci->lElements.end() != lei; ++lei)
{
if((lID == lei->lID) && (lei->lFlags & EF_HOTKEY))
return lei->wHotKey;
} // for
} // for
return 0;
}
BOOL CPropertyGrid::LoadComboBoxEntries(MSXML2::IXMLDOMNodeListPtr pNodes, ELEMENT &elm)
{
MSXML2::IXMLDOMNode *pNode;
long lNodes = pNodes->length;
elm.vComboBox.clear();
for(long l = 0; l < lNodes; ++l)
{
if(FAILED(pNodes->get_item(l, &pNode)))
return FALSE;
if(!LoadComboBoxString(pNode, elm))
return FALSE;
pNode->Release();
} // for
return TRUE;
}
BOOL CPropertyGrid::LoadComboBoxString(MSXML2::IXMLDOMNodePtr pNode, ELEMENT &elm)
{
if(_bstr_t(_T("comboBox")) != pNode->nodeName)
return FALSE;
MSXML2::IXMLDOMNodePtr pAttribute;
MSXML2::IXMLDOMNamedNodeMapPtr pMap;
if(0 == (pMap = pNode->attributes))
return FALSE;
COMBOBOXDATA cbd;
// Combo Box String
if(0 == (pAttribute = pMap->getNamedItem(_T("comboBoxEntry"))))
return FALSE;
_variant_t vt;
if(FAILED(pAttribute->get_nodeValue(&vt)))
return FALSE;
cbd.strData = (LPCTSTR)_bstr_t(vt);
// Combo Box Item Data
if(0 == (pAttribute = pMap->getNamedItem(_T("comboBoxItemData"))))
return FALSE;
if(FAILED(pAttribute->get_nodeValue(&vt)))
return FALSE;
cbd.lItemData = (long)vt;
elm.vComboBox.push_back(cbd);
return TRUE;
}
void CPropertyGrid::InitComboBox(ELEMENT &elm)
{
m_ctrlCombo.ResetContent();
VCOMBOBOXDATA::iterator vcsi;
int n = 0;
for(vcsi = elm.vComboBox.begin(); elm.vComboBox.end() != vcsi; ++vcsi, ++n)
{
m_ctrlCombo.AddString(vcsi->strData);
m_ctrlCombo.SetItemData(n, vcsi->lItemData);
} // for
}
void CPropertyGrid::DrawFocusRect(HDC hDC, const CRect &rcRect, const CString &strText)
{
SIZE szText;
::GetTextExtentPoint32(hDC, strText, strText.GetLength(), &szText);
TRACE(" Drawing %d\n", szText.cx);
//
// Adding a few pixels to the left and to the right
//
RECT rcFocus = { CATEGORY_TEXT_LEFT_INDENT - CATEGORY_FOCUS_RECT_LEFT_INDENT,
rcRect.top + CATEGORY_FOCUS_RECT_TOP_SPACING,
szText.cx + CATEGORY_TEXT_LEFT_INDENT + CATEGORY_FOCUS_RECT_RIGHT_INDENT,
rcRect.bottom - CATEGORY_FOCUS_RECT_BOTTOM_SPACING };
::DrawFocusRect(hDC, &rcFocus);
}
BOOL CPropertyGrid::LoadPropertyGridTheme(MSXML2::IXMLDOMElementPtr pXML)
{
if(_bstr_t(_T("propertyGridTheme")) != pXML->nodeName)
return FALSE;
// Iterate through all children
MSXML2::IXMLDOMNodeListPtr pList;
MSXML2::IXMLDOMNode *pNode;
if(0 == (pList = pXML->childNodes))
return FALSE;
long lNodes;
lNodes = pList->length;
for(long l = 0; l < lNodes; ++l)
{
if(FAILED(pList->get_item(l, &pNode)))
return FALSE;
LoadThemeElement(pNode);
pNode->Release();
} // for
UninitializeGdiObjects();
InitializeGdiObjects();
Invalidate();
return TRUE;
}
void CPropertyGrid::LoadThemeElement(MSXML2::IXMLDOMNodePtr pNode)
{
if(_bstr_t(_T("categorySection")) == pNode->nodeName) // Category Section
m_crCategory = LoadThemeColor(pNode);
else if(_bstr_t(_T("categoryFont")) == pNode->nodeName) // Category Font
m_crCategoryFont = LoadThemeColor(pNode);
else if(_bstr_t(_T("categoryTopBorder")) == pNode->nodeName) // Category Top Border
m_crCategoryTopBorder = LoadThemeColor(pNode);
else if(_bstr_t(_T("subcategorySection")) == pNode->nodeName) // Subcategory Section
m_crSubcategory = LoadThemeColor(pNode);
else if(_bstr_t(_T("subcategoryFont")) == pNode->nodeName) // Subcategory Font
m_crSubcategoryFont = LoadThemeColor(pNode);
else if(_bstr_t(_T("elementSection")) == pNode->nodeName) // Element Section
m_crElement = LoadThemeColor(pNode);
else if(_bstr_t(_T("elementFont")) == pNode->nodeName) // Element Font
m_crElementFont = LoadThemeColor(pNode);
else if(_bstr_t(_T("elementSectionSelected")) == pNode->nodeName) // Element Section Selected
m_crElementSelected = LoadThemeColor(pNode);
else if(_bstr_t(_T("elementFontSelected")) == pNode->nodeName) // Element Font Selected
m_crElementSelectedFont = LoadThemeColor(pNode);
}
COLORREF CPropertyGrid::LoadThemeColor(MSXML2::IXMLDOMNodePtr pNode)
{
BYTE nRed = 0, nGreen = 0, nBlue = 0;
//
// Loading RGB color
//
MSXML2::IXMLDOMNodePtr pAttribute;
MSXML2::IXMLDOMNamedNodeMapPtr pMap;
if(0 == (pMap = pNode->attributes))
return RGB(nRed, nGreen, nBlue);
nRed = LoadThemeColorComponent(pMap, _T("colorRed"));
nGreen = LoadThemeColorComponent(pMap, _T("colorGreen"));
nBlue = LoadThemeColorComponent(pMap, _T("colorBlue"));
return RGB(nRed, nGreen, nBlue);
}
BYTE CPropertyGrid::LoadThemeColorComponent(MSXML2::IXMLDOMNamedNodeMapPtr pMap, LPCTSTR pszAttribute)
{
//
// Loading Color component
//
MSXML2::IXMLDOMNodePtr pAttribute;
if(0 == (pAttribute = pMap->getNamedItem(pszAttribute)))
return 0;
_variant_t vt;
if(FAILED(pAttribute->get_nodeValue(&vt)))
return 0;
return (BYTE)(long)vt;
}
void CPropertyGrid::InitializeGdiObjects()
{
// Brushes
m_brCategory = ::CreateSolidBrush(m_crCategory);
m_brSubcategory = ::CreateSolidBrush(m_crSubcategory);
m_brElement = ::CreateSolidBrush(m_crElement);
// Pens
m_pnDot = ::CreatePen(PS_DOT, 1, RGB(0, 0, 0));
m_pnLines = ::CreatePen(PS_SOLID, 2, m_crCategory);
}
void CPropertyGrid::UninitializeGdiObjects()
{
// Brushes
::DeleteObject(m_brCategory);
::DeleteObject(m_brElement);
::DeleteObject(m_brSubcategory);
// Pens
::DeleteObject(m_pnDot);
::DeleteObject(m_pnLines);
}
void CPropertyGrid::MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct)
{
TRACE("%d %d %d %d\n", lpMeasureItemStruct->CtlType, ODT_COMBOBOX, ODT_LISTBOX, ODT_MENU);
//lpMeasureItemStruct->itemHeight = 40;
}
CString CPropertyGrid::FindComboBoxString(ELEMENT &elm, long lValue)
{
VCOMBOBOXDATA::iterator vcbi = elm.vComboBox.begin();
for( ; elm.vComboBox.end() != vcbi; ++vcbi)
{
if(lValue == vcbi->lItemData)
return vcbi->strData;
} // for
return _T("");
}
BOOL CPropertyGrid::PreTranslateMessage(MSG* pMsg)
{
TRACE("Message %d\n", pMsg->message);
if(WM_KEYUP == pMsg->message)
{
VCATEGORY::iterator vci;
LELEMENT::iterator lei;
if((VK_UP == pMsg->wParam) || (VK_DOWN == pMsg->wParam))
{
int nItem = GetNextItem(-1, LVNI_SELECTED);
if(-1 != nItem)
{
if(!GetItem(nItem, vci, lei)) // Category
{
if(m_pNotificationReciever)
{
if(m_pNotificationReciever->Ready())
m_pNotificationReciever->CategorySelected(vci->strName, vci->strTip);
} // if
} // if
else // Element
{
if(m_pNotificationReciever)
{
if(m_pNotificationReciever->Ready())
m_pNotificationReciever->ElementSelected(lei->strName, lei->strTip);
} // if
} // else
} // if
return 1;
} // if
} // if
return CListCtrl::PreTranslateMessage(pMsg);
}