Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

606 lines
14 KiB

  1. /*****************************************************************************
  2. *
  3. * treelist.cpp
  4. *
  5. * A tree-like listview. (Worst of both worlds!)
  6. *
  7. *****************************************************************************/
  8. //
  9. // state icon: Doesn't get ugly highlight when selected
  10. // but indent doesn't work unless there is a small imagelist
  11. //
  12. // image: gets ugly highlight
  13. // but at least indent works
  14. #include "sdview.h"
  15. TreeItem *TreeItem::NextVisible()
  16. {
  17. if (IsExpanded()) {
  18. return FirstChild();
  19. }
  20. TreeItem *pti = this;
  21. do {
  22. if (pti->NextSibling()) {
  23. return pti->NextSibling();
  24. }
  25. pti = pti->Parent();
  26. } while (pti);
  27. return NULL;
  28. }
  29. BOOL TreeItem::IsVisibleOrRoot()
  30. {
  31. TreeItem *pti = Parent();
  32. while (pti) {
  33. ASSERT(pti->IsExpandable());
  34. if (!pti->IsExpanded())
  35. {
  36. return FALSE;
  37. }
  38. pti = pti->Parent();
  39. }
  40. // Made it all the way to the root without incident
  41. return TRUE;
  42. }
  43. BOOL TreeItem::IsVisible()
  44. {
  45. TreeItem *pti = Parent();
  46. //
  47. // The root itself is not visible.
  48. //
  49. if (!pti) {
  50. return FALSE;
  51. }
  52. return IsVisibleOrRoot();
  53. }
  54. Tree::Tree(TreeItem *ptiRoot)
  55. : _ptiRoot(ptiRoot)
  56. , _iHint(-1)
  57. , _ptiHint(ptiRoot)
  58. {
  59. if (_ptiRoot) {
  60. _ptiRoot->_ptiChild = PTI_ONDEMAND;
  61. _ptiRoot->_iVisIndex = -1;
  62. _ptiRoot->_iDepth = -1;
  63. }
  64. }
  65. Tree::~Tree()
  66. {
  67. DeleteNode(_ptiRoot);
  68. }
  69. void Tree::SetHWND(HWND hwnd)
  70. {
  71. _hwnd = hwnd;
  72. SHFILEINFO sfi;
  73. HIMAGELIST himl = ImageList_LoadBitmap(g_hinst, MAKEINTRESOURCE(IDB_PLUS),
  74. 16, 0, RGB(0xFF, 0x00, 0xFF));
  75. ListView_SetImageList(_hwnd, himl, LVSIL_STATE);
  76. ListView_SetCallbackMask(_hwnd, LVIS_STATEIMAGEMASK | LVIS_OVERLAYMASK);
  77. }
  78. HIMAGELIST Tree::SetImageList(HIMAGELIST himl)
  79. {
  80. return RECAST(HIMAGELIST, ListView_SetImageList(_hwnd, himl, LVSIL_SMALL));
  81. }
  82. LRESULT Tree::SendNotify(int code, NMHDR *pnm)
  83. {
  84. pnm->hwndFrom = _hwnd;
  85. pnm->code = code;
  86. pnm->idFrom = GetDlgCtrlID(_hwnd);
  87. return ::SendMessage(GetParent(_hwnd), WM_NOTIFY, pnm->idFrom, RECAST(LPARAM, pnm));
  88. }
  89. LRESULT Tree::OnCacheHint(NMLVCACHEHINT *phint)
  90. {
  91. _ptiHint = IndexToItem(phint->iFrom);
  92. _iHint = phint->iFrom;
  93. return 0;
  94. }
  95. //
  96. // pti = the first item that needs to be recalced
  97. //
  98. void Tree::Recalc(TreeItem *pti)
  99. {
  100. int iItem = pti->_iVisIndex;
  101. if (_iHint > iItem) {
  102. _iHint = iItem;
  103. _ptiHint = pti;
  104. }
  105. do {
  106. pti->_iVisIndex = iItem;
  107. pti = pti->NextVisible();
  108. iItem++;
  109. } while (pti);
  110. }
  111. TreeItem* Tree::IndexToItem(int iItem)
  112. {
  113. int iHave;
  114. TreeItem *ptiHave;
  115. if (iItem >= _iHint && _ptiHint) {
  116. iHave = _iHint;
  117. ptiHave = _ptiHint;
  118. ASSERT(ptiHave->_iVisIndex == iHave);
  119. } else {
  120. iHave = -1;
  121. ptiHave = _ptiRoot;
  122. }
  123. while (iHave < iItem && ptiHave) {
  124. ASSERT(ptiHave->_iVisIndex == iHave);
  125. ptiHave = ptiHave->NextVisible();
  126. iHave++;
  127. }
  128. return ptiHave;
  129. }
  130. int Tree::InsertListviewItem(int iItem)
  131. {
  132. LVITEM lvi;
  133. lvi.iItem = iItem;
  134. lvi.iSubItem = 0;
  135. lvi.mask = 0;
  136. return ListView_InsertItem(_hwnd, &lvi);
  137. }
  138. BOOL Tree::Insert(TreeItem *pti, TreeItem *ptiParent, TreeItem *ptiAfter)
  139. {
  140. pti->_ptiParent = ptiParent;
  141. TreeItem **pptiUpdate;
  142. // Convenience: PTI_APPEND appends as last child
  143. if (ptiAfter == PTI_APPEND) {
  144. ptiAfter = ptiParent->FirstChild();
  145. if (ptiAfter == PTI_ONDEMAND) {
  146. ptiAfter = NULL;
  147. } else if (ptiAfter) {
  148. while (ptiAfter->NextSibling()) {
  149. ptiAfter = ptiAfter->NextSibling();
  150. }
  151. }
  152. }
  153. if (ptiAfter) {
  154. pti->_iVisIndex = ptiAfter->_iVisIndex + 1;
  155. pptiUpdate = &ptiAfter->_ptiNext;
  156. } else {
  157. pti->_iVisIndex = ptiParent->_iVisIndex + 1;
  158. pptiUpdate = &ptiParent->_ptiChild;
  159. if (ptiParent->_ptiChild == PTI_ONDEMAND) {
  160. ptiParent->_ptiChild = NULL;
  161. }
  162. }
  163. if (ptiParent->IsExpanded()) {
  164. if (InsertListviewItem(pti->_iVisIndex) < 0) {
  165. return FALSE;
  166. }
  167. ptiParent->_cVisKids++;
  168. }
  169. pti->_ptiNext = *pptiUpdate;
  170. *pptiUpdate = pti;
  171. pti->_iDepth = ptiParent->_iDepth + 1;
  172. if (ptiParent->IsExpanded()) {
  173. Recalc(pti);
  174. }
  175. return TRUE;
  176. }
  177. //
  178. // Update the visible kids count for pti and all its parents.
  179. // Sop when we find a node that is collapsed (which means
  180. // the visible kids counter is no longer being kept track of).
  181. //
  182. void Tree::UpdateVisibleCounts(TreeItem *pti, int cDelta)
  183. {
  184. //
  185. // Earlying-out the cDelta==0 case is a clear optimization,
  186. // and it's actually important in the goofy scenario where
  187. // an expand failed (so the item being updated isn't even
  188. // expandable any more).
  189. //
  190. if (cDelta) {
  191. do {
  192. ASSERT(pti->IsExpandable());
  193. pti->_cVisKids += cDelta;
  194. pti = pti->Parent();
  195. } while (pti && pti->IsExpanded());
  196. }
  197. }
  198. int Tree::Expand(TreeItem *ptiRoot)
  199. {
  200. if (ptiRoot->IsExpanded()) {
  201. return 0;
  202. }
  203. if (!ptiRoot->IsExpandable()) {
  204. return 0;
  205. }
  206. if (ptiRoot->FirstChild() == PTI_ONDEMAND) {
  207. NMTREELIST tl;
  208. tl.pti = ptiRoot;
  209. SendNotify(TLN_FILLCHILDREN, &tl.hdr);
  210. //
  211. // If the callback failed to insert any items, then turn the
  212. // entry into an unexpandable item. (We need to redraw it
  213. // so the new button shows up.)
  214. //
  215. if (ptiRoot->FirstChild() == PTI_ONDEMAND) {
  216. ptiRoot->SetNotExpandable();
  217. }
  218. }
  219. BOOL fRootVisible = ptiRoot->IsVisibleOrRoot();
  220. TreeItem *pti = ptiRoot->FirstChild();
  221. int iNewIndex = ptiRoot->_iVisIndex + 1;
  222. int cExpanded = 0;
  223. while (pti) {
  224. cExpanded += 1 + pti->_cVisKids;
  225. if (fRootVisible) {
  226. // Start at -1 so we also include the item itself
  227. for (int i = -1; i < pti->_cVisKids; i++) {
  228. InsertListviewItem(iNewIndex);
  229. iNewIndex++;
  230. }
  231. }
  232. pti = pti->NextSibling();
  233. }
  234. UpdateVisibleCounts(ptiRoot, cExpanded);
  235. if (fRootVisible) {
  236. Recalc(ptiRoot);
  237. // Also need to redraw the root item because its button changed
  238. ListView_RedrawItems(_hwnd, ptiRoot->_iVisIndex, ptiRoot->_iVisIndex);
  239. }
  240. return cExpanded;
  241. }
  242. int Tree::Collapse(TreeItem *ptiRoot)
  243. {
  244. if (!ptiRoot->IsExpanded()) {
  245. return 0;
  246. }
  247. if (!ptiRoot->IsExpandable()) {
  248. return 0;
  249. }
  250. TreeItem *pti = ptiRoot->FirstChild();
  251. int iDelIndex = ptiRoot->_iVisIndex + 1;
  252. int cCollapsed = 0;
  253. BOOL fRootVisible = ptiRoot->IsVisibleOrRoot();
  254. //
  255. // HACKHACK for some reason, listview in ownerdata mode animates
  256. // deletes but not insertions. What's worse, the deletion animation
  257. // occurs even if the item being deleted isn't even visible (because
  258. // we deleted a screenful of items ahead of it). So let's just disable
  259. // redraws while doing collapses.
  260. //
  261. if (fRootVisible) {
  262. SetWindowRedraw(_hwnd, FALSE);
  263. }
  264. while (pti) {
  265. cCollapsed += 1 + pti->_cVisKids;
  266. if (fRootVisible) {
  267. // Start at -1 so we also include the item itself
  268. for (int i = -1; i < pti->_cVisKids; i++) {
  269. ListView_DeleteItem(_hwnd, iDelIndex);
  270. }
  271. }
  272. pti = pti->NextSibling();
  273. }
  274. UpdateVisibleCounts(ptiRoot, -cCollapsed);
  275. if (fRootVisible) {
  276. Recalc(ptiRoot);
  277. // Also need to redraw the root item because its button changed
  278. ListView_RedrawItems(_hwnd, ptiRoot->_iVisIndex, ptiRoot->_iVisIndex);
  279. SetWindowRedraw(_hwnd, TRUE);
  280. }
  281. return cCollapsed;
  282. }
  283. int Tree::ToggleExpand(TreeItem *pti)
  284. {
  285. if (pti->IsExpandable()) {
  286. if (pti->IsExpanded()) {
  287. return -Collapse(pti);
  288. } else {
  289. return Expand(pti);
  290. }
  291. }
  292. return 0;
  293. }
  294. void Tree::RedrawItem(TreeItem *pti)
  295. {
  296. if (pti->IsVisible()) {
  297. ListView_RedrawItems(_hwnd, pti->_iVisIndex, pti->_iVisIndex);
  298. }
  299. }
  300. LRESULT Tree::OnClick(NMITEMACTIVATE *pia)
  301. {
  302. if (pia->iSubItem == 0) {
  303. // Maybe it was a click on the +/- button
  304. LVHITTESTINFO hti;
  305. hti.pt = pia->ptAction;
  306. ListView_HitTest(_hwnd, &hti);
  307. if (hti.flags & (LVHT_ONITEMICON | LVHT_ONITEMSTATEICON)) {
  308. TreeItem *pti = IndexToItem(pia->iItem);
  309. if (pti) {
  310. ToggleExpand(pti);
  311. }
  312. }
  313. }
  314. return 0;
  315. }
  316. LRESULT Tree::OnItemActivate(int iItem)
  317. {
  318. NMTREELIST tl;
  319. tl.pti = IndexToItem(iItem);
  320. if (tl.pti) {
  321. SendNotify(TLN_ITEMACTIVATE, &tl.hdr);
  322. }
  323. return 0;
  324. }
  325. //
  326. // Classic treeview keys:
  327. //
  328. // Ctrl+(Left, Right, PgUp, Home, PgDn, End, Up, Down) = scroll the
  329. // window without changing selection.
  330. //
  331. // Enter = activate
  332. // PgUp, PgDn, Home, End = navigate
  333. // Numpad+, Numpad- = expand/collapse
  334. // Numpad* = expand all
  335. // Left = collapse focus item or move to parent
  336. // Right = expand focus item or move down
  337. // Backspace = move to parent
  338. //
  339. // We don't mimic it perfectly, but we get close enough that hopefully
  340. // nobody will notice.
  341. //
  342. LRESULT Tree::OnKeyDown(NMLVKEYDOWN *pkd)
  343. {
  344. if (GetKeyState(VK_CONTROL) < 0) {
  345. // Allow key to go through - listview will do the work
  346. } else {
  347. TreeItem *pti;
  348. switch (pkd->wVKey) {
  349. case VK_ADD:
  350. pti = GetCurSel();
  351. if (pti) {
  352. Expand(pti);
  353. }
  354. return 1;
  355. case VK_SUBTRACT:
  356. pti = GetCurSel();
  357. if (pti) {
  358. Collapse(pti);
  359. }
  360. return 1;
  361. case VK_LEFT:
  362. pti = GetCurSel();
  363. if (pti) {
  364. if (pti->IsExpanded()) {
  365. Collapse(pti);
  366. } else {
  367. SetCurSel(pti->Parent());
  368. }
  369. }
  370. return 1;
  371. case VK_BACK:
  372. pti = GetCurSel();
  373. if (pti) {
  374. SetCurSel(pti->Parent());
  375. }
  376. return 1;
  377. case VK_RIGHT:
  378. pti = GetCurSel();
  379. if (pti) {
  380. if (!Expand(pti)) {
  381. pti = pti->NextVisible();
  382. if (pti) {
  383. SetCurSel(pti);
  384. }
  385. }
  386. }
  387. return 1;
  388. }
  389. }
  390. return 0;
  391. }
  392. //
  393. // Convert the item number into a tree item.
  394. //
  395. LRESULT Tree::OnGetDispInfo(NMLVDISPINFO *plvd)
  396. {
  397. TreeItem *pti = IndexToItem(plvd->item.iItem);
  398. ASSERT(pti);
  399. if (!pti) {
  400. return 0;
  401. }
  402. if (plvd->item.mask & LVIF_STATE) {
  403. if (pti->IsExpandable()) {
  404. // State images are 1-based
  405. plvd->item.state |= INDEXTOSTATEIMAGEMASK(pti->IsExpanded() ? 1 : 2);
  406. }
  407. }
  408. if (plvd->item.mask & LVIF_INDENT) {
  409. plvd->item.iIndent = pti->_iDepth;
  410. }
  411. NMTREELIST tl;
  412. tl.pti = pti;
  413. if (plvd->item.mask & (LVIF_IMAGE | LVIF_STATE)) {
  414. tl.iSubItem = -1;
  415. tl.cchTextMax = 0;
  416. SendNotify(TLN_GETDISPINFO, &tl.hdr);
  417. plvd->item.iImage = tl.iSubItem;
  418. if (plvd->item.stateMask & LVIS_OVERLAYMASK) {
  419. plvd->item.state |= tl.cchTextMax;
  420. }
  421. }
  422. if (plvd->item.mask & LVIF_TEXT) {
  423. tl.iSubItem = plvd->item.iSubItem;
  424. tl.pszText = plvd->item.pszText;
  425. tl.cchTextMax = plvd->item.cchTextMax;
  426. SendNotify(TLN_GETDISPINFO, &tl.hdr);
  427. plvd->item.pszText = tl.pszText;
  428. }
  429. return 0;
  430. }
  431. LRESULT Tree::OnGetInfoTip(NMLVGETINFOTIP *pgit)
  432. {
  433. TreeItem *pti = IndexToItem(pgit->iItem);
  434. ASSERT(pti);
  435. if (pti) {
  436. NMTREELIST tl;
  437. tl.pti = pti;
  438. tl.pszText = pgit->pszText;
  439. tl.cchTextMax = pgit->cchTextMax;
  440. SendNotify(TLN_GETINFOTIP, &tl.hdr);
  441. pgit->pszText = tl.pszText;
  442. }
  443. return 0;
  444. }
  445. LRESULT Tree::OnGetContextMenu(int iItem)
  446. {
  447. TreeItem *pti = IndexToItem(iItem);
  448. ASSERT(pti);
  449. if (pti) {
  450. NMTREELIST tl;
  451. tl.pti = pti;
  452. return SendNotify(TLN_GETCONTEXTMENU, &tl.hdr);
  453. }
  454. return 0;
  455. }
  456. LRESULT Tree::OnCopyToClipboard(int iMin, int iMax)
  457. {
  458. TreeItem *pti = IndexToItem(iMin);
  459. ASSERT(pti);
  460. if (pti) {
  461. TreeItem *ptiMax = IndexToItem(iMax);
  462. String str;
  463. while (pti != ptiMax) {
  464. NMTREELIST tl;
  465. tl.pti = pti;
  466. tl.pszText = NULL;
  467. tl.cchTextMax = 0;
  468. SendNotify(TLN_GETINFOTIP, &tl.hdr);
  469. if (tl.pszText) {
  470. str << tl.pszText << TEXT("\r\n");
  471. }
  472. pti = pti->NextVisible();
  473. }
  474. SetClipboardText(_hwnd, str);
  475. }
  476. return 0;
  477. }
  478. TreeItem *Tree::GetCurSel()
  479. {
  480. int iItem = ListView_GetCurSel(_hwnd);
  481. if (iItem >= 0) {
  482. return IndexToItem(iItem);
  483. }
  484. return NULL;
  485. }
  486. void Tree::SetCurSel(TreeItem *pti)
  487. {
  488. if (pti->IsVisible()) {
  489. ListView_SetCurSel(_hwnd, pti->_iVisIndex);
  490. ListView_EnsureVisible(_hwnd, pti->_iVisIndex, FALSE);
  491. }
  492. }
  493. void Tree::DeleteNode(TreeItem *pti)
  494. {
  495. if (pti) {
  496. // Nuke all the kids, recursively
  497. TreeItem *ptiKid = pti->FirstChild();
  498. if (!ptiKid->IsSentinel()) {
  499. do {
  500. TreeItem *ptiNext = ptiKid->NextSibling();
  501. DeleteNode(ptiKid);
  502. ptiKid = ptiNext;
  503. } while (ptiKid);
  504. }
  505. // This is moved to a subroutine so we don't eat stack
  506. // in this highly-recursive function.
  507. SendDeleteNotify(pti);
  508. }
  509. }
  510. void Tree::SendDeleteNotify(TreeItem *pti)
  511. {
  512. NMTREELIST tl;
  513. tl.pti = pti;
  514. SendNotify(TLN_DELETEITEM, &tl.hdr);
  515. }