/* * 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 * * UPDATE HISTORY: * 07/01/2005 Created */ #include "precomp.h" #ifdef SUPPORT_UXTHEME #include #include #endif #define NDEBUG #include 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 themeed 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 themeed 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); }