LEGOIslandRebuilder/ext/PropertyGrid/PropertyGrid.cpp

2349 lines
59 KiB
C++
Raw Normal View History

2022-03-31 14:38:16 -04:00
// PropertyGrid.cpp : implementation file
//
#include "stdafx.h"
#include "CustomItem.h"
#include "PropertyGrid.h"
#include "PropertyGridDirectoryPicker.h"
#include "PropertyGridMonthCalCtrl.h"
#include "DynDialogEx.h"
#include <algorithm>
#include <shlwapi.h>
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
#define IDC_MONTHCAL 1023
// CPropertyGrid
static const int margin = 2;
IMPLEMENT_DYNAMIC(CPropertyGrid, CWnd)
CPropertyGrid::CPropertyGrid()
{
m_section_id = 0;
m_item_id = 0;
m_resizing_gutter = false;
m_button_pushed = false;
m_button_depressed = false;
m_value_clicked = false;
m_custom_tracking = false;
m_scroll_enabled = false;
m_draw_lines = true;
m_shade_titles = true;
m_draw_gutter = true;
m_focus_disabled = true;
m_bold_modified = false;
m_bold_editables = false;
m_display_mode = DM_CATEGORIZED;
m_control = NULL;
m_rect_button = CRect(0,0,0,0);
m_ptLast = CPoint(0,0);
m_strTrue = "True";
m_strFalse = "False";
m_strDate = "Date";
m_strTime = "Time";
m_strUndefined = "";
m_strEmpty = "";
m_clrBack = GetSysColor(COLOR_WINDOW);
m_clrShade = GetSysColor(COLOR_3DFACE);
m_clrText = GetSysColor(COLOR_WINDOWTEXT);
m_clrTitle = GetSysColor(COLOR_WINDOWTEXT);
m_clrFocus = GetSysColor(COLOR_HIGHLIGHT);
m_clrHilite = GetSysColor(COLOR_HIGHLIGHTTEXT);
m_clrEditable = GetSysColor(COLOR_WINDOWTEXT);
m_clrDisabled = GetSysColor(COLOR_GRAYTEXT);
m_focused_section = -1;
m_focused_item = -1;
}
CPropertyGrid::~CPropertyGrid()
{
}
//
// customization
//
bool CPropertyGrid::GetShadeTitles()
{
return m_shade_titles;
}
void CPropertyGrid::SetShadeTitles(bool shade_titles)
{
m_shade_titles = shade_titles;
if (GetSafeHwnd())
Invalidate();
}
bool CPropertyGrid::GetDrawLines()
{
return m_draw_lines;
}
void CPropertyGrid::SetDrawLines(bool draw_lines)
{
m_draw_lines = draw_lines;
if (GetSafeHwnd())
Invalidate();
}
bool CPropertyGrid::GetDrawGutter()
{
return m_draw_gutter;
}
void CPropertyGrid::SetDrawGutter(bool draw_gutter)
{
m_draw_gutter = draw_gutter;
if (GetSafeHwnd())
Invalidate();
}
bool CPropertyGrid::GetFocusDisabled()
{
return m_focus_disabled;
}
void CPropertyGrid::SetFocusDisabled(bool focus_disabled)
{
m_focus_disabled = focus_disabled;
if (GetSafeHwnd())
Invalidate();
}
bool CPropertyGrid::GetBoldModified()
{
return m_bold_modified;
}
void CPropertyGrid::SetBoldModified(bool bold_modified)
{
m_bold_modified = bold_modified;
}
bool CPropertyGrid::GetBoldEditables()
{
return m_bold_editables;
}
void CPropertyGrid::SetBoldEditables(bool bold_editables)
{
m_bold_editables = bold_editables;
}
//
// gutter width
//
int CPropertyGrid::GetGutterWidth()
{
return m_gutter_width;
}
void CPropertyGrid::SetGutterWidth(int gutter_width)
{
m_gutter_width = gutter_width;
if (GetSafeHwnd())
Invalidate();
}
//
// custom colors
//
void CPropertyGrid::SetTextColor(COLORREF clrText)
{
if (m_clrText == m_clrEditable)
m_clrEditable = clrText;
m_clrText = clrText;
if (GetSafeHwnd())
Invalidate();
}
void CPropertyGrid::SetTitleColor(COLORREF clrTitle)
{
m_clrTitle = clrTitle;
if (GetSafeHwnd())
Invalidate();
}
void CPropertyGrid::SetBackColor(COLORREF clrBack)
{
m_clrBack = clrBack;
if (GetSafeHwnd())
Invalidate();
}
void CPropertyGrid::SetShadeColor(COLORREF clrShade)
{
m_clrShade = clrShade;
if (GetSafeHwnd())
Invalidate();
}
void CPropertyGrid::SetFocusColor(COLORREF clrFocus)
{
m_clrFocus = clrFocus;
if (GetSafeHwnd())
Invalidate();
}
void CPropertyGrid::SetHiliteColor(COLORREF clrHilite)
{
m_clrHilite = clrHilite;
if (GetSafeHwnd())
Invalidate();
}
void CPropertyGrid::SetEditableColor(COLORREF clrEditable)
{
m_clrEditable = clrEditable;
if (GetSafeHwnd())
Invalidate();
}
void CPropertyGrid::SetDisabledColor(COLORREF clrDisabled)
{
m_clrDisabled = clrDisabled;
if (GetSafeHwnd())
Invalidate();
}
//
// localization
//
void CPropertyGrid::SetTrueFalseStrings(string strTrue, string strFalse)
{
m_strTrue = strTrue;
m_strFalse = strFalse;
}
void CPropertyGrid::SetOkCancelStrings(string strOk, string strCancel)
{
m_strOk = strOk;
m_strCancel = strCancel;
}
void CPropertyGrid::SetDateTimeStrings(string strDate, string strTime)
{
m_strDate = strDate;
m_strTime = strTime;
}
void CPropertyGrid::SetUndefinedString(string strUndefined)
{
m_strUndefined = strUndefined;
}
void CPropertyGrid::SetEmptyString(string strEmpty)
{
m_strEmpty = strEmpty;
}
//
// appearance
//
void CPropertyGrid::SetDisplayMode(EDisplayMode display_mode)
{
m_display_mode = display_mode;
RecalcLayout();
}
void CPropertyGrid::ExpandAll(bool expand)
{
for (vector<CSection>::iterator it = m_sections.begin(); it != m_sections.end(); ++it)
it->m_collapsed = !expand;
RecalcLayout();
}
void CPropertyGrid::ExpandSection(HSECTION hs, bool expand)
{
CSection* pSection = FindSection(hs);
if (pSection)
{
pSection->m_collapsed = !expand;
RecalcLayout();
}
}
bool CPropertyGrid::IsSectionCollapsed(HSECTION hs)
{
CSection* pSection = FindSection(hs);
if (pSection)
return pSection->m_collapsed;
return false;
}
string CPropertyGrid::GetItemText(HITEM hItem)
{
CItem *item = FindItem(hItem);
if (item) {
return item->m_name;
}
return string();
}
//
// item management
//
bool CPropertyGrid::CItem::operator==(const HITEM& item) const
{
return m_id == item;
}
bool CPropertyGrid::CItem::operator==(const string& name) const
{
return m_name == name;
}
bool CPropertyGrid::CSection::operator==(const HSECTION& section) const
{
return m_id == section;
}
void CPropertyGrid::CItem::ValidateChanges()
{
// save the values
m_undefined_old = m_undefined;
m_nValue_old = m_nValue;
m_dValue_old = m_dValue;
m_strValue_old = m_strValue;
m_bValue_old = m_bValue;
m_dtValue_old = m_dtValue;
m_clrValue_old = m_clrValue;
memcpy(&m_lfValue_old, &m_lfValue, sizeof(LOGFONT));
// callback for custom
if (m_type == IT_CUSTOM)
m_pCustom->ValidateChanges();
}
HSECTION CPropertyGrid::AddSection(string title, bool collapsed, HSECTION after)
{
// build it
CSection section;
section.m_id = m_section_id;
section.m_title = title;
section.m_collapsed = collapsed;
// insert it
// if after does not exist then it is appended
vector<CSection>::iterator it = find(m_sections.begin(), m_sections.end(), after);
m_sections.insert(it, section);
// done
RecalcLayout();
return m_section_id++;
}
HITEM CPropertyGrid::AddItem(HSECTION hs, EItemType type, string name, void* pValue, bool editable, bool undefined, HITEM after)
{
// check section exists
vector<CSection>::iterator it = find(m_sections.begin(), m_sections.end(), hs);
if (it == m_sections.end())
return -1;
// check item does not already exists
vector<CItem>::iterator it2 = find(it->m_items.begin(), it->m_items.end(), name);
if (it2 != it->m_items.end())
return -1;
// build the item
CItem item;
item.m_id = m_item_id++;
item.m_type = type;
item.m_name = name;
item.m_editable = editable;
item.m_undefined = undefined;
// assign the value
if (type == IT_CUSTOM) item.m_pCustom = (ICustomItem*)pValue;
else if (type == IT_STRING || type == IT_TEXT || type == IT_FILE || type == IT_FOLDER) item.m_strValue = *(string*)pValue;
else if (type == IT_COMBO || type == IT_INTEGER) item.m_nValue = *(int*)pValue;
else if (type == IT_DOUBLE) item.m_dValue = *(double*)pValue;
else if (type == IT_BOOLEAN) item.m_bValue = *(bool*)pValue;
else if (type == IT_DATE || type == IT_DATETIME) item.m_dtValue = *(COleDateTime*)pValue;
else if (type == IT_COLOR) item.m_clrValue = *(COLORREF*)pValue;
else if (type == IT_FONT) memcpy(&item.m_lfValue, pValue, sizeof(LOGFONT));
else assert(false);
// finish and add
item.ValidateChanges();
it->m_items.push_back(item);
RecalcLayout();
return item.m_id;
}
HITEM CPropertyGrid::AddCustomItem(HSECTION section, string name, ICustomItem* pItem, bool editable, HITEM after)
{
pItem->m_pGrid = this;
return AddItem(section, IT_CUSTOM, name, pItem, editable, false, after);
}
HITEM CPropertyGrid::AddStringItem(HSECTION section, string name, string value, bool editable, HITEM after)
{
return AddItem(section, IT_STRING, name, &value, editable, false, after);
}
HITEM CPropertyGrid::AddTextItem(HSECTION section, string name, string value, bool editable, HITEM after)
{
return AddItem(section, IT_TEXT, name, &value, editable, false, after);
}
HITEM CPropertyGrid::AddIntegerItem(HSECTION section, string name, int value, string format, bool editable, bool undefined, HITEM after)
{
HITEM it = AddItem(section, IT_INTEGER, name, &value, editable, undefined, after);
CItem* pItem = FindItem(it);
if (pItem) pItem->m_options.push_back(format);
return it;
}
HITEM CPropertyGrid::AddDoubleItem(HSECTION section, string name, double value, string format, bool editable, bool undefined, HITEM after)
{
HITEM it = AddItem(section, IT_DOUBLE, name, &value, editable, undefined, after);
CItem* pItem = FindItem(it);
if (pItem) pItem->m_options.push_back(format);
return it;
}
HITEM CPropertyGrid::AddComboItem(HSECTION section, string name, const vector<string>& values, int cur, bool editable, bool undefined, HITEM after)
{
HITEM it = AddItem(section, IT_COMBO, name, &cur, editable, undefined, after);
CItem* pItem = FindItem(it);
if (pItem) pItem->m_options = values;
return it;
}
HITEM CPropertyGrid::AddBoolItem(HSECTION section, string name, bool value, bool editable, bool undefined, HITEM after)
{
return AddItem(section, IT_BOOLEAN, name, &value, editable, undefined, after);
}
HITEM CPropertyGrid::AddDateItem(HSECTION section, string name, COleDateTime value, string format, bool editable, bool undefined, HITEM after)
{
HITEM it = AddItem(section, IT_DATE, name, &value, editable, undefined, after);
CItem* pItem = FindItem(it);
if (pItem) pItem->m_options.push_back(format);
return it;
}
HITEM CPropertyGrid::AddDateTimeItem(HSECTION section, string name, COleDateTime value, string format, bool editable, bool undefined, HITEM after)
{
HITEM it = AddItem(section, IT_DATETIME, name, &value, editable, undefined, after);
CItem* pItem = FindItem(it);
if (pItem) pItem->m_options.push_back(format);
return it;
}
HITEM CPropertyGrid::AddFileItem(HSECTION section, string name, string value, string filter, bool editable, HITEM after)
{
HITEM it = AddItem(section, IT_FILE, name, &value, editable, false, after);
CItem* pItem = FindItem(it);
if (pItem) pItem->m_options.push_back(filter);
return it;
}
HITEM CPropertyGrid::AddFolderItem(HSECTION section, string name, string value, string title, bool editable, HITEM after)
{
HITEM it = AddItem(section, IT_FOLDER, name, &value, editable, false, after);
CItem* pItem = FindItem(it);
if (pItem) pItem->m_options.push_back(title);
return it;
}
HITEM CPropertyGrid::AddColorItem(HSECTION section, string name, COLORREF value, bool editable, bool undefined, HITEM after)
{
return AddItem(section, IT_COLOR, name, &value, editable, undefined, after);
}
HITEM CPropertyGrid::AddFontItem(HSECTION section, string name, LOGFONT value, bool editable, bool undefined, HITEM after)
{
return AddItem(section, IT_FONT, name, &value, editable, undefined, after);
}
void CPropertyGrid::ResetContents()
{
m_sections.clear();
m_section_id = 0;
m_item_id = 0;
RecalcLayout();
}
bool CPropertyGrid::RemoveSection(HSECTION hs)
{
vector<CSection>::iterator it = find(m_sections.begin(), m_sections.end(), hs);
if (it == m_sections.end()) return false;
m_sections.erase(it);
return true;
}
bool CPropertyGrid::RemoveItem(HITEM item)
{
for (vector<CSection>::iterator it = m_sections.begin(); it != m_sections.end(); ++it)
{
vector<CItem>::iterator it2 = find(it->m_items.begin(), it->m_items.end(), item);
if (it2 != it->m_items.end())
{
it->m_items.erase(it2);
return true;
}
}
return false;
}
int CPropertyGrid::GetNumSections()
{
return int(m_sections.size());
}
int CPropertyGrid::GetSectionSize(HSECTION hs)
{
CSection* pSection = FindSection(hs);
if (pSection) return int(pSection->m_items.size());
return 0;
}
void CPropertyGrid::ValidateChanges()
{
for (vector<CSection>::iterator it = m_sections.begin(); it != m_sections.end(); ++it)
{
for (vector<CItem>::iterator it2 = it->m_items.begin(); it2 != it->m_items.end(); ++it2)
it2->ValidateChanges();
}
}
CPropertyGrid::CSection* CPropertyGrid::FindSection(HSECTION hs) const
{
vector<CSection>::const_iterator it = find(m_sections.begin(), m_sections.end(), hs);
if (it == m_sections.end()) return NULL;
return const_cast<CSection*>(&(*it));
}
CPropertyGrid::CItem* CPropertyGrid::FindItem(HITEM hi) const
{
for (vector<CSection>::const_iterator it = m_sections.begin(); it != m_sections.end(); ++it)
{
vector<CItem>::const_iterator it2 = find(it->m_items.begin(), it->m_items.end(), hi);
if (it2 != it->m_items.end())
return const_cast<CItem*>(&(*it2));
}
return NULL;
}
bool CPropertyGrid::GetItemValue(HITEM item, string& strValue) const
{
// get the item
CItem* pItem = FindItem(item);
if (pItem == NULL) return false;
if (pItem->m_undefined) return false;
// check
if (pItem->m_type == IT_STRING || pItem->m_type == IT_TEXT || pItem->m_type == IT_FILE || pItem->m_type == IT_FOLDER)
{
strValue = pItem->m_strValue;
return true;
}
else if (pItem->m_type == IT_COMBO)
{
if (pItem->m_nValue < 0 || pItem->m_nValue > int(pItem->m_options.size())-1) return false;
strValue = pItem->m_options[pItem->m_nValue];
return true;
}
return false;
}
bool CPropertyGrid::GetItemValue(HITEM item, int& nValue) const
{
// get the item
CItem* pItem = FindItem(item);
if (pItem == NULL) return false;
if (pItem->m_undefined) return false;
// check
if (pItem->m_type == IT_COMBO || pItem->m_type == IT_INTEGER)
{
nValue = pItem->m_nValue;
return true;
}
return false;
}
bool CPropertyGrid::GetItemValue(HITEM item, double& dValue) const
{
// get the item
CItem* pItem = FindItem(item);
if (pItem == NULL) return false;
if (pItem->m_undefined) return false;
// check
if (pItem->m_type == IT_DOUBLE)
{
dValue = pItem->m_dValue;
return true;
}
return false;
}
bool CPropertyGrid::GetItemValue(HITEM item, bool& bValue) const
{
// get the item
CItem* pItem = FindItem(item);
if (pItem == NULL) return false;
if (pItem->m_undefined) return false;
// check
if (pItem->m_type == IT_BOOLEAN)
{
bValue = pItem->m_bValue;
return true;
}
return false;
}
bool CPropertyGrid::GetItemValue(HITEM item, COleDateTime& dtValue) const
{
// get the item
CItem* pItem = FindItem(item);
if (pItem == NULL) return false;
if (pItem->m_undefined) return false;
// check
if (pItem->m_type == IT_DATE || pItem->m_type == IT_DATETIME)
{
dtValue = pItem->m_dtValue;
return true;
}
return false;
}
bool CPropertyGrid::GetItemValue(HITEM item, COLORREF& clrValue) const
{
// get the item
CItem* pItem = FindItem(item);
if (pItem == NULL) return false;
if (pItem->m_undefined) return false;
// check
if (pItem->m_type == IT_COLOR)
{
clrValue = pItem->m_clrValue;
return true;
}
return false;
}
bool CPropertyGrid::GetItemValue(HITEM item, LOGFONT& lfValue) const
{
// get the item
CItem* pItem = FindItem(item);
if (pItem == NULL) return false;
if (pItem->m_undefined) return false;
// check
if (pItem->m_type == IT_FONT)
{
lfValue = pItem->m_lfValue;
return true;
}
return false;
}
bool CPropertyGrid::SetItemValue(HITEM item, const string strValue)
{
// get the item
CItem* pItem = FindItem(item);
if (pItem == NULL) return false;
// check
if (pItem->m_type == IT_STRING || pItem->m_type == IT_TEXT || pItem->m_type == IT_FILE || pItem->m_type == IT_FOLDER)
{
pItem->m_strValue = strValue;
pItem->m_undefined = false;
Invalidate();
return true;
}
else if (pItem->m_type == IT_COMBO)
{
for (size_t i=0; i<pItem->m_options.size(); i++) {
if (pItem->m_options.at(i) == strValue) {
return SetItemValue(item, (int) i);
}
}
}
return false;
}
bool CPropertyGrid::SetItemValue(HITEM item, const int nValue)
{
// get the item
CItem* pItem = FindItem(item);
if (pItem == NULL) return false;
// check
if (pItem->m_type == IT_COMBO || pItem->m_type == IT_INTEGER)
{
pItem->m_nValue = nValue;
pItem->m_undefined = false;
Invalidate();
return true;
}
return false;
}
bool CPropertyGrid::SetItemValue(HITEM item, const double dValue)
{
// get the item
CItem* pItem = FindItem(item);
if (pItem == NULL) return false;
// check
if (pItem->m_type == IT_DOUBLE)
{
pItem->m_dValue = dValue;
pItem->m_undefined = false;
Invalidate();
return true;
}
return false;
}
bool CPropertyGrid::SetItemValue(HITEM item, const bool bValue)
{
// get the item
CItem* pItem = FindItem(item);
if (pItem == NULL) return false;
// check
if (pItem->m_type == IT_BOOLEAN)
{
pItem->m_bValue = bValue;
pItem->m_undefined = false;
Invalidate();
return true;
}
return false;
}
bool CPropertyGrid::SetItemValue(HITEM item, const COleDateTime dtValue)
{
// get the item
CItem* pItem = FindItem(item);
if (pItem == NULL) return false;
// check
if (pItem->m_type == IT_DATE || pItem->m_type == IT_DATETIME)
{
pItem->m_dtValue = dtValue;
pItem->m_undefined = false;
Invalidate();
return true;
}
return false;
}
bool CPropertyGrid::SetItemValue(HITEM item, const COLORREF clrValue)
{
// get the item
CItem* pItem = FindItem(item);
if (pItem == NULL) return false;
// check
if (pItem->m_type == IT_COLOR)
{
pItem->m_clrValue = clrValue;
pItem->m_undefined = false;
Invalidate();
return true;
}
return false;
}
bool CPropertyGrid::SetItemValue(HITEM item, const LOGFONT lfValue)
{
// get the item
CItem* pItem = FindItem(item);
if (pItem == NULL) return false;
// check
if (pItem->m_type == IT_FONT)
{
memcpy(&pItem->m_lfValue, &lfValue, sizeof(LOGFONT));
pItem->m_undefined = false;
Invalidate();
return true;
}
return false;
}
int CPropertyGrid::GetTextMargin()
{
return 2*margin;
}
CFont* CPropertyGrid::GetFontNormal()
{
return &m_fntNormal;
}
CFont* CPropertyGrid::GetFontBold()
{
return &m_fntBold;
}
BEGIN_MESSAGE_MAP(CPropertyGrid, CWnd)
ON_WM_PAINT()
ON_WM_LBUTTONDOWN()
ON_WM_MOUSEMOVE()
ON_WM_CREATE()
ON_WM_LBUTTONUP()
ON_WM_VSCROLL()
ON_WM_ERASEBKGND()
ON_MESSAGE(WM_PG_COMBOSELCHANGED, OnComboSelChanged)
ON_MESSAGE(WM_PG_ENDLABELEDIT, OnEditChanged)
ON_MESSAGE(WM_PG_DATESELCHANGED, OnDateChanged)
ON_WM_LBUTTONDBLCLK()
ON_WM_MOUSEWHEEL()
ON_WM_DESTROY()
ON_WM_SIZE()
ON_WM_GETDLGCODE()
ON_WM_CHAR()
ON_WM_KEYDOWN()
END_MESSAGE_MAP()
//
// creation and window stuff
//
void CPropertyGrid::InitControl()
{
// first gutter
CRect rc;
GetClientRect(&rc);
m_gutter_width = rc.Width()/2;
// check if already done
if (m_fntNormal.GetSafeHandle() == NULL)
{
// fonts
LOGFONT lf;
if (GetParent() && GetParent()->GetFont())
{
CFont* pFont = GetParent()->GetFont();
pFont->GetLogFont(&lf);
m_fntNormal.CreateFontIndirect(&lf);
lf.lfWeight = FW_BOLD;
m_fntBold.CreateFontIndirect(&lf);
}
else
{
m_fntNormal.CreateStockObject(DEFAULT_GUI_FONT);
m_fntNormal.GetLogFont(&lf);
lf.lfWeight = FW_BOLD;
m_fntBold.CreateFontIndirect(&lf);
}
}
// get line height
CDC* pDC = GetDC();
CFont* pOldFont = pDC->SelectObject(&m_fntNormal);
m_line_height = pDC->GetTextExtent("Gg").cy + 2*margin;
pDC->SelectObject(pOldFont);
ReleaseDC(pDC);
// styles
ModifyStyle(0, WS_CLIPCHILDREN);
// try to get some strings
if (m_strOk.empty())
{
m_strOk = "OK";
if (GetParent() && GetParent()->GetDlgItem(IDOK))
{
CString strOk;
GetParent()->GetDlgItem(IDOK)->GetWindowText(strOk);
m_strOk = strOk;
}
}
if (m_strCancel.empty())
{
m_strCancel = "Cancel";
if (GetParent() && GetParent()->GetDlgItem(IDCANCEL))
{
CString strCancel;
GetParent()->GetDlgItem(IDCANCEL)->GetWindowText(strCancel);
m_strCancel = strCancel;
}
}
}
int CPropertyGrid::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CWnd::OnCreate(lpCreateStruct) == -1) return -1;
InitControl();
return 0;
}
void CPropertyGrid::PreSubclassWindow()
{
InitControl();
CWnd::PreSubclassWindow();
}
void CPropertyGrid::OnDestroy()
{
DeleteEditControl();
CWnd::OnDestroy();
}
void CPropertyGrid::OnSize(UINT nType, int cx, int cy)
{
CWnd::OnSize(nType, cx, cy);
RecalcLayout();
if (m_scrollbar.GetSafeHwnd())
{
CRect rect;
GetClientRect(&rect);
m_scrollbar.MoveWindow(rect.right - GetSystemMetrics(SM_CXVSCROLL), rect.top, GetSystemMetrics(SM_CXVSCROLL), rect.Height());
}
}
//
// painting
//
BOOL CPropertyGrid::OnEraseBkgnd(CDC* pDC)
{
return TRUE;
}
bool item_alpha_sort(vector<CPropertyGrid::CItem>::iterator it1, vector<CPropertyGrid::CItem>::iterator it2)
{
return (it1->m_name.compare(it2->m_name) < 0);
}
void CPropertyGrid::OnPaint()
{
// stuff needed
const int sign_size = 8;
// the scrollbar offset
int top = GetScrollOffset();
// the rect
CRect rc_dummy;
GetClientRect(&rc_dummy);
if (m_scroll_enabled)
rc_dummy.right -= GetSystemMetrics(SM_CXVSCROLL);
// make sure we do not modify this one
// because we use it to bitblt
const CRect rc(rc_dummy);
// stuff for flicker free drawing
CDC dcMem;
CBitmap bmpMem;
CPaintDC dc(this);
// create and configure the memdc
dcMem.CreateCompatibleDC(&dc);
bmpMem.CreateCompatibleBitmap(&dc, rc.Width(), rc.Height());
CBitmap* pOldBmp = dcMem.SelectObject(&bmpMem);
// brush needed
CBrush brushTitle;
brushTitle.CreateSolidBrush(m_clrTitle);
// pen needed
CPen penShade(PS_SOLID, 1, m_clrShade);
CPen penTitle(PS_SOLID, 1, m_clrTitle);
// to make sure we won't leak gdi resources
CBrush* pOldBrush = dcMem.SelectObject(&brushTitle);
CPen* pOldPen = dcMem.SelectObject(&penShade);
CFont* pOldFont = dcMem.SelectObject(&m_fntNormal);
// needed
int w = rc.Width();
// blank
dcMem.FillSolidRect(rc, m_clrBack);
dcMem.SetBkMode(TRANSPARENT);
// empty text
if (m_sections.empty())
{
CRect rect = rc;
rect.top += 10;
rect.DeflateRect(rect.Width()/4, 0);
dcMem.DrawText(m_strEmpty.c_str(), rect, DT_CENTER|DT_WORDBREAK|DT_NOPREFIX);
}
else
{
// needed
int sign_left = margin;
// we start here
int y = -top;
// alphabetical needs special
if (m_display_mode == DM_ALPHABETICAL)
{
// put all the items in a vector
vector<vector<CItem>::iterator> lst;
for (vector<CSection>::iterator it = m_sections.begin(); it != m_sections.end(); ++it)
{
for (vector<CItem>::iterator it2 = it->m_items.begin(); it2 != it->m_items.end(); ++it2)
lst.push_back(it2);
}
// sort the vector
sort(lst.begin(), lst.end(), item_alpha_sort);
// display the items
for (vector<vector<CItem>::iterator>::iterator it2 = lst.begin(); it2 != lst.end(); ++it2)
{
// first reset
(*it2)->m_rcName.SetRectEmpty();
(*it2)->m_rcValue.SetRectEmpty();
// draw if visible
(*it2)->m_rcName = CRect(0, y, w, y+m_line_height);
CRect rcInter = (*it2)->m_rcName;
rcInter.IntersectRect(rc, rcInter);
if (!rcInter.IsRectEmpty())
DrawItem(dcMem, w, sign_left+sign_size, y, *it2);
// next line
y += m_line_height;
}
}
else
{
// next iterate on sections
for (vector<CSection>::iterator it = m_sections.begin(); it != m_sections.end(); ++it)
{
// reset
it->m_rcSign.SetRectEmpty();
it->m_rcTitle.SetRectEmpty();
// is visible?
it->m_rcTitle = CRect(0, y, w, y+m_line_height);
CRect rcInter = it->m_rcTitle;
rcInter.IntersectRect(rcInter, rc);
if (m_display_mode == DM_CATEGORIZED && !rcInter.IsRectEmpty())
{
// first shade rect
if (m_shade_titles)
dcMem.FillSolidRect(0, y, w, m_line_height, m_clrShade);
// now draw a separator lines
if (m_draw_lines)
{
dcMem.SelectObject(&penShade);
dcMem.MoveTo(0, y);
dcMem.LineTo(w+1, y);
dcMem.MoveTo(0, y+m_line_height);
dcMem.LineTo(w+1, y+m_line_height);
}
// now draw gutter
if (m_draw_gutter)
{
dcMem.SelectObject(&penShade);
dcMem.MoveTo(m_gutter_width, y);
dcMem.LineTo(m_gutter_width, y+m_line_height+1);
}
// now draw collapse sign
int sign_top = y+margin+2;
dcMem.SelectObject(&penTitle);
it->m_rcSign = CRect(sign_left, sign_top, sign_left+sign_size+1, sign_top+sign_size+1);
dcMem.FrameRect(it->m_rcSign, &brushTitle);
dcMem.MoveTo(sign_left+2, sign_top+sign_size/2);
dcMem.LineTo(sign_left+2+sign_size/2+1, sign_top+sign_size/2);
if (it->m_collapsed)
{
dcMem.MoveTo(sign_left+sign_size/2, sign_top+2);
dcMem.LineTo(sign_left+sign_size/2, sign_top+2+sign_size/2+1);
}
// prepare draw text
int title_left = sign_left+sign_size+2*margin;
int title_top = y;
dcMem.SelectObject(&m_fntBold);
it->m_rcTitle = CRect(title_left, title_top, w, title_top+m_line_height);
// draw focus rect
if (m_focused_section == it->m_id)
{
CSize sz = dcMem.GetTextExtent(it->m_title.c_str());
int rect_left = title_left;
int rect_top = title_top+(m_line_height-sz.cy)/2;
int rect_width = sz.cx+3*margin;
int rect_height = sz.cy;
dcMem.DrawFocusRect(CRect(rect_left, rect_top, rect_left+rect_width, rect_top+rect_height));
}
// now draw text
dcMem.SetTextColor(m_clrTitle);
dcMem.DrawText(it->m_title.c_str(), CRect(title_left+GetTextMargin(), title_top, w, title_top+m_line_height), DT_END_ELLIPSIS|DT_LEFT|DT_SINGLELINE|DT_VCENTER|DT_NOPREFIX);
}
// next line
if (m_display_mode == DM_CATEGORIZED)
y+=m_line_height;
// iterate on items
if (!it->m_collapsed || m_display_mode != DM_CATEGORIZED)
{
for (vector<CItem>::iterator it2 = it->m_items.begin(); it2 != it->m_items.end(); ++it2)
{
// reset
it2->m_rcName.SetRectEmpty();
it2->m_rcValue.SetRectEmpty();
// is visible?
it2->m_rcName = CRect(0, y, w, y+m_line_height);
CRect rcInter = it2->m_rcName;
rcInter.IntersectRect(rc, rcInter);
if (!rcInter.IsRectEmpty())
DrawItem(dcMem, w, sign_left+sign_size, y, it2);
// next line
y+=m_line_height;
}
}
}
}
}
// Blt the changes to the screen DC.
dc.BitBlt(rc.left, rc.top, rc.right-rc.left, rc.bottom-rc.top, &dcMem, 0, 0, SRCCOPY);
// Done with off-screen bitmap and DC.
dcMem.SelectObject(pOldBmp);
dcMem.SelectObject(pOldFont);
dcMem.SelectObject(pOldPen);
dcMem.SelectObject(pOldBrush);
bmpMem.DeleteObject();
dcMem.DeleteDC();
// Validate All
ValidateRgn(NULL);
ValidateRect(NULL);
}
void CPropertyGrid::DrawItem(CDC& dc, int w, int x, int y, vector<CItem>::iterator& it)
{
// brush needed
CBrush brushText;
brushText.CreateSolidBrush(m_clrText);
// pen needed
CPen penShade(PS_SOLID, 1, m_clrShade);
// to make sure we won't leak gdi resources
CBrush* pOldBrush = dc.SelectObject(&brushText);
CPen* pOldPen = dc.SelectObject(&penShade);
CFont* pOldFont = dc.SelectObject(&m_fntNormal);
// first shade rect
if (m_shade_titles)
dc.FillSolidRect(0, y, x+2*margin, m_line_height, m_clrShade);
// now draw a separator line
if (m_draw_lines)
{
dc.SelectObject(&penShade);
dc.MoveTo(0, y+m_line_height);
dc.LineTo(w+1, y+m_line_height);
}
// now draw gutter
if (m_draw_gutter)
{
dc.SelectObject(&penShade);
dc.MoveTo(m_gutter_width, y);
dc.LineTo(m_gutter_width, y+m_line_height+1);
}
// needed
int name_left = x+2*margin+GetTextMargin();
int name_right = m_gutter_width-1;
int value_left = m_gutter_width;
int value_right = w;
// is being edited?
if (m_focused_item == it->m_id && it->m_editable && GetEditMode(*it) != EM_CUSTOM)
{
value_right -= m_line_height;
// the rect of the button
m_rect_button = CRect(w-m_line_height, y, w, y+m_line_height);
UINT pushed = m_button_depressed?DFCS_PUSHED:0;
// now draw the button
switch (GetEditMode(*it))
{
case EM_MODAL:
// draw a button
dc.DrawFrameControl(m_rect_button, DFC_BUTTON, DFCS_BUTTONPUSH|pushed);
dc.SelectObject(&m_fntBold);
dc.DrawText("...", m_rect_button, DT_CENTER|DT_SINGLELINE|DT_VCENTER|DT_NOPREFIX);
break;
case EM_DROPDOWN:
// draw an arrow
dc.DrawFrameControl(m_rect_button, DFC_SCROLL, DFCS_SCROLLDOWN|pushed);
break;
case EM_INPLACE:
// whole area is edit
m_rect_button.left = m_gutter_width;
break;
default:
assert(false);
}
}
// update the rects
it->m_rcName = CRect(0, y, m_gutter_width, y+m_line_height);
it->m_rcValue = CRect(value_left, y, value_right, y+m_line_height);
CRect rcValue = it->m_rcValue;
rcValue.left += GetTextMargin();
// focused
if (m_focused_item == it->m_id)
{
int rect_left = name_left-2*margin;
int rect_right = name_right;
dc.FillSolidRect(rect_left, y, rect_right-rect_left+1, m_line_height, m_clrFocus);
dc.SetTextColor(m_clrHilite);
}
else
{
dc.SetTextColor(m_clrText);
}
// put name and value
dc.SelectObject(&m_fntNormal);
dc.DrawText(it->m_name.c_str(), -1, CRect(name_left, y, name_right, y+m_line_height), DT_END_ELLIPSIS|DT_LEFT|DT_SINGLELINE|DT_VCENTER|DT_NOPREFIX);
// get back to normal text
if (it->m_editable) dc.SetTextColor(m_clrEditable);
else dc.SetTextColor(m_clrDisabled);
// custom item
if (it->m_type == IT_CUSTOM)
{
int save = dc.SaveDC();
it->m_pCustom->DrawItem(dc, it->m_rcValue, m_focused_item == it->m_id);
dc.RestoreDC(save);
}
else
{
// modified flag
bool modified = (it->m_strValue != it->m_strValue_old);
// now draw text
string strValue = it->m_strValue;
switch (it->m_type)
{
case IT_TEXT:
{
size_t j;
for (;( j = strValue.find( "\r\n" )) != string::npos;)
strValue.replace( j, 2, "<EFBFBD>");
break;
}
case IT_INTEGER:
{
CString strTemp;
string strFormat = "%d";
if (it->m_options.size() && !it->m_options.front().empty()) strFormat = it->m_options.front();
strTemp.Format(strFormat.c_str(), it->m_nValue);
strValue = LPCTSTR(strTemp);
modified = (it->m_nValue != it->m_nValue_old);
break;
}
case IT_DOUBLE:
{
CString strTemp;
string strFormat = "%g";
if (it->m_options.size() && !it->m_options.front().empty()) strFormat = it->m_options.front();
strTemp.Format(strFormat.c_str(), it->m_dValue);
strValue = LPCTSTR(strTemp);
modified = (it->m_dValue != it->m_dValue_old);
break;
}
case IT_DATE:
{
CString strTemp;
if (it->m_options.size() && !it->m_options.front().empty()) strTemp = it->m_dtValue.Format(it->m_options.front().c_str());
else strTemp = it->m_dtValue.Format(VAR_DATEVALUEONLY);
strValue = LPCTSTR(strTemp);
modified = (it->m_dtValue != it->m_dtValue_old);
break;
}
case IT_DATETIME:
{
CString strTemp;
if (it->m_options.size() && !it->m_options.front().empty()) strTemp = it->m_dtValue.Format(it->m_options.front().c_str());
else strTemp = it->m_dtValue.Format();
strValue = LPCTSTR(strTemp);
modified = (it->m_dtValue != it->m_dtValue_old);
break;
}
case IT_BOOLEAN:
{
strValue = it->m_bValue ? m_strTrue : m_strFalse;
modified = (it->m_bValue != it->m_bValue_old);
break;
}
case IT_COMBO:
{
if (it->m_nValue>=0 && it->m_nValue<int(it->m_options.size()))
strValue = it->m_options[it->m_nValue];
modified = (it->m_nValue != it->m_nValue_old);
break;
}
case IT_FILE:
case IT_FOLDER:
{
TCHAR szBuffer[1024];
_tcsncpy(szBuffer, strValue.c_str(), 1024);
PathCompactPath(dc.GetSafeHdc(), szBuffer, rcValue.Width());
strValue = szBuffer;
break;
}
case IT_COLOR:
{
// draw a sample rectangle
CRect rc = rcValue;
rc.DeflateRect(0,2,0,2);
rc.top++;
rc.right = rc.left + m_line_height;
dc.FrameRect(rc, &brushText);
rc.DeflateRect(1,1);
dc.FillSolidRect(rc, it->m_clrValue);
rcValue.left = rc.right + 3*margin;
// update the text
CString strTemp;
strTemp.Format("%d; %d; %d", GetRValue(it->m_clrValue), GetGValue(it->m_clrValue), GetBValue(it->m_clrValue));
strValue = LPCTSTR(strTemp);
modified = (it->m_clrValue != it->m_clrValue_old);
break;
}
case IT_FONT:
{
CString strTemp;
strTemp.Format("%s; %dpt", it->m_lfValue.lfFaceName, -MulDiv(it->m_lfValue.lfHeight, 72, dc.GetDeviceCaps(LOGPIXELSY)));
strValue = LPCTSTR(strTemp);
modified = (memcmp(&it->m_lfValue, &it->m_lfValue_old, sizeof(LOGFONT))!=0);
break;
}
}
// we must also take undefined state change into account
modified |= (it->m_undefined != it->m_undefined_old);
// set proper font
if (modified && m_bold_modified) dc.SelectObject(&m_fntBold);
else if (it->m_editable && m_bold_editables) dc.SelectObject(&m_fntBold);
else dc.SelectObject(&m_fntNormal);
// now draw it
if (it->m_undefined) strValue = m_strUndefined;
dc.DrawText(strValue.c_str(), -1, rcValue, DT_END_ELLIPSIS|DT_LEFT|DT_SINGLELINE|DT_VCENTER|DT_NOPREFIX);
}
// clean up
dc.SelectObject(pOldFont);
dc.SelectObject(pOldPen);
dc.SelectObject(pOldBrush);
}
//
// mouse interaction
//
void CPropertyGrid::OnLButtonDown(UINT nFlags, CPoint point)
{
// destroy edit
SetFocus();
DeleteEditControl();
// click on button?
if (m_rect_button.PtInRect(point))
{
m_button_pushed = true;
m_button_depressed = true;
SetCapture();
Invalidate();
return;
}
// click on button?
if (m_focused_item != -1)
{
CItem* pItem = FindItem(m_focused_item);
if ( pItem && pItem->m_type == IT_CUSTOM
&& GetEditMode(*pItem) == EM_CUSTOM
&& pItem->m_pCustom->OnLButtonDown(pItem->m_rcValue, point))
{
m_custom_tracking = true;
SetCapture();
Invalidate();
return;
}
}
// resizing gutter?
if (abs(point.x-m_gutter_width)<3)
{
::SetCursor(AfxGetApp()->LoadStandardCursor(IDC_SIZEWE));
m_resizing_gutter = true;
m_ptLast = point;
SetCapture();
Invalidate();
return;
}
// disable focus
m_focused_item = -1;
m_focused_section = -1;
m_rect_button.SetRectEmpty();
// did we click on a section
if (m_display_mode == DM_CATEGORIZED)
{
for (vector<CSection>::iterator it = m_sections.begin(); it != m_sections.end(); ++it)
{
if (it->m_rcSign.PtInRect(point))
{
it->m_collapsed = !it->m_collapsed;
m_focused_section = it->m_id;
RecalcLayout();
return;
}
else if (it->m_rcTitle.PtInRect(point))
{
m_focused_section = it->m_id;
Invalidate();
return;
}
}
}
// focus
for (vector<CSection>::iterator it = m_sections.begin(); it != m_sections.end(); ++it)
{
if (!it->m_collapsed || m_display_mode != DM_CATEGORIZED)
{
for (vector<CItem>::iterator it2 = it->m_items.begin(); it2 != it->m_items.end(); ++it2)
{
if (it2->m_rcName.PtInRect(point) || it2->m_rcValue.PtInRect(point))
{
if (it2->m_editable || m_focus_disabled)
{
m_focused_item = it2->m_id;
GetOwner()->SendMessage(WM_PG_SELECTIONCHANGED, it2->m_id);
if (it2->m_rcValue.PtInRect(point))
m_value_clicked = (GetEditMode(*it2) == EM_INPLACE || GetEditMode(*it2) == EM_DROPDOWN);
Invalidate();
return;
}
}
}
}
}
CWnd::OnLButtonDown(nFlags, point);
Invalidate();
}
void CPropertyGrid::OnLButtonDblClk(UINT nFlags, CPoint point)
{
if (m_focused_item != -1)
{
CItem* pItem = FindItem(m_focused_item);
if (pItem)
{
if (pItem->m_type == IT_BOOLEAN)
{
if (!pItem->m_undefined)
{
pItem->m_bValue = !pItem->m_bValue;
GetOwner()->SendMessage(WM_PG_ITEMCHANGED, pItem->m_id);
Invalidate();
}
}
else if (pItem->m_type == IT_COMBO)
{
if (!pItem->m_undefined)
{
pItem->m_nValue = (pItem->m_nValue+1)%int(pItem->m_options.size());
GetOwner()->SendMessage(WM_PG_ITEMCHANGED, pItem->m_id);
Invalidate();
}
}
else if (GetEditMode(*pItem) == EM_MODAL)
{
EditFocusedItem();
}
}
}
else if (m_focused_section != -1)
{
CSection* pSection = FindSection(m_focused_section);
if (pSection)
{
pSection->m_collapsed = !pSection->m_collapsed;
Invalidate();
}
}
CWnd::OnLButtonDblClk(nFlags, point);
}
void CPropertyGrid::OnMouseMove(UINT nHitTest, CPoint point)
{
if (m_custom_tracking)
{
CItem* pItem = FindItem(m_focused_item);
if (pItem)
{
pItem->m_pCustom->OnMouseMove(pItem->m_rcValue, point);
Invalidate();
}
}
else if (m_button_pushed)
{
m_button_depressed = m_rect_button.PtInRect(point)?true:false;
Invalidate();
}
else if (m_resizing_gutter)
{
::SetCursor(AfxGetApp()->LoadStandardCursor(IDC_SIZEWE));
m_gutter_width += point.x-m_ptLast.x;
CRect rc;
GetClientRect(&rc);
if (m_gutter_width<rc.Width()/5) m_gutter_width = rc.Width()/5;
if (m_gutter_width>4*rc.Width()/5) m_gutter_width = 4*rc.Width()/5;
m_ptLast = point;
Invalidate();
}
else if (!m_control)
{
if (abs(point.x-m_gutter_width)<3) ::SetCursor(AfxGetApp()->LoadStandardCursor(IDC_SIZEWE));
else ::SetCursor(AfxGetApp()->LoadStandardCursor(IDC_ARROW));
}
CWnd::OnMouseMove(nHitTest, point);
}
void CPropertyGrid::OnLButtonUp(UINT nFlags, CPoint point)
{
if (m_custom_tracking)
{
m_custom_tracking = false;
ReleaseCapture();
Invalidate();
CItem* pItem = FindItem(m_focused_item);
if (pItem)
pItem->m_pCustom->OnLButtonUp(pItem->m_rcValue, point);
}
else if (m_button_pushed || m_value_clicked)
{
m_button_pushed = false;
m_button_depressed = false;
ReleaseCapture();
Invalidate();
if (m_rect_button.PtInRect(point) || (m_value_clicked && m_focused_item != -1 && FindItem(m_focused_item) && FindItem(m_focused_item)->m_rcValue.PtInRect(point)))
{
m_value_clicked = false;
CItem* pItem = FindItem(m_focused_item);
if (pItem)
{
if (GetEditMode(*pItem) == EM_DROPDOWN)
{
if (pItem->m_type == IT_CUSTOM)
{
CRect rc = m_rect_button;
rc.left = m_gutter_width;
pItem->m_pCustom->ShowDropDown(rc);
}
else if (pItem->m_type == IT_DATE)
{
// the calendar rect
CRect rc = m_rect_button;
rc.left = m_gutter_width;
rc.top += m_line_height;
rc.bottom = rc.top + 100;
ClientToScreen(&rc);
// create it
m_control = new CPropertyGridMonthCalCtrl;
CPropertyGridMonthCalCtrl* mc = (CPropertyGridMonthCalCtrl*) m_control;
mc->CreateEx(0, MONTHCAL_CLASS, NULL, WS_POPUP|WS_BORDER, rc, GetParent(), 0);
mc->SetCurSel(pItem->m_dtValue);
mc->SetOwner(this);
mc->SizeMinReq();
// now position it
CRect rc2;
mc->GetWindowRect(&rc2);
rc2.OffsetRect(rc.right-rc2.right, 0);
mc->SetWindowPos(NULL, rc2.left, rc2.top, 0, 0, SWP_NOZORDER|SWP_NOSIZE|SWP_SHOWWINDOW);
}
else
{
// the combo rect
CRect rc = m_rect_button;
rc.left = m_gutter_width;
rc.top += m_line_height;
rc.bottom = rc.top + 100;
// create it
m_control = new CPropertyGridCombo();
CPropertyGridCombo* pCombo = (CPropertyGridCombo*)m_control;
pCombo->Create(WS_CHILD, rc, this, 0);
pCombo->SetColors(m_clrBack, m_clrText, m_clrFocus, m_clrHilite);
pCombo->SetFont(&m_fntNormal);
if (pItem->m_type == IT_BOOLEAN)
{
pCombo->AddString(m_strTrue);
pCombo->AddString(m_strFalse);
if (!pItem->m_undefined)
pCombo->SetCurSel(pItem->m_bValue?0:1);
}
else
{
for (vector<string>::iterator it = pItem->m_options.begin(); it != pItem->m_options.end(); ++it)
pCombo->AddString(*it);
if (!pItem->m_undefined)
pCombo->SetCurSel(pItem->m_nValue);
}
pCombo->ShowWindow(SW_SHOW);
}
}
else if (GetEditMode(*pItem) == EM_INPLACE)
{
// the in-place edit rect
CRect rc = m_rect_button;
rc.left++;
rc.top += margin;
// the value
string strValue;
if (pItem->m_type == IT_STRING)
{
strValue = pItem->m_strValue;
}
else if (pItem->m_type == IT_INTEGER)
{
if (!pItem->m_undefined)
{
CString strTemp;
strTemp.Format("%d", pItem->m_nValue);
strValue = LPCTSTR(strTemp);
}
}
else if (pItem->m_type == IT_DOUBLE)
{
if (!pItem->m_undefined)
{
CString strTemp;
strTemp.Format("%g", pItem->m_dValue);
strValue = LPCTSTR(strTemp);
}
}
else if (pItem->m_type == IT_CUSTOM)
{
strValue = pItem->m_pCustom->GetStringForInPlaceEdit();
}
else
{
assert(false);
}
// create it
m_control = new CPropertyGridInPlaceEdit(this, rc, WS_CHILD, 1000, strValue);
CPropertyGridInPlaceEdit* pEdit = (CPropertyGridInPlaceEdit*)m_control;
pEdit->SetColors(m_clrBack, m_clrText);
pEdit->SetFont(&m_fntNormal);
pEdit->ShowWindow(SW_SHOW);
}
else if (GetEditMode(*pItem) == EM_MODAL)
{
EditFocusedItem();
}
else if (GetEditMode(*pItem) == EM_CUSTOM)
{
pItem->m_pCustom->OnLButtonUp(pItem->m_rcValue, point);
}
else
{
assert(false);
}
}
}
}
else if (m_resizing_gutter)
{
ReleaseCapture();
m_resizing_gutter = false;
::SetCursor(AfxGetApp()->LoadStandardCursor(IDC_ARROW));
}
CWnd::OnLButtonUp(nFlags, point);
}
//
// keyboard interaction
//
UINT CPropertyGrid::OnGetDlgCode()
{
return CWnd::OnGetDlgCode()|DLGC_WANTCHARS|DLGC_WANTARROWS;
}
void CPropertyGrid::MoveForward(HSECTION& focused_section, HITEM& focused_item)
{
for (int pass = 0; pass<2; pass++)
{
bool found = false;
bool stop_on_next_valid = false;
if (focused_section == -1 && focused_item == -1)
stop_on_next_valid = true;
for (vector<CSection>::iterator it = m_sections.begin(); it != m_sections.end(); ++it)
{
if (m_display_mode == DM_CATEGORIZED)
{
if (it->m_id == focused_section)
{
stop_on_next_valid = true;
}
else if (stop_on_next_valid)
{
focused_section = it->m_id;
focused_item = -1;
found = true;
break;
}
}
if (!it->m_collapsed || m_display_mode != DM_CATEGORIZED)
{
for (vector<CItem>::iterator it2 = it->m_items.begin(); it2 != it->m_items.end(); ++it2)
{
if (it2->m_id == focused_item)
{
stop_on_next_valid = true;
}
else if (stop_on_next_valid)
{
if (it2->m_editable || m_focus_disabled)
{
focused_section = -1;
focused_item = it2->m_id;
found = true;
break;
}
}
}
if (found)
break;
}
}
if (found)
break;
focused_section = -1;
focused_item = -1;
}
}
void CPropertyGrid::FocusNextItem()
{
// simple move forward
MoveForward(m_focused_section, m_focused_item);
// ensure visible
CRect rc(0,0,0,0);
if (m_focused_section != -1 && FindSection(m_focused_section)) rc = FindSection(m_focused_section)->m_rcTitle;
else if (m_focused_item != -1 && FindItem(m_focused_item)) rc = FindItem(m_focused_item)->m_rcName;
if (!rc.IsRectEmpty())
{
CRect rect;
GetClientRect(&rect);
rect.IntersectRect(rc, rect);
if (rect.Height() != m_line_height)
OnVScroll(SB_THUMBPOSITION, rc.top, &m_scrollbar);
}
// done
Invalidate();
}
void CPropertyGrid::FocusPrevItem()
{
for (vector<CSection>::iterator it = m_sections.begin(); it != m_sections.end(); ++it)
{
if (m_display_mode == DM_CATEGORIZED)
{
HSECTION focused_section = it->m_id;
HITEM focused_item = -1;
MoveForward(focused_section, focused_item);
if (focused_section == m_focused_section && focused_item == m_focused_item)
{
m_focused_section = it->m_id;
m_focused_item = -1;
break;
}
}
if (!it->m_collapsed || m_display_mode != DM_CATEGORIZED)
{
bool found = false;
for (vector<CItem>::iterator it2 = it->m_items.begin(); it2 != it->m_items.end(); ++it2)
{
if (!it2->m_editable && !m_focus_disabled)
continue;
HSECTION focused_section = -1;
HITEM focused_item = it2->m_id;
MoveForward(focused_section, focused_item);
if (focused_section == m_focused_section && focused_item == m_focused_item)
{
m_focused_section = -1;
m_focused_item = it2->m_id;
GetOwner()->SendMessage(WM_PG_SELECTIONCHANGED, it2->m_id);
found = true;
break;
}
}
if (found)
break;
}
}
// ensure visible
CRect rc(0,0,0,0);
if (m_focused_section != -1 && FindSection(m_focused_section)) rc = FindSection(m_focused_section)->m_rcTitle;
else if (m_focused_item != -1 && FindItem(m_focused_item)) rc = FindItem(m_focused_item)->m_rcName;
if (!rc.IsRectEmpty())
{
CRect rect;
GetClientRect(&rect);
rect.IntersectRect(rc, rect);
if (rect.Height() != m_line_height)
OnVScroll(SB_THUMBPOSITION, rc.top, &m_scrollbar);
}
// done
Invalidate();
}
void CPropertyGrid::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
{
if (nChar == '*')
{
ExpandAll(true);
}
else if (nChar == '/')
{
ExpandAll(false);
}
else if (nChar == '+' || nChar == '-')
{
if (m_focused_section != -1)
{
CSection* pSection = FindSection(m_focused_section);
if (pSection) pSection->m_collapsed = (nChar=='-');
RecalcLayout();
}
}
CWnd::OnChar(nChar, nRepCnt, nFlags);
}
void CPropertyGrid::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
if (nChar == VK_DOWN)
{
FocusNextItem();
}
else if (nChar == VK_UP)
{
FocusPrevItem();
}
else if (nChar == VK_LEFT)
{
if (m_focused_section != -1 && FindSection(m_focused_section) && FindSection(m_focused_section)->m_collapsed == false)
{
ExpandSection(m_focused_section, false);
}
else
{
FocusPrevItem();
}
}
else if (nChar == VK_RIGHT)
{
if (m_focused_section != -1 && FindSection(m_focused_section) && FindSection(m_focused_section)->m_collapsed == true)
{
ExpandSection(m_focused_section, true);
}
else
{
FocusNextItem();
}
}
CWnd::OnKeyDown(nChar, nRepCnt, nFlags);
}
//
// scrolling
//
void CPropertyGrid::RecalcLayout()
{
// save current scroll offset
int offset = GetScrollOffset();
// total height
int height = 0;
for (vector<CSection>::iterator it = m_sections.begin(); it != m_sections.end(); ++it)
{
if (m_display_mode == DM_CATEGORIZED)
height += m_line_height;
if (!it->m_collapsed || m_display_mode != DM_CATEGORIZED)
height += int(it->m_items.size())*m_line_height;
}
// client rect
CRect rc;
GetClientRect(&rc);
if (height < rc.Height())
{
if (m_scrollbar.GetSafeHwnd() != NULL)
{
m_scrollbar.EnableScrollBar(ESB_DISABLE_BOTH);
m_scrollbar.ShowScrollBar(FALSE);
}
m_scroll_enabled = false;
}
else
{
if (m_scrollbar.GetSafeHwnd() == NULL)
{
CRect rect = rc;
rect.left = rect.right - GetSystemMetrics(SM_CXVSCROLL);
m_scrollbar.Create(WS_CHILD|SBS_VERT, rect, this, 1000);
}
m_scrollbar.EnableScrollBar(ESB_ENABLE_BOTH);
SCROLLINFO info;
info.cbSize = sizeof(SCROLLINFO);
info.fMask = SIF_ALL;
info.nMin = 0;
info.nMax = height;
info.nPage = rc.Height();
info.nPos = min(offset, height);
info.nTrackPos = 2;
m_scrollbar.SetScrollInfo(&info);
m_scrollbar.ShowScrollBar();
m_scroll_enabled = true;
}
if (GetSafeHwnd())
Invalidate();
}
int CPropertyGrid::GetScrollOffset()
{
if (m_scrollbar && m_scrollbar.IsWindowEnabled() == TRUE)
return m_scrollbar.GetScrollPos();
return 0;
}
void CPropertyGrid::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
// check
if (!m_scroll_enabled) return;
if (pScrollBar != &m_scrollbar) return;
if (nSBCode == SB_ENDSCROLL) return;
// set focus to us
SetFocus();
// get the scroll info
SCROLLINFO info;
info.cbSize = sizeof(SCROLLINFO);
info.fMask = SIF_ALL;
m_scrollbar.GetScrollInfo(&info);
int min = info.nMin;
int pos = info.nPos;
int max = info.nMax-info.nPage;
// the entire rect
CRect rect;
GetClientRect(&rect);
int h = rect.Height();
// the rect without the scrollbar
CRect rc(0,0,rect.right-GetSystemMetrics(SM_CXVSCROLL),rect.bottom);
switch(nSBCode)
{
case SB_TOP:
pScrollBar->SetScrollPos(min);
break;
case SB_BOTTOM:
pScrollBar->SetScrollPos(max);
break;
case SB_LINEDOWN:
if (pos+m_line_height>=max) pScrollBar->SetScrollPos(max);
else pScrollBar->SetScrollPos(pos+m_line_height);
break;
case SB_LINEUP:
if (pos-m_line_height<=min) pScrollBar->SetScrollPos(min);
else pScrollBar->SetScrollPos(pos-m_line_height);
break;
case SB_PAGEDOWN:
if (pos+h>=max) pScrollBar->SetScrollPos(max);
else pScrollBar->SetScrollPos(pos+h);
break;
case SB_PAGEUP:
if (pos-h<=min) pScrollBar->SetScrollPos(min);
else pScrollBar->SetScrollPos(pos-h);
break;
case SB_THUMBPOSITION:
case SB_THUMBTRACK:
int diff = nPos - pos;
if (diff == 0) return;
if (pos <= min && diff<0) return;
if (pos >= max && diff>0) return;
pScrollBar->SetScrollPos(nPos);
}
Invalidate();
CWnd::OnVScroll(nSBCode, nPos, pScrollBar);
}
BOOL CPropertyGrid::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
{
/*int steps = abs(zDelta)/WHEEL_DELTA;
for (int i=0; i<3*steps; i++)
{
if (zDelta>0) OnVScroll(SB_LINEUP, 0, &m_scrollbar);
if (zDelta<0) OnVScroll(SB_LINEDOWN, 0, &m_scrollbar);
}*/
if (zDelta>0) OnVScroll(SB_LINEUP, 0, &m_scrollbar);
if (zDelta<0) OnVScroll(SB_LINEDOWN, 0, &m_scrollbar);
return CWnd::OnMouseWheel(nFlags, zDelta, pt);
}
//
// editing
//
CPropertyGrid::EEditMode CPropertyGrid::GetEditMode(CItem& item)
{
switch (item.m_type)
{
case IT_CUSTOM:
return item.m_pCustom->GetEditMode();
case IT_STRING:
case IT_INTEGER:
case IT_DOUBLE:
return EM_INPLACE;
case IT_COMBO:
case IT_BOOLEAN:
case IT_DATE:
return EM_DROPDOWN;
case IT_TEXT:
case IT_DATETIME:
case IT_FILE:
case IT_FOLDER:
case IT_COLOR:
case IT_FONT:
return EM_MODAL;
default:
assert(false);
return EM_CUSTOM;
}
}
void CPropertyGrid::DeleteEditControl()
{
// destroy edit
if (m_control)
{
if (m_control->GetSafeHwnd())
m_control->DestroyWindow();
delete m_control;
m_control = NULL;
}
}
LRESULT CPropertyGrid::OnComboSelChanged(WPARAM wParam, LPARAM lParam)
{
CItem* pItem = FindItem(m_focused_item);
if (pItem)
{
if (pItem->m_type == IT_BOOLEAN)
{
pItem->m_bValue = (wParam == 0);
pItem->m_undefined = false;
GetOwner()->SendMessage(WM_PG_ITEMCHANGED, pItem->m_id);
DeleteEditControl();
Invalidate();
}
else if (pItem->m_type == IT_COMBO)
{
pItem->m_nValue = int(wParam);
pItem->m_undefined = false;
GetOwner()->SendMessage(WM_PG_ITEMCHANGED, pItem->m_id);
DeleteEditControl();
Invalidate();
}
else
{
assert(false);
}
}
return 0;
}
LRESULT CPropertyGrid::OnEditChanged(WPARAM wParam, LPARAM lParam)
{
CItem* pItem = FindItem(m_focused_item);
if (pItem)
{
if (pItem->m_type == IT_STRING)
{
pItem->m_strValue = string((char*)wParam);
pItem->m_undefined = false;
GetOwner()->SendMessage(WM_PG_ITEMCHANGED, pItem->m_id);
DeleteEditControl();
Invalidate();
}
else if (pItem->m_type == IT_INTEGER)
{
if (strlen((char*)wParam))
{
pItem->m_nValue = atoi((char*)wParam);
pItem->m_undefined = false;
GetOwner()->SendMessage(WM_PG_ITEMCHANGED, pItem->m_id);
}
DeleteEditControl();
Invalidate();
}
else if (pItem->m_type == IT_DOUBLE)
{
if (strlen((char*)wParam))
{
pItem->m_dValue = atof((char*)wParam);
pItem->m_undefined = false;
GetOwner()->SendMessage(WM_PG_ITEMCHANGED, pItem->m_id);
}
DeleteEditControl();
Invalidate();
}
else if (pItem->m_type == IT_CUSTOM)
{
if (pItem->m_pCustom->OnItemEdited(string((char*)wParam)))
GetOwner()->SendMessage(WM_PG_ITEMCHANGED, pItem->m_id);
DeleteEditControl();
Invalidate();
}
else
{
assert(false);
}
}
return 0;
}
LRESULT CPropertyGrid::OnDateChanged(WPARAM wParam, LPARAM lParam)
{
CItem* pItem = FindItem(m_focused_item);
if (pItem)
{
if (pItem->m_type == IT_DATE)
{
CPropertyGridMonthCalCtrl* mc = (CPropertyGridMonthCalCtrl*) m_control;
mc->GetCurSel(pItem->m_dtValue);
pItem->m_undefined = false;
GetOwner()->SendMessage(WM_PG_ITEMCHANGED, pItem->m_id);
DeleteEditControl();
Invalidate();
}
else
{
assert(false);
}
}
return 0;
}
void CPropertyGrid::EditFocusedItem()
{
CItem* pItem = FindItem(m_focused_item);
if (pItem)
{
if (!pItem->m_editable)
return;
if (pItem->m_type == IT_TEXT)
{
CDynDialogEx dlg(GetParent());
dlg.SetUseSystemButtons(FALSE);
dlg.SetWindowTitle(pItem->m_name.c_str());
dlg.SetFont(&m_fntNormal);
CString strValue = pItem->m_strValue.c_str();
dlg.AddDlgControl("EDIT", pItem->m_strValue.c_str(), STYLE_EDIT|WS_VSCROLL|WS_HSCROLL|ES_AUTOHSCROLL|ES_AUTOVSCROLL|ES_LEFT|ES_MULTILINE|ES_WANTRETURN, EXSTYLE_EDIT, CRect(7, 7, 200, 100), (void*) &strValue);
dlg.AddDlgControl("BUTTON", m_strOk.c_str(), STYLE_BUTTON, EXSTYLE_BUTTON, CRect(56, 106, 106, 120), NULL, IDOK);
dlg.AddDlgControl("BUTTON", m_strCancel.c_str(), STYLE_BUTTON, EXSTYLE_BUTTON, CRect(110, 106, 160, 120), NULL, IDCANCEL);
if (dlg.DoModal() == IDOK)
{
pItem->m_strValue = LPCTSTR(strValue);
pItem->m_undefined = false;
GetOwner()->SendMessage(WM_PG_ITEMCHANGED, pItem->m_id);
Invalidate();
}
}
else if (pItem->m_type == IT_DATETIME)
{
CDynDialogEx dlg(GetParent());
dlg.SetUseSystemButtons(FALSE);
dlg.SetWindowTitle(pItem->m_name.c_str());
dlg.SetFont(&m_fntNormal);
COleDateTime dtValueDate = pItem->m_dtValue;
CTime dtValueTime(pItem->m_dtValue.GetYear(), pItem->m_dtValue.GetMonth(), pItem->m_dtValue.GetDay(), pItem->m_dtValue.GetHour(), pItem->m_dtValue.GetMinute(), pItem->m_dtValue.GetSecond());
dlg.AddDlgControl("STATIC", m_strDate.c_str(), STYLE_STATIC, EXSTYLE_STATIC, CRect(7, 3, 60, 12));
dlg.AddDlgControl("STATIC", m_strTime.c_str(), STYLE_STATIC, EXSTYLE_STATIC, CRect(67, 3, 120, 12));
dlg.AddDlgControl("SysDateTimePick32", "", STYLE_DATETIMEPICKER|DTS_SHORTDATEFORMAT, EXSTYLE_DATETIMEPICKER, CRect(7, 13, 60, 26), (void*) &dtValueDate);
dlg.AddDlgControl("SysDateTimePick32", "", STYLE_DATETIMEPICKER|DTS_TIMEFORMAT , EXSTYLE_DATETIMEPICKER, CRect(67, 13, 120, 26), (void*) &dtValueTime);
dlg.AddDlgControl("BUTTON", m_strOk.c_str(), STYLE_BUTTON, EXSTYLE_BUTTON, CRect(7, 37, 60, 51), NULL, IDOK);
dlg.AddDlgControl("BUTTON", m_strCancel.c_str(), STYLE_BUTTON, EXSTYLE_BUTTON, CRect(67, 37, 120, 51), NULL, IDCANCEL);
if (dlg.DoModal() == IDOK)
{
pItem->m_dtValue.SetDateTime(dtValueDate.GetYear(), dtValueDate.GetMonth(), dtValueDate.GetDay(),
dtValueTime.GetHour(), dtValueTime.GetMinute(), dtValueTime.GetSecond());
pItem->m_undefined = false;
GetOwner()->SendMessage(WM_PG_ITEMCHANGED, pItem->m_id);
Invalidate();
}
}
else if (pItem->m_type == IT_COLOR)
{
CColorDialog dlg(pItem->m_clrValue, 0, GetParent());
if (dlg.DoModal() == IDOK)
{
pItem->m_clrValue = dlg.GetColor();
pItem->m_undefined = false;
GetOwner()->SendMessage(WM_PG_ITEMCHANGED, pItem->m_id);
Invalidate();
}
}
else if (pItem->m_type == IT_FILE)
{
CFileDialog dlg(TRUE, NULL, pItem->m_strValue.c_str(), OFN_HIDEREADONLY|OFN_OVERWRITEPROMPT, pItem->m_options.front().c_str(), GetParent());
if (dlg.DoModal() == IDOK)
{
pItem->m_strValue = dlg.GetPathName();
pItem->m_undefined = false;
GetOwner()->SendMessage(WM_PG_ITEMCHANGED, pItem->m_id);
Invalidate();
}
}
else if (pItem->m_type == IT_FOLDER)
{
CPropertyGridDirectoryPicker::m_strTitle = pItem->m_options.front();
if (CPropertyGridDirectoryPicker::PickDirectory(pItem->m_strValue, GetParent()->GetSafeHwnd()))
{
pItem->m_undefined = false;
GetOwner()->SendMessage(WM_PG_ITEMCHANGED, pItem->m_id);
Invalidate();
}
}
else if (pItem->m_type == IT_FONT)
{
CFontDialog dlg(&pItem->m_lfValue, CF_EFFECTS|CF_SCREENFONTS, NULL, GetParent());
if (dlg.DoModal() == IDOK)
{
memcpy(&pItem->m_lfValue, dlg.m_cf.lpLogFont, sizeof(LOGFONT));
pItem->m_undefined = false;
GetOwner()->SendMessage(WM_PG_ITEMCHANGED, pItem->m_id);
Invalidate();
}
}
else if (pItem->m_type == IT_CUSTOM)
{
if (pItem->m_pCustom->OnEditItem())
{
GetOwner()->SendMessage(WM_PG_ITEMCHANGED, pItem->m_id);
Invalidate();
}
}
else
{
assert(false);
}
}
}