NortonLikeListView.pas 32 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088
  1. unit NortonLikeListView;
  2. interface
  3. uses
  4. Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  5. ComCtrls, ListViewColProperties, CommCtrl;
  6. type
  7. TCustomNortonLikeListView = class;
  8. TSelectMode = (smAll, smNone, smInvert);
  9. TNortonLikeMode = (nlOn, nlOff, nlKeyboard);
  10. TSelectMethod = (smNoneYet, smMouse, smKeyboard);
  11. TSelectByMaskEvent = procedure(Control: TCustomNortonLikeListView; Select: Boolean) of object;
  12. TCustomNortonLikeListView = class(TCustomListView)
  13. private
  14. { Private declarations }
  15. FColProperties: TCustomListViewColProperties;
  16. FDontSelectItem: Boolean;
  17. FDontUnSelectItem: Boolean;
  18. FSelCount: Integer;
  19. FNortonLike: TNortonLikeMode;
  20. FOnSelectByMask: TSelectByMaskEvent;
  21. FLastDeletedItem: TListItem; // aby sme nepocitali smazany item 2x
  22. FFocusingItem: Boolean;
  23. FManageSelection: Boolean;
  24. FForceUpdateOnItemUnfocus: Boolean;
  25. FFirstSelected: Integer;
  26. FLastSelected: Integer;
  27. FFocused: TDateTime;
  28. FIgnoreSetFocusFrom: THandle;
  29. FSelectingImplicitly: Boolean;
  30. FAnyAndAllSelectedImplicitly: Boolean;
  31. FLButtonDownShiftState: TShiftState;
  32. FLButtonDownPos: TPoint;
  33. FLastSelectMethod: TSelectMethod;
  34. FPendingInternalFocus: TListItem;
  35. procedure WMLButtonDown(var Message: TWMLButtonDown); message WM_LBUTTONDOWN;
  36. procedure WMRButtonDown(var Message: TWMRButtonDown); message WM_RBUTTONDOWN;
  37. procedure WMLButtonUp(var Message: TWMLButtonUp); message WM_LBUTTONUP;
  38. procedure WMKeyDown(var Message: TWMKeyDown); message WM_KEYDOWN;
  39. procedure WMChar(var Message: TWMChar); message WM_CHAR;
  40. procedure WMNotify(var Message: TWMNotify); message WM_NOTIFY;
  41. procedure CNNotify(var Message: TWMNotify); message CN_NOTIFY;
  42. procedure LVMEditLabel(var Message: TMessage); message LVM_EDITLABEL;
  43. procedure WMSetFocus(var Message: TWMSetFocus); message WM_SETFOCUS;
  44. procedure CMWantSpecialKey(var Message: TCMWantSpecialKey); message CM_WANTSPECIALKEY;
  45. function GetMarkedCount: Integer;
  46. function GetMarkedFile: TListItem;
  47. procedure ItemSelected(Item: TListItem; Index: Integer);
  48. procedure ItemUnselected(Item: TListItem; Index: Integer);
  49. procedure SelectAll(Mode: TSelectMode; Exclude: TListItem); reintroduce; overload;
  50. protected
  51. { Protected declarations }
  52. FClearingItems: Boolean;
  53. FUpdatingSelection: Integer;
  54. FNextCharToIgnore: Word;
  55. FHeaderHandle: HWND;
  56. procedure CreateWnd; override;
  57. procedure DestroyWnd; override;
  58. procedure BeginSelectionUpdate; virtual;
  59. procedure EndSelectionUpdate; virtual;
  60. function CanChangeSelection(Item: TListItem; Select: Boolean): Boolean; virtual;
  61. procedure ClearItems; virtual;
  62. procedure ItemsReordered;
  63. procedure ColRightClick(Column: TListColumn; Point: TPoint); override;
  64. procedure Delete(Item: TListItem); override;
  65. function DoSelectByMask(Select: Boolean): Boolean; virtual;
  66. function ExCanChange(Item: TListItem; Change: Integer;
  67. NewState, OldState: Word): Boolean; dynamic;
  68. procedure InsertItem(Item: TListItem); override;
  69. function NewColProperties: TCustomListViewColProperties; virtual;
  70. procedure FocusSomething; virtual;
  71. function EnableDragOnClick: Boolean; virtual;
  72. procedure FocusItem(Item: TListItem);
  73. function GetItemFromHItem(const Item: TLVItem): TListItem;
  74. function GetValid: Boolean; virtual;
  75. function GetSelCount: Integer; override;
  76. procedure DDBeforeDrag;
  77. function CanEdit(Item: TListItem): Boolean; override;
  78. procedure DoEnter; override;
  79. public
  80. { Public declarations }
  81. constructor Create(AOwner: TComponent); override;
  82. destructor Destroy; override;
  83. function ClosestUnselected(Item: TListItem): TListItem;
  84. procedure SelectAll(Mode: TSelectMode); reintroduce; overload;
  85. procedure SelectCurrentItem(FocusNext: Boolean);
  86. function GetNextItem(StartItem: TListItem; Direction: TSearchDirection;
  87. States: TItemStates): TListItem;
  88. property ColProperties: TCustomListViewColProperties read FColProperties write FColProperties stored False;
  89. property MultiSelect default True;
  90. property NortonLike: TNortonLikeMode read FNortonLike write FNortonLike default nlOn;
  91. property OnSelectByMask: TSelectByMaskEvent read FOnSelectByMask write FOnSelectByMask;
  92. property MarkedCount: Integer read GetMarkedCount;
  93. property MarkedFile: TListItem read GetMarkedFile;
  94. property Valid: Boolean read GetValid;
  95. property LastSelectMethod: TSelectMethod read FLastSelectMethod;
  96. end;
  97. type
  98. TNortonLikeListView = class(TCustomNortonLikeListView)
  99. published
  100. { Published declarations }
  101. property Align;
  102. property AllocBy;
  103. property Anchors;
  104. property BiDiMode;
  105. property BorderStyle;
  106. property BorderWidth;
  107. property Checkboxes;
  108. property Color;
  109. property ColumnClick;
  110. property Constraints;
  111. property Ctl3D;
  112. property Enabled;
  113. property Font;
  114. property FlatScrollBars;
  115. property FullDrag;
  116. property GridLines;
  117. property HideSelection;
  118. property HotTrack;
  119. property HotTrackStyles;
  120. property IconOptions;
  121. property Items;
  122. property LargeImages;
  123. property ReadOnly;
  124. property RowSelect;
  125. property ParentBiDiMode;
  126. property ParentColor;
  127. property ParentFont;
  128. property ParentShowHint;
  129. property PopupMenu;
  130. property ShowColumnHeaders;
  131. property ShowHint;
  132. property SmallImages;
  133. property StateImages;
  134. property TabOrder;
  135. property TabStop;
  136. property ViewStyle;
  137. property Visible;
  138. property OnChange;
  139. property OnChanging;
  140. property OnClick;
  141. property OnColumnClick;
  142. property OnCustomDraw;
  143. property OwnerDraw;
  144. property OnCustomDrawItem;
  145. property OnCustomDrawSubItem;
  146. property OwnerData;
  147. property OnGetImageIndex;
  148. property OnCompare;
  149. property OnData;
  150. property OnDataFind;
  151. property OnDataHint;
  152. property OnDataStateChange;
  153. property OnDblClick;
  154. property OnDeletion;
  155. property OnDrawItem;
  156. property OnEdited;
  157. property OnEditing;
  158. property OnEndDock;
  159. property OnEnter;
  160. property OnExit;
  161. property OnInsert;
  162. property OnKeyDown;
  163. property OnKeyPress;
  164. property OnKeyUp;
  165. property OnMouseDown;
  166. property OnMouseMove;
  167. property OnMouseUp;
  168. property OnResize;
  169. property OnStartDock;
  170. property OnSelectItem;
  171. property NortonLike;
  172. property OnSelectByMask;
  173. property ColProperties;
  174. end;
  175. procedure Register;
  176. implementation
  177. uses
  178. PasTools, Types;
  179. procedure Register;
  180. begin
  181. RegisterComponents('Martin', [TNortonLikeListView]);
  182. end;
  183. { TCustomNortonLikeListView }
  184. constructor TCustomNortonLikeListView.Create(AOwner: TComponent);
  185. begin
  186. inherited Create(AOwner);
  187. FSelCount := 0;
  188. FFirstSelected := -1;
  189. FLastSelected := -1;
  190. FClearingItems := False;
  191. MultiSelect := True;
  192. FDontSelectItem := False;
  193. FDontUnSelectItem := False;
  194. FNortonLike := nlOn;
  195. FColProperties := NewColProperties;
  196. FLastDeletedItem := nil;
  197. FUpdatingSelection := 0;
  198. FFocusingItem := False;
  199. FLastSelectMethod := smNoneYet;
  200. FPendingInternalFocus := nil;
  201. // On Windows Vista, native GetNextItem for selection stops working once we
  202. // disallow deselecting any item (see ExCanChange).
  203. // So we need to manage selection state ourselves
  204. // cannot use Win32MajorVersion as it is affected by compatibility mode and
  205. // the bug is present even in compatibility mode
  206. FManageSelection := IsVistaHard;
  207. FFocused := 0;
  208. FIgnoreSetFocusFrom := INVALID_HANDLE_VALUE;
  209. // On Windows 7 we have to force item update when it looses focus,
  210. // otherwise some remnants of focus rectangle remain
  211. // Doing the same on WinXP makes list view down from the item flicker,
  212. // so we avoid this there.
  213. // Not sure about Vista
  214. FForceUpdateOnItemUnfocus := IsWin7;
  215. FNextCharToIgnore := 0;
  216. end;
  217. destructor TCustomNortonLikeListView.Destroy;
  218. begin
  219. FColProperties.Free;
  220. inherited;
  221. end;
  222. procedure TCustomNortonLikeListView.ItemSelected(Item: TListItem; Index: Integer);
  223. begin
  224. Inc(FSelCount);
  225. if FSelectingImplicitly and (FSelCount = 1) then
  226. begin
  227. FAnyAndAllSelectedImplicitly := True;
  228. end
  229. else
  230. if not FSelectingImplicitly then
  231. begin
  232. FAnyAndAllSelectedImplicitly := False;
  233. end;
  234. if FManageSelection then
  235. begin
  236. if Index < 0 then
  237. Index := Item.Index;
  238. if FSelCount = 1 then
  239. begin
  240. Assert(FFirstSelected < 0);
  241. FFirstSelected := Index;
  242. Assert(FLastSelected < 0);
  243. FLastSelected := Index;
  244. end
  245. else
  246. begin
  247. // if reference is not assigned, do not assign it as we
  248. // cannot be sure that the item is actually first/last
  249. if (FFirstSelected >= 0) and (Index < FFirstSelected) then
  250. FFirstSelected := Index;
  251. if (FLastSelected >= 0) and (Index > FLastSelected) then
  252. FLastSelected := Index;
  253. end;
  254. end;
  255. end;
  256. procedure TCustomNortonLikeListView.ItemUnselected(Item: TListItem; Index: Integer);
  257. begin
  258. Dec(FSelCount);
  259. if (FSelCount = 0) or (not FSelectingImplicitly) then
  260. begin
  261. FAnyAndAllSelectedImplicitly := False;
  262. end;
  263. if FManageSelection then
  264. begin
  265. if Index < 0 then
  266. Index := Item.Index;
  267. if FFirstSelected = Index then
  268. begin
  269. if FSelCount = 1 then
  270. FFirstSelected := FLastSelected // may be -1
  271. else
  272. FFirstSelected := -1;
  273. end;
  274. if FLastSelected = Index then
  275. begin
  276. if FSelCount = 1 then
  277. FLastSelected := FFirstSelected // may be -1
  278. else
  279. FLastSelected := -1;
  280. end;
  281. end;
  282. end;
  283. procedure TCustomNortonLikeListView.Delete(Item: TListItem);
  284. begin
  285. if (FLastDeletedItem <> Item) and Item.Selected then
  286. begin
  287. ItemUnselected(Item, -1);
  288. end;
  289. if FPendingInternalFocus = Item then
  290. begin
  291. FPendingInternalFocus := nil;
  292. end;
  293. FLastDeletedItem := Item;
  294. inherited;
  295. FLastDeletedItem := nil;
  296. end;
  297. function TCustomNortonLikeListView.DoSelectByMask(Select: Boolean): Boolean;
  298. begin
  299. if Assigned(FOnSelectByMask) then
  300. begin
  301. FOnSelectByMask(Self, Select);
  302. Result := True;
  303. end
  304. else Result := False;
  305. end;
  306. function TCustomNortonLikeListView.ExCanChange(Item: TListItem; Change: Integer;
  307. NewState, OldState: Word): Boolean;
  308. begin
  309. Assert(Assigned(Item));
  310. Result := True;
  311. if (Change = LVIF_STATE) and
  312. ((((OldState and LVIS_SELECTED) < (NewState and LVIS_SELECTED)) and
  313. (FDontSelectItem or (not CanChangeSelection(Item, True)))) or
  314. (((OldState and LVIS_SELECTED) > (NewState and LVIS_SELECTED)) and
  315. (FDontUnSelectItem or (not CanChangeSelection(Item, False))))) then
  316. begin
  317. if (OldState or LVIS_SELECTED) <> (NewState or LVIS_SELECTED) then
  318. begin
  319. ListView_SetItemState(Handle, Item.Index, NewState,
  320. (NewState or OldState) - LVIS_SELECTED);
  321. end;
  322. Result := False;
  323. end;
  324. end;
  325. function TCustomNortonLikeListView.CanChangeSelection(Item: TListItem;
  326. Select: Boolean): Boolean;
  327. begin
  328. Result := True;
  329. end;
  330. procedure TCustomNortonLikeListView.ClearItems;
  331. begin
  332. Items.BeginUpdate;
  333. try
  334. FClearingItems := True;
  335. Items.Clear;
  336. finally
  337. FSelCount := 0;
  338. if FManageSelection then
  339. begin
  340. FFirstSelected := -1;
  341. FLastSelected := -1;
  342. end;
  343. FClearingItems := False;
  344. Items.EndUpdate;
  345. end;
  346. end; { ClearItems }
  347. procedure TCustomNortonLikeListView.ItemsReordered;
  348. begin
  349. if FManageSelection then
  350. begin
  351. FFirstSelected := -1;
  352. FLastSelected := -1;
  353. end;
  354. end;
  355. procedure TCustomNortonLikeListView.ColRightClick(Column: TListColumn; Point: TPoint);
  356. var
  357. HitInfo: TLVHitTestInfo;
  358. begin
  359. // Fix: Otherwise we get wrong column when view is horizontally scrolled
  360. HitInfo.pt := Point;
  361. if ListView_SubItemHitTest(Handle, @HitInfo) = 0 then
  362. Column := Columns[HitInfo.iSubItem];
  363. inherited ColRightClick(Column, Point);
  364. end;
  365. function TCustomNortonLikeListView.ClosestUnselected(Item: TListItem): TListItem;
  366. var
  367. Index: Integer;
  368. begin
  369. if Assigned(Item) and (Item.Selected or ((NortonLike <> nlOff) and (SelCount = 0))) then
  370. begin
  371. Index := Item.Index + 1;
  372. while (Index < Items.Count) and Items[Index].Selected do Inc(Index);
  373. if (Index >= Items.Count) or Items[Index].Selected then
  374. begin
  375. Index := Item.Index - 1;
  376. while (Index >= 0) and Items[Index].Selected do Dec(Index);
  377. end;
  378. if (Index >= 0) and (Index < Items.Count) and (not Items[Index].Selected) then
  379. Result := Items[Index]
  380. else
  381. Result := nil;
  382. end
  383. else Result := Item;
  384. end;
  385. procedure TCustomNortonLikeListView.WMNotify(var Message: TWMNotify);
  386. var
  387. HDNotify: PHDNotify;
  388. begin
  389. if (FHeaderHandle <> 0) and (Message.NMHdr^.hWndFrom = FHeaderHandle) then
  390. begin
  391. HDNotify := PHDNotify(Message.NMHdr);
  392. // Disallow resizing of "invisible" (width=0) columns.
  393. // (We probably get only Unicode versions of the messages here as
  394. // controls are created as Unicode by VCL)
  395. case HDNotify.Hdr.code of
  396. HDN_BEGINTRACKA, HDN_TRACKA, HDN_BEGINTRACKW, HDN_TRACKW:
  397. if not ColProperties.Visible[HDNotify.Item] then
  398. begin
  399. Message.Result := 1;
  400. Exit;
  401. end;
  402. // We won't get here when user tries to resize the column by mouse,
  403. // as that's prevented above.
  404. // But we get here when other methods are used
  405. // (the only we know about atm is Ctrl-+ shortcut)
  406. // We are getting this notification also when control is being setup,
  407. // with mask including also other fields, not just HDI_WIDTH.
  408. // While it does not seem to hurt to swallow even those messages,
  409. // not sure it's good thing to do, so we swallow width-only messages only.
  410. // That's why there's "= HDI_WIDTH" not "and HDI_WIDTH <> 0".
  411. HDN_ITEMCHANGINGA, HDN_ITEMCHANGINGW:
  412. if (HDNotify.PItem.Mask = HDI_WIDTH) and
  413. (HDNotify.PItem.cxy <> 0) and
  414. (not ColProperties.Visible[HDNotify.Item]) then
  415. begin
  416. Message.Result := 1;
  417. Exit;
  418. end;
  419. end;
  420. end;
  421. inherited;
  422. end;
  423. procedure TCustomNortonLikeListView.DDBeforeDrag;
  424. begin
  425. FDontSelectItem := False;
  426. FDontUnSelectItem := False;
  427. end;
  428. procedure TCustomNortonLikeListView.CNNotify(var Message: TWMNotify);
  429. var
  430. Item: TListItem;
  431. begin
  432. with Message do
  433. case NMHdr^.code of
  434. LVN_ITEMCHANGING:
  435. with PNMListView(NMHdr)^ do
  436. begin
  437. Item := Items[iItem];
  438. if Valid and (not FClearingItems) and (Item <> FLastDeletedItem) and
  439. ((not CanChange(Item, uChanged)) or
  440. (not ExCanChange(Item, uChanged, uNewState, uOldState)))
  441. then
  442. begin
  443. Result := 1;
  444. end;
  445. end;
  446. LVN_ITEMCHANGED:
  447. begin
  448. with PNMListView(NMHdr)^ do
  449. begin
  450. Item := Items[iItem];
  451. if Valid and (not FClearingItems) and
  452. (uChanged = LVIF_STATE) and (Item <> FLastDeletedItem) then
  453. begin
  454. if FForceUpdateOnItemUnfocus and
  455. (NortonLike <> nlOff) and
  456. ((uOldState and LVIS_FOCUSED) > (uNewState and LVIS_FOCUSED)) then
  457. begin
  458. // force update, otherwise some remnants of focus rectangle remain
  459. Item.Update;
  460. end;
  461. if (uOldState and LVIS_SELECTED) <> (uNewState and LVIS_SELECTED) then
  462. begin
  463. if (uOldState and LVIS_SELECTED) <> 0 then
  464. begin
  465. ItemUnselected(Item, iItem);
  466. end
  467. else
  468. begin
  469. ItemSelected(Item, iItem);
  470. end;
  471. end;
  472. end;
  473. end;
  474. inherited;
  475. end;
  476. LVN_ENDLABELEDIT:
  477. begin
  478. FIgnoreSetFocusFrom := ListView_GetEditControl(Handle);
  479. inherited;
  480. end;
  481. else
  482. begin
  483. inherited;
  484. end;
  485. end;
  486. end;
  487. procedure TCustomNortonLikeListView.SelectCurrentItem(FocusNext: Boolean);
  488. var
  489. Item: TListItem;
  490. begin
  491. Item := ItemFocused;
  492. if Item = nil then Item := Items[0];
  493. Item.Selected := not Item.Selected;
  494. if FocusNext then
  495. begin
  496. SendMessage(Handle, WM_KEYDOWN, VK_DOWN, LongInt(0));
  497. end;
  498. end;
  499. procedure TCustomNortonLikeListView.WMKeyDown(var Message: TWMKeyDown);
  500. var
  501. PLastSelectMethod: TSelectMethod;
  502. PDontUnSelectItem: Boolean;
  503. PDontSelectItem: Boolean;
  504. begin
  505. FNextCharToIgnore := 0;
  506. if (NortonLike <> nlOff) and (Message.CharCode = VK_INSERT) then
  507. begin
  508. if Items.Count > 0 then
  509. begin
  510. SelectCurrentItem(True);
  511. Message.Result := 1;
  512. end;
  513. end
  514. else
  515. if Message.CharCode = VK_ADD then
  516. begin
  517. FNextCharToIgnore := Word('+');
  518. inherited;
  519. end
  520. else
  521. if Message.CharCode = VK_SUBTRACT then
  522. begin
  523. FNextCharToIgnore := Word('-');
  524. inherited;
  525. end
  526. else
  527. if Message.CharCode = VK_MULTIPLY then
  528. begin
  529. FNextCharToIgnore := Word('*');
  530. inherited;
  531. end
  532. else
  533. if (NortonLike <> nlOff) and (Message.CharCode in [VK_LEFT, VK_RIGHT]) and
  534. (ViewStyle = vsReport) and
  535. ((GetWindowLong(Handle, GWL_STYLE) and WS_HSCROLL) = 0) then
  536. begin
  537. if Items.Count > 0 then
  538. begin
  539. // do not focus item directly to make later selecting work
  540. if Message.CharCode = VK_LEFT then
  541. SendMessage(Handle, WM_KEYDOWN, VK_HOME, LongInt(0))
  542. else
  543. SendMessage(Handle, WM_KEYDOWN, VK_END, LongInt(0));
  544. end;
  545. Message.Result := 1;
  546. end
  547. else
  548. if (NortonLike <> nlOff) and (Message.CharCode = VK_SPACE) and
  549. ((KeyDataToShiftState(Message.KeyData) * [ssCtrl]) <> []) then
  550. begin
  551. // prevent Ctrl+Space landing in else branch below,
  552. // this can safely get processed by default handler as Ctrl+Space
  553. // toggles only focused item, not affecting others
  554. inherited;
  555. end
  556. else
  557. if (Message.CharCode in [VK_SPACE, VK_PRIOR, VK_NEXT, VK_END, VK_HOME, VK_LEFT,
  558. VK_UP, VK_RIGHT, VK_DOWN, VK_SELECT]) then
  559. begin
  560. PLastSelectMethod := FLastSelectMethod;
  561. PDontSelectItem := FDontSelectItem;
  562. PDontUnSelectItem := FDontUnSelectItem;
  563. FLastSelectMethod := smKeyboard;
  564. FDontSelectItem := FDontSelectItem or
  565. ((NortonLike <> nlOff) and
  566. ((KeyDataToShiftState(Message.KeyData) * [ssShift]) = []));
  567. // Note that Space (selecting toggling) is processed by default handler for WM_CHAR,
  568. // otherwise the below condition would prevent unselection
  569. FDontUnSelectItem :=
  570. FDontUnSelectItem or
  571. (NortonLike = nlOn) or
  572. ((NortonLike = nlKeyboard) and (not FAnyAndAllSelectedImplicitly));
  573. try
  574. inherited;
  575. finally
  576. FDontSelectItem := PDontSelectItem;
  577. FDontUnSelectItem := PDontUnSelectItem;
  578. FLastSelectMethod := PLastSelectMethod;
  579. end;
  580. end
  581. else inherited;
  582. end;
  583. procedure TCustomNortonLikeListView.WMChar(var Message: TWMChar);
  584. var
  585. PLastSelectMethod: TSelectMethod;
  586. PDontUnSelectItem: Boolean;
  587. PDontSelectItem: Boolean;
  588. begin
  589. if Message.CharCode = FNextCharToIgnore then
  590. begin
  591. // ugly fix to avoid Windows beeping when these keys are processed by
  592. // WMKeyDown instead of here (WMChar)
  593. Message.Result := 1;
  594. end
  595. else
  596. if (NortonLike <> nlOff) and (Message.CharCode = Byte(' ')) then
  597. begin
  598. if (GetKeyState(VK_CONTROL) >= 0) then
  599. begin
  600. if Assigned(ItemFocused) then
  601. ItemFocused.Selected := not ItemFocused.Selected;
  602. end
  603. else inherited;
  604. end
  605. else
  606. begin
  607. PLastSelectMethod := FLastSelectMethod;
  608. PDontSelectItem := FDontSelectItem;
  609. PDontUnSelectItem := FDontUnSelectItem;
  610. FDontSelectItem := FDontSelectItem or (NortonLike <> nlOff);
  611. FLastSelectMethod := smKeyboard;
  612. FDontUnSelectItem :=
  613. FDontUnSelectItem or
  614. (NortonLike = nlOn) or
  615. ((NortonLike = nlKeyboard) and (not FAnyAndAllSelectedImplicitly));
  616. try
  617. inherited;
  618. finally
  619. FLastSelectMethod := PLastSelectMethod;
  620. FDontSelectItem := PDontSelectItem;
  621. FDontUnSelectItem := PDontUnSelectItem;
  622. end;
  623. end;
  624. FNextCharToIgnore := 0;
  625. end;
  626. procedure TCustomNortonLikeListView.FocusSomething;
  627. begin
  628. if Valid and (Items.Count > 0) and not Assigned(ItemFocused) then
  629. begin
  630. if (NortonLike <> nlOff) then SendMessage(Handle, WM_KEYDOWN, VK_DOWN, LongInt(0));
  631. if not Assigned(ItemFocused) then
  632. ItemFocused := Items[0];
  633. end;
  634. if Assigned(ItemFocused) then
  635. ItemFocused.MakeVisible(False);
  636. end;
  637. function TCustomNortonLikeListView.EnableDragOnClick: Boolean;
  638. begin
  639. Result := (not FFocusingItem);
  640. end;
  641. procedure TCustomNortonLikeListView.FocusItem(Item: TListItem);
  642. var
  643. P: TPoint;
  644. PLastSelectMethod: TSelectMethod;
  645. PDontUnSelectItem: Boolean;
  646. PDontSelectItem: Boolean;
  647. WParam: UINT_PTR;
  648. LParam: INT_PTR;
  649. begin
  650. // This whole is replacement for mere ItemFocused := Item
  651. // because that does not reset some internal focused pointer,
  652. // causing subsequent Shift-Click selects range from the first item,
  653. // not from focused item.
  654. Item.MakeVisible(False);
  655. if Focused then
  656. begin
  657. P := Item.GetPosition;
  658. PLastSelectMethod := FLastSelectMethod;
  659. PDontSelectItem := FDontSelectItem;
  660. PDontUnSelectItem := FDontUnSelectItem;
  661. FLastSelectMethod := smNoneYet;
  662. FDontSelectItem := True;
  663. FDontUnSelectItem := True;
  664. FFocusingItem := True;
  665. try
  666. // HACK
  667. // WM_LBUTTONDOWN enters loop, waiting for WM_LBUTTONUP,
  668. // so we have to post it in advance to break the loop immediately
  669. // Without MK_CONTROL, if there are more items selected,
  670. // they won't get unselected on subsequent focus change
  671. // (with explorer-style selection).
  672. // And it also makes the click the least obtrusive, affecting the focused
  673. // file only.
  674. WParam := MK_LBUTTON or MK_CONTROL;
  675. LParam := MAKELPARAM(P.X, P.Y);
  676. PostMessage(Handle, WM_LBUTTONUP, WParam, LParam);
  677. SendMessage(Handle, WM_LBUTTONDOWN, WParam, LParam);
  678. finally
  679. FFocusingItem := False;
  680. FLastSelectMethod := PLastSelectMethod;
  681. FDontSelectItem := PDontSelectItem;
  682. FDontUnSelectItem := PDontUnSelectItem;
  683. end;
  684. FPendingInternalFocus := nil;
  685. end
  686. else
  687. begin
  688. FPendingInternalFocus := Item;
  689. end;
  690. if ItemFocused <> Item then
  691. ItemFocused := Item;
  692. end;
  693. procedure TCustomNortonLikeListView.DoEnter;
  694. begin
  695. inherited;
  696. if Assigned(FPendingInternalFocus) then
  697. begin
  698. if FPendingInternalFocus = ItemFocused then
  699. begin
  700. FocusItem(FPendingInternalFocus);
  701. end;
  702. FPendingInternalFocus := nil;
  703. end;
  704. end;
  705. procedure TCustomNortonLikeListView.SelectAll(Mode: TSelectMode; Exclude: TListItem);
  706. var
  707. Index: Integer;
  708. Item: TListItem;
  709. begin
  710. BeginSelectionUpdate;
  711. try
  712. for Index := 0 to Items.Count - 1 do
  713. begin
  714. Item := Items[Index];
  715. if Item <> Exclude then
  716. begin
  717. case Mode of
  718. smAll: Item.Selected := True;
  719. smNone: Item.Selected := False;
  720. smInvert: Item.Selected := not Item.Selected;
  721. end;
  722. end;
  723. end;
  724. finally
  725. EndSelectionUpdate;
  726. end;
  727. end;
  728. procedure TCustomNortonLikeListView.SelectAll(Mode: TSelectMode);
  729. begin
  730. SelectAll(Mode, nil);
  731. end;
  732. procedure TCustomNortonLikeListView.WMLButtonDown(var Message: TWMLButtonDown);
  733. var
  734. PLastSelectMethod: TSelectMethod;
  735. PDontUnSelectItem: Boolean;
  736. PDontSelectItem: Boolean;
  737. PSelectingImplicitly: Boolean;
  738. SelectingImplicitly: Boolean;
  739. Shift: TShiftState;
  740. Item: TListItem;
  741. begin
  742. Shift := KeysToShiftState(Message.Keys);
  743. PLastSelectMethod := FLastSelectMethod;
  744. PDontSelectItem := FDontSelectItem;
  745. PDontUnSelectItem := FDontUnSelectItem;
  746. PSelectingImplicitly := FSelectingImplicitly;
  747. FLastSelectMethod := smMouse;
  748. FDontSelectItem := FDontSelectItem or ((NortonLike = nlOn) and ((Shift * [ssCtrl, ssShift]) = []));
  749. FDontUnSelectItem := FDontUnSelectItem or ((NortonLike = nlOn) and ((Shift * [ssCtrl]) = []));
  750. SelectingImplicitly := ((Shift * [ssCtrl, ssShift]) = []);
  751. if SelectingImplicitly and (NortonLike = nlKeyboard) then
  752. begin
  753. // in general, when clicking, we clear selection only after mouse button is released,
  754. // from within WMLButtonUp, so we know we are not starting dragging,
  755. // so we do not want to clear the selection.
  756. // on the other hand, when clicking outside of the selection,
  757. // we want to explicitly clear the selection, no matter what
  758. Item := GetItemAt(Message.XPos, Message.YPos);
  759. if (Item = nil) or (not Item.Selected) then
  760. SelectAll(smNone);
  761. end;
  762. FSelectingImplicitly := FSelectingImplicitly or SelectingImplicitly;
  763. FLButtonDownShiftState := Shift;
  764. FLButtonDownPos := Point(Message.XPos, Message.YPos);
  765. try
  766. inherited;
  767. finally
  768. FLastSelectMethod := PLastSelectMethod;
  769. FDontSelectItem := PDontSelectItem;
  770. FDontUnSelectItem := PDontUnSelectItem;
  771. FSelectingImplicitly := PSelectingImplicitly;
  772. end;
  773. end;
  774. procedure TCustomNortonLikeListView.WMRButtonDown(var Message: TWMRButtonDown);
  775. var
  776. PLastSelectMethod: TSelectMethod;
  777. PDontUnSelectItem: Boolean;
  778. PDontSelectItem: Boolean;
  779. PSelectingImplicitly: Boolean;
  780. SelectingImplicitly: Boolean;
  781. Shift: TShiftState;
  782. begin
  783. Shift := KeysToShiftState(Message.Keys);
  784. PLastSelectMethod := FLastSelectMethod;
  785. PDontSelectItem := FDontSelectItem;
  786. PDontUnSelectItem := FDontUnSelectItem;
  787. PSelectingImplicitly := FSelectingImplicitly;
  788. FLastSelectMethod := smMouse;
  789. FDontSelectItem := FDontSelectItem or (NortonLike = nlOn);
  790. FDontUnSelectItem := FDontUnSelectItem or (NortonLike = nlOn);
  791. SelectingImplicitly := ((Shift * [ssCtrl, ssShift]) = []);
  792. // TODO unselect all when clicking outside of selection
  793. // (is not done automatically when focused item is not selected)
  794. FSelectingImplicitly := FSelectingImplicitly or SelectingImplicitly;
  795. try
  796. inherited;
  797. finally
  798. FLastSelectMethod := PLastSelectMethod;
  799. FDontSelectItem := PDontSelectItem;
  800. FDontUnSelectItem := PDontUnSelectItem;
  801. FSelectingImplicitly := PSelectingImplicitly;
  802. end;
  803. end;
  804. procedure TCustomNortonLikeListView.WMLButtonUp(var Message: TWMLButtonUp);
  805. var
  806. SelectingImplicitly: Boolean;
  807. Shift: TShiftState;
  808. begin
  809. // Workaround
  810. // For some reason Message.Keys is always 0 here,
  811. // so we use shift state from the LButtonDown as a workaround
  812. Shift := KeysToShiftState(Message.Keys);
  813. SelectingImplicitly :=
  814. ((Shift * [ssCtrl, ssShift]) = []) and
  815. ((FLButtonDownShiftState * [ssCtrl, ssShift]) = []);
  816. if SelectingImplicitly and (csClicked in ControlState) and
  817. (Abs(FLButtonDownPos.X - Message.XPos) <= 4) and
  818. (Abs(FLButtonDownPos.Y - Message.YPos) <= 4) then
  819. begin
  820. SelectAll(smNone, ItemFocused);
  821. // Because condition in ItemSelected is not triggered as we first select
  822. // the new item and then unselect the previous.
  823. // This probably means that we can get rid of the code in ItemSelected.
  824. FAnyAndAllSelectedImplicitly := True;
  825. end;
  826. inherited;
  827. end;
  828. function TCustomNortonLikeListView.GetMarkedFile: TListItem;
  829. begin
  830. if Assigned(Selected) then Result := Selected
  831. else
  832. if Assigned(ItemFocused) and (NortonLike <> nlOff) then Result := ItemFocused
  833. else Result := nil;
  834. end;
  835. function TCustomNortonLikeListView.GetNextItem(StartItem: TListItem;
  836. Direction: TSearchDirection; States: TItemStates): TListItem;
  837. var
  838. Start, Index, First, Last: Integer;
  839. begin
  840. if not FManageSelection then
  841. begin
  842. Result := inherited GetNextItem(StartItem, Direction, States);
  843. end
  844. else
  845. begin
  846. Assert(Direction = sdAll);
  847. if States = [isSelected] then
  848. begin
  849. if FSelCount = 0 then
  850. begin
  851. Result := nil
  852. end
  853. else
  854. if (not Assigned(StartItem)) and (FFirstSelected >= 0) then
  855. begin
  856. Result := Items[FFirstSelected]
  857. end
  858. else
  859. begin
  860. if Assigned(StartItem) then
  861. Start := StartItem.Index
  862. else
  863. Start := -1;
  864. if (FFirstSelected >= 0) and (Start < FFirstSelected) then
  865. First := FFirstSelected
  866. else
  867. First := Start + 1;
  868. if FLastSelected >= 0 then
  869. Last := FLastSelected
  870. else
  871. Last := Items.Count - 1;
  872. if Start > Last then
  873. begin
  874. Result := nil;
  875. end
  876. else
  877. begin
  878. Index := First;
  879. while (Index <= Last) and (not (Items[Index].Selected)) do
  880. begin
  881. Inc(Index);
  882. end;
  883. if Index > Last then
  884. begin
  885. Result := nil;
  886. if Assigned(StartItem) and StartItem.Selected then
  887. begin
  888. Assert((FLastSelected < 0) or (FLastSelected = Start));
  889. FLastSelected := Start;
  890. end;
  891. end
  892. else
  893. begin
  894. Result := Items[Index];
  895. Assert(Result.Selected);
  896. if not Assigned(StartItem) then
  897. begin
  898. Assert((FFirstSelected < 0) or (FFirstSelected = Index));
  899. FFirstSelected := Index;
  900. end;
  901. end;
  902. end;
  903. end;
  904. end
  905. else
  906. if States = [isCut] then
  907. begin
  908. Result := inherited GetNextItem(StartItem, Direction, States);
  909. end
  910. else
  911. begin
  912. Assert(False);
  913. Result := nil;
  914. end;
  915. end;
  916. end;
  917. function TCustomNortonLikeListView.GetSelCount: Integer;
  918. begin
  919. Result := FSelCount;
  920. end;
  921. procedure TCustomNortonLikeListView.InsertItem(Item: TListItem);
  922. begin
  923. inherited;
  924. if Item.Selected then
  925. ItemSelected(Item, -1);
  926. end;
  927. function TCustomNortonLikeListView.NewColProperties: TCustomListViewColProperties;
  928. begin
  929. Result := TListViewColProperties.Create(Self, 5);
  930. end;
  931. function TCustomNortonLikeListView.GetItemFromHItem(const Item: TLVItem): TListItem;
  932. begin
  933. with Item do
  934. if (state and LVIF_PARAM) <> 0 then Result := Pointer(lParam)
  935. else Result := Items[iItem];
  936. end;
  937. function TCustomNortonLikeListView.GetMarkedCount: Integer;
  938. begin
  939. if (SelCount > 0) or (NortonLike = nlOff) then Result := SelCount
  940. else
  941. if Assigned(ItemFocused) then Result := 1
  942. else Result := 0;
  943. end;
  944. function TCustomNortonLikeListView.GetValid: Boolean;
  945. begin
  946. // Note that TCustomDirView::GetValid don't inherit
  947. // this method because of optimalization
  948. Result := (not (csDestroying in ComponentState)) and (not FClearingItems);
  949. end;
  950. procedure TCustomNortonLikeListView.BeginSelectionUpdate;
  951. begin
  952. // Higher value is probably some nesting error
  953. Assert(FUpdatingSelection in [0..4]);
  954. Inc(FUpdatingSelection);
  955. end; { BeginUpdatingSelection }
  956. procedure TCustomNortonLikeListView.EndSelectionUpdate;
  957. begin
  958. Assert(FUpdatingSelection > 0);
  959. Dec(FUpdatingSelection);
  960. end; { EndUpdatingSelection }
  961. procedure TCustomNortonLikeListView.CreateWnd;
  962. begin
  963. try
  964. Assert(ColProperties <> nil);
  965. inherited;
  966. FHeaderHandle := ListView_GetHeader(Handle);
  967. ColProperties.ListViewWndCreated;
  968. finally
  969. end;
  970. end;
  971. procedure TCustomNortonLikeListView.DestroyWnd;
  972. begin
  973. ColProperties.ListViewWndDestroying;
  974. try
  975. inherited;
  976. finally
  977. ColProperties.ListViewWndDestroyed;
  978. end;
  979. end;
  980. procedure TCustomNortonLikeListView.LVMEditLabel(var Message: TMessage);
  981. begin
  982. // explicitly requesting editing (e.g. F2),
  983. // so we do not care anymore when the view was focused
  984. FFocused := 0;
  985. inherited;
  986. end;
  987. function TCustomNortonLikeListView.CanEdit(Item: TListItem): Boolean;
  988. var
  989. N: TDateTime;
  990. Delta: Double;
  991. begin
  992. N := Now;
  993. Result := inherited CanEdit(Item);
  994. if Result and (FFocused > 0) then
  995. begin
  996. Delta := N - FFocused;
  997. // it takes little more than 500ms to trigger editing after click
  998. Result := Delta > (750.0/MSecsPerDay);
  999. end;
  1000. FFocused := 0;
  1001. end;
  1002. procedure TCustomNortonLikeListView.WMSetFocus(var Message: TWMSetFocus);
  1003. begin
  1004. inherited;
  1005. if Message.FocusedWnd <> FIgnoreSetFocusFrom then
  1006. FFocused := Now;
  1007. end;
  1008. procedure TCustomNortonLikeListView.CMWantSpecialKey(var Message: TCMWantSpecialKey);
  1009. begin
  1010. inherited;
  1011. if IsEditing and (Message.CharCode = VK_TAB) then
  1012. Message.Result := 1;
  1013. end;
  1014. end.