xref: /reactos/dll/win32/aclui/checklist.c (revision c2c66aff)
1 /*
2  * ReactOS Access Control List Editor
3  * Copyright (C) 2004-2005 ReactOS Team
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
18  */
19 /*
20  * PROJECT:         ReactOS Access Control List Editor
21  * FILE:            lib/aclui/checklist.c
22  * PURPOSE:         Access Control List Editor
23  * PROGRAMMER:      Thomas Weidenmueller <w3seek@reactos.com>
24  *
25  * UPDATE HISTORY:
26  *      07/01/2005  Created
27  */
28 
29 #include "precomp.h"
30 
31 #ifdef SUPPORT_UXTHEME
32 #include <uxtheme.h>
33 #include <tmschema.h>
34 #endif
35 
36 #define NDEBUG
37 #include <debug.h>
38 
39 static const WCHAR szCheckListWndClass[] = L"CHECKLIST_ACLUI";
40 
41 #define CI_TEXT_MARGIN_WIDTH    (8)
42 #define CI_TEXT_MARGIN_HEIGHT   (3)
43 #define CI_TEXT_SELECTIONMARGIN (1)
44 
45 #define TIMER_ID_SETHITFOCUS    (1)
46 #define TIMER_ID_RESETQUICKSEARCH       (2)
47 
48 #define DEFAULT_QUICKSEARCH_SETFOCUS_DELAY      (2000)
49 #define DEFAULT_QUICKSEARCH_RESET_DELAY (3000)
50 
51 typedef struct _CHECKITEM
52 {
53     struct _CHECKITEM *Next;
54     ACCESS_MASK AccessMask;
55     DWORD State;
56     WCHAR Name[1];
57 } CHECKITEM, *PCHECKITEM;
58 
59 typedef struct _CHECKLISTWND
60 {
61     HWND hSelf;
62     HWND hNotify;
63     HFONT hFont;
64 
65     PCHECKITEM CheckItemListHead;
66     UINT CheckItemCount;
67 
68     INT ItemHeight;
69 
70     PCHECKITEM FocusedCheckItem;
71     UINT FocusedCheckItemBox;
72 
73     COLORREF TextColor[2];
74     INT CheckBoxLeft[2];
75 
76     PCHECKITEM QuickSearchHitItem;
77     WCHAR QuickSearchText[65];
78     UINT QuickSearchSetFocusDelay;
79     UINT QuickSearchResetDelay;
80 
81     DWORD CaretWidth;
82 
83     DWORD UIState;
84 
85 #if SUPPORT_UXTHEME
86     PCHECKITEM HoveredCheckItem;
87     UINT HoveredCheckItemBox;
88     UINT HoverTime;
89 
90     HTHEME ThemeHandle;
91 #endif
92 
93     UINT HasFocus : 1;
94     UINT FocusedPushed : 1;
95     UINT QuickSearchEnabled : 1;
96     UINT ShowingCaret : 1;
97 } CHECKLISTWND, *PCHECKLISTWND;
98 
99 static VOID EscapeQuickSearch(IN PCHECKLISTWND infoPtr);
100 #if SUPPORT_UXTHEME
101 static VOID ChangeCheckItemHotTrack(IN PCHECKLISTWND infoPtr,
102                                     IN PCHECKITEM NewHotTrack,
103                                     IN UINT NewHotTrackBox);
104 #endif
105 static VOID ChangeCheckItemFocus(IN PCHECKLISTWND infoPtr,
106                                  IN PCHECKITEM NewFocus,
107                                  IN UINT NewFocusBox);
108 
109 /******************************************************************************/
110 
111 static LRESULT
NotifyControlParent(IN PCHECKLISTWND infoPtr,IN UINT code,IN OUT PVOID data)112 NotifyControlParent(IN PCHECKLISTWND infoPtr,
113                     IN UINT code,
114                     IN OUT PVOID data)
115 {
116     LRESULT Ret = 0;
117 
118     if (infoPtr->hNotify != NULL)
119     {
120         LPNMHDR pnmh = (LPNMHDR)data;
121 
122         pnmh->hwndFrom = infoPtr->hSelf;
123         pnmh->idFrom = GetWindowLongPtr(infoPtr->hSelf,
124                                         GWLP_ID);
125         pnmh->code = code;
126 
127         Ret = SendMessage(infoPtr->hNotify,
128                           WM_NOTIFY,
129                           (WPARAM)pnmh->idFrom,
130                           (LPARAM)pnmh);
131     }
132 
133     return Ret;
134 }
135 
136 static PCHECKITEM
FindCheckItemByIndex(IN PCHECKLISTWND infoPtr,IN INT Index)137 FindCheckItemByIndex(IN PCHECKLISTWND infoPtr,
138                      IN INT Index)
139 {
140     PCHECKITEM Item, Found = NULL;
141 
142     if (Index >= 0)
143     {
144         for (Item = infoPtr->CheckItemListHead;
145              Item != NULL;
146              Item = Item->Next)
147         {
148             if (Index == 0)
149             {
150                 Found = Item;
151                 break;
152             }
153 
154             Index--;
155         }
156     }
157 
158     return Found;
159 }
160 
161 static INT
FindCheckItemIndexByAccessMask(IN PCHECKLISTWND infoPtr,IN ACCESS_MASK AccessMask)162 FindCheckItemIndexByAccessMask(IN PCHECKLISTWND infoPtr,
163                                IN ACCESS_MASK AccessMask)
164 {
165     PCHECKITEM Item;
166     INT Index = 0, Found = -1;
167 
168     for (Item = infoPtr->CheckItemListHead;
169          Item != NULL;
170          Item = Item->Next)
171     {
172         if (Item->AccessMask == AccessMask)
173         {
174             Found = Index;
175             break;
176         }
177 
178         Index++;
179     }
180 
181     return Found;
182 }
183 
184 static INT
CheckItemToIndex(IN PCHECKLISTWND infoPtr,IN PCHECKITEM Item)185 CheckItemToIndex(IN PCHECKLISTWND infoPtr,
186                  IN PCHECKITEM Item)
187 {
188     PCHECKITEM CurItem;
189     INT Index;
190 
191     for (CurItem = infoPtr->CheckItemListHead, Index = 0;
192          CurItem != NULL;
193          CurItem = CurItem->Next, Index++)
194     {
195         if (CurItem == Item)
196         {
197             return Index;
198         }
199     }
200 
201     return -1;
202 }
203 
204 static PCHECKITEM
FindCheckItem(IN PCHECKLISTWND infoPtr,IN LPWSTR SearchText)205 FindCheckItem(IN PCHECKLISTWND infoPtr,
206               IN LPWSTR SearchText)
207 {
208     PCHECKITEM CurItem;
209     SIZE_T Count = wcslen(SearchText);
210 
211     for (CurItem = infoPtr->CheckItemListHead;
212          CurItem != NULL;
213          CurItem = CurItem->Next)
214     {
215         if ((CurItem->State & CIS_DISABLED) != CIS_DISABLED &&
216             !_wcsnicmp(CurItem->Name,
217                       SearchText, Count))
218         {
219             break;
220         }
221     }
222 
223     return CurItem;
224 }
225 
226 static PCHECKITEM
FindFirstEnabledCheckBox(IN PCHECKLISTWND infoPtr,OUT UINT * CheckBox)227 FindFirstEnabledCheckBox(IN PCHECKLISTWND infoPtr,
228                          OUT UINT *CheckBox)
229 {
230     PCHECKITEM CurItem;
231 
232     for (CurItem = infoPtr->CheckItemListHead;
233          CurItem != NULL;
234          CurItem = CurItem->Next)
235     {
236         if ((CurItem->State & CIS_DISABLED) != CIS_DISABLED)
237         {
238             /* return the Allow checkbox in case both check boxes are enabled! */
239             *CheckBox = ((!(CurItem->State & CIS_ALLOWDISABLED)) ? CLB_ALLOW : CLB_DENY);
240             break;
241         }
242     }
243 
244     return CurItem;
245 }
246 
247 static PCHECKITEM
FindLastEnabledCheckBox(IN PCHECKLISTWND infoPtr,OUT UINT * CheckBox)248 FindLastEnabledCheckBox(IN PCHECKLISTWND infoPtr,
249                         OUT UINT *CheckBox)
250 {
251     PCHECKITEM CurItem;
252     PCHECKITEM LastEnabledItem = NULL;
253 
254     for (CurItem = infoPtr->CheckItemListHead;
255          CurItem != NULL;
256          CurItem = CurItem->Next)
257     {
258         if ((CurItem->State & CIS_DISABLED) != CIS_DISABLED)
259         {
260             LastEnabledItem = CurItem;
261         }
262     }
263 
264     if (LastEnabledItem != NULL)
265     {
266         /* return the Deny checkbox in case both check boxes are enabled! */
267         *CheckBox = ((!(LastEnabledItem->State & CIS_DENYDISABLED)) ? CLB_DENY : CLB_ALLOW);
268     }
269 
270     return LastEnabledItem;
271 }
272 
273 static PCHECKITEM
FindPreviousEnabledCheckBox(IN PCHECKLISTWND infoPtr,OUT UINT * CheckBox)274 FindPreviousEnabledCheckBox(IN PCHECKLISTWND infoPtr,
275                             OUT UINT *CheckBox)
276 {
277     PCHECKITEM Item;
278 
279     if (infoPtr->FocusedCheckItem != NULL)
280     {
281         Item = infoPtr->FocusedCheckItem;
282 
283         if (infoPtr->FocusedCheckItemBox == CLB_DENY &&
284             !(Item->State & CIS_ALLOWDISABLED))
285         {
286             /* currently an Deny checkbox is focused. return the Allow checkbox
287                if it's enabled */
288             *CheckBox = CLB_ALLOW;
289         }
290         else
291         {
292             PCHECKITEM CurItem;
293 
294             Item = NULL;
295 
296             for (CurItem = infoPtr->CheckItemListHead;
297                  CurItem != infoPtr->FocusedCheckItem;
298                  CurItem = CurItem->Next)
299             {
300                 if ((CurItem->State & CIS_DISABLED) != CIS_DISABLED)
301                 {
302                     Item = CurItem;
303                 }
304             }
305 
306             if (Item != NULL)
307             {
308                 /* return the Deny checkbox in case both check boxes are enabled! */
309                 *CheckBox = ((!(Item->State & CIS_DENYDISABLED)) ? CLB_DENY : CLB_ALLOW);
310             }
311         }
312     }
313     else
314     {
315         Item = FindLastEnabledCheckBox(infoPtr,
316                                        CheckBox);
317     }
318 
319     return Item;
320 }
321 
322 static PCHECKITEM
FindNextEnabledCheckBox(IN PCHECKLISTWND infoPtr,OUT UINT * CheckBox)323 FindNextEnabledCheckBox(IN PCHECKLISTWND infoPtr,
324                         OUT UINT *CheckBox)
325 {
326     PCHECKITEM Item;
327 
328     if (infoPtr->FocusedCheckItem != NULL)
329     {
330         Item = infoPtr->FocusedCheckItem;
331 
332         if (infoPtr->FocusedCheckItemBox != CLB_DENY &&
333             !(Item->State & CIS_DENYDISABLED))
334         {
335             /* currently an Allow checkbox is focused. return the Deny checkbox
336                if it's enabled */
337             *CheckBox = CLB_DENY;
338         }
339         else
340         {
341             Item = Item->Next;
342 
343             while (Item != NULL)
344             {
345                 if ((Item->State & CIS_DISABLED) != CIS_DISABLED)
346                 {
347                     /* return the Allow checkbox in case both check boxes are enabled! */
348                     *CheckBox = ((!(Item->State & CIS_ALLOWDISABLED)) ? CLB_ALLOW : CLB_DENY);
349                     break;
350                 }
351 
352                 Item = Item->Next;
353             }
354         }
355     }
356     else
357     {
358         Item = FindFirstEnabledCheckBox(infoPtr,
359                                         CheckBox);
360     }
361 
362     return Item;
363 }
364 
365 static PCHECKITEM
FindEnabledCheckBox(IN PCHECKLISTWND infoPtr,IN BOOL ReverseSearch,OUT UINT * CheckBox)366 FindEnabledCheckBox(IN PCHECKLISTWND infoPtr,
367                     IN BOOL ReverseSearch,
368                     OUT UINT *CheckBox)
369 {
370     PCHECKITEM Item;
371 
372     if (ReverseSearch)
373     {
374         Item = FindPreviousEnabledCheckBox(infoPtr,
375                                            CheckBox);
376     }
377     else
378     {
379         Item = FindNextEnabledCheckBox(infoPtr,
380                                        CheckBox);
381     }
382 
383     return Item;
384 }
385 
386 static PCHECKITEM
PtToCheckItemBox(IN PCHECKLISTWND infoPtr,IN PPOINT ppt,OUT UINT * CheckBox,OUT BOOL * DirectlyInCheckBox)387 PtToCheckItemBox(IN PCHECKLISTWND infoPtr,
388                  IN PPOINT ppt,
389                  OUT UINT *CheckBox,
390                  OUT BOOL *DirectlyInCheckBox)
391 {
392     INT FirstVisible, Index;
393     PCHECKITEM Item;
394 
395     FirstVisible = GetScrollPos(infoPtr->hSelf,
396                                 SB_VERT);
397 
398     Index = FirstVisible + (ppt->y / infoPtr->ItemHeight);
399 
400     Item = FindCheckItemByIndex(infoPtr,
401                                 Index);
402     if (Item != NULL)
403     {
404         INT cx;
405 
406         cx = infoPtr->CheckBoxLeft[CLB_ALLOW] +
407              ((infoPtr->CheckBoxLeft[CLB_DENY] - infoPtr->CheckBoxLeft[CLB_ALLOW]) / 2);
408 
409         *CheckBox = ((ppt->x <= cx) ? CLB_ALLOW : CLB_DENY);
410 
411         if (DirectlyInCheckBox != NULL)
412         {
413             INT y = ppt->y % infoPtr->ItemHeight;
414             INT cxBox = infoPtr->ItemHeight - (2 * CI_TEXT_MARGIN_HEIGHT);
415 
416             if ((y >= CI_TEXT_MARGIN_HEIGHT &&
417                  y < infoPtr->ItemHeight - CI_TEXT_MARGIN_HEIGHT) &&
418 
419                 (((ppt->x >= (infoPtr->CheckBoxLeft[CLB_ALLOW] - (cxBox / 2))) &&
420                   (ppt->x < (infoPtr->CheckBoxLeft[CLB_ALLOW] - (cxBox / 2) + cxBox)))
421                  ||
422                  ((ppt->x >= (infoPtr->CheckBoxLeft[CLB_DENY] - (cxBox / 2))) &&
423                   (ppt->x < (infoPtr->CheckBoxLeft[CLB_DENY] - (cxBox / 2) + cxBox)))))
424             {
425                 *DirectlyInCheckBox = TRUE;
426             }
427             else
428             {
429                 *DirectlyInCheckBox = FALSE;
430             }
431         }
432     }
433 
434     return Item;
435 }
436 
437 static VOID
ClearCheckItems(IN PCHECKLISTWND infoPtr)438 ClearCheckItems(IN PCHECKLISTWND infoPtr)
439 {
440     PCHECKITEM CurItem, NextItem;
441 
442     CurItem = infoPtr->CheckItemListHead;
443     while (CurItem != NULL)
444     {
445         NextItem = CurItem->Next;
446         HeapFree(GetProcessHeap(),
447                  0,
448                  CurItem);
449         CurItem = NextItem;
450     }
451 
452     infoPtr->CheckItemListHead = NULL;
453     infoPtr->CheckItemCount = 0;
454 }
455 
456 static BOOL
DeleteCheckItem(IN PCHECKLISTWND infoPtr,IN PCHECKITEM Item)457 DeleteCheckItem(IN PCHECKLISTWND infoPtr,
458                 IN PCHECKITEM Item)
459 {
460     PCHECKITEM CurItem;
461     PCHECKITEM *PrevPtr = &infoPtr->CheckItemListHead;
462 
463     for (CurItem = infoPtr->CheckItemListHead;
464          CurItem != NULL;
465          CurItem = CurItem->Next)
466     {
467         if (CurItem == Item)
468         {
469             if (Item == infoPtr->QuickSearchHitItem && infoPtr->QuickSearchEnabled)
470             {
471                 EscapeQuickSearch(infoPtr);
472             }
473 
474 #if SUPPORT_UXTHEME
475             if (Item == infoPtr->HoveredCheckItem)
476             {
477                 ChangeCheckItemHotTrack(infoPtr,
478                                         NULL,
479                                         0);
480             }
481 #endif
482 
483             if (Item == infoPtr->FocusedCheckItem)
484             {
485                 ChangeCheckItemFocus(infoPtr,
486                                      NULL,
487                                      0);
488             }
489 
490             *PrevPtr = CurItem->Next;
491             HeapFree(GetProcessHeap(),
492                      0,
493                      CurItem);
494             infoPtr->CheckItemCount--;
495             return TRUE;
496         }
497 
498         PrevPtr = &CurItem->Next;
499     }
500 
501     return FALSE;
502 }
503 
504 static PCHECKITEM
AddCheckItem(IN PCHECKLISTWND infoPtr,IN LPWSTR Name,IN DWORD State,IN ACCESS_MASK AccessMask,OUT INT * Index)505 AddCheckItem(IN PCHECKLISTWND infoPtr,
506              IN LPWSTR Name,
507              IN DWORD State,
508              IN ACCESS_MASK AccessMask,
509              OUT INT *Index)
510 {
511     PCHECKITEM CurItem;
512     INT i;
513     PCHECKITEM *PrevPtr = &infoPtr->CheckItemListHead;
514     PCHECKITEM Item = HeapAlloc(GetProcessHeap(),
515                                 0,
516                                 sizeof(CHECKITEM) + (wcslen(Name) * sizeof(WCHAR)));
517     if (Item != NULL)
518     {
519         for (CurItem = infoPtr->CheckItemListHead, i = 0;
520              CurItem != NULL;
521              CurItem = CurItem->Next)
522         {
523             PrevPtr = &CurItem->Next;
524             i++;
525         }
526 
527         Item->Next = NULL;
528         Item->AccessMask = AccessMask;
529         Item->State = State & CIS_MASK;
530         wcscpy(Item->Name,
531                Name);
532 
533         *PrevPtr = Item;
534         infoPtr->CheckItemCount++;
535 
536         if (Index != NULL)
537         {
538             *Index = i;
539         }
540     }
541 
542     return Item;
543 }
544 
545 static UINT
ClearCheckBoxes(IN PCHECKLISTWND infoPtr)546 ClearCheckBoxes(IN PCHECKLISTWND infoPtr)
547 {
548     PCHECKITEM CurItem;
549     UINT nUpdated = 0;
550 
551     for (CurItem = infoPtr->CheckItemListHead;
552          CurItem != NULL;
553          CurItem = CurItem->Next)
554     {
555         if (CurItem->State & (CIS_ALLOW | CIS_DENY))
556         {
557             CurItem->State &= ~(CIS_ALLOW | CIS_DENY);
558             nUpdated++;
559         }
560     }
561 
562     return nUpdated;
563 }
564 
565 static VOID
UpdateControl(IN PCHECKLISTWND infoPtr)566 UpdateControl(IN PCHECKLISTWND infoPtr)
567 {
568     RECT rcClient;
569     SCROLLINFO ScrollInfo;
570     INT VisibleItems;
571 
572     GetClientRect(infoPtr->hSelf,
573                   &rcClient);
574 
575     ScrollInfo.cbSize = sizeof(ScrollInfo);
576     ScrollInfo.fMask = SIF_PAGE | SIF_RANGE;
577     ScrollInfo.nMin = 0;
578     ScrollInfo.nMax = infoPtr->CheckItemCount;
579     ScrollInfo.nPage = ((rcClient.bottom - rcClient.top) + infoPtr->ItemHeight - 1) / infoPtr->ItemHeight;
580     ScrollInfo.nPos = 0;
581     ScrollInfo.nTrackPos = 0;
582 
583     VisibleItems = (rcClient.bottom - rcClient.top) / infoPtr->ItemHeight;
584 
585     if (ScrollInfo.nPage == (UINT)VisibleItems && ScrollInfo.nMax > 0)
586     {
587         ScrollInfo.nMax--;
588     }
589 
590     SetScrollInfo(infoPtr->hSelf,
591                   SB_VERT,
592                   &ScrollInfo,
593                   TRUE);
594 
595     RedrawWindow(infoPtr->hSelf,
596                  NULL,
597                  NULL,
598                  RDW_INVALIDATE | RDW_ERASE | RDW_UPDATENOW | RDW_NOCHILDREN);
599 }
600 
601 static VOID
UpdateCheckItem(IN PCHECKLISTWND infoPtr,IN PCHECKITEM Item)602 UpdateCheckItem(IN PCHECKLISTWND infoPtr,
603                 IN PCHECKITEM Item)
604 {
605     RECT rcClient;
606     INT VisibleFirst, VisibleItems;
607     INT Index = CheckItemToIndex(infoPtr,
608                                  Item);
609     if (Index != -1)
610     {
611         VisibleFirst = GetScrollPos(infoPtr->hSelf,
612                                     SB_VERT);
613 
614         if (Index >= VisibleFirst)
615         {
616             GetClientRect(infoPtr->hSelf,
617                           &rcClient);
618 
619             VisibleItems = ((rcClient.bottom - rcClient.top) + infoPtr->ItemHeight - 1) / infoPtr->ItemHeight;
620 
621             if (Index <= VisibleFirst + VisibleItems)
622             {
623                 RECT rcUpdate;
624 
625                 rcUpdate.left = rcClient.left;
626                 rcUpdate.right = rcClient.right;
627                 rcUpdate.top = (Index - VisibleFirst) * infoPtr->ItemHeight;
628                 rcUpdate.bottom = rcUpdate.top + infoPtr->ItemHeight;
629 
630                 RedrawWindow(infoPtr->hSelf,
631                              &rcUpdate,
632                              NULL,
633                              RDW_INVALIDATE | RDW_ERASE | RDW_UPDATENOW | RDW_NOCHILDREN);
634             }
635         }
636     }
637 }
638 
639 static VOID
MakeCheckItemVisible(IN PCHECKLISTWND infoPtr,IN PCHECKITEM Item)640 MakeCheckItemVisible(IN PCHECKLISTWND infoPtr,
641                      IN PCHECKITEM Item)
642 {
643     RECT rcClient;
644     INT VisibleFirst, VisibleItems, NewPos;
645     INT Index = CheckItemToIndex(infoPtr,
646                                  Item);
647     if (Index != -1)
648     {
649         VisibleFirst = GetScrollPos(infoPtr->hSelf,
650                                     SB_VERT);
651 
652         if (Index <= VisibleFirst)
653         {
654             NewPos = Index;
655         }
656         else
657         {
658             GetClientRect(infoPtr->hSelf,
659                           &rcClient);
660 
661             VisibleItems = (rcClient.bottom - rcClient.top) / infoPtr->ItemHeight;
662             if (Index - VisibleItems + 1 > VisibleFirst)
663             {
664                 NewPos = Index - VisibleItems + 1;
665             }
666             else
667             {
668                 NewPos = VisibleFirst;
669             }
670         }
671 
672         if (VisibleFirst != NewPos)
673         {
674             SCROLLINFO ScrollInfo;
675 
676             ScrollInfo.cbSize = sizeof(ScrollInfo);
677             ScrollInfo.fMask = SIF_POS;
678             ScrollInfo.nPos = NewPos;
679             NewPos = SetScrollInfo(infoPtr->hSelf,
680                                    SB_VERT,
681                                    &ScrollInfo,
682                                    TRUE);
683 
684             if (VisibleFirst != NewPos)
685             {
686                 ScrollWindowEx(infoPtr->hSelf,
687                                0,
688                                (NewPos - VisibleFirst) * infoPtr->ItemHeight,
689                                NULL,
690                                NULL,
691                                NULL,
692                                NULL,
693                                SW_INVALIDATE | SW_SCROLLCHILDREN);
694 
695                 RedrawWindow(infoPtr->hSelf,
696                              NULL,
697                              NULL,
698                              RDW_INVALIDATE | RDW_ERASE | RDW_UPDATENOW | RDW_NOCHILDREN);
699             }
700         }
701     }
702 }
703 
704 static UINT
GetIdealItemHeight(IN PCHECKLISTWND infoPtr)705 GetIdealItemHeight(IN PCHECKLISTWND infoPtr)
706 {
707     HDC hdc = GetDC(infoPtr->hSelf);
708     if(hdc != NULL)
709     {
710         UINT height;
711         TEXTMETRIC tm;
712         HGDIOBJ hOldFont = SelectObject(hdc,
713                                         infoPtr->hFont);
714 
715         if(GetTextMetrics(hdc,
716                           &tm))
717         {
718             height = tm.tmHeight;
719         }
720         else
721         {
722             height = 2;
723         }
724 
725         SelectObject(hdc,
726                      hOldFont);
727 
728         ReleaseDC(infoPtr->hSelf,
729                   hdc);
730 
731         return height;
732     }
733     return 0;
734 }
735 
736 static HFONT
RetChangeControlFont(IN PCHECKLISTWND infoPtr,IN HFONT hFont,IN BOOL Redraw)737 RetChangeControlFont(IN PCHECKLISTWND infoPtr,
738                      IN HFONT hFont,
739                      IN BOOL Redraw)
740 {
741     HFONT hOldFont = infoPtr->hFont;
742     infoPtr->hFont = hFont;
743 
744     if (hOldFont != hFont)
745     {
746         infoPtr->ItemHeight = (2 * CI_TEXT_MARGIN_HEIGHT) + GetIdealItemHeight(infoPtr);
747     }
748 
749     if (infoPtr->ShowingCaret)
750     {
751         DestroyCaret();
752         CreateCaret(infoPtr->hSelf,
753                     NULL,
754                     0,
755                     infoPtr->ItemHeight - (2 * CI_TEXT_MARGIN_HEIGHT));
756     }
757 
758     UpdateControl(infoPtr);
759 
760     return hOldFont;
761 }
762 
763 #if SUPPORT_UXTHEME
764 static INT
CalculateCheckBoxStyle(IN BOOL Checked,IN BOOL Enabled,IN BOOL HotTrack,IN BOOL Pushed)765 CalculateCheckBoxStyle(IN BOOL Checked,
766                        IN BOOL Enabled,
767                        IN BOOL HotTrack,
768                        IN BOOL Pushed)
769 {
770     INT BtnState;
771 
772     if (Checked)
773     {
774         BtnState = (Enabled ?
775                     (Pushed ? CBS_CHECKEDPRESSED : (HotTrack ? CBS_CHECKEDHOT : CBS_CHECKEDNORMAL)) :
776                     CBS_CHECKEDDISABLED);
777     }
778     else
779     {
780         BtnState = (Enabled ?
781                     (Pushed ? CBS_UNCHECKEDPRESSED : (HotTrack ? CBS_UNCHECKEDHOT : CBS_UNCHECKEDNORMAL)) :
782                     CBS_UNCHECKEDDISABLED);
783     }
784 
785     return BtnState;
786 }
787 #endif
788 
789 static VOID
PaintControl(IN PCHECKLISTWND infoPtr,IN HDC hDC,IN PRECT rcUpdate)790 PaintControl(IN PCHECKLISTWND infoPtr,
791              IN HDC hDC,
792              IN PRECT rcUpdate)
793 {
794     INT ScrollPos;
795     PCHECKITEM FirstItem, Item;
796     RECT rcClient;
797     UINT VisibleFirstIndex = rcUpdate->top / infoPtr->ItemHeight;
798     UINT LastTouchedIndex = rcUpdate->bottom / infoPtr->ItemHeight;
799 
800     FillRect(hDC,
801              rcUpdate,
802              (HBRUSH)(COLOR_WINDOW + 1));
803 
804     GetClientRect(infoPtr->hSelf,
805                   &rcClient);
806 
807     ScrollPos = GetScrollPos(infoPtr->hSelf,
808                              SB_VERT);
809 
810     FirstItem = FindCheckItemByIndex(infoPtr,
811                                      ScrollPos + VisibleFirstIndex);
812     if (FirstItem != NULL)
813     {
814         RECT TextRect, ItemRect, CheckBox;
815         HFONT hOldFont;
816         DWORD CurrentIndex;
817         COLORREF OldTextColor;
818         BOOL Enabled, PrevEnabled, IsPushed;
819         POINT hOldBrushOrg;
820 #if SUPPORT_UXTHEME
821         HRESULT hDrawResult;
822         BOOL ItemHovered;
823 #endif
824 
825         Enabled = IsWindowEnabled(infoPtr->hSelf);
826         PrevEnabled = Enabled;
827 
828         ItemRect.left = 0;
829         ItemRect.right = rcClient.right;
830         ItemRect.top = VisibleFirstIndex * infoPtr->ItemHeight;
831 
832         TextRect.left = ItemRect.left + CI_TEXT_MARGIN_WIDTH;
833         TextRect.right = ItemRect.right - CI_TEXT_MARGIN_WIDTH;
834         TextRect.top = ItemRect.top + CI_TEXT_MARGIN_HEIGHT;
835 
836         SetBrushOrgEx(hDC,
837                       ItemRect.left,
838                       ItemRect.top,
839                       &hOldBrushOrg);
840 
841         OldTextColor = SetTextColor(hDC,
842                                     infoPtr->TextColor[Enabled]);
843 
844         hOldFont = SelectObject(hDC,
845                                 infoPtr->hFont);
846 
847         for (Item = FirstItem, CurrentIndex = VisibleFirstIndex;
848              Item != NULL && CurrentIndex <= LastTouchedIndex;
849              Item = Item->Next, CurrentIndex++)
850         {
851             TextRect.bottom = TextRect.top + infoPtr->ItemHeight - (2 * CI_TEXT_MARGIN_HEIGHT);
852             ItemRect.bottom = ItemRect.top + infoPtr->ItemHeight;
853 
854             SetBrushOrgEx(hDC,
855                           ItemRect.left,
856                           ItemRect.top,
857                           NULL);
858 
859             if (Enabled && PrevEnabled != ((Item->State & CIS_DISABLED) != CIS_DISABLED))
860             {
861                 PrevEnabled = ((Item->State & CIS_DISABLED) != CIS_DISABLED);
862 
863                 SetTextColor(hDC,
864                              infoPtr->TextColor[PrevEnabled]);
865             }
866 
867 #if SUPPORT_UXTHEME
868             ItemHovered = (Enabled && infoPtr->HoveredCheckItem == Item);
869 #endif
870 
871             if (infoPtr->QuickSearchHitItem == Item)
872             {
873                 COLORREF OldBkColor, OldFgColor;
874                 SIZE TextSize;
875                 SIZE_T TextLen, HighlightLen = wcslen(infoPtr->QuickSearchText);
876 
877                 /* highlight the quicksearch text */
878                 if (GetTextExtentPoint32(hDC,
879                                          Item->Name,
880                                          HighlightLen,
881                                          &TextSize))
882                 {
883                     COLORREF HighlightTextColor, HighlightBackground;
884                     RECT rcHighlight = TextRect;
885 
886                     HighlightTextColor = GetSysColor(COLOR_HIGHLIGHTTEXT);
887                     HighlightBackground = GetSysColor(COLOR_HIGHLIGHT);
888 
889                     rcHighlight.right = rcHighlight.left + TextSize.cx;
890 
891                     InflateRect(&rcHighlight,
892                                 0,
893                                 CI_TEXT_SELECTIONMARGIN);
894 
895                     OldBkColor = SetBkColor(hDC,
896                                             HighlightBackground);
897                     OldFgColor = SetTextColor(hDC,
898                                               HighlightTextColor);
899 
900                     /* draw the highlighted text */
901                     DrawText(hDC,
902                              Item->Name,
903                              HighlightLen,
904                              &rcHighlight,
905                              DT_LEFT | DT_NOPREFIX | DT_SINGLELINE | DT_VCENTER);
906 
907                     SetBkColor(hDC,
908                                OldBkColor);
909                     SetTextColor(hDC,
910                                  OldFgColor);
911 
912                     /* draw the remaining part of the text */
913                     TextLen = wcslen(Item->Name);
914                     if (HighlightLen < TextLen)
915                     {
916                         rcHighlight.left = rcHighlight.right;
917                         rcHighlight.right = TextRect.right;
918 
919                         DrawText(hDC,
920                                  Item->Name + HighlightLen,
921                                  -1,
922                                  &rcHighlight,
923                                  DT_LEFT | DT_NOPREFIX | DT_SINGLELINE | DT_VCENTER);
924                     }
925                 }
926             }
927             else
928             {
929                 /* draw the text */
930                 DrawText(hDC,
931                          Item->Name,
932                          -1,
933                          &TextRect,
934                          DT_LEFT | DT_NOPREFIX | DT_SINGLELINE | DT_VCENTER);
935             }
936 
937             CheckBox.top = TextRect.top;
938             CheckBox.bottom = TextRect.bottom;
939 
940             /* draw the Allow checkbox */
941             IsPushed = (Enabled && Item == infoPtr->FocusedCheckItem && infoPtr->HasFocus &&
942                         !(Item->State & CIS_ALLOWDISABLED) && infoPtr->FocusedCheckItemBox != CLB_DENY &&
943                         infoPtr->FocusedPushed);
944 
945             CheckBox.left = infoPtr->CheckBoxLeft[CLB_ALLOW] - ((TextRect.bottom - TextRect.top) / 2);
946             CheckBox.right = CheckBox.left + (TextRect.bottom - TextRect.top);
947 #if SUPPORT_UXTHEME
948             if (infoPtr->ThemeHandle != NULL)
949             {
950                 INT BtnState = CalculateCheckBoxStyle(Item->State & CIS_ALLOW,
951                                                       Enabled && !(Item->State & CIS_ALLOWDISABLED),
952                                                       (ItemHovered && infoPtr->HoveredCheckItemBox != CLB_DENY),
953                                                       IsPushed);
954 
955 
956                 hDrawResult = DrawThemeBackground(infoPtr->ThemeHandle,
957                                                   hDC,
958                                                   BP_CHECKBOX,
959                                                   BtnState,
960                                                   &CheckBox,
961                                                   NULL);
962 
963             }
964             else
965             {
966                 hDrawResult = E_FAIL;
967             }
968 
969             /* draw the standard checkbox if no themes are enabled or drawing the
970                themed control failed */
971             if (FAILED(hDrawResult))
972 #endif
973             {
974                 DrawFrameControl(hDC,
975                                  &CheckBox,
976                                  DFC_BUTTON,
977                                  DFCS_BUTTONCHECK | DFCS_FLAT |
978                                  ((Item->State & CIS_ALLOWDISABLED) || !Enabled ? DFCS_INACTIVE : 0) |
979                                  ((Item->State & CIS_ALLOW) ? DFCS_CHECKED : 0) |
980                                  (IsPushed ? DFCS_PUSHED : 0));
981             }
982             if (Item == infoPtr->FocusedCheckItem && !(infoPtr->UIState & UISF_HIDEFOCUS) &&
983                 infoPtr->HasFocus &&
984                 infoPtr->FocusedCheckItemBox != CLB_DENY)
985             {
986                 RECT rcFocus = CheckBox;
987 
988                 InflateRect (&rcFocus,
989                              CI_TEXT_MARGIN_HEIGHT,
990                              CI_TEXT_MARGIN_HEIGHT);
991 
992                 DrawFocusRect(hDC,
993                               &rcFocus);
994             }
995 
996             /* draw the Deny checkbox */
997             IsPushed = (Enabled && Item == infoPtr->FocusedCheckItem && infoPtr->HasFocus &&
998                         !(Item->State & CIS_DENYDISABLED) && infoPtr->FocusedCheckItemBox == CLB_DENY &&
999                         infoPtr->FocusedPushed);
1000 
1001             CheckBox.left = infoPtr->CheckBoxLeft[CLB_DENY] - ((TextRect.bottom - TextRect.top) / 2);
1002             CheckBox.right = CheckBox.left + (TextRect.bottom - TextRect.top);
1003 #if SUPPORT_UXTHEME
1004             if (infoPtr->ThemeHandle != NULL)
1005             {
1006                 INT BtnState = CalculateCheckBoxStyle(Item->State & CIS_DENY,
1007                                                       Enabled && !(Item->State & CIS_DENYDISABLED),
1008                                                       (ItemHovered && infoPtr->HoveredCheckItemBox == CLB_DENY),
1009                                                       IsPushed);
1010 
1011                 hDrawResult = DrawThemeBackground(infoPtr->ThemeHandle,
1012                                                   hDC,
1013                                                   BP_CHECKBOX,
1014                                                   BtnState,
1015                                                   &CheckBox,
1016                                                   NULL);
1017 
1018             }
1019             else
1020             {
1021                 hDrawResult = E_FAIL;
1022             }
1023 
1024             /* draw the standard checkbox if no themes are enabled or drawing the
1025                themed control failed */
1026             if (FAILED(hDrawResult))
1027 #endif
1028             {
1029                 DrawFrameControl(hDC,
1030                                  &CheckBox,
1031                                  DFC_BUTTON,
1032                                  DFCS_BUTTONCHECK | DFCS_FLAT |
1033                                  ((Item->State & CIS_DENYDISABLED) || !Enabled ? DFCS_INACTIVE : 0) |
1034                                  ((Item->State & CIS_DENY) ? DFCS_CHECKED : 0) |
1035                                  (IsPushed ? DFCS_PUSHED : 0));
1036             }
1037             if (infoPtr->HasFocus && !(infoPtr->UIState & UISF_HIDEFOCUS) &&
1038                 Item == infoPtr->FocusedCheckItem &&
1039                 infoPtr->FocusedCheckItemBox == CLB_DENY)
1040             {
1041                 RECT rcFocus = CheckBox;
1042 
1043                 InflateRect (&rcFocus,
1044                              CI_TEXT_MARGIN_HEIGHT,
1045                              CI_TEXT_MARGIN_HEIGHT);
1046 
1047                 DrawFocusRect(hDC,
1048                               &rcFocus);
1049             }
1050 
1051             TextRect.top += infoPtr->ItemHeight;
1052             ItemRect.top += infoPtr->ItemHeight;
1053         }
1054 
1055         SelectObject(hDC,
1056                      hOldFont);
1057 
1058         SetTextColor(hDC,
1059                      OldTextColor);
1060 
1061         SetBrushOrgEx(hDC,
1062                       hOldBrushOrg.x,
1063                       hOldBrushOrg.y,
1064                       NULL);
1065     }
1066 }
1067 
1068 static VOID
ChangeCheckItemFocus(IN PCHECKLISTWND infoPtr,IN PCHECKITEM NewFocus,IN UINT NewFocusBox)1069 ChangeCheckItemFocus(IN PCHECKLISTWND infoPtr,
1070                      IN PCHECKITEM NewFocus,
1071                      IN UINT NewFocusBox)
1072 {
1073     if (NewFocus != infoPtr->FocusedCheckItem)
1074     {
1075         PCHECKITEM OldFocus = infoPtr->FocusedCheckItem;
1076         infoPtr->FocusedCheckItem = NewFocus;
1077         infoPtr->FocusedCheckItemBox = NewFocusBox;
1078 
1079         if (OldFocus != NULL)
1080         {
1081             UpdateCheckItem(infoPtr,
1082                             OldFocus);
1083         }
1084     }
1085     else
1086     {
1087         infoPtr->FocusedCheckItemBox = NewFocusBox;
1088     }
1089 
1090     if (NewFocus != NULL)
1091     {
1092         MakeCheckItemVisible(infoPtr,
1093                              NewFocus);
1094         UpdateCheckItem(infoPtr,
1095                         NewFocus);
1096     }
1097 }
1098 
1099 static VOID
UpdateCheckItemBox(IN PCHECKLISTWND infoPtr,IN PCHECKITEM Item,IN UINT ItemBox)1100 UpdateCheckItemBox(IN PCHECKLISTWND infoPtr,
1101                    IN PCHECKITEM Item,
1102                    IN UINT ItemBox)
1103 {
1104     RECT rcClient;
1105     INT VisibleFirst, VisibleItems;
1106     INT Index = CheckItemToIndex(infoPtr,
1107                                  Item);
1108     if (Index != -1)
1109     {
1110         VisibleFirst = GetScrollPos(infoPtr->hSelf,
1111                                     SB_VERT);
1112 
1113         if (Index >= VisibleFirst)
1114         {
1115             GetClientRect(infoPtr->hSelf,
1116                           &rcClient);
1117 
1118             VisibleItems = ((rcClient.bottom - rcClient.top) + infoPtr->ItemHeight - 1) / infoPtr->ItemHeight;
1119 
1120             if (Index <= VisibleFirst + VisibleItems)
1121             {
1122                 RECT rcUpdate;
1123 
1124                 rcUpdate.left = rcClient.left + infoPtr->CheckBoxLeft[ItemBox] - (infoPtr->ItemHeight / 2);
1125                 rcUpdate.right = rcUpdate.left + infoPtr->ItemHeight;
1126                 rcUpdate.top = ((Index - VisibleFirst) * infoPtr->ItemHeight);
1127                 rcUpdate.bottom = rcUpdate.top + infoPtr->ItemHeight;
1128 
1129                 RedrawWindow(infoPtr->hSelf,
1130                              &rcUpdate,
1131                              NULL,
1132                              RDW_INVALIDATE | RDW_ERASE | RDW_UPDATENOW | RDW_NOCHILDREN);
1133             }
1134         }
1135     }
1136 }
1137 
1138 #if SUPPORT_UXTHEME
1139 static VOID
ChangeCheckItemHotTrack(IN PCHECKLISTWND infoPtr,IN PCHECKITEM NewHotTrack,IN UINT NewHotTrackBox)1140 ChangeCheckItemHotTrack(IN PCHECKLISTWND infoPtr,
1141                         IN PCHECKITEM NewHotTrack,
1142                         IN UINT NewHotTrackBox)
1143 {
1144     if (NewHotTrack != infoPtr->HoveredCheckItem)
1145     {
1146         PCHECKITEM OldHotTrack = infoPtr->HoveredCheckItem;
1147         UINT OldHotTrackBox = infoPtr->HoveredCheckItemBox;
1148 
1149         infoPtr->HoveredCheckItem = NewHotTrack;
1150         infoPtr->HoveredCheckItemBox = NewHotTrackBox;
1151 
1152         if (OldHotTrack != NULL)
1153         {
1154             UpdateCheckItemBox(infoPtr,
1155                                OldHotTrack,
1156                                OldHotTrackBox);
1157         }
1158     }
1159     else
1160     {
1161         infoPtr->HoveredCheckItemBox = NewHotTrackBox;
1162     }
1163 
1164     if (NewHotTrack != NULL)
1165     {
1166         UpdateCheckItemBox(infoPtr,
1167                            NewHotTrack,
1168                            NewHotTrackBox);
1169     }
1170 }
1171 #endif
1172 
1173 static BOOL
ChangeCheckBox(IN PCHECKLISTWND infoPtr,IN PCHECKITEM CheckItem,IN UINT CheckItemBox)1174 ChangeCheckBox(IN PCHECKLISTWND infoPtr,
1175                IN PCHECKITEM CheckItem,
1176                IN UINT CheckItemBox)
1177 {
1178     NMCHANGEITEMCHECKBOX CheckData;
1179     DWORD OldState = CheckItem->State;
1180     DWORD CheckedBit = ((infoPtr->FocusedCheckItemBox == CLB_DENY) ? CIS_DENY : CIS_ALLOW);
1181     BOOL Checked = (CheckItem->State & CheckedBit) != 0;
1182 
1183     CheckData.OldState = OldState;
1184     CheckData.NewState = (Checked ? OldState & ~CheckedBit : OldState | CheckedBit);
1185     CheckData.CheckBox = infoPtr->FocusedCheckItemBox;
1186     CheckData.Checked = !Checked;
1187 
1188     if (NotifyControlParent(infoPtr,
1189                             CLN_CHANGINGITEMCHECKBOX,
1190                             &CheckData) != (LRESULT)-1)
1191     {
1192         CheckItem->State = CheckData.NewState;
1193     }
1194 
1195     return (CheckItem->State != OldState);
1196 }
1197 
1198 static VOID
DisplayCaret(IN PCHECKLISTWND infoPtr)1199 DisplayCaret(IN PCHECKLISTWND infoPtr)
1200 {
1201     if (IsWindowEnabled(infoPtr->hSelf) && !infoPtr->ShowingCaret)
1202     {
1203         infoPtr->ShowingCaret = TRUE;
1204 
1205         CreateCaret(infoPtr->hSelf,
1206                     NULL,
1207                     infoPtr->CaretWidth,
1208                     infoPtr->ItemHeight - (2 * CI_TEXT_MARGIN_HEIGHT));
1209 
1210         ShowCaret(infoPtr->hSelf);
1211     }
1212 }
1213 
1214 static VOID
RemoveCaret(IN PCHECKLISTWND infoPtr)1215 RemoveCaret(IN PCHECKLISTWND infoPtr)
1216 {
1217     if (IsWindowEnabled(infoPtr->hSelf) && infoPtr->ShowingCaret)
1218     {
1219         infoPtr->ShowingCaret = FALSE;
1220 
1221         HideCaret(infoPtr->hSelf);
1222         DestroyCaret();
1223     }
1224 }
1225 
1226 static VOID
KillQuickSearchTimers(IN PCHECKLISTWND infoPtr)1227 KillQuickSearchTimers(IN PCHECKLISTWND infoPtr)
1228 {
1229     KillTimer(infoPtr->hSelf,
1230               TIMER_ID_SETHITFOCUS);
1231     KillTimer(infoPtr->hSelf,
1232               TIMER_ID_RESETQUICKSEARCH);
1233 }
1234 
1235 static VOID
MapItemToRect(IN PCHECKLISTWND infoPtr,IN PCHECKITEM CheckItem,OUT RECT * prcItem)1236 MapItemToRect(IN PCHECKLISTWND infoPtr,
1237               IN PCHECKITEM CheckItem,
1238               OUT RECT *prcItem)
1239 {
1240     INT Index = CheckItemToIndex(infoPtr,
1241                                  CheckItem);
1242     if (Index != -1)
1243     {
1244         RECT rcClient;
1245         INT VisibleFirst;
1246 
1247         GetClientRect(infoPtr->hSelf,
1248                       &rcClient);
1249 
1250         VisibleFirst = GetScrollPos(infoPtr->hSelf,
1251                                     SB_VERT);
1252 
1253         prcItem->left = rcClient.left;
1254         prcItem->right = rcClient.right;
1255         prcItem->top = (Index - VisibleFirst) * infoPtr->ItemHeight;
1256         prcItem->bottom = prcItem->top + infoPtr->ItemHeight;
1257     }
1258     else
1259     {
1260         prcItem->left = 0;
1261         prcItem->top = 0;
1262         prcItem->right = 0;
1263         prcItem->bottom = 0;
1264     }
1265 }
1266 
1267 static VOID
UpdateCaretPos(IN PCHECKLISTWND infoPtr)1268 UpdateCaretPos(IN PCHECKLISTWND infoPtr)
1269 {
1270     if (infoPtr->ShowingCaret && infoPtr->QuickSearchHitItem != NULL)
1271     {
1272         HDC hDC = GetDC(infoPtr->hSelf);
1273         if (hDC != NULL)
1274         {
1275             SIZE TextSize;
1276             HGDIOBJ hOldFont = SelectObject(hDC,
1277                                             infoPtr->hFont);
1278 
1279             TextSize.cx = 0;
1280             TextSize.cy = 0;
1281 
1282             if (infoPtr->QuickSearchText[0] == L'\0' ||
1283                 GetTextExtentPoint32(hDC,
1284                                      infoPtr->QuickSearchHitItem->Name,
1285                                      wcslen(infoPtr->QuickSearchText),
1286                                      &TextSize))
1287             {
1288                 RECT rcItem;
1289 
1290                 MapItemToRect(infoPtr,
1291                               infoPtr->QuickSearchHitItem,
1292                               &rcItem);
1293 
1294                 /* actually change the caret position */
1295                 SetCaretPos(rcItem.left + CI_TEXT_MARGIN_WIDTH + TextSize.cx,
1296                             rcItem.top + CI_TEXT_MARGIN_HEIGHT);
1297             }
1298 
1299             SelectObject(hDC,
1300                          hOldFont);
1301 
1302             ReleaseDC(infoPtr->hSelf,
1303                       hDC);
1304         }
1305     }
1306 }
1307 
1308 static VOID
EscapeQuickSearch(IN PCHECKLISTWND infoPtr)1309 EscapeQuickSearch(IN PCHECKLISTWND infoPtr)
1310 {
1311     if (infoPtr->QuickSearchEnabled && infoPtr->QuickSearchHitItem != NULL)
1312     {
1313         PCHECKITEM OldHit = infoPtr->QuickSearchHitItem;
1314 
1315         infoPtr->QuickSearchHitItem = NULL;
1316         infoPtr->QuickSearchText[0] = L'\0';
1317 
1318         /* scroll back to the focused item */
1319         if (infoPtr->FocusedCheckItem != NULL)
1320         {
1321             MakeCheckItemVisible(infoPtr,
1322                                  infoPtr->FocusedCheckItem);
1323         }
1324 
1325         /* repaint the old search hit item if it's still visible */
1326         UpdateCheckItem(infoPtr,
1327                         OldHit);
1328 
1329         KillQuickSearchTimers(infoPtr);
1330 
1331         RemoveCaret(infoPtr);
1332     }
1333 }
1334 
1335 static VOID
ChangeSearchHit(IN PCHECKLISTWND infoPtr,IN PCHECKITEM NewHit)1336 ChangeSearchHit(IN PCHECKLISTWND infoPtr,
1337                 IN PCHECKITEM NewHit)
1338 {
1339     PCHECKITEM OldHit = infoPtr->QuickSearchHitItem;
1340 
1341     infoPtr->QuickSearchHitItem = NewHit;
1342 
1343     if (OldHit != NewHit)
1344     {
1345         /* scroll to the new search hit */
1346         MakeCheckItemVisible(infoPtr,
1347                              NewHit);
1348 
1349         /* repaint the old hit if present and visible */
1350         if (OldHit != NULL)
1351         {
1352             UpdateCheckItem(infoPtr,
1353                             OldHit);
1354         }
1355         else
1356         {
1357             /* show the caret the first time we find an item */
1358              DisplayCaret(infoPtr);
1359         }
1360     }
1361 
1362     UpdateCaretPos(infoPtr);
1363 
1364     UpdateCheckItem(infoPtr,
1365                     NewHit);
1366 
1367     /* kill the reset timer and restart the set hit focus timer */
1368     KillTimer(infoPtr->hSelf,
1369               TIMER_ID_RESETQUICKSEARCH);
1370     if (infoPtr->QuickSearchSetFocusDelay != 0)
1371     {
1372         SetTimer(infoPtr->hSelf,
1373                  TIMER_ID_SETHITFOCUS,
1374                  infoPtr->QuickSearchSetFocusDelay,
1375                  NULL);
1376     }
1377 }
1378 
1379 static BOOL
QuickSearchFindHit(IN PCHECKLISTWND infoPtr,IN WCHAR c)1380 QuickSearchFindHit(IN PCHECKLISTWND infoPtr,
1381                    IN WCHAR c)
1382 {
1383     if (infoPtr->QuickSearchEnabled)
1384     {
1385         BOOL Ret = FALSE;
1386         PCHECKITEM NewHit;
1387 
1388         switch (c)
1389         {
1390             case '\r':
1391             case '\n':
1392             {
1393                 Ret = infoPtr->QuickSearchHitItem != NULL;
1394                 if (Ret)
1395                 {
1396                     /* NOTE: QuickSearchHitItem definitely has at least one
1397                              enabled check box, the user can't search for disabled
1398                              check items */
1399 
1400                     ChangeCheckItemFocus(infoPtr,
1401                                          infoPtr->QuickSearchHitItem,
1402                                          ((!(infoPtr->QuickSearchHitItem->State & CIS_ALLOWDISABLED)) ? CLB_ALLOW : CLB_DENY));
1403 
1404                     EscapeQuickSearch(infoPtr);
1405                 }
1406                 break;
1407             }
1408 
1409             case VK_BACK:
1410             {
1411                 if (infoPtr->QuickSearchHitItem != NULL)
1412                 {
1413                     INT SearchLen = wcslen(infoPtr->QuickSearchText);
1414                     if (SearchLen > 0)
1415                     {
1416                         /* delete the last character */
1417                         infoPtr->QuickSearchText[--SearchLen] = L'\0';
1418 
1419                         if (SearchLen > 0)
1420                         {
1421                             /* search again */
1422                             NewHit = FindCheckItem(infoPtr,
1423                                                    infoPtr->QuickSearchText);
1424 
1425                             if (NewHit != NULL)
1426                             {
1427                                 /* change the search hit */
1428                                 ChangeSearchHit(infoPtr,
1429                                                 NewHit);
1430 
1431                                 Ret = TRUE;
1432                             }
1433                         }
1434                     }
1435 
1436                     if (!Ret)
1437                     {
1438                         EscapeQuickSearch(infoPtr);
1439                     }
1440                 }
1441                 break;
1442             }
1443 
1444             default:
1445             {
1446                 INT SearchLen = wcslen(infoPtr->QuickSearchText);
1447                 if (SearchLen < (INT)(sizeof(infoPtr->QuickSearchText) / sizeof(infoPtr->QuickSearchText[0])) - 1)
1448                 {
1449                     infoPtr->QuickSearchText[SearchLen++] = c;
1450                     infoPtr->QuickSearchText[SearchLen] = L'\0';
1451 
1452                     NewHit = FindCheckItem(infoPtr,
1453                                            infoPtr->QuickSearchText);
1454                     if (NewHit != NULL)
1455                     {
1456                         /* change the search hit */
1457                         ChangeSearchHit(infoPtr,
1458                                         NewHit);
1459 
1460                         Ret = TRUE;
1461                     }
1462                     else
1463                     {
1464                         /* reset the input */
1465                         infoPtr->QuickSearchText[--SearchLen] = L'\0';
1466                     }
1467                 }
1468                 break;
1469             }
1470         }
1471         return Ret;
1472     }
1473 
1474     return FALSE;
1475 }
1476 
1477 static LRESULT CALLBACK
CheckListWndProc(IN HWND hwnd,IN UINT uMsg,IN WPARAM wParam,IN LPARAM lParam)1478 CheckListWndProc(IN HWND hwnd,
1479                  IN UINT uMsg,
1480                  IN WPARAM wParam,
1481                  IN LPARAM lParam)
1482 {
1483     PCHECKLISTWND infoPtr;
1484     LRESULT Ret;
1485 
1486     infoPtr = (PCHECKLISTWND)GetWindowLongPtr(hwnd,
1487                                               0);
1488 
1489     if (infoPtr == NULL && uMsg != WM_CREATE)
1490     {
1491         goto HandleDefaultMessage;
1492     }
1493 
1494     Ret = 0;
1495 
1496     switch (uMsg)
1497     {
1498         case WM_PAINT:
1499         {
1500             HDC hdc;
1501             RECT rcUpdate;
1502             PAINTSTRUCT ps;
1503 
1504             if (GetUpdateRect(hwnd,
1505                               &rcUpdate,
1506                               FALSE))
1507             {
1508                 hdc = (wParam != 0 ? (HDC)wParam : BeginPaint(hwnd, &ps));
1509 
1510                 if (hdc != NULL)
1511                 {
1512                     PaintControl(infoPtr,
1513                                  hdc,
1514                                  &rcUpdate);
1515 
1516                     if (wParam == 0)
1517                     {
1518                         EndPaint(hwnd,
1519                                  &ps);
1520                     }
1521                 }
1522             }
1523             break;
1524         }
1525 
1526         case WM_MOUSEMOVE:
1527         {
1528             POINT pt;
1529             BOOL InCheckBox;
1530             HWND hWndCapture = GetCapture();
1531 
1532             pt.x = (LONG)LOWORD(lParam);
1533             pt.y = (LONG)HIWORD(lParam);
1534 
1535 #if SUPPORT_UXTHEME
1536             /* handle hovering checkboxes */
1537             if (hWndCapture == NULL && infoPtr->ThemeHandle != NULL)
1538             {
1539                 TRACKMOUSEEVENT tme;
1540                 PCHECKITEM HotTrackItem;
1541                 UINT HotTrackItemBox;
1542 
1543                 HotTrackItem = PtToCheckItemBox(infoPtr,
1544                                                 &pt,
1545                                                 &HotTrackItemBox,
1546                                                 &InCheckBox);
1547                 if (HotTrackItem != NULL && InCheckBox)
1548                 {
1549                     if (infoPtr->HoveredCheckItem != HotTrackItem ||
1550                         infoPtr->HoveredCheckItemBox != HotTrackItemBox)
1551                     {
1552                         ChangeCheckItemHotTrack(infoPtr,
1553                                                 HotTrackItem,
1554                                                 HotTrackItemBox);
1555                     }
1556                 }
1557                 else
1558                 {
1559                     ChangeCheckItemHotTrack(infoPtr,
1560                                             NULL,
1561                                             0);
1562                 }
1563 
1564                 tme.cbSize = sizeof(tme);
1565                 tme.dwFlags = TME_LEAVE;
1566                 tme.hwndTrack = hwnd;
1567                 tme.dwHoverTime = infoPtr->HoverTime;
1568 
1569                 TrackMouseEvent(&tme);
1570             }
1571 #endif
1572 
1573             if (hWndCapture == hwnd && infoPtr->FocusedCheckItem != NULL)
1574             {
1575                 PCHECKITEM PtItem;
1576                 UINT PtItemBox;
1577                 UINT OldPushed;
1578 
1579                 PtItem = PtToCheckItemBox(infoPtr,
1580                                           &pt,
1581                                           &PtItemBox,
1582                                           &InCheckBox);
1583 
1584                 OldPushed = infoPtr->FocusedPushed;
1585                 infoPtr->FocusedPushed = InCheckBox && infoPtr->FocusedCheckItem == PtItem &&
1586                                          infoPtr->FocusedCheckItemBox == PtItemBox;
1587 
1588                 if (OldPushed != infoPtr->FocusedPushed)
1589                 {
1590                     UpdateCheckItemBox(infoPtr,
1591                                        infoPtr->FocusedCheckItem,
1592                                        infoPtr->FocusedCheckItemBox);
1593                 }
1594             }
1595 
1596             break;
1597         }
1598 
1599         case WM_VSCROLL:
1600         {
1601             SCROLLINFO ScrollInfo;
1602 
1603             ScrollInfo.cbSize = sizeof(ScrollInfo);
1604             ScrollInfo.fMask = SIF_RANGE | SIF_POS;
1605 
1606             if (GetScrollInfo(hwnd,
1607                               SB_VERT,
1608                               &ScrollInfo))
1609             {
1610                 INT OldPos = ScrollInfo.nPos;
1611 
1612                 switch (LOWORD(wParam))
1613                 {
1614                     case SB_BOTTOM:
1615                         ScrollInfo.nPos = ScrollInfo.nMax;
1616                         break;
1617 
1618                     case SB_LINEDOWN:
1619                         if (ScrollInfo.nPos < ScrollInfo.nMax)
1620                         {
1621                             ScrollInfo.nPos++;
1622                         }
1623                         break;
1624 
1625                     case SB_LINEUP:
1626                         if (ScrollInfo.nPos > 0)
1627                         {
1628                             ScrollInfo.nPos--;
1629                         }
1630                         break;
1631 
1632                     case SB_PAGEDOWN:
1633                     {
1634                         RECT rcClient;
1635                         INT ScrollLines;
1636 
1637                         /* don't use ScrollInfo.nPage because we should only scroll
1638                            down by the number of completely visible list entries.
1639                            nPage however also includes the partly cropped list
1640                            item at the bottom of the control */
1641 
1642                         GetClientRect(hwnd,
1643                                       &rcClient);
1644 
1645                         ScrollLines = max(1,
1646                                           (rcClient.bottom - rcClient.top) / infoPtr->ItemHeight);
1647 
1648                         if (ScrollInfo.nPos + ScrollLines <= ScrollInfo.nMax)
1649                         {
1650                             ScrollInfo.nPos += ScrollLines;
1651                         }
1652                         else
1653                         {
1654                             ScrollInfo.nPos = ScrollInfo.nMax;
1655                         }
1656                         break;
1657                     }
1658 
1659                     case SB_PAGEUP:
1660                     {
1661                         RECT rcClient;
1662                         INT ScrollLines;
1663 
1664                         /* don't use ScrollInfo.nPage because we should only scroll
1665                            down by the number of completely visible list entries.
1666                            nPage however also includes the partly cropped list
1667                            item at the bottom of the control */
1668 
1669                         GetClientRect(hwnd,
1670                                       &rcClient);
1671 
1672                         ScrollLines = max(1,
1673                                           (rcClient.bottom - rcClient.top) / infoPtr->ItemHeight);
1674 
1675                         if (ScrollInfo.nPos >= ScrollLines)
1676                         {
1677                             ScrollInfo.nPos -= ScrollLines;
1678                         }
1679                         else
1680                         {
1681                             ScrollInfo.nPos = 0;
1682                         }
1683                         break;
1684                     }
1685 
1686                     case SB_THUMBPOSITION:
1687                     case SB_THUMBTRACK:
1688                     {
1689                         ScrollInfo.nPos = HIWORD(wParam);
1690                         break;
1691                     }
1692 
1693                     case SB_TOP:
1694                         ScrollInfo.nPos = 0;
1695                         break;
1696                 }
1697 
1698                 if (OldPos != ScrollInfo.nPos)
1699                 {
1700                     ScrollInfo.fMask = SIF_POS;
1701 
1702                     ScrollInfo.nPos = SetScrollInfo(hwnd,
1703                                                     SB_VERT,
1704                                                     &ScrollInfo,
1705                                                     TRUE);
1706 
1707                     if (OldPos != ScrollInfo.nPos)
1708                     {
1709                         ScrollWindowEx(hwnd,
1710                                        0,
1711                                        (OldPos - ScrollInfo.nPos) * infoPtr->ItemHeight,
1712                                        NULL,
1713                                        NULL,
1714                                        NULL,
1715                                        NULL,
1716                                        SW_INVALIDATE | SW_SCROLLCHILDREN);
1717 
1718                         RedrawWindow(hwnd,
1719                                      NULL,
1720                                      NULL,
1721                                      RDW_ERASE | RDW_UPDATENOW | RDW_NOCHILDREN);
1722                     }
1723                 }
1724             }
1725             break;
1726         }
1727 
1728         case CLM_ADDITEM:
1729         {
1730             INT Index = -1;
1731             PCHECKITEM Item = AddCheckItem(infoPtr,
1732                                            (LPWSTR)lParam,
1733                                            CIS_NONE,
1734                                            (ACCESS_MASK)wParam,
1735                                            &Index);
1736             if (Item != NULL)
1737             {
1738                 UpdateControl(infoPtr);
1739                 Ret = (LRESULT)Index;
1740             }
1741             else
1742             {
1743                 Ret = (LRESULT)-1;
1744             }
1745             break;
1746         }
1747 
1748         case CLM_DELITEM:
1749         {
1750             PCHECKITEM Item = FindCheckItemByIndex(infoPtr,
1751                                                    wParam);
1752             if (Item != NULL)
1753             {
1754                 Ret = DeleteCheckItem(infoPtr,
1755                                       Item);
1756                 if (Ret)
1757                 {
1758                     UpdateControl(infoPtr);
1759                 }
1760             }
1761             else
1762             {
1763                 Ret = FALSE;
1764             }
1765             break;
1766         }
1767 
1768         case CLM_SETITEMSTATE:
1769         {
1770             PCHECKITEM Item = FindCheckItemByIndex(infoPtr,
1771                                                    wParam);
1772             if (Item != NULL)
1773             {
1774                 DWORD OldState = Item->State;
1775                 Item->State = (DWORD)lParam & CIS_MASK;
1776 
1777                 if (Item->State != OldState)
1778                 {
1779                     /* revert the focus if the currently focused item is about
1780                        to be disabled */
1781                     if (Item == infoPtr->FocusedCheckItem &&
1782                         (Item->State & CIS_DISABLED))
1783                     {
1784                         if (infoPtr->FocusedCheckItemBox == CLB_DENY)
1785                         {
1786                             if (Item->State & CIS_DENYDISABLED)
1787                             {
1788                                 infoPtr->FocusedCheckItem = NULL;
1789                             }
1790                         }
1791                         else
1792                         {
1793                             if (Item->State & CIS_ALLOWDISABLED)
1794                             {
1795                                 infoPtr->FocusedCheckItem = NULL;
1796                             }
1797                         }
1798                     }
1799 
1800                     UpdateControl(infoPtr);
1801                 }
1802                 Ret = TRUE;
1803             }
1804             break;
1805         }
1806 
1807         case CLM_GETITEMCOUNT:
1808         {
1809             Ret = infoPtr->CheckItemCount;
1810             break;
1811         }
1812 
1813         case CLM_CLEAR:
1814         {
1815             ClearCheckItems(infoPtr);
1816             UpdateControl(infoPtr);
1817             break;
1818         }
1819 
1820         case CLM_SETCHECKBOXCOLUMN:
1821         {
1822             infoPtr->CheckBoxLeft[wParam != CLB_DENY] = (INT)lParam;
1823             UpdateControl(infoPtr);
1824             Ret = 1;
1825             break;
1826         }
1827 
1828         case CLM_GETCHECKBOXCOLUMN:
1829         {
1830             Ret = (LRESULT)infoPtr->CheckBoxLeft[wParam != CLB_DENY];
1831             break;
1832         }
1833 
1834         case CLM_CLEARCHECKBOXES:
1835         {
1836             Ret = (LRESULT)ClearCheckBoxes(infoPtr);
1837             if (Ret)
1838             {
1839                 UpdateControl(infoPtr);
1840             }
1841             break;
1842         }
1843 
1844         case CLM_ENABLEQUICKSEARCH:
1845         {
1846             if (wParam == 0)
1847             {
1848                 EscapeQuickSearch(infoPtr);
1849             }
1850             infoPtr->QuickSearchEnabled = (wParam != 0);
1851             break;
1852         }
1853 
1854         case CLM_SETQUICKSEARCH_TIMEOUT_RESET:
1855         {
1856             infoPtr->QuickSearchResetDelay = (UINT)wParam;
1857             break;
1858         }
1859 
1860         case CLM_SETQUICKSEARCH_TIMEOUT_SETFOCUS:
1861         {
1862             infoPtr->QuickSearchSetFocusDelay = (UINT)wParam;
1863             break;
1864         }
1865 
1866         case CLM_FINDITEMBYACCESSMASK:
1867         {
1868             Ret = (LRESULT)FindCheckItemIndexByAccessMask(infoPtr,
1869                                                           (ACCESS_MASK)wParam);
1870             break;
1871         }
1872 
1873         case WM_SETFONT:
1874         {
1875             Ret = (LRESULT)RetChangeControlFont(infoPtr,
1876                                                 (HFONT)wParam,
1877                                                 (BOOL)LOWORD(lParam));
1878             break;
1879         }
1880 
1881         case WM_GETFONT:
1882         {
1883             Ret = (LRESULT)infoPtr->hFont;
1884             break;
1885         }
1886 
1887         case WM_STYLECHANGED:
1888         {
1889             if (wParam == (WPARAM)GWL_STYLE)
1890             {
1891                 UpdateControl(infoPtr);
1892             }
1893             break;
1894         }
1895 
1896         case WM_ENABLE:
1897         {
1898             EscapeQuickSearch(infoPtr);
1899 
1900             UpdateControl(infoPtr);
1901             break;
1902         }
1903 
1904         case WM_MOUSEWHEEL:
1905         {
1906             SHORT ScrollDelta;
1907             UINT ScrollLines = 3;
1908 
1909             SystemParametersInfo(SPI_GETWHEELSCROLLLINES,
1910                                  0,
1911                                  &ScrollLines,
1912                                  0);
1913             ScrollDelta = 0 - (SHORT)HIWORD(wParam);
1914 
1915             if (ScrollLines != 0 &&
1916                 abs(ScrollDelta) >= WHEEL_DELTA)
1917             {
1918                 SCROLLINFO ScrollInfo;
1919 
1920                 ScrollInfo.cbSize = sizeof(ScrollInfo);
1921                 ScrollInfo.fMask = SIF_RANGE | SIF_POS;
1922 
1923                 if (GetScrollInfo(hwnd,
1924                                   SB_VERT,
1925                                   &ScrollInfo))
1926                 {
1927                     INT OldPos = ScrollInfo.nPos;
1928 
1929                     ScrollInfo.nPos += (ScrollDelta / WHEEL_DELTA) * ScrollLines;
1930                     if (ScrollInfo.nPos < 0)
1931                         ScrollInfo.nPos = 0;
1932                     else if (ScrollInfo.nPos > ScrollInfo.nMax)
1933                         ScrollInfo.nPos = ScrollInfo.nMax;
1934 
1935                     if (OldPos != ScrollInfo.nPos)
1936                     {
1937                         ScrollInfo.fMask = SIF_POS;
1938 
1939                         ScrollInfo.nPos = SetScrollInfo(hwnd,
1940                                                         SB_VERT,
1941                                                         &ScrollInfo,
1942                                                         TRUE);
1943 
1944                         if (OldPos != ScrollInfo.nPos)
1945                         {
1946                             ScrollWindowEx(hwnd,
1947                                            0,
1948                                            (OldPos - ScrollInfo.nPos) * infoPtr->ItemHeight,
1949                                            NULL,
1950                                            NULL,
1951                                            NULL,
1952                                            NULL,
1953                                            SW_INVALIDATE | SW_SCROLLCHILDREN);
1954 
1955                             RedrawWindow(hwnd,
1956                                          NULL,
1957                                          NULL,
1958                                          RDW_ERASE | RDW_UPDATENOW | RDW_NOCHILDREN);
1959                         }
1960                     }
1961                 }
1962             }
1963             break;
1964         }
1965 
1966         case WM_SETFOCUS:
1967         {
1968             infoPtr->HasFocus = TRUE;
1969 
1970             if (infoPtr->FocusedCheckItem == NULL)
1971             {
1972                 BOOL Shift = GetKeyState(VK_SHIFT) & 0x8000;
1973                 infoPtr->FocusedCheckItem = FindEnabledCheckBox(infoPtr,
1974                                                                 Shift,
1975                                                                 &infoPtr->FocusedCheckItemBox);
1976             }
1977             if (infoPtr->FocusedCheckItem != NULL)
1978             {
1979                 MakeCheckItemVisible(infoPtr,
1980                                      infoPtr->FocusedCheckItem);
1981 
1982                 UpdateCheckItem(infoPtr,
1983                                 infoPtr->FocusedCheckItem);
1984             }
1985             break;
1986         }
1987 
1988         case WM_KILLFOCUS:
1989         {
1990             EscapeQuickSearch(infoPtr);
1991 
1992             infoPtr->HasFocus = FALSE;
1993             if (infoPtr->FocusedCheckItem != NULL)
1994             {
1995                 infoPtr->FocusedPushed = FALSE;
1996 
1997                 UpdateCheckItem(infoPtr,
1998                                 infoPtr->FocusedCheckItem);
1999             }
2000             break;
2001         }
2002 
2003         case WM_LBUTTONDBLCLK:
2004         case WM_LBUTTONDOWN:
2005         case WM_MBUTTONDOWN:
2006         case WM_RBUTTONDOWN:
2007         {
2008             if (IsWindowEnabled(hwnd))
2009             {
2010                 PCHECKITEM NewFocus;
2011                 UINT NewFocusBox = 0;
2012                 BOOL InCheckBox;
2013                 POINT pt;
2014                 BOOL ChangeFocus, Capture = FALSE;
2015 
2016                 pt.x = (LONG)LOWORD(lParam);
2017                 pt.y = (LONG)HIWORD(lParam);
2018 
2019                 NewFocus = PtToCheckItemBox(infoPtr,
2020                                             &pt,
2021                                             &NewFocusBox,
2022                                             &InCheckBox);
2023                 if (NewFocus != NULL)
2024                 {
2025                     if (NewFocus->State & ((NewFocusBox != CLB_DENY) ? CIS_ALLOWDISABLED : CIS_DENYDISABLED))
2026                     {
2027                         /* the user clicked on a disabled checkbox, try to set
2028                            the focus to the other one or not change it at all */
2029 
2030                         InCheckBox = FALSE;
2031 
2032                         ChangeFocus = ((NewFocus->State & CIS_DISABLED) != CIS_DISABLED);
2033                         if (ChangeFocus)
2034                         {
2035                             NewFocusBox = ((NewFocusBox != CLB_DENY) ? CLB_DENY : CLB_ALLOW);
2036                         }
2037                     }
2038                     else
2039                     {
2040                         ChangeFocus = TRUE;
2041                     }
2042 
2043                     if (InCheckBox && ChangeFocus && GetCapture() == NULL &&
2044                         (uMsg == WM_LBUTTONDOWN || uMsg == WM_LBUTTONDBLCLK))
2045                     {
2046                         infoPtr->FocusedPushed = TRUE;
2047                         Capture = TRUE;
2048                     }
2049                 }
2050                 else
2051                 {
2052                     ChangeFocus = TRUE;
2053                 }
2054 
2055                 if (ChangeFocus)
2056                 {
2057                     if (infoPtr->QuickSearchEnabled && infoPtr->QuickSearchHitItem != NULL &&
2058                         infoPtr->QuickSearchHitItem != NewFocus)
2059                     {
2060                         EscapeQuickSearch(infoPtr);
2061                     }
2062 
2063                     ChangeCheckItemFocus(infoPtr,
2064                                          NewFocus,
2065                                          NewFocusBox);
2066                 }
2067 
2068                 if (!infoPtr->HasFocus)
2069                 {
2070                     SetFocus(hwnd);
2071                 }
2072 
2073                 if (Capture)
2074                 {
2075                     SetCapture(hwnd);
2076                 }
2077             }
2078             break;
2079         }
2080 
2081         case WM_LBUTTONUP:
2082         {
2083             if (GetCapture() == hwnd)
2084             {
2085                 if (infoPtr->FocusedCheckItem != NULL && infoPtr->FocusedPushed)
2086                 {
2087                     PCHECKITEM PtItem;
2088                     UINT PtItemBox;
2089                     BOOL InCheckBox;
2090                     POINT pt;
2091 
2092                     pt.x = (LONG)LOWORD(lParam);
2093                     pt.y = (LONG)HIWORD(lParam);
2094 
2095                     infoPtr->FocusedPushed = FALSE;
2096 
2097                     PtItem = PtToCheckItemBox(infoPtr,
2098                                               &pt,
2099                                               &PtItemBox,
2100                                               &InCheckBox);
2101 
2102                     if (PtItem == infoPtr->FocusedCheckItem && InCheckBox &&
2103                         PtItemBox == infoPtr->FocusedCheckItemBox)
2104                     {
2105                         UINT OtherBox = ((PtItemBox == CLB_ALLOW) ? CLB_DENY : CLB_ALLOW);
2106                         DWORD OtherStateMask = ((OtherBox == CLB_ALLOW) ?
2107                                                 (CIS_ALLOW | CIS_ALLOWDISABLED) :
2108                                                 (CIS_DENY | CIS_DENYDISABLED));
2109                         DWORD OtherStateOld = PtItem->State & OtherStateMask;
2110                         if (ChangeCheckBox(infoPtr,
2111                                            PtItem,
2112                                            PtItemBox) &&
2113                             ((PtItem->State & OtherStateMask) != OtherStateOld))
2114                         {
2115                             UpdateCheckItemBox(infoPtr,
2116                                                infoPtr->FocusedCheckItem,
2117                                                OtherBox);
2118                         }
2119                     }
2120 
2121                     UpdateCheckItemBox(infoPtr,
2122                                        infoPtr->FocusedCheckItem,
2123                                        infoPtr->FocusedCheckItemBox);
2124                 }
2125 
2126                 ReleaseCapture();
2127             }
2128             break;
2129         }
2130 
2131         case WM_KEYDOWN:
2132         {
2133             switch (wParam)
2134             {
2135                 case VK_SPACE:
2136                 {
2137                     if (GetCapture() == NULL &&
2138                         !QuickSearchFindHit(infoPtr,
2139                                             L' '))
2140                     {
2141                         if (infoPtr->FocusedCheckItem != NULL &&
2142                             (infoPtr->QuickSearchHitItem == NULL ||
2143                              infoPtr->QuickSearchHitItem == infoPtr->FocusedCheckItem))
2144                         {
2145                             UINT OldPushed = infoPtr->FocusedPushed;
2146                             infoPtr->FocusedPushed = TRUE;
2147 
2148                             if (infoPtr->FocusedPushed != OldPushed)
2149                             {
2150                                 MakeCheckItemVisible(infoPtr,
2151                                                      infoPtr->FocusedCheckItem);
2152 
2153                                 UpdateCheckItemBox(infoPtr,
2154                                                    infoPtr->FocusedCheckItem,
2155                                                    infoPtr->FocusedCheckItemBox);
2156                             }
2157                         }
2158                     }
2159                     break;
2160                 }
2161 
2162                 case VK_RETURN:
2163                 {
2164                     if (GetCapture() == NULL &&
2165                         !QuickSearchFindHit(infoPtr,
2166                                             L'\n'))
2167                     {
2168                         if (infoPtr->FocusedCheckItem != NULL &&
2169                             infoPtr->QuickSearchHitItem == NULL)
2170                         {
2171                             UINT OtherBox;
2172                             DWORD OtherStateMask;
2173                             DWORD OtherStateOld;
2174 
2175                             MakeCheckItemVisible(infoPtr,
2176                                                  infoPtr->FocusedCheckItem);
2177 
2178                             OtherBox = ((infoPtr->FocusedCheckItemBox == CLB_ALLOW) ? CLB_DENY : CLB_ALLOW);
2179                             OtherStateMask = ((OtherBox == CLB_ALLOW) ?
2180                                               (CIS_ALLOW | CIS_ALLOWDISABLED) :
2181                                               (CIS_DENY | CIS_DENYDISABLED));
2182                             OtherStateOld = infoPtr->FocusedCheckItem->State & OtherStateMask;
2183                             if (ChangeCheckBox(infoPtr,
2184                                                infoPtr->FocusedCheckItem,
2185                                                infoPtr->FocusedCheckItemBox))
2186                             {
2187                                 UpdateCheckItemBox(infoPtr,
2188                                                    infoPtr->FocusedCheckItem,
2189                                                    infoPtr->FocusedCheckItemBox);
2190                                 if ((infoPtr->FocusedCheckItem->State & OtherStateMask) != OtherStateOld)
2191                                 {
2192                                     UpdateCheckItemBox(infoPtr,
2193                                                        infoPtr->FocusedCheckItem,
2194                                                        OtherBox);
2195                                 }
2196                             }
2197                         }
2198                     }
2199                     break;
2200                 }
2201 
2202                 case VK_TAB:
2203                 {
2204                     if (GetCapture() == NULL)
2205                     {
2206                         PCHECKITEM NewFocus;
2207                         UINT NewFocusBox = 0;
2208                         BOOL Shift = GetKeyState(VK_SHIFT) & 0x8000;
2209 
2210                         EscapeQuickSearch(infoPtr);
2211 
2212                         NewFocus = FindEnabledCheckBox(infoPtr,
2213                                                        Shift,
2214                                                        &NewFocusBox);
2215 
2216                         /* update the UI status */
2217                         SendMessage(GetAncestor(hwnd,
2218                                                 GA_PARENT),
2219                                     WM_CHANGEUISTATE,
2220                                     MAKEWPARAM(UIS_INITIALIZE,
2221                                                0),
2222                                     0);
2223 
2224                         ChangeCheckItemFocus(infoPtr,
2225                                              NewFocus,
2226                                              NewFocusBox);
2227                     }
2228                     break;
2229                 }
2230 
2231                 default:
2232                 {
2233                     goto HandleDefaultMessage;
2234                 }
2235             }
2236             break;
2237         }
2238 
2239         case WM_KEYUP:
2240         {
2241             if (wParam == VK_SPACE && IsWindowEnabled(hwnd) &&
2242                 infoPtr->FocusedCheckItem != NULL &&
2243                 infoPtr->FocusedPushed)
2244             {
2245                 UINT OtherBox = ((infoPtr->FocusedCheckItemBox == CLB_ALLOW) ? CLB_DENY : CLB_ALLOW);
2246                 DWORD OtherStateMask = ((OtherBox == CLB_ALLOW) ?
2247                                         (CIS_ALLOW | CIS_ALLOWDISABLED) :
2248                                         (CIS_DENY | CIS_DENYDISABLED));
2249                 DWORD OtherStateOld = infoPtr->FocusedCheckItem->State & OtherStateMask;
2250 
2251                 infoPtr->FocusedPushed = FALSE;
2252 
2253                 if (ChangeCheckBox(infoPtr,
2254                                    infoPtr->FocusedCheckItem,
2255                                    infoPtr->FocusedCheckItemBox))
2256                 {
2257                     UpdateCheckItemBox(infoPtr,
2258                                        infoPtr->FocusedCheckItem,
2259                                        infoPtr->FocusedCheckItemBox);
2260 
2261                     if ((infoPtr->FocusedCheckItem->State & OtherStateMask) != OtherStateOld)
2262                     {
2263                         UpdateCheckItemBox(infoPtr,
2264                                            infoPtr->FocusedCheckItem,
2265                                            OtherBox);
2266                     }
2267                 }
2268             }
2269             break;
2270         }
2271 
2272         case WM_GETDLGCODE:
2273         {
2274             INT virtKey;
2275 
2276             Ret = 0;
2277             virtKey = (lParam != 0 ? (INT)((LPMSG)lParam)->wParam : 0);
2278             switch (virtKey)
2279             {
2280                 case VK_RETURN:
2281                 {
2282                     if (infoPtr->QuickSearchEnabled && infoPtr->QuickSearchHitItem != NULL)
2283                     {
2284                         Ret |= DLGC_WANTCHARS | DLGC_WANTMESSAGE;
2285                     }
2286                     else
2287                     {
2288                         Ret |= DLGC_WANTMESSAGE;
2289                     }
2290                     break;
2291                 }
2292 
2293                 case VK_TAB:
2294                 {
2295                     UINT CheckBox;
2296                     BOOL EnabledBox;
2297                     BOOL Shift = GetKeyState(VK_SHIFT) & 0x8000;
2298 
2299                     EnabledBox = FindEnabledCheckBox(infoPtr,
2300                                                      Shift,
2301                                                      &CheckBox) != NULL;
2302                     Ret |= (EnabledBox ? DLGC_WANTTAB : DLGC_WANTCHARS);
2303                     break;
2304                 }
2305 
2306                 default:
2307                 {
2308                     if (infoPtr->QuickSearchEnabled)
2309                     {
2310                         Ret |= DLGC_WANTCHARS;
2311                     }
2312                     break;
2313                 }
2314             }
2315             break;
2316         }
2317 
2318         case WM_CHAR:
2319         {
2320             QuickSearchFindHit(infoPtr,
2321                                (WCHAR)wParam);
2322             break;
2323         }
2324 
2325         case WM_SYSCOLORCHANGE:
2326         {
2327             infoPtr->TextColor[0] = GetSysColor(COLOR_GRAYTEXT);
2328             infoPtr->TextColor[1] = GetSysColor(COLOR_WINDOWTEXT);
2329             break;
2330         }
2331 
2332 #if SUPPORT_UXTHEME
2333         case WM_MOUSELEAVE:
2334         {
2335             if (infoPtr->HoveredCheckItem != NULL)
2336             {
2337                 /* reset and repaint the hovered check item box */
2338                 ChangeCheckItemHotTrack(infoPtr,
2339                                         NULL,
2340                                         0);
2341             }
2342             break;
2343         }
2344 
2345         case WM_THEMECHANGED:
2346         {
2347             if (infoPtr->ThemeHandle != NULL)
2348             {
2349                 CloseThemeData(infoPtr->ThemeHandle);
2350                 infoPtr->ThemeHandle = NULL;
2351             }
2352             if (IsAppThemed())
2353             {
2354                 infoPtr->ThemeHandle = OpenThemeData(infoPtr->hSelf,
2355                                                      L"BUTTON");
2356             }
2357             break;
2358         }
2359 #endif
2360 
2361         case WM_SETTINGCHANGE:
2362         {
2363             DWORD OldCaretWidth = infoPtr->CaretWidth;
2364 
2365 #if SUPPORT_UXTHEME
2366             /* update the hover time */
2367             if (!SystemParametersInfo(SPI_GETMOUSEHOVERTIME,
2368                                       0,
2369                                       &infoPtr->HoverTime,
2370                                       0))
2371             {
2372                 infoPtr->HoverTime = HOVER_DEFAULT;
2373             }
2374 #endif
2375 
2376             /* update the caret */
2377             if (!SystemParametersInfo(SPI_GETCARETWIDTH,
2378                                       0,
2379                                       &infoPtr->CaretWidth,
2380                                       0))
2381             {
2382                 infoPtr->CaretWidth = 2;
2383             }
2384             if (OldCaretWidth != infoPtr->CaretWidth && infoPtr->ShowingCaret)
2385             {
2386                 DestroyCaret();
2387                 CreateCaret(hwnd,
2388                             NULL,
2389                             infoPtr->CaretWidth,
2390                             infoPtr->ItemHeight - (2 * CI_TEXT_MARGIN_HEIGHT));
2391             }
2392             break;
2393         }
2394 
2395         case WM_SIZE:
2396         {
2397             UpdateControl(infoPtr);
2398             break;
2399         }
2400 
2401         case WM_UPDATEUISTATE:
2402         {
2403             DWORD OldUIState = infoPtr->UIState;
2404 
2405             switch (LOWORD(wParam))
2406             {
2407                 case UIS_SET:
2408                     infoPtr->UIState |= HIWORD(wParam);
2409                     break;
2410 
2411                 case UIS_CLEAR:
2412                     infoPtr->UIState &= ~(HIWORD(wParam));
2413                     break;
2414             }
2415 
2416             if (OldUIState != infoPtr->UIState)
2417             {
2418                 if (infoPtr->FocusedCheckItem != NULL)
2419                 {
2420                     UpdateCheckItemBox(infoPtr,
2421                                        infoPtr->FocusedCheckItem,
2422                                        infoPtr->FocusedCheckItemBox);
2423                 }
2424             }
2425             break;
2426         }
2427 
2428         case WM_TIMER:
2429         {
2430             switch (wParam)
2431             {
2432                 case TIMER_ID_SETHITFOCUS:
2433                 {
2434                     /* kill the timer */
2435                     KillTimer(hwnd,
2436                               wParam);
2437 
2438                     if (infoPtr->QuickSearchEnabled && infoPtr->QuickSearchHitItem != NULL)
2439                     {
2440                         /* change the focus to the hit item, this item has to have
2441                            at least one enabled checkbox! */
2442                         ChangeCheckItemFocus(infoPtr,
2443                                              infoPtr->QuickSearchHitItem,
2444                                              ((!(infoPtr->QuickSearchHitItem->State & CIS_ALLOWDISABLED)) ? CLB_ALLOW : CLB_DENY));
2445 
2446                         /* start the timer to reset quicksearch */
2447                         if (infoPtr->QuickSearchResetDelay != 0)
2448                         {
2449                             SetTimer(hwnd,
2450                                      TIMER_ID_RESETQUICKSEARCH,
2451                                      infoPtr->QuickSearchResetDelay,
2452                                      NULL);
2453                         }
2454                     }
2455                     break;
2456                 }
2457                 case TIMER_ID_RESETQUICKSEARCH:
2458                 {
2459                     /* kill the timer */
2460                     KillTimer(hwnd,
2461                               wParam);
2462 
2463                     /* escape quick search */
2464                     EscapeQuickSearch(infoPtr);
2465                     break;
2466                 }
2467             }
2468             break;
2469         }
2470 
2471         case WM_CREATE:
2472         {
2473             infoPtr = HeapAlloc(GetProcessHeap(),
2474                                 0,
2475                                 sizeof(CHECKLISTWND));
2476             if (infoPtr != NULL)
2477             {
2478                 RECT rcClient;
2479 
2480                 infoPtr->hSelf = hwnd;
2481                 infoPtr->hNotify = ((LPCREATESTRUCTW)lParam)->hwndParent;
2482 
2483                 SetWindowLongPtr(hwnd,
2484                                  0,
2485                                  (DWORD_PTR)infoPtr);
2486 
2487                 infoPtr->CheckItemListHead = NULL;
2488                 infoPtr->CheckItemCount = 0;
2489 
2490                 if (!SystemParametersInfo(SPI_GETCARETWIDTH,
2491                                           0,
2492                                           &infoPtr->CaretWidth,
2493                                           0))
2494                 {
2495                     infoPtr->CaretWidth = 2;
2496                 }
2497                 infoPtr->ItemHeight = 10;
2498                 infoPtr->ShowingCaret = FALSE;
2499 
2500                 infoPtr->HasFocus = FALSE;
2501                 infoPtr->FocusedCheckItem = NULL;
2502                 infoPtr->FocusedCheckItemBox = 0;
2503                 infoPtr->FocusedPushed = FALSE;
2504 
2505                 infoPtr->TextColor[0] = GetSysColor(COLOR_GRAYTEXT);
2506                 infoPtr->TextColor[1] = GetSysColor(COLOR_WINDOWTEXT);
2507 
2508                 GetClientRect(hwnd,
2509                               &rcClient);
2510 
2511                 infoPtr->CheckBoxLeft[0] = rcClient.right - 30;
2512                 infoPtr->CheckBoxLeft[1] = rcClient.right - 15;
2513 
2514                 infoPtr->QuickSearchEnabled = FALSE;
2515                 infoPtr->QuickSearchText[0] = L'\0';
2516 
2517                 infoPtr->QuickSearchSetFocusDelay = DEFAULT_QUICKSEARCH_SETFOCUS_DELAY;
2518                 infoPtr->QuickSearchResetDelay = DEFAULT_QUICKSEARCH_RESET_DELAY;
2519 
2520 #if SUPPORT_UXTHEME
2521                 infoPtr->HoveredCheckItem = NULL;
2522                 infoPtr->HoveredCheckItemBox = 0;
2523                 if (!SystemParametersInfo(SPI_GETMOUSEHOVERTIME,
2524                                           0,
2525                                           &infoPtr->HoverTime,
2526                                           0))
2527                 {
2528                     infoPtr->HoverTime = HOVER_DEFAULT;
2529                 }
2530 
2531                 if (IsAppThemed())
2532                 {
2533                     infoPtr->ThemeHandle = OpenThemeData(infoPtr->hSelf,
2534                                                          L"BUTTON");
2535                 }
2536                 else
2537                 {
2538                     infoPtr->ThemeHandle = NULL;
2539                 }
2540 #endif
2541 
2542                 infoPtr->UIState = SendMessage(hwnd,
2543                                                WM_QUERYUISTATE,
2544                                                0,
2545                                                0);
2546             }
2547             else
2548             {
2549                 Ret = -1;
2550             }
2551             break;
2552         }
2553 
2554         case WM_DESTROY:
2555         {
2556             if (infoPtr->ShowingCaret)
2557             {
2558                 DestroyCaret();
2559             }
2560 
2561             ClearCheckItems(infoPtr);
2562 
2563 #if SUPPORT_UXTHEME
2564             if (infoPtr->ThemeHandle != NULL)
2565             {
2566                 CloseThemeData(infoPtr->ThemeHandle);
2567             }
2568 #endif
2569 
2570             HeapFree(GetProcessHeap(),
2571                      0,
2572                      infoPtr);
2573             SetWindowLongPtr(hwnd,
2574                              0,
2575                              (DWORD_PTR)NULL);
2576             break;
2577         }
2578 
2579         default:
2580         {
2581 HandleDefaultMessage:
2582             Ret = DefWindowProc(hwnd,
2583                                 uMsg,
2584                                 wParam,
2585                                 lParam);
2586             break;
2587         }
2588     }
2589 
2590     return Ret;
2591 }
2592 
2593 BOOL
RegisterCheckListControl(IN HINSTANCE hInstance)2594 RegisterCheckListControl(IN HINSTANCE hInstance)
2595 {
2596     WNDCLASS wc;
2597 
2598     wc.style = CS_DBLCLKS;
2599     wc.lpfnWndProc = CheckListWndProc;
2600     wc.cbClsExtra = 0;
2601     wc.cbWndExtra = sizeof(PCHECKLISTWND);
2602     wc.hInstance = hInstance;
2603     wc.hIcon = NULL;
2604     wc.hCursor = LoadCursor(NULL,
2605                             (LPWSTR)IDC_ARROW);
2606     wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
2607     wc.lpszMenuName = NULL;
2608     wc.lpszClassName = szCheckListWndClass;
2609 
2610     return RegisterClass(&wc) != 0;
2611 }
2612 
2613 VOID
UnregisterCheckListControl(HINSTANCE hInstance)2614 UnregisterCheckListControl(HINSTANCE hInstance)
2615 {
2616     UnregisterClass(szCheckListWndClass,
2617                     hInstance);
2618 }
2619