Files
2024-12-29 19:01:43 +01:00

507 lines
20 KiB
C++

/*****************************************************************************/
/* DlgFlags.cpp Copyright (c) Ladislav Zezula 2004 */
/*---------------------------------------------------------------------------*/
/* Each entry in the dialog is defined by the structure: */
/* */
/* struct TFlagInfo */
/* { */
/* union */
/* { */
/* LPCTSTR szItemText; // Text of the checkbox/radio button */
/* UINT nIDCtrl; // ID of the checkbox/radio button */
/* }; */
/* */
/* DWORD dwMask; // Item is checked when */
/* DWORD dwValue; // (dwFlags & dwMask) == dwValue */
/* }; */
/* */
/*---------------------------------------------------------------------------*/
/* Date Ver Who Comment */
/* -------- ---- --- ------- */
/* 06.01.04 1.00 Lad Created */
/* 22.10.15 1.00 Lad Flag info extended to contain mask as well */
/*****************************************************************************/
#include "FileTest.h"
#include "resource.h"
//-----------------------------------------------------------------------------
// Local structures
typedef std::vector<TDlgFlagInfo> TFlagList;
struct TFlagDialogData
{
TFlagDialogData()
{
ZeroMemory(&pFlags, sizeof(TFlagDialogData) - FIELD_OFFSET(TFlagDialogData, pFlags));
}
TFlagList FlagList;
TFlagInfo * pFlags; // Flags (structure array)
HWND hWndParent; // Parent of the dialog
HWND hWndPrev; // The previous window
HWND hDlg; // The dialog itself
UINT nIDTitle; // String ID of the dialog title
size_t nColumns; // Number of columns in the dialog
size_t nColumn1; // Number of bits in the first column
LPCSTR szFocusText; // If non-NULL, this string will be focused, if found
size_t ccFocusText; // Length of the szFocusText
DWORD dwBitMask; // Flag value (in/out)
// Dialog layout
HFONT hFont; // DialogFont
RECT RectInner; // Space between dialog and checkbox/radiobutton
RECT RectButton; // Size of the checkbox/radiobutton
DWORD dwExStyle;
DWORD dwStyle;
int xGroup; // Distance of the group from the left-top corner of the client
int yGroup; // Distance of the group from the left-top corner of the client
int xButton; // Distance of the button from the left border of the dialog
int yButton; // Distance of the button from the top border of the dialog
int cxButton; // Vertical distance between two buttons
int cyButton; // Vertical distance between two buttons
int cxSpace; // Column space (for multi-column dialogs)
int cySpace; // Column space (for multi-column dialogs)
bool bIsValuesDialog; // True = this is list of radio buttons
char szCustomValue[0x40]; // String container for the extra check box, containing a custom value
};
typedef void (*CHILD_WINDOW_CALLBACK)(TFlagDialogData * pData, TFlagInfo * pFlags, HWND hWndChild, DWORD dwStyles);
//-----------------------------------------------------------------------------
// Local functions
static bool IsThisButtonChecked(TFlagDialogData * pData, TDlgFlagInfo & FlagInfo, DWORD dwFlags)
{
// Is there a focus string?
if(pData->szFocusText && pData->ccFocusText)
return (_strnicmp(FlagInfo.szFlagText, pData->szFocusText, pData->ccFocusText) == 0);
// Default behavior
return FlagInfo.IsValuePresent(dwFlags);
}
static size_t BuildFlagList(TFlagDialogData * pData)
{
TFlagList & FlagList = pData->FlagList;
TFlagInfo * pFlags = pData->pFlags;
DWORD dwCustomValue = pData->dwBitMask;
DWORD dwPrevMask = 0xF0F0F0F0;
DWORD i = 0;
// Initialize the flag about values dialog
pData->bIsValuesDialog = true;
// Parse all flags. Determine their count, types, and whether we are values dialog of flag dialog
for(pFlags = pData->pFlags; !pFlags->IsTerminator(); i++, pFlags++)
{
// By default, it's check box
TDlgFlagInfo FlagInfo(pFlags->szFlagText, pFlags->dwValue, pFlags->dwMask, BS_AUTOCHECKBOX);
// Exclude separators
if(!pFlags->IsSeparator())
{
// If the flag value has the same mask like the previous one, set both to radio-button
if(i > 0)
{
if(pFlags->dwMask == dwPrevMask)
{
FlagList[i-1].dwButtonType = BS_AUTORADIOBUTTON;
FlagInfo.dwButtonType = BS_AUTORADIOBUTTON;
}
else
{
pData->bIsValuesDialog = false;
}
}
// Remember the previous mask
dwCustomValue = dwCustomValue & ~pFlags->dwMask;
dwPrevMask = pFlags->dwMask;
}
else
{
FlagInfo.dwButtonType = BS_TYPEMASK;
}
// Insert the flag into the list
FlagList.push_back(FlagInfo);
}
// If flags dialog, we can also have a custom value
if(pData->bIsValuesDialog == false && dwCustomValue != 0)
{
FlagList.push_back(TDlgFlagInfo(pData->szCustomValue, dwCustomValue, dwCustomValue, BS_AUTOCHECKBOX));
LoadStringA(g_hInst, IDS_CUSTOM_VALUE, pData->szCustomValue, _countof(pData->szCustomValue));
}
return FlagList.size();
}
static HWND CreateButtonItem(
TFlagDialogData * pData,
TDlgFlagInfo & FlagInfo,
size_t nButton,
HWND hWndChild)
{
// Calculate position of the button
int x = pData->xButton + (int)((nButton / pData->nColumn1) * (pData->cxButton + pData->cxSpace));
int y = pData->yButton + (int)((nButton % pData->nColumn1) * (pData->cyButton + pData->cySpace));
// Do we already have a child?
if(hWndChild == NULL)
{
// Shall we create a separator?
if(!FlagInfo.IsSeparator())
{
// Create the checkbox
hWndChild = CreateWindowEx(pData->dwExStyle,
WC_BUTTON,
NULL,
pData->dwStyle | FlagInfo.dwButtonType | WS_VISIBLE,
x,
y,
pData->cxButton,
pData->cyButton,
pData->hDlg,
0,
g_hInst,
NULL);
}
else
{
hWndChild = CreateWindowEx(WS_EX_LEFT | WS_EX_NOPARENTNOTIFY,
WC_STATIC,
NULL,
WS_CHILD | WS_VISIBLE | SS_SUNKEN,
x,
y + (pData->cyButton - 3) / 2,
pData->cxButton,
3,
pData->hDlg,
0,
g_hInst,
NULL);
}
// Set the checkbox's Z-order after the previous one
SetWindowPos(hWndChild, pData->hWndPrev, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
SendMessage(hWndChild, WM_SETFONT, (WPARAM)pData->hFont, FALSE);
}
else
{
// Set the child type to radio button, if needed
SetWindowLong(hWndChild, GWL_STYLE, pData->dwStyle | FlagInfo.dwButtonType | WS_VISIBLE | WS_GROUP | WS_TABSTOP);
}
// Set the button text and user data, if not a separator
if(!FlagInfo.IsSeparator())
{
char szItemText[256];
// Set the pointer to the flag info
SetWindowLongPtr(hWndChild, GWLP_USERDATA, (LONG_PTR)(&FlagInfo));
// Set the window text
StringCchPrintfA(szItemText, _countof(szItemText), "(%08X) %s", FlagInfo.dwValue, FlagInfo.szFlagText);
SetWindowTextA(hWndChild, szItemText);
}
// Remember the child window for the next time
pData->hWndPrev = hWndChild;
return hWndChild;
}
static void ArrangeDialogButton(TFlagDialogData * pData, UINT nIDButton, int y, int width)
{
HWND hWndChild;
HWND hDlg = pData->hDlg;
RECT rect;
UINT uFlags = SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE;
int x;
// Retrieve handle to the child window
if((hWndChild = GetDlgItem(hDlg, nIDButton)) != NULL)
{
// Retrieve the rect of the button (parent-relative)
GetWindowRectParentRelative(hWndChild, &rect);
x = rect.left;
// OK button is always visible and on the right side of the dialog
if(nIDButton == IDOK)
x = width - (rect.right - rect.left);
if(pData->bIsValuesDialog)
uFlags = uFlags | ((nIDButton != IDOK) ? SWP_HIDEWINDOW : 0);
// Move the button
SetWindowPos(hWndChild, NULL, x, y, 0, 0, uFlags);
}
}
// Arranging the dialog layout for empty dialogs, where the controls need to be created
static void CreateDialogLayout(TFlagDialogData * pData)
{
HWND hWndFirst = GetDlgItem(pData->hDlg, IDC_CHILD_MUSTER);
HWND hWndChild = hWndFirst;
HWND hWndGroup;
RECT DialogRect; // Rectangle of the dialog (screen-relative)
RECT ParentRect; // Rectangle of the dialog's parent (screen-relative)
RECT ClientRect; // Rectangle of the dialog client area (screen-relative)
RECT GroupRect; // Rectangle of the group box
RECT CheckBoxRect; // Rectangle of the checkbox
size_t dwFlagCount;
DWORD dwFlags = pData->dwBitMask;
int cxAllButtons;
int cyAllButtons;
int nChecked = BST_UNCHECKED;
int xButtons;
int yButtons;
int cxGroup = 0;
int cyGroup = 0;
int x, y, cx, cy;
// Get the size of the parent
if(pData->hWndParent == NULL)
SystemParametersInfo(SPI_GETWORKAREA, 0, &ParentRect, FALSE);
else
GetWindowRect(pData->hWndParent, &ParentRect);
// Set the dialog title
SetWindowTextRc(pData->hDlg, pData->nIDTitle);
// Get the size of the dialog and the "master" settings
GetWindowRect(pData->hDlg, &DialogRect);
GetClientRect(pData->hDlg, &ClientRect);
ClientRectToScreenRect(pData->hDlg, &ClientRect);
// Remember the group position
if((hWndGroup = GetDlgItem(pData->hDlg, IDC_GROUPBOX)) != NULL)
{
GetWindowRect(hWndGroup, &GroupRect);
pData->xGroup = (GroupRect.left - ClientRect.left);
pData->yGroup = (GroupRect.top - ClientRect.top);
}
// Calculate button position
GetWindowRect(hWndChild, &CheckBoxRect);
pData->xButton = (CheckBoxRect.left - ClientRect.left);
pData->yButton = (CheckBoxRect.top - ClientRect.top);
pData->cxButton = (CheckBoxRect.right - CheckBoxRect.left);
pData->cyButton = (CheckBoxRect.bottom - CheckBoxRect.top);
pData->cxSpace = pData->cyButton;
pData->cySpace = 6;
// Get the settings of the "master" checkbox
pData->hFont = (HFONT)SendMessage(hWndChild, WM_GETFONT, 0, 0);
pData->dwExStyle = GetWindowLong(hWndChild, GWL_EXSTYLE);
pData->dwStyle = GetWindowLong(hWndChild, GWL_STYLE) & ~(WS_TABSTOP | WS_GROUP | BS_TYPEMASK);
// Get the button types
dwFlagCount = BuildFlagList(pData);
// If 20 flags or more, we split the dialog into more columns
pData->nColumn1 = pData->FlagList.size();
pData->nColumns = 1;
while(pData->nColumn1 > 23)
{
pData->nColumns++;
pData->nColumn1 = (dwFlagCount / pData->nColumns) + ((dwFlagCount % pData->nColumns) ? 1 : 0);
}
// Create all switch items
for(size_t nButton = 0; nButton < pData->FlagList.size(); nButton++)
{
TDlgFlagInfo & FlagInfo = pData->FlagList[nButton];
// Create the (next) switch item
hWndChild = CreateButtonItem(pData, FlagInfo, nButton, hWndChild);
// Check/uncheck the box
if(!FlagInfo.IsSeparator())
{
nChecked = IsThisButtonChecked(pData, FlagInfo, dwFlags) ? BST_CHECKED : BST_UNCHECKED;
if(nChecked == BST_CHECKED)
dwFlags = dwFlags & ~FlagInfo.dwMask;
Button_SetCheck(hWndChild, nChecked);
}
// The next control will be created dynamically, 6 dialog units lower
hWndChild = NULL;
}
// Calculate the size of the entire button area
cxAllButtons = (int)((pData->nColumns * pData->cxButton) + ((pData->nColumns - 1) * pData->cxSpace));
cyAllButtons = (int)((pData->nColumn1 * pData->cyButton) + ((pData->nColumn1 - 1) * pData->cySpace));
// Resize the group frame, if any
if(hWndGroup != NULL)
{
cxGroup = (pData->xButton - pData->xGroup) + cxAllButtons + (pData->xButton - pData->xGroup);
cyGroup = (pData->yButton - pData->yGroup) + cyAllButtons + (pData->yButton - pData->yGroup);
SetWindowPos(hWndGroup, NULL, pData->xGroup, pData->yGroup, cxGroup, cyGroup, SWP_NOZORDER | SWP_NOACTIVATE);
}
// Arrange the buttons
xButtons = pData->xButton + cxAllButtons + pData->xButton;
yButtons = pData->yButton + cyAllButtons + pData->yButton;
ArrangeDialogButton(pData, IDC_SELECT_ALL, yButtons, pData->xGroup + cxGroup);
ArrangeDialogButton(pData, IDC_CLEAR_ALL, yButtons, pData->xGroup + cxGroup);
ArrangeDialogButton(pData, IDOK, yButtons, pData->xGroup + cxGroup);
// Resize and center the dialog
cx = (DialogRect.right - DialogRect.left) - (ClientRect.right - ClientRect.left) + xButtons;
cy = (DialogRect.bottom - DialogRect.top) - (ClientRect.bottom - ClientRect.top) + yButtons + pData->yButton + pData->yGroup;
x = ParentRect.left + ((ParentRect.right - ParentRect.left) - cx) / 2;
y = ParentRect.top + ((ParentRect.bottom - ParentRect.top) - cy) / 2;
SetWindowPos(pData->hDlg, NULL, x, y, cx, cy, SWP_NOZORDER | SWP_NOACTIVATE);
}
// Only do checkboxes. Ignore radio buttons
static void Callback_SetCheck(TFlagDialogData * /* pData */, TFlagInfo * /* pFlags */, HWND hWndChild, DWORD dwStyles)
{
if((dwStyles & BS_TYPEMASK) == BS_AUTOCHECKBOX)
Button_SetCheck(hWndChild, BST_CHECKED);
}
// Only do checkboxes. Ignore radio buttons
static void Callback_ClearCheck(TFlagDialogData * /* pData */, TFlagInfo * /* pFlags */, HWND hWndChild, DWORD dwStyles)
{
if((dwStyles & BS_TYPEMASK) == BS_AUTOCHECKBOX)
Button_SetCheck(hWndChild, BST_UNCHECKED);
}
// If the button is checked, add the flag to the flags
static void Callback_SaveFlag(TFlagDialogData * pData, TFlagInfo * pFlags, HWND hWndChild, DWORD /* dwStyles */)
{
if(pFlags != NULL && Button_GetCheck(hWndChild) == BST_CHECKED)
{
pData->dwBitMask = (pData->dwBitMask & ~pFlags->dwMask) | pFlags->dwValue;
}
}
//-----------------------------------------------------------------------------
// Dialog handlers
static int OnInitDialog(HWND hDlg, LPARAM lParam)
{
TFlagDialogData * pData = (TFlagDialogData *)lParam;
// Configure the dialog
SetDialogIcon(hDlg, IDI_FILE_TEST);
SetDialogData(hDlg, lParam);
pData->hDlg = hDlg;
// Create the layout and clear the bit flags
CreateDialogLayout(pData);
pData->dwBitMask = 0;
return TRUE;
}
static void OnEnumAllButtons(HWND hDlg, CHILD_WINDOW_CALLBACK ChildCallback)
{
TFlagDialogData * pData = (TFlagDialogData *)GetWindowLongPtr(hDlg, DWLP_USER);
TDlgFlagInfo * pFlags;
DWORD dwButtonType;
DWORD dwStyles;
HWND hWndChild;
// Enumerate all children. For checkboxes and radio buttons, call the given callback
for(hWndChild = GetWindow(hDlg, GW_CHILD); hWndChild != NULL; hWndChild = GetWindow(hWndChild, GW_HWNDNEXT))
{
// Retrieve child window styles and button type
dwStyles = GetWindowLong(hWndChild, GWL_STYLE);
dwButtonType = dwStyles & BS_TYPEMASK;
// Is that a check box or a radio button?
if(dwButtonType == BS_AUTOCHECKBOX || dwButtonType == BS_AUTORADIOBUTTON)
{
// Retrieve flag info structure the button
pFlags = (TDlgFlagInfo *)GetWindowLongPtr(hWndChild, GWLP_USERDATA);
if(pFlags != NULL)
{
// Call the for-each callback
ChildCallback(pData, pFlags, hWndChild, dwStyles);
}
}
}
}
static INT_PTR CALLBACK DialogProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
// Dialog initialization
if(uMsg == WM_INITDIALOG)
return OnInitDialog(hDlg, lParam);
if(uMsg == WM_COMMAND)
{
if(HIWORD(wParam) == BN_CLICKED)
{
switch(LOWORD(wParam))
{
case IDC_SELECT_ALL:
OnEnumAllButtons(hDlg, Callback_SetCheck);
break;
case IDC_CLEAR_ALL:
OnEnumAllButtons(hDlg, Callback_ClearCheck);
break;
case IDOK:
OnEnumAllButtons(hDlg, Callback_SaveFlag);
// No break here !!
case IDCANCEL:
EndDialog(hDlg, LOWORD(wParam));
break;
}
}
}
return FALSE;
}
//-----------------------------------------------------------------------------
// Public functions
INT_PTR FlagsDialog(HWND hWndParent, UINT nIDTitle, TFlagInfo * pFlags, DWORD & dwBitMask)
{
TFlagDialogData fdd;
INT_PTR Result;
// Retrieve the flags
fdd.hWndParent = hWndParent;
fdd.pFlags = pFlags;
fdd.dwBitMask = dwBitMask;
fdd.nIDTitle = nIDTitle;
// Execute the dialog
Result = DialogBoxParam(g_hInst, MAKEINTRESOURCE(IDD_FLAGS_DIALOG), hWndParent, DialogProc, (LPARAM)&fdd);
// Only change the value on success
if(Result == IDOK)
dwBitMask = fdd.dwBitMask;
return Result;
}
INT_PTR FlagsDialog_OnControl(HWND hWndParent, UINT nIDTitle, TFlagInfo * pFlags, UINT nIDCtrl)
{
INT_PTR Result = IDCANCEL;
DWORD dwBitMask = 0;
// Read the flags from the item
if(DlgText2Hex32(hWndParent, nIDCtrl, &dwBitMask) == ERROR_SUCCESS)
{
// Invoke the dialog
Result = FlagsDialog(hWndParent, nIDTitle, pFlags, dwBitMask);
if(Result == IDOK)
{
Hex2DlgText32(hWndParent, nIDCtrl, dwBitMask);
}
}
return Result;
}