NewCheckListBox.pas 70 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128
  1. unit NewCheckListBox;
  2. { TNewCheckListBox by Martijn Laan for Inno Setup
  3. Based on TPBCheckListBox by Patrick Brisacier and TCheckListBox by Borland
  4. Group item support, child item support, exclusive item support,
  5. ShowLines support and 'WantTabs mode' by Alex Yackimoff.
  6. Note: TNewCheckListBox uses Items.Objects to store the item state. Don't use
  7. Item.Objects yourself, use ItemObject instead.
  8. }
  9. interface
  10. uses
  11. Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  12. StdCtrls, NewUxTheme;
  13. const
  14. WM_UPDATEUISTATE = $0128;
  15. type
  16. TItemType = (itGroup, itCheck, itRadio);
  17. TCheckBoxState2 = (cb2Normal, cb2Hot, cb2Pressed, cb2Disabled);
  18. TItemState = class (TObject)
  19. public
  20. Enabled: Boolean;
  21. HasInternalChildren: Boolean;
  22. CheckWhenParentChecked: Boolean;
  23. IsLastChild: Boolean;
  24. ItemType: TItemType;
  25. Level: Byte;
  26. Obj: TObject;
  27. State: TCheckBoxState;
  28. SubItem: string;
  29. ThreadCache: set of Byte;
  30. MeasuredHeight: Integer;
  31. ItemFontStyle: TFontStyles;
  32. SubItemFontStyle: TFontStyles;
  33. end;
  34. TCheckItemOperation = (coUncheck, coCheck, coCheckWithChildren);
  35. TEnumChildrenProc = procedure(Index: Integer; HasChildren: Boolean; Ext: Longint) of object;
  36. TNewCheckListBox = class (TCustomListBox)
  37. private
  38. FAccObjectInstance: TObject;
  39. FCaptureIndex: Integer;
  40. FSpaceDown: Boolean;
  41. FCheckHeight: Integer;
  42. FCheckWidth: Integer;
  43. FFormFocusChanged: Boolean;
  44. FFlat: Boolean;
  45. FLastMouseMoveIndex: Integer;
  46. FMinItemHeight: Integer;
  47. FOffset: Integer;
  48. FOnClickCheck: TNotifyEvent;
  49. FRequireRadioSelection: Boolean;
  50. FShowLines: Boolean;
  51. FStateList: TList;
  52. FWantTabs: Boolean;
  53. FThemeData: HTHEME;
  54. FThreadsUpToDate: Boolean;
  55. FHotIndex: Integer;
  56. FDisableItemStateDeletion: Integer;
  57. FWheelAccum: Integer;
  58. FUseRightToLeft: Boolean;
  59. procedure UpdateThemeData(const Close, Open: Boolean);
  60. function CanFocusItem(Item: Integer): Boolean;
  61. function CheckPotentialRadioParents(Index, ALevel: Integer): Boolean;
  62. procedure CMDialogChar(var Message: TCMDialogChar); message CM_DIALOGCHAR;
  63. procedure CMEnter(var Message: TCMEnter); message CM_ENTER;
  64. procedure CMExit(var Message: TCMExit); message CM_EXIT;
  65. procedure CMFocusChanged(var Message: TCMFocusChanged); message CM_FOCUSCHANGED;
  66. procedure CMFontChanged(var Message: TMessage); message CM_FONTCHANGED;
  67. procedure CMWantSpecialKey(var Message: TMessage); message CM_WANTSPECIALKEY;
  68. procedure CNDrawItem(var Message: TWMDrawItem); message CN_DRAWITEM;
  69. procedure EndCapture(Cancel: Boolean);
  70. function AddItem2(AType: TItemType; const ACaption, ASubItem: string;
  71. ALevel: Byte; AChecked, AEnabled, AHasInternalChildren,
  72. ACheckWhenParentChecked: Boolean; AObject: TObject): Integer;
  73. function FindAccel(VK: Word): Integer;
  74. function FindCheckedSibling(const AIndex: Integer): Integer;
  75. function FindNextItem(StartFrom: Integer; GoForward,
  76. SkipUncheckedRadios: Boolean): Integer;
  77. function GetItemState(Index: Integer): TItemState;
  78. procedure InvalidateCheck(Index: Integer);
  79. function RemeasureItem(Index: Integer): Integer;
  80. procedure Toggle(Index: Integer);
  81. procedure UpdateScrollRange;
  82. procedure LBDeleteString(var Message: TMessage); message LB_DELETESTRING;
  83. procedure LBResetContent(var Message: TMessage); message LB_RESETCONTENT;
  84. procedure WMGetDlgCode(var Message: TWMGetDlgCode); message WM_GETDLGCODE;
  85. procedure WMGetObject(var Message: TMessage); message WM_GETOBJECT;
  86. procedure WMKeyDown(var Message: TWMKeyDown); message WM_KEYDOWN;
  87. procedure WMMouseMove(var Message: TWMMouseMove); message WM_MOUSEMOVE;
  88. procedure WMNCHitTest(var Message: TWMNCHitTest); message WM_NCHITTEST;
  89. procedure WMSetFocus(var Message: TWMSetFocus); message WM_SETFOCUS;
  90. procedure WMSize(var Message: TWMSize); message WM_SIZE;
  91. procedure WMThemeChanged(var Message: TMessage); message WM_THEMECHANGED;
  92. procedure WMUpdateUIState(var Message: TMessage); message WM_UPDATEUISTATE;
  93. protected
  94. procedure CreateParams(var Params: TCreateParams); override;
  95. procedure CreateWnd; override;
  96. procedure MeasureItem(Index: Integer; var Height: Integer); override;
  97. procedure DrawItem(Index: Integer; Rect: TRect; State: TOwnerDrawState);
  98. override;
  99. function GetCaption(Index: Integer): String;
  100. function GetChecked(Index: Integer): Boolean;
  101. function GetItemEnabled(Index: Integer): Boolean;
  102. function GetItemFontStyle(Index: Integer): TFontStyles;
  103. function GetLevel(Index: Integer): Byte;
  104. function GetObject(Index: Integer): TObject;
  105. function GetState(Index: Integer): TCheckBoxState;
  106. function GetSubItem(Index: Integer): string;
  107. function GetSubItemFontStyle(Index: Integer): TFontStyles;
  108. procedure KeyDown(var Key: Word; Shift: TShiftState); override;
  109. procedure KeyUp(var Key: Word; Shift: TShiftState); override;
  110. procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
  111. override;
  112. procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
  113. procedure UpdateHotIndex(NewHotIndex: Integer);
  114. procedure CMMouseLeave(var Message: TMessage); message CM_MOUSELEAVE;
  115. procedure SetCaption(Index: Integer; const Value: String);
  116. procedure SetChecked(Index: Integer; const AChecked: Boolean);
  117. procedure SetFlat(Value: Boolean);
  118. procedure SetItemEnabled(Index: Integer; const AEnabled: Boolean);
  119. procedure SetItemFontStyle(Index: Integer; const AItemFontStyle: TFontStyles);
  120. procedure SetObject(Index: Integer; const AObject: TObject);
  121. procedure SetOffset(AnOffset: Integer);
  122. procedure SetShowLines(Value: Boolean);
  123. procedure SetSubItem(Index: Integer; const ASubItem: String);
  124. procedure SetSubItemFontStyle(Index: Integer; const ASubItemFontStyle: TFontStyles);
  125. property ItemStates[Index: Integer]: TItemState read GetItemState;
  126. public
  127. constructor Create(AOwner: TComponent); override;
  128. procedure CreateWindowHandle(const Params: TCreateParams); override;
  129. destructor Destroy; override;
  130. function AddCheckBox(const ACaption, ASubItem: string; ALevel: Byte;
  131. AChecked, AEnabled, AHasInternalChildren, ACheckWhenParentChecked: Boolean;
  132. AObject: TObject): Integer;
  133. function AddGroup(const ACaption, ASubItem: string; ALevel: Byte;
  134. AObject: TObject): Integer;
  135. function AddRadioButton(const ACaption, ASubItem: string;
  136. ALevel: Byte; AChecked, AEnabled: Boolean; AObject: TObject): Integer;
  137. function CheckItem(const Index: Integer; const AOperation: TCheckItemOperation): Boolean;
  138. procedure EnumChildrenOf(Item: Integer; Proc: TEnumChildrenProc; Ext: Longint);
  139. function GetParentOf(Item: Integer): Integer;
  140. procedure UpdateThreads;
  141. property Checked[Index: Integer]: Boolean read GetChecked write SetChecked;
  142. property ItemCaption[Index: Integer]: String read GetCaption write SetCaption;
  143. property ItemEnabled[Index: Integer]: Boolean read GetItemEnabled write SetItemEnabled;
  144. property ItemFontStyle[Index: Integer]: TFontStyles read GetItemFontStyle write SetItemFontStyle;
  145. property ItemLevel[Index: Integer]: Byte read GetLevel;
  146. property ItemObject[Index: Integer]: TObject read GetObject write SetObject;
  147. property ItemSubItem[Index: Integer]: string read GetSubItem write SetSubItem;
  148. property State[Index: Integer]: TCheckBoxState read GetState;
  149. property SubItemFontStyle[Index: Integer]: TFontStyles read GetSubItemFontStyle write SetSubItemFontStyle;
  150. published
  151. property Align;
  152. property Anchors;
  153. property BorderStyle;
  154. property Color;
  155. property Ctl3D;
  156. property DragCursor;
  157. property DragMode;
  158. property Enabled;
  159. property Flat: Boolean read FFlat write SetFlat default False;
  160. property Font;
  161. property Items;
  162. property MinItemHeight: Integer read FMinItemHeight write FMinItemHeight default 16;
  163. property Offset: Integer read FOffset write SetOffset default 4;
  164. property OnClick;
  165. property OnClickCheck: TNotifyEvent read FOnClickCheck write FOnClickCheck;
  166. property OnDblClick;
  167. property OnDragDrop;
  168. property OnDragOver;
  169. property OnEndDrag;
  170. property OnEnter;
  171. property OnExit;
  172. property OnKeyDown;
  173. property OnKeyPress;
  174. property OnKeyUp;
  175. property OnMouseDown;
  176. property OnMouseMove;
  177. property OnMouseUp;
  178. property OnStartDrag;
  179. property ParentColor;
  180. property ParentCtl3D;
  181. property ParentFont;
  182. property ParentShowHint;
  183. property PopupMenu;
  184. property RequireRadioSelection: Boolean read FRequireRadioSelection write FRequireRadioSelection default False;
  185. property ShowHint;
  186. property ShowLines: Boolean read FShowLines write SetShowLines default True;
  187. property TabOrder;
  188. property Visible;
  189. property WantTabs: Boolean read FWantTabs write FWantTabs default False;
  190. end;
  191. procedure Register;
  192. implementation
  193. uses
  194. NewUxTheme.TmSchema, PathFunc, ActiveX, BidiUtils, Types;
  195. const
  196. sRadioCantHaveDisabledChildren = 'Radio item cannot have disabled child items';
  197. OBM_CHECKBOXES = 32759;
  198. WM_CHANGEUISTATE = $0127;
  199. WM_QUERYUISTATE = $0129;
  200. UIS_SET = 1;
  201. UIS_CLEAR = 2;
  202. UIS_INITIALIZE = 3;
  203. UISF_HIDEFOCUS = $1;
  204. UISF_HIDEACCEL = $2;
  205. DT_HIDEPREFIX = $00100000;
  206. OBJID_CLIENT = $FFFFFFFC;
  207. CHILDID_SELF = 0;
  208. ROLE_SYSTEM_OUTLINE = $23;
  209. ROLE_SYSTEM_STATICTEXT = $29;
  210. ROLE_SYSTEM_CHECKBUTTON = $2c;
  211. ROLE_SYSTEM_RADIOBUTTON = $2d;
  212. STATE_SYSTEM_UNAVAILABLE = $1;
  213. STATE_SYSTEM_CHECKED = $10;
  214. STATE_SYSTEM_MIXED = $20;
  215. EVENT_OBJECT_STATECHANGE = $800A;
  216. IID_IUnknown: TGUID = (
  217. D1:$00000000; D2:$0000; D3:$0000; D4:($C0,$00,$00,$00,$00,$00,$00,$46));
  218. IID_IDispatch: TGUID = (
  219. D1:$00020400; D2:$0000; D3:$0000; D4:($C0,$00,$00,$00,$00,$00,$00,$46));
  220. IID_IAccessible: TGUID = (
  221. D1:$618736e0; D2:$3c3d; D3:$11cf; D4:($81,$0c,$00,$aa,$00,$38,$9b,$71));
  222. type
  223. TWinControlAccess = class (TWinControl);
  224. { Note: We have to use TVariantArg for Delphi 2 compat., because D2 passes
  225. Variant parameters by reference (wrong), unlike D3+ which pass
  226. Variant/OleVariant parameters by value }
  227. NewOleVariant = TVariantArg;
  228. NewWideString = Pointer;
  229. TIUnknown = class
  230. public
  231. function QueryInterface(const iid: TIID; var obj): HRESULT; virtual; stdcall; abstract;
  232. function AddRef: Longint; virtual; stdcall; abstract;
  233. function Release: Longint; virtual; stdcall; abstract;
  234. end;
  235. TIDispatch = class(TIUnknown)
  236. public
  237. function GetTypeInfoCount(var ctinfo: Integer): HRESULT; virtual; stdcall; abstract;
  238. function GetTypeInfo(itinfo: Integer; lcid: TLCID; var tinfo: ITypeInfo): HRESULT; virtual; stdcall; abstract;
  239. function GetIDsOfNames(const iid: TIID; rgszNames: POleStrList;
  240. cNames: Integer; lcid: TLCID; rgdispid: PDispIDList): HRESULT; virtual; stdcall; abstract;
  241. function Invoke(dispIDMember: TDispID; const iid: TIID; lcid: TLCID;
  242. flags: Word; var dispParams: TDispParams; varResult: PVariant;
  243. excepInfo: PExcepInfo; argErr: PInteger): HRESULT; virtual; stdcall; abstract;
  244. end;
  245. TIAccessible = class(TIDispatch)
  246. public
  247. function get_accParent(var ppdispParent: IDispatch): HRESULT; virtual; stdcall; abstract;
  248. function get_accChildCount(var pcountChildren: Integer): HRESULT; virtual; stdcall; abstract;
  249. function get_accChild(varChild: NewOleVariant; var ppdispChild: IDispatch): HRESULT; virtual; stdcall; abstract;
  250. function get_accName(varChild: NewOleVariant; var pszName: NewWideString): HRESULT; virtual; stdcall; abstract;
  251. function get_accValue(varChild: NewOleVariant; var pszValue: NewWideString): HRESULT; virtual; stdcall; abstract;
  252. function get_accDescription(varChild: NewOleVariant; var pszDescription: NewWideString): HRESULT; virtual; stdcall; abstract;
  253. function get_accRole(varChild: NewOleVariant; var pvarRole: NewOleVariant): HRESULT; virtual; stdcall; abstract;
  254. function get_accState(varChild: NewOleVariant; var pvarState: NewOleVariant): HRESULT; virtual; stdcall; abstract;
  255. function get_accHelp(varChild: NewOleVariant; var pszHelp: NewWideString): HRESULT; virtual; stdcall; abstract;
  256. function get_accHelpTopic(var pszHelpFile: NewWideString; varChild: NewOleVariant; var pidTopic: Integer): HRESULT; virtual; stdcall; abstract;
  257. function get_accKeyboardShortcut(varChild: NewOleVariant; var pszKeyboardShortcut: NewWideString): HRESULT; virtual; stdcall; abstract;
  258. function get_accFocus(var pvarID: NewOleVariant): HRESULT; virtual; stdcall; abstract;
  259. function get_accSelection(var pvarChildren: NewOleVariant): HRESULT; virtual; stdcall; abstract;
  260. function get_accDefaultAction(varChild: NewOleVariant; var pszDefaultAction: NewWideString): HRESULT; virtual; stdcall; abstract;
  261. function accSelect(flagsSelect: Integer; varChild: NewOleVariant): HRESULT; virtual; stdcall; abstract;
  262. function accLocation(var pxLeft: Integer; var pyTop: Integer; var pcxWidth: Integer;
  263. var pcyHeight: Integer; varChild: NewOleVariant): HRESULT; virtual; stdcall; abstract;
  264. function accNavigate(navDir: Integer; varStart: NewOleVariant; var pvarEnd: NewOleVariant): HRESULT; virtual; stdcall; abstract;
  265. function accHitTest(xLeft: Integer; yTop: Integer; var pvarID: NewOleVariant): HRESULT; virtual; stdcall; abstract;
  266. function accDoDefaultAction(varChild: NewOleVariant): HRESULT; virtual; stdcall; abstract;
  267. function put_accName(varChild: NewOleVariant; const pszName: NewWideString): HRESULT; virtual; stdcall; abstract;
  268. function put_accValue(varChild: NewOleVariant; const pszValue: NewWideString): HRESULT; virtual; stdcall; abstract;
  269. end;
  270. TAccObject = class(TIAccessible)
  271. private
  272. FControl: TNewCheckListBox;
  273. FRefCount: Integer;
  274. FStdAcc: TIAccessible;
  275. { TIUnknown }
  276. function QueryInterface(const iid: TIID; var obj): HRESULT; override;
  277. function AddRef: Longint; override;
  278. function Release: Longint; override;
  279. { TIDispatch }
  280. function GetTypeInfoCount(var ctinfo: Integer): HRESULT; override;
  281. function GetTypeInfo(itinfo: Integer; lcid: TLCID; var tinfo: ITypeInfo): HRESULT; override;
  282. function GetIDsOfNames(const iid: TIID; rgszNames: POleStrList;
  283. cNames: Integer; lcid: TLCID; rgdispid: PDispIDList): HRESULT; override;
  284. function Invoke(dispIDMember: TDispID; const iid: TIID; lcid: TLCID;
  285. flags: Word; var dispParams: TDispParams; varResult: PVariant;
  286. excepInfo: PExcepInfo; argErr: PInteger): HRESULT; override;
  287. { TIAccessible }
  288. function get_accParent(var ppdispParent: IDispatch): HRESULT; override;
  289. function get_accChildCount(var pcountChildren: Integer): HRESULT; override;
  290. function get_accChild(varChild: NewOleVariant; var ppdispChild: IDispatch): HRESULT; override;
  291. function get_accName(varChild: NewOleVariant; var pszName: NewWideString): HRESULT; override;
  292. function get_accValue(varChild: NewOleVariant; var pszValue: NewWideString): HRESULT; override;
  293. function get_accDescription(varChild: NewOleVariant; var pszDescription: NewWideString): HRESULT; override;
  294. function get_accRole(varChild: NewOleVariant; var pvarRole: NewOleVariant): HRESULT; override;
  295. function get_accState(varChild: NewOleVariant; var pvarState: NewOleVariant): HRESULT; override;
  296. function get_accHelp(varChild: NewOleVariant; var pszHelp: NewWideString): HRESULT; override;
  297. function get_accHelpTopic(var pszHelpFile: NewWideString; varChild: NewOleVariant; var pidTopic: Integer): HRESULT; override;
  298. function get_accKeyboardShortcut(varChild: NewOleVariant; var pszKeyboardShortcut: NewWideString): HRESULT; override;
  299. function get_accFocus(var pvarID: NewOleVariant): HRESULT; override;
  300. function get_accSelection(var pvarChildren: NewOleVariant): HRESULT; override;
  301. function get_accDefaultAction(varChild: NewOleVariant; var pszDefaultAction: NewWideString): HRESULT; override;
  302. function accSelect(flagsSelect: Integer; varChild: NewOleVariant): HRESULT; override;
  303. function accLocation(var pxLeft: Integer; var pyTop: Integer; var pcxWidth: Integer;
  304. var pcyHeight: Integer; varChild: NewOleVariant): HRESULT; override;
  305. function accNavigate(navDir: Integer; varStart: NewOleVariant; var pvarEnd: NewOleVariant): HRESULT; override;
  306. function accHitTest(xLeft: Integer; yTop: Integer; var pvarID: NewOleVariant): HRESULT; override;
  307. function accDoDefaultAction(varChild: NewOleVariant): HRESULT; override;
  308. function put_accName(varChild: NewOleVariant; const pszName: NewWideString): HRESULT; override;
  309. function put_accValue(varChild: NewOleVariant; const pszValue: NewWideString): HRESULT; override;
  310. public
  311. constructor Create(AControl: TNewCheckListBox);
  312. destructor Destroy; override;
  313. procedure ControlDestroying;
  314. end;
  315. function CoDisconnectObject(unk: TIUnknown; dwReserved: DWORD): HRESULT;
  316. stdcall; external 'ole32.dll';
  317. var
  318. NotifyWinEventFunc: procedure(event: DWORD; hwnd: HWND; idObject: DWORD;
  319. idChild: Longint); stdcall;
  320. OleAccInited: BOOL;
  321. OleAccAvailable: BOOL;
  322. LresultFromObjectFunc: function(const riid: TGUID; wParam: WPARAM;
  323. pUnk: TIUnknown): LRESULT; stdcall;
  324. CreateStdAccessibleObjectFunc: function(hwnd: HWND; idObject: Longint;
  325. const riidInterface: TGUID; var ppvObject: Pointer): HRESULT; stdcall;
  326. function InitializeOleAcc: Boolean;
  327. function GetSystemDir: String;
  328. var
  329. Buf: array[0..MAX_PATH-1] of Char;
  330. begin
  331. GetSystemDirectory(Buf, SizeOf(Buf) div SizeOf(Buf[0]));
  332. Result := StrPas(Buf);
  333. end;
  334. var
  335. M: HMODULE;
  336. begin
  337. if not OleAccInited then begin
  338. M := LoadLibrary(PChar(AddBackslash(GetSystemDir) + 'oleacc.dll'));
  339. if M <> 0 then begin
  340. LresultFromObjectFunc := GetProcAddress(M, 'LresultFromObject');
  341. CreateStdAccessibleObjectFunc := GetProcAddress(M, 'CreateStdAccessibleObject');
  342. if Assigned(LresultFromObjectFunc) and
  343. Assigned(CreateStdAccessibleObjectFunc) then
  344. OleAccAvailable := True;
  345. end;
  346. OleAccInited := True;
  347. end;
  348. Result := OleAccAvailable;
  349. end;
  350. { TNewCheckListBox }
  351. constructor TNewCheckListBox.Create(AOwner: TComponent);
  352. begin
  353. inherited Create(AOwner);
  354. with TBitmap.Create do
  355. begin
  356. try
  357. Handle := LoadBitmap(0, PChar(OBM_CHECKBOXES));
  358. FCheckWidth := Width div 4;
  359. FCheckHeight := Height div 3;
  360. finally
  361. Free;
  362. end;
  363. end;
  364. FStateList := TList.Create;
  365. FMinItemHeight := 16;
  366. FOffset := 4;
  367. FShowLines := True;
  368. Style := lbOwnerDrawVariable;
  369. FHotIndex := -1;
  370. FCaptureIndex := -1;
  371. end;
  372. procedure TNewCheckListBox.CreateParams(var Params: TCreateParams);
  373. begin
  374. inherited;
  375. FUseRightToLeft := SetBiDiStyles(Self, Params);
  376. end;
  377. procedure TNewCheckListBox.CreateWnd;
  378. begin
  379. { TCustomListBox.CreateWnd causes a LB_RESETCONTENT message to be sent when
  380. it's restoring FSaveItems. Increment FDisableItemStateDeletion so that
  381. our LB_RESETCONTENT handler doesn't delete any item states. }
  382. Inc(FDisableItemStateDeletion);
  383. try
  384. inherited;
  385. finally
  386. Dec(FDisableItemStateDeletion);
  387. end;
  388. end;
  389. procedure TNewCheckListBox.UpdateThemeData(const Close, Open: Boolean);
  390. begin
  391. if Close then begin
  392. if FThemeData <> 0 then begin
  393. CloseThemeData(FThemeData);
  394. FThemeData := 0;
  395. end;
  396. end;
  397. if Open then begin
  398. if UseThemes then
  399. FThemeData := OpenThemeData(Handle, 'Button')
  400. else
  401. FThemeData := 0;
  402. end;
  403. end;
  404. procedure TNewCheckListBox.CreateWindowHandle(const Params: TCreateParams);
  405. begin
  406. inherited CreateWindowHandle(Params);
  407. UpdateThemeData(True, True);
  408. end;
  409. destructor TNewCheckListBox.Destroy;
  410. var
  411. I: Integer;
  412. begin
  413. if Assigned(FAccObjectInstance) then begin
  414. { Detach from FAccObjectInstance if someone still has a reference to it }
  415. TAccObject(FAccObjectInstance).ControlDestroying;
  416. FAccObjectInstance := nil;
  417. end;
  418. if Assigned(FStateList) then begin
  419. for I := FStateList.Count-1 downto 0 do
  420. TItemState(FStateList[I]).Free;
  421. FStateList.Free;
  422. end;
  423. UpdateThemeData(True, False);
  424. inherited Destroy;
  425. end;
  426. function TNewCheckListBox.AddCheckBox(const ACaption, ASubItem: string;
  427. ALevel: Byte; AChecked, AEnabled, AHasInternalChildren,
  428. ACheckWhenParentChecked: Boolean; AObject: TObject): Integer;
  429. begin
  430. if not AEnabled and CheckPotentialRadioParents(Items.Count, ALevel) then
  431. raise Exception.Create(sRadioCantHaveDisabledChildren);
  432. Result := AddItem2(itCheck, ACaption, ASubItem, ALevel, AChecked, AEnabled,
  433. AHasInternalChildren, ACheckWhenParentChecked, AObject);
  434. end;
  435. function TNewCheckListBox.AddGroup(const ACaption, ASubItem: string;
  436. ALevel: Byte; AObject: TObject): Integer;
  437. begin
  438. Result := AddItem2(itGroup, ACaption, ASubItem, ALevel, False, True, False,
  439. True, AObject);
  440. end;
  441. function TNewCheckListBox.AddRadioButton(const ACaption, ASubItem: string;
  442. ALevel: Byte; AChecked, AEnabled: Boolean; AObject: TObject): Integer;
  443. begin
  444. if not AEnabled then
  445. AChecked := False;
  446. Result := AddItem2(itRadio, ACaption, ASubItem, ALevel, AChecked, AEnabled,
  447. False, True, AObject);
  448. end;
  449. function TNewCheckListBox.CanFocusItem(Item: Integer): Boolean;
  450. begin
  451. with ItemStates[Item] do
  452. Result := Self.Enabled and Enabled and (ItemType <> itGroup);
  453. end;
  454. function TNewCheckListBox.CheckPotentialRadioParents(Index, ALevel: Integer): Boolean;
  455. begin
  456. Result := True;
  457. Dec(Index);
  458. Dec(ALevel);
  459. while Index >= 0 do
  460. begin
  461. with ItemStates[Index] do
  462. if Level = ALevel then
  463. if ItemType = itRadio then
  464. Exit
  465. else
  466. Break;
  467. Dec(Index);
  468. end;
  469. if Index >= 0 then
  470. begin
  471. Index := GetParentOf(Index);
  472. while Index >= 0 do
  473. begin
  474. if ItemStates[Index].ItemType = itRadio then
  475. Exit;
  476. Index := GetParentOf(Index);
  477. end;
  478. end;
  479. Result := False;
  480. end;
  481. procedure TNewCheckListBox.CMDialogChar(var Message: TCMDialogChar);
  482. var
  483. I: Integer;
  484. begin
  485. if FWantTabs and CanFocus then
  486. with Message do
  487. begin
  488. I := FindAccel(CharCode);
  489. if I >= 0 then
  490. begin
  491. SetFocus;
  492. if (FCaptureIndex <> I) or FSpaceDown then EndCapture(not FSpaceDown);
  493. ItemIndex := I;
  494. Toggle(I);
  495. Result := 1
  496. end;
  497. end;
  498. end;
  499. procedure TNewCheckListBox.CMEnter(var Message: TCMEnter);
  500. var
  501. GoForward, Arrows: Boolean;
  502. begin
  503. if FWantTabs and FFormFocusChanged and (GetKeyState(VK_LBUTTON) >= 0) then
  504. begin
  505. if GetKeyState(VK_TAB) < 0 then begin
  506. Arrows := False;
  507. GoForward := (GetKeyState(VK_SHIFT) >= 0);
  508. end
  509. else if (GetKeyState(VK_UP) < 0) or (GetKeyState(VK_LEFT) < 0) then begin
  510. Arrows := True;
  511. GoForward := False;
  512. end
  513. else if (GetKeyState(VK_DOWN) < 0) or (GetKeyState(VK_RIGHT) < 0) then begin
  514. Arrows := True;
  515. GoForward := True;
  516. end
  517. else begin
  518. { Otherwise, just select the first item }
  519. Arrows := False;
  520. GoForward := True;
  521. end;
  522. if GoForward then
  523. ItemIndex := FindNextItem(-1, True, not Arrows)
  524. else
  525. ItemIndex := FindNextItem(Items.Count, False, not Arrows)
  526. end;
  527. inherited;
  528. end;
  529. procedure TNewCheckListBox.CMExit(var Message: TCMExit);
  530. begin
  531. EndCapture(not FSpaceDown or (GetKeyState(VK_MENU) >= 0));
  532. inherited;
  533. end;
  534. procedure TNewCheckListBox.CMFocusChanged(var Message: TCMFocusChanged);
  535. begin
  536. FFormFocusChanged := True;
  537. inherited;
  538. end;
  539. procedure TNewCheckListBox.CMFontChanged(var Message: TMessage);
  540. begin
  541. inherited;
  542. Canvas.Font := Font;
  543. end;
  544. procedure LineDDAProc(X, Y: Integer; Canvas: TCanvas); stdcall;
  545. begin
  546. if ((X xor Y) and 1) = 0 then
  547. begin
  548. Canvas.MoveTo(X, Y);
  549. Canvas.LineTo(X + 1, Y)
  550. end;
  551. end;
  552. procedure TNewCheckListBox.CMWantSpecialKey(var Message: TMessage);
  553. begin
  554. Message.Result := Ord(FWantTabs and (Message.WParam = VK_TAB));
  555. end;
  556. procedure TNewCheckListBox.CNDrawItem(var Message: TWMDrawItem);
  557. var
  558. L: Integer;
  559. begin
  560. with Message.DrawItemStruct^ do
  561. begin
  562. { Note: itemID is -1 when there are no items }
  563. if Integer(itemID) >= 0 then begin
  564. L := ItemStates[itemID].Level;
  565. if ItemStates[itemID].ItemType <> itGroup then Inc(L);
  566. rcItem.Left := rcItem.Left + (FCheckWidth + 2 * FOffset) * L;
  567. FlipRect(rcItem, ClientRect, FUseRightToLeft);
  568. end;
  569. { Don't let TCustomListBox.CNDrawItem draw the focus }
  570. if FWantTabs or
  571. (SendMessage(Handle, WM_QUERYUISTATE, 0, 0) and UISF_HIDEFOCUS <> 0) then
  572. itemState := itemState and not ODS_FOCUS;
  573. inherited;
  574. end;
  575. end;
  576. function TNewCheckListBox.RemeasureItem(Index: Integer): Integer;
  577. { Recalculates an item's height. Does not repaint and does not update the
  578. vertical scroll range (as the LB_SETITEMHEIGHT message does neither). }
  579. begin
  580. Result := ItemHeight;
  581. MeasureItem(Index, Result);
  582. SendMessage(Handle, LB_SETITEMHEIGHT, Index, Result);
  583. end;
  584. procedure TNewCheckListBox.UpdateScrollRange;
  585. { Updates the vertical scroll range, hiding/showing the scroll bar if needed.
  586. This should be called after any RemeasureItem call. }
  587. begin
  588. { Update the scroll bounds by sending a seemingly-ineffectual LB_SETTOPINDEX
  589. message. This works on Windows 95 and 2000.
  590. NOTE: This causes the selected item to be repainted for no apparent reason!
  591. I wish I knew of a better way to do this... }
  592. SendMessage(Handle, LB_SETTOPINDEX, SendMessage(Handle, LB_GETTOPINDEX, 0, 0), 0);
  593. end;
  594. procedure TNewCheckListBox.MeasureItem(Index: Integer; var Height: Integer);
  595. var
  596. DrawTextFormat: Integer;
  597. Rect, SubItemRect: TRect;
  598. ItemState: TItemState;
  599. L, SubItemWidth: Integer;
  600. S: String;
  601. begin
  602. with Canvas do begin
  603. ItemState := ItemStates[Index];
  604. Rect := Classes.Rect(0, 0, ClientWidth, 0);
  605. L := ItemState.Level;
  606. if ItemState.ItemType <> itGroup then
  607. Inc(L);
  608. Rect.Left := Rect.Left + (FCheckWidth + 2 * FOffset) * L;
  609. Inc(Rect.Left);
  610. if ItemState.SubItem <> '' then begin
  611. DrawTextFormat := DT_CALCRECT or DT_NOCLIP or DT_NOPREFIX or DT_SINGLELINE;
  612. if FUseRightToLeft then
  613. DrawTextFormat := DrawTextFormat or (DT_RIGHT or DT_RTLREADING);
  614. SetRectEmpty(SubItemRect);
  615. DrawText(Canvas.Handle, PChar(ItemState.SubItem), Length(ItemState.SubItem),
  616. SubItemRect, DrawTextFormat);
  617. SubItemWidth := SubItemRect.Right + 2 * FOffset;
  618. Dec(Rect.Right, SubItemWidth)
  619. end else
  620. Dec(Rect.Right, FOffset);
  621. if not FWantTabs then
  622. Inc(Rect.Left);
  623. DrawTextFormat := DT_NOCLIP or DT_CALCRECT or DT_WORDBREAK or DT_WORD_ELLIPSIS;
  624. if not FWantTabs or (ItemState.ItemType = itGroup) then
  625. DrawTextFormat := DrawTextFormat or DT_NOPREFIX;
  626. if FUseRightToLeft then
  627. DrawTextFormat := DrawTextFormat or (DT_RIGHT or DT_RTLREADING);
  628. S := Items[Index]; { Passing Items[Index] directly into DrawText doesn't work on Unicode build. }
  629. ItemState.MeasuredHeight := DrawText(Canvas.Handle, PChar(S), Length(S), Rect, DrawTextFormat);
  630. if ItemState.MeasuredHeight < FMinItemHeight then
  631. Height := FMinItemHeight
  632. else
  633. Height := ItemState.MeasuredHeight + 4;
  634. { The height must be an even number for tree lines to be painted correctly }
  635. if Odd(Height) then
  636. Inc(Height);
  637. end;
  638. end;
  639. procedure TNewCheckListBox.DrawItem(Index: Integer; Rect: TRect; State: TOwnerDrawState);
  640. const
  641. ButtonStates: array [TItemType] of Integer =
  642. (
  643. 0,
  644. DFCS_BUTTONCHECK,
  645. DFCS_BUTTONRADIO
  646. );
  647. ButtonPartIds: array [TItemType] of Integer =
  648. (
  649. 0,
  650. BP_CHECKBOX,
  651. BP_RADIOBUTTON
  652. );
  653. ButtonStateIds: array [TCheckBoxState, TCheckBoxState2] of Integer =
  654. (
  655. //Can be used for both checkboxes and radiobuttons because RBS_... constants
  656. //equal CBS_... constants
  657. (CBS_UNCHECKEDNORMAL, CBS_UNCHECKEDHOT, CBS_UNCHECKEDPRESSED, CBS_UNCHECKEDDISABLED),
  658. (CBS_CHECKEDNORMAL, CBS_CHECKEDHOT, CBS_CHECKEDPRESSED, CBS_CHECKEDDISABLED),
  659. (CBS_MIXEDNORMAL, CBS_MIXEDHOT, CBS_MIXEDPRESSED, CBS_MIXEDDISABLED)
  660. );
  661. var
  662. SavedClientRect: TRect;
  663. function FlipX(const X: Integer): Integer;
  664. begin
  665. if FUseRightToLeft then
  666. Result := (SavedClientRect.Right - 1) - X
  667. else
  668. Result := X;
  669. end;
  670. procedure InternalDrawText(const S: string; var R: TRect; Format: Integer;
  671. Embossed: Boolean);
  672. begin
  673. if Embossed then
  674. begin
  675. Canvas.Brush.Style := bsClear;
  676. OffsetRect(R, 1, 1);
  677. SetTextColor(Canvas.Handle, GetSysColor(COLOR_BTNHIGHLIGHT));
  678. DrawText(Canvas.Handle, PChar(S), Length(S), R, Format);
  679. OffsetRect(R, -1, -1);
  680. SetTextColor(Canvas.Handle, GetSysColor(COLOR_BTNSHADOW));
  681. DrawText(Canvas.Handle, PChar(S), Length(S), R, Format);
  682. end
  683. else
  684. DrawText(Canvas.Handle, PChar(S), Length(S), R, Format);
  685. end;
  686. var
  687. Disabled: Boolean;
  688. uState, I, ThreadPosX, ThreadBottom, ThreadLevel, ItemMiddle,
  689. DrawTextFormat: Integer;
  690. CheckRect, SubItemRect, FocusRect: TRect;
  691. NewTextColor: TColor;
  692. OldColor: TColorRef;
  693. ItemState: TItemState;
  694. UIState: DWORD;
  695. SubItemWidth: Integer;
  696. PartId, StateId: Integer;
  697. Size: TSize;
  698. begin
  699. if FShowLines and not FThreadsUpToDate then begin
  700. UpdateThreads;
  701. FThreadsUpToDate := True;
  702. end;
  703. SavedClientRect := ClientRect;
  704. { Undo flipping performed by TNewCheckListBox.CNDrawItem }
  705. FlipRect(Rect, SavedClientRect, FUseRightToLeft);
  706. ItemState := ItemStates[Index];
  707. UIState := SendMessage(Handle, WM_QUERYUISTATE, 0, 0);
  708. Disabled := not Enabled or not ItemState.Enabled;
  709. with Canvas do begin
  710. if not FWantTabs and (odSelected in State) and Focused then begin
  711. Brush.Color := clHighlight;
  712. NewTextColor := clHighlightText;
  713. end
  714. else begin
  715. Brush.Color := Self.Color;
  716. if Disabled then
  717. NewTextColor := clGrayText
  718. else
  719. NewTextColor := Self.Font.Color;
  720. end;
  721. { Draw threads }
  722. if FShowLines then begin
  723. Pen.Color := clGrayText;
  724. ThreadLevel := ItemLevel[Index];
  725. for I := 0 to ThreadLevel - 1 do
  726. if I in ItemStates[Index].ThreadCache then begin
  727. ThreadPosX := (FCheckWidth + 2 * FOffset) * I + FCheckWidth div 2 + FOffset;
  728. ItemMiddle := (Rect.Bottom - Rect.Top) div 2 + Rect.Top;
  729. ThreadBottom := Rect.Bottom;
  730. if I = ThreadLevel - 1 then begin
  731. if ItemStates[Index].IsLastChild then
  732. ThreadBottom := ItemMiddle;
  733. LineDDA(FlipX(ThreadPosX), ItemMiddle, FlipX(ThreadPosX + FCheckWidth div 2 + FOffset),
  734. ItemMiddle, @LineDDAProc, Integer(Canvas));
  735. end;
  736. LineDDA(FlipX(ThreadPosX), Rect.Top, FlipX(ThreadPosX), ThreadBottom,
  737. @LineDDAProc, Integer(Canvas));
  738. end;
  739. end;
  740. { Draw checkmark}
  741. if ItemState.ItemType <> itGroup then begin
  742. CheckRect := Bounds(Rect.Left - (FCheckWidth + FOffset),
  743. Rect.Top + ((Rect.Bottom - Rect.Top - FCheckHeight) div 2),
  744. FCheckWidth, FCheckHeight);
  745. FlipRect(CheckRect, SavedClientRect, FUseRightToLeft);
  746. if FThemeData = 0 then begin
  747. case ItemState.State of
  748. cbChecked: uState := ButtonStates[ItemState.ItemType] or DFCS_CHECKED;
  749. cbUnchecked: uState := ButtonStates[ItemState.ItemType];
  750. else
  751. uState := DFCS_BUTTON3STATE or DFCS_CHECKED;
  752. end;
  753. if FFlat then
  754. uState := uState or DFCS_FLAT;
  755. if Disabled then
  756. uState := uState or DFCS_INACTIVE;
  757. if (FCaptureIndex = Index) and (FSpaceDown or (FLastMouseMoveIndex = Index)) then
  758. uState := uState or DFCS_PUSHED;
  759. DrawFrameControl(Handle, CheckRect, DFC_BUTTON, uState)
  760. end else begin
  761. PartId := ButtonPartIds[ItemState.ItemType];
  762. if Disabled then
  763. StateId := ButtonStateIds[ItemState.State][cb2Disabled]
  764. else if Index = FCaptureIndex then
  765. if FSpaceDown or (FLastMouseMoveIndex = Index) then
  766. StateId := ButtonStateIds[ItemState.State][cb2Pressed]
  767. else
  768. StateId := ButtonStateIds[ItemState.State][cb2Hot]
  769. else if (FCaptureIndex < 0) and (Index = FHotIndex) then
  770. StateId := ButtonStateIds[ItemState.State][cb2Hot]
  771. else
  772. StateId := ButtonStateIds[ItemState.State][cb2Normal];
  773. GetThemePartSize(FThemeData, Handle, PartId, StateId, @CheckRect, TS_TRUE, Size);
  774. if (Size.cx <> FCheckWidth) or (Size.cy <> FCheckHeight) then begin
  775. CheckRect := Bounds(Rect.Left - (Size.cx + FOffset),
  776. Rect.Top + ((Rect.Bottom - Rect.Top - Size.cy) div 2),
  777. Size.cx, Size.cy);
  778. FlipRect(CheckRect, SavedClientRect, FUseRightToLeft);
  779. end;
  780. //if IsThemeBackgroundPartiallyTransparent(FThemeData, PartId, StateId) then
  781. // DrawThemeParentBackground(Self.Handle, Handle, @CheckRect);
  782. DrawThemeBackGround(FThemeData, Handle, PartId, StateId, CheckRect, @CheckRect);
  783. end;
  784. end;
  785. { Draw SubItem }
  786. FlipRect(Rect, SavedClientRect, FUseRightToLeft);
  787. FillRect(Rect);
  788. FlipRect(Rect, SavedClientRect, FUseRightToLeft);
  789. Inc(Rect.Left);
  790. OldColor := SetTextColor(Handle, ColorToRGB(NewTextColor));
  791. if ItemState.SubItem <> '' then
  792. begin
  793. DrawTextFormat := DT_NOCLIP or DT_NOPREFIX or DT_SINGLELINE or DT_VCENTER;
  794. if FUseRightToLeft then
  795. DrawTextFormat := DrawTextFormat or (DT_RIGHT or DT_RTLREADING);
  796. Font.Style := ItemState.SubItemFontStyle;
  797. SetRectEmpty(SubItemRect);
  798. InternalDrawText(ItemState.SubItem, SubItemRect, DrawTextFormat or
  799. DT_CALCRECT, False);
  800. SubItemWidth := SubItemRect.Right + 2 * FOffset;
  801. SubItemRect := Rect;
  802. SubItemRect.Left := SubItemRect.Right - SubItemWidth + FOffset;
  803. FlipRect(SubItemRect, SavedClientRect, FUseRightToLeft);
  804. InternalDrawText(ItemState.SubItem, SubItemRect, DrawTextFormat,
  805. FWantTabs and Disabled);
  806. Dec(Rect.Right, SubItemWidth);
  807. end
  808. else
  809. Dec(Rect.Right, FOffset);
  810. { Draw item text }
  811. if not FWantTabs then
  812. Inc(Rect.Left);
  813. OffsetRect(Rect, 0, (Rect.Bottom - Rect.Top - ItemState.MeasuredHeight) div 2);
  814. DrawTextFormat := DT_NOCLIP or DT_WORDBREAK or DT_WORD_ELLIPSIS;
  815. if not FWantTabs or (ItemState.ItemType = itGroup) then
  816. DrawTextFormat := DrawTextFormat or DT_NOPREFIX;
  817. if (UIState and UISF_HIDEACCEL) <> 0 then
  818. DrawTextFormat := DrawTextFormat or DT_HIDEPREFIX;
  819. if FUseRightToLeft then
  820. DrawTextFormat := DrawTextFormat or (DT_RIGHT or DT_RTLREADING);
  821. Font.Style := ItemState.ItemFontStyle;
  822. { When you call DrawText with the DT_CALCRECT flag and there's a word wider
  823. than the rectangle width, it increases the rectangle width and wraps
  824. at the new Right point. On the other hand, when you call DrawText
  825. _without_ the DT_CALCRECT flag, it always wraps at the Right point you
  826. specify -- it doesn't check for long words first.
  827. Therefore, to ensure we wrap at the same place when drawing as when
  828. measuring, pass our rectangle to DrawText with DT_CALCRECT first.
  829. Wrapping at the same place is important because it can affect how many
  830. lines are drawn -- and we mustn't draw too many. }
  831. InternalDrawText(Items[Index], Rect, DrawTextFormat or DT_CALCRECT, False);
  832. FlipRect(Rect, SavedClientRect, FUseRightToLeft);
  833. InternalDrawText(Items[Index], Rect, DrawTextFormat, FWantTabs and Disabled);
  834. { Draw focus rectangle }
  835. if FWantTabs and not Disabled and (odSelected in State) and Focused and
  836. (UIState and UISF_HIDEFOCUS = 0) then
  837. begin
  838. FocusRect := Rect;
  839. InflateRect(FocusRect, 1, 1);
  840. DrawFocusRect(FocusRect);
  841. end;
  842. SetTextColor(Handle, OldColor);
  843. end;
  844. end;
  845. procedure TNewCheckListBox.EndCapture(Cancel: Boolean);
  846. var
  847. InvalidateItem: Boolean;
  848. Item: Integer;
  849. begin
  850. Item := FCaptureIndex;
  851. if Item >= 0 then
  852. begin
  853. InvalidateItem := FSpaceDown or (FCaptureIndex = FLastMouseMoveIndex) or (FThemeData <> 0);
  854. FSpaceDown := False;
  855. FCaptureIndex := -1;
  856. FLastMouseMoveIndex := -1;
  857. if not Cancel then
  858. Toggle(Item);
  859. if InvalidateItem then
  860. InvalidateCheck(Item);
  861. end;
  862. if MouseCapture then
  863. MouseCapture := False;
  864. end;
  865. procedure TNewCheckListBox.EnumChildrenOf(Item: Integer; Proc: TEnumChildrenProc;
  866. Ext: Longint);
  867. var
  868. L: Integer;
  869. begin
  870. if (Item < -1) or (Item >= Items.Count) then
  871. Exit;
  872. if Item = -1 then
  873. begin
  874. L := 0;
  875. Item := 0;
  876. end
  877. else
  878. begin
  879. L := ItemLevel[Item] + 1;
  880. Inc(Item);
  881. end;
  882. while (Item < Items.Count) and (ItemLevel[Item] >= L) do
  883. begin
  884. if ItemLevel[Item] = L then
  885. Proc(Item, (Item < Items.Count - 1) and (ItemLevel[Item + 1] > L), Ext);
  886. Inc(Item);
  887. end;
  888. end;
  889. function TNewCheckListBox.AddItem2(AType: TItemType;
  890. const ACaption, ASubItem: string; ALevel: Byte;
  891. AChecked, AEnabled, AHasInternalChildren, ACheckWhenParentChecked: Boolean;
  892. AObject: TObject): Integer;
  893. var
  894. ItemState: TItemState;
  895. I: Integer;
  896. begin
  897. if Items.Count <> FStateList.Count then { sanity check }
  898. raise Exception.Create('List item and state item count mismatch');
  899. if Items.Count > 0 then
  900. begin
  901. if ItemLevel[Items.Count - 1] + 1 < ALevel then
  902. ALevel := ItemLevel[Items.Count - 1] + 1;
  903. end
  904. else
  905. ALevel := 0;
  906. FThreadsUpToDate := False;
  907. { Use our own grow code to minimize heap fragmentation }
  908. if FStateList.Count = FStateList.Capacity then begin
  909. if FStateList.Capacity < 64 then
  910. FStateList.Capacity := 64
  911. else
  912. FStateList.Capacity := FStateList.Capacity * 2;
  913. end;
  914. ItemState := TItemState.Create;
  915. try
  916. ItemState.ItemType := AType;
  917. ItemState.Enabled := AEnabled;
  918. ItemState.Obj := AObject;
  919. ItemState.Level := ALevel;
  920. ItemState.SubItem := ASubItem;
  921. ItemState.HasInternalChildren := AHasInternalChildren;
  922. ItemState.CheckWhenParentChecked := ACheckWhenParentChecked;
  923. except
  924. ItemState.Free;
  925. raise;
  926. end;
  927. FStateList.Add(ItemState);
  928. try
  929. Result := Items.Add(ACaption);
  930. except
  931. FStateList.Delete(FStateList.Count-1);
  932. ItemState.Free;
  933. raise;
  934. end;
  935. { If the first item in a radio group is being added, and it is top-level or
  936. has a checked parent, force it to be checked. (We don't want to allow radio
  937. groups with no selection.) }
  938. if (AType = itRadio) and not AChecked and AEnabled then begin
  939. I := GetParentOf(Result);
  940. { FRequireRadioSelection only affects top-level items; we never allow
  941. child radio groups with no selection (because nobody should need that) }
  942. if FRequireRadioSelection or (I <> -1) then
  943. if (I = -1) or (GetState(I) <> cbUnchecked) then
  944. if FindCheckedSibling(Result) = -1 then
  945. AChecked := True;
  946. end;
  947. SetChecked(Result, AChecked);
  948. end;
  949. function TNewCheckListBox.FindAccel(VK: Word): Integer;
  950. begin
  951. for Result := 0 to Items.Count - 1 do
  952. if CanFocusItem(Result) and IsAccel(VK, Items[Result]) then
  953. Exit;
  954. Result := -1;
  955. end;
  956. function TNewCheckListBox.FindNextItem(StartFrom: Integer; GoForward,
  957. SkipUncheckedRadios: Boolean): Integer;
  958. function ShouldSkip(Index: Integer): Boolean;
  959. begin
  960. with ItemStates[Index] do
  961. Result := (ItemType = itRadio) and (State <> cbChecked)
  962. end;
  963. var
  964. Delta: Integer;
  965. begin
  966. if StartFrom < -1 then
  967. StartFrom := ItemIndex;
  968. if Items.Count > 0 then
  969. begin
  970. Delta := Ord(GoForward) * 2 - 1;
  971. Result := StartFrom + Delta;
  972. while (Result >= 0) and (Result < Items.Count) and
  973. (not CanFocusItem(Result) or SkipUncheckedRadios and ShouldSkip(Result)) do
  974. Result := Result + Delta;
  975. if (Result < 0) or (Result >= Items.Count) then
  976. Result := -1;
  977. end
  978. else
  979. Result := -1;
  980. end;
  981. function TNewCheckListBox.GetCaption(Index: Integer): String;
  982. begin
  983. Result := Items[Index];
  984. end;
  985. function TNewCheckListBox.GetChecked(Index: Integer): Boolean;
  986. begin
  987. Result := GetState(Index) <> cbUnchecked;
  988. end;
  989. function TNewCheckListBox.GetItemEnabled(Index: Integer): Boolean;
  990. begin
  991. Result := ItemStates[Index].Enabled;
  992. end;
  993. function TNewCheckListBox.GetItemFontStyle(Index: Integer): TFontStyles;
  994. begin
  995. Result := ItemStates[Index].ItemFontStyle;
  996. end;
  997. function TNewCheckListBox.GetItemState(Index: Integer): TItemState;
  998. begin
  999. Result := FStateList[Index];
  1000. end;
  1001. function TNewCheckListBox.GetLevel(Index: Integer): Byte;
  1002. begin
  1003. Result := ItemStates[Index].Level;
  1004. end;
  1005. function TNewCheckListBox.GetObject(Index: Integer): TObject;
  1006. begin
  1007. Result := ItemStates[Index].Obj;
  1008. end;
  1009. function TNewCheckListBox.GetParentOf(Item: Integer): Integer;
  1010. { Gets index of Item's parent, or -1 if there is none. }
  1011. var
  1012. Level, I: Integer;
  1013. begin
  1014. Level := ItemStates[Item].Level;
  1015. if Level > 0 then
  1016. for I := Item-1 downto 0 do begin
  1017. if ItemStates[I].Level < Level then begin
  1018. Result := I;
  1019. Exit;
  1020. end;
  1021. end;
  1022. Result := -1;
  1023. end;
  1024. function TNewCheckListBox.GetState(Index: Integer): TCheckBoxState;
  1025. begin
  1026. Result := ItemStates[Index].State;
  1027. end;
  1028. function TNewCheckListBox.GetSubItem(Index: Integer): String;
  1029. begin
  1030. Result := ItemStates[Index].SubItem;
  1031. end;
  1032. function TNewCheckListBox.GetSubItemFontStyle(Index: Integer): TFontStyles;
  1033. begin
  1034. Result := ItemStates[Index].SubItemFontStyle;
  1035. end;
  1036. procedure TNewCheckListBox.InvalidateCheck(Index: Integer);
  1037. var
  1038. IRect: TRect;
  1039. begin
  1040. IRect := ItemRect(Index);
  1041. Inc(IRect.Left, (FCheckWidth + 2 * Offset) * (ItemLevel[Index]));
  1042. IRect.Right := IRect.Left + (FCheckWidth + 2 * Offset);
  1043. FlipRect(IRect, ClientRect, FUseRightToLeft);
  1044. InvalidateRect(Handle, @IRect, FThemeData <> 0);
  1045. end;
  1046. procedure TNewCheckListBox.KeyDown(var Key: Word; Shift: TShiftState);
  1047. begin
  1048. if (Key = VK_SPACE) and not (ssAlt in Shift) and (ItemIndex >= 0) and
  1049. (FCaptureIndex < 0) and CanFocusItem(ItemIndex) then
  1050. if FWantTabs then begin
  1051. if not FSpaceDown then begin
  1052. FCaptureIndex := ItemIndex;
  1053. FSpaceDown := True;
  1054. InvalidateCheck(ItemIndex);
  1055. if (FHotIndex <> ItemIndex) and (FHotIndex <> -1) and (FThemeData <> 0) then
  1056. InvalidateCheck(FHotIndex);
  1057. end;
  1058. end
  1059. else
  1060. Toggle(ItemIndex);
  1061. inherited;
  1062. end;
  1063. procedure TNewCheckListBox.KeyUp(var Key: Word; Shift: TShiftState);
  1064. begin
  1065. if (Key = VK_SPACE) and FWantTabs and FSpaceDown and (FCaptureIndex >= 0) then begin
  1066. EndCapture(False);
  1067. if (FHotIndex <> -1) and (FThemeData <> 0) then
  1068. InvalidateCheck(FHotIndex);
  1069. end;
  1070. inherited;
  1071. end;
  1072. procedure TNewCheckListBox.MouseDown(Button: TMouseButton; Shift: TShiftState;
  1073. X, Y: Integer);
  1074. var
  1075. Index: Integer;
  1076. begin
  1077. if Button = mbLeft then begin
  1078. Index := ItemAtPos(Point(X, Y), True);
  1079. if (Index <> -1) and CanFocusItem(Index) then
  1080. begin
  1081. if FWantTabs then begin
  1082. if not FSpaceDown then begin
  1083. if not MouseCapture then
  1084. MouseCapture := True;
  1085. FCaptureIndex := Index;
  1086. FLastMouseMoveIndex := Index;
  1087. InvalidateCheck(Index);
  1088. end;
  1089. end
  1090. else
  1091. Toggle(Index);
  1092. end;
  1093. end;
  1094. inherited;
  1095. end;
  1096. procedure TNewCheckListBox.MouseUp(Button: TMouseButton; Shift: TShiftState;
  1097. X, Y: Integer);
  1098. var
  1099. Index: Integer;
  1100. begin
  1101. if (Button = mbLeft) and FWantTabs and not FSpaceDown and (FCaptureIndex >= 0) then
  1102. begin
  1103. Index := ItemAtPos(Point(X, Y), True);
  1104. EndCapture(Index <> FCaptureIndex);
  1105. if (FHotIndex <> -1) and (FThemeData <> 0) then
  1106. InvalidateCheck(FHotIndex);
  1107. end;
  1108. end;
  1109. procedure TNewCheckListBox.UpdateHotIndex(NewHotIndex: Integer);
  1110. var
  1111. OldHotIndex: Integer;
  1112. begin
  1113. OldHotIndex := FHotIndex;
  1114. if NewHotIndex <> OldHotIndex then
  1115. begin
  1116. FHotIndex := NewHotIndex;
  1117. if FCaptureIndex = -1 then begin
  1118. if (OldHotIndex <> -1) and (FThemeData <> 0) then
  1119. InvalidateCheck(OldHotIndex);
  1120. if (NewHotIndex <> -1) and (FThemeData <> 0) then
  1121. InvalidateCheck(NewHotIndex);
  1122. end;
  1123. end;
  1124. end;
  1125. procedure TNewCheckListBox.CMMouseLeave(var Message: TMessage);
  1126. begin
  1127. UpdateHotIndex(-1);
  1128. inherited;
  1129. end;
  1130. procedure TNewCheckListBox.SetCaption(Index: Integer; const Value: String);
  1131. begin
  1132. { Changing an item's text actually involves deleting and re-inserting the
  1133. item. Increment FDisableItemStateDeletion so the item state isn't lost. }
  1134. Inc(FDisableItemStateDeletion);
  1135. try
  1136. Items[Index] := Value;
  1137. finally
  1138. Dec(FDisableItemStateDeletion);
  1139. end;
  1140. end;
  1141. procedure TNewCheckListBox.SetChecked(Index: Integer; const AChecked: Boolean);
  1142. begin
  1143. if AChecked then
  1144. CheckItem(Index, coCheck)
  1145. else
  1146. CheckItem(Index, coUncheck);
  1147. end;
  1148. function TNewCheckListBox.FindCheckedSibling(const AIndex: Integer): Integer;
  1149. { Finds a checked sibling of AIndex (which is assumed to be a radio button).
  1150. Returns -1 if no checked sibling was found. }
  1151. var
  1152. ThisLevel, I: Integer;
  1153. begin
  1154. ThisLevel := ItemStates[AIndex].Level;
  1155. for I := AIndex-1 downto 0 do begin
  1156. if ItemStates[I].Level < ThisLevel then
  1157. Break;
  1158. if ItemStates[I].Level = ThisLevel then begin
  1159. if ItemStates[I].ItemType <> itRadio then
  1160. Break;
  1161. if GetState(I) <> cbUnchecked then begin
  1162. Result := I;
  1163. Exit;
  1164. end;
  1165. end;
  1166. end;
  1167. for I := AIndex+1 to Items.Count-1 do begin
  1168. if ItemStates[I].Level < ThisLevel then
  1169. Break;
  1170. if ItemStates[I].Level = ThisLevel then begin
  1171. if ItemStates[I].ItemType <> itRadio then
  1172. Break;
  1173. if GetState(I) <> cbUnchecked then begin
  1174. Result := I;
  1175. Exit;
  1176. end;
  1177. end;
  1178. end;
  1179. Result := -1;
  1180. end;
  1181. function TNewCheckListBox.CheckItem(const Index: Integer;
  1182. const AOperation: TCheckItemOperation): Boolean;
  1183. { Tries to update the checked state of Index. Returns True if any changes were
  1184. made to the state of Index or any of its children. }
  1185. procedure SetItemState(const AIndex: Integer; const AState: TCheckBoxState);
  1186. begin
  1187. if ItemStates[AIndex].State <> AState then begin
  1188. ItemStates[AIndex].State := AState;
  1189. InvalidateCheck(AIndex);
  1190. { Notify MSAA of the state change }
  1191. if Assigned(NotifyWinEventFunc) then
  1192. NotifyWinEventFunc(EVENT_OBJECT_STATECHANGE, Handle, OBJID_CLIENT,
  1193. 1 + AIndex);
  1194. end;
  1195. end;
  1196. function CalcState(const AIndex: Integer; ACheck: Boolean): TCheckBoxState;
  1197. { Determines new state for AIndex based on desired checked state (ACheck) and
  1198. current state of the item's immediate children. }
  1199. var
  1200. RootLevel, I: Integer;
  1201. HasChecked, HasUnchecked: Boolean;
  1202. begin
  1203. HasChecked := False;
  1204. HasUnchecked := False;
  1205. RootLevel := ItemStates[AIndex].Level;
  1206. for I := AIndex+1 to Items.Count-1 do begin
  1207. if ItemStates[I].Level <= RootLevel then
  1208. Break;
  1209. if (ItemStates[I].Level = RootLevel+1) and
  1210. (ItemStates[I].ItemType in [itCheck, itRadio]) then begin
  1211. case GetState(I) of
  1212. cbUnchecked: begin
  1213. if (ItemStates[I].ItemType <> itRadio) or
  1214. (FindCheckedSibling(I) = -1) then
  1215. HasUnchecked := True;
  1216. end;
  1217. cbChecked: begin
  1218. HasChecked := True;
  1219. end;
  1220. cbGrayed: begin
  1221. HasChecked := True;
  1222. HasUnchecked := True;
  1223. end;
  1224. end;
  1225. end;
  1226. end;
  1227. { If the parent is a check box with children, don't allow it to be checked
  1228. if none of its children are checked, unless it "has internal children" }
  1229. if HasUnchecked and not HasChecked and
  1230. (ItemStates[AIndex].ItemType = itCheck) and
  1231. not ItemStates[AIndex].HasInternalChildren then
  1232. ACheck := False;
  1233. if ACheck or HasChecked then begin
  1234. if HasUnchecked and (ItemStates[AIndex].ItemType = itCheck) then
  1235. Result := cbGrayed
  1236. else
  1237. Result := cbChecked;
  1238. end
  1239. else
  1240. Result := cbUnchecked;
  1241. end;
  1242. function RecursiveCheck(const AIndex: Integer;
  1243. const AOperation: TCheckItemOperation): Boolean;
  1244. { Checks or unchecks AIndex and all enabled child items of AIndex at any
  1245. level. In radio button groups, only one item per group is checked.
  1246. Returns True if any of the items' states were changed. }
  1247. var
  1248. RootLevel, I: Integer;
  1249. NewState: TCheckBoxState;
  1250. begin
  1251. Result := False;
  1252. RootLevel := ItemStates[AIndex].Level;
  1253. for I := AIndex+1 to Items.Count-1 do begin
  1254. if ItemStates[I].Level <= RootLevel then
  1255. Break;
  1256. if (ItemStates[I].Level = RootLevel+1) and ItemStates[I].Enabled and
  1257. ((AOperation = coUncheck) or
  1258. ((AOperation = coCheckWithChildren) and ItemStates[I].CheckWhenParentChecked) or
  1259. (ItemStates[I].ItemType = itRadio)) then
  1260. { If checking and I is a radio button, don't recurse if a sibling
  1261. already got checked in a previous iteration of this loop. This is
  1262. needed in the following case to prevent all three radio buttons from
  1263. being checked when "Parent check" is checked. In addition, it
  1264. prevents "Child check" from being checked.
  1265. [ ] Parent check
  1266. ( ) Radio 1
  1267. ( ) Radio 2
  1268. ( ) Radio 3
  1269. [ ] Child check
  1270. }
  1271. if (AOperation = coUncheck) or (ItemStates[I].ItemType <> itRadio) or
  1272. (FindCheckedSibling(I) = -1) then
  1273. if RecursiveCheck(I, AOperation) then
  1274. Result := True;
  1275. end;
  1276. NewState := CalcState(AIndex, AOperation <> coUncheck);
  1277. if GetState(AIndex) <> NewState then begin
  1278. SetItemState(AIndex, NewState);
  1279. Result := True;
  1280. end;
  1281. end;
  1282. procedure UncheckSiblings(const AIndex: Integer);
  1283. { Unchecks all siblings (and their children) of AIndex, which is assumed to
  1284. be a radio button. }
  1285. var
  1286. I: Integer;
  1287. begin
  1288. while True do begin
  1289. I := FindCheckedSibling(AIndex);
  1290. if I = -1 then
  1291. Break;
  1292. RecursiveCheck(I, coUncheck);
  1293. end;
  1294. end;
  1295. procedure EnsureChildRadioItemsHaveSelection(const AIndex: Integer);
  1296. { Ensures all radio button groups that are immediate children of AIndex have
  1297. a selected item. }
  1298. var
  1299. RootLevel, I: Integer;
  1300. begin
  1301. RootLevel := ItemStates[AIndex].Level;
  1302. for I := AIndex+1 to Items.Count-1 do begin
  1303. if ItemStates[I].Level <= RootLevel then
  1304. Break;
  1305. if (ItemStates[I].Level = RootLevel+1) and
  1306. (ItemStates[I].ItemType = itRadio) and
  1307. ItemStates[I].Enabled and
  1308. (GetState(I) <> cbChecked) and
  1309. (FindCheckedSibling(I) = -1) then
  1310. { Note: This uses coCheck instead of coCheckWithChildren (or the value
  1311. of AOperation) in order to keep side effects to a minimum. Seems
  1312. like the most logical behavior. For example, in this case:
  1313. [ ] A
  1314. ( ) B
  1315. [ ] C
  1316. [ ] D
  1317. clicking D will cause the radio button B to be checked (out of
  1318. necessity), but won't automatically check its child check box, C.
  1319. (If C were instead a radio button, it *would* be checked.) }
  1320. RecursiveCheck(I, coCheck);
  1321. end;
  1322. end;
  1323. procedure UpdateParentStates(const AIndex: Integer);
  1324. var
  1325. I: Integer;
  1326. ChildChecked: Boolean;
  1327. NewState: TCheckBoxState;
  1328. begin
  1329. I := AIndex;
  1330. while True do begin
  1331. ChildChecked := (GetState(I) <> cbUnchecked);
  1332. I := GetParentOf(I);
  1333. if I = -1 then
  1334. Break;
  1335. { When a child item is checked, must ensure that all sibling radio button
  1336. groups have selections }
  1337. if ChildChecked then
  1338. EnsureChildRadioItemsHaveSelection(I);
  1339. NewState := CalcState(I, GetState(I) <> cbUnchecked);
  1340. { If a parent radio button is becoming checked, uncheck any previously
  1341. selected sibling of that radio button }
  1342. if (NewState <> cbUnchecked) and (ItemStates[I].ItemType = itRadio) then
  1343. UncheckSiblings(I);
  1344. SetItemState(I, NewState);
  1345. end;
  1346. end;
  1347. begin
  1348. if ItemStates[Index].ItemType = itRadio then begin
  1349. { Setting Checked to False on a radio button is a no-op. (A radio button
  1350. may only be unchecked by checking another radio button in the group, or
  1351. by unchecking a parent check box.) }
  1352. if AOperation = coUncheck then begin
  1353. Result := False;
  1354. Exit;
  1355. end;
  1356. { Before checking a new item in a radio group, uncheck any siblings and
  1357. their children }
  1358. UncheckSiblings(Index);
  1359. end;
  1360. { Check or uncheck this item and all its children }
  1361. Result := RecursiveCheck(Index, AOperation);
  1362. { Update state of parents. For example, if a child check box is being
  1363. checked, its parent must also become checked if it isn't already. }
  1364. UpdateParentStates(Index);
  1365. end;
  1366. procedure TNewCheckListBox.SetFlat(Value: Boolean);
  1367. begin
  1368. if Value <> FFlat then
  1369. begin
  1370. FFlat := Value;
  1371. Invalidate;
  1372. end;
  1373. end;
  1374. procedure TNewCheckListBox.SetItemEnabled(Index: Integer; const AEnabled: Boolean);
  1375. begin
  1376. if ItemStates[Index].Enabled <> AEnabled then
  1377. begin
  1378. ItemStates[Index].Enabled := AEnabled;
  1379. InvalidateCheck(Index);
  1380. end;
  1381. end;
  1382. procedure TNewCheckListBox.SetItemFontStyle(Index: Integer; const AItemFontStyle: TFontStyles);
  1383. var
  1384. R: TRect;
  1385. begin
  1386. if ItemStates[Index].ItemFontStyle <> AItemFontStyle then begin
  1387. ItemStates[Index].ItemFontStyle := AItemFontStyle;
  1388. R := ItemRect(Index);
  1389. InvalidateRect(Handle, @R, True);
  1390. end;
  1391. end;
  1392. procedure TNewCheckListBox.SetObject(Index: Integer; const AObject: TObject);
  1393. begin
  1394. ItemStates[Index].Obj := AObject;
  1395. end;
  1396. procedure TNewCheckListBox.SetOffset(AnOffset: Integer);
  1397. begin
  1398. if FOffset <> AnOffset then
  1399. begin
  1400. FOffset := AnOffset;
  1401. Invalidate;
  1402. end;
  1403. end;
  1404. procedure TNewCheckListBox.SetShowLines(Value: Boolean);
  1405. begin
  1406. if FShowLines <> Value then
  1407. begin
  1408. FShowLines := Value;
  1409. Invalidate;
  1410. end;
  1411. end;
  1412. procedure TNewCheckListBox.SetSubItem(Index: Integer; const ASubItem: String);
  1413. var
  1414. OldHeight, NewHeight: Integer;
  1415. R, R2: TRect;
  1416. begin
  1417. if ItemStates[Index].SubItem <> ASubItem then
  1418. begin
  1419. ItemStates[Index].SubItem := ASubItem;
  1420. OldHeight := SendMessage(Handle, LB_GETITEMHEIGHT, Index, 0);
  1421. NewHeight := RemeasureItem(Index);
  1422. R := ItemRect(Index);
  1423. { Scroll subsequent items down or up, if necessary }
  1424. if NewHeight <> OldHeight then begin
  1425. if Index >= TopIndex then begin
  1426. R2 := ClientRect;
  1427. R2.Top := R.Top + OldHeight;
  1428. if not IsRectEmpty(R2) then
  1429. ScrollWindowEx(Handle, 0, NewHeight - OldHeight, @R2, nil, 0, nil,
  1430. SW_INVALIDATE or SW_ERASE);
  1431. end;
  1432. UpdateScrollRange;
  1433. end;
  1434. InvalidateRect(Handle, @R, True);
  1435. end;
  1436. end;
  1437. procedure TNewCheckListBox.SetSubItemFontStyle(Index: Integer; const ASubItemFontStyle: TFontStyles);
  1438. var
  1439. R: TRect;
  1440. begin
  1441. if ItemStates[Index].SubItemFontStyle <> ASubItemFontStyle then begin
  1442. ItemStates[Index].SubItemFontStyle := ASubItemFontStyle;
  1443. R := ItemRect(Index);
  1444. InvalidateRect(Handle, @R, True);
  1445. end;
  1446. end;
  1447. procedure TNewCheckListBox.Toggle(Index: Integer);
  1448. begin
  1449. case ItemStates[Index].ItemType of
  1450. itCheck:
  1451. case ItemStates[Index].State of
  1452. cbUnchecked: CheckItem(Index, coCheckWithChildren);
  1453. cbChecked: CheckItem(Index, coUncheck);
  1454. cbGrayed:
  1455. { First try checking, but if that doesn't work because of children
  1456. that are disabled and unchecked, try unchecking }
  1457. if not CheckItem(Index, coCheckWithChildren) then
  1458. CheckItem(Index, coUncheck);
  1459. end;
  1460. itRadio: CheckItem(Index, coCheckWithChildren);
  1461. end;
  1462. if Assigned(FOnClickCheck) then
  1463. FOnClickCheck(Self);
  1464. end;
  1465. procedure TNewCheckListBox.UpdateThreads;
  1466. function LastImmediateChildOf(Item: Integer): Integer;
  1467. var
  1468. L: Integer;
  1469. begin
  1470. Result := -1;
  1471. L := ItemLevel[Item] + 1;
  1472. Inc(Item);
  1473. while (Item < Items.Count) and (ItemLevel[Item] >= L) do
  1474. begin
  1475. if ItemLevel[Item] = L then
  1476. Result := Item;
  1477. Inc(Item);
  1478. end;
  1479. if Result >= 0 then
  1480. ItemStates[Result].IsLastChild := True;
  1481. end;
  1482. var
  1483. I, J, LastChild, L: Integer;
  1484. begin
  1485. for I := 0 to Items.Count - 1 do
  1486. begin
  1487. ItemStates[I].ThreadCache := [0]; //Doing ':= []' causes a "F2084 Internal Error: C21846" compiler error on Delphi 10.3 Rio }
  1488. Exclude(ItemStates[I].ThreadCache, 0); //
  1489. ItemStates[I].IsLastChild := False;
  1490. end;
  1491. for I := 0 to Items.Count - 1 do
  1492. begin
  1493. LastChild := LastImmediateChildOf(I);
  1494. L := ItemLevel[I];
  1495. for J := I + 1 to LastChild do
  1496. Include(ItemStates[J].ThreadCache, L);
  1497. end;
  1498. end;
  1499. procedure TNewCheckListBox.LBDeleteString(var Message: TMessage);
  1500. var
  1501. I: Integer;
  1502. ItemState: TItemState;
  1503. begin
  1504. inherited;
  1505. if FDisableItemStateDeletion = 0 then begin
  1506. I := Message.WParam;
  1507. if (I >= 0) and (I < FStateList.Count) then begin
  1508. ItemState := FStateList[I];
  1509. FStateList.Delete(I);
  1510. ItemState.Free;
  1511. end;
  1512. end;
  1513. end;
  1514. procedure TNewCheckListBox.LBResetContent(var Message: TMessage);
  1515. var
  1516. I: Integer;
  1517. ItemState: TItemState;
  1518. begin
  1519. inherited;
  1520. if FDisableItemStateDeletion = 0 then
  1521. for I := FStateList.Count-1 downto 0 do begin
  1522. ItemState := FStateList[I];
  1523. FStateList.Delete(I);
  1524. ItemState.Free;
  1525. end;
  1526. end;
  1527. procedure TNewCheckListBox.WMGetDlgCode(var Message: TWMGetDlgCode);
  1528. begin
  1529. inherited;
  1530. if FWantTabs then
  1531. Message.Result := Message.Result and not DLGC_WANTCHARS;
  1532. end;
  1533. procedure TNewCheckListBox.WMKeyDown(var Message: TWMKeyDown);
  1534. var
  1535. GoForward, Arrows: Boolean;
  1536. I: Integer;
  1537. Prnt, Ctrl: TWinControl;
  1538. begin
  1539. { If space is pressed, avoid flickering -- exit now. }
  1540. if not FWantTabs or (Message.CharCode = VK_SPACE) then
  1541. begin
  1542. inherited;
  1543. Exit;
  1544. end;
  1545. Arrows := True;
  1546. case Message.CharCode of
  1547. VK_TAB:
  1548. begin
  1549. GoForward := GetKeyState(VK_SHIFT) >= 0;
  1550. Arrows := False
  1551. end;
  1552. VK_DOWN, VK_RIGHT: GoForward := True;
  1553. VK_UP, VK_LEFT: GoForward := False
  1554. else
  1555. if FSpaceDown then EndCapture(True);
  1556. inherited;
  1557. Exit;
  1558. end;
  1559. EndCapture(not FSpaceDown);
  1560. SendMessage(Handle, WM_CHANGEUISTATE, UIS_CLEAR or (UISF_HIDEFOCUS shl 16), 0);
  1561. if Arrows or TabStop then
  1562. I := FindNextItem(-2, GoForward, not Arrows)
  1563. else
  1564. I := -1;
  1565. if I < 0 then
  1566. begin
  1567. Prnt := nil;
  1568. if not Arrows then
  1569. Prnt := GetParentForm(Self);
  1570. if Prnt = nil then Prnt := Parent;
  1571. if Prnt <> nil then
  1572. begin
  1573. Ctrl := TWinControlAccess(Prnt).FindNextControl(Self, GoForward, True, Arrows);
  1574. if (Ctrl <> nil) and (Ctrl <> Self) then
  1575. begin
  1576. Ctrl.SetFocus;
  1577. Exit;
  1578. end;
  1579. end;
  1580. if GoForward then
  1581. I := FindNextItem(-1, True, not Arrows)
  1582. else
  1583. I := FindNextItem(Items.Count, False, not Arrows);
  1584. end;
  1585. ItemIndex := I;
  1586. if (I <> -1) and (ItemStates[I].ItemType = itRadio) and Arrows then
  1587. Toggle(I);
  1588. end;
  1589. procedure TNewCheckListBox.WMMouseMove(var Message: TWMMouseMove);
  1590. var
  1591. Pos: TPoint;
  1592. Index, NewHotIndex: Integer;
  1593. Rect: TRect;
  1594. Indent: Integer;
  1595. begin
  1596. Pos := SmallPointToPoint(Message.Pos);
  1597. Index := ItemAtPos(Pos, True);
  1598. if FCaptureIndex >= 0 then begin
  1599. if not FSpaceDown and (Index <> FLastMouseMoveIndex) then begin
  1600. if (FLastMouseMoveIndex = FCaptureIndex) or (Index = FCaptureIndex) then
  1601. InvalidateCheck(FCaptureIndex);
  1602. FLastMouseMoveIndex := Index;
  1603. end
  1604. end;
  1605. NewHotIndex := -1;
  1606. if (Index <> -1) and CanFocusItem(Index) then
  1607. begin
  1608. Rect := ItemRect(Index);
  1609. Indent := (FOffset * 2 + FCheckWidth);
  1610. if FWantTabs or ((Pos.X >= Rect.Left + Indent * ItemLevel[Index]) and
  1611. (Pos.X < Rect.Left + Indent * (ItemLevel[Index] + 1))) then
  1612. NewHotIndex := Index;
  1613. end;
  1614. UpdateHotIndex(NewHotIndex);
  1615. end;
  1616. procedure TNewCheckListBox.WMNCHitTest(var Message: TWMNCHitTest);
  1617. var
  1618. I: Integer;
  1619. begin
  1620. inherited;
  1621. if FWantTabs and not (csDesigning in ComponentState) then
  1622. begin
  1623. if Message.Result = HTCLIENT then
  1624. begin
  1625. I := ItemAtPos(ScreenToClient(SmallPointToPoint(Message.Pos)), True);
  1626. if (I < 0) or not CanFocusItem(I) then
  1627. begin
  1628. UpdateHotIndex(-1);
  1629. Message.Result := 12345;
  1630. Exit;
  1631. end;
  1632. end;
  1633. end;
  1634. end;
  1635. procedure TNewCheckListBox.WMSetFocus(var Message: TWMSetFocus);
  1636. begin
  1637. FWheelAccum := 0;
  1638. inherited;
  1639. end;
  1640. procedure TNewCheckListBox.WMSize(var Message: TWMSize);
  1641. var
  1642. I: Integer;
  1643. begin
  1644. inherited;
  1645. { When the scroll bar appears/disappears, the client width changes and we
  1646. must recalculate the height of the items }
  1647. for I := Items.Count-1 downto 0 do
  1648. RemeasureItem(I);
  1649. UpdateScrollRange;
  1650. end;
  1651. procedure TNewCheckListBox.WMThemeChanged(var Message: TMessage);
  1652. begin
  1653. { Don't Run to Cursor into this function, it will interrupt up the theme change }
  1654. UpdateThemeData(True, True);
  1655. inherited;
  1656. end;
  1657. procedure TNewCheckListBox.WMUpdateUIState(var Message: TMessage);
  1658. begin
  1659. Invalidate;
  1660. inherited;
  1661. end;
  1662. procedure TNewCheckListBox.WMGetObject(var Message: TMessage);
  1663. begin
  1664. if (Message.LParam = Integer(OBJID_CLIENT)) and InitializeOleAcc then begin
  1665. if FAccObjectInstance = nil then begin
  1666. try
  1667. FAccObjectInstance := TAccObject.Create(Self);
  1668. except
  1669. inherited;
  1670. Exit;
  1671. end;
  1672. end;
  1673. Message.Result := LresultFromObjectFunc(IID_IAccessible, Message.WParam,
  1674. TAccObject(FAccObjectInstance));
  1675. end
  1676. else
  1677. inherited;
  1678. end;
  1679. { TAccObject }
  1680. constructor TAccObject.Create(AControl: TNewCheckListBox);
  1681. begin
  1682. inherited Create;
  1683. if CreateStdAccessibleObjectFunc(AControl.Handle, Integer(OBJID_CLIENT),
  1684. IID_IAccessible, Pointer(FStdAcc)) <> S_OK then begin
  1685. { Note: The user will never actually see this message since the call to
  1686. TAccObject.Create in TNewCheckListBox.WMGetObject is protected by a
  1687. try..except. }
  1688. raise Exception.Create('CreateStdAccessibleObject failed');
  1689. end;
  1690. FControl := AControl;
  1691. end;
  1692. destructor TAccObject.Destroy;
  1693. begin
  1694. { If FControl is assigned, then we are being destroyed before the control --
  1695. the usual case. Clear FControl's reference to us. }
  1696. if Assigned(FControl) then begin
  1697. FControl.FAccObjectInstance := nil;
  1698. FControl := nil;
  1699. end;
  1700. if Assigned(FStdAcc) then
  1701. FStdAcc.Release;
  1702. inherited;
  1703. end;
  1704. procedure TAccObject.ControlDestroying;
  1705. begin
  1706. { Set FControl to nil, since it's no longer valid }
  1707. FControl := nil;
  1708. { Take this opportunity to disconnect remote clients, i.e. don't allow them
  1709. to call us anymore. This prevents invalid memory accesses if this unit's
  1710. code is in a DLL, and the application subsequently unloads the DLL while
  1711. remote clients still hold (and are using) references to this TAccObject. }
  1712. CoDisconnectObject(Self, 0);
  1713. { NOTE: Don't access Self in any way at this point. The CoDisconnectObject
  1714. call likely caused all references to be relinquished and Self to be
  1715. destroyed. }
  1716. end;
  1717. function TAccObject.QueryInterface(const iid: TIID; var obj): HRESULT;
  1718. begin
  1719. if IsEqualIID(iid, IID_IUnknown) or
  1720. IsEqualIID(iid, IID_IDispatch) or
  1721. IsEqualIID(iid, IID_IAccessible) then begin
  1722. Pointer(obj) := Self;
  1723. AddRef;
  1724. Result := S_OK;
  1725. end
  1726. else begin
  1727. Pointer(obj) := nil;
  1728. Result := E_NOINTERFACE;
  1729. end;
  1730. end;
  1731. function TAccObject.AddRef: Longint;
  1732. begin
  1733. Inc(FRefCount);
  1734. Result := FRefCount;
  1735. end;
  1736. function TAccObject.Release: Longint;
  1737. begin
  1738. Dec(FRefCount);
  1739. Result := FRefCount;
  1740. if Result = 0 then
  1741. Destroy;
  1742. end;
  1743. function TAccObject.GetTypeInfoCount(var ctinfo: Integer): HRESULT;
  1744. begin
  1745. Result := E_NOTIMPL;
  1746. end;
  1747. function TAccObject.GetTypeInfo(itinfo: Integer; lcid: TLCID; var tinfo: ITypeInfo): HRESULT;
  1748. begin
  1749. Result := E_NOTIMPL;
  1750. end;
  1751. function TAccObject.GetIDsOfNames(const iid: TIID; rgszNames: POleStrList;
  1752. cNames: Integer; lcid: TLCID; rgdispid: PDispIDList): HRESULT;
  1753. begin
  1754. Result := E_NOTIMPL;
  1755. end;
  1756. function TAccObject.Invoke(dispIDMember: TDispID; const iid: TIID; lcid: TLCID;
  1757. flags: Word; var dispParams: TDispParams; varResult: PVariant;
  1758. excepInfo: PExcepInfo; argErr: PInteger): HRESULT;
  1759. begin
  1760. Result := E_NOTIMPL;
  1761. end;
  1762. function TAccObject.accDoDefaultAction(varChild: NewOleVariant): HRESULT;
  1763. begin
  1764. { A list box's default action is Double Click, which is useless for a
  1765. list of check boxes. }
  1766. Result := DISP_E_MEMBERNOTFOUND;
  1767. end;
  1768. function TAccObject.accHitTest(xLeft, yTop: Integer;
  1769. var pvarID: NewOleVariant): HRESULT;
  1770. begin
  1771. Result := FStdAcc.accHitTest(xLeft, yTop, pvarID);
  1772. end;
  1773. function TAccObject.accLocation(var pxLeft, pyTop, pcxWidth,
  1774. pcyHeight: Integer; varChild: NewOleVariant): HRESULT;
  1775. begin
  1776. Result := FStdAcc.accLocation(pxLeft, pyTop, pcxWidth, pcyHeight, varChild);
  1777. end;
  1778. function TAccObject.accNavigate(navDir: Integer; varStart: NewOleVariant;
  1779. var pvarEnd: NewOleVariant): HRESULT;
  1780. begin
  1781. Result := FStdAcc.accNavigate(navDir, varStart, pvarEnd);
  1782. end;
  1783. function TAccObject.accSelect(flagsSelect: Integer;
  1784. varChild: NewOleVariant): HRESULT;
  1785. begin
  1786. Result := FStdAcc.accSelect(flagsSelect, varChild);
  1787. end;
  1788. function TAccObject.get_accChild(varChild: NewOleVariant;
  1789. var ppdispChild: IDispatch): HRESULT;
  1790. begin
  1791. Result := FStdAcc.get_accChild(varChild, ppdispChild);
  1792. end;
  1793. function TAccObject.get_accChildCount(var pcountChildren: Integer): HRESULT;
  1794. begin
  1795. Result := FStdAcc.get_accChildCount(pcountChildren);
  1796. end;
  1797. function TAccObject.get_accDefaultAction(varChild: NewOleVariant;
  1798. var pszDefaultAction: NewWideString): HRESULT;
  1799. begin
  1800. { A list box's default action is Double Click, which is useless for a
  1801. list of check boxes. }
  1802. pszDefaultAction := nil;
  1803. Result := S_FALSE;
  1804. end;
  1805. function TAccObject.get_accDescription(varChild: NewOleVariant;
  1806. var pszDescription: NewWideString): HRESULT;
  1807. begin
  1808. Result := FStdAcc.get_accDescription(varChild, pszDescription);
  1809. end;
  1810. function TAccObject.get_accFocus(var pvarID: NewOleVariant): HRESULT;
  1811. begin
  1812. Result := FStdAcc.get_accFocus(pvarID);
  1813. end;
  1814. function TAccObject.get_accHelp(varChild: NewOleVariant;
  1815. var pszHelp: NewWideString): HRESULT;
  1816. begin
  1817. Result := FStdAcc.get_accHelp(varChild, pszHelp);
  1818. end;
  1819. function TAccObject.get_accHelpTopic(var pszHelpFile: NewWideString;
  1820. varChild: NewOleVariant; var pidTopic: Integer): HRESULT;
  1821. begin
  1822. Result := FStdAcc.get_accHelpTopic(pszHelpFile, varChild, pidTopic);
  1823. end;
  1824. function TAccObject.get_accKeyboardShortcut(varChild: NewOleVariant;
  1825. var pszKeyboardShortcut: NewWideString): HRESULT;
  1826. begin
  1827. Result := FStdAcc.get_accKeyboardShortcut(varChild, pszKeyboardShortcut);
  1828. end;
  1829. function TAccObject.get_accName(varChild: NewOleVariant;
  1830. var pszName: NewWideString): HRESULT;
  1831. begin
  1832. Result := FStdAcc.get_accName(varChild, pszName);
  1833. end;
  1834. function TAccObject.get_accParent(var ppdispParent: IDispatch): HRESULT;
  1835. begin
  1836. Result := FStdAcc.get_accParent(ppdispParent);
  1837. end;
  1838. function TAccObject.get_accRole(varChild: NewOleVariant;
  1839. var pvarRole: NewOleVariant): HRESULT;
  1840. begin
  1841. pvarRole.vt := VT_EMPTY;
  1842. if FControl = nil then begin
  1843. Result := E_FAIL;
  1844. Exit;
  1845. end;
  1846. if varChild.vt <> VT_I4 then begin
  1847. Result := E_INVALIDARG;
  1848. Exit;
  1849. end;
  1850. if varChild.lVal = CHILDID_SELF then begin
  1851. pvarRole.lVal := ROLE_SYSTEM_OUTLINE;
  1852. pvarRole.vt := VT_I4;
  1853. Result := S_OK;
  1854. end
  1855. else begin
  1856. try
  1857. case FControl.ItemStates[varChild.lVal-1].ItemType of
  1858. itCheck: pvarRole.lVal := ROLE_SYSTEM_CHECKBUTTON;
  1859. itRadio: pvarRole.lVal := ROLE_SYSTEM_RADIOBUTTON;
  1860. else
  1861. pvarRole.lVal := ROLE_SYSTEM_STATICTEXT;
  1862. end;
  1863. pvarRole.vt := VT_I4;
  1864. Result := S_OK;
  1865. except
  1866. Result := E_INVALIDARG;
  1867. end;
  1868. end;
  1869. end;
  1870. function TAccObject.get_accSelection(var pvarChildren: NewOleVariant): HRESULT;
  1871. begin
  1872. Result := FStdAcc.get_accSelection(pvarChildren);
  1873. end;
  1874. function TAccObject.get_accState(varChild: NewOleVariant;
  1875. var pvarState: NewOleVariant): HRESULT;
  1876. var
  1877. ItemState: TItemState;
  1878. begin
  1879. Result := FStdAcc.get_accState(varChild, pvarState);
  1880. try
  1881. if (Result = S_OK) and (varChild.vt = VT_I4) and
  1882. (varChild.lVal <> CHILDID_SELF) and (pvarState.vt = VT_I4) and
  1883. Assigned(FControl) then begin
  1884. ItemState := FControl.ItemStates[varChild.lVal-1];
  1885. case ItemState.State of
  1886. cbChecked: pvarState.lVal := pvarState.lVal or STATE_SYSTEM_CHECKED;
  1887. cbGrayed: pvarState.lVal := pvarState.lVal or STATE_SYSTEM_MIXED;
  1888. end;
  1889. if not ItemState.Enabled then
  1890. pvarState.lVal := pvarState.lVal or STATE_SYSTEM_UNAVAILABLE;
  1891. end;
  1892. except
  1893. Result := E_INVALIDARG;
  1894. end;
  1895. end;
  1896. function TAccObject.get_accValue(varChild: NewOleVariant;
  1897. var pszValue: NewWideString): HRESULT;
  1898. begin
  1899. pszValue := nil;
  1900. if FControl = nil then begin
  1901. Result := E_FAIL;
  1902. Exit;
  1903. end;
  1904. if varChild.vt <> VT_I4 then begin
  1905. Result := E_INVALIDARG;
  1906. Exit;
  1907. end;
  1908. if varChild.lVal = CHILDID_SELF then
  1909. Result := S_FALSE
  1910. else begin
  1911. { Return the level as the value, like standard tree view controls do.
  1912. Not sure if any screen readers will actually use this, seeing as we
  1913. aren't a real tree view control. }
  1914. try
  1915. pszValue := StringToOleStr(IntToStr(FControl.ItemStates[varChild.lVal-1].Level));
  1916. Result := S_OK;
  1917. except
  1918. Result := E_INVALIDARG;
  1919. end;
  1920. end;
  1921. end;
  1922. function TAccObject.put_accName(varChild: NewOleVariant;
  1923. const pszName: NewWideString): HRESULT;
  1924. begin
  1925. Result := S_FALSE;
  1926. end;
  1927. function TAccObject.put_accValue(varChild: NewOleVariant;
  1928. const pszValue: NewWideString): HRESULT;
  1929. begin
  1930. Result := S_FALSE;
  1931. end;
  1932. procedure Register;
  1933. begin
  1934. RegisterComponents('JR', [TNewCheckListBox]);
  1935. end;
  1936. { Note: This COM initialization code based on code from DBTables }
  1937. var
  1938. SaveInitProc: Pointer;
  1939. NeedToUninitialize: Boolean;
  1940. procedure InitCOM;
  1941. begin
  1942. if SaveInitProc <> nil then TProcedure(SaveInitProc);
  1943. NeedToUninitialize := SUCCEEDED(CoInitialize(nil));
  1944. end;
  1945. initialization
  1946. if not IsLibrary then begin
  1947. SaveInitProc := InitProc;
  1948. InitProc := @InitCOM;
  1949. end;
  1950. InitThemeLibrary;
  1951. NotifyWinEventFunc := GetProcAddress(GetModuleHandle(user32), 'NotifyWinEvent');
  1952. finalization
  1953. if NeedToUninitialize then
  1954. CoUninitialize;
  1955. end.