mirror of
https://github.com/reactos/reactos.git
synced 2024-08-18 09:28:36 +00:00
2619 lines
78 KiB
C
2619 lines
78 KiB
C
/*
|
|
* ReactOS Access Control List Editor
|
|
* Copyright (C) 2004-2005 ReactOS Team
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library 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. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
/*
|
|
* PROJECT: ReactOS Access Control List Editor
|
|
* FILE: lib/aclui/checklist.c
|
|
* PURPOSE: Access Control List Editor
|
|
* PROGRAMMER: Thomas Weidenmueller <w3seek@reactos.com>
|
|
*
|
|
* UPDATE HISTORY:
|
|
* 07/01/2005 Created
|
|
*/
|
|
|
|
#include "precomp.h"
|
|
|
|
#ifdef SUPPORT_UXTHEME
|
|
#include <uxtheme.h>
|
|
#include <tmschema.h>
|
|
#endif
|
|
|
|
#define NDEBUG
|
|
#include <debug.h>
|
|
|
|
static const WCHAR szCheckListWndClass[] = L"CHECKLIST_ACLUI";
|
|
|
|
#define CI_TEXT_MARGIN_WIDTH (8)
|
|
#define CI_TEXT_MARGIN_HEIGHT (3)
|
|
#define CI_TEXT_SELECTIONMARGIN (1)
|
|
|
|
#define TIMER_ID_SETHITFOCUS (1)
|
|
#define TIMER_ID_RESETQUICKSEARCH (2)
|
|
|
|
#define DEFAULT_QUICKSEARCH_SETFOCUS_DELAY (2000)
|
|
#define DEFAULT_QUICKSEARCH_RESET_DELAY (3000)
|
|
|
|
typedef struct _CHECKITEM
|
|
{
|
|
struct _CHECKITEM *Next;
|
|
ACCESS_MASK AccessMask;
|
|
DWORD State;
|
|
WCHAR Name[1];
|
|
} CHECKITEM, *PCHECKITEM;
|
|
|
|
typedef struct _CHECKLISTWND
|
|
{
|
|
HWND hSelf;
|
|
HWND hNotify;
|
|
HFONT hFont;
|
|
|
|
PCHECKITEM CheckItemListHead;
|
|
UINT CheckItemCount;
|
|
|
|
INT ItemHeight;
|
|
|
|
PCHECKITEM FocusedCheckItem;
|
|
UINT FocusedCheckItemBox;
|
|
|
|
COLORREF TextColor[2];
|
|
INT CheckBoxLeft[2];
|
|
|
|
PCHECKITEM QuickSearchHitItem;
|
|
WCHAR QuickSearchText[65];
|
|
UINT QuickSearchSetFocusDelay;
|
|
UINT QuickSearchResetDelay;
|
|
|
|
DWORD CaretWidth;
|
|
|
|
DWORD UIState;
|
|
|
|
#if SUPPORT_UXTHEME
|
|
PCHECKITEM HoveredCheckItem;
|
|
UINT HoveredCheckItemBox;
|
|
UINT HoverTime;
|
|
|
|
HTHEME ThemeHandle;
|
|
#endif
|
|
|
|
UINT HasFocus : 1;
|
|
UINT FocusedPushed : 1;
|
|
UINT QuickSearchEnabled : 1;
|
|
UINT ShowingCaret : 1;
|
|
} CHECKLISTWND, *PCHECKLISTWND;
|
|
|
|
static VOID EscapeQuickSearch(IN PCHECKLISTWND infoPtr);
|
|
#if SUPPORT_UXTHEME
|
|
static VOID ChangeCheckItemHotTrack(IN PCHECKLISTWND infoPtr,
|
|
IN PCHECKITEM NewHotTrack,
|
|
IN UINT NewHotTrackBox);
|
|
#endif
|
|
static VOID ChangeCheckItemFocus(IN PCHECKLISTWND infoPtr,
|
|
IN PCHECKITEM NewFocus,
|
|
IN UINT NewFocusBox);
|
|
|
|
/******************************************************************************/
|
|
|
|
static LRESULT
|
|
NotifyControlParent(IN PCHECKLISTWND infoPtr,
|
|
IN UINT code,
|
|
IN OUT PVOID data)
|
|
{
|
|
LRESULT Ret = 0;
|
|
|
|
if (infoPtr->hNotify != NULL)
|
|
{
|
|
LPNMHDR pnmh = (LPNMHDR)data;
|
|
|
|
pnmh->hwndFrom = infoPtr->hSelf;
|
|
pnmh->idFrom = GetWindowLongPtr(infoPtr->hSelf,
|
|
GWLP_ID);
|
|
pnmh->code = code;
|
|
|
|
Ret = SendMessage(infoPtr->hNotify,
|
|
WM_NOTIFY,
|
|
(WPARAM)pnmh->idFrom,
|
|
(LPARAM)pnmh);
|
|
}
|
|
|
|
return Ret;
|
|
}
|
|
|
|
static PCHECKITEM
|
|
FindCheckItemByIndex(IN PCHECKLISTWND infoPtr,
|
|
IN INT Index)
|
|
{
|
|
PCHECKITEM Item, Found = NULL;
|
|
|
|
if (Index >= 0)
|
|
{
|
|
for (Item = infoPtr->CheckItemListHead;
|
|
Item != NULL;
|
|
Item = Item->Next)
|
|
{
|
|
if (Index == 0)
|
|
{
|
|
Found = Item;
|
|
break;
|
|
}
|
|
|
|
Index--;
|
|
}
|
|
}
|
|
|
|
return Found;
|
|
}
|
|
|
|
static INT
|
|
FindCheckItemIndexByAccessMask(IN PCHECKLISTWND infoPtr,
|
|
IN ACCESS_MASK AccessMask)
|
|
{
|
|
PCHECKITEM Item;
|
|
INT Index = 0, Found = -1;
|
|
|
|
for (Item = infoPtr->CheckItemListHead;
|
|
Item != NULL;
|
|
Item = Item->Next)
|
|
{
|
|
if (Item->AccessMask == AccessMask)
|
|
{
|
|
Found = Index;
|
|
break;
|
|
}
|
|
|
|
Index++;
|
|
}
|
|
|
|
return Found;
|
|
}
|
|
|
|
static INT
|
|
CheckItemToIndex(IN PCHECKLISTWND infoPtr,
|
|
IN PCHECKITEM Item)
|
|
{
|
|
PCHECKITEM CurItem;
|
|
INT Index;
|
|
|
|
for (CurItem = infoPtr->CheckItemListHead, Index = 0;
|
|
CurItem != NULL;
|
|
CurItem = CurItem->Next, Index++)
|
|
{
|
|
if (CurItem == Item)
|
|
{
|
|
return Index;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
static PCHECKITEM
|
|
FindCheckItem(IN PCHECKLISTWND infoPtr,
|
|
IN LPWSTR SearchText)
|
|
{
|
|
PCHECKITEM CurItem;
|
|
SIZE_T Count = wcslen(SearchText);
|
|
|
|
for (CurItem = infoPtr->CheckItemListHead;
|
|
CurItem != NULL;
|
|
CurItem = CurItem->Next)
|
|
{
|
|
if ((CurItem->State & CIS_DISABLED) != CIS_DISABLED &&
|
|
!_wcsnicmp(CurItem->Name,
|
|
SearchText, Count))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
return CurItem;
|
|
}
|
|
|
|
static PCHECKITEM
|
|
FindFirstEnabledCheckBox(IN PCHECKLISTWND infoPtr,
|
|
OUT UINT *CheckBox)
|
|
{
|
|
PCHECKITEM CurItem;
|
|
|
|
for (CurItem = infoPtr->CheckItemListHead;
|
|
CurItem != NULL;
|
|
CurItem = CurItem->Next)
|
|
{
|
|
if ((CurItem->State & CIS_DISABLED) != CIS_DISABLED)
|
|
{
|
|
/* return the Allow checkbox in case both check boxes are enabled! */
|
|
*CheckBox = ((!(CurItem->State & CIS_ALLOWDISABLED)) ? CLB_ALLOW : CLB_DENY);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return CurItem;
|
|
}
|
|
|
|
static PCHECKITEM
|
|
FindLastEnabledCheckBox(IN PCHECKLISTWND infoPtr,
|
|
OUT UINT *CheckBox)
|
|
{
|
|
PCHECKITEM CurItem;
|
|
PCHECKITEM LastEnabledItem = NULL;
|
|
|
|
for (CurItem = infoPtr->CheckItemListHead;
|
|
CurItem != NULL;
|
|
CurItem = CurItem->Next)
|
|
{
|
|
if ((CurItem->State & CIS_DISABLED) != CIS_DISABLED)
|
|
{
|
|
LastEnabledItem = CurItem;
|
|
}
|
|
}
|
|
|
|
if (LastEnabledItem != NULL)
|
|
{
|
|
/* return the Deny checkbox in case both check boxes are enabled! */
|
|
*CheckBox = ((!(LastEnabledItem->State & CIS_DENYDISABLED)) ? CLB_DENY : CLB_ALLOW);
|
|
}
|
|
|
|
return LastEnabledItem;
|
|
}
|
|
|
|
static PCHECKITEM
|
|
FindPreviousEnabledCheckBox(IN PCHECKLISTWND infoPtr,
|
|
OUT UINT *CheckBox)
|
|
{
|
|
PCHECKITEM Item;
|
|
|
|
if (infoPtr->FocusedCheckItem != NULL)
|
|
{
|
|
Item = infoPtr->FocusedCheckItem;
|
|
|
|
if (infoPtr->FocusedCheckItemBox == CLB_DENY &&
|
|
!(Item->State & CIS_ALLOWDISABLED))
|
|
{
|
|
/* currently an Deny checkbox is focused. return the Allow checkbox
|
|
if it's enabled */
|
|
*CheckBox = CLB_ALLOW;
|
|
}
|
|
else
|
|
{
|
|
PCHECKITEM CurItem;
|
|
|
|
Item = NULL;
|
|
|
|
for (CurItem = infoPtr->CheckItemListHead;
|
|
CurItem != infoPtr->FocusedCheckItem;
|
|
CurItem = CurItem->Next)
|
|
{
|
|
if ((CurItem->State & CIS_DISABLED) != CIS_DISABLED)
|
|
{
|
|
Item = CurItem;
|
|
}
|
|
}
|
|
|
|
if (Item != NULL)
|
|
{
|
|
/* return the Deny checkbox in case both check boxes are enabled! */
|
|
*CheckBox = ((!(Item->State & CIS_DENYDISABLED)) ? CLB_DENY : CLB_ALLOW);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Item = FindLastEnabledCheckBox(infoPtr,
|
|
CheckBox);
|
|
}
|
|
|
|
return Item;
|
|
}
|
|
|
|
static PCHECKITEM
|
|
FindNextEnabledCheckBox(IN PCHECKLISTWND infoPtr,
|
|
OUT UINT *CheckBox)
|
|
{
|
|
PCHECKITEM Item;
|
|
|
|
if (infoPtr->FocusedCheckItem != NULL)
|
|
{
|
|
Item = infoPtr->FocusedCheckItem;
|
|
|
|
if (infoPtr->FocusedCheckItemBox != CLB_DENY &&
|
|
!(Item->State & CIS_DENYDISABLED))
|
|
{
|
|
/* currently an Allow checkbox is focused. return the Deny checkbox
|
|
if it's enabled */
|
|
*CheckBox = CLB_DENY;
|
|
}
|
|
else
|
|
{
|
|
Item = Item->Next;
|
|
|
|
while (Item != NULL)
|
|
{
|
|
if ((Item->State & CIS_DISABLED) != CIS_DISABLED)
|
|
{
|
|
/* return the Allow checkbox in case both check boxes are enabled! */
|
|
*CheckBox = ((!(Item->State & CIS_ALLOWDISABLED)) ? CLB_ALLOW : CLB_DENY);
|
|
break;
|
|
}
|
|
|
|
Item = Item->Next;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Item = FindFirstEnabledCheckBox(infoPtr,
|
|
CheckBox);
|
|
}
|
|
|
|
return Item;
|
|
}
|
|
|
|
static PCHECKITEM
|
|
FindEnabledCheckBox(IN PCHECKLISTWND infoPtr,
|
|
IN BOOL ReverseSearch,
|
|
OUT UINT *CheckBox)
|
|
{
|
|
PCHECKITEM Item;
|
|
|
|
if (ReverseSearch)
|
|
{
|
|
Item = FindPreviousEnabledCheckBox(infoPtr,
|
|
CheckBox);
|
|
}
|
|
else
|
|
{
|
|
Item = FindNextEnabledCheckBox(infoPtr,
|
|
CheckBox);
|
|
}
|
|
|
|
return Item;
|
|
}
|
|
|
|
static PCHECKITEM
|
|
PtToCheckItemBox(IN PCHECKLISTWND infoPtr,
|
|
IN PPOINT ppt,
|
|
OUT UINT *CheckBox,
|
|
OUT BOOL *DirectlyInCheckBox)
|
|
{
|
|
INT FirstVisible, Index;
|
|
PCHECKITEM Item;
|
|
|
|
FirstVisible = GetScrollPos(infoPtr->hSelf,
|
|
SB_VERT);
|
|
|
|
Index = FirstVisible + (ppt->y / infoPtr->ItemHeight);
|
|
|
|
Item = FindCheckItemByIndex(infoPtr,
|
|
Index);
|
|
if (Item != NULL)
|
|
{
|
|
INT cx;
|
|
|
|
cx = infoPtr->CheckBoxLeft[CLB_ALLOW] +
|
|
((infoPtr->CheckBoxLeft[CLB_DENY] - infoPtr->CheckBoxLeft[CLB_ALLOW]) / 2);
|
|
|
|
*CheckBox = ((ppt->x <= cx) ? CLB_ALLOW : CLB_DENY);
|
|
|
|
if (DirectlyInCheckBox != NULL)
|
|
{
|
|
INT y = ppt->y % infoPtr->ItemHeight;
|
|
INT cxBox = infoPtr->ItemHeight - (2 * CI_TEXT_MARGIN_HEIGHT);
|
|
|
|
if ((y >= CI_TEXT_MARGIN_HEIGHT &&
|
|
y < infoPtr->ItemHeight - CI_TEXT_MARGIN_HEIGHT) &&
|
|
|
|
(((ppt->x >= (infoPtr->CheckBoxLeft[CLB_ALLOW] - (cxBox / 2))) &&
|
|
(ppt->x < (infoPtr->CheckBoxLeft[CLB_ALLOW] - (cxBox / 2) + cxBox)))
|
|
||
|
|
((ppt->x >= (infoPtr->CheckBoxLeft[CLB_DENY] - (cxBox / 2))) &&
|
|
(ppt->x < (infoPtr->CheckBoxLeft[CLB_DENY] - (cxBox / 2) + cxBox)))))
|
|
{
|
|
*DirectlyInCheckBox = TRUE;
|
|
}
|
|
else
|
|
{
|
|
*DirectlyInCheckBox = FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
return Item;
|
|
}
|
|
|
|
static VOID
|
|
ClearCheckItems(IN PCHECKLISTWND infoPtr)
|
|
{
|
|
PCHECKITEM CurItem, NextItem;
|
|
|
|
CurItem = infoPtr->CheckItemListHead;
|
|
while (CurItem != NULL)
|
|
{
|
|
NextItem = CurItem->Next;
|
|
HeapFree(GetProcessHeap(),
|
|
0,
|
|
CurItem);
|
|
CurItem = NextItem;
|
|
}
|
|
|
|
infoPtr->CheckItemListHead = NULL;
|
|
infoPtr->CheckItemCount = 0;
|
|
}
|
|
|
|
static BOOL
|
|
DeleteCheckItem(IN PCHECKLISTWND infoPtr,
|
|
IN PCHECKITEM Item)
|
|
{
|
|
PCHECKITEM CurItem;
|
|
PCHECKITEM *PrevPtr = &infoPtr->CheckItemListHead;
|
|
|
|
for (CurItem = infoPtr->CheckItemListHead;
|
|
CurItem != NULL;
|
|
CurItem = CurItem->Next)
|
|
{
|
|
if (CurItem == Item)
|
|
{
|
|
if (Item == infoPtr->QuickSearchHitItem && infoPtr->QuickSearchEnabled)
|
|
{
|
|
EscapeQuickSearch(infoPtr);
|
|
}
|
|
|
|
#if SUPPORT_UXTHEME
|
|
if (Item == infoPtr->HoveredCheckItem)
|
|
{
|
|
ChangeCheckItemHotTrack(infoPtr,
|
|
NULL,
|
|
0);
|
|
}
|
|
#endif
|
|
|
|
if (Item == infoPtr->FocusedCheckItem)
|
|
{
|
|
ChangeCheckItemFocus(infoPtr,
|
|
NULL,
|
|
0);
|
|
}
|
|
|
|
*PrevPtr = CurItem->Next;
|
|
HeapFree(GetProcessHeap(),
|
|
0,
|
|
CurItem);
|
|
infoPtr->CheckItemCount--;
|
|
return TRUE;
|
|
}
|
|
|
|
PrevPtr = &CurItem->Next;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static PCHECKITEM
|
|
AddCheckItem(IN PCHECKLISTWND infoPtr,
|
|
IN LPWSTR Name,
|
|
IN DWORD State,
|
|
IN ACCESS_MASK AccessMask,
|
|
OUT INT *Index)
|
|
{
|
|
PCHECKITEM CurItem;
|
|
INT i;
|
|
PCHECKITEM *PrevPtr = &infoPtr->CheckItemListHead;
|
|
PCHECKITEM Item = HeapAlloc(GetProcessHeap(),
|
|
0,
|
|
sizeof(CHECKITEM) + (wcslen(Name) * sizeof(WCHAR)));
|
|
if (Item != NULL)
|
|
{
|
|
for (CurItem = infoPtr->CheckItemListHead, i = 0;
|
|
CurItem != NULL;
|
|
CurItem = CurItem->Next)
|
|
{
|
|
PrevPtr = &CurItem->Next;
|
|
i++;
|
|
}
|
|
|
|
Item->Next = NULL;
|
|
Item->AccessMask = AccessMask;
|
|
Item->State = State & CIS_MASK;
|
|
wcscpy(Item->Name,
|
|
Name);
|
|
|
|
*PrevPtr = Item;
|
|
infoPtr->CheckItemCount++;
|
|
|
|
if (Index != NULL)
|
|
{
|
|
*Index = i;
|
|
}
|
|
}
|
|
|
|
return Item;
|
|
}
|
|
|
|
static UINT
|
|
ClearCheckBoxes(IN PCHECKLISTWND infoPtr)
|
|
{
|
|
PCHECKITEM CurItem;
|
|
UINT nUpdated = 0;
|
|
|
|
for (CurItem = infoPtr->CheckItemListHead;
|
|
CurItem != NULL;
|
|
CurItem = CurItem->Next)
|
|
{
|
|
if (CurItem->State & (CIS_ALLOW | CIS_DENY))
|
|
{
|
|
CurItem->State &= ~(CIS_ALLOW | CIS_DENY);
|
|
nUpdated++;
|
|
}
|
|
}
|
|
|
|
return nUpdated;
|
|
}
|
|
|
|
static VOID
|
|
UpdateControl(IN PCHECKLISTWND infoPtr)
|
|
{
|
|
RECT rcClient;
|
|
SCROLLINFO ScrollInfo;
|
|
INT VisibleItems;
|
|
|
|
GetClientRect(infoPtr->hSelf,
|
|
&rcClient);
|
|
|
|
ScrollInfo.cbSize = sizeof(ScrollInfo);
|
|
ScrollInfo.fMask = SIF_PAGE | SIF_RANGE;
|
|
ScrollInfo.nMin = 0;
|
|
ScrollInfo.nMax = infoPtr->CheckItemCount;
|
|
ScrollInfo.nPage = ((rcClient.bottom - rcClient.top) + infoPtr->ItemHeight - 1) / infoPtr->ItemHeight;
|
|
ScrollInfo.nPos = 0;
|
|
ScrollInfo.nTrackPos = 0;
|
|
|
|
VisibleItems = (rcClient.bottom - rcClient.top) / infoPtr->ItemHeight;
|
|
|
|
if (ScrollInfo.nPage == (UINT)VisibleItems && ScrollInfo.nMax > 0)
|
|
{
|
|
ScrollInfo.nMax--;
|
|
}
|
|
|
|
SetScrollInfo(infoPtr->hSelf,
|
|
SB_VERT,
|
|
&ScrollInfo,
|
|
TRUE);
|
|
|
|
RedrawWindow(infoPtr->hSelf,
|
|
NULL,
|
|
NULL,
|
|
RDW_INVALIDATE | RDW_ERASE | RDW_UPDATENOW | RDW_NOCHILDREN);
|
|
}
|
|
|
|
static VOID
|
|
UpdateCheckItem(IN PCHECKLISTWND infoPtr,
|
|
IN PCHECKITEM Item)
|
|
{
|
|
RECT rcClient;
|
|
INT VisibleFirst, VisibleItems;
|
|
INT Index = CheckItemToIndex(infoPtr,
|
|
Item);
|
|
if (Index != -1)
|
|
{
|
|
VisibleFirst = GetScrollPos(infoPtr->hSelf,
|
|
SB_VERT);
|
|
|
|
if (Index >= VisibleFirst)
|
|
{
|
|
GetClientRect(infoPtr->hSelf,
|
|
&rcClient);
|
|
|
|
VisibleItems = ((rcClient.bottom - rcClient.top) + infoPtr->ItemHeight - 1) / infoPtr->ItemHeight;
|
|
|
|
if (Index <= VisibleFirst + VisibleItems)
|
|
{
|
|
RECT rcUpdate;
|
|
|
|
rcUpdate.left = rcClient.left;
|
|
rcUpdate.right = rcClient.right;
|
|
rcUpdate.top = (Index - VisibleFirst) * infoPtr->ItemHeight;
|
|
rcUpdate.bottom = rcUpdate.top + infoPtr->ItemHeight;
|
|
|
|
RedrawWindow(infoPtr->hSelf,
|
|
&rcUpdate,
|
|
NULL,
|
|
RDW_INVALIDATE | RDW_ERASE | RDW_UPDATENOW | RDW_NOCHILDREN);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static VOID
|
|
MakeCheckItemVisible(IN PCHECKLISTWND infoPtr,
|
|
IN PCHECKITEM Item)
|
|
{
|
|
RECT rcClient;
|
|
INT VisibleFirst, VisibleItems, NewPos;
|
|
INT Index = CheckItemToIndex(infoPtr,
|
|
Item);
|
|
if (Index != -1)
|
|
{
|
|
VisibleFirst = GetScrollPos(infoPtr->hSelf,
|
|
SB_VERT);
|
|
|
|
if (Index <= VisibleFirst)
|
|
{
|
|
NewPos = Index;
|
|
}
|
|
else
|
|
{
|
|
GetClientRect(infoPtr->hSelf,
|
|
&rcClient);
|
|
|
|
VisibleItems = (rcClient.bottom - rcClient.top) / infoPtr->ItemHeight;
|
|
if (Index - VisibleItems + 1 > VisibleFirst)
|
|
{
|
|
NewPos = Index - VisibleItems + 1;
|
|
}
|
|
else
|
|
{
|
|
NewPos = VisibleFirst;
|
|
}
|
|
}
|
|
|
|
if (VisibleFirst != NewPos)
|
|
{
|
|
SCROLLINFO ScrollInfo;
|
|
|
|
ScrollInfo.cbSize = sizeof(ScrollInfo);
|
|
ScrollInfo.fMask = SIF_POS;
|
|
ScrollInfo.nPos = NewPos;
|
|
NewPos = SetScrollInfo(infoPtr->hSelf,
|
|
SB_VERT,
|
|
&ScrollInfo,
|
|
TRUE);
|
|
|
|
if (VisibleFirst != NewPos)
|
|
{
|
|
ScrollWindowEx(infoPtr->hSelf,
|
|
0,
|
|
(NewPos - VisibleFirst) * infoPtr->ItemHeight,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
SW_INVALIDATE | SW_SCROLLCHILDREN);
|
|
|
|
RedrawWindow(infoPtr->hSelf,
|
|
NULL,
|
|
NULL,
|
|
RDW_INVALIDATE | RDW_ERASE | RDW_UPDATENOW | RDW_NOCHILDREN);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static UINT
|
|
GetIdealItemHeight(IN PCHECKLISTWND infoPtr)
|
|
{
|
|
HDC hdc = GetDC(infoPtr->hSelf);
|
|
if(hdc != NULL)
|
|
{
|
|
UINT height;
|
|
TEXTMETRIC tm;
|
|
HGDIOBJ hOldFont = SelectObject(hdc,
|
|
infoPtr->hFont);
|
|
|
|
if(GetTextMetrics(hdc,
|
|
&tm))
|
|
{
|
|
height = tm.tmHeight;
|
|
}
|
|
else
|
|
{
|
|
height = 2;
|
|
}
|
|
|
|
SelectObject(hdc,
|
|
hOldFont);
|
|
|
|
ReleaseDC(infoPtr->hSelf,
|
|
hdc);
|
|
|
|
return height;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static HFONT
|
|
RetChangeControlFont(IN PCHECKLISTWND infoPtr,
|
|
IN HFONT hFont,
|
|
IN BOOL Redraw)
|
|
{
|
|
HFONT hOldFont = infoPtr->hFont;
|
|
infoPtr->hFont = hFont;
|
|
|
|
if (hOldFont != hFont)
|
|
{
|
|
infoPtr->ItemHeight = (2 * CI_TEXT_MARGIN_HEIGHT) + GetIdealItemHeight(infoPtr);
|
|
}
|
|
|
|
if (infoPtr->ShowingCaret)
|
|
{
|
|
DestroyCaret();
|
|
CreateCaret(infoPtr->hSelf,
|
|
NULL,
|
|
0,
|
|
infoPtr->ItemHeight - (2 * CI_TEXT_MARGIN_HEIGHT));
|
|
}
|
|
|
|
UpdateControl(infoPtr);
|
|
|
|
return hOldFont;
|
|
}
|
|
|
|
#if SUPPORT_UXTHEME
|
|
static INT
|
|
CalculateCheckBoxStyle(IN BOOL Checked,
|
|
IN BOOL Enabled,
|
|
IN BOOL HotTrack,
|
|
IN BOOL Pushed)
|
|
{
|
|
INT BtnState;
|
|
|
|
if (Checked)
|
|
{
|
|
BtnState = (Enabled ?
|
|
(Pushed ? CBS_CHECKEDPRESSED : (HotTrack ? CBS_CHECKEDHOT : CBS_CHECKEDNORMAL)) :
|
|
CBS_CHECKEDDISABLED);
|
|
}
|
|
else
|
|
{
|
|
BtnState = (Enabled ?
|
|
(Pushed ? CBS_UNCHECKEDPRESSED : (HotTrack ? CBS_UNCHECKEDHOT : CBS_UNCHECKEDNORMAL)) :
|
|
CBS_UNCHECKEDDISABLED);
|
|
}
|
|
|
|
return BtnState;
|
|
}
|
|
#endif
|
|
|
|
static VOID
|
|
PaintControl(IN PCHECKLISTWND infoPtr,
|
|
IN HDC hDC,
|
|
IN PRECT rcUpdate)
|
|
{
|
|
INT ScrollPos;
|
|
PCHECKITEM FirstItem, Item;
|
|
RECT rcClient;
|
|
UINT VisibleFirstIndex = rcUpdate->top / infoPtr->ItemHeight;
|
|
UINT LastTouchedIndex = rcUpdate->bottom / infoPtr->ItemHeight;
|
|
|
|
FillRect(hDC,
|
|
rcUpdate,
|
|
(HBRUSH)(COLOR_WINDOW + 1));
|
|
|
|
GetClientRect(infoPtr->hSelf,
|
|
&rcClient);
|
|
|
|
ScrollPos = GetScrollPos(infoPtr->hSelf,
|
|
SB_VERT);
|
|
|
|
FirstItem = FindCheckItemByIndex(infoPtr,
|
|
ScrollPos + VisibleFirstIndex);
|
|
if (FirstItem != NULL)
|
|
{
|
|
RECT TextRect, ItemRect, CheckBox;
|
|
HFONT hOldFont;
|
|
DWORD CurrentIndex;
|
|
COLORREF OldTextColor;
|
|
BOOL Enabled, PrevEnabled, IsPushed;
|
|
POINT hOldBrushOrg;
|
|
#if SUPPORT_UXTHEME
|
|
HRESULT hDrawResult;
|
|
BOOL ItemHovered;
|
|
#endif
|
|
|
|
Enabled = IsWindowEnabled(infoPtr->hSelf);
|
|
PrevEnabled = Enabled;
|
|
|
|
ItemRect.left = 0;
|
|
ItemRect.right = rcClient.right;
|
|
ItemRect.top = VisibleFirstIndex * infoPtr->ItemHeight;
|
|
|
|
TextRect.left = ItemRect.left + CI_TEXT_MARGIN_WIDTH;
|
|
TextRect.right = ItemRect.right - CI_TEXT_MARGIN_WIDTH;
|
|
TextRect.top = ItemRect.top + CI_TEXT_MARGIN_HEIGHT;
|
|
|
|
SetBrushOrgEx(hDC,
|
|
ItemRect.left,
|
|
ItemRect.top,
|
|
&hOldBrushOrg);
|
|
|
|
OldTextColor = SetTextColor(hDC,
|
|
infoPtr->TextColor[Enabled]);
|
|
|
|
hOldFont = SelectObject(hDC,
|
|
infoPtr->hFont);
|
|
|
|
for (Item = FirstItem, CurrentIndex = VisibleFirstIndex;
|
|
Item != NULL && CurrentIndex <= LastTouchedIndex;
|
|
Item = Item->Next, CurrentIndex++)
|
|
{
|
|
TextRect.bottom = TextRect.top + infoPtr->ItemHeight - (2 * CI_TEXT_MARGIN_HEIGHT);
|
|
ItemRect.bottom = ItemRect.top + infoPtr->ItemHeight;
|
|
|
|
SetBrushOrgEx(hDC,
|
|
ItemRect.left,
|
|
ItemRect.top,
|
|
NULL);
|
|
|
|
if (Enabled && PrevEnabled != ((Item->State & CIS_DISABLED) != CIS_DISABLED))
|
|
{
|
|
PrevEnabled = ((Item->State & CIS_DISABLED) != CIS_DISABLED);
|
|
|
|
SetTextColor(hDC,
|
|
infoPtr->TextColor[PrevEnabled]);
|
|
}
|
|
|
|
#if SUPPORT_UXTHEME
|
|
ItemHovered = (Enabled && infoPtr->HoveredCheckItem == Item);
|
|
#endif
|
|
|
|
if (infoPtr->QuickSearchHitItem == Item)
|
|
{
|
|
COLORREF OldBkColor, OldFgColor;
|
|
SIZE TextSize;
|
|
SIZE_T TextLen, HighlightLen = wcslen(infoPtr->QuickSearchText);
|
|
|
|
/* highlight the quicksearch text */
|
|
if (GetTextExtentPoint32(hDC,
|
|
Item->Name,
|
|
HighlightLen,
|
|
&TextSize))
|
|
{
|
|
COLORREF HighlightTextColor, HighlightBackground;
|
|
RECT rcHighlight = TextRect;
|
|
|
|
HighlightTextColor = GetSysColor(COLOR_HIGHLIGHTTEXT);
|
|
HighlightBackground = GetSysColor(COLOR_HIGHLIGHT);
|
|
|
|
rcHighlight.right = rcHighlight.left + TextSize.cx;
|
|
|
|
InflateRect(&rcHighlight,
|
|
0,
|
|
CI_TEXT_SELECTIONMARGIN);
|
|
|
|
OldBkColor = SetBkColor(hDC,
|
|
HighlightBackground);
|
|
OldFgColor = SetTextColor(hDC,
|
|
HighlightTextColor);
|
|
|
|
/* draw the highlighted text */
|
|
DrawText(hDC,
|
|
Item->Name,
|
|
HighlightLen,
|
|
&rcHighlight,
|
|
DT_LEFT | DT_NOPREFIX | DT_SINGLELINE | DT_VCENTER);
|
|
|
|
SetBkColor(hDC,
|
|
OldBkColor);
|
|
SetTextColor(hDC,
|
|
OldFgColor);
|
|
|
|
/* draw the remaining part of the text */
|
|
TextLen = wcslen(Item->Name);
|
|
if (HighlightLen < TextLen)
|
|
{
|
|
rcHighlight.left = rcHighlight.right;
|
|
rcHighlight.right = TextRect.right;
|
|
|
|
DrawText(hDC,
|
|
Item->Name + HighlightLen,
|
|
-1,
|
|
&rcHighlight,
|
|
DT_LEFT | DT_NOPREFIX | DT_SINGLELINE | DT_VCENTER);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* draw the text */
|
|
DrawText(hDC,
|
|
Item->Name,
|
|
-1,
|
|
&TextRect,
|
|
DT_LEFT | DT_NOPREFIX | DT_SINGLELINE | DT_VCENTER);
|
|
}
|
|
|
|
CheckBox.top = TextRect.top;
|
|
CheckBox.bottom = TextRect.bottom;
|
|
|
|
/* draw the Allow checkbox */
|
|
IsPushed = (Enabled && Item == infoPtr->FocusedCheckItem && infoPtr->HasFocus &&
|
|
!(Item->State & CIS_ALLOWDISABLED) && infoPtr->FocusedCheckItemBox != CLB_DENY &&
|
|
infoPtr->FocusedPushed);
|
|
|
|
CheckBox.left = infoPtr->CheckBoxLeft[CLB_ALLOW] - ((TextRect.bottom - TextRect.top) / 2);
|
|
CheckBox.right = CheckBox.left + (TextRect.bottom - TextRect.top);
|
|
#if SUPPORT_UXTHEME
|
|
if (infoPtr->ThemeHandle != NULL)
|
|
{
|
|
INT BtnState = CalculateCheckBoxStyle(Item->State & CIS_ALLOW,
|
|
Enabled && !(Item->State & CIS_ALLOWDISABLED),
|
|
(ItemHovered && infoPtr->HoveredCheckItemBox != CLB_DENY),
|
|
IsPushed);
|
|
|
|
|
|
hDrawResult = DrawThemeBackground(infoPtr->ThemeHandle,
|
|
hDC,
|
|
BP_CHECKBOX,
|
|
BtnState,
|
|
&CheckBox,
|
|
NULL);
|
|
|
|
}
|
|
else
|
|
{
|
|
hDrawResult = E_FAIL;
|
|
}
|
|
|
|
/* draw the standard checkbox if no themes are enabled or drawing the
|
|
themed control failed */
|
|
if (FAILED(hDrawResult))
|
|
#endif
|
|
{
|
|
DrawFrameControl(hDC,
|
|
&CheckBox,
|
|
DFC_BUTTON,
|
|
DFCS_BUTTONCHECK | DFCS_FLAT |
|
|
((Item->State & CIS_ALLOWDISABLED) || !Enabled ? DFCS_INACTIVE : 0) |
|
|
((Item->State & CIS_ALLOW) ? DFCS_CHECKED : 0) |
|
|
(IsPushed ? DFCS_PUSHED : 0));
|
|
}
|
|
if (Item == infoPtr->FocusedCheckItem && !(infoPtr->UIState & UISF_HIDEFOCUS) &&
|
|
infoPtr->HasFocus &&
|
|
infoPtr->FocusedCheckItemBox != CLB_DENY)
|
|
{
|
|
RECT rcFocus = CheckBox;
|
|
|
|
InflateRect (&rcFocus,
|
|
CI_TEXT_MARGIN_HEIGHT,
|
|
CI_TEXT_MARGIN_HEIGHT);
|
|
|
|
DrawFocusRect(hDC,
|
|
&rcFocus);
|
|
}
|
|
|
|
/* draw the Deny checkbox */
|
|
IsPushed = (Enabled && Item == infoPtr->FocusedCheckItem && infoPtr->HasFocus &&
|
|
!(Item->State & CIS_DENYDISABLED) && infoPtr->FocusedCheckItemBox == CLB_DENY &&
|
|
infoPtr->FocusedPushed);
|
|
|
|
CheckBox.left = infoPtr->CheckBoxLeft[CLB_DENY] - ((TextRect.bottom - TextRect.top) / 2);
|
|
CheckBox.right = CheckBox.left + (TextRect.bottom - TextRect.top);
|
|
#if SUPPORT_UXTHEME
|
|
if (infoPtr->ThemeHandle != NULL)
|
|
{
|
|
INT BtnState = CalculateCheckBoxStyle(Item->State & CIS_DENY,
|
|
Enabled && !(Item->State & CIS_DENYDISABLED),
|
|
(ItemHovered && infoPtr->HoveredCheckItemBox == CLB_DENY),
|
|
IsPushed);
|
|
|
|
hDrawResult = DrawThemeBackground(infoPtr->ThemeHandle,
|
|
hDC,
|
|
BP_CHECKBOX,
|
|
BtnState,
|
|
&CheckBox,
|
|
NULL);
|
|
|
|
}
|
|
else
|
|
{
|
|
hDrawResult = E_FAIL;
|
|
}
|
|
|
|
/* draw the standard checkbox if no themes are enabled or drawing the
|
|
themed control failed */
|
|
if (FAILED(hDrawResult))
|
|
#endif
|
|
{
|
|
DrawFrameControl(hDC,
|
|
&CheckBox,
|
|
DFC_BUTTON,
|
|
DFCS_BUTTONCHECK | DFCS_FLAT |
|
|
((Item->State & CIS_DENYDISABLED) || !Enabled ? DFCS_INACTIVE : 0) |
|
|
((Item->State & CIS_DENY) ? DFCS_CHECKED : 0) |
|
|
(IsPushed ? DFCS_PUSHED : 0));
|
|
}
|
|
if (infoPtr->HasFocus && !(infoPtr->UIState & UISF_HIDEFOCUS) &&
|
|
Item == infoPtr->FocusedCheckItem &&
|
|
infoPtr->FocusedCheckItemBox == CLB_DENY)
|
|
{
|
|
RECT rcFocus = CheckBox;
|
|
|
|
InflateRect (&rcFocus,
|
|
CI_TEXT_MARGIN_HEIGHT,
|
|
CI_TEXT_MARGIN_HEIGHT);
|
|
|
|
DrawFocusRect(hDC,
|
|
&rcFocus);
|
|
}
|
|
|
|
TextRect.top += infoPtr->ItemHeight;
|
|
ItemRect.top += infoPtr->ItemHeight;
|
|
}
|
|
|
|
SelectObject(hDC,
|
|
hOldFont);
|
|
|
|
SetTextColor(hDC,
|
|
OldTextColor);
|
|
|
|
SetBrushOrgEx(hDC,
|
|
hOldBrushOrg.x,
|
|
hOldBrushOrg.y,
|
|
NULL);
|
|
}
|
|
}
|
|
|
|
static VOID
|
|
ChangeCheckItemFocus(IN PCHECKLISTWND infoPtr,
|
|
IN PCHECKITEM NewFocus,
|
|
IN UINT NewFocusBox)
|
|
{
|
|
if (NewFocus != infoPtr->FocusedCheckItem)
|
|
{
|
|
PCHECKITEM OldFocus = infoPtr->FocusedCheckItem;
|
|
infoPtr->FocusedCheckItem = NewFocus;
|
|
infoPtr->FocusedCheckItemBox = NewFocusBox;
|
|
|
|
if (OldFocus != NULL)
|
|
{
|
|
UpdateCheckItem(infoPtr,
|
|
OldFocus);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
infoPtr->FocusedCheckItemBox = NewFocusBox;
|
|
}
|
|
|
|
if (NewFocus != NULL)
|
|
{
|
|
MakeCheckItemVisible(infoPtr,
|
|
NewFocus);
|
|
UpdateCheckItem(infoPtr,
|
|
NewFocus);
|
|
}
|
|
}
|
|
|
|
static VOID
|
|
UpdateCheckItemBox(IN PCHECKLISTWND infoPtr,
|
|
IN PCHECKITEM Item,
|
|
IN UINT ItemBox)
|
|
{
|
|
RECT rcClient;
|
|
INT VisibleFirst, VisibleItems;
|
|
INT Index = CheckItemToIndex(infoPtr,
|
|
Item);
|
|
if (Index != -1)
|
|
{
|
|
VisibleFirst = GetScrollPos(infoPtr->hSelf,
|
|
SB_VERT);
|
|
|
|
if (Index >= VisibleFirst)
|
|
{
|
|
GetClientRect(infoPtr->hSelf,
|
|
&rcClient);
|
|
|
|
VisibleItems = ((rcClient.bottom - rcClient.top) + infoPtr->ItemHeight - 1) / infoPtr->ItemHeight;
|
|
|
|
if (Index <= VisibleFirst + VisibleItems)
|
|
{
|
|
RECT rcUpdate;
|
|
|
|
rcUpdate.left = rcClient.left + infoPtr->CheckBoxLeft[ItemBox] - (infoPtr->ItemHeight / 2);
|
|
rcUpdate.right = rcUpdate.left + infoPtr->ItemHeight;
|
|
rcUpdate.top = ((Index - VisibleFirst) * infoPtr->ItemHeight);
|
|
rcUpdate.bottom = rcUpdate.top + infoPtr->ItemHeight;
|
|
|
|
RedrawWindow(infoPtr->hSelf,
|
|
&rcUpdate,
|
|
NULL,
|
|
RDW_INVALIDATE | RDW_ERASE | RDW_UPDATENOW | RDW_NOCHILDREN);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#if SUPPORT_UXTHEME
|
|
static VOID
|
|
ChangeCheckItemHotTrack(IN PCHECKLISTWND infoPtr,
|
|
IN PCHECKITEM NewHotTrack,
|
|
IN UINT NewHotTrackBox)
|
|
{
|
|
if (NewHotTrack != infoPtr->HoveredCheckItem)
|
|
{
|
|
PCHECKITEM OldHotTrack = infoPtr->HoveredCheckItem;
|
|
UINT OldHotTrackBox = infoPtr->HoveredCheckItemBox;
|
|
|
|
infoPtr->HoveredCheckItem = NewHotTrack;
|
|
infoPtr->HoveredCheckItemBox = NewHotTrackBox;
|
|
|
|
if (OldHotTrack != NULL)
|
|
{
|
|
UpdateCheckItemBox(infoPtr,
|
|
OldHotTrack,
|
|
OldHotTrackBox);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
infoPtr->HoveredCheckItemBox = NewHotTrackBox;
|
|
}
|
|
|
|
if (NewHotTrack != NULL)
|
|
{
|
|
UpdateCheckItemBox(infoPtr,
|
|
NewHotTrack,
|
|
NewHotTrackBox);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static BOOL
|
|
ChangeCheckBox(IN PCHECKLISTWND infoPtr,
|
|
IN PCHECKITEM CheckItem,
|
|
IN UINT CheckItemBox)
|
|
{
|
|
NMCHANGEITEMCHECKBOX CheckData;
|
|
DWORD OldState = CheckItem->State;
|
|
DWORD CheckedBit = ((infoPtr->FocusedCheckItemBox == CLB_DENY) ? CIS_DENY : CIS_ALLOW);
|
|
BOOL Checked = (CheckItem->State & CheckedBit) != 0;
|
|
|
|
CheckData.OldState = OldState;
|
|
CheckData.NewState = (Checked ? OldState & ~CheckedBit : OldState | CheckedBit);
|
|
CheckData.CheckBox = infoPtr->FocusedCheckItemBox;
|
|
CheckData.Checked = !Checked;
|
|
|
|
if (NotifyControlParent(infoPtr,
|
|
CLN_CHANGINGITEMCHECKBOX,
|
|
&CheckData) != (LRESULT)-1)
|
|
{
|
|
CheckItem->State = CheckData.NewState;
|
|
}
|
|
|
|
return (CheckItem->State != OldState);
|
|
}
|
|
|
|
static VOID
|
|
DisplayCaret(IN PCHECKLISTWND infoPtr)
|
|
{
|
|
if (IsWindowEnabled(infoPtr->hSelf) && !infoPtr->ShowingCaret)
|
|
{
|
|
infoPtr->ShowingCaret = TRUE;
|
|
|
|
CreateCaret(infoPtr->hSelf,
|
|
NULL,
|
|
infoPtr->CaretWidth,
|
|
infoPtr->ItemHeight - (2 * CI_TEXT_MARGIN_HEIGHT));
|
|
|
|
ShowCaret(infoPtr->hSelf);
|
|
}
|
|
}
|
|
|
|
static VOID
|
|
RemoveCaret(IN PCHECKLISTWND infoPtr)
|
|
{
|
|
if (IsWindowEnabled(infoPtr->hSelf) && infoPtr->ShowingCaret)
|
|
{
|
|
infoPtr->ShowingCaret = FALSE;
|
|
|
|
HideCaret(infoPtr->hSelf);
|
|
DestroyCaret();
|
|
}
|
|
}
|
|
|
|
static VOID
|
|
KillQuickSearchTimers(IN PCHECKLISTWND infoPtr)
|
|
{
|
|
KillTimer(infoPtr->hSelf,
|
|
TIMER_ID_SETHITFOCUS);
|
|
KillTimer(infoPtr->hSelf,
|
|
TIMER_ID_RESETQUICKSEARCH);
|
|
}
|
|
|
|
static VOID
|
|
MapItemToRect(IN PCHECKLISTWND infoPtr,
|
|
IN PCHECKITEM CheckItem,
|
|
OUT RECT *prcItem)
|
|
{
|
|
INT Index = CheckItemToIndex(infoPtr,
|
|
CheckItem);
|
|
if (Index != -1)
|
|
{
|
|
RECT rcClient;
|
|
INT VisibleFirst;
|
|
|
|
GetClientRect(infoPtr->hSelf,
|
|
&rcClient);
|
|
|
|
VisibleFirst = GetScrollPos(infoPtr->hSelf,
|
|
SB_VERT);
|
|
|
|
prcItem->left = rcClient.left;
|
|
prcItem->right = rcClient.right;
|
|
prcItem->top = (Index - VisibleFirst) * infoPtr->ItemHeight;
|
|
prcItem->bottom = prcItem->top + infoPtr->ItemHeight;
|
|
}
|
|
else
|
|
{
|
|
prcItem->left = 0;
|
|
prcItem->top = 0;
|
|
prcItem->right = 0;
|
|
prcItem->bottom = 0;
|
|
}
|
|
}
|
|
|
|
static VOID
|
|
UpdateCaretPos(IN PCHECKLISTWND infoPtr)
|
|
{
|
|
if (infoPtr->ShowingCaret && infoPtr->QuickSearchHitItem != NULL)
|
|
{
|
|
HDC hDC = GetDC(infoPtr->hSelf);
|
|
if (hDC != NULL)
|
|
{
|
|
SIZE TextSize;
|
|
HGDIOBJ hOldFont = SelectObject(hDC,
|
|
infoPtr->hFont);
|
|
|
|
TextSize.cx = 0;
|
|
TextSize.cy = 0;
|
|
|
|
if (infoPtr->QuickSearchText[0] == L'\0' ||
|
|
GetTextExtentPoint32(hDC,
|
|
infoPtr->QuickSearchHitItem->Name,
|
|
wcslen(infoPtr->QuickSearchText),
|
|
&TextSize))
|
|
{
|
|
RECT rcItem;
|
|
|
|
MapItemToRect(infoPtr,
|
|
infoPtr->QuickSearchHitItem,
|
|
&rcItem);
|
|
|
|
/* actually change the caret position */
|
|
SetCaretPos(rcItem.left + CI_TEXT_MARGIN_WIDTH + TextSize.cx,
|
|
rcItem.top + CI_TEXT_MARGIN_HEIGHT);
|
|
}
|
|
|
|
SelectObject(hDC,
|
|
hOldFont);
|
|
|
|
ReleaseDC(infoPtr->hSelf,
|
|
hDC);
|
|
}
|
|
}
|
|
}
|
|
|
|
static VOID
|
|
EscapeQuickSearch(IN PCHECKLISTWND infoPtr)
|
|
{
|
|
if (infoPtr->QuickSearchEnabled && infoPtr->QuickSearchHitItem != NULL)
|
|
{
|
|
PCHECKITEM OldHit = infoPtr->QuickSearchHitItem;
|
|
|
|
infoPtr->QuickSearchHitItem = NULL;
|
|
infoPtr->QuickSearchText[0] = L'\0';
|
|
|
|
/* scroll back to the focused item */
|
|
if (infoPtr->FocusedCheckItem != NULL)
|
|
{
|
|
MakeCheckItemVisible(infoPtr,
|
|
infoPtr->FocusedCheckItem);
|
|
}
|
|
|
|
/* repaint the old search hit item if it's still visible */
|
|
UpdateCheckItem(infoPtr,
|
|
OldHit);
|
|
|
|
KillQuickSearchTimers(infoPtr);
|
|
|
|
RemoveCaret(infoPtr);
|
|
}
|
|
}
|
|
|
|
static VOID
|
|
ChangeSearchHit(IN PCHECKLISTWND infoPtr,
|
|
IN PCHECKITEM NewHit)
|
|
{
|
|
PCHECKITEM OldHit = infoPtr->QuickSearchHitItem;
|
|
|
|
infoPtr->QuickSearchHitItem = NewHit;
|
|
|
|
if (OldHit != NewHit)
|
|
{
|
|
/* scroll to the new search hit */
|
|
MakeCheckItemVisible(infoPtr,
|
|
NewHit);
|
|
|
|
/* repaint the old hit if present and visible */
|
|
if (OldHit != NULL)
|
|
{
|
|
UpdateCheckItem(infoPtr,
|
|
OldHit);
|
|
}
|
|
else
|
|
{
|
|
/* show the caret the first time we find an item */
|
|
DisplayCaret(infoPtr);
|
|
}
|
|
}
|
|
|
|
UpdateCaretPos(infoPtr);
|
|
|
|
UpdateCheckItem(infoPtr,
|
|
NewHit);
|
|
|
|
/* kill the reset timer and restart the set hit focus timer */
|
|
KillTimer(infoPtr->hSelf,
|
|
TIMER_ID_RESETQUICKSEARCH);
|
|
if (infoPtr->QuickSearchSetFocusDelay != 0)
|
|
{
|
|
SetTimer(infoPtr->hSelf,
|
|
TIMER_ID_SETHITFOCUS,
|
|
infoPtr->QuickSearchSetFocusDelay,
|
|
NULL);
|
|
}
|
|
}
|
|
|
|
static BOOL
|
|
QuickSearchFindHit(IN PCHECKLISTWND infoPtr,
|
|
IN WCHAR c)
|
|
{
|
|
if (infoPtr->QuickSearchEnabled)
|
|
{
|
|
BOOL Ret = FALSE;
|
|
PCHECKITEM NewHit;
|
|
|
|
switch (c)
|
|
{
|
|
case '\r':
|
|
case '\n':
|
|
{
|
|
Ret = infoPtr->QuickSearchHitItem != NULL;
|
|
if (Ret)
|
|
{
|
|
/* NOTE: QuickSearchHitItem definitely has at least one
|
|
enabled check box, the user can't search for disabled
|
|
check items */
|
|
|
|
ChangeCheckItemFocus(infoPtr,
|
|
infoPtr->QuickSearchHitItem,
|
|
((!(infoPtr->QuickSearchHitItem->State & CIS_ALLOWDISABLED)) ? CLB_ALLOW : CLB_DENY));
|
|
|
|
EscapeQuickSearch(infoPtr);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case VK_BACK:
|
|
{
|
|
if (infoPtr->QuickSearchHitItem != NULL)
|
|
{
|
|
INT SearchLen = wcslen(infoPtr->QuickSearchText);
|
|
if (SearchLen > 0)
|
|
{
|
|
/* delete the last character */
|
|
infoPtr->QuickSearchText[--SearchLen] = L'\0';
|
|
|
|
if (SearchLen > 0)
|
|
{
|
|
/* search again */
|
|
NewHit = FindCheckItem(infoPtr,
|
|
infoPtr->QuickSearchText);
|
|
|
|
if (NewHit != NULL)
|
|
{
|
|
/* change the search hit */
|
|
ChangeSearchHit(infoPtr,
|
|
NewHit);
|
|
|
|
Ret = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!Ret)
|
|
{
|
|
EscapeQuickSearch(infoPtr);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
INT SearchLen = wcslen(infoPtr->QuickSearchText);
|
|
if (SearchLen < (INT)(sizeof(infoPtr->QuickSearchText) / sizeof(infoPtr->QuickSearchText[0])) - 1)
|
|
{
|
|
infoPtr->QuickSearchText[SearchLen++] = c;
|
|
infoPtr->QuickSearchText[SearchLen] = L'\0';
|
|
|
|
NewHit = FindCheckItem(infoPtr,
|
|
infoPtr->QuickSearchText);
|
|
if (NewHit != NULL)
|
|
{
|
|
/* change the search hit */
|
|
ChangeSearchHit(infoPtr,
|
|
NewHit);
|
|
|
|
Ret = TRUE;
|
|
}
|
|
else
|
|
{
|
|
/* reset the input */
|
|
infoPtr->QuickSearchText[--SearchLen] = L'\0';
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return Ret;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static LRESULT CALLBACK
|
|
CheckListWndProc(IN HWND hwnd,
|
|
IN UINT uMsg,
|
|
IN WPARAM wParam,
|
|
IN LPARAM lParam)
|
|
{
|
|
PCHECKLISTWND infoPtr;
|
|
LRESULT Ret;
|
|
|
|
infoPtr = (PCHECKLISTWND)GetWindowLongPtr(hwnd,
|
|
0);
|
|
|
|
if (infoPtr == NULL && uMsg != WM_CREATE)
|
|
{
|
|
goto HandleDefaultMessage;
|
|
}
|
|
|
|
Ret = 0;
|
|
|
|
switch (uMsg)
|
|
{
|
|
case WM_PAINT:
|
|
{
|
|
HDC hdc;
|
|
RECT rcUpdate;
|
|
PAINTSTRUCT ps;
|
|
|
|
if (GetUpdateRect(hwnd,
|
|
&rcUpdate,
|
|
FALSE))
|
|
{
|
|
hdc = (wParam != 0 ? (HDC)wParam : BeginPaint(hwnd, &ps));
|
|
|
|
if (hdc != NULL)
|
|
{
|
|
PaintControl(infoPtr,
|
|
hdc,
|
|
&rcUpdate);
|
|
|
|
if (wParam == 0)
|
|
{
|
|
EndPaint(hwnd,
|
|
&ps);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case WM_MOUSEMOVE:
|
|
{
|
|
POINT pt;
|
|
BOOL InCheckBox;
|
|
HWND hWndCapture = GetCapture();
|
|
|
|
pt.x = (LONG)LOWORD(lParam);
|
|
pt.y = (LONG)HIWORD(lParam);
|
|
|
|
#if SUPPORT_UXTHEME
|
|
/* handle hovering checkboxes */
|
|
if (hWndCapture == NULL && infoPtr->ThemeHandle != NULL)
|
|
{
|
|
TRACKMOUSEEVENT tme;
|
|
PCHECKITEM HotTrackItem;
|
|
UINT HotTrackItemBox;
|
|
|
|
HotTrackItem = PtToCheckItemBox(infoPtr,
|
|
&pt,
|
|
&HotTrackItemBox,
|
|
&InCheckBox);
|
|
if (HotTrackItem != NULL && InCheckBox)
|
|
{
|
|
if (infoPtr->HoveredCheckItem != HotTrackItem ||
|
|
infoPtr->HoveredCheckItemBox != HotTrackItemBox)
|
|
{
|
|
ChangeCheckItemHotTrack(infoPtr,
|
|
HotTrackItem,
|
|
HotTrackItemBox);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ChangeCheckItemHotTrack(infoPtr,
|
|
NULL,
|
|
0);
|
|
}
|
|
|
|
tme.cbSize = sizeof(tme);
|
|
tme.dwFlags = TME_LEAVE;
|
|
tme.hwndTrack = hwnd;
|
|
tme.dwHoverTime = infoPtr->HoverTime;
|
|
|
|
TrackMouseEvent(&tme);
|
|
}
|
|
#endif
|
|
|
|
if (hWndCapture == hwnd && infoPtr->FocusedCheckItem != NULL)
|
|
{
|
|
PCHECKITEM PtItem;
|
|
UINT PtItemBox;
|
|
UINT OldPushed;
|
|
|
|
PtItem = PtToCheckItemBox(infoPtr,
|
|
&pt,
|
|
&PtItemBox,
|
|
&InCheckBox);
|
|
|
|
OldPushed = infoPtr->FocusedPushed;
|
|
infoPtr->FocusedPushed = InCheckBox && infoPtr->FocusedCheckItem == PtItem &&
|
|
infoPtr->FocusedCheckItemBox == PtItemBox;
|
|
|
|
if (OldPushed != infoPtr->FocusedPushed)
|
|
{
|
|
UpdateCheckItemBox(infoPtr,
|
|
infoPtr->FocusedCheckItem,
|
|
infoPtr->FocusedCheckItemBox);
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case WM_VSCROLL:
|
|
{
|
|
SCROLLINFO ScrollInfo;
|
|
|
|
ScrollInfo.cbSize = sizeof(ScrollInfo);
|
|
ScrollInfo.fMask = SIF_RANGE | SIF_POS;
|
|
|
|
if (GetScrollInfo(hwnd,
|
|
SB_VERT,
|
|
&ScrollInfo))
|
|
{
|
|
INT OldPos = ScrollInfo.nPos;
|
|
|
|
switch (LOWORD(wParam))
|
|
{
|
|
case SB_BOTTOM:
|
|
ScrollInfo.nPos = ScrollInfo.nMax;
|
|
break;
|
|
|
|
case SB_LINEDOWN:
|
|
if (ScrollInfo.nPos < ScrollInfo.nMax)
|
|
{
|
|
ScrollInfo.nPos++;
|
|
}
|
|
break;
|
|
|
|
case SB_LINEUP:
|
|
if (ScrollInfo.nPos > 0)
|
|
{
|
|
ScrollInfo.nPos--;
|
|
}
|
|
break;
|
|
|
|
case SB_PAGEDOWN:
|
|
{
|
|
RECT rcClient;
|
|
INT ScrollLines;
|
|
|
|
/* don't use ScrollInfo.nPage because we should only scroll
|
|
down by the number of completely visible list entries.
|
|
nPage however also includes the partly cropped list
|
|
item at the bottom of the control */
|
|
|
|
GetClientRect(hwnd,
|
|
&rcClient);
|
|
|
|
ScrollLines = max(1,
|
|
(rcClient.bottom - rcClient.top) / infoPtr->ItemHeight);
|
|
|
|
if (ScrollInfo.nPos + ScrollLines <= ScrollInfo.nMax)
|
|
{
|
|
ScrollInfo.nPos += ScrollLines;
|
|
}
|
|
else
|
|
{
|
|
ScrollInfo.nPos = ScrollInfo.nMax;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SB_PAGEUP:
|
|
{
|
|
RECT rcClient;
|
|
INT ScrollLines;
|
|
|
|
/* don't use ScrollInfo.nPage because we should only scroll
|
|
down by the number of completely visible list entries.
|
|
nPage however also includes the partly cropped list
|
|
item at the bottom of the control */
|
|
|
|
GetClientRect(hwnd,
|
|
&rcClient);
|
|
|
|
ScrollLines = max(1,
|
|
(rcClient.bottom - rcClient.top) / infoPtr->ItemHeight);
|
|
|
|
if (ScrollInfo.nPos >= ScrollLines)
|
|
{
|
|
ScrollInfo.nPos -= ScrollLines;
|
|
}
|
|
else
|
|
{
|
|
ScrollInfo.nPos = 0;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SB_THUMBPOSITION:
|
|
case SB_THUMBTRACK:
|
|
{
|
|
ScrollInfo.nPos = HIWORD(wParam);
|
|
break;
|
|
}
|
|
|
|
case SB_TOP:
|
|
ScrollInfo.nPos = 0;
|
|
break;
|
|
}
|
|
|
|
if (OldPos != ScrollInfo.nPos)
|
|
{
|
|
ScrollInfo.fMask = SIF_POS;
|
|
|
|
ScrollInfo.nPos = SetScrollInfo(hwnd,
|
|
SB_VERT,
|
|
&ScrollInfo,
|
|
TRUE);
|
|
|
|
if (OldPos != ScrollInfo.nPos)
|
|
{
|
|
ScrollWindowEx(hwnd,
|
|
0,
|
|
(OldPos - ScrollInfo.nPos) * infoPtr->ItemHeight,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
SW_INVALIDATE | SW_SCROLLCHILDREN);
|
|
|
|
RedrawWindow(hwnd,
|
|
NULL,
|
|
NULL,
|
|
RDW_ERASE | RDW_UPDATENOW | RDW_NOCHILDREN);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case CLM_ADDITEM:
|
|
{
|
|
INT Index = -1;
|
|
PCHECKITEM Item = AddCheckItem(infoPtr,
|
|
(LPWSTR)lParam,
|
|
CIS_NONE,
|
|
(ACCESS_MASK)wParam,
|
|
&Index);
|
|
if (Item != NULL)
|
|
{
|
|
UpdateControl(infoPtr);
|
|
Ret = (LRESULT)Index;
|
|
}
|
|
else
|
|
{
|
|
Ret = (LRESULT)-1;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case CLM_DELITEM:
|
|
{
|
|
PCHECKITEM Item = FindCheckItemByIndex(infoPtr,
|
|
wParam);
|
|
if (Item != NULL)
|
|
{
|
|
Ret = DeleteCheckItem(infoPtr,
|
|
Item);
|
|
if (Ret)
|
|
{
|
|
UpdateControl(infoPtr);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Ret = FALSE;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case CLM_SETITEMSTATE:
|
|
{
|
|
PCHECKITEM Item = FindCheckItemByIndex(infoPtr,
|
|
wParam);
|
|
if (Item != NULL)
|
|
{
|
|
DWORD OldState = Item->State;
|
|
Item->State = (DWORD)lParam & CIS_MASK;
|
|
|
|
if (Item->State != OldState)
|
|
{
|
|
/* revert the focus if the currently focused item is about
|
|
to be disabled */
|
|
if (Item == infoPtr->FocusedCheckItem &&
|
|
(Item->State & CIS_DISABLED))
|
|
{
|
|
if (infoPtr->FocusedCheckItemBox == CLB_DENY)
|
|
{
|
|
if (Item->State & CIS_DENYDISABLED)
|
|
{
|
|
infoPtr->FocusedCheckItem = NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (Item->State & CIS_ALLOWDISABLED)
|
|
{
|
|
infoPtr->FocusedCheckItem = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
UpdateControl(infoPtr);
|
|
}
|
|
Ret = TRUE;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case CLM_GETITEMCOUNT:
|
|
{
|
|
Ret = infoPtr->CheckItemCount;
|
|
break;
|
|
}
|
|
|
|
case CLM_CLEAR:
|
|
{
|
|
ClearCheckItems(infoPtr);
|
|
UpdateControl(infoPtr);
|
|
break;
|
|
}
|
|
|
|
case CLM_SETCHECKBOXCOLUMN:
|
|
{
|
|
infoPtr->CheckBoxLeft[wParam != CLB_DENY] = (INT)lParam;
|
|
UpdateControl(infoPtr);
|
|
Ret = 1;
|
|
break;
|
|
}
|
|
|
|
case CLM_GETCHECKBOXCOLUMN:
|
|
{
|
|
Ret = (LRESULT)infoPtr->CheckBoxLeft[wParam != CLB_DENY];
|
|
break;
|
|
}
|
|
|
|
case CLM_CLEARCHECKBOXES:
|
|
{
|
|
Ret = (LRESULT)ClearCheckBoxes(infoPtr);
|
|
if (Ret)
|
|
{
|
|
UpdateControl(infoPtr);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case CLM_ENABLEQUICKSEARCH:
|
|
{
|
|
if (wParam == 0)
|
|
{
|
|
EscapeQuickSearch(infoPtr);
|
|
}
|
|
infoPtr->QuickSearchEnabled = (wParam != 0);
|
|
break;
|
|
}
|
|
|
|
case CLM_SETQUICKSEARCH_TIMEOUT_RESET:
|
|
{
|
|
infoPtr->QuickSearchResetDelay = (UINT)wParam;
|
|
break;
|
|
}
|
|
|
|
case CLM_SETQUICKSEARCH_TIMEOUT_SETFOCUS:
|
|
{
|
|
infoPtr->QuickSearchSetFocusDelay = (UINT)wParam;
|
|
break;
|
|
}
|
|
|
|
case CLM_FINDITEMBYACCESSMASK:
|
|
{
|
|
Ret = (LRESULT)FindCheckItemIndexByAccessMask(infoPtr,
|
|
(ACCESS_MASK)wParam);
|
|
break;
|
|
}
|
|
|
|
case WM_SETFONT:
|
|
{
|
|
Ret = (LRESULT)RetChangeControlFont(infoPtr,
|
|
(HFONT)wParam,
|
|
(BOOL)LOWORD(lParam));
|
|
break;
|
|
}
|
|
|
|
case WM_GETFONT:
|
|
{
|
|
Ret = (LRESULT)infoPtr->hFont;
|
|
break;
|
|
}
|
|
|
|
case WM_STYLECHANGED:
|
|
{
|
|
if (wParam == (WPARAM)GWL_STYLE)
|
|
{
|
|
UpdateControl(infoPtr);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case WM_ENABLE:
|
|
{
|
|
EscapeQuickSearch(infoPtr);
|
|
|
|
UpdateControl(infoPtr);
|
|
break;
|
|
}
|
|
|
|
case WM_MOUSEWHEEL:
|
|
{
|
|
SHORT ScrollDelta;
|
|
UINT ScrollLines = 3;
|
|
|
|
SystemParametersInfo(SPI_GETWHEELSCROLLLINES,
|
|
0,
|
|
&ScrollLines,
|
|
0);
|
|
ScrollDelta = 0 - (SHORT)HIWORD(wParam);
|
|
|
|
if (ScrollLines != 0 &&
|
|
abs(ScrollDelta) >= WHEEL_DELTA)
|
|
{
|
|
SCROLLINFO ScrollInfo;
|
|
|
|
ScrollInfo.cbSize = sizeof(ScrollInfo);
|
|
ScrollInfo.fMask = SIF_RANGE | SIF_POS;
|
|
|
|
if (GetScrollInfo(hwnd,
|
|
SB_VERT,
|
|
&ScrollInfo))
|
|
{
|
|
INT OldPos = ScrollInfo.nPos;
|
|
|
|
ScrollInfo.nPos += (ScrollDelta / WHEEL_DELTA) * ScrollLines;
|
|
if (ScrollInfo.nPos < 0)
|
|
ScrollInfo.nPos = 0;
|
|
else if (ScrollInfo.nPos > ScrollInfo.nMax)
|
|
ScrollInfo.nPos = ScrollInfo.nMax;
|
|
|
|
if (OldPos != ScrollInfo.nPos)
|
|
{
|
|
ScrollInfo.fMask = SIF_POS;
|
|
|
|
ScrollInfo.nPos = SetScrollInfo(hwnd,
|
|
SB_VERT,
|
|
&ScrollInfo,
|
|
TRUE);
|
|
|
|
if (OldPos != ScrollInfo.nPos)
|
|
{
|
|
ScrollWindowEx(hwnd,
|
|
0,
|
|
(OldPos - ScrollInfo.nPos) * infoPtr->ItemHeight,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
SW_INVALIDATE | SW_SCROLLCHILDREN);
|
|
|
|
RedrawWindow(hwnd,
|
|
NULL,
|
|
NULL,
|
|
RDW_ERASE | RDW_UPDATENOW | RDW_NOCHILDREN);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case WM_SETFOCUS:
|
|
{
|
|
infoPtr->HasFocus = TRUE;
|
|
|
|
if (infoPtr->FocusedCheckItem == NULL)
|
|
{
|
|
BOOL Shift = GetKeyState(VK_SHIFT) & 0x8000;
|
|
infoPtr->FocusedCheckItem = FindEnabledCheckBox(infoPtr,
|
|
Shift,
|
|
&infoPtr->FocusedCheckItemBox);
|
|
}
|
|
if (infoPtr->FocusedCheckItem != NULL)
|
|
{
|
|
MakeCheckItemVisible(infoPtr,
|
|
infoPtr->FocusedCheckItem);
|
|
|
|
UpdateCheckItem(infoPtr,
|
|
infoPtr->FocusedCheckItem);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case WM_KILLFOCUS:
|
|
{
|
|
EscapeQuickSearch(infoPtr);
|
|
|
|
infoPtr->HasFocus = FALSE;
|
|
if (infoPtr->FocusedCheckItem != NULL)
|
|
{
|
|
infoPtr->FocusedPushed = FALSE;
|
|
|
|
UpdateCheckItem(infoPtr,
|
|
infoPtr->FocusedCheckItem);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case WM_LBUTTONDBLCLK:
|
|
case WM_LBUTTONDOWN:
|
|
case WM_MBUTTONDOWN:
|
|
case WM_RBUTTONDOWN:
|
|
{
|
|
if (IsWindowEnabled(hwnd))
|
|
{
|
|
PCHECKITEM NewFocus;
|
|
UINT NewFocusBox = 0;
|
|
BOOL InCheckBox;
|
|
POINT pt;
|
|
BOOL ChangeFocus, Capture = FALSE;
|
|
|
|
pt.x = (LONG)LOWORD(lParam);
|
|
pt.y = (LONG)HIWORD(lParam);
|
|
|
|
NewFocus = PtToCheckItemBox(infoPtr,
|
|
&pt,
|
|
&NewFocusBox,
|
|
&InCheckBox);
|
|
if (NewFocus != NULL)
|
|
{
|
|
if (NewFocus->State & ((NewFocusBox != CLB_DENY) ? CIS_ALLOWDISABLED : CIS_DENYDISABLED))
|
|
{
|
|
/* the user clicked on a disabled checkbox, try to set
|
|
the focus to the other one or not change it at all */
|
|
|
|
InCheckBox = FALSE;
|
|
|
|
ChangeFocus = ((NewFocus->State & CIS_DISABLED) != CIS_DISABLED);
|
|
if (ChangeFocus)
|
|
{
|
|
NewFocusBox = ((NewFocusBox != CLB_DENY) ? CLB_DENY : CLB_ALLOW);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ChangeFocus = TRUE;
|
|
}
|
|
|
|
if (InCheckBox && ChangeFocus && GetCapture() == NULL &&
|
|
(uMsg == WM_LBUTTONDOWN || uMsg == WM_LBUTTONDBLCLK))
|
|
{
|
|
infoPtr->FocusedPushed = TRUE;
|
|
Capture = TRUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ChangeFocus = TRUE;
|
|
}
|
|
|
|
if (ChangeFocus)
|
|
{
|
|
if (infoPtr->QuickSearchEnabled && infoPtr->QuickSearchHitItem != NULL &&
|
|
infoPtr->QuickSearchHitItem != NewFocus)
|
|
{
|
|
EscapeQuickSearch(infoPtr);
|
|
}
|
|
|
|
ChangeCheckItemFocus(infoPtr,
|
|
NewFocus,
|
|
NewFocusBox);
|
|
}
|
|
|
|
if (!infoPtr->HasFocus)
|
|
{
|
|
SetFocus(hwnd);
|
|
}
|
|
|
|
if (Capture)
|
|
{
|
|
SetCapture(hwnd);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case WM_LBUTTONUP:
|
|
{
|
|
if (GetCapture() == hwnd)
|
|
{
|
|
if (infoPtr->FocusedCheckItem != NULL && infoPtr->FocusedPushed)
|
|
{
|
|
PCHECKITEM PtItem;
|
|
UINT PtItemBox;
|
|
BOOL InCheckBox;
|
|
POINT pt;
|
|
|
|
pt.x = (LONG)LOWORD(lParam);
|
|
pt.y = (LONG)HIWORD(lParam);
|
|
|
|
infoPtr->FocusedPushed = FALSE;
|
|
|
|
PtItem = PtToCheckItemBox(infoPtr,
|
|
&pt,
|
|
&PtItemBox,
|
|
&InCheckBox);
|
|
|
|
if (PtItem == infoPtr->FocusedCheckItem && InCheckBox &&
|
|
PtItemBox == infoPtr->FocusedCheckItemBox)
|
|
{
|
|
UINT OtherBox = ((PtItemBox == CLB_ALLOW) ? CLB_DENY : CLB_ALLOW);
|
|
DWORD OtherStateMask = ((OtherBox == CLB_ALLOW) ?
|
|
(CIS_ALLOW | CIS_ALLOWDISABLED) :
|
|
(CIS_DENY | CIS_DENYDISABLED));
|
|
DWORD OtherStateOld = PtItem->State & OtherStateMask;
|
|
if (ChangeCheckBox(infoPtr,
|
|
PtItem,
|
|
PtItemBox) &&
|
|
((PtItem->State & OtherStateMask) != OtherStateOld))
|
|
{
|
|
UpdateCheckItemBox(infoPtr,
|
|
infoPtr->FocusedCheckItem,
|
|
OtherBox);
|
|
}
|
|
}
|
|
|
|
UpdateCheckItemBox(infoPtr,
|
|
infoPtr->FocusedCheckItem,
|
|
infoPtr->FocusedCheckItemBox);
|
|
}
|
|
|
|
ReleaseCapture();
|
|
}
|
|
break;
|
|
}
|
|
|
|
case WM_KEYDOWN:
|
|
{
|
|
switch (wParam)
|
|
{
|
|
case VK_SPACE:
|
|
{
|
|
if (GetCapture() == NULL &&
|
|
!QuickSearchFindHit(infoPtr,
|
|
L' '))
|
|
{
|
|
if (infoPtr->FocusedCheckItem != NULL &&
|
|
(infoPtr->QuickSearchHitItem == NULL ||
|
|
infoPtr->QuickSearchHitItem == infoPtr->FocusedCheckItem))
|
|
{
|
|
UINT OldPushed = infoPtr->FocusedPushed;
|
|
infoPtr->FocusedPushed = TRUE;
|
|
|
|
if (infoPtr->FocusedPushed != OldPushed)
|
|
{
|
|
MakeCheckItemVisible(infoPtr,
|
|
infoPtr->FocusedCheckItem);
|
|
|
|
UpdateCheckItemBox(infoPtr,
|
|
infoPtr->FocusedCheckItem,
|
|
infoPtr->FocusedCheckItemBox);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case VK_RETURN:
|
|
{
|
|
if (GetCapture() == NULL &&
|
|
!QuickSearchFindHit(infoPtr,
|
|
L'\n'))
|
|
{
|
|
if (infoPtr->FocusedCheckItem != NULL &&
|
|
infoPtr->QuickSearchHitItem == NULL)
|
|
{
|
|
UINT OtherBox;
|
|
DWORD OtherStateMask;
|
|
DWORD OtherStateOld;
|
|
|
|
MakeCheckItemVisible(infoPtr,
|
|
infoPtr->FocusedCheckItem);
|
|
|
|
OtherBox = ((infoPtr->FocusedCheckItemBox == CLB_ALLOW) ? CLB_DENY : CLB_ALLOW);
|
|
OtherStateMask = ((OtherBox == CLB_ALLOW) ?
|
|
(CIS_ALLOW | CIS_ALLOWDISABLED) :
|
|
(CIS_DENY | CIS_DENYDISABLED));
|
|
OtherStateOld = infoPtr->FocusedCheckItem->State & OtherStateMask;
|
|
if (ChangeCheckBox(infoPtr,
|
|
infoPtr->FocusedCheckItem,
|
|
infoPtr->FocusedCheckItemBox))
|
|
{
|
|
UpdateCheckItemBox(infoPtr,
|
|
infoPtr->FocusedCheckItem,
|
|
infoPtr->FocusedCheckItemBox);
|
|
if ((infoPtr->FocusedCheckItem->State & OtherStateMask) != OtherStateOld)
|
|
{
|
|
UpdateCheckItemBox(infoPtr,
|
|
infoPtr->FocusedCheckItem,
|
|
OtherBox);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case VK_TAB:
|
|
{
|
|
if (GetCapture() == NULL)
|
|
{
|
|
PCHECKITEM NewFocus;
|
|
UINT NewFocusBox = 0;
|
|
BOOL Shift = GetKeyState(VK_SHIFT) & 0x8000;
|
|
|
|
EscapeQuickSearch(infoPtr);
|
|
|
|
NewFocus = FindEnabledCheckBox(infoPtr,
|
|
Shift,
|
|
&NewFocusBox);
|
|
|
|
/* update the UI status */
|
|
SendMessage(GetAncestor(hwnd,
|
|
GA_PARENT),
|
|
WM_CHANGEUISTATE,
|
|
MAKEWPARAM(UIS_INITIALIZE,
|
|
0),
|
|
0);
|
|
|
|
ChangeCheckItemFocus(infoPtr,
|
|
NewFocus,
|
|
NewFocusBox);
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
goto HandleDefaultMessage;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case WM_KEYUP:
|
|
{
|
|
if (wParam == VK_SPACE && IsWindowEnabled(hwnd) &&
|
|
infoPtr->FocusedCheckItem != NULL &&
|
|
infoPtr->FocusedPushed)
|
|
{
|
|
UINT OtherBox = ((infoPtr->FocusedCheckItemBox == CLB_ALLOW) ? CLB_DENY : CLB_ALLOW);
|
|
DWORD OtherStateMask = ((OtherBox == CLB_ALLOW) ?
|
|
(CIS_ALLOW | CIS_ALLOWDISABLED) :
|
|
(CIS_DENY | CIS_DENYDISABLED));
|
|
DWORD OtherStateOld = infoPtr->FocusedCheckItem->State & OtherStateMask;
|
|
|
|
infoPtr->FocusedPushed = FALSE;
|
|
|
|
if (ChangeCheckBox(infoPtr,
|
|
infoPtr->FocusedCheckItem,
|
|
infoPtr->FocusedCheckItemBox))
|
|
{
|
|
UpdateCheckItemBox(infoPtr,
|
|
infoPtr->FocusedCheckItem,
|
|
infoPtr->FocusedCheckItemBox);
|
|
|
|
if ((infoPtr->FocusedCheckItem->State & OtherStateMask) != OtherStateOld)
|
|
{
|
|
UpdateCheckItemBox(infoPtr,
|
|
infoPtr->FocusedCheckItem,
|
|
OtherBox);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case WM_GETDLGCODE:
|
|
{
|
|
INT virtKey;
|
|
|
|
Ret = 0;
|
|
virtKey = (lParam != 0 ? (INT)((LPMSG)lParam)->wParam : 0);
|
|
switch (virtKey)
|
|
{
|
|
case VK_RETURN:
|
|
{
|
|
if (infoPtr->QuickSearchEnabled && infoPtr->QuickSearchHitItem != NULL)
|
|
{
|
|
Ret |= DLGC_WANTCHARS | DLGC_WANTMESSAGE;
|
|
}
|
|
else
|
|
{
|
|
Ret |= DLGC_WANTMESSAGE;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case VK_TAB:
|
|
{
|
|
UINT CheckBox;
|
|
BOOL EnabledBox;
|
|
BOOL Shift = GetKeyState(VK_SHIFT) & 0x8000;
|
|
|
|
EnabledBox = FindEnabledCheckBox(infoPtr,
|
|
Shift,
|
|
&CheckBox) != NULL;
|
|
Ret |= (EnabledBox ? DLGC_WANTTAB : DLGC_WANTCHARS);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
if (infoPtr->QuickSearchEnabled)
|
|
{
|
|
Ret |= DLGC_WANTCHARS;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case WM_CHAR:
|
|
{
|
|
QuickSearchFindHit(infoPtr,
|
|
(WCHAR)wParam);
|
|
break;
|
|
}
|
|
|
|
case WM_SYSCOLORCHANGE:
|
|
{
|
|
infoPtr->TextColor[0] = GetSysColor(COLOR_GRAYTEXT);
|
|
infoPtr->TextColor[1] = GetSysColor(COLOR_WINDOWTEXT);
|
|
break;
|
|
}
|
|
|
|
#if SUPPORT_UXTHEME
|
|
case WM_MOUSELEAVE:
|
|
{
|
|
if (infoPtr->HoveredCheckItem != NULL)
|
|
{
|
|
/* reset and repaint the hovered check item box */
|
|
ChangeCheckItemHotTrack(infoPtr,
|
|
NULL,
|
|
0);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case WM_THEMECHANGED:
|
|
{
|
|
if (infoPtr->ThemeHandle != NULL)
|
|
{
|
|
CloseThemeData(infoPtr->ThemeHandle);
|
|
infoPtr->ThemeHandle = NULL;
|
|
}
|
|
if (IsAppThemed())
|
|
{
|
|
infoPtr->ThemeHandle = OpenThemeData(infoPtr->hSelf,
|
|
L"BUTTON");
|
|
}
|
|
break;
|
|
}
|
|
#endif
|
|
|
|
case WM_SETTINGCHANGE:
|
|
{
|
|
DWORD OldCaretWidth = infoPtr->CaretWidth;
|
|
|
|
#if SUPPORT_UXTHEME
|
|
/* update the hover time */
|
|
if (!SystemParametersInfo(SPI_GETMOUSEHOVERTIME,
|
|
0,
|
|
&infoPtr->HoverTime,
|
|
0))
|
|
{
|
|
infoPtr->HoverTime = HOVER_DEFAULT;
|
|
}
|
|
#endif
|
|
|
|
/* update the caret */
|
|
if (!SystemParametersInfo(SPI_GETCARETWIDTH,
|
|
0,
|
|
&infoPtr->CaretWidth,
|
|
0))
|
|
{
|
|
infoPtr->CaretWidth = 2;
|
|
}
|
|
if (OldCaretWidth != infoPtr->CaretWidth && infoPtr->ShowingCaret)
|
|
{
|
|
DestroyCaret();
|
|
CreateCaret(hwnd,
|
|
NULL,
|
|
infoPtr->CaretWidth,
|
|
infoPtr->ItemHeight - (2 * CI_TEXT_MARGIN_HEIGHT));
|
|
}
|
|
break;
|
|
}
|
|
|
|
case WM_SIZE:
|
|
{
|
|
UpdateControl(infoPtr);
|
|
break;
|
|
}
|
|
|
|
case WM_UPDATEUISTATE:
|
|
{
|
|
DWORD OldUIState = infoPtr->UIState;
|
|
|
|
switch (LOWORD(wParam))
|
|
{
|
|
case UIS_SET:
|
|
infoPtr->UIState |= HIWORD(wParam);
|
|
break;
|
|
|
|
case UIS_CLEAR:
|
|
infoPtr->UIState &= ~(HIWORD(wParam));
|
|
break;
|
|
}
|
|
|
|
if (OldUIState != infoPtr->UIState)
|
|
{
|
|
if (infoPtr->FocusedCheckItem != NULL)
|
|
{
|
|
UpdateCheckItemBox(infoPtr,
|
|
infoPtr->FocusedCheckItem,
|
|
infoPtr->FocusedCheckItemBox);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case WM_TIMER:
|
|
{
|
|
switch (wParam)
|
|
{
|
|
case TIMER_ID_SETHITFOCUS:
|
|
{
|
|
/* kill the timer */
|
|
KillTimer(hwnd,
|
|
wParam);
|
|
|
|
if (infoPtr->QuickSearchEnabled && infoPtr->QuickSearchHitItem != NULL)
|
|
{
|
|
/* change the focus to the hit item, this item has to have
|
|
at least one enabled checkbox! */
|
|
ChangeCheckItemFocus(infoPtr,
|
|
infoPtr->QuickSearchHitItem,
|
|
((!(infoPtr->QuickSearchHitItem->State & CIS_ALLOWDISABLED)) ? CLB_ALLOW : CLB_DENY));
|
|
|
|
/* start the timer to reset quicksearch */
|
|
if (infoPtr->QuickSearchResetDelay != 0)
|
|
{
|
|
SetTimer(hwnd,
|
|
TIMER_ID_RESETQUICKSEARCH,
|
|
infoPtr->QuickSearchResetDelay,
|
|
NULL);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case TIMER_ID_RESETQUICKSEARCH:
|
|
{
|
|
/* kill the timer */
|
|
KillTimer(hwnd,
|
|
wParam);
|
|
|
|
/* escape quick search */
|
|
EscapeQuickSearch(infoPtr);
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case WM_CREATE:
|
|
{
|
|
infoPtr = HeapAlloc(GetProcessHeap(),
|
|
0,
|
|
sizeof(CHECKLISTWND));
|
|
if (infoPtr != NULL)
|
|
{
|
|
RECT rcClient;
|
|
|
|
infoPtr->hSelf = hwnd;
|
|
infoPtr->hNotify = ((LPCREATESTRUCTW)lParam)->hwndParent;
|
|
|
|
SetWindowLongPtr(hwnd,
|
|
0,
|
|
(DWORD_PTR)infoPtr);
|
|
|
|
infoPtr->CheckItemListHead = NULL;
|
|
infoPtr->CheckItemCount = 0;
|
|
|
|
if (!SystemParametersInfo(SPI_GETCARETWIDTH,
|
|
0,
|
|
&infoPtr->CaretWidth,
|
|
0))
|
|
{
|
|
infoPtr->CaretWidth = 2;
|
|
}
|
|
infoPtr->ItemHeight = 10;
|
|
infoPtr->ShowingCaret = FALSE;
|
|
|
|
infoPtr->HasFocus = FALSE;
|
|
infoPtr->FocusedCheckItem = NULL;
|
|
infoPtr->FocusedCheckItemBox = 0;
|
|
infoPtr->FocusedPushed = FALSE;
|
|
|
|
infoPtr->TextColor[0] = GetSysColor(COLOR_GRAYTEXT);
|
|
infoPtr->TextColor[1] = GetSysColor(COLOR_WINDOWTEXT);
|
|
|
|
GetClientRect(hwnd,
|
|
&rcClient);
|
|
|
|
infoPtr->CheckBoxLeft[0] = rcClient.right - 30;
|
|
infoPtr->CheckBoxLeft[1] = rcClient.right - 15;
|
|
|
|
infoPtr->QuickSearchEnabled = FALSE;
|
|
infoPtr->QuickSearchText[0] = L'\0';
|
|
|
|
infoPtr->QuickSearchSetFocusDelay = DEFAULT_QUICKSEARCH_SETFOCUS_DELAY;
|
|
infoPtr->QuickSearchResetDelay = DEFAULT_QUICKSEARCH_RESET_DELAY;
|
|
|
|
#if SUPPORT_UXTHEME
|
|
infoPtr->HoveredCheckItem = NULL;
|
|
infoPtr->HoveredCheckItemBox = 0;
|
|
if (!SystemParametersInfo(SPI_GETMOUSEHOVERTIME,
|
|
0,
|
|
&infoPtr->HoverTime,
|
|
0))
|
|
{
|
|
infoPtr->HoverTime = HOVER_DEFAULT;
|
|
}
|
|
|
|
if (IsAppThemed())
|
|
{
|
|
infoPtr->ThemeHandle = OpenThemeData(infoPtr->hSelf,
|
|
L"BUTTON");
|
|
}
|
|
else
|
|
{
|
|
infoPtr->ThemeHandle = NULL;
|
|
}
|
|
#endif
|
|
|
|
infoPtr->UIState = SendMessage(hwnd,
|
|
WM_QUERYUISTATE,
|
|
0,
|
|
0);
|
|
}
|
|
else
|
|
{
|
|
Ret = -1;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case WM_DESTROY:
|
|
{
|
|
if (infoPtr->ShowingCaret)
|
|
{
|
|
DestroyCaret();
|
|
}
|
|
|
|
ClearCheckItems(infoPtr);
|
|
|
|
#if SUPPORT_UXTHEME
|
|
if (infoPtr->ThemeHandle != NULL)
|
|
{
|
|
CloseThemeData(infoPtr->ThemeHandle);
|
|
}
|
|
#endif
|
|
|
|
HeapFree(GetProcessHeap(),
|
|
0,
|
|
infoPtr);
|
|
SetWindowLongPtr(hwnd,
|
|
0,
|
|
(DWORD_PTR)NULL);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
HandleDefaultMessage:
|
|
Ret = DefWindowProc(hwnd,
|
|
uMsg,
|
|
wParam,
|
|
lParam);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return Ret;
|
|
}
|
|
|
|
BOOL
|
|
RegisterCheckListControl(IN HINSTANCE hInstance)
|
|
{
|
|
WNDCLASS wc;
|
|
|
|
wc.style = CS_DBLCLKS;
|
|
wc.lpfnWndProc = CheckListWndProc;
|
|
wc.cbClsExtra = 0;
|
|
wc.cbWndExtra = sizeof(PCHECKLISTWND);
|
|
wc.hInstance = hInstance;
|
|
wc.hIcon = NULL;
|
|
wc.hCursor = LoadCursor(NULL,
|
|
(LPWSTR)IDC_ARROW);
|
|
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
|
|
wc.lpszMenuName = NULL;
|
|
wc.lpszClassName = szCheckListWndClass;
|
|
|
|
return RegisterClass(&wc) != 0;
|
|
}
|
|
|
|
VOID
|
|
UnregisterCheckListControl(HINSTANCE hInstance)
|
|
{
|
|
UnregisterClass(szCheckListWndClass,
|
|
hInstance);
|
|
}
|