Files
Blocks3D/propertyGrid.cpp
2018-10-21 19:30:21 -07:00

4440 lines
145 KiB
C++
Raw Blame History

//////////////////////////////////////////////////////////////////////////////
///
/// @file propertyGrid.c
///
/// @brief A property grid control in Win32 SDK C.
///
/// @author David MacDermot
///
/// @par Comments:
/// This source is distributed in the hope that it will be useful,
/// but WITHOUT ANY WARRANTY; without even the implied warranty of
/// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
///
/// @date 2-27-16
///
/// @todo
///
/// @bug
///
//////////////////////////////////////////////////////////////////////////////
//DWM 1.1: Version 1.1 changes labelled thus.
//DWM 1.2: Version 1.2 changes labelled thus.
//DWM 1.3: Version 1.3 changes labelled thus.
//DWM 1.4: Version 1.4 changes labelled thus.
//DWM 1.5: Version 1.5 changes labelled thus.
//DWM 1.6: Version 1.6 changes labelled thus.
//DWM 1.7: Version 1.7 changes labelled thus.
//DWM 1.8: Version 1.8 changes labelled thus.
//DWM 1.9: Version 1.9 changes labelled thus.
//DWM 1.9: Suppress POCC Warning "Argument x to 'sscanf' does not match the format string;
// expected 'unsigned char *' but found 'unsigned long'"
#ifdef __POCC__
#pragma warn(disable:2234)
#endif
#ifndef _WIN32_WINNT // Necessary for WM_MOUSEWHEEL support
#define _WIN32_WINNT 0x0500
#endif
// MSVC++ Support
#ifndef _CRT_SECURE_NO_WARNINGS
#define _CRT_SECURE_NO_WARNINGS
#endif
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <windowsx.h>
#include <commctrl.h>
#include <commdlg.h>
#include <shlobj.h>
#include <stdlib.h>
#include <stdio.h>
#include <wchar.h>
#include <tchar.h>
#include <uxtheme.h>
#include "propertyGrid.h"
// Begin MSVC++ Support
#ifdef _stprintf
#undef _stprintf
#endif
#ifdef _UNICODE
#define _tmemcpy wmemcpy
#define _tmemmove wmemmove
#define _tmemset wmemset
#define _stprintf swprintf
#else
#define _tmemcpy memcpy
#define _tmemmove memmove
#define _tmemset memset
#define _stprintf _snprintf
#endif
#define ToolTip_AddTool(hwnd,lpti) (BOOL)SNDMSG((hwnd),TTM_ADDTOOL,0,(LPARAM)(LPTOOLINFO)(lpti))
#define ToolTip_EnumTools(hwnd,iTool,lpti) (BOOL)SNDMSG((hwnd),TTM_ENUMTOOLS,(WPARAM)(UINT)(iTool),(LPARAM)(LPTOOLINFO)(lpti))
#define ToolTip_UpdateTipText(hwnd,lpti) (void)SNDMSG((hwnd),TTM_UPDATETIPTEXT,0,(LPARAM)(LPTOOLINFO)(lpti))
#define ToolTip_NewToolRect(hwnd,lpti) (void)SNDMSG((hwnd),TTM_NEWTOOLRECT,0,(LPARAM)(LPTOOLINFO)(lpti))
// End MSVC++ Support
#define ID_LISTBOX 2000 ///<An Id for the Listbox
#define ID_LISTMAP 2001 ///<An Id for the Listmap
#define ID_PROPDESC 2002 ///<An Id for the Property description
#define ID_EDIT 2003 ///<An Id for an editor
#define ID_BUTTON 2004 ///<An Id for the button
#define ID_IPEDIT 2005 ///<An Id for an editor
#define ID_DATE 2006 ///<An Id for an editor
#define ID_TIME 2007 ///<An Id for an editor
#define ID_COMBO 2008 ///<An Id for an editor
#define ID_EDITCOMBO 2009 ///<An Id for an editor
#define WIDTH_PART0 16 ///< Constant
#define WIDTH_PART2 20 ///< Constant
#define HEIGHT_DESC 80 ///< Constant
#define MINIMUM_ITEM_HEIGHT 20 ///< Constant
//DWM 1.2: Converted the following 4 items to constants
#define SELECT _T("T") ///< PIT_CHECK select
#define UNSELECT _T("F") ///< PIT_CHECK unselect
#define CHECKED SELECT ///< PIT_CHECK checked
#define UNCHECKED UNSELECT ///< PIT_CHECK unchecked
//DWM 1.3: Added
#define WPRC _T("Wprc")
//DWM 1.8: Added
#define FLATCHECKS 0x01 ///< Draw flat checks flag
/// @name Macroes
/// @{
/// @def NELEMS(a)
///
/// @brief Computes number of elements of an array.
///
/// @param a An array.
#define NELEMS(a) (sizeof(a) / sizeof((a)[0]))
/// @def HEIGHT(rect)
///
/// @brief Given a RECT, Computes height.
///
/// @param rect A RECT struct.
#define HEIGHT(rect) ((LONG)(rect.bottom - rect.top))
/// @def WIDTH(rect)
///
/// @brief Given a RECT, Computes width.
///
/// @param rect A RECT struct.
#define WIDTH(rect) ((LONG)(rect.right - rect.left))
/// @def ListBox_ItemFromPoint(hwndCtl, xPos, yPos)
///
/// @brief Gets the zero-based index of the item nearest the specified point
/// in a list box.
///
/// @param hwndCtl The handle of a listbox.
/// @param xPos The x coordinate of a point.
/// @param yPos The y coordinate of a point.
///
/// @returns The return value contains the index of the nearest item
/// in the low-order word. The high-order word is zero if
/// the specified point is in the client area of the list box,
/// or one if it is outside the client area.
#define ListBox_ItemFromPoint(hwndCtl, xPos, yPos) \
(DWORD)SendMessage((hwndCtl),LB_ITEMFROMPOINT, \
(WPARAM)0,MAKELPARAM((UINT)(xPos),(UINT)(yPos)))
/// @def Refresh(hwnd)
///
/// @brief Refresh a window.
///
/// @param hwnd The handle to the window.
#define Refresh(hwnd) RedrawWindow((hwnd), NULL, NULL, \
RDW_ERASE|RDW_INVALIDATE|RDW_UPDATENOW)
/// @def AllocatedString_Replace(lpszTarget, lpszReplace)
///
/// @brief Replace an allocated string.
///
/// @param lpszTarget The existing allocated string.
/// @param lpszReplace The new string.
#define AllocatedString_Replace(lpszTarget, lpszReplace) \
(free((lpszTarget)), (lpszTarget) = NewString(lpszReplace))
/// @}
LPCTSTR g_szClassName = _T("PropGridCtl"); ///< The classname.
/// @brief A nice assortment of custom colors.
static COLORREF g_CustomColors[] = {
0xDCDCDC, 0xCDCDDB, 0xBDBED2, 0xE3C4C3,
0xFFE0C0, 0xE0C8AB, 0xD7AFB0, 0xF0BFEB,
0xE2A7DA, 0x9697FF, 0xBFBFFF, 0xBBDBFF,
0xCBFFFF, 0xBFEFCB, 0xA6DEB3, 0x82D28B
};
/****************************************************************************/
//Functions
static LRESULT CALLBACK Grid_Proc(HWND, UINT, WPARAM, LPARAM);
static LRESULT CALLBACK ListBox_Proc(HWND, UINT, WPARAM, LPARAM);
static VOID Grid_NotifyParent(VOID);
/// @brief Default window procedure for the grid and child windows.
///
/// @param hwnd Handle of grid or child.
/// @param msg Which message?
/// @param wParam Message parameter.
/// @param lParam Message parameter.
///
/// @returns LRESULT depends on message.
static LRESULT DefProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
//DWM 1.3: Added
return CallWindowProc((WNDPROC)GetProp(hwnd, WPRC), hwnd, msg, wParam, lParam);
}
/// @brief Declare an inline rect.
///
/// @param left The left coordinate.
/// @param top The top coordinate.
/// @param right The right coordinate.
/// @param bottom The bottom coordinate.
static inline PRECT MAKE_PRECT(LONG left, LONG top, LONG right, LONG bottom)
{
static RECT rc;
memset(&rc, 0, sizeof(rc));
rc.left = left;
rc.top = top;
rc.right = right;
rc.bottom = bottom;
return &rc;
}
/// @brief Determine if this is running on XP or lower.
///
/// @returns TRUE if OS is XP or earlier.
static BOOL IsWindowsXPorLower(VOID) {
//DWM 1.4: Added
OSVERSIONINFO osvi;
ZeroMemory(&osvi, sizeof(OSVERSIONINFO));
osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
GetVersionEx(&osvi);
return osvi.dwMajorVersion < 6;
}
#pragma region Instance Data
/// @var LISTBOXITEM
/// @brief An item object
/// @var LPLISTBOXITEM
/// @brief Pointer to an item
/// @struct tagLISTBOXITEM
/// @brief This is the data associated with a grid item
typedef struct tagLISTBOXITEM {
LPTSTR lpszCatalog; ///< Catalog (group) name
LPTSTR lpszPropName;///< Property (item) name
LPTSTR lpszMisc; ///< Item specific data string
LPTSTR lpszPropDesc;///< Property (item) description
LPTSTR lpszCurValue;///< Property (item) value
INT iItemType; ///< Property (item) type identifier
BOOL fCollapsed; ///< Catalog (group) collapsed flag
} LISTBOXITEM , *LPLISTBOXITEM;
/// @var INSTANCEDATA
/// @brief Data for this instance of the control
/// @var LPINSTANCEDATA
/// @brief Pointer instance data
/// @struct tagINSTANCEDATA
/// @brief This is the data associated with an instance of the grid
typedef struct tagINSTANCEDATA {
HINSTANCE hInstance; ///< Handle to this instance
LPLISTBOXITEM lpCurrent;///< Currently selected grid item
HWND hwndParent; ///< Handle of grid's parent
HWND hwndToolTip; ///< ToolTip handle
HWND hwndPropDesc; ///< Description pane handle
HWND hwndListBox; ///< Handle of the visible list box
HWND hwndListMap; ///< Handle of the hidden list box
HWND hwndCtl1; ///< Handle of the primary edit control
HWND hwndCtl2; ///< Handle of the secondary edit control
INT iHDivider; ///< Position of horizontal divider
INT iVDivider; ///< Position of verticle divider
INT iDescHeight; ///< Height of description pane
INT iPrevSel; ///< Index of previously selected item
BOOL fXpOrLower; ///< TRUE is running on Xp or earlier version of OS
BOOL fGotFocus; ///< TRUE while focus resides within property grid
BOOL fScrolling; ///< TRUE while scrolling
BOOL fTracking; ///< TRUE while moving dividers
LONG nOldDivX; ///< Previous divider x position
LONG nOldDivY; ///< Previous divider y position
LONG nDivTop; ///< Horizontal Divider positon limit
LONG nDivBtm; ///< Horizontal Divider positon limit
LONG nDivLft; ///< Vertical Divider positon limit
LONG nDivRht; ///< Vertical Divider positon limit
} INSTANCEDATA , *LPINSTANCEDATA;
static LPINSTANCEDATA g_lpInst; ///< instance data (this) pointer
/// @brief Get the Instance data associated with this instance.
///
/// @param hControl Handle to Current instance.
/// @param ppInstanceData - Pointer to the address of an INSTANCEDATA struct.
///
/// @returns TRUE if successful
static BOOL Control_GetInstanceData(HWND hControl, LPINSTANCEDATA * ppInstanceData)
{
*ppInstanceData = (LPINSTANCEDATA)GetProp(hControl, (LPCTSTR)_T("lpInsData"));
if (NULL != *ppInstanceData)
return TRUE;
return FALSE;
}
/// @brief Allocate the Instance data associated with this instance.
///
/// @param hControl Handle to current instance.
/// @param pInstanceData Pointer to an INSTANCEDATA struct
///
/// @returns TRUE if successful
static BOOL Control_CreateInstanceData(HWND hControl, LPINSTANCEDATA pInstanceData)
{
LPINSTANCEDATA pInst = (LPINSTANCEDATA)malloc(sizeof(INSTANCEDATA));
memmove(pInst, pInstanceData, sizeof(INSTANCEDATA));
return SetProp(hControl, (LPCTSTR)_T("lpInsData"), pInst);
}
/// @brief Free the instance data allocation of an instance of the Grid Control.
///
/// @param hControl Handle to current instance.
///
/// @returns TRUE if successful
static BOOL Control_FreeInstanceData(HWND hControl)
{
LPINSTANCEDATA pInst;
if (Control_GetInstanceData(hControl, &pInst))
{
free((LPINSTANCEDATA)pInst);
RemoveProp(hControl, (LPCTSTR)_T("lpInsData"));
return TRUE;
}
return FALSE;
}
/// @brief Get Item data associated with a property grid item.
///
/// @param hwnd Handle of a listbox.
/// @param idx The item index.
///
/// @returns a listbox item pointer if successful, otherwise NULL
static LPLISTBOXITEM ListBox_GetItemDataSafe(HWND hwnd, INT idx)
{
LRESULT lres = ListBox_GetItemData(hwnd, idx);
// Don't return LB_ERR cast to an item!
if (LB_ERR == lres)
return NULL;
return (LPLISTBOXITEM)lres;
}
#pragma endregion Instance Data
#pragma region memory management
/// @brief Allocate and store a string.
///
/// @param str The string to store.
///
/// @returns a Pointer to the allocated string.
static LPTSTR NewString(LPTSTR str)
{
if(NULL == str || _T('\0') == *str) str = _T("");
LPTSTR tmp = (LPTSTR)calloc(_tcslen(str) + 1, sizeof(TCHAR));
if(NULL == tmp)
{
return (LPTSTR)calloc(1, sizeof(TCHAR));
}
return (LPTSTR)_tmemmove(tmp, str, _tcslen(str));
}
/// @brief Allocate and store a string array (double-null-terminated string).
///
/// @param szzStr The double-null-terminated string to store.
///
/// @returns a Pointer to the allocated string array.
static LPTSTR NewStringArray(LPTSTR szzStr)
{
if(NULL == szzStr || _T('\0') == *szzStr) szzStr = _T("");
//Determine total buffer length
INT iLen = 0;
//Walk the buffer to find the terminating empty string.
for (LPTSTR p = szzStr; *p; (p += _tcslen(p) + 1, iLen = p - szzStr)) ;
//Allocate for array
LPTSTR tmp = (LPTSTR)calloc(iLen + 1, sizeof(TCHAR));
if(NULL == tmp)
{
return (LPTSTR)calloc(1, sizeof(TCHAR));
}
return (LPTSTR)_tmemmove(tmp, szzStr, iLen);
}
/// @brief Allocate and populate a list box item data structure.
///
/// @param szCatalog The catalog this item belongs to.
/// @param szPropName The item's name.
/// @param szCurValue The item's current value.
/// @param szMisc This data varies with item type.
/// @param szPropDesc The item's description string.
/// @param iItemType The item type designation.
///
/// @returns a Pointer to the allocated list box item.
static LPLISTBOXITEM NewItem(LPTSTR szCatalog, LPTSTR szPropName, LPTSTR szCurValue, LPTSTR szMisc, LPTSTR szPropDesc, INT iItemType)
{
LPLISTBOXITEM lpItem = (LPLISTBOXITEM)calloc(1, sizeof(LISTBOXITEM));
lpItem->iItemType = iItemType;
lpItem->lpszCatalog = NewString(szCatalog);
lpItem->lpszPropName = NewString(szPropName);
lpItem->lpszCurValue = NewString(szCurValue);
if (PIT_COMBO == iItemType || PIT_EDITCOMBO == iItemType)
lpItem->lpszMisc = NewStringArray(szMisc);
else
lpItem->lpszMisc = NewString(szMisc);
lpItem->lpszPropDesc = NewString(szPropDesc);
return lpItem;
}
/// @brief Free a list box item's data structure.
///
/// @param lpItem A pointer to a LISTBOXITEM object.
///
/// @returns VOID.
static VOID DeleteItem(LPLISTBOXITEM lpItem)
{
free(lpItem->lpszCatalog);
free(lpItem->lpszPropName);
if(PIT_CHECK != lpItem->iItemType) //DWM 1.2: Don't attempt to free a constant
{
free(lpItem->lpszCurValue);
free(lpItem->lpszMisc);
}
free(lpItem->lpszPropDesc);
free(lpItem);
}
/// @brief Handle the WM_DELETEIETM message.
///
/// @param hwnd The handle of a list box (in this case the list map).
/// @param lpDeleteItem A pointer to the delete item struct.
///
/// @returns VOID.
static VOID Grid_OnDeleteItem(HWND hwnd, const DELETEITEMSTRUCT * lpDeleteItem)
{
if (g_lpInst->hwndListMap == lpDeleteItem->hwndItem)
DeleteItem((LPLISTBOXITEM)lpDeleteItem->itemData);
// Catalogs only reside in visible list box
if (g_lpInst->hwndListBox == lpDeleteItem->hwndItem)
{
if(PIT_CATALOG == ((LPLISTBOXITEM)lpDeleteItem->itemData)->iItemType)
DeleteItem((LPLISTBOXITEM)lpDeleteItem->itemData);
}
}
#pragma endregion memory management
#pragma region Drawing
/// @brief Pass keyboard focus to parent and refresh grid.
///
/// @returns VOID.
static VOID SetFocusToParent(VOID)
{
SetFocus(g_lpInst->hwndParent);
}
/// @brief Handle WM_KILLFOCUS in editors.
///
/// @param hwnd Handle of the editor.
/// @param hwndNewFocus Handle of the window that recieved focus.
///
/// @returns VOID.
static VOID Editor_OnKillFocus(HWND hwnd, HWND hwndNewFocus)
{
//DWM 1.3: Added so that grid selection is drawn inactive
// when grid doesn't have focus.
g_lpInst->fGotFocus =
(NULL != hwndNewFocus &&
(g_lpInst->hwndListBox == hwndNewFocus ||
g_lpInst->hwndCtl1 == hwndNewFocus ||
g_lpInst->hwndCtl2 == hwndNewFocus ||
g_lpInst->hwndPropDesc == hwndNewFocus ||
g_lpInst->hwndToolTip == hwndNewFocus));
if(!g_lpInst->fGotFocus)
Refresh(g_lpInst->hwndListBox);
}
/// @brief Handle WM_KILLFOCUS in listbox.
///
/// @param hwnd Handle of the editor.
/// @param hwndNewFocus Handle of the window that recieved focus.
///
/// @returns VOID.
static VOID ListBox_OnKillFocus(HWND hwnd, HWND hwndNewFocus)
{
if (NULL != g_lpInst->lpCurrent)
{
if(PIT_CHECK == g_lpInst->lpCurrent->iItemType)//DWM 1.3: Added
g_lpInst->lpCurrent->lpszMisc = UNSELECT;
}
Editor_OnKillFocus(hwnd, hwndNewFocus);
}
/// @brief Set a control's font to bold or back to normal.
///
/// @param hwndCtl The handle of a control.
/// @param fBold TRUE if bold desired.
///
/// @returns HFONT A new font with the desired font weight.
static HFONT Font_SetBold(HWND hwndCtl, BOOL fBold)
{
HFONT hFont;
LOGFONT lf;
// Get a handle to the control's font object
hFont = (HFONT)SendMessage(hwndCtl, WM_GETFONT, 0, 0);
// Pull the handle into a Logical Font UDT type
GetObject(hFont, sizeof(LOGFONT), &lf);
// Determine if that font should be bold or not
if (fBold)
lf.lfWeight = FW_BOLD;
else
lf.lfWeight = FW_NORMAL;
// Create a new font based off the logical font UDT
return CreateFontIndirect(&lf);
}
/// @brief Draw a filled rectangle of a desired color.
///
/// @param hdc A handle to a device context.
/// @param lprc The address of a RECT structure with drawing coordinates.
/// @param clr The desired fill color value.
///
/// @returns VOID.
static VOID FillSolidRect(HDC hdc, LPRECT lprc, COLORREF clr)
{
HBRUSH hbrush = CreateSolidBrush(clr);
FillRect(hdc, lprc, hbrush);
DeleteObject(hbrush);
}
/// @brief Draw a line.
///
/// @param hdc A handle to a device context.
/// @param x1 From point x-coordinate.
/// @param y1 From point y-coordinate.
/// @param x2 To point x-coordinate.
/// @param y2 To point y-coordinate.
///
/// @returns VOID.
static VOID DrawLine(HDC hdc, LONG x1, LONG y1, LONG x2, LONG y2)
{
MoveToEx(hdc, x1, y1, NULL);
LineTo(hdc, x2, y2);
}
/// @brief Draw an inverted line.
///
/// @param hdc A handle to a device context.
/// @param x1 From point x-coordinate.
/// @param y1 From point y-coordinate.
/// @param x2 To point x-coordinate.
/// @param y2 To point y-coordinate.
///
/// @returns VOID.
static VOID InvertLine(HDC hdc, LONG x1, LONG y1, LONG x2, LONG y2)
{
INT nOldMode = SetROP2(hdc, R2_NOT);
DrawLine(hdc, x1, y1, x2, y2);
SetROP2(hdc, nOldMode);
}
/// @brief Draw specified borders of a rectangle.
///
/// @param hdc A handle to a device context.
/// @param lprc The address of a RECT structure with drawing coordinates.
/// @param dwBorder specifies which borders to draw.
/// @param clr The desired line color value.
///
/// @returns VOID.
static VOID DrawBorder(HDC hdc, LPRECT lprc, DWORD dwBorder, COLORREF clr)
{
LOGPEN oLogPen;
HPEN hOld;
GetObject(hOld = (HPEN) SelectObject(hdc, GetStockObject(BLACK_PEN)), sizeof(oLogPen), &oLogPen);
oLogPen.lopnColor = clr;
SelectObject(hdc, CreatePenIndirect(&oLogPen));
if (dwBorder & BF_LEFT)
DrawLine(hdc, lprc->left, lprc->top, lprc->left, lprc->bottom);
if (dwBorder & BF_TOP)
DrawLine(hdc, lprc->left, lprc->top, lprc->right, lprc->top);
if (dwBorder & BF_RIGHT)
DrawLine(hdc, lprc->right, lprc->top, lprc->right, lprc->bottom);
if (dwBorder & BF_BOTTOM)
DrawLine(hdc, lprc->left, lprc->bottom, lprc->right, lprc->bottom);
DeleteObject(SelectObject(hdc, hOld));
}
/// @brief Convert string value, Ex: "255 255 255" to RGB COLORREF.
///
/// @param src The string to convert.
///
/// @returns COLORREF The converted color.
static COLORREF GetColor(LPTSTR src)
{
INT RVal, GVal, BVal;
RVal = GVal = BVal = 0;
if (_tcslen(src) > 0)
_stscanf(src, _T("%d,%d,%d"), &RVal, &GVal, &BVal);
return RGB(RVal, GVal, BVal);
}
/// @brief Handle the WM_PAINT message for most of the edit controls.
/// Obliterate the control's border to get that integrated flat look.
///
/// @param hwnd The control's handle.
/// @param msg The window message (in this case WM_PAINT).
/// @param wParam The message WPARAM.
/// @param lParam The message LPARAM.
///
/// @returns BOOL Always TRUE.
static BOOL Editor_OnPaint(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
HDC hdc = GetWindowDC(hwnd);
RECT rect;
// First let the system do its thing
DefProc(hwnd, msg, wParam, lParam);
// Next obliterate the border
GetWindowRect(hwnd, &rect);
MapWindowPoints(HWND_DESKTOP, hwnd, (LPPOINT) & rect.left, 2);
rect.top += 2;
rect.left += 2;
DrawBorder(hdc, &rect, BF_RECT, GetSysColor(COLOR_WINDOW));
rect.top += 1;
rect.left += 1;
rect.bottom += 1;
rect.right += 1;
DrawBorder(hdc, &rect, BF_RECT, GetSysColor(COLOR_WINDOW));
ReleaseDC(hwnd, hdc);
return TRUE;
}
/// @brief Set the colors used to paint controls in WM_CTLCOLORLISTBOX handler.
///
/// @param hdc Handle of a device context.
/// @param TxtColr Desired text color.
/// @param BkColr Desired back color.
///
/// @returns HBRUSH A reusable brush object.
static HBRUSH SetColor(HDC hdc, COLORREF TxtColr, COLORREF BkColr)
{
static HBRUSH ReUsableBrush;
DeleteObject(ReUsableBrush);
ReUsableBrush = CreateSolidBrush(BkColr);
SetTextColor(hdc, TxtColr);
SetBkColor(hdc, BkColr);
return ReUsableBrush;
}
#pragma endregion Drawing
#pragma region ToolTip
/// @brief Create the tool tip object that will display tips for grid items.
///
/// @param hInstance The handle of an instance.
/// @param hwndParent The handle of the tooltip's parent.
///
/// @returns HWND A handle to a tooltip object.
static HWND CreateToolTip(HINSTANCE hInstance, HWND hwndParent)
{
RECT rect;
TOOLINFO ti;
DWORD dwStyle, dwExStyle;
HWND hwnd;
dwStyle = WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP;
dwExStyle = WS_EX_TOPMOST;
hwnd = CreateWindowEx(dwExStyle, // ex style
TOOLTIPS_CLASS, // class name - defined in commctrl.h
NULL, // dummy text
dwStyle, // style
CW_USEDEFAULT, // x position
CW_USEDEFAULT, // y position
CW_USEDEFAULT, // width
CW_USEDEFAULT, // height
hwndParent, // parent
NULL, // ID
hInstance, // instance
NULL); // no extra data
if (!hwnd)
return NULL;
SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
SendMessage(hwnd, WM_SETFONT, (WPARAM)GetStockObject(DEFAULT_GUI_FONT), 0);
GetClientRect(hwndParent, &rect);
// Initialize members of toolinfo structure
ti.cbSize = sizeof(TOOLINFO);
ti.uFlags = TTF_SUBCLASS;
ti.hwnd = hwndParent;
ti.hinst = hInstance;
ti.uId = 0;
ti.lpszText = _T("");
// ToolTip control will cover the entire window
ti.rect.left = rect.left;
ti.rect.top = rect.top;
ti.rect.right = rect.right;
ti.rect.bottom = rect.bottom;
ToolTip_AddTool(hwnd, &ti);
return hwnd;
}
#pragma endregion ToolTip
#pragma region Static
/// @brief Create the static control for the property description pane.
///
/// @param hInstance The handle of an instance.
/// @param hwndParent The handle of the parent (the Property Grid window).
/// @param id An id tag for this control
///
/// @returns HWND A handle to a static control.
static HWND CreateStatic(HINSTANCE hInstance, HWND hwndParent, INT id)
{
DWORD dwStyle, dwExStyle;
HWND hwnd;
dwStyle = WS_CHILD | SS_LEFT;
dwExStyle = WS_EX_LEFT | WS_EX_CLIENTEDGE;
hwnd = CreateWindowEx(dwExStyle,
WC_STATIC,
NULL,
dwStyle,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
hwndParent,
(HMENU)id,
hInstance,
NULL);
if (!hwnd)
return NULL;
SendMessage(hwnd, WM_SETFONT, (WPARAM)GetStockObject(DEFAULT_GUI_FONT), 0);
return hwnd;
}
#pragma endregion Static
#pragma region ListBox and ListMap
/// @brief Create the owner draw listbox for item display.
///
/// @param hInstance The handle of an instance.
/// @param hwndParent The handle of the parent (the Property Grid window).
/// @param id An id tag for this control
///
/// @par Comments:
/// There is some kind of bug where a listbox created as an
/// LBS_OWNERDRAWVARIABLE will scroll erratically when the mouse
/// wheel is used.
///
/// @returns HWND A handle to the owner draw listbox control.
static HWND CreateListBox(HINSTANCE hInstance, HWND hwndParent, INT id)
{
DWORD dwStyle, dwExStyle;
HWND hwnd;
dwStyle = WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_VSCROLL | WS_HSCROLL |
LBS_NOTIFY | LBS_OWNERDRAWFIXED |
LBS_NOINTEGRALHEIGHT | LBS_WANTKEYBOARDINPUT;
dwExStyle = WS_EX_LEFT | WS_EX_RTLREADING | WS_EX_RIGHTSCROLLBAR |
WS_EX_CLIENTEDGE;
hwnd = CreateWindowEx(dwExStyle,
WC_LISTBOX,
NULL,
dwStyle,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
hwndParent,
(HMENU)id,
hInstance,
NULL);
if (!hwnd)
return NULL;
SendMessage(hwnd, WM_SETFONT, (WPARAM)GetStockObject(DEFAULT_GUI_FONT), 0);
// Subclass listbox and save the old proc
SetProp(hwnd, WPRC, (HANDLE)GetWindowLongPtr(hwnd, GWLP_WNDPROC));
SubclassWindow(hwnd, ListBox_Proc);
return hwnd;
}
/// @brief Create a minimal hidden listbox (the listmap) to store pointers
/// to all items, including those not displayed.
///
/// @param hInstance The handle of an instance.
/// @param hwndParent The handle of the parent (the Property Grid window).
/// @param id An id tag for this control
///
/// @returns HWND A handle to the hidden listmap control.
static HWND CreateListMap(HINSTANCE hInstance, HWND hwndParent, INT id)
{
DWORD dwStyle, dwExStyle;
HWND hwnd;
dwStyle = WS_CHILD | LBS_OWNERDRAWFIXED; // Want WM_DELETEITEM messages
dwExStyle = 0;
hwnd = CreateWindowEx(dwExStyle,
WC_LISTBOX,
NULL,
dwStyle,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
hwndParent,
(HMENU)id,
hInstance,
NULL);
if (!hwnd)
return NULL;
return hwnd;
}
#pragma endregion ListBox and ListMap
#pragma region Edit Control
/// @brief Window procedure for the edit control.
///
/// @param hEdit Handle of editor.
/// @param msg Which message?
/// @param wParam Message parameter.
/// @param lParam Message parameter.
///
/// @returns LRESULT depends on message.
static LRESULT CALLBACK Edit_Proc(HWND hEdit, UINT msg, WPARAM wParam, LPARAM lParam)
{
HWND hGParent = GetParent(GetParent(hEdit));
// Note: Instance data is attached to Edit's grandparent
Control_GetInstanceData(hGParent, &g_lpInst);
if (WM_DESTROY == msg) // Unsubclass the Edit Control
{
SetWindowLongPtr(hEdit, GWLP_WNDPROC, (DWORD_PTR)GetProp(hEdit, WPRC)); //DWM 1.5: fixed cast
RemoveProp(hEdit, WPRC);
return 0;
}
else if (WM_KILLFOCUS == msg)
{
FORWARD_WM_CHAR(hEdit, VK_RETURN, 0, SNDMSG);//DWM 1.6: force update of grid data
Editor_OnKillFocus(hEdit, (HWND)wParam);
}
else if (WM_PAINT == msg) // Obliterate border
{
return Editor_OnPaint(hEdit, msg, wParam, lParam);
}
else if (WM_MOUSEWHEEL == msg)
{
FORWARD_WM_CHAR(hEdit, VK_RETURN, 0, SNDMSG);
}
else if (WM_CHAR == msg && VK_RETURN == wParam)
{
if (NULL != g_lpInst->lpCurrent)
{
TCHAR buf[MAX_PATH];
Edit_GetText(hEdit, buf, sizeof buf);
AllocatedString_Replace(g_lpInst->lpCurrent->lpszCurValue, buf);
}
ShowWindow(hEdit, SW_HIDE);
SetFocus(g_lpInst->hwndListBox);
Grid_NotifyParent();
return TRUE; // handle Enter (NO BELL)
}
else if (WM_KEYDOWN == msg)
{
switch (wParam)
{
case VK_TAB:
if (GetKeyState(VK_SHIFT) & 0x8000)
{
FORWARD_WM_CHAR(hEdit, VK_RETURN, 0, SNDMSG);
}
else //DWM 1.3: Added Focus to grid parent
{
SetFocusToParent();
}
return TRUE;
case VK_ESCAPE:
ShowWindow(hEdit, SW_HIDE);
SetFocus(g_lpInst->hwndListBox);
return FALSE;
}
}
return DefProc(hEdit, msg, wParam, lParam);
}
/// @brief Create an Edit control to edit PIT_EDIT fields.
///
/// @param hInstance The handle of an instance.
/// @param hwndParent The handle of the parent (the visible listbox).
/// @param id An id tag for this control.
///
/// @returns HWND A handle to the edit control.
static HWND CreateEdit(HINSTANCE hInstance, HWND hwndParent, INT id)
{
DWORD dwStyle, dwExStyle;
HWND hwnd;
dwStyle = WS_CHILD | ES_LEFT | ES_AUTOHSCROLL | ES_MULTILINE | ES_WANTRETURN;
dwExStyle = WS_EX_LEFT | WS_EX_CLIENTEDGE;
hwnd = CreateWindowEx(dwExStyle, WC_EDIT, TEXT(""), dwStyle, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, hwndParent, (HMENU)id, hInstance, NULL);
if (!hwnd)
return NULL;
//DWM 1.4: Added Disable visual styles for this control.
SetWindowTheme(hwnd, L" ", L" ");
SendMessage(hwnd, WM_SETFONT, (WPARAM)GetStockObject(DEFAULT_GUI_FONT), 0L);
// Subclass Editor and save the OldProc
SetProp(hwnd, WPRC, (HANDLE)GetWindowLongPtr(hwnd, GWLP_WNDPROC));
SubclassWindow(hwnd, Edit_Proc);
return hwnd;
}
#pragma endregion Edit Control
#pragma region IP Control
/// @brief Window procedure for the ipedit control.
///
/// @param hwnd Handle of the ipedit or a child edit control.
/// @param msg Which message?
/// @param wParam Message parameter.
/// @param lParam Message parameter.
///
/// @par Comments
/// The ipedit control wraps four edit control children.
/// Each child needs to be subclassed in order to handle
/// keyboard events. Each edit control posts some notification
/// to the parent ipedit control and I use this behavior to capture
/// and subclass these children. These children are subclassed to
/// this very procedure and so 'hwnd' could be parent ipedit or one
/// of the child Edits. Care then, must be taken to differentiate between
/// child and parent. I do this by getting the class name and restricting
/// the handling of certain messages to one or the other control type.
///
/// @returns LRESULT depends on message.
static LRESULT CALLBACK IpEdit_Proc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
static TCHAR buf[MAX_PATH];
HWND hGParent = GetParent(GetParent(hwnd));
// Note: Instance data is attached to ipedit's grandparent
// or the edit field's greatgrandparent
GetClassName(hwnd, buf, NELEMS(buf));
BOOL fEdit = (0 == _tcsicmp(buf, WC_EDIT));
if (fEdit)
Control_GetInstanceData(GetParent(hGParent), &g_lpInst);
else
Control_GetInstanceData(hGParent, &g_lpInst);
HWND hIpEdit = fEdit ? GetParent(hwnd) : hwnd;
if (WM_DESTROY == msg) //Unsubclass the ipedit or child edit control
{
SetWindowLongPtr(hwnd, GWLP_WNDPROC, (DWORD_PTR)GetProp(hwnd, WPRC)); //DWM 1.5: fixed cast
RemoveProp(hwnd, WPRC);
return 0;
}
else if (WM_KILLFOCUS == msg)//DWM 1.3: Added
{
if(fEdit)
{
// Determine if hwndNewfocus is a child of the IpEdit
if(hIpEdit != GetParent((HWND) wParam)) //No, result of mouse click
{
FORWARD_WM_CHAR(hIpEdit, VK_RETURN, 0, SNDMSG);//DWM 1.6: force update of grid data
Editor_OnKillFocus(hIpEdit, (HWND)wParam);
}
}
}
else if (WM_CHAR == msg && VK_RETURN == wParam)
{
if (NULL != g_lpInst->lpCurrent)
{
//DWM 1.9: Updated this code to use DWORD and IPADDRESS macroes instead of 4 byte array and cast.
DWORD ip;
TCHAR buf[MAX_PATH];
if (4 == SNDMSG(hIpEdit, IPM_GETADDRESS, 0, (LPARAM) &ip))
{
_stprintf(buf, MAX_PATH, _T("%d.%d.%d.%d"), FIRST_IPADDRESS(ip), SECOND_IPADDRESS(ip), THIRD_IPADDRESS(ip), FOURTH_IPADDRESS(ip));
AllocatedString_Replace(g_lpInst->lpCurrent->lpszCurValue, buf);
}
}
ShowWindow(hIpEdit, SW_HIDE);
SetFocus(g_lpInst->hwndListBox);
Grid_NotifyParent();
return TRUE;
}
if (fEdit) //Handle keyboard events in the child edit controls
{
if (WM_GETDLGCODE == msg)
{
return DLGC_WANTALLKEYS;
}
else if (WM_MOUSEWHEEL == msg)
{
FORWARD_WM_CHAR(hwnd, VK_RETURN, 0, SNDMSG);
}
else if (WM_CHAR == msg && VK_TAB == wParam)
{
HWND hNext;
if (GetKeyState(VK_SHIFT) & 0x8000) //Shift tab
{
hNext = GetWindow(hwnd, GW_HWNDNEXT);
if (NULL == hNext) //DWM 1.3: Added Focus to Listbox
{
SetFocus(g_lpInst->hwndListBox);
return TRUE;
}
}
else
{
hNext = GetWindow(hwnd, GW_HWNDPREV);
if (NULL == hNext) //DWM 1.3: Added Focus to grid parent
{
SetFocusToParent();
Editor_OnKillFocus(hIpEdit, NULL);
return TRUE;
}
}
Edit_SetSel(hNext, 0, -1);
SetFocus(hNext);
return TRUE;
}
else if (WM_CHAR == msg && VK_ESCAPE == wParam)
{
ShowWindow(hIpEdit, SW_HIDE);
SetFocus(g_lpInst->hwndListBox);
return TRUE;
}
else if (WM_PASTE == msg)
return TRUE; //Do not allow paste
}
else //Handle messages (events) in the parent ipedit control
{
if (WM_PAINT == msg) // Obliterate border
{
return Editor_OnPaint(hwnd, msg, wParam, lParam);
}
else if (WM_COMMAND == msg)
{
// Each of the control's edit fields posts notifications on showing,
// the first time they do so we'll grab and subclass them.
HWND hwndCtl = GET_WM_COMMAND_HWND(wParam, lParam);
{
WNDPROC lpfn = (WNDPROC)GetProp(hwndCtl, WPRC);
if (NULL == lpfn)
{
//Subclass child and save the OldProc
SetProp(hwndCtl, WPRC, (HANDLE)GetWindowLongPtr(hwndCtl, GWLP_WNDPROC));
SubclassWindow(hwndCtl, IpEdit_Proc);
}
}
}
}
return DefProc(hwnd, msg, wParam, lParam);
}
/// @brief Create an ipedit control to edit PIT_IP fields.
///
/// @param hInstance The handle of an instance.
/// @param hwndParent The handle of the parent (the visible listbox).
/// @param id An id tag for this control.
/// @param lprc A pointer to RECT struct that contains initial size data.
///
/// @par Comments
/// It is not possible to create this control with CW_USEDEFAULT for width
/// and height. The control cannot be properly resized once created and
/// so it is necessary to specify width and height at the time of creation.
///
/// @returns HWND A handle to the ipedit control.
static HWND CreateIpEdit(HINSTANCE hInstance, HWND hwndParent, INT id, LPRECT lprc)
{
INITCOMMONCONTROLSEX cc;
cc.dwSize = sizeof(INITCOMMONCONTROLSEX);
cc.dwICC = ICC_INTERNET_CLASSES;
if (!InitCommonControlsEx(&cc))
return NULL;
DWORD dwStyle, dwExStyle;
HWND hwnd;
dwStyle = WS_CHILD;
dwExStyle = WS_EX_LEFT;
hwnd = CreateWindowEx(dwExStyle,
WC_IPADDRESS,
NULL,
dwStyle,
CW_USEDEFAULT, // x position can be changed after creation
CW_USEDEFAULT, // y position can be changed after creation
lprc->right - lprc->left, // width can only be set here
lprc->bottom - lprc->top, // height can only be set here
hwndParent, (HMENU)id, hInstance, NULL);
if (!hwnd)
return NULL;
SendMessage(hwnd, WM_SETFONT, (WPARAM)GetStockObject(DEFAULT_GUI_FONT), 0L);
SetProp(hwnd, WPRC, (HANDLE)GetWindowLongPtr(hwnd, GWLP_WNDPROC));
SubclassWindow(hwnd, IpEdit_Proc);
return hwnd;
}
#pragma endregion IP Control
#pragma region Button Control
/// @brief Window procedure for the button control.
///
/// @param hButton Handle of button control.
/// @param msg Which message?
/// @param wParam Message parameter.
/// @param lParam Message parameter.
///
/// @returns LRESULT depends on message.
static LRESULT CALLBACK Button_Proc(HWND hButton, UINT msg, WPARAM wParam, LPARAM lParam)
{
HWND hGParent = GetParent(GetParent(hButton));
// Note: Instance data is attached to buttons's grandparent
Control_GetInstanceData(hGParent, &g_lpInst);
if (WM_DESTROY == msg) // Unsubclass the button control
{
SetWindowLongPtr(hButton, GWLP_WNDPROC, (DWORD_PTR)GetProp(hButton, WPRC)); //DWM 1.5: fixed cast
RemoveProp(hButton, WPRC);
return 0;
}
else if (WM_KILLFOCUS == msg)
{
ShowWindow(hButton, SW_HIDE);
Editor_OnKillFocus(hButton, (HWND) wParam);
}
else if (WM_MOUSEWHEEL == msg)
{
FORWARD_WM_KEYDOWN(hButton, VK_ESCAPE, 0, 0, SNDMSG);
}
else if (WM_GETDLGCODE == msg)
{
return DLGC_WANTALLKEYS;
}
else if (WM_KEYUP == msg && VK_RETURN == wParam)
{
FORWARD_WM_KEYUP(hButton, VK_SPACE, 0, 0, SNDMSG);
return TRUE;
}
else if (WM_KEYDOWN == msg)
{
switch (wParam)
{
case VK_RETURN:
FORWARD_WM_KEYDOWN(hButton, VK_SPACE, 0, 0, SNDMSG);
return TRUE;
case VK_TAB:
if (GetKeyState(VK_SHIFT) & 0x8000)
{
FORWARD_WM_KEYDOWN(hButton, VK_ESCAPE, 0, 0, SNDMSG);
}
else //DWM 1.3: Added Focus to grid parent
{
ShowWindow(hButton, SW_HIDE);
SetFocusToParent();
}
return FALSE;
case VK_ESCAPE:
{
ShowWindow(hButton, SW_HIDE);
SetFocus(g_lpInst->hwndListBox);
return FALSE;
}
}
}
return DefProc(hButton, msg, wParam, lParam);
}
/// @brief Create button control to launch dialogs.
///
/// @param hInstance The handle of an instance.
/// @param hwndParent The handle of the parent (the visible listbox).
/// @param id An id tag for this control.
///
/// @returns HWND A handle to the button control.
static HWND CreateButton(HINSTANCE hInstance, HWND hwndParent, INT id)
{
DWORD dwStyle;
HWND hwnd;
dwStyle = WS_VISIBLE | WS_CHILD | BS_PUSHBUTTON;
hwnd = CreateWindowEx(0,
WC_BUTTON,
TEXT("..."),
dwStyle,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
hwndParent,
(HMENU)id,
hInstance,
NULL);
if (!hwnd)
return NULL;
//DWM 1.4: Added Disable visual styles for this control.
SetWindowTheme(hwnd, L" ", L" ");
SendMessage(hwnd, WM_SETFONT, (WPARAM)GetStockObject(DEFAULT_GUI_FONT), 0L);
SetProp(hwnd, WPRC, (HANDLE)GetWindowLongPtr(hwnd, GWLP_WNDPROC));
SubclassWindow(hwnd, Button_Proc);
return hwnd;
}
#pragma endregion Button Control
#pragma region Date Picker
/// @brief Window Procedure for the datepicker control.
///
/// @param hDate Handle of the datepicker.
/// @param msg Which message?
/// @param wParam Message parameter.
/// @param lParam Message parameter.
///
/// @par Comments
/// This procedure handles datepicker controls configured either as
/// date pickers or time pickers for three grid field types PIT_DATE,
/// PIT_TIME, and PIT_DATETIME.
///
/// @returns LRESULT depends on message.
static LRESULT CALLBACK DatePicker_Proc(HWND hDate, UINT msg, WPARAM wParam, LPARAM lParam)
{
HWND hGParent = GetParent(GetParent(hDate));
// Note: Instance data is attached to datepicker's grandparent
Control_GetInstanceData(hGParent, &g_lpInst);
if (WM_DESTROY == msg) // Unsubclass the control
{
SetWindowLongPtr(hDate, GWLP_WNDPROC, (DWORD_PTR)GetProp(hDate, WPRC)); //DWM 1.5: fixed cast
RemoveProp(hDate, WPRC);
return 0;
}
else if (WM_KILLFOCUS == msg) //DWM 1.3: Added this
{
if (NULL != g_lpInst->lpCurrent)
{
if(PIT_DATETIME == g_lpInst->lpCurrent->iItemType)
{
HWND hFocus = (HWND)wParam;
if(g_lpInst->hwndCtl1 == hFocus ||
g_lpInst->hwndCtl2 == hFocus) // ignore thise two windows
return 0;
ShowWindow(g_lpInst->hwndCtl2, SW_HIDE);
}
}
FORWARD_WM_CHAR(hDate, VK_RETURN, 0, SNDMSG);//DWM 1.6: force update of grid data
Editor_OnKillFocus(hDate, (HWND)wParam);
}
else if (WM_PAINT == msg) // Obliterate border
{
//DWM 1.4: Changed this handler
if(g_lpInst->fXpOrLower)
{
return Editor_OnPaint(hDate, msg, wParam, lParam);
}
else
{
// First let the system do its thing
DefProc(hDate, msg, wParam, lParam);
// Next obliterate the border
HDC hdc = GetWindowDC(hDate);
RECT rect;
GetClientRect(hDate, &rect);
DrawBorder(hdc, &rect, BF_TOPLEFT, GetSysColor(COLOR_WINDOW));
rect.top += 1;
rect.left += 1;
//DWM 1.9: Added
#ifdef __POCC__
rect.bottom -= 2;
#else
rect.bottom += 2;
#endif
DrawBorder(hdc, &rect, BF_RECT, GetSysColor(COLOR_WINDOW));
ReleaseDC(hDate, hdc);
return TRUE;
}
}
else if (WM_MOUSEWHEEL == msg)
{
FORWARD_WM_CHAR(hDate, VK_RETURN, 0, SNDMSG);
}
else if (WM_GETDLGCODE == msg)
{
return DLGC_WANTALLKEYS;
}
else if (WM_CHAR == msg && VK_RETURN == wParam)
{
if (NULL != g_lpInst->lpCurrent)
{
switch (g_lpInst->lpCurrent->iItemType)
{
case PIT_DATE:
{
SYSTEMTIME st = {0};
TCHAR buf[MAX_PATH] = {0};
DateTime_GetSystemtime(g_lpInst->hwndCtl1, &st);
GetDateFormat(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &st, NULL, buf, MAX_PATH);
AllocatedString_Replace(g_lpInst->lpCurrent->lpszCurValue, buf);
ShowWindow(g_lpInst->hwndCtl1, SW_HIDE);
}
break;
case PIT_TIME:
{
SYSTEMTIME st = {0};
TCHAR buf[MAX_PATH] = {0};
DateTime_GetSystemtime(g_lpInst->hwndCtl1, &st);
GetTimeFormat(LOCALE_USER_DEFAULT, 0, &st, _T("hh':'mm':'ss tt"), buf, MAX_PATH);
AllocatedString_Replace(g_lpInst->lpCurrent->lpszCurValue, buf);
ShowWindow(g_lpInst->hwndCtl1, SW_HIDE);
}
break;
case PIT_DATETIME:
{
SYSTEMTIME st = {0};
TCHAR buf[MAX_PATH] = {0};
DateTime_GetSystemtime(g_lpInst->hwndCtl1, &st);
GetDateFormat(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &st, NULL, buf, MAX_PATH);
_tcscat(buf, _T(" "));
DateTime_GetSystemtime(g_lpInst->hwndCtl2, &st);
GetTimeFormat(LOCALE_USER_DEFAULT, 0, &st, _T("hh':'mm':'ss tt"), (LPTSTR) (buf + _tcslen(buf)), MAX_PATH - _tcslen(buf));
AllocatedString_Replace(g_lpInst->lpCurrent->lpszCurValue, buf);
ShowWindow(g_lpInst->hwndCtl1, SW_HIDE);
ShowWindow(g_lpInst->hwndCtl2, SW_HIDE);
}
break;
}
}
SetFocus(g_lpInst->hwndListBox);
Grid_NotifyParent();
return TRUE; // handle Enter (NO BELL)
}
else if (WM_CHAR == msg && VK_TAB == wParam)
{
if (NULL != g_lpInst->lpCurrent)
{
if (PIT_DATETIME == g_lpInst->lpCurrent->iItemType)
{
//DWM 1.3: Fixed focus selection
HWND hFocus = GetFocus();
if(g_lpInst->hwndCtl1 == hFocus)
{
if (GetKeyState(VK_SHIFT) & 0x8000)
{
FORWARD_WM_CHAR(hDate, VK_RETURN, 0, SNDMSG);
}
else
{
SetFocus(g_lpInst->hwndCtl2);
}
}
else if (g_lpInst->hwndCtl2 == hFocus)
{
if (GetKeyState(VK_SHIFT) & 0x8000)
{
SetFocus(g_lpInst->hwndCtl1);
}
else
{
FORWARD_WM_CHAR(hDate, VK_RETURN, 0, SNDMSG);
SetFocusToParent();
}
}
}
else
{
if (GetKeyState(VK_SHIFT) & 0x8000)
{
FORWARD_WM_CHAR(hDate, VK_RETURN, 0, SNDMSG);
}
else
{
FORWARD_WM_CHAR(hDate, VK_RETURN, 0, SNDMSG);
SetFocusToParent();
}
}
return TRUE;
}
}
else if (WM_KEYDOWN == msg && VK_ESCAPE == wParam)
{
if (NULL != g_lpInst->lpCurrent)
{
switch (g_lpInst->lpCurrent->iItemType)
{
case PIT_DATE:
case PIT_TIME:
ShowWindow(g_lpInst->hwndCtl1, SW_HIDE);
break;
case PIT_DATETIME:
{
ShowWindow(g_lpInst->hwndCtl1, SW_HIDE);
ShowWindow(g_lpInst->hwndCtl2, SW_HIDE);
}
break;
}
}
}
return DefProc(hDate, msg, wParam, lParam);
}
/// @brief Create datepicker control configured either as a date or time picker.
///
/// @param hInstance The handle of an instance.
/// @param hwndParent The handle of the parent (the visible listbox).
/// @param id An id tag for this control.
/// @param fDate TRUE to create a date picker, FALSE for time picker.
///
/// @returns HWND A handle to the control.
static HWND CreateDatePicker(HINSTANCE hInstance, HWND hwndParent, INT id, BOOL fDate)
{
DWORD dwStyle, dwExStyle;
HWND hwnd;
dwStyle = WS_CHILD | (fDate ? DTS_SHORTDATEFORMAT : DTS_TIMEFORMAT);
dwExStyle = WS_EX_LEFT;
hwnd = CreateWindowEx(dwExStyle, DATETIMEPICK_CLASS, TEXT(""), dwStyle, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, hwndParent, (HMENU)id, hInstance, NULL);
if (!hwnd)
return NULL;
//DWM 1.4: Added Disable visual styles for this control.
SetWindowTheme(hwnd, L" ", L" ");
SendMessage(hwnd, WM_SETFONT, (WPARAM)GetStockObject(DEFAULT_GUI_FONT), 0L);
// Subclass date or timepicker and save the old proc
SetProp(hwnd, WPRC, (HANDLE)GetWindowLongPtr(hwnd, GWLP_WNDPROC));
SubclassWindow(hwnd, DatePicker_Proc);
return hwnd;
}
#pragma endregion Date Picker
#pragma region Combo Box
/// @brief Window procedure for the combobox control.
///
/// @param hwnd Handle of the combobox or a child edit control.
/// @param msg Which message?
/// @param wParam Message parameter.
/// @param lParam Message parameter.
///
/// @par Comments
/// The combobox control wraps a listbox (drop down) and an edit control
/// child (if it is an editable list box). The edit control child needs
/// to be subclassed in order to handle keyboard events. Each child control
/// posts some notification to the parent combobox control and I use this
/// behavior to capture and subclass the child edit control. This edit control is
/// subclassed to this very procedure and so 'hwnd' could be parent combobox or
/// child edit control. Care then, must be taken to differentiate between
/// child and parent. I do this by getting the class name and restricting
/// the handling of certain messages to one or the other control type.
/// @par Warning
/// The drop down list control of a combobox is not a child of the combobox
/// it is the child of the desktop so that the list is not clipped by the
/// combobox's client area. Do not sub class it to this procedure since
/// doing so will cause the instance data pointer to be reset to NULL! The
/// instance data pointer is attached to the PropertyGrid's window.
///
/// @returns LRESULT depends on message.
static LRESULT CALLBACK ComboBox_Proc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
static TCHAR classname[MAX_PATH];
HWND hGParent = GetParent(GetParent(hwnd));
// Note: Instance data is attached to combo's grandparent
// or the edit field's greatgrandparent
GetClassName(hwnd, classname, NELEMS(classname));
BOOL fEdit = (0 == _tcsicmp(classname, WC_EDIT));
if (fEdit)
Control_GetInstanceData(GetParent(hGParent), &g_lpInst);
else
Control_GetInstanceData(hGParent, &g_lpInst);
if (WM_DESTROY == msg) //Unsubclass the combobox or child edit control
{
SetWindowLongPtr(hwnd, GWLP_WNDPROC, (DWORD_PTR)GetProp(hwnd, WPRC)); //DWM 1.5: fixed cast
RemoveProp(hwnd, WPRC);
return 0;
}
else if (WM_KILLFOCUS == msg)//DWM 1.3: Added
{
HWND hFocus = (HWND) wParam;
if(hwnd != GetParent(hFocus))// not a combobox editor or a combobox
{
FORWARD_WM_CHAR(hwnd, VK_RETURN, 0, SNDMSG);//DWM 1.6: force update of grid data
Editor_OnKillFocus(hwnd, (HWND) wParam);
}
}
else if (WM_PAINT == msg && !fEdit) // Obliterate border (differs from standard method)
{
// First let the system do its thing
DefProc(hwnd, msg, wParam, lParam);
// Next obliterate the border
HDC hdc = GetWindowDC(hwnd);
RECT rect;
GetClientRect(hwnd, &rect);
DrawBorder(hdc, &rect, BF_TOPLEFT, GetSysColor(COLOR_WINDOW));
rect.top += 1;
rect.left += 1;
DrawBorder(hdc, &rect, BF_TOPLEFT, GetSysColor(COLOR_WINDOW));
ReleaseDC(hwnd, hdc);
return TRUE;
}
else if (WM_GETDLGCODE == msg)
{
return DLGC_WANTALLKEYS;
}
else if (WM_COMMAND == msg)
{
// The editable combo's edit box posts a notification on loading
// the first time it does so we'll grab and subclass it.
HWND hwndCtl = GET_WM_COMMAND_HWND(wParam, lParam);
{
WNDPROC lpfn = (WNDPROC)GetProp(hwndCtl, WPRC);
if (NULL == lpfn)
{
// Do not subclass the drop down list
GetClassName(hwndCtl, classname, NELEMS(classname));
if (0 == _tcsicmp(classname, WC_EDIT))
{
//Subclass edit and save the old proc
SetProp(hwndCtl, WPRC, (HANDLE)GetWindowLongPtr(hwndCtl, GWLP_WNDPROC));
SubclassWindow(hwndCtl, ComboBox_Proc);
}
}
}
}
else if (WM_CHAR == msg && VK_RETURN == wParam)
{
if (NULL != g_lpInst->lpCurrent)
{
GetWindowText(hwnd, classname, sizeof classname); //Combo or child edit text is the same
AllocatedString_Replace(g_lpInst->lpCurrent->lpszCurValue, classname);
}
if (fEdit)
ShowWindow(GetParent(hwnd), SW_HIDE);
else
ShowWindow(hwnd, SW_HIDE);
SetFocus(g_lpInst->hwndListBox);
Grid_NotifyParent();
return TRUE; // handle Enter (NO BELL)
}
else if (WM_CHAR == msg && VK_TAB == wParam)
{
if (GetKeyState(VK_SHIFT) & 0x8000)
{
FORWARD_WM_CHAR(hwnd, VK_RETURN, 0, SNDMSG);
}
else //DWM 1.3: Added Focus to grid parent
{
ShowWindow(fEdit ? GetParent(hwnd) : hwnd, SW_HIDE);
SetFocusToParent();
Editor_OnKillFocus(hwnd, NULL);
}
return TRUE;
}
else if (WM_KEYDOWN == msg && VK_ESCAPE == wParam)
{
if (fEdit)
ShowWindow(GetParent(hwnd), SW_HIDE);
else
ShowWindow(hwnd, SW_HIDE);
SetFocus(g_lpInst->hwndListBox);
return FALSE;
}
return DefProc(hwnd, msg, wParam, lParam);
}
/// @brief Create combobox control configured either as editable or static.
///
/// @param hInstance The handle of an instance.
/// @param hwndParent The handle of the parent (the visible listbox).
/// @param id An id tag for this control.
/// @param fEditable TRUE to create an editable combo, FALSE for static.
///
/// @returns HWND A handle to the control.
static HWND CreateCombo(HINSTANCE hInstance, HWND hwndParent, INT id, BOOL fEditable)
{
DWORD dwStyle, dwExStyle;
HWND hwnd;
dwStyle = WS_CHILD | CBS_NOINTEGRALHEIGHT | (fEditable ? CBS_DROPDOWN : CBS_DROPDOWNLIST);
dwExStyle = WS_EX_LEFT;
hwnd = CreateWindowEx(dwExStyle,
WC_COMBOBOX,
NULL,
dwStyle,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
hwndParent,
(HMENU)id,
hInstance,
NULL);
if (!hwnd)
return NULL;
//DWM 1.4: Disable visual styles for the time being since the combo looks bad
// in this grid when drawn using the Vista and later styles.
SetWindowTheme(hwnd, L" ", L" ");
SendMessage(hwnd, WM_SETFONT, (WPARAM)GetStockObject(DEFAULT_GUI_FONT), 0L);
SetProp(hwnd, WPRC, (HANDLE)GetWindowLongPtr(hwnd, GWLP_WNDPROC));
SubclassWindow(hwnd, ComboBox_Proc);
return hwnd;
}
#pragma endregion Combo Box
#pragma region choose font
/// @brief Create a string from a property grid font dialog item.
///
/// @param lpLogFontItem The address of a PROPGRIDFONTITEM struct.
/// @param lpszFont The string representation of PROPGRIDFONTITEM elements.
///
/// @returns VOID.
static VOID LogFontItem_FromString(LPPROPGRIDFONTITEM lpLogFontItem, LPTSTR lpszFont)
{
LPLOGFONT lpLf = (LPLOGFONT)lpLogFontItem;
_stscanf(lpszFont,
#ifdef _UNICODE
L"Height: %d " \
L"Width: %d " \
L"Escapement: %d " \
L"Orientation: %d " \
L"Weight: %d " \
L"Italic: %hhd " \
L"Underline: %hhd " \
L"StrikeOut: %hhd " \
L"CharSet: %hhd " \
L"OutPrecision: %hhd " \
L"ClipPrecision: %hhd " \
L"Quality: %hhd " \
L"PitchAndFamily: %hhd " \
L"FaceName: %32l[^\r\n] " \
L"Color: %d",
#else
"Height: %d " \
"Width: %d " \
"Escapement: %d " \
"Orientation: %d " \
"Weight: %d " \
"Italic: %hhd " \
"Underline: %hhd " \
"StrikeOut: %hhd " \
"CharSet: %hhd " \
"OutPrecision: %hhd " \
"ClipPrecision: %hhd " \
"Quality: %hhd " \
"PitchAndFamily: %hhd " \
"FaceName: %32[^\r\n] " \
"Color: %d",
#endif
&lpLf->lfHeight,
&lpLf->lfWidth,
&lpLf->lfEscapement,
&lpLf->lfOrientation,
&lpLf->lfWeight,
&lpLf->lfItalic,
&lpLf->lfUnderline,
&lpLf->lfStrikeOut,
&lpLf->lfCharSet,
&lpLf->lfOutPrecision,
&lpLf->lfClipPrecision,
&lpLf->lfQuality,
&lpLf->lfPitchAndFamily,
(LPTSTR)&lpLf->lfFaceName,
&lpLogFontItem->crFont);
}
/// @brief Create a string from a property grid font dialog item.
///
/// @param lpLogFontItem A pointer to a PROPGRIDFONTITEM.
///
/// @returns LPTSTR The string representation of PROPGRIDFONTITEM elements.
static LPTSTR LogFontItem_ToString(LPPROPGRIDFONTITEM lpLogFontItem)
{
static TCHAR buf[MAX_PATH];
_tmemset(buf, (TCHAR)0, MAX_PATH);
LPLOGFONT lpLf = (LPLOGFONT)lpLogFontItem;
_stprintf(buf, MAX_PATH,
#ifdef _UNICODE
L"Height: %d\r\n" \
L"Width: %d\r\n" \
L"Escapement: %d\r\n" \
L"Orientation: %d\r\n" \
L"Weight: %d\r\n" \
L"Italic: %d\r\n" \
L"Underline: %d\r\n" \
L"StrikeOut: %d\r\n" \
L"CharSet: %d\r\n" \
L"OutPrecision: %d\r\n" \
L"ClipPrecision: %d\r\n" \
L"Quality: %d\r\n" \
L"PitchAndFamily: %d\r\n" \
L"FaceName: %ls\r\n" \
L"Color: %d",
#else
"Height: %d\r\n" \
"Width: %d\r\n" \
"Escapement: %d\r\n" \
"Orientation: %d\r\n" \
"Weight: %d\r\n" \
"Italic: %d\r\n" \
"Underline: %d\r\n" \
"StrikeOut: %d\r\n" \
"CharSet: %d\r\n" \
"OutPrecision: %d\r\n" \
"ClipPrecision: %d\r\n" \
"Quality: %d\r\n" \
"PitchAndFamily: %d\r\n" \
"FaceName: %s\r\n" \
"Color: %d",
#endif
lpLf->lfHeight,
lpLf->lfWidth,
lpLf->lfEscapement,
lpLf->lfOrientation,
lpLf->lfWeight,
lpLf->lfItalic,
lpLf->lfUnderline,
lpLf->lfStrikeOut,
lpLf->lfCharSet,
lpLf->lfOutPrecision,
lpLf->lfClipPrecision,
lpLf->lfQuality,
lpLf->lfPitchAndFamily,
lpLf->lfFaceName,
lpLogFontItem->crFont);
return buf;
}
#pragma endregion choose font
#pragma region browse folder
/// @brief Create a string from a property grid file dialog item.
///
/// @param lpPgFdItem The address of a PROPGRIDFDITEM struct.
/// @param lpszFdItem The string representation of PROPGRIDFDITEM elements.
///
/// @returns VOID.
static VOID FileDialogItem_FromString(LPPROPGRIDFDITEM lpPgFdItem, LPTSTR lpszFdItem)
{
//DWM 1.2: Added method
static TCHAR PgFdItem[4][MAX_PATH];
memset(PgFdItem, (TCHAR)0, sizeof(PgFdItem));
_stscanf(lpszFdItem,
#ifdef _UNICODE
L"Title: %256l[^\r\n] " \
L"Path: %256l[^\r\n] " \
L"Filter: %256l[^\r\n] " \
L"Default Extension: %3ls",
#else
"Title: %256[^\r\n] " \
"Path: %256[^\r\n] " \
"Filter: %256[^\r\n] " \
"Default Extension: %3s",
#endif
(LPTSTR)&PgFdItem[0], (LPTSTR)&PgFdItem[1], (LPTSTR)&PgFdItem[2], (LPTSTR)&PgFdItem[3]);
for(int i = 0; i < NELEMS(PgFdItem); i++)
{
if (0 == _tcscmp(_T("?"), PgFdItem[i]))
{
//Convert back to empty string
PgFdItem[i][0] = (TCHAR)0;
}
else if(2 == i)
{
//Convert back to double null-terminated string
for (LPTSTR ptr = PgFdItem[2]; *ptr; ptr++)
if (_T('\t') == *ptr) *ptr = _T('\0');
}
}
lpPgFdItem->lpszDlgTitle = PgFdItem[0];
lpPgFdItem->lpszFilePath = PgFdItem[1];
lpPgFdItem->lpszFilter = PgFdItem[2];
lpPgFdItem->lpszDefExt = PgFdItem[3];
}
/// @brief Create a string from a property grid file dialog item.
///
/// @param lpPgFdItem A pointer to a PROPGRIDFDITEM.
///
/// @returns LPTSTR The string representation of PROPGRIDFDITEM elements.
static LPTSTR FileDialogItem_ToString(LPPROPGRIDFDITEM lpPgFdItem)
{
static TCHAR szBuf[3 * MAX_PATH];
_tmemset(szBuf, (TCHAR)0, NELEMS(szBuf));
TCHAR filter[MAX_PATH] = { 0 };
//Copy filter string replacing \0 with \t"
INT iLen = 0;
for (LPTSTR psz = (LPTSTR)lpPgFdItem->lpszFilter,
ps = filter, pe = filter + NELEMS(filter) - 1;
*psz && ps < pe; psz += iLen + 1)
{
_tmemmove(ps, psz, (iLen = _tcslen(psz)));
ps += iLen;
*ps++ = _T('\t');
}
_stprintf(szBuf, NELEMS(szBuf),
#ifdef _UNICODE
L"Title: %ls\r\n" \
L"Path: %ls\r\n" \
L"Filter: %ls\r\n" \
L"Default Extension: %3ls",
#else
"Title: %s\r\n" \
"Path: %s\r\n" \
"Filter: %s\r\n" \
"Default Extension: %3s",
#endif
0 < _tcslen(lpPgFdItem->lpszDlgTitle) ? lpPgFdItem->lpszDlgTitle : _T("?"), //DWM 1.2: Added default "?"
0 < _tcslen(lpPgFdItem->lpszFilePath) ? lpPgFdItem->lpszFilePath : _T("?"), //DWM 1.2: Added default "?"
0 < _tcslen(filter) ? filter : _T("?"), //DWM 1.2: Added default "?"
0 < _tcslen(lpPgFdItem->lpszDefExt) ? lpPgFdItem->lpszDefExt : _T("?")); //DWM 1.2: Added default "?"
return szBuf;
}
/// @brief Convert a CHAR string to a WCHAR string.
///
/// @param dest Pointer to a buffer that will contain the converted string.
/// @param ccDest destination buffer size (number of characters)
/// @param source Constant pointer to a source string to be converted to WCHAR.
///
/// @returns VOID.
static VOID CharToWide(LPWSTR dest, INT ccDest, const LPSTR source)
{
//DWM 1.7: Added
int i = 0;
while(source[i] != '\0' && i <= ccDest)
{
dest[i] = (WCHAR)source[i];
++i;
}
}
/// @brief Convert a path to an item id list object.
///
/// @param pszPath The file path string.
///
/// @returns LPITEMIDLIST A pointer to an item id list object.
static LPITEMIDLIST ConvertPathToLpItemIdList(LPTSTR pszPath)
{
LPITEMIDLIST pidl;
LPSHELLFOLDER pDesktopFolder;
ULONG chEaten;
ULONG dwAttributes;
//DWM 1.7: Added
#ifdef _UNICODE
LPWSTR pwzPath = pszPath;
#else
int iLen = strlen(pszPath);
WCHAR * pwzPath = new WCHAR[iLen + 1];
wmemset(pwzPath, (WCHAR)0, NELEMS(pwzPath));
CharToWide(pwzPath, NELEMS(pwzPath), pszPath);
#endif
if (SUCCEEDED(SHGetDesktopFolder(&pDesktopFolder)))
{
pDesktopFolder->ParseDisplayName(NULL, NULL, pwzPath, &chEaten, &pidl, &dwAttributes);
pDesktopFolder->Release();
}
delete pwzPath;
return pidl;
}
/// @brief An application-defined callback function used with the
/// SHBrowseForFolder function.
///
/// @param hwnd Handle of the dialog.
/// @param uMsg Which message?
/// @param lParam Message parameter.
/// @param lpData Pointer to message data.
///
/// @returns BOOL FALSE.
static BOOL CALLBACK BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData)
{
//Buffer for the folder dialog
static TCHAR SelectedDir[MAX_PATH];
switch (uMsg)
{
case BFFM_INITIALIZED:
{
// change the selected folder.
SNDMSG(hwnd, BFFM_SETSELECTION, TRUE, lpData);
break;
}
case BFFM_SELCHANGED:
{
// Set the status window to the currently selected path.
if (SHGetPathFromIDList((LPITEMIDLIST)lParam, SelectedDir))
SNDMSG(hwnd, BFFM_SETSTATUSTEXT, 0, (LPARAM)SelectedDir);
break;
}
default:
break;
}
return FALSE;
}
/// @brief Show the folder browser dialog to get a directory pathname.
///
/// @param hwnd Handle of the dialog's owner.
/// @param curPath The path of the currently selected folder.
/// @param title Browser dialog title.
/// @param rootPath Path location to begin browsing.
///
/// @returns BOOL Depends on message.
static LPTSTR BrowseFolder(HWND hwnd, LPTSTR curPath, LPTSTR title, LPTSTR rootPath)
{
BROWSEINFO bi;
static TCHAR szDir[MAX_PATH];
LPITEMIDLIST pidl;
LPMALLOC pMalloc;
if (SUCCEEDED(SHGetMalloc(&pMalloc)))
{
memset(&bi, 0, sizeof(bi));
bi.hwndOwner = hwnd;
bi.pszDisplayName = 0;
bi.lpszTitle = title;
bi.pidlRoot = ConvertPathToLpItemIdList(rootPath);
bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_STATUSTEXT;
bi.lpfn = BrowseCallbackProc;
bi.lParam = (long)curPath;
pidl = SHBrowseForFolder(&bi);
if (pidl)
SHGetPathFromIDList(pidl, szDir);
else
_tmemset(szDir, (TCHAR)0, MAX_PATH);
// free memory used
pMalloc->Free(pidl);
pMalloc->Release();
}
return szDir;
}
#pragma endregion browse folder
#pragma region list box message handlers
/// @brief Find the catalog item that matches the given catalog name.
///
/// @param hwnd Handle of the listbox.
/// @param indexStart Index to begin search.
/// @param szCatalog The catalog name.
///
/// @returns INT The index of the catalog otherwise LB_ERR.
static INT ListBox_FindCatalog(HWND hwnd, INT indexStart, LPCTSTR szCatalog)
{
INT ln = _tcslen(szCatalog) + 1;
if (ln == 1 || indexStart >= ListBox_GetCount(hwnd))
return LB_ERR;
for (INT i = indexStart < 0 ? 0 : indexStart; i < ListBox_GetCount(hwnd); i++)
{
LPLISTBOXITEM pItem = ListBox_GetItemDataSafe(hwnd, i);
if (NULL != pItem && 0 == _tcsicmp(pItem->lpszCatalog, szCatalog))
return i;
}
return LB_ERR;
}
/// @brief Given the address of a LISTBOXITEM object of type PIT_CATALOG,
/// collapse it if it is expanded, or expand it if it is collapsed.
///
/// @param pItem Pointer to an LISTBOXITEM object.
///
/// @returns VOID.
static VOID ToggleCatalog(LPLISTBOXITEM pItem)
{
if (NULL != pItem && PIT_CATALOG != pItem->iItemType)
return;
pItem->fCollapsed = !pItem->fCollapsed;
if (pItem->fCollapsed)
{
for (INT i = ListBox_GetCount(g_lpInst->hwndListBox) - 1; i >= 0; i--)
{
LPLISTBOXITEM p = (LPLISTBOXITEM)ListBox_GetItemDataSafe(g_lpInst->hwndListBox, i);
if (NULL != p && _tcsicmp(p->lpszCatalog, pItem->lpszCatalog) == 0)
{
if (PIT_CATALOG != p->iItemType)
{
ListBox_DeleteString(g_lpInst->hwndListBox, i);
}
}
}
}
else
{
INT idx = ListBox_FindCatalog(g_lpInst->hwndListBox, 0, pItem->lpszCatalog);
if (LB_ERR != idx)
{
LPLISTBOXITEM pp;
for (INT i = ListBox_GetCount(g_lpInst->hwndListMap) - 1; 0 <= i; i--)
{
pp = ListBox_GetItemDataSafe(g_lpInst->hwndListMap, i);
if (NULL != pp->lpszCatalog)
{
if (0 == _tcsicmp(pp->lpszCatalog, pItem->lpszCatalog))
{
if (PIT_CATALOG != pp->iItemType)
{
ListBox_InsertItemData(g_lpInst->hwndListBox, idx + 1, pp);
}
}
}
}
}
}
}
/// @brief Handle the begin scroll event of a listbox.
///
/// @param hwnd Handle of a listbox.
///
/// @par Comments:
/// This event is not raised by the listbox but is detected
/// by indirect means.
///
/// @see ListBox_Proc() WM_NCLBUTTONDOWN for details.
///
/// @returns VOID.
static VOID ListBox_OnBeginScroll(HWND hwnd)
{
g_lpInst->fGotFocus = TRUE;//DWM 1:3: Added
if (NULL == g_lpInst->lpCurrent)
return;
switch (g_lpInst->lpCurrent->iItemType)
{
case PIT_CHECK:
g_lpInst->lpCurrent->lpszMisc = UNSELECT;
break;
case PIT_STATIC:
break;
case PIT_DATETIME:
ShowWindow(g_lpInst->hwndCtl1, SW_HIDE);
ShowWindow(g_lpInst->hwndCtl2, SW_HIDE);
break;
default:
ShowWindow(g_lpInst->hwndCtl1, SW_HIDE);
}
}
/// @brief Handle the end scroll event of a listbox.
///
/// @param hwnd Handle of a listbox.
///
/// @par Comments:
/// This event is not raised by the listbox but is detected
/// by indirect means.
///
/// @see ListBox_Proc() WM_SETCURSOR for details.
///
/// @returns VOID.
static VOID ListBox_OnEndScroll(HWND hwnd)
{
if (NULL == g_lpInst->lpCurrent)
return;
RECT rect;
memset(&rect, 0, sizeof rect);
//Caculate the grid width
ListBox_GetItemRect(hwnd, ListBox_GetCurSel(hwnd), &rect);
rect.top += 1;
rect.left = g_lpInst->iHDivider + 1;
switch (g_lpInst->lpCurrent->iItemType)
{
case PIT_CATALOG:
case PIT_STATIC:
break; //Don't display anything
case PIT_CHECK:
g_lpInst->lpCurrent->lpszMisc = SELECT;
RedrawWindow(hwnd, &rect, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_UPDATENOW);
break;
case PIT_EDIT:
if (NULL == g_lpInst->hwndCtl1)
{
SetFocus(g_lpInst->hwndListBox);
}
else //Display edit box
{
MoveWindow(g_lpInst->hwndCtl1, rect.left, rect.top, WIDTH(rect), HEIGHT(rect), TRUE);
ShowWindow(g_lpInst->hwndCtl1, SW_SHOW);
SetFocus(g_lpInst->hwndCtl1);
}
break;
case PIT_COMBO:
case PIT_EDITCOMBO:
case PIT_IP:
case PIT_DATE:
case PIT_TIME:
if (NULL == g_lpInst->hwndCtl1)
SetFocus(g_lpInst->hwndListBox);
else //Display editable combobox
{
MoveWindow(g_lpInst->hwndCtl1, rect.left, rect.top, WIDTH(rect), HEIGHT(rect), TRUE);
ShowWindow(g_lpInst->hwndCtl1, SW_SHOW);
SetFocus(g_lpInst->hwndCtl1);
}
break;
case PIT_DATETIME:
if (NULL == g_lpInst->hwndCtl1 || NULL == g_lpInst->hwndCtl2)
SetFocus(g_lpInst->hwndListBox);
else //Display date and time
{
RECT rect0, rect1;
rect0 = rect1 = rect;
rect0.right = rect0.left + (rect0.right - rect0.left) / 2;
rect1.left = rect1.left + (rect1.right - rect1.left) / 2;
MoveWindow(g_lpInst->hwndCtl1, rect0.left, rect0.top, WIDTH(rect0), HEIGHT(rect0), TRUE);
MoveWindow(g_lpInst->hwndCtl2, rect1.left, rect1.top, WIDTH(rect1), HEIGHT(rect1), TRUE);
ShowWindow(g_lpInst->hwndCtl2, SW_SHOW);
ShowWindow(g_lpInst->hwndCtl1, SW_SHOW);
SetFocus(g_lpInst->hwndCtl1);
}
break;
default: //Button control
if (NULL == g_lpInst->hwndCtl1)
{
SetFocus(g_lpInst->hwndListBox);
}
else //Display Button
{
if (WIDTH(rect) > 19)
{
rect.left = rect.right - 19;
rect.right -= 2;
}
rect.top += 2;
rect.bottom -= 2;
MoveWindow(g_lpInst->hwndCtl1, rect.left, rect.top, WIDTH(rect), HEIGHT(rect), TRUE);
ShowWindow(g_lpInst->hwndCtl1, SW_SHOW);
SetFocus(g_lpInst->hwndCtl1);
}
break;
}
}
/// @brief Handles WM_LBUTTONDOWN message in the listbox.
///
/// @param hwnd Handle of listbox.
/// @param fDoubleClick TRUE if this is a double click event.
/// @param x The xpos of the mouse.
/// @param y The ypos of the mouse.
/// @param keyFlags Set if certain keys down at time of click.
///
/// @returns VOID.
static VOID ListBox_OnLButtonDown(HWND hwnd, BOOL fDoubleClick, INT x, INT y, UINT keyFlags)
{
if ((x >= g_lpInst->iHDivider - 5) && (x <= g_lpInst->iHDivider + 5))
{
//If mouse clicked on divider line, then start resizing
SetCursor(LoadCursor(NULL, IDC_SIZEWE));
RECT rcWindow;
GetWindowRect(hwnd, &rcWindow);
rcWindow.left += WIDTH_PART0;
rcWindow.right -= 10;
//Do not let mouse leave the list box boundary
ClipCursor(&rcWindow);
RECT rcClient;
GetClientRect(hwnd, &rcClient);
g_lpInst->fTracking = TRUE;
g_lpInst->nDivTop = rcClient.top;
g_lpInst->nDivBtm = rcClient.bottom;
g_lpInst->nOldDivX = x;
HDC hdc = GetDC(hwnd);
InvertLine(hdc, x, rcClient.top, x, rcClient.bottom);
ReleaseDC(hwnd, hdc);
if (NULL != g_lpInst->lpCurrent)
{
TCHAR buf[MAX_PATH] = {0};
switch (g_lpInst->lpCurrent->iItemType)
{
case PIT_CATALOG:
case PIT_STATIC:
break; //Ignore
case PIT_CHECK:
g_lpInst->lpCurrent->lpszMisc = UNSELECT; //Prevent toggle
break;
case PIT_DATETIME:
//DWM 1.6: force update of grid data
GetWindowText(g_lpInst->hwndCtl2,buf, sizeof buf);
AllocatedString_Replace(g_lpInst->lpCurrent->lpszCurValue, buf);
ShowWindow(g_lpInst->hwndCtl2, SW_HIDE);
// Fall through
default:
//DWM 1.6: force update of grid data
GetWindowText(g_lpInst->hwndCtl1,buf, sizeof buf);
AllocatedString_Replace(g_lpInst->lpCurrent->lpszCurValue, buf);
ShowWindow(g_lpInst->hwndCtl1, SW_HIDE);
break;
}
}
//Capture the mouse
SetCapture(hwnd);
}
else
{
if (fDoubleClick)
{
INT i = LOWORD(ListBox_ItemFromPoint(hwnd, x, y));
LPLISTBOXITEM pItem = ListBox_GetItemDataSafe(hwnd, i);
if (NULL != pItem)
{
if (PIT_CATALOG == pItem->iItemType)
ToggleCatalog(pItem);
}
}
g_lpInst->fTracking = FALSE;
}
}
/// @brief Handles WM_LBUTTONUP message in the listbox.
///
/// @param hwnd Handle of listbox.
/// @param x The xpos of the mouse.
/// @param y The ypos of the mouse.
/// @param keyFlags Set if certain keys down at time of click.
///
/// @returns VOID.
static VOID ListBox_OnLButtonUp(HWND hwnd, INT x, INT y, UINT keyFlags)
{
if (g_lpInst->fTracking)
{
//If columns were being resized then this indicates
// that mouse is up so resizing is done. Need to redraw
// columns to reflect their new widths.
g_lpInst->fTracking = FALSE;
//If mouse was captured then release it
if (hwnd == GetCapture())
ReleaseCapture();
ClipCursor(NULL);
HDC hdc = GetDC(hwnd);
InvertLine(hdc, x, g_lpInst->nDivTop, x, g_lpInst->nDivBtm);
ReleaseDC(hwnd, hdc);
//Set the divider position to the new value
g_lpInst->iHDivider = x;
Refresh(hwnd);
if (NULL != g_lpInst->lpCurrent)
{
switch (g_lpInst->lpCurrent->iItemType)
{
case PIT_CATALOG:
case PIT_STATIC:
case PIT_CHECK:
break; //Ignore
case PIT_DATETIME:
ShowWindow(g_lpInst->hwndCtl2, SW_SHOW);
//Fall through
default:
ShowWindow(g_lpInst->hwndCtl1, SW_SHOW);
break;
}
}
}
else
{
INT i = LOWORD(ListBox_ItemFromPoint(hwnd, x, y));
if (ListBox_GetCurSel(hwnd) != i)
{
ListBox_SetCurSel(hwnd, i);
}
}
//Update the fields
FORWARD_WM_CHAR(g_lpInst->hwndCtl1, VK_RETURN, 0, SNDMSG);
FORWARD_WM_CHAR(g_lpInst->hwndCtl2, VK_RETURN, 0, SNDMSG);
FORWARD_WM_COMMAND(GetParent(hwnd), GetDlgCtrlID(hwnd), hwnd, LBN_SELCHANGE, SNDMSG);
if (NULL != g_lpInst->lpCurrent)
{
switch(g_lpInst->lpCurrent->iItemType)//DWM 1.3: Added switch
{
case PIT_CATALOG:
case PIT_STATIC:
return;
case PIT_CHECK:
if(0 == _tcsicmp(g_lpInst->lpCurrent->lpszMisc, SELECT))
{
FORWARD_WM_KEYDOWN(hwnd, VK_SPACE, 0, 0, SNDMSG);
break;
} // else fallthrough
default:
FORWARD_WM_KEYDOWN(hwnd, VK_TAB, 0, 0, SNDMSG); //Focus to editor
}
}
}
/// @brief Handles WM_MOUSEMOVE message in the listbox.
///
/// @param hwnd Handle of listbox.
/// @param x The xpos of the mouse.
/// @param y The ypos of the mouse.
/// @param keyFlags Set if certain keys down at time of move.
///
/// @returns VOID.
static VOID ListBox_OnMouseMove(HWND hwnd, INT x, INT y, UINT keyFlags)
{
if (g_lpInst->fTracking)
{
//Move divider line to the mouse pos. if columns are
// currently being resized
HDC hdc = GetDC(hwnd);
//Remove old divider line
InvertLine(hdc, g_lpInst->nOldDivX, g_lpInst->nDivTop, g_lpInst->nOldDivX, g_lpInst->nDivBtm);
//Draw new divider line
InvertLine(hdc, x, g_lpInst->nDivTop, x, g_lpInst->nDivBtm);
ReleaseDC(hwnd, hdc);
g_lpInst->nOldDivX = x;
}
else if ((x >= g_lpInst->iHDivider - 5) && (x <= g_lpInst->iHDivider + 5))
{
//Set the cursor to a sizing cursor if the cursor is over the row divider
SetCursor(LoadCursor(NULL, IDC_SIZEWE));
}
//
//Set the tool tip text
//
if (NULL != g_lpInst->hwndToolTip)
{
TOOLINFO tiToolInfo;
//Allocate a buf for lpszText
TCHAR buf[80];
TCHAR newText[80] = {0};
tiToolInfo.lpszText = buf;
tiToolInfo.cbSize = sizeof(TOOLINFO);
//Populate TOOLINFO with the info for the tool (including old text)
ToolTip_EnumTools(g_lpInst->hwndToolTip, 0, &tiToolInfo);
if ((x >= g_lpInst->iHDivider + 5) && !g_lpInst->fTracking)
{
INT i = LOWORD(ListBox_ItemFromPoint(hwnd, x, y));
LPLISTBOXITEM pItem = ListBox_GetItemDataSafe(hwnd, i);
if (NULL != pItem)
{
switch (pItem->iItemType)
{
case PIT_FONT:
{
LPTSTR szFmt;
PROPGRIDFONTITEM pgfi;
LONG PointSize = 0;
LogFontItem_FromString(&pgfi, pItem->lpszCurValue);
HDC hDC = GetDC(hwnd);
PointSize = -MulDiv(pgfi.logFont.lfHeight, 72, GetDeviceCaps(hDC, LOGPIXELSY));
ReleaseDC(hwnd, hDC);
#ifdef _UNICODE
szFmt = _T("%ls %d");
#else
szFmt = _T("%s %d");
#endif
//Replace the text in the buf and update the tool
_stprintf(newText, NELEMS(buf), szFmt, pgfi.logFont.lfFaceName, PointSize);
}
break;
case PIT_CHECK: //Skip this item
break;
default:
//Replace the text in the buf and update the tool
_tcsncpy(newText, pItem->lpszCurValue, NELEMS(newText) - 1);
break;
}
}
}
if(0 != _tcsncmp(buf, newText, _tcslen(newText))) //DWM 1.4: Do not update unless text changed to reduce flicker
{
//Clear out old text
_tmemset(buf, (TCHAR)0, NELEMS(buf)-1);
//Replace the text in the buf and update the tool
_tcsncpy(buf, newText, NELEMS(buf) - 1);
ToolTip_UpdateTipText(g_lpInst->hwndToolTip, &tiToolInfo);
}
}
}
/// @brief Handle the selection of a listbox item of type PIT_EDIT.
///
/// @param hwnd The handle of the listbox.
/// @param rc RECT containing desired coordinates for the edit control.
/// @param pItem Pointer to a LISTBOXITEM object.
///
/// @returns VOID.
static VOID ListBox_OnSelectEdit(HWND hwnd, RECT rc, LPLISTBOXITEM pItem)
{
rc.top += 1;
//display edit box
if (NULL == g_lpInst->hwndCtl1)
g_lpInst->hwndCtl1 = CreateEdit(g_lpInst->hInstance, hwnd, ID_EDIT);
MoveWindow(g_lpInst->hwndCtl1, rc.left, rc.top, WIDTH(rc), HEIGHT(rc), TRUE);
//Set the text in the edit box to the property's current value
Edit_SetText(g_lpInst->hwndCtl1, pItem->lpszCurValue);
ShowWindow(g_lpInst->hwndCtl1, SW_SHOW);
SetFocus(g_lpInst->hwndCtl1);
Edit_SetSel(g_lpInst->hwndCtl1, 0, -1);
}
/// @brief Handle the selection of a listbox item of type PIT_IP.
///
/// @param hwnd The handle of the listbox.
/// @param rc RECT containing desired coordinates for the ipedit control.
/// @param pItem Pointer to a LISTBOXITEM object.
///
/// @returns VOID.
static VOID ListBox_OnSelectIP(HWND hwnd, RECT rc, LPLISTBOXITEM pItem)
{
rc.top += 1;
if (NULL == g_lpInst->hwndCtl1)
g_lpInst->hwndCtl1 = CreateIpEdit(g_lpInst->hInstance, hwnd, ID_IPEDIT, &rc);
MoveWindow(g_lpInst->hwndCtl1, rc.left, rc.top, WIDTH(rc), HEIGHT(rc), TRUE);
//DWM 1.9: Updated this code to use DWORD array and MAKEIPADDRESS macroe instead of 4 byte array and cast.
//DWM 1.9: This was necessary to support MSVC _stscanf (sscanf - swscanf) beahavior which expects DWORD arguments.
DWORD ip[4] = { 0, 0, 0, 0 };
_stscanf(pItem->lpszCurValue, _T("%hhu.%hhu.%hhu.%hhu"), &ip[0], &ip[1], &ip[2], &ip[3]);
SNDMSG(g_lpInst->hwndCtl1, IPM_SETADDRESS, 0, MAKEIPADDRESS(ip[0],ip[1],ip[2],ip[3]));
ShowWindow(g_lpInst->hwndCtl1, SW_SHOW);
SetFocus(g_lpInst->hwndCtl1);
}
/// @brief Handle the selection of a listbox item of the following types:
/// PIT_COLOR, PIT_FONT, PIT_FILE, and PIT_FOLDER.
///
/// @param hwnd The handle of the listbox.
/// @param rc RECT containing desired coordinates for the button control.
///
/// @returns VOID.
static VOID ListBox_OnDisplayButton(HWND hwnd, RECT rc)
{
if (WIDTH(rc) > 19)
{
rc.left = rc.right - 19;
rc.right -= 2;
}
rc.top += 2;
rc.bottom -= 2;
if (NULL == g_lpInst->hwndCtl1)
g_lpInst->hwndCtl1 = CreateButton(g_lpInst->hInstance, hwnd, ID_BUTTON);
MoveWindow(g_lpInst->hwndCtl1, rc.left, rc.top, WIDTH(rc), HEIGHT(rc), TRUE);
ShowWindow(g_lpInst->hwndCtl1, SW_SHOW);
SetFocus(g_lpInst->hwndCtl1);
}
/// @brief Handle the selection of a listbox item of the following types:
/// PIT_DATE, PIT_TIME, and PIT_DATETIME.
///
/// @param hwnd The handle of the listbox.
/// @param rc RECT containing desired coordinates for the date or timepicker control(s).
/// @param pItem Pointer to a LISTBOXITEM object.
///
/// @returns VOID.
static VOID ListBox_OnSelectDateTime(HWND hwnd, RECT rc, LPLISTBOXITEM pItem)
{
rc.top += 1;
LPTSTR szFormat;
TCHAR buf[3];
_tmemset(buf, (TCHAR)0, 3);
SYSTEMTIME st;
memset(&st, 0, sizeof(SYSTEMTIME));
if (PIT_DATE == pItem->iItemType)
{
if (NULL == g_lpInst->hwndCtl1)
g_lpInst->hwndCtl1 = CreateDatePicker(g_lpInst->hInstance, hwnd, ID_DATE, TRUE);
MoveWindow(g_lpInst->hwndCtl1, rc.left, rc.top, WIDTH(rc), HEIGHT(rc), TRUE);
if (NULL != pItem)
_stscanf(pItem->lpszCurValue, _T("%hd/%hd/%hd"), &st.wMonth, &st.wDay, &st.wYear);
DateTime_SetSystemtime(g_lpInst->hwndCtl1, GDT_VALID, &st);
ShowWindow(g_lpInst->hwndCtl1, SW_SHOW);
SetFocus(g_lpInst->hwndCtl1);
}
else if (PIT_TIME == pItem->iItemType)
{
if (NULL == g_lpInst->hwndCtl1)
g_lpInst->hwndCtl1 = CreateDatePicker(g_lpInst->hInstance, hwnd, ID_TIME, FALSE);
MoveWindow(g_lpInst->hwndCtl1, rc.left, rc.top, WIDTH(rc), HEIGHT(rc), TRUE);
#ifdef _UNICODE
szFormat = _T("%hd:%hd:%hd %2ls");
#else
szFormat = _T("%hd:%hd:%hd %2s");
#endif
if (NULL != pItem)
{
//DWM 1.6: Initialize unused date portion so DTP doesn't default to current date time
GetLocalTime(&st);
_stscanf(pItem->lpszCurValue, szFormat, &st.wHour, &st.wMinute, &st.wSecond, &buf);
}
if ((0 == _tcsicmp(_T("PM"), buf)) && st.wHour != 12)//DWM 1.6:Added st.wHour != 12
st.wHour += 12;
DateTime_SetSystemtime(g_lpInst->hwndCtl1, GDT_VALID, &st);
ShowWindow(g_lpInst->hwndCtl1, SW_SHOW);
SetFocus(g_lpInst->hwndCtl1);
}
else if (PIT_DATETIME == pItem->iItemType)
{
RECT rect0, rect1;
rect0 = rect1 = rc;
rect0.right = rect0.left + (rect0.right - rect0.left) / 2;
rect1.left = rect1.left + (rect1.right - rect1.left) / 2;
if (NULL == g_lpInst->hwndCtl1)
g_lpInst->hwndCtl1 = CreateDatePicker(g_lpInst->hInstance, hwnd, ID_DATE, TRUE);
MoveWindow(g_lpInst->hwndCtl1, rect0.left, rect0.top, WIDTH(rect0), HEIGHT(rect0), TRUE);
if (NULL == g_lpInst->hwndCtl2)
g_lpInst->hwndCtl2 = CreateDatePicker(g_lpInst->hInstance, hwnd, ID_TIME, FALSE);
MoveWindow(g_lpInst->hwndCtl2, rect1.left, rect1.top, WIDTH(rect1), HEIGHT(rect1), TRUE);
#ifdef _UNICODE
szFormat = _T("%hd/%hd/%hd %hd:%hd:%hd %2ls");
#else
szFormat = _T("%hd/%hd/%hd %hd:%hd:%hd %2s");
#endif
if (NULL != pItem)
_stscanf(pItem->lpszCurValue, szFormat, &st.wMonth, &st.wDay, &st.wYear, &st.wHour, &st.wMinute, &st.wSecond, &buf);
if ((0 == _tcsicmp(_T("PM"), buf)) && st.wHour != 12)//DWM 1.6:Added st.wHour != 12
st.wHour += 12;
DateTime_SetSystemtime(g_lpInst->hwndCtl1, GDT_VALID, &st);
DateTime_SetSystemtime(g_lpInst->hwndCtl2, GDT_VALID, &st);
ShowWindow(g_lpInst->hwndCtl1, SW_SHOW);
ShowWindow(g_lpInst->hwndCtl2, SW_SHOW);
SetFocus(g_lpInst->hwndCtl1);
}
}
/// @brief Handle the selection of a listbox item of the following types:
/// PIT_COMBO, and PIT_EDITCOMBO.
///
/// @param hwnd The handle of the listbox.
/// @param rc RECT containing desired coordinates for the combobox control.
/// @param pItem Pointer to a LISTBOXITEM object.
///
/// @returns VOID.
static VOID ListBox_OnSelectComboBox(HWND hwnd, RECT rc, LPLISTBOXITEM pItem)
{
HWND hCombo;
rc.top += 1;
rc.bottom += 100;
if (PIT_COMBO == pItem->iItemType)
{
if (NULL == g_lpInst->hwndCtl1)
g_lpInst->hwndCtl1 = CreateCombo(g_lpInst->hInstance, hwnd, ID_COMBO, FALSE);
hCombo = g_lpInst->hwndCtl1;
}
else //PIT_EDITCOMBO
{
if (NULL == g_lpInst->hwndCtl1)
g_lpInst->hwndCtl1 = CreateCombo(g_lpInst->hInstance, hwnd, ID_EDITCOMBO, TRUE);
hCombo = g_lpInst->hwndCtl1;
}
ComboBox_ResetContent(hCombo);
MoveWindow(hCombo, rc.left, rc.top, WIDTH(rc), HEIGHT(rc), TRUE);
//Walk the item list and add each string until the empty string
for (LPTSTR p = pItem->lpszMisc; *p; p += _tcslen(p) + 1)
{
if (CB_ERR == ComboBox_FindStringExact(hCombo, 0, p))
ComboBox_AddString(hCombo, p);
}
ShowWindow(hCombo, SW_SHOW);
SetFocus(hCombo);
//Jump to the property's current value in the combo box
INT itm = ComboBox_FindStringExact(hCombo, 0, pItem->lpszCurValue);
if (itm != CB_ERR)
ComboBox_SetCurSel(hCombo, itm);
else
{
ComboBox_SetCurSel(hCombo, 0);
ComboBox_SetText(hCombo, pItem->lpszCurValue);
ComboBox_SetEditSel(hCombo, 0, -1);
}
}
/// @brief Handles WM_KEYDOWN messages sent to the listbox.
///
/// @param hwnd Handle of the listbox.
/// @param vk The virtual key code.
/// @param fDown TRUE for keydown (always TRUE).
/// @param cRepeat The number of times the keystroke is repeated
/// as a result of the user holding down the key.
/// @param flags Indicate OEM scan codes etc.
///
/// @returns VOID.
static VOID ListBox_OnKeyDown(HWND hwnd, UINT vk, BOOL fDown, INT cRepeat, UINT flags)
{
BOOL fHandled = FALSE;
if (NULL != g_lpInst->lpCurrent)
{
RECT rc;
memset(&rc, 0, sizeof rc);
ListBox_GetItemRect(hwnd, ListBox_GetCurSel(hwnd), &rc);
if (PIT_CATALOG == g_lpInst->lpCurrent->iItemType)
{
if (VK_RIGHT == vk && g_lpInst->lpCurrent->fCollapsed || VK_LEFT == vk && !g_lpInst->lpCurrent->fCollapsed)
{
ToggleCatalog(g_lpInst->lpCurrent);
fHandled = TRUE;
}
}
if (PIT_CHECK == g_lpInst->lpCurrent->iItemType && (VK_SPACE == vk || VK_RETURN == vk))
{
if (0 == _tcsicmp(g_lpInst->lpCurrent->lpszMisc, SELECT))
{
g_lpInst->lpCurrent->lpszCurValue = (0 == _tcsicmp(g_lpInst->lpCurrent->lpszCurValue, CHECKED) ? UNCHECKED : CHECKED);
RedrawWindow(hwnd, &rc, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_UPDATENOW);
Grid_NotifyParent(); //DWM 1.2: Notify of check change
}
}
else if (PIT_CHECK == g_lpInst->lpCurrent->iItemType && VK_ESCAPE == vk)
{
g_lpInst->lpCurrent->lpszMisc = UNSELECT;
RedrawWindow(hwnd, &rc, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_UPDATENOW);
}
else if (PIT_CHECK == g_lpInst->lpCurrent->iItemType &&
VK_LEFT <= vk && vk <= VK_DOWN &&
0 == _tcsicmp(g_lpInst->lpCurrent->lpszMisc, SELECT))
{
fHandled = TRUE;
}
else
{
if (VK_TAB == vk)
{
if (PIT_COLOR == g_lpInst->lpCurrent->iItemType)
{
rc.left = g_lpInst->iHDivider + 1 + WIDTH_PART2;
}
else
{
rc.left = g_lpInst->iHDivider + 1;
}
switch (g_lpInst->lpCurrent->iItemType)
{
case PIT_CATALOG: //Ignore this
case PIT_STATIC:
SetFocusToParent(); //DWM 1.3: Added
break;
case PIT_EDIT:
ListBox_OnSelectEdit(hwnd, rc, g_lpInst->lpCurrent);
break;
case PIT_COMBO:
case PIT_EDITCOMBO:
ListBox_OnSelectComboBox(hwnd, rc, g_lpInst->lpCurrent);
break;
case PIT_IP:
ListBox_OnSelectIP(hwnd, rc, g_lpInst->lpCurrent);
break;
case PIT_CHECK:
if(GetKeyState(VK_SHIFT) & 0x8000)
{
FORWARD_WM_KEYDOWN(hwnd, VK_ESCAPE, cRepeat, flags, SNDMSG);
}
else if(0 == _tcsicmp(g_lpInst->lpCurrent->lpszMisc, SELECT))
{
g_lpInst->lpCurrent->lpszMisc = UNSELECT;
SetFocusToParent();
}
else
{
g_lpInst->lpCurrent->lpszMisc = SELECT;
}
RedrawWindow(hwnd, &rc, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_UPDATENOW);
break;
case PIT_DATE:
case PIT_TIME:
case PIT_DATETIME:
ListBox_OnSelectDateTime(hwnd, rc, g_lpInst->lpCurrent);
break;
default:
ListBox_OnDisplayButton(hwnd, rc);
break;
}
}
else if (VK_PRIOR == vk) //Mimic behavior of VS propGrid
{
FORWARD_WM_KEYDOWN(hwnd, VK_HOME, cRepeat, flags, SNDMSG);
fHandled = TRUE;
}
else if (VK_NEXT == vk) //Mimic behavior of VS propGrid
{
FORWARD_WM_KEYDOWN(hwnd, VK_END, cRepeat, flags, SNDMSG);
fHandled = TRUE;
}
}
}
if (!fHandled) //Not fully handled so follow up with default handler
FORWARD_WM_KEYDOWN(hwnd, vk, cRepeat, flags, DefProc);
}
/// @brief Handles WM_COMMAND messages sent to the listbox.
///
/// @param hwnd Handle of listbox.
/// @param id The id of the sender.
/// @param hwndCtl The hwnd of the sender.
/// @param codeNotify The notification code sent.
///
/// @par Comments:
/// We are only concerned with handling button click notifications here.
/// These notifications originate when the button is clicked for items of
/// type PIT_COLOR, PIT_FONT, PIT_FILE, and PIT_FOLDER.
///
/// @returns VOID.
static VOID ListBox_OnCommand(HWND hwnd, INT id, HWND hwndCtl, UINT codeNotify)
{
if (ID_BUTTON == id)
{
if (NULL == g_lpInst->lpCurrent)
return;
//Display the appropriate common dialog depending on what type
// of chooser is associated with the property
switch (g_lpInst->lpCurrent->iItemType)
{
case PIT_COLOR:
{
CHOOSECOLOR cc;
memset(&cc, 0, sizeof(cc));
cc.lStructSize = sizeof(cc);
cc.hwndOwner = hwnd;
cc.hInstance = (HWND)g_lpInst->hInstance;
cc.rgbResult = GetColor(g_lpInst->lpCurrent->lpszCurValue);
cc.lpCustColors = (LPDWORD)g_CustomColors;
cc.Flags = CC_FULLOPEN | CC_RGBINIT;
if (ChooseColor(&cc))
{
TCHAR buf[MAX_PATH];
_stprintf(buf, MAX_PATH, _T("%d,%d,%d"), GetRValue(cc.rgbResult), GetGValue(cc.rgbResult), GetBValue(cc.rgbResult));
AllocatedString_Replace(g_lpInst->lpCurrent->lpszCurValue, buf);
}
}
break;
case PIT_FILE:
{
TCHAR title[MAX_PATH] = { 0 };
TCHAR filename[MAX_PATH] = { 0 };
TCHAR path[MAX_PATH] = { 0 };
TCHAR filter[MAX_PATH] = { 0 };
TCHAR ext[4] = { 0 };
OPENFILENAME ofn;
memset(&ofn, 0, sizeof(OPENFILENAME));
ofn.lStructSize = sizeof(OPENFILENAME);
ofn.hwndOwner = hwnd;
ofn.hInstance = g_lpInst->hInstance;
_stscanf(g_lpInst->lpCurrent->lpszMisc,
#ifdef _UNICODE
L"Title: %32l[^\r\n] " \
L"Path: %256l[^\r\n] " \
L"Filter: %256l[^\r\n] " \
L"Default Extension: %3l[^\r\n] ",
#else
"Title: %32[^\r\n] " \
"Path: %256[^\r\n] " \
"Filter: %256[^\r\n] " \
"Default Extension: %3[^\r\n]",
#endif
(LPTSTR)&title, (LPTSTR)&path, (LPTSTR)&filter, (LPTSTR)&ext);
if (0 != _tcscmp(_T("?"), title)) //DWM 1.2: Added test
ofn.lpstrTitle = title;
else
ofn.lpstrTitle = _T("Select file");
if (0 != _tcscmp(_T("?"), path)) //DWM 1.2: Added test
{
//Exclude the filename
for (LPTSTR ptr = path + _tcslen(path) - 1; *ptr; ptr--)
if (*ptr == _T('\\'))
{
*ptr = _T('\0');
break;
}
ofn.lpstrInitialDir = path;
}
if (0 != _tcscmp(_T("?"), filter)) //DWM 1.2: Added test
{
//Convert back to double null-terminated string
for (LPTSTR ptr = filter; *ptr; ptr++)
if (_T('\t') == *ptr)
*ptr = _T('\0');
ofn.lpstrFilter = filter;
}
else
ofn.lpstrFilter = _T("All Files (*.*)\0*.*\0");
if (0 != _tcscmp(_T("?"), ext)) //DWM 1.2: Added test
{
ofn.lpstrDefExt = ext;
}
else
{
ofn.lpstrDefExt = _T("txt");
}
ofn.lpstrFile = filename;
ofn.nMaxFile = MAX_PATH;
ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY;
if (GetOpenFileName(&ofn))
{
PROPGRIDFDITEM pgi;
pgi.lpszDlgTitle = (LPTSTR)ofn.lpstrTitle;
pgi.lpszFilePath = (LPTSTR)ofn.lpstrFile;
pgi.lpszFilter = (LPTSTR)ofn.lpstrFilter;
pgi.lpszDefExt = (LPTSTR)ofn.lpstrDefExt;
AllocatedString_Replace(g_lpInst->lpCurrent->lpszCurValue, pgi.lpszFilePath);
AllocatedString_Replace(g_lpInst->lpCurrent->lpszMisc, FileDialogItem_ToString(&pgi));
}
else //DWM 1.2: Reset to unselected file
{
PROPGRIDFDITEM pgi;
pgi.lpszDlgTitle = (LPTSTR)ofn.lpstrTitle;
pgi.lpszFilePath = _T("");
pgi.lpszFilter = (LPTSTR)ofn.lpstrFilter;
pgi.lpszDefExt = (LPTSTR)ofn.lpstrDefExt;
AllocatedString_Replace(g_lpInst->lpCurrent->lpszCurValue, pgi.lpszFilePath);
AllocatedString_Replace(g_lpInst->lpCurrent->lpszMisc, FileDialogItem_ToString(&pgi));
}
}
break;
case PIT_FONT:
{
CHOOSEFONT ocf;
memset(&ocf, 0, sizeof(ocf));
PROPGRIDFONTITEM pgfi;
LogFontItem_FromString(&pgfi, g_lpInst->lpCurrent->lpszCurValue);
ocf.lStructSize = sizeof(ocf);
ocf.hwndOwner = hwnd;
ocf.hInstance = g_lpInst->hInstance;
ocf.Flags = CF_INITTOLOGFONTSTRUCT | CF_EFFECTS | CF_SCREENFONTS;
ocf.lpLogFont = &pgfi.logFont;
ocf.rgbColors = pgfi.crFont;
if (ChooseFont(&ocf))
{
pgfi.crFont = ocf.rgbColors;
AllocatedString_Replace(g_lpInst->lpCurrent->lpszCurValue, LogFontItem_ToString(&pgfi));
}
}
break;
case PIT_FOLDER:
{
LPTSTR temp = BrowseFolder(hwnd, g_lpInst->lpCurrent->lpszCurValue, _T(""), _T(""));
//DWM 1.2: Reset to unselected folder //if (0 < _tcslen(temp))
AllocatedString_Replace(g_lpInst->lpCurrent->lpszCurValue, temp);
}
break;
}
ShowWindow(hwndCtl, SW_HIDE);
SetFocus(hwnd);
Refresh(hwnd); //Trigger WM_DRAWITEM
Grid_NotifyParent();
}
}
#pragma endregion list box message handlers
#pragma region grid message handlers
/// @brief Handles WM_SIZE message.
///
/// @param hwnd Handle of grid.
/// @param state Specifies the type of resizing requested.
/// @param cx The width of client area.
/// @param cy The height of client area.
///
/// @returns VOID.
static VOID Grid_OnSize(HWND hwnd, UINT state, INT cx, INT cy)
{
g_lpInst->iVDivider = cy - g_lpInst->iDescHeight;
if (NULL != g_lpInst->hwndPropDesc)
{
//Size listbox component
SetWindowPos(g_lpInst->hwndListBox, NULL, 0, 0, cx,
g_lpInst->iVDivider - 2, SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
SetWindowPos(g_lpInst->hwndPropDesc, NULL, 0, g_lpInst->iVDivider,
cx, g_lpInst->iDescHeight, SWP_NOZORDER | SWP_NOACTIVATE);
}
else
{
//Size listbox component to fill parent
SetWindowPos(g_lpInst->hwndListBox, NULL, 0, 0, cx, cy,
SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
}
if(NULL != g_lpInst->hwndToolTip)//Resize ToolTips
{
TOOLINFO ti = { sizeof(ti) };
ti.hwnd = g_lpInst->hwndListBox;
ti.uId = 0;
GetClientRect(hwnd, &ti.rect);
ToolTip_NewToolRect(g_lpInst->hwndToolTip, &ti);
}
if (NULL != g_lpInst->lpCurrent)
{
if(PIT_CHECK == g_lpInst->lpCurrent->iItemType)
g_lpInst->lpCurrent->lpszMisc = UNSELECT; //Prevent toggle
}
FORWARD_WM_COMMAND(hwnd, GetDlgCtrlID(g_lpInst->hwndListBox),
g_lpInst->hwndListBox, LBN_SELCHANGE, SNDMSG);
}
/// @brief Handles WM_SETCURSOR message.
///
/// @param hwnd Handle of grid.
/// @param hwndCursor The handle of the cursor.
/// @param codeHitTest The hit test code.
/// @param msg The windows mouse message related to hit test.
///
/// @par Comments:
/// We are only concerned with detecting the completion of a sizing operation
/// consequently return false and let the default behavior stand.
///
/// @returns BOOL TRUE if handled.
static BOOL Grid_OnSetCursor(HWND hwnd, HWND hwndCursor, UINT codeHitTest, UINT msg)
{
if (NULL != g_lpInst->lpCurrent)
{
if(g_lpInst->fTracking) //Sizing operation concluded
{
if(PIT_CHECK == g_lpInst->lpCurrent->iItemType)
g_lpInst->lpCurrent->lpszMisc = SELECT;
g_lpInst->fTracking = FALSE;
FORWARD_WM_KEYDOWN(g_lpInst->hwndListBox, VK_TAB, 0, 0, SNDMSG); //Focus to editor
}
}
return FALSE;
}
/// @brief Handles WM_LBUTTONDOWN message.
///
/// @param hwnd Handle of grid.
/// @param fDoubleClick TRUE if this is a double click event.
/// @param x The xpos of the mouse.
/// @param y The ypos of the mouse.
/// @param keyFlags Set if certain keys down at time of click.
///
/// @returns VOID.
static VOID Grid_OnLButtonDown(HWND hwnd, BOOL fDoubleClick, INT x, INT y, UINT keyFlags)
{
if ((y >= g_lpInst->iVDivider - 5) && (y <= g_lpInst->iVDivider + 5))
{
//If mouse clicked on divider line, then start resizing
SetCursor(LoadCursor(NULL, IDC_SIZENS));
RECT rc;
GetWindowRect(hwnd, &rc);
rc.top += 10;
rc.bottom -= 10;
//Do not let mouse leave the list box boundary
ClipCursor(&rc);
g_lpInst->fTracking = TRUE;
g_lpInst->nDivLft = 0;
g_lpInst->nDivRht = WIDTH(rc); //DWM 1.7: Changed from = rc.right
g_lpInst->nOldDivY = y;
HDC hdc = GetDC(hwnd);
HPEN hOld = (HPEN)SelectObject(hdc, CreatePen(PS_SOLID, 3, 0));
InvertLine(hdc, g_lpInst->nDivLft, g_lpInst->nOldDivY,
g_lpInst->nDivRht, g_lpInst->nOldDivY);
DeleteObject(SelectObject(hdc, hOld));
ReleaseDC(hwnd, hdc);
//Capture the mouse
SetCapture(hwnd);
}
}
/// @brief Handles WM_LBUTTONUP message.
///
/// @param hwnd Handle of grid.
/// @param x The xpos of the mouse.
/// @param y The ypos of the mouse.
/// @param keyFlags Set if certain keys down at time of click.
///
/// @returns VOID.
static VOID Grid_OnLButtonUp(HWND hwnd, INT x, INT y, UINT keyFlags)
{
if (g_lpInst->fTracking)
{
g_lpInst->fTracking = FALSE;
//If mouse was captured then release it
if (hwnd == GetCapture())
ReleaseCapture();
ClipCursor(NULL);
//Set the divider position to the new value
g_lpInst->nOldDivY = y;
HDC hdc = GetDC(hwnd);
HPEN hOld = (HPEN)SelectObject(hdc, CreatePen(PS_SOLID, 3, 0));
InvertLine(hdc, g_lpInst->nDivLft, g_lpInst->nOldDivY,
g_lpInst->nDivRht, g_lpInst->nOldDivY);
DeleteObject(SelectObject(hdc, hOld));
ReleaseDC(hwnd,hdc);
//Trigger a resize
RECT rc;
GetClientRect(hwnd, &rc);
g_lpInst->iDescHeight = HEIGHT(rc) - y;
Grid_OnSize(hwnd, 0, WIDTH(rc), HEIGHT(rc));
}
}
/// @brief Handles WM_MOUSEMOVE message.
///
/// @param hwnd Handle of grid.
/// @param x The xpos of the mouse.
/// @param y The ypos of the mouse.
/// @param keyFlags Set if certain keys down at time of move.
///
/// @returns VOID.
static VOID Grid_OnMouseMove(HWND hwnd, INT x, INT y, UINT keyFlags)
{
if ((y >= g_lpInst->iVDivider - 5) && (y <= g_lpInst->iVDivider + 5))
{
SetCursor(LoadCursor(NULL, IDC_SIZENS));
}
else if (g_lpInst->fTracking)
{
//Move divider line to the mouse pos. if columns are
// currently being resized
HDC hdc = GetDC(hwnd);
HPEN hOld = (HPEN)SelectObject(hdc, CreatePen(PS_SOLID, 3, 0));
//Remove old divider line
InvertLine(hdc, g_lpInst->nDivLft, g_lpInst->nOldDivY,
g_lpInst->nDivRht, g_lpInst->nOldDivY);
//Draw new divider line
g_lpInst->nOldDivY = y;
InvertLine(hdc, g_lpInst->nDivLft, g_lpInst->nOldDivY,
g_lpInst->nDivRht, g_lpInst->nOldDivY);
DeleteObject(SelectObject(hdc, hOld));
ReleaseDC(hwnd, hdc);
}
else
{
SetCursor(LoadCursor(NULL, IDC_ARROW));
}
}
/// @brief Handles WM_CTLCOLORLISTBOX message sent to the grid.
///
/// @param hwnd Handle of grid.
/// @param hdc The handle of the device context.
/// @param hwndChild The handle of the listbox.
/// @param type CTLCOLOR_LISTBOX.
///
/// @returns HBRUSH The handle of the brush used to paint the
/// listbox's background.
static HBRUSH Grid_OnCtlColorListbox(HWND hwnd, HDC hdc, HWND hwndChild, INT type)
{
return SetColor(hdc, GetSysColor(COLOR_MENUTEXT), GetSysColor(COLOR_3DFACE));
}
/// @brief Handles WM_CTLCOLORSTATIC message sent to the grid.
///
/// @param hwnd Handle of grid.
/// @param hdc The handle of the device context.
/// @param hwndChild The handle of the static.
/// @param type CTLCOLOR_STATIC.
///
/// @returns HBRUSH The handle of the brush used to paint the
/// static's background.
static HBRUSH Grid_OnCtlColorStatic(HWND hwnd, HDC hdc, HWND hwndChild, INT type)
{
//DWM 1.3: Keep the area between the description and the list refreshed
if (NULL != g_lpInst->hwndPropDesc)
{
RECT rc;
GetClientRect(hwnd, &rc);
HDC hdc = GetDC(hwnd);
FillSolidRect(hdc,MAKE_PRECT(0, g_lpInst->iVDivider - 2,
WIDTH(rc), g_lpInst->iVDivider),GetSysColor(COLOR_BTNFACE));
ReleaseDC(hwnd,hdc);
}
return FORWARD_WM_CTLCOLORSTATIC(hwnd, hdc, hwndChild, DefWindowProc);
}
/// @brief Handles WM_MEASUREITEM message sent to the grid when the owner-drawn
/// listbox is created.
///
/// @param hwnd Handle of grid.
/// @param lpMeasureItem The structure that contains the dimensions of the
/// owner-drawn listbox.
///
/// @returns VOID.
static VOID Grid_OnMeasureItem(HWND hwnd, LPMEASUREITEMSTRUCT lpMeasureItem)
{
lpMeasureItem->itemHeight = MINIMUM_ITEM_HEIGHT; //pixels
}
/// @brief Ensure that a catalog item matching the given catalog name is expanded.
///
/// @param szCatalog The catalog name.
///
/// @returns VOID.
static VOID Grid_ExpandCatalog(LPCTSTR szCatalog)
{
if (NULL != szCatalog)
{
INT ln = _tcslen(szCatalog) + 1; //Check for empty string
if (ln == 1)
return;
}
for (INT i = 0; i < ListBox_GetCount(g_lpInst->hwndListBox); i++)
{
LPLISTBOXITEM pItem = ListBox_GetItemDataSafe(g_lpInst->hwndListBox, i);
if (NULL != pItem && PIT_CATALOG == pItem->iItemType &&
(NULL == szCatalog || 0 == _tcsicmp(pItem->lpszCatalog, szCatalog)))
{
if (pItem->fCollapsed)
ToggleCatalog(pItem);
}
}
}
/// @brief Ensure that a catalog item matching the given catalog name is collapsed.
///
/// @param szCatalog The catalog name.
///
/// @returns VOID.
static VOID Grid_CollapseCatalog(LPCTSTR szCatalog)
{
if (NULL != szCatalog)
{
INT ln = _tcslen(szCatalog) + 1; //Check for empty string
if (ln == 1)
return;
}
for (INT i = 0; i < ListBox_GetCount(g_lpInst->hwndListBox); i++)
{
LPLISTBOXITEM pItem = ListBox_GetItemDataSafe(g_lpInst->hwndListBox, i);
if (NULL != pItem && PIT_CATALOG == pItem->iItemType &&
(NULL == szCatalog || 0 == _tcsicmp(pItem->lpszCatalog, szCatalog)))
{
if (!pItem->fCollapsed)
ToggleCatalog(pItem);
}
}
}
/// @brief Handles WM_COMMAND message.
///
/// @param hwnd Handle of grid.
/// @param id The id of the sender.
/// @param hwndCtl The hwnd of the sender.
/// @param codeNotify The notification code sent.
///
/// @returns VOID.
static VOID Grid_OnCommand(HWND hwnd, INT id, HWND hwndCtl, UINT codeNotify)
{
if (g_lpInst->hwndListBox == hwndCtl)
{
switch (codeNotify)
{
case LBN_SELCHANGE:
{
SetFocus(hwndCtl);
INT iCurSel = ListBox_GetCurSel(hwndCtl);
//If this is a checkbox, notify of the previous item's change
LPLISTBOXITEM pItem = (LPLISTBOXITEM)ListBox_GetItemDataSafe(hwndCtl,
g_lpInst->iPrevSel);
if (NULL != pItem)
{
if (PIT_CHECK == pItem->iItemType)
{
if (iCurSel != g_lpInst->iPrevSel &&
0 == _tcsicmp(pItem->lpszMisc, SELECT))
pItem->lpszMisc = UNSELECT;
}
}
g_lpInst->lpCurrent = (LPLISTBOXITEM)ListBox_GetItemDataSafe(hwndCtl,
iCurSel);
if (NULL == g_lpInst->lpCurrent)
return;
//Display the property description if desired
if (NULL != g_lpInst->hwndPropDesc)
Static_SetText(g_lpInst->hwndPropDesc,
g_lpInst->lpCurrent->lpszPropDesc);
//Destroy previous editor
if (NULL != g_lpInst->hwndCtl1)
{
DestroyWindow(g_lpInst->hwndCtl1);
g_lpInst->hwndCtl1 = NULL;
}
if (NULL != g_lpInst->hwndCtl2)
{
DestroyWindow(g_lpInst->hwndCtl2);
g_lpInst->hwndCtl2 = NULL;
}
//Redraw the selection
if(iCurSel != g_lpInst->iPrevSel)
{
RECT rc;
memset(&rc, 0, sizeof rc);
ListBox_GetItemRect(hwndCtl, g_lpInst->iPrevSel, &rc);
RedrawWindow(hwndCtl, &rc,
NULL, RDW_ERASE | RDW_INVALIDATE | RDW_UPDATENOW);
ListBox_GetItemRect(hwndCtl, iCurSel, &rc);
RedrawWindow(hwndCtl, &rc,
NULL, RDW_ERASE | RDW_INVALIDATE | RDW_UPDATENOW);
g_lpInst->iPrevSel = iCurSel;
}
}
}
}
}
/// @brief Handles WM_DRAWITEM message sent to the grid when a visual aspect of
/// the owner-drawn listbox has changed.
///
/// @param hwnd Handle of grid.
/// @param lpDIS The structure that contains information about the item
/// to be drawn and the type of drawing required.
///
/// @returns VOID.
static VOID Grid_OnDrawItem(HWND hwnd, const DRAWITEMSTRUCT *lpDIS)
{
UINT nIndex = lpDIS->itemID;
if (lpDIS->hwndItem != g_lpInst->hwndListBox)
return;
if (nIndex == (UINT) - 1 || !(lpDIS->itemAction & ODA_DRAWENTIRE))
return;
//Get the item for the current row
LPLISTBOXITEM pItem = ListBox_GetItemDataSafe(lpDIS->hwndItem, nIndex);
if (NULL == pItem)
return;
RECT rectFullItem, rectPart0, rectCatPart1, rectPart1, rectPart2, rectPart3;
rectFullItem = lpDIS->rcItem;
if (g_lpInst->iHDivider == 0)
g_lpInst->iHDivider = WIDTH(rectFullItem) / 2;
rectPart0 = rectFullItem;
rectPart0.right = rectPart0.left + WIDTH_PART0;
rectCatPart1 = rectFullItem;
rectCatPart1.left = rectPart0.right;
rectPart1 = rectCatPart1;
rectPart1.right = g_lpInst->iHDivider;
rectPart2 = rectFullItem;
rectPart2.left = g_lpInst->iHDivider + (PIT_CATALOG == pItem->iItemType ? 0 : 1);
if (PIT_COLOR == pItem->iItemType || PIT_CHECK == pItem->iItemType)
{
rectPart2.right = rectPart2.left + WIDTH_PART2;
}
else
rectPart2.right = rectPart2.left;
rectPart3 = rectFullItem;
rectPart3.left = rectPart2.right;
//
//First part of item
//
FillRect(lpDIS->hDC, &rectPart0, (HBRUSH) GetStockObject(HOLLOW_BRUSH));
if (PIT_CATALOG == pItem->iItemType) //Draw expand / collapse box
{
RECT rect2;
rect2.left = rectPart0.left + 4;
rect2.top = rectPart0.top + 4;
rect2.right = rect2.left + 9;
rect2.bottom = rect2.top + 9;
FillRect(lpDIS->hDC, &rect2, GetSysColorBrush(COLOR_WINDOW));
FrameRect(lpDIS->hDC, &rect2, (HBRUSH) GetStockObject(BLACK_BRUSH));
POINT ptCtr;
ptCtr.x = (LONG) (rect2.left + (WIDTH(rect2) * 0.5));
ptCtr.y = (LONG) (rect2.top + (HEIGHT(rect2) * 0.5));
InflateRect(&rect2, -2, -2);
DrawLine(lpDIS->hDC, rect2.left, ptCtr.y, rect2.right, ptCtr.y); //Make a -
if (pItem->fCollapsed) //Convert to +
DrawLine(lpDIS->hDC, ptCtr.x, rect2.top, ptCtr.x, rect2.bottom);
}
//
//Second part of item
//
//If it is the selected item, set its background-color
HFONT oldFont;
if (PIT_CATALOG == pItem->iItemType)
{
FillRect(lpDIS->hDC, &rectCatPart1, (HBRUSH) GetStockObject(HOLLOW_BRUSH));
if (nIndex == (UINT)ListBox_GetCurSel(lpDIS->hwndItem) && g_lpInst->fGotFocus)
{
InflateRect(&rectCatPart1, -2, -2);
FrameRect(lpDIS->hDC, &rectCatPart1, GetSysColorBrush(COLOR_BTNSHADOW));
InflateRect(&rectCatPart1, 2, 2);
}
//Write the property name (bold font, Visual Studio style)
oldFont = (HFONT) SelectObject(lpDIS->hDC, Font_SetBold(lpDIS->hwndItem, TRUE));
SetTextColor(lpDIS->hDC, GetSysColor(COLOR_BTNTEXT));
DrawText(lpDIS->hDC, pItem->lpszCatalog, _tcslen(pItem->lpszCatalog),
MAKE_PRECT(rectCatPart1.left + 6, rectCatPart1.top + 3,
rectCatPart1.right - 6, rectCatPart1.bottom + 3),
DT_NOCLIP | DT_LEFT | DT_SINGLELINE);
}
else
{
SetBkMode(lpDIS->hDC, TRANSPARENT);
FillRect(lpDIS->hDC, &rectPart1,
GetSysColorBrush(nIndex == (UINT)ListBox_GetCurSel(lpDIS->hwndItem) ?
(g_lpInst->fGotFocus ? COLOR_HIGHLIGHT : COLOR_BTNFACE) : COLOR_WINDOW));
//Write the property name
oldFont = (HFONT) SelectObject(lpDIS->hDC, Font_SetBold(lpDIS->hwndItem, FALSE));
SetTextColor(lpDIS->hDC,
GetSysColor(nIndex == (UINT)ListBox_GetCurSel(lpDIS->hwndItem) ?
(g_lpInst->fGotFocus ? COLOR_HIGHLIGHTTEXT : COLOR_BTNTEXT) : COLOR_WINDOWTEXT));
DrawText(lpDIS->hDC, pItem->lpszPropName, _tcslen(pItem->lpszPropName),
MAKE_PRECT(rectPart1.left + 3, rectPart1.top + 3, rectPart1.right - 3,
rectPart1.bottom + 3), DT_NOCLIP | DT_LEFT | DT_SINGLELINE);
DrawBorder(lpDIS->hDC, &rectPart1, BF_TOPRIGHT,
GetSysColor(COLOR_BTNFACE));
}
DeleteObject(oldFont);
//
//Third part - Draw color box or check box
//
if (PIT_CATALOG != pItem->iItemType)
{
FillRect(lpDIS->hDC, &rectPart2, GetSysColorBrush(COLOR_WINDOW));
RECT rect3 = rectPart2;
rect3.left += 2;
rect3.top += 3;
rect3.bottom = rect3.top + 15;
rect3.right = rect3.left + 15;
if (PIT_COLOR == pItem->iItemType)
{
FillSolidRect(lpDIS->hDC, &rect3, GetColor(pItem->lpszCurValue));
FrameRect(lpDIS->hDC, &rect3, (HBRUSH) GetStockObject(BLACK_BRUSH));
}
else if (PIT_CHECK == pItem->iItemType)
{
if (0 == _tcsicmp(pItem->lpszMisc, SELECT))
{
FillRect(lpDIS->hDC, &rectPart2,
GetSysColorBrush(COLOR_HIGHLIGHT));
}
DrawFrameControl(lpDIS->hDC, &rect3, DFC_BUTTON, DFCS_BUTTONCHECK |
(_tcsicmp(pItem->lpszCurValue, CHECKED) == 0 ? DFCS_CHECKED : 0));
//DWM 1.8: Added
if (FLATCHECKS & (DWORD)GetWindowLongPtr(lpDIS->hwndItem, GWLP_USERDATA))
{
//Make border thin
FrameRect(lpDIS->hDC, &rect3, GetSysColorBrush(COLOR_BTNFACE));
InflateRect(&rect3, -1, -1);
FrameRect(lpDIS->hDC, &rect3, GetSysColorBrush(COLOR_WINDOW));
}
}
}
DrawBorder(lpDIS->hDC, &rectPart2, BF_TOP, GetSysColor(COLOR_BTNFACE));
//
//Fourth part - Write the initial property value in the second rectangle
//
if (PIT_CATALOG != pItem->iItemType)
{
FillRect(lpDIS->hDC, &rectPart3, GetSysColorBrush(COLOR_WINDOW));
}
if (PIT_CHECK != pItem->iItemType)
{
SetBkMode(lpDIS->hDC, TRANSPARENT);
if (PIT_FONT == pItem->iItemType)
{
TCHAR buf[MAX_PATH] = { 0 };
LPTSTR szFmt;
PROPGRIDFONTITEM pgfi;
TEXTMETRIC tm;
HFONT hf, hfOld;
LONG PointSize = 0;
LogFontItem_FromString(&pgfi, pItem->lpszCurValue);
PointSize = -MulDiv(pgfi.logFont.lfHeight, 72,
GetDeviceCaps(lpDIS->hDC, LOGPIXELSY));
if (0 < PointSize)
{
if (GetTextMetrics(lpDIS->hDC, &tm))
pgfi.logFont.lfHeight = tm.tmHeight + 2; //So displayed text is not oversized
hf = CreateFontIndirect(&pgfi.logFont);
hfOld = (HFONT) SelectObject(lpDIS->hDC, hf);
SetTextColor(lpDIS->hDC, pgfi.crFont);
#ifdef _UNICODE
szFmt = _T("%ls %d");
#else
szFmt = _T("%s %d");
#endif
_stprintf(buf, MAX_PATH, szFmt, pgfi.logFont.lfFaceName, PointSize);
DrawText(lpDIS->hDC, buf, _tcslen(buf),
MAKE_PRECT(rectPart3.left + 3, rectPart3.top + 3,
rectPart3.right + 3, rectPart3.bottom + 3),
DT_NOCLIP | DT_LEFT | DT_SINGLELINE);
DeleteObject(SelectObject(lpDIS->hDC, hfOld));
}
}
else
{
SetTextColor(lpDIS->hDC, GetSysColor(COLOR_WINDOWTEXT));
DrawText(lpDIS->hDC, pItem->lpszCurValue, _tcslen(pItem->lpszCurValue),
MAKE_PRECT(rectPart3.left + 3, rectPart3.top + 3,
rectPart3.right + 3, rectPart3.bottom + 3),
DT_NOCLIP | DT_LEFT | DT_SINGLELINE);
}
}
DrawBorder(lpDIS->hDC, &rectPart3, BF_TOP, GetSysColor(COLOR_BTNFACE));
}
/// @brief Handles WM_SHOWWINDOW message.
///
/// @param hwnd Handle of grid.
/// @param fShow Show/hide flag TRUE for show FALSE for hide.
/// @param status Status flag.
///
/// @returns VOID.
static VOID Grid_OnShowWindow(HWND hwnd, BOOL fShow, UINT status)
{
//DWM 1.3: Added this handler
if(!fShow) //Hiding so make sure we update the fields
{
FORWARD_WM_CHAR(g_lpInst->hwndCtl1, VK_RETURN, 0, SNDMSG);
FORWARD_WM_CHAR(g_lpInst->hwndCtl2, VK_RETURN, 0, SNDMSG);
}
FORWARD_WM_SHOWWINDOW(hwnd, fShow, status, DefWindowProc);
}
#pragma endregion grid message handlers
#pragma region public interface handlers
/// @brief Handles LB_ADDSTRING message sent to the grid.
///
/// @param pgi A pointer to a PROPGRIDITEM object.
///
/// @returns INT The index of the item added to the grid.
static INT Grid_OnAddString(LPPROPGRIDITEM pgi)
{
LPLISTBOXITEM lpi;
TCHAR buf[MAX_PATH];
_tmemset(buf, (TCHAR)0, MAX_PATH);
LPTSTR lpszCurValue = _T("");
//Make sure the catalog for this item has been inserted
INT idx = ListBox_FindCatalog(g_lpInst->hwndListBox, 0, pgi->lpszCatalog);
if (LB_ERR == idx) // Must add catalog to the listBox
{
lpi = NewItem(pgi->lpszCatalog,_T(""), _T(""),
_T(""), _T(""), PIT_CATALOG);
lpi->fCollapsed = TRUE;
ListBox_AddItemData(g_lpInst->hwndListBox, lpi);
}
switch (pgi->iItemType)
{
case PIT_CATALOG: //We explicitly added a catalog item to the listbox
return -2; // catalogs are not added to the list map so skip the rest
case PIT_EDIT:
case PIT_STATIC:
case PIT_COMBO:
case PIT_EDITCOMBO:
case PIT_FOLDER:
lpszCurValue = (LPTSTR)pgi->lpCurValue;
break;
case PIT_COLOR:
_stprintf(buf, MAX_PATH, _T("%d,%d,%d"),
GetRValue((COLORREF)pgi->lpCurValue),
GetGValue((COLORREF)pgi->lpCurValue),
GetBValue((COLORREF)pgi->lpCurValue));
lpszCurValue = buf;
break;
case PIT_FONT:
lpszCurValue = LogFontItem_ToString((LPPROPGRIDFONTITEM)pgi->lpCurValue);
break;
case PIT_CHECK:
lpszCurValue = (BOOL)pgi->lpCurValue ? CHECKED : UNCHECKED;
break;
case PIT_FILE:
pgi->lpszzCmbItems = FileDialogItem_ToString((LPPROPGRIDFDITEM)pgi->lpCurValue);
lpszCurValue = ((LPPROPGRIDFDITEM)pgi->lpCurValue)->lpszFilePath;
break;
case PIT_IP:
_stprintf(buf, MAX_PATH,
_T("%d.%d.%d.%d"),
FIRST_IPADDRESS((DWORD)pgi->lpCurValue),
SECOND_IPADDRESS((DWORD)pgi->lpCurValue),
THIRD_IPADDRESS((DWORD)pgi->lpCurValue),
FOURTH_IPADDRESS((DWORD)pgi->lpCurValue));
lpszCurValue = buf;
break;
case PIT_DATE:
GetDateFormat(LOCALE_USER_DEFAULT, DATE_SHORTDATE,
(LPSYSTEMTIME)pgi->lpCurValue, NULL, buf, MAX_PATH);
lpszCurValue = buf;
break;
case PIT_TIME:
GetTimeFormat(LOCALE_USER_DEFAULT, 0, (LPSYSTEMTIME)pgi->lpCurValue,
_T("hh':'mm':'ss tt"), buf, MAX_PATH);
lpszCurValue = buf;
break;
case PIT_DATETIME:
GetDateFormat(LOCALE_USER_DEFAULT, DATE_SHORTDATE,
(LPSYSTEMTIME)pgi->lpCurValue, NULL, buf, MAX_PATH);
_tcscat(buf, _T(" "));
GetTimeFormat(LOCALE_USER_DEFAULT, 0, (LPSYSTEMTIME)pgi->lpCurValue,
_T("hh':'mm':'ss tt"), (LPTSTR) (buf + _tcslen(buf)),
MAX_PATH - _tcslen(buf));
lpszCurValue = buf;
break;
}
lpi = NewItem(pgi->lpszCatalog, pgi->lpszPropName,
lpszCurValue, pgi->lpszzCmbItems, pgi->lpszPropDesc, pgi->iItemType);
return ListBox_AddItemData(g_lpInst->hwndListMap, lpi);
}
/// @brief Handles LB_DELETESTRING message sent to the grid.
///
/// @param nIndex The index of the item to delete.
///
/// @returns INT A count of the items remaining in the grid. The return value is
/// LB_ERR if the index parameter specifies an index greater than
/// the number of items in the grid.
static INT Grid_OnDeleteString(INT nIndex)
{
LPLISTBOXITEM pItem = ListBox_GetItemDataSafe(g_lpInst->hwndListMap, nIndex);
if (NULL == pItem)
return LB_ERR;
INT iItem = ListBox_FindItemData(g_lpInst->hwndListBox, 0, pItem);
if (LB_ERR != iItem)
ListBox_DeleteString(g_lpInst->hwndListBox, iItem);
return ListBox_DeleteString(g_lpInst->hwndListMap, nIndex);
}
/// @brief Handles LB_GETCURSEL message sent to the grid.
///
/// @returns INT The zero-based index of the selected item. If there is no
/// selection, the return value is LB_ERR.
static INT Grid_OnGetCurSel(VOID)
{
INT iItem = ListBox_GetCurSel(g_lpInst->hwndListBox);
if (LB_ERR != iItem)
{
LPLISTBOXITEM pItem = ListBox_GetItemDataSafe(g_lpInst->hwndListBox, iItem);
if (NULL != pItem)
{
if (PIT_CATALOG != pItem->iItemType)
return ListBox_FindItemData(g_lpInst->hwndListMap, 0, pItem);
}
}
//If we get here we didn't succeed
return LB_ERR;
}
/// @brief Handles LB_GETITEMDATA message sent to the grid.
///
/// @param iItem The zero-based index of the item.
///
/// @returns LRESULT In this case a pointer to a PROPGRIDITEM object.
static LRESULT Grid_OnGetItemData(INT iItem)
{
static PROPGRIDITEM pgi;
static PROPGRIDFONTITEM pgfi;
static PROPGRIDFDITEM pgfdi;
static SYSTEMTIME st = {0};
static TCHAR outbuf[MAX_PATH];
LPLISTBOXITEM pItem = ListBox_GetItemDataSafe(g_lpInst->hwndListMap, iItem);
if (NULL != pItem)
{
if (PIT_CATALOG != pItem->iItemType)
{
pgi.lpszCatalog = pItem->lpszCatalog;
pgi.lpszPropName = pItem->lpszPropName;
pgi.lpszzCmbItems = pItem->lpszMisc;
pgi.lpszPropDesc = pItem->lpszPropDesc;
pgi.iItemType = pItem->iItemType;
switch (pgi.iItemType)
{
case PIT_EDIT:
case PIT_COMBO:
case PIT_EDITCOMBO:
case PIT_STATIC:
case PIT_FOLDER:
pgi.lpCurValue = (LPARAM)pItem->lpszCurValue;
break;
case PIT_COLOR:
pgi.lpCurValue = (LPARAM)GetColor(pItem->lpszCurValue);
break;
case PIT_FONT:
LogFontItem_FromString(&pgfi, pItem->lpszCurValue);
pgi.lpCurValue = (LPARAM) & pgfi;
break;
case PIT_CHECK:
pgi.lpCurValue = (LPARAM) (0 == _tcsicmp(CHECKED,
pItem->lpszCurValue));
break;
case PIT_FILE:
FileDialogItem_FromString(&pgfdi, pgi.lpszzCmbItems);
pgi.lpCurValue = (LPARAM) & pgfdi;
break;
case PIT_IP:
{
//DWM 1.9: Updated this code to use DWORD array and MAKEIPADDRESS macroe instead of 4 byte array and cast.
//DWM 1.9: This was necessary to support MSVC _stscanf (sscanf - swscanf) beahavior which expects DWORD arguments.
DWORD ip[4] = { 0, 0, 0, 0 };
_stscanf(pItem->lpszCurValue, _T("%hhd.%hhd.%hhd.%hhd"),
&ip[0], &ip[1], &ip[2], &ip[3]);
pgi.lpCurValue = (LPARAM) MAKEIPADDRESS(ip[0],ip[1],ip[2],ip[3]);
}
break;
case PIT_DATE:
memset(&st, 0, sizeof(SYSTEMTIME));
_stscanf(pItem->lpszCurValue, _T("%hd/%hd/%hd"),
&st.wMonth, &st.wDay, &st.wYear);
pgi.lpCurValue = (LPARAM) & st;
break;
case PIT_TIME:
memset(&st, 0, sizeof(SYSTEMTIME));
_tmemset(outbuf, (TCHAR)0, MAX_PATH);
_stscanf(pItem->lpszCurValue,
#ifdef _UNICODE
_T("%hd:%hd:%hd %2ls"),
#else
_T("%hd:%hd:%hd %2s"),
#endif
&st.wHour, &st.wMinute, &st.wSecond, (LPTSTR)&outbuf);
if ((0 == _tcsicmp(_T("PM"), outbuf)) && st.wHour != 12)//DWM 1.6:Added st.wHour != 12
st.wHour += 12;
pgi.lpCurValue = (LPARAM)&st;
break;
case PIT_DATETIME:
memset(&st, 0, sizeof(SYSTEMTIME));
_tmemset(outbuf, (TCHAR)0, MAX_PATH);
_stscanf(pItem->lpszCurValue,
#ifdef _UNICODE
_T("%hd/%hd/%hd %hd:%hd:%hd %2ls"),
#else
_T("%hd/%hd/%hd %hd:%hd:%hd %2s"),
#endif
&st.wMonth, &st.wDay, &st.wYear,
&st.wHour, &st.wMinute, &st.wSecond, (LPTSTR)&outbuf);
if ((0 == _tcsicmp(_T("PM"), outbuf)) && st.wHour != 12)//DWM 1.6:Added st.wHour != 12
st.wHour += 12;
pgi.lpCurValue = (LPARAM) & st;
break;
}
}
return (LRESULT) & pgi;
}
else
return LB_ERR;
}
/// @brief Handles LB_GETSEL message sent to the grid.
///
/// @param iItem The zero-based index of the item.
///
/// @returns BOOL TRUE if the item is selected, FALSE if not, or LB_ERR
/// if an error occurs.
static BOOL Grid_OnGetSel(INT iItem)
{
LPLISTBOXITEM pItem = ListBox_GetItemDataSafe(g_lpInst->hwndListMap, iItem);
if (NULL != pItem)
{
if (PIT_CATALOG != pItem->iItemType)
{
INT i = ListBox_FindItemData(g_lpInst->hwndListBox, 0, pItem);
if (LB_ERR != i)
return ListBox_GetSel(g_lpInst->hwndListBox, i);
else
return FALSE; //Collapsed Not selected
}
}
return LB_ERR;
}
/// @brief Handles LB_RESETCONTENT message sent to the grid.
///
/// @returns VOID.
static VOID Grid_OnResetContent(VOID)
{
ListBox_ResetContent(g_lpInst->hwndListMap);
if (NULL != g_lpInst->hwndCtl1)
{
DestroyWindow(g_lpInst->hwndCtl1);
g_lpInst->hwndCtl1 = NULL;
}
if (NULL != g_lpInst->hwndCtl2)
{
DestroyWindow(g_lpInst->hwndCtl2);
g_lpInst->hwndCtl2 = NULL;
}
ListBox_ResetContent(g_lpInst->hwndListBox);
Static_SetText(g_lpInst->hwndPropDesc,_T("")); //DWM 1.2: Clear the property pane
}
/// @brief Handles LB_SETCURSEL message sent to the grid.
///
/// @param iItem The zero-based index of the item to select, or <20>1 to clear the selection.
///
/// @returns LRESULT If an error occurs, the return value is LB_ERR.
/// If the index parameter is <20>1, the return value is LB_ERR
/// even though no error occurred.
static LRESULT Grid_OnSetCurSel(INT iItem)
{
LPLISTBOXITEM pItem = ListBox_GetItemDataSafe(g_lpInst->hwndListMap, iItem);
if (NULL != pItem)
{
if (PIT_CATALOG != pItem->iItemType)
{
INT i = ListBox_FindItemData(g_lpInst->hwndListBox, 0, pItem);
if (LB_ERR != i)
{
INT ret = ListBox_SetCurSel(g_lpInst->hwndListBox, i);
FORWARD_WM_COMMAND(GetParent(g_lpInst->hwndListBox),
GetDlgCtrlID(g_lpInst->hwndListBox), g_lpInst->hwndListBox,
LBN_SELCHANGE, SNDMSG);
if(PIT_CHECK == pItem->iItemType)
pItem->lpszMisc = SELECT; //DWM 1.2: Ensure Checkbox selected
Refresh(g_lpInst->hwndListBox);
return ret;
}
}
}
return LB_ERR;
}
/// @brief Handles LB_ADDSTRING message sent to the grid.
///
/// @param iItem The zero-based index of the item.
/// @param pgi A pointer to a PROPGRIDITEM object.
///
/// @returns LRESULT The return value is the zero-based index of the item in
/// the list. If an error occurs, the return value is LB_ERR.
/// If there is insufficient space to store the data,
/// the return value is LB_ERRSPACE.
static LRESULT Grid_OnSetItemData(INT iItem, LPPROPGRIDITEM pgi)
{
if (PIT_CATALOG == pgi->iItemType)
return LB_ERR; //Can't change an item to a catalog
LRESULT lrtn = LB_ERR;
LPLISTBOXITEM lpiCurrent, lpiNew;
TCHAR buf[MAX_PATH];
_tmemset(buf, (TCHAR)0, MAX_PATH);
LPTSTR lpszCurValue = _T("");
//Get the current item
lpiCurrent = ListBox_GetItemDataSafe(g_lpInst->hwndListMap, iItem);
if (NULL != lpiCurrent)
{
switch (pgi->iItemType)
{
case PIT_EDIT:
case PIT_STATIC:
case PIT_COMBO:
case PIT_EDITCOMBO:
case PIT_FOLDER:
lpszCurValue = (LPTSTR)pgi->lpCurValue;
break;
case PIT_COLOR:
_stprintf(buf, MAX_PATH, _T("%d,%d,%d"),
GetRValue((COLORREF)pgi->lpCurValue),
GetGValue((COLORREF)pgi->lpCurValue),
GetBValue((COLORREF)pgi->lpCurValue));
lpszCurValue = buf;
break;
case PIT_FONT:
lpszCurValue = LogFontItem_ToString((LPPROPGRIDFONTITEM)pgi->lpCurValue);
break;
case PIT_CHECK:
lpszCurValue = (BOOL)pgi->lpCurValue ? CHECKED : UNCHECKED;
break;
case PIT_FILE:
pgi->lpszzCmbItems = FileDialogItem_ToString((LPPROPGRIDFDITEM)pgi->lpCurValue);
lpszCurValue = ((LPPROPGRIDFDITEM)pgi->lpCurValue)->lpszFilePath;
break;
case PIT_IP:
_stprintf(buf, MAX_PATH, _T("%d.%d.%d.%d"),
FIRST_IPADDRESS((DWORD)pgi->lpCurValue),
SECOND_IPADDRESS((DWORD)pgi->lpCurValue),
THIRD_IPADDRESS((DWORD)pgi->lpCurValue),
FOURTH_IPADDRESS((DWORD)pgi->lpCurValue));
lpszCurValue = buf;
break;
case PIT_DATE:
GetDateFormat(LOCALE_USER_DEFAULT, DATE_SHORTDATE,
(LPSYSTEMTIME)pgi->lpCurValue, NULL, buf, MAX_PATH);
lpszCurValue = buf;
break;
case PIT_TIME:
GetTimeFormat(LOCALE_USER_DEFAULT, 0,
(LPSYSTEMTIME)pgi->lpCurValue, _T("hh':'mm':'ss tt"),
buf, MAX_PATH);
lpszCurValue = buf;
break;
case PIT_DATETIME:
GetDateFormat(LOCALE_USER_DEFAULT, DATE_SHORTDATE,
(LPSYSTEMTIME)pgi->lpCurValue, NULL, buf, MAX_PATH);
_tcscat(buf, _T(" "));
GetTimeFormat(LOCALE_USER_DEFAULT, 0, (LPSYSTEMTIME)pgi->lpCurValue,
_T("hh':'mm':'ss tt"), (LPTSTR) (buf + _tcslen(buf)),
MAX_PATH - _tcslen(buf));
lpszCurValue = buf;
break;
}
//Do not allow the catalog to change; to do so would necessitate moving
// the item to a different index throwing off the indexing for items.
lpiNew = NewItem(lpiCurrent->lpszCatalog, pgi->lpszPropName,
lpszCurValue, pgi->lpszzCmbItems, pgi->lpszPropDesc, pgi->iItemType);
//Set new data
lrtn = ListBox_SetItemData(g_lpInst->hwndListMap, iItem, lpiNew);
if(LB_ERR != lrtn)
{
lrtn = ListBox_FindItemData(g_lpInst->hwndListBox,0,lpiCurrent);
if(LB_ERR != lrtn)
{
lrtn = ListBox_SetItemData(g_lpInst->hwndListBox, lrtn, lpiNew);
//DWM 1.3: Refresh Display of item
Refresh(g_lpInst->hwndListBox);
}
}
if(LB_ERR == lrtn)
return LB_ERR;
//Reset lpCurrent if necessary
if(lpiCurrent == g_lpInst->lpCurrent)
g_lpInst->lpCurrent = lpiNew;
//Delete old data
DeleteItem(lpiCurrent);
}
return lrtn;
}
#pragma endregion public interface handlers
#pragma region create destroy
/// @brief Handles WM_CREATE message.
///
/// @param hwnd Handle of grid.
/// @param lpCreateStruct Pointer to a structure with creation data.
///
/// @returns BOOL If an application processes this message,
/// it should return TRUE to continue creation of the window.
static BOOL Grid_OnCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct)
{
INSTANCEDATA inst;
memset(&inst, 0, sizeof(INSTANCEDATA));
inst.hInstance = lpCreateStruct->hInstance;
inst.hwndParent = lpCreateStruct->hwndParent;
inst.fXpOrLower = IsWindowsXPorLower(); // DWM 1.4: Added
inst.fGotFocus = FALSE;
inst.fScrolling = FALSE;
inst.fTracking = FALSE;
inst.iDescHeight = HEIGHT_DESC;
inst.iPrevSel = LB_ERR; //Nothing selected
//Create the listbox control
inst.hwndListBox = CreateListBox(lpCreateStruct->hInstance, hwnd, ID_LISTBOX);
if (NULL == inst.hwndListBox)
return FALSE;
//And the hidden list map
inst.hwndListMap = CreateListMap(lpCreateStruct->hInstance, hwnd, ID_LISTMAP);
if (NULL == inst.hwndListMap)
return FALSE;
return Control_CreateInstanceData(hwnd, &inst);
}
/// @brief Handles WM_DESTROY message.
///
/// @param hwnd Handle of Grid.
///
/// @returns VOID.
static VOID Grid_OnDestroy(HWND hwnd)
{
if (NULL != g_lpInst)
{
ListBox_ResetContent(g_lpInst->hwndListMap); //Delete all Items
DestroyWindow(g_lpInst->hwndListMap);
DestroyWindow(g_lpInst->hwndListBox);
if (NULL != g_lpInst->hwndToolTip)
DestroyWindow(g_lpInst->hwndToolTip);
if (NULL != g_lpInst->hwndPropDesc)
DestroyWindow(g_lpInst->hwndPropDesc);
if (NULL != g_lpInst->hwndCtl1)
DestroyWindow(g_lpInst->hwndCtl1);
if (NULL != g_lpInst->hwndCtl2)
DestroyWindow(g_lpInst->hwndCtl2);
Control_FreeInstanceData(hwnd);
//DWM 1.3: Removed PostQuitMessage() call.
}
}
/// @brief Initialize and register the property grid class.
///
/// @param hInstance Handle of application instance.
///
/// @returns ATOM If the function succeeds, the atom that uniquely identifies
/// the class being registered, otherwise 0.
ATOM InitPropertyGrid(HINSTANCE hInstance)
{
WNDCLASSEX wcex;
// Get standard listbox information
wcex.cbSize = sizeof(wcex);
if (!GetClassInfoEx(NULL, WC_LISTBOX, &wcex))
return 0;
// Add our own stuff
wcex.lpfnWndProc = (WNDPROC)Grid_Proc;;
wcex.hInstance = hInstance;
wcex.lpszClassName = g_szClassName;
// Register our new class
return RegisterClassEx(&wcex);
}
/// @brief Create an new instance of the property grid.
///
/// @param hParent Handle of the grid's parent.
/// @param dwID The ID for this control.
///
/// @returns HWND If the function succeeds, the grid handle, otherwise NULL.
HWND New_PropertyGrid(HWND hParent, DWORD dwID)
{
static ATOM aPropertyGrid = 0;
static HWND hPropertyGrid;
HINSTANCE hinst = (HINSTANCE)GetWindowLongPtr(hParent, GWLP_HINSTANCE);
//Only need to register the property grid once
if (!aPropertyGrid)
aPropertyGrid = InitPropertyGrid(hinst);
hPropertyGrid = CreateWindowEx(0, g_szClassName, _T(""),
WS_CHILD | WS_TABSTOP, 0, 0, 0, 0, hParent, (HMENU)dwID, hinst, NULL);
return hPropertyGrid;
}
#pragma endregion create destroy
/// @brief Send a PGN_PROPERTYCHANGE notification to grid's parent.
///
/// @returns VOID.
static VOID Grid_NotifyParent(VOID)
{
static NMPROPGRID nmPropGrid;
// Notify of the change
if (NULL != g_lpInst->lpCurrent &&
PIT_CATALOG != g_lpInst->lpCurrent->iItemType &&
PIT_STATIC != g_lpInst->lpCurrent->iItemType)
{
nmPropGrid.hdr.hwndFrom = GetParent(g_lpInst->hwndListBox);
nmPropGrid.hdr.idFrom = GetDlgCtrlID(nmPropGrid.hdr.hwndFrom);
nmPropGrid.hdr.code = PGN_PROPERTYCHANGE;
LRESULT lres = ListBox_FindItemData(g_lpInst->hwndListMap, 0, g_lpInst->lpCurrent);
nmPropGrid.iIndex = LB_ERR == lres ? -1 : lres;
FORWARD_WM_NOTIFY(g_lpInst->hwndParent, nmPropGrid.hdr.idFrom, &nmPropGrid, SNDMSG);
}
}
/// @brief Window procedure for the visible owner-drawn listbox control.
///
/// @param hList Handle of listbox.
/// @param msg Which message?
/// @param wParam Message parameter.
/// @param lParam Message parameter.
///
/// @returns LRESULT depends on message.
static LRESULT CALLBACK ListBox_Proc(HWND hList, UINT msg,
WPARAM wParam, LPARAM lParam)
{
HWND hParent = GetParent(hList);
// Note: Instance data is attached to listbox's parent
Control_GetInstanceData(hParent, &g_lpInst);
switch (msg)
{
HANDLE_MSG(hList, WM_COMMAND, ListBox_OnCommand);
HANDLE_MSG(hList, WM_LBUTTONDOWN, ListBox_OnLButtonDown);
HANDLE_MSG(hList, WM_LBUTTONDBLCLK, ListBox_OnLButtonDown);
HANDLE_MSG(hList, WM_LBUTTONUP, ListBox_OnLButtonUp);
HANDLE_MSG(hList, WM_MOUSEMOVE, ListBox_OnMouseMove);
HANDLE_MSG(hList, WM_KEYDOWN, ListBox_OnKeyDown);
HANDLE_MSG(hList, WM_KILLFOCUS, ListBox_OnKillFocus);
case WM_SETFOCUS: //DWM 1.3: Focus to ListBox
g_lpInst->fGotFocus = TRUE;
Refresh(g_lpInst->hwndListBox);
return 0;
case WM_MBUTTONDOWN:
case WM_NCLBUTTONDOWN:
//The listbox doesn't have a scrollbar component, it draws a scroll
// bar in the non-client area of the control. A mouse click in the
// non-client area then, equals clicking on a scroll bar. A click
// on the middle mouse button equals pan, we'll handle that as if
// it were a scroll event.
ListBox_OnBeginScroll(hList);
g_lpInst->fScrolling = TRUE;
break;
case WM_SETCURSOR:
//Whenever the mouse leaves the non-client area of a listbox, it
// fires a WM_SETCURSOR message. The same happens when the middle
// mouse button is released. We can use this behavior to detect the
// completion of a scrolling operation.
if (g_lpInst->fScrolling)
{
ListBox_OnEndScroll(hList);
g_lpInst->fScrolling = FALSE;
}
break;
case WM_GETDLGCODE:
return DLGC_WANTALLKEYS;
case WM_DESTROY: //Unsubclass the listbox Control
{
SetWindowLongPtr(hList, GWLP_WNDPROC, (DWORD_PTR)GetProp(hList, WPRC)); //DWM 1.5: fixed cast
RemoveProp(hList, WPRC);
return 0;
}
}
return DefProc(hList, msg, wParam, lParam);
}
/// @brief Window procedure and public interface for the property grid.
///
/// @param hwnd Handle of grid.
/// @param msg Which message?
/// @param wParam Message parameter.
/// @param lParam Message parameter.
///
/// @returns LRESULT depends on message.
static LRESULT CALLBACK Grid_Proc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
Control_GetInstanceData(hwnd, &g_lpInst);//Update the instance pointer
switch (msg)
{
HANDLE_MSG(hwnd, WM_COMMAND, Grid_OnCommand);
HANDLE_MSG(hwnd, WM_CREATE, Grid_OnCreate);
HANDLE_MSG(hwnd, WM_DESTROY, Grid_OnDestroy);
HANDLE_MSG(hwnd, WM_SIZE, Grid_OnSize);
HANDLE_MSG(hwnd, WM_CTLCOLORLISTBOX, Grid_OnCtlColorListbox);
HANDLE_MSG(hwnd, WM_CTLCOLORSTATIC, Grid_OnCtlColorStatic);
HANDLE_MSG(hwnd, WM_DRAWITEM, Grid_OnDrawItem);
HANDLE_MSG(hwnd, WM_MEASUREITEM, Grid_OnMeasureItem);
HANDLE_MSG(hwnd, WM_DELETEITEM, Grid_OnDeleteItem);
HANDLE_MSG(hwnd, WM_LBUTTONDOWN, Grid_OnLButtonDown);
HANDLE_MSG(hwnd, WM_LBUTTONUP, Grid_OnLButtonUp);
HANDLE_MSG(hwnd, WM_MOUSEMOVE, Grid_OnMouseMove);
HANDLE_MSG(hwnd, WM_SETCURSOR, Grid_OnSetCursor);
HANDLE_MSG(hwnd, WM_SHOWWINDOW, Grid_OnShowWindow);
case WM_SETFOCUS: //DWM 1.3: Focus to ListBox
SetFocus(g_lpInst->hwndListBox);
return 0;
case LB_ADDSTRING: //PropGrid_AddItem
return Grid_OnAddString((LPPROPGRIDITEM)lParam);
case LB_DELETESTRING: //PropGrid_DeleteItem
return Grid_OnDeleteString((INT)wParam);
case LB_GETCOUNT: //PropGrid_GetCount
return ListBox_GetCount(g_lpInst->hwndListMap);
case LB_GETCURSEL: //PropGrid_GetCurSel
return Grid_OnGetCurSel();
case LB_GETHORIZONTALEXTENT: //PropGrid_GetHorizontalExtent
return ListBox_GetHorizontalExtent(g_lpInst->hwndListBox);
case LB_GETITEMDATA: //PropGrid_GetItemData
return Grid_OnGetItemData((INT)wParam);
case LB_GETITEMHEIGHT: //PropGrid_GetItemHeight
return ListBox_GetItemHeight(g_lpInst->hwndListBox, wParam);
case LB_GETITEMRECT: //PropGrid_GetItemRect
return ListBox_GetItemRect(g_lpInst->hwndListBox, wParam, lParam);
case LB_GETSEL: //PropGrid_GetSel
return Grid_OnGetSel((INT)wParam);
case LB_RESETCONTENT: //PropGrid_ResetContent
Grid_OnResetContent();
break;
case LB_SETCURSEL: //PropGrid_SetCurSel
return Grid_OnSetCurSel((INT)wParam);
case LB_SETHORIZONTALEXTENT: //PropGrid_SetHorizontalExtent
ListBox_SetHorizontalExtent(g_lpInst->hwndListBox, wParam);
break;
case LB_SETITEMDATA: //PropGrid_SetItemData
return Grid_OnSetItemData((INT)wParam, (LPPROPGRIDITEM)lParam);
case LB_SETITEMHEIGHT: //PropGrid_SetItemHeight
{
if (MINIMUM_ITEM_HEIGHT > LOWORD(lParam))
return LB_ERR;
LRESULT lres = SNDMSG(g_lpInst->hwndListBox, msg, wParam, lParam);
Refresh(g_lpInst->hwndListBox);
return lres;
}
case PG_FLATCHECKS: //DWM 1.8: Added
{
DWORD dwUserData = (DWORD)GetWindowLongPtr(g_lpInst->hwndListBox, GWLP_USERDATA);
if (FALSE != (BOOL)wParam)
dwUserData |= FLATCHECKS;
else
dwUserData &= ~FLATCHECKS;
return SetWindowLongPtr(g_lpInst->hwndListBox, GWLP_USERDATA,
(LONG_PTR)dwUserData);
}
case PG_EXPANDCATALOGS:
{
if (NULL == (LPTSTR)lParam) //Expand all
{
Grid_ExpandCatalog(NULL);
}
else
{
//Walk the catalog list and handle each string until the empty string
for (LPTSTR p = (LPTSTR)lParam; *p; p += _tcslen(p) + 1)
Grid_ExpandCatalog((LPCTSTR)p);
}
}
break;
case PG_COLLAPSECATALOGS:
{
if (NULL == (LPTSTR)lParam) //Collapse all
{
Grid_CollapseCatalog(NULL);
}
else
{
for (LPTSTR p = (LPTSTR)lParam; *p; p += _tcslen(p) + 1)
Grid_CollapseCatalog((LPCTSTR)p);
}
}
break;
case PG_SHOWTOOLTIPS:
{
if ((BOOL)wParam)
{
if (NULL == g_lpInst->hwndToolTip)
{
g_lpInst->hwndToolTip = CreateToolTip(g_lpInst->hInstance, g_lpInst->hwndListBox);
if (NULL != g_lpInst->hwndToolTip)
{
TOOLINFO ti = { sizeof(ti) };
ti.hwnd = g_lpInst->hwndListBox;
ti.uId = 0;
GetClientRect(hwnd, &ti.rect);
ToolTip_NewToolRect(g_lpInst->hwndToolTip, &ti);
ShowWindow(g_lpInst->hwndToolTip, SW_SHOW);
}
}
}
else
{
DestroyWindow(g_lpInst->hwndToolTip);
g_lpInst->hwndToolTip = NULL;
}
}
break;
case PG_SHOWPROPERTYDESC:
{
if ((BOOL)wParam)
{
if (NULL == g_lpInst->hwndPropDesc)
g_lpInst->hwndPropDesc = CreateStatic(g_lpInst->hInstance, hwnd, ID_PROPDESC);
ShowWindow(g_lpInst->hwndPropDesc, SW_SHOW);
}
else
{
DestroyWindow(g_lpInst->hwndPropDesc);
g_lpInst->hwndPropDesc = NULL;
}
//Call Grid_OnSize() to position and display the control
RECT rc;
GetClientRect(hwnd, &rc);
Grid_OnSize(hwnd, 0, WIDTH(rc), HEIGHT(rc));
}
break;
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}