Leaked source code of windows server 2003
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.

1360 lines
39 KiB

  1. #include "ctlspriv.h"
  2. #include "treeview.h"
  3. BOOL NEAR TV_EnsureVisible(PTREE pTree, TREEITEM FAR * hItem);
  4. // ----------------------------------------------------------------------------
  5. //
  6. // Updates the iShownIndex for every item below (in list order) a given item
  7. //
  8. // ----------------------------------------------------------------------------
  9. int NEAR TV_UpdateShownIndexes(PTREE pTree, HTREEITEM hWalk)
  10. {
  11. WORD iShownIndex;
  12. if (hWalk == pTree->hRoot) {
  13. hWalk = pTree->hRoot->hKids;
  14. if (hWalk) {
  15. hWalk->iShownIndex = 0;
  16. } else {
  17. return -1;
  18. }
  19. }
  20. iShownIndex = hWalk->iShownIndex + hWalk->iIntegral;
  21. if (iShownIndex <= 0)
  22. {
  23. // BUGBUG: We should #define the special TVITEM_HIDDEN value and check
  24. // for it explicitly
  25. // This can happen if TV_SortCB passes in a hidden item
  26. return(-1);
  27. }
  28. while ((hWalk = TV_GetNextVisItem(hWalk)) != NULL) {
  29. hWalk->iShownIndex = iShownIndex;
  30. iShownIndex += (WORD) hWalk->iIntegral;
  31. }
  32. //#ifdef DEBUG
  33. // TraceMsg(TF_TREEVIEW, "tv: updated show indexes (now %d items)", (int)iShownIndex);
  34. //#endif
  35. return (int)iShownIndex;
  36. }
  37. //
  38. // in:
  39. // hItem expanded node to count decendants of
  40. //
  41. // returns:
  42. // total number of expanded descendants below the given item.
  43. //
  44. UINT NEAR TV_CountVisibleDescendants(HTREEITEM hItem)
  45. {
  46. UINT cnt;
  47. for (cnt = 0, hItem = hItem->hKids; hItem; hItem = hItem->hNext)
  48. {
  49. cnt += hItem->iIntegral;
  50. if (hItem->hKids && (hItem->state & TVIS_EXPANDED))
  51. cnt += TV_CountVisibleDescendants(hItem);
  52. }
  53. return cnt;
  54. }
  55. // scrolls nItems in the direction of fDown starting from iTopShownIndex
  56. void TV_ScrollItems(PTREE pTree, int nItems, int iTopShownIndex, BOOL fDown)
  57. {
  58. RECT rc;
  59. rc.left = 0;
  60. rc.top = (iTopShownIndex+1) * pTree->cyItem;
  61. rc.right = pTree->cxWnd;
  62. rc.bottom = pTree->cyWnd;
  63. {
  64. SMOOTHSCROLLINFO si =
  65. {
  66. sizeof(si),
  67. SSIF_MINSCROLL | SSIF_MAXSCROLLTIME,
  68. pTree->ci.hwnd,
  69. 0,
  70. ((fDown)?1:-1) * nItems * pTree->cyItem,
  71. &rc,
  72. &rc,
  73. NULL,
  74. NULL,
  75. SW_ERASE|SW_INVALIDATE,
  76. pTree->uMaxScrollTime,
  77. 1,
  78. 1
  79. };
  80. SmoothScrollWindow(&si);
  81. }
  82. TV_UpdateToolTip(pTree);
  83. }
  84. //
  85. // If fRedrawParent is FALSE, then the return value is garbage.
  86. // If fRedrawParent is TRUE, then returns the number of children scrolled.
  87. //
  88. // Does not update iShownIndex for any items.
  89. //
  90. UINT NEAR TV_ScrollBelow(PTREE pTree, HTREEITEM hItem, BOOL fRedrawParent, BOOL fDown)
  91. {
  92. int iTop;
  93. UINT cnt;
  94. // Do nothing if the item is not visible
  95. if (!ITEM_VISIBLE(hItem))
  96. return 0;
  97. cnt = hItem->iIntegral; // default return val
  98. if (pTree->fRedraw) {
  99. UINT cVisDesc;
  100. BOOL fEffect;
  101. // iTop is the top edge (client coordinates) of the bottom integral
  102. // cell of the item that just got expanded/contracted.
  103. // (Confused yet? I sure am.)
  104. iTop = hItem->iShownIndex - pTree->hTop->iShownIndex + hItem->iIntegral - 1;
  105. cVisDesc = TV_CountVisibleDescendants(hItem);
  106. // See if the item being expanded/contracted has any effect on the
  107. // screen. If not, then don't TV_ScrollItems or we will end up
  108. // double-counting them when we do post-scroll adjustment.
  109. if (fDown)
  110. {
  111. // When scrolling down, we have an effect if the item that just
  112. // got expanded was below the top of the screen
  113. fEffect = iTop >= 0;
  114. }
  115. else
  116. {
  117. // When scrolling up, we have an effect if any of the items
  118. // that just got collapsed out were below the top of the screen
  119. fEffect = (int)(iTop + cVisDesc) >= 0;
  120. }
  121. if (fEffect)
  122. TV_ScrollItems(pTree, cVisDesc, iTop, fDown);
  123. TV_InvalidateItem(pTree, hItem, TRUE);
  124. if (fRedrawParent)
  125. cnt = cVisDesc;
  126. } else {
  127. if (fRedrawParent)
  128. cnt = TV_CountVisibleDescendants(hItem);
  129. }
  130. return(cnt);
  131. }
  132. // The FakeCustomDraw functions are used when you want the customdraw client
  133. // to set up a HDC so you can do stuff like GetTextExtent.
  134. //
  135. // Usage:
  136. //
  137. // TVFAKEDRAW tvfd;
  138. // TreeView_BeginFakeCustomDraw(pTree, &tvfd);
  139. // for each item you care about {
  140. // TreeView_BeginFakeItemDraw(&tvfd, hitem);
  141. // <party on the HDC in tvfd.nmcd.nmcd.hdc>
  142. // TreeView_EndFakeItemDraw(&tvfd);
  143. // }
  144. // TreeView_EndFakeCustomDraw(&tvfd);
  145. //
  146. void TreeView_BeginFakeCustomDraw(PTREE pTree, PTVFAKEDRAW ptvfd)
  147. {
  148. ptvfd->nmcd.nmcd.hdc = GetDC(pTree->ci.hwnd);
  149. ptvfd->nmcd.nmcd.uItemState = 0;
  150. ptvfd->nmcd.nmcd.dwItemSpec = 0;
  151. ptvfd->nmcd.nmcd.lItemlParam = 0;
  152. ptvfd->hfontPrev = (HFONT)GetCurrentObject(ptvfd->nmcd.nmcd.hdc, OBJ_FONT);
  153. //
  154. // Since we aren't actually painting anything, we pass an empty
  155. // paint rectangle. Gosh, I hope no app faults when it sees an
  156. // empty paint rectangle.
  157. //
  158. SetRectEmpty(&ptvfd->nmcd.nmcd.rc);
  159. ptvfd->pTree = pTree;
  160. ptvfd->dwCustomPrev = pTree->ci.dwCustom;
  161. pTree->ci.dwCustom = CIFakeCustomDrawNotify(&pTree->ci, CDDS_PREPAINT, &ptvfd->nmcd.nmcd);
  162. }
  163. DWORD TreeView_BeginFakeItemDraw(PTVFAKEDRAW ptvfd, HTREEITEM hitem)
  164. {
  165. PTREE pTree = ptvfd->pTree;
  166. // Note that if the client says CDRF_SKIPDEFAULT (i.e., is owner-draw)
  167. // we measure the item anyway, because that's what IE4 did.
  168. ptvfd->nmcd.nmcd.dwItemSpec = (DWORD_PTR)hitem;
  169. ptvfd->nmcd.nmcd.lItemlParam = hitem->lParam;
  170. if (hitem->state & TVIS_BOLD) {
  171. SelectFont(ptvfd->nmcd.nmcd.hdc, pTree->hFontBold);
  172. } else {
  173. SelectFont(ptvfd->nmcd.nmcd.hdc, pTree->hFont);
  174. }
  175. if (!(pTree->ci.dwCustom & CDRF_SKIPDEFAULT)) {
  176. // Font should not depend on colors or flags since those change
  177. // dynamically but we cache the width info forever. So we don't
  178. // need to set up uItemState.
  179. ptvfd->nmcd.clrText = pTree->clrText;
  180. ptvfd->nmcd.clrTextBk = pTree->clrBk;
  181. ptvfd->nmcd.iLevel = hitem->iLevel;
  182. ptvfd->dwCustomItem = CIFakeCustomDrawNotify(&pTree->ci, CDDS_ITEMPREPAINT, &ptvfd->nmcd.nmcd);
  183. } else {
  184. ptvfd->dwCustomItem = CDRF_DODEFAULT;
  185. }
  186. return ptvfd->dwCustomItem;
  187. }
  188. void TreeView_EndFakeItemDraw(PTVFAKEDRAW ptvfd)
  189. {
  190. PTREE pTree = ptvfd->pTree;
  191. if (!(ptvfd->dwCustomItem & CDRF_SKIPDEFAULT) &&
  192. (ptvfd->dwCustomItem & CDRF_NOTIFYPOSTPAINT)) {
  193. CIFakeCustomDrawNotify(&pTree->ci, CDDS_ITEMPOSTPAINT, &ptvfd->nmcd.nmcd);
  194. }
  195. }
  196. void TreeView_EndFakeCustomDraw(PTVFAKEDRAW ptvfd)
  197. {
  198. PTREE pTree = ptvfd->pTree;
  199. // notify parent afterwards if they want us to
  200. if (!(pTree->ci.dwCustom & CDRF_SKIPDEFAULT) &&
  201. pTree->ci.dwCustom & CDRF_NOTIFYPOSTPAINT) {
  202. CIFakeCustomDrawNotify(&pTree->ci, CDDS_POSTPAINT, &ptvfd->nmcd.nmcd);
  203. }
  204. // Restore previous state
  205. pTree->ci.dwCustom = ptvfd->dwCustomPrev;
  206. SelectObject(ptvfd->nmcd.nmcd.hdc, ptvfd->hfontPrev);
  207. ReleaseDC(pTree->ci.hwnd, ptvfd->nmcd.nmcd.hdc);
  208. }
  209. // ----------------------------------------------------------------------------
  210. //
  211. // Returns the width of the widest shown item in the tree
  212. //
  213. // ----------------------------------------------------------------------------
  214. UINT NEAR TV_RecomputeMaxWidth(PTREE pTree)
  215. {
  216. if (!(pTree->ci.style & TVS_NOSCROLL)) {
  217. HTREEITEM hItem;
  218. WORD wMax = 0;
  219. // REVIEW: this might not be the most efficient traversal of the tree
  220. for (hItem = pTree->hRoot->hKids; hItem; hItem = TV_GetNextVisItem(hItem))
  221. {
  222. if (wMax < FULL_WIDTH(pTree, hItem))
  223. wMax = FULL_WIDTH(pTree, hItem);
  224. }
  225. return((UINT)wMax);
  226. } else {
  227. return pTree->cxWnd;
  228. }
  229. }
  230. // ----------------------------------------------------------------------------
  231. //
  232. // Returns the horizontal text extent of the given item's text
  233. //
  234. // ----------------------------------------------------------------------------
  235. WORD NEAR TV_GetItemTextWidth(HDC hdc, PTREE pTree, HTREEITEM hItem)
  236. {
  237. TVITEMEX sItem;
  238. TCHAR szTemp[MAX_PATH];
  239. SIZE size = {0,0};
  240. sItem.pszText = szTemp;
  241. sItem.cchTextMax = ARRAYSIZE(szTemp);
  242. TV_GetItem(pTree, hItem, TVIF_TEXT, &sItem);
  243. GetTextExtentPoint(hdc, sItem.pszText, lstrlen(sItem.pszText), &size);
  244. return (WORD)(size.cx + (g_cxLabelMargin * 2));
  245. }
  246. // ----------------------------------------------------------------------------
  247. //
  248. // Compute the text extent and the full width (indent, image, and text) of
  249. // the given item.
  250. //
  251. // If there is a HDC, then we assume that the HDC has been set up with
  252. // the proper attributes (specifically, the font). If there is no HDC,
  253. // then we will set one up, measure the text, then tear it down.
  254. // If you will be measuring more than one item, it is recommended that
  255. // the caller set up the HDC and keep re-using it, because creating,
  256. // initializing, then destroy the HDC is rather slow.
  257. //
  258. // ----------------------------------------------------------------------------
  259. void NEAR TV_ComputeItemWidth(PTREE pTree, HTREEITEM hItem, HDC hdc)
  260. {
  261. TVFAKEDRAW tvfd; // in case client uses customdraw
  262. int iOldWidth = hItem->iWidth;
  263. if (hdc == NULL) {
  264. TreeView_BeginFakeCustomDraw(pTree, &tvfd);
  265. TreeView_BeginFakeItemDraw(&tvfd, hItem);
  266. }
  267. else
  268. {
  269. tvfd.nmcd.nmcd.hdc = hdc;
  270. }
  271. hItem->iWidth = TV_GetItemTextWidth(tvfd.nmcd.nmcd.hdc, pTree, hItem);
  272. if (!(pTree->ci.style & TVS_NOSCROLL) && iOldWidth != hItem->iWidth)
  273. if (pTree->cxMax < FULL_WIDTH(pTree, hItem)) {
  274. PostMessage(pTree->ci.hwnd, TVMP_CALCSCROLLBARS, 0, 0);
  275. pTree->cxMax = FULL_WIDTH(pTree, hItem);
  276. }
  277. if (hdc == NULL)
  278. {
  279. TreeView_EndFakeItemDraw(&tvfd);
  280. TreeView_EndFakeCustomDraw(&tvfd);
  281. }
  282. }
  283. // ----------------------------------------------------------------------------
  284. //
  285. // Returns TRUE if the item is expanded, FALSE otherwise
  286. //
  287. // ----------------------------------------------------------------------------
  288. BOOL NEAR TV_IsShowing(HTREEITEM hItem)
  289. {
  290. for (hItem = hItem->hParent; hItem; hItem = hItem->hParent)
  291. if (!(hItem->state & TVIS_EXPANDED))
  292. return FALSE;
  293. return TRUE;
  294. }
  295. // ----------------------------------------------------------------------------
  296. //
  297. // If the added item is showing, update the shown (expanded) count, the max
  298. // item width -- then recompute the scroll bars.
  299. //
  300. // sets cxMax, cShowing
  301. //
  302. // ----------------------------------------------------------------------------
  303. BOOL NEAR TV_ScrollBarsAfterAdd(PTREE pTree, HTREEITEM hItem)
  304. {
  305. HTREEITEM hPrev;
  306. if (!TV_IsShowing(hItem))
  307. {
  308. // item isn't visible -- set index to NOTVISIBLE and return
  309. hItem->iShownIndex = (WORD)-1;
  310. return FALSE;
  311. }
  312. hPrev = TV_GetPrevVisItem(hItem);
  313. // increment every shown index after newly added item
  314. hItem->iShownIndex = (hPrev) ? hPrev->iShownIndex + hPrev->iIntegral : 0;
  315. TV_UpdateShownIndexes(pTree, hItem);
  316. pTree->cShowing += hItem->iIntegral;
  317. TV_ComputeItemWidth(pTree, hItem, NULL);
  318. TV_CalcScrollBars(pTree);
  319. return(TRUE);
  320. }
  321. // ----------------------------------------------------------------------------
  322. //
  323. // If the removed item was showing, update the shown (expanded) count, the max
  324. // item width -- then recompute the scroll bars.
  325. //
  326. // sets cxMax, cShowing
  327. //
  328. // ----------------------------------------------------------------------------
  329. BOOL NEAR TV_ScrollBarsAfterRemove(PTREE pTree, HTREEITEM hItem)
  330. {
  331. HTREEITEM hWalk;
  332. if (!ITEM_VISIBLE(hItem))
  333. return FALSE;
  334. // decrement every shown index after removed item
  335. hItem->iShownIndex = (WORD)-1;
  336. hWalk = TV_GetNextVisItem(hItem);
  337. if (hWalk) {
  338. hWalk->iShownIndex -= (WORD) hItem->iIntegral;
  339. TV_UpdateShownIndexes(pTree, hWalk);
  340. // If we delete the top item, the tree scrolls to the end, so ...
  341. if (pTree->hTop == hItem) {
  342. TV_SetTopItem(pTree, hWalk->iShownIndex);
  343. ASSERT(pTree->hTop != hItem);
  344. }
  345. }
  346. pTree->cShowing -= hItem->iIntegral;
  347. if (pTree->fRedraw) {
  348. if (!hItem->iWidth)
  349. TV_ComputeItemWidth(pTree, hItem, NULL);
  350. if (!(pTree->ci.style & TVS_NOSCROLL))
  351. if (pTree->cxMax == FULL_WIDTH(pTree, hItem))
  352. pTree->cxMax = (WORD) TV_RecomputeMaxWidth(pTree);
  353. TV_CalcScrollBars(pTree);
  354. }
  355. return TRUE;
  356. }
  357. // ----------------------------------------------------------------------------
  358. //
  359. // Common worker function for
  360. // TV_ScrollBarsAfterExpand and TV_ScrollBarsAfterCollapse, since they
  361. // are completely identical save for two lines of code.
  362. //
  363. // If the expanded items are / collapsed items were showing, update
  364. // the shown (expanded) count, the max item width -- then recompute
  365. // the scroll bars.
  366. //
  367. // ----------------------------------------------------------------------------
  368. #define SBAEC_COLLAPSE 0
  369. #define SBAEC_EXPAND 1
  370. BOOL NEAR TV_ScrollBarsAfterExpandCollapse(PTREE pTree, HTREEITEM hParent, UINT flags)
  371. {
  372. WORD cxMax = 0;
  373. HTREEITEM hWalk;
  374. TVFAKEDRAW tvfd;
  375. if (!ITEM_VISIBLE(hParent))
  376. return FALSE;
  377. //
  378. // We're going to be measuring a lot of items, so let's set up
  379. // our DC ahead of time.
  380. //
  381. TreeView_BeginFakeCustomDraw(pTree, &tvfd);
  382. for (hWalk = hParent->hKids;
  383. hWalk && (hWalk->iLevel > hParent->iLevel);
  384. hWalk = TV_GetNextVisItem(hWalk))
  385. {
  386. if (flags == SBAEC_COLLAPSE)
  387. hWalk->iShownIndex = (WORD)-1;
  388. if (!hWalk->iWidth)
  389. {
  390. TreeView_BeginFakeItemDraw(&tvfd, hWalk);
  391. TV_ComputeItemWidth(pTree, hWalk, tvfd.nmcd.nmcd.hdc);
  392. TreeView_EndFakeItemDraw(&tvfd);
  393. }
  394. if (cxMax < FULL_WIDTH(pTree, hWalk))
  395. cxMax = FULL_WIDTH(pTree, hWalk);
  396. }
  397. TreeView_EndFakeCustomDraw(&tvfd);
  398. // update every shown index after expanded parent
  399. pTree->cShowing = TV_UpdateShownIndexes(pTree, hParent);
  400. // Update the pTree->cxMax if it is affected by the items we
  401. // expanded/collapsed.
  402. if (!(pTree->ci.style & TVS_NOSCROLL))
  403. {
  404. if (flags == SBAEC_COLLAPSE)
  405. {
  406. // If one of our newly-hidden items was responsible for
  407. // the width being what it is, recompute the max width
  408. // since we hid those items.
  409. if (cxMax == pTree->cxMax)
  410. pTree->cxMax = (WORD) TV_RecomputeMaxWidth(pTree);
  411. }
  412. else
  413. {
  414. // If one of our newly-shown items was responsible is wider
  415. // then the previous max, then we have set a new max.
  416. if (cxMax > pTree->cxMax)
  417. pTree->cxMax = cxMax;
  418. }
  419. }
  420. TV_CalcScrollBars(pTree);
  421. return(TRUE);
  422. }
  423. // ----------------------------------------------------------------------------
  424. //
  425. // If the expanded items are showing, update the shown (expanded) count,
  426. // the max item width -- then recompute the scroll bars.
  427. //
  428. // sets cxMax, cShowing
  429. //
  430. // ----------------------------------------------------------------------------
  431. BOOL NEAR TV_ScrollBarsAfterExpand(PTREE pTree, HTREEITEM hParent)
  432. {
  433. return TV_ScrollBarsAfterExpandCollapse(pTree, hParent, SBAEC_EXPAND);
  434. }
  435. // ----------------------------------------------------------------------------
  436. //
  437. // If the collapsed items were showing, update the shown (expanded) count,
  438. // the max item width -- then recompute the scroll bars.
  439. //
  440. // sets cxMax, cShowing
  441. //
  442. // ----------------------------------------------------------------------------
  443. BOOL NEAR TV_ScrollBarsAfterCollapse(PTREE pTree, HTREEITEM hParent)
  444. {
  445. return TV_ScrollBarsAfterExpandCollapse(pTree, hParent, SBAEC_COLLAPSE);
  446. }
  447. // ----------------------------------------------------------------------------
  448. //
  449. // If the added item changed height, then scroll thing around,
  450. // update the shown (expanded) count, recompute the scroll bars.
  451. //
  452. // sets cShowing
  453. //
  454. // ----------------------------------------------------------------------------
  455. void NEAR TV_ScrollBarsAfterResize(PTREE pTree, HTREEITEM hItem, int iIntegralPrev, UINT uRDWFlags)
  456. {
  457. int iMaxIntegral = max(hItem->iIntegral, iIntegralPrev);
  458. ASSERT(hItem->iIntegral != iIntegralPrev);
  459. if (pTree->fRedraw)
  460. {
  461. int iTop = hItem->iShownIndex - pTree->hTop->iShownIndex +
  462. iMaxIntegral - 1;
  463. if (iTop >= 0)
  464. {
  465. int iGrowth = hItem->iIntegral - iIntegralPrev;
  466. TV_ScrollItems(pTree, abs(iGrowth), iTop, iGrowth > 0);
  467. }
  468. }
  469. // update every shown index after resized item
  470. pTree->cShowing = TV_UpdateShownIndexes(pTree, hItem);
  471. TV_CalcScrollBars(pTree);
  472. // Invalidate based on the worst-case height so we handle
  473. // both the grow and shrink cases.
  474. if (pTree->fRedraw)
  475. {
  476. RECT rc;
  477. if (TV_GetItemRect(pTree, hItem, &rc, FALSE))
  478. {
  479. rc.bottom = rc.top + pTree->cyItem * iMaxIntegral;
  480. RedrawWindow(pTree->ci.hwnd, &rc, NULL, uRDWFlags);
  481. }
  482. }
  483. }
  484. // ----------------------------------------------------------------------------
  485. //
  486. // Returns the item just below the given item in the tree.
  487. //
  488. // ----------------------------------------------------------------------------
  489. TREEITEM FAR * NEAR TV_GetNext(TREEITEM FAR * hItem)
  490. {
  491. DBG_ValidateTreeItem(hItem, FALSE);
  492. if (hItem->hKids)
  493. return hItem->hKids;
  494. checkNext:
  495. if (hItem->hNext)
  496. return hItem->hNext;
  497. hItem = hItem->hParent;
  498. if (hItem)
  499. goto checkNext;
  500. return NULL;
  501. }
  502. // ----------------------------------------------------------------------------
  503. //
  504. // Go through all the items in the tree, recomputing each item's text extent
  505. // and full width (indent, image, and text).
  506. //
  507. // ----------------------------------------------------------------------------
  508. void NEAR TV_RecomputeItemWidths(PTREE pTree)
  509. {
  510. HTREEITEM hItem;
  511. TVFAKEDRAW tvfd;
  512. TreeView_BeginFakeCustomDraw(pTree, &tvfd);
  513. hItem = pTree->hRoot->hKids;
  514. while (hItem)
  515. {
  516. TreeView_BeginFakeItemDraw(&tvfd, hItem);
  517. TV_ComputeItemWidth(pTree, hItem, tvfd.nmcd.nmcd.hdc);
  518. TreeView_EndFakeItemDraw(&tvfd);
  519. hItem = TV_GetNext(hItem);
  520. }
  521. TreeView_EndFakeCustomDraw(&tvfd);
  522. }
  523. // ----------------------------------------------------------------------------
  524. //
  525. // If a single item's width changed, alter the max width if needed.
  526. // If all widths changed, recompute widths and max width.
  527. // Then recompute the scroll bars.
  528. //
  529. // sets cxMax
  530. //
  531. // ----------------------------------------------------------------------------
  532. BOOL NEAR TV_ScrollBarsAfterSetWidth(PTREE pTree, HTREEITEM hItem)
  533. {
  534. if (hItem)
  535. {
  536. UINT iOldWidth = FULL_WIDTH(pTree, hItem);
  537. TV_ComputeItemWidth(pTree, hItem, NULL);
  538. if (!(pTree->ci.style & TVS_NOSCROLL)) {
  539. if (pTree->cxMax == iOldWidth)
  540. pTree->cxMax = (WORD) TV_RecomputeMaxWidth(pTree);
  541. else
  542. return(FALSE);
  543. }
  544. }
  545. else
  546. {
  547. TV_RecomputeItemWidths(pTree);
  548. pTree->cxMax = (WORD) TV_RecomputeMaxWidth(pTree);
  549. }
  550. TV_CalcScrollBars(pTree);
  551. return(TRUE);
  552. }
  553. // ----------------------------------------------------------------------------
  554. //
  555. // Scroll window vertically as needed to make given item fully visible
  556. // vertically
  557. //
  558. // ----------------------------------------------------------------------------
  559. BOOL NEAR TV_ScrollVertIntoView(PTREE pTree, HTREEITEM hItem)
  560. {
  561. // This function has crashed in stress before, so we need to assert the incoming parameters.
  562. ASSERT(hItem);
  563. ASSERT(pTree && pTree->hTop);
  564. // Do nothing if the parameters are invalid
  565. if (!hItem || !pTree || !(pTree->hTop))
  566. return FALSE;
  567. // Do nothing if this item is not visible
  568. if (!ITEM_VISIBLE(hItem))
  569. return FALSE;
  570. if (hItem->iShownIndex < pTree->hTop->iShownIndex)
  571. return(TV_SetTopItem(pTree, hItem->iShownIndex));
  572. if (hItem->iShownIndex >= (pTree->hTop->iShownIndex + pTree->cFullVisible))
  573. return(TV_SetTopItem(pTree, hItem->iShownIndex + 1 - pTree->cFullVisible));
  574. return FALSE;
  575. }
  576. // ----------------------------------------------------------------------------
  577. //
  578. // Scroll window vertically and horizontally as needed to make given item
  579. // fully visible vertically and horizontally
  580. //
  581. // ----------------------------------------------------------------------------
  582. BOOL NEAR TV_ScrollIntoView(PTREE pTree, HTREEITEM hItem)
  583. {
  584. UINT iWidth, iOffset;
  585. BOOL fChange;
  586. fChange = TV_ScrollVertIntoView(pTree, hItem);
  587. // ensure that item's text is fully visible horizontally
  588. iWidth = pTree->cxImage + pTree->cxState + hItem->iWidth;
  589. if (iWidth > (UINT)pTree->cxWnd)
  590. iWidth = pTree->cxWnd; //hItem->iWidth;
  591. iOffset = ITEM_OFFSET(pTree, hItem);
  592. if ((int) (iOffset) < pTree->xPos)
  593. fChange |= TV_SetLeft(pTree, iOffset);
  594. else if ((iOffset + iWidth) > (UINT)(pTree->xPos + pTree->cxWnd))
  595. fChange |= TV_SetLeft(pTree, iOffset + iWidth - pTree->cxWnd);
  596. return fChange;
  597. }
  598. // ----------------------------------------------------------------------------
  599. //
  600. // Sets position of horizontal scroll bar and scrolls window to match that
  601. // position
  602. //
  603. // sets xPos
  604. //
  605. // ----------------------------------------------------------------------------
  606. BOOL NEAR TV_SetLeft(PTREE pTree, int x)
  607. {
  608. if (!pTree->fHorz || pTree->ci.style & (TVS_NOSCROLL | TVS_NOHSCROLL))
  609. return(FALSE);
  610. if (x > (int) (pTree->cxMax - pTree->cxWnd))
  611. x = (pTree->cxMax - pTree->cxWnd);
  612. if (x < 0)
  613. x = 0;
  614. if (x == pTree->xPos)
  615. return(FALSE);
  616. if (pTree->fRedraw) {
  617. SMOOTHSCROLLINFO si =
  618. {
  619. sizeof(si),
  620. SSIF_MINSCROLL | SSIF_MAXSCROLLTIME,
  621. pTree->ci.hwnd,
  622. pTree->xPos - x,
  623. 0,
  624. NULL,
  625. NULL,
  626. NULL,
  627. NULL,
  628. SW_INVALIDATE | SW_ERASE,
  629. pTree->uMaxScrollTime,
  630. 1,
  631. 1
  632. };
  633. SmoothScrollWindow(&si);
  634. }
  635. pTree->xPos = (SHORT) x;
  636. SetScrollPos(pTree->ci.hwnd, SB_HORZ, x, TRUE);
  637. TV_UpdateToolTip(pTree);
  638. return(TRUE);
  639. }
  640. // ----------------------------------------------------------------------------
  641. //
  642. // Returns the tree's item that has the given shown index, NULL if no item
  643. // found with that index.
  644. //
  645. // ----------------------------------------------------------------------------
  646. HTREEITEM NEAR TV_GetShownIndexItem(HTREEITEM hItem, UINT wShownIndex)
  647. {
  648. HTREEITEM hWalk;
  649. if (hItem == NULL)
  650. return NULL;
  651. ASSERT((int)wShownIndex >= 0);
  652. for (hWalk = hItem;
  653. hWalk && (hWalk->iShownIndex <= wShownIndex);
  654. hWalk = hWalk->hNext) {
  655. hItem = hWalk;
  656. if (hWalk->iShownIndex + (UINT)hWalk->iIntegral > wShownIndex)
  657. return hWalk;
  658. }
  659. return TV_GetShownIndexItem(hItem->hKids, wShownIndex);
  660. }
  661. // ----------------------------------------------------------------------------
  662. //
  663. // Sets position of vertical scroll bar and scrolls window to match that
  664. // position
  665. //
  666. // sets hTop
  667. //
  668. // ----------------------------------------------------------------------------
  669. BOOL NEAR TV_SmoothSetTopItem(PTREE pTree, UINT wNewTop, UINT uSmooth)
  670. {
  671. HTREEITEM hItem = pTree->hRoot->hKids;
  672. UINT wOldTop;
  673. if (!hItem)
  674. return FALSE;
  675. if ((pTree->ci.style & TVS_NOSCROLL) || (wNewTop == (UINT)-1) || (pTree->cShowing <= pTree->cFullVisible)) {
  676. // we've wrapped around (treat as a negative index) -- use min pos
  677. // or there aren't enough items to scroll
  678. wNewTop = 0;
  679. } else if (wNewTop > (UINT)(pTree->cShowing - pTree->cFullVisible)) {
  680. // we've gone too far down -- use max pos
  681. wNewTop = (pTree->cShowing - pTree->cFullVisible);
  682. }
  683. // if there's no room for anything to show. peg at the end
  684. if (wNewTop > 0 && wNewTop >= pTree->cShowing) {
  685. wNewTop = pTree->cShowing - 1;
  686. }
  687. hItem = TV_GetShownIndexItem(hItem, wNewTop);
  688. if (NULL == hItem || pTree->hTop == hItem)
  689. return FALSE;
  690. // need to refetch because wNewTop couldhave pointed to the middle of this item,
  691. // which is not allowed
  692. wNewTop = hItem->iShownIndex;
  693. wOldTop = pTree->hTop->iShownIndex;
  694. pTree->hTop = hItem;
  695. if (pTree->fRedraw) {
  696. SMOOTHSCROLLINFO si =
  697. {
  698. sizeof(si),
  699. SSIF_MINSCROLL | SSIF_MAXSCROLLTIME,
  700. pTree->ci.hwnd,
  701. 0,
  702. (int) (wOldTop - wNewTop) * (int) pTree->cyItem,
  703. NULL,
  704. NULL,
  705. NULL,
  706. NULL,
  707. SW_INVALIDATE | SW_ERASE | uSmooth,
  708. pTree->uMaxScrollTime,
  709. 1,
  710. 1
  711. };
  712. SmoothScrollWindow(&si);
  713. }
  714. SetScrollPos(pTree->ci.hwnd, SB_VERT, wNewTop, TRUE);
  715. TV_UpdateToolTip(pTree);
  716. return(TRUE);
  717. }
  718. // ----------------------------------------------------------------------------
  719. //
  720. // Computes the horizontal and vertical scroll bar ranges, pages, and
  721. // positions, adding or removing the scroll bars as needed.
  722. //
  723. // sets fHorz, fVert
  724. //
  725. // ----------------------------------------------------------------------------
  726. BOOL NEAR TV_CalcScrollBars(PTREE pTree)
  727. {
  728. // UINT wMaxPos;
  729. BOOL fChange = FALSE;
  730. SCROLLINFO si;
  731. if (pTree->ci.style & TVS_NOSCROLL)
  732. return FALSE;
  733. si.cbSize = sizeof(SCROLLINFO);
  734. if (!(pTree->ci.style & TVS_NOHSCROLL))
  735. {
  736. if ((SHORT)pTree->cxMax > (SHORT)pTree->cxWnd)
  737. {
  738. if (!pTree->fHorz)
  739. {
  740. fChange = TRUE;
  741. pTree->fHorz = TRUE;
  742. }
  743. si.fMask = SIF_PAGE | SIF_RANGE;
  744. si.nMin = 0;
  745. si.nMax = pTree->cxMax - 1;
  746. si.nPage = pTree->cxWnd;
  747. TV_SetLeft(pTree, (UINT)SetScrollInfo(pTree->ci.hwnd, SB_HORZ, &si, TRUE));
  748. }
  749. else if (pTree->fHorz)
  750. {
  751. TV_SetLeft(pTree, 0);
  752. SetScrollRange(pTree->ci.hwnd, SB_HORZ, 0, 0, TRUE);
  753. pTree->fHorz = FALSE;
  754. fChange = TRUE;
  755. }
  756. }
  757. if (pTree->cShowing > pTree->cFullVisible)
  758. {
  759. if (!pTree->fVert)
  760. {
  761. pTree->fVert = TRUE;
  762. fChange = TRUE;
  763. }
  764. si.fMask = SIF_PAGE | SIF_RANGE;
  765. si.nMin = 0;
  766. si.nMax = pTree->cShowing - 1;
  767. si.nPage = pTree->cFullVisible;
  768. TV_SetTopItem(pTree, (UINT)SetScrollInfo(pTree->ci.hwnd, SB_VERT, &si, TRUE));
  769. }
  770. else if (pTree->fVert)
  771. {
  772. TV_SetTopItem(pTree, 0);
  773. SetScrollRange(pTree->ci.hwnd, SB_VERT, 0, 0, TRUE);
  774. pTree->fVert = FALSE;
  775. fChange = TRUE;
  776. }
  777. if (fChange)
  778. TV_SizeWnd(pTree, 0, 0);
  779. return(TRUE);
  780. }
  781. // ----------------------------------------------------------------------------
  782. //
  783. // Handles horizontal scrolling.
  784. //
  785. // ----------------------------------------------------------------------------
  786. BOOL NEAR TV_HorzScroll(PTREE pTree, UINT wCode, UINT wNewPos)
  787. {
  788. BOOL fChanged;
  789. TV_DismissEdit(pTree, FALSE);
  790. switch (wCode)
  791. {
  792. case SB_BOTTOM:
  793. wNewPos = pTree->cxMax - pTree->cxWnd;
  794. break;
  795. case SB_ENDSCROLL:
  796. wNewPos = pTree->xPos;
  797. break;
  798. case SB_LINEDOWN:
  799. wNewPos = pTree->xPos + MAGIC_HORZLINE;
  800. break;
  801. case SB_LINEUP:
  802. wNewPos = pTree->xPos - MAGIC_HORZLINE;
  803. break;
  804. case SB_PAGEDOWN:
  805. wNewPos = pTree->xPos + (pTree->cxWnd - MAGIC_HORZLINE);
  806. break;
  807. case SB_PAGEUP:
  808. wNewPos = pTree->xPos - (pTree->cxWnd - MAGIC_HORZLINE);
  809. break;
  810. case SB_THUMBPOSITION:
  811. case SB_THUMBTRACK:
  812. break;
  813. case SB_TOP:
  814. wNewPos = 0;
  815. break;
  816. }
  817. if (fChanged = TV_SetLeft(pTree, wNewPos))
  818. UpdateWindow(pTree->ci.hwnd);
  819. return(fChanged);
  820. }
  821. // ----------------------------------------------------------------------------
  822. //
  823. // Handles vertical scrolling.
  824. //
  825. // ----------------------------------------------------------------------------
  826. BOOL NEAR TV_VertScroll(PTREE pTree, UINT wCode, UINT wPos)
  827. {
  828. UINT wNewPos = 0;
  829. UINT wOldPos;
  830. BOOL fChanged;
  831. UINT uSmooth = 0;
  832. if (!pTree->hTop)
  833. return FALSE;
  834. wOldPos = pTree->hTop->iShownIndex;
  835. TV_DismissEdit(pTree, FALSE);
  836. switch (wCode)
  837. {
  838. case SB_BOTTOM:
  839. wNewPos = pTree->cShowing - pTree->cFullVisible;
  840. break;
  841. case SB_ENDSCROLL:
  842. wNewPos = wOldPos;
  843. break;
  844. case SB_LINEDOWN:
  845. wNewPos = wOldPos + pTree->hTop->iIntegral;
  846. break;
  847. case SB_LINEUP:
  848. wNewPos = wOldPos - 1;
  849. if (wNewPos > wOldPos)
  850. wNewPos = 0;
  851. break;
  852. case SB_PAGEDOWN:
  853. wNewPos = wOldPos + (pTree->cFullVisible - 1);
  854. break;
  855. case SB_PAGEUP:
  856. wNewPos = wOldPos - (pTree->cFullVisible - 1);
  857. if (wNewPos > wOldPos)
  858. wNewPos = 0;
  859. break;
  860. case SB_THUMBPOSITION:
  861. case SB_THUMBTRACK:
  862. uSmooth = SSW_EX_IMMEDIATE;
  863. wNewPos = wPos;
  864. break;
  865. case SB_TOP:
  866. wNewPos = 0;
  867. break;
  868. }
  869. if (fChanged = TV_SmoothSetTopItem(pTree, wNewPos, uSmooth))
  870. UpdateWindow(pTree->ci.hwnd);
  871. return(fChanged);
  872. }
  873. #ifdef DEBUG
  874. static int nCompares;
  875. #endif
  876. typedef struct {
  877. LPTSTR lpstr;
  878. BOOL bCallBack;
  879. HTREEITEM hItem;
  880. } TVCOMPARE, FAR *LPTVCOMPARE;
  881. // Pointer comparision function for Sort and Search functions.
  882. // lParam is lParam passed to sort/search functions. Returns
  883. // -1 if p1 < p2, 0 if p1 == p2, and 1 if p1 > p2.
  884. //
  885. int CALLBACK TV_DefCompare(LPTVCOMPARE sCmp1, LPTVCOMPARE sCmp2, LPARAM lParam)
  886. {
  887. #ifdef DEBUG
  888. ++nCompares;
  889. #endif
  890. return lstrcmpi(sCmp1->lpstr, sCmp2->lpstr);
  891. }
  892. int CALLBACK TV_CompareItems(LPTVCOMPARE sCmp1, LPTVCOMPARE sCmp2, LPARAM lParam)
  893. {
  894. TV_SORTCB FAR *pSortCB = (TV_SORTCB FAR *)lParam;
  895. #ifdef DEBUG
  896. ++nCompares;
  897. #endif
  898. return(pSortCB->lpfnCompare(sCmp1->hItem->lParam, sCmp2->hItem->lParam,
  899. pSortCB->lParam));
  900. }
  901. UINT NEAR TV_CountKids(HTREEITEM hItem)
  902. {
  903. int cnt;
  904. for (cnt = 0, hItem = hItem->hKids; hItem; hItem = hItem->hNext)
  905. cnt++;
  906. return cnt;
  907. }
  908. // BUGBUG: bRecurse not implemented
  909. BOOL PASCAL TV_SortCB(PTREE pTree, TV_SORTCB FAR *pSortCB, BOOL bRecurse,
  910. PFNDPACOMPARE lpfnDPACompare)
  911. {
  912. HDPA dpaSort;
  913. HDSA dsaCmp;
  914. HTREEITEM hItem, hNext, hFirstMoved;
  915. LPTVCOMPARE psCompare, FAR *ppsCompare;
  916. int i, cKids;
  917. HTREEITEM hParent = pSortCB->hParent;
  918. #ifdef DEBUG
  919. DWORD dwTime = GetTickCount();
  920. nCompares = 0;
  921. #endif
  922. if (!hParent || hParent == TVI_ROOT)
  923. hParent = pTree->hRoot;
  924. if (!ValidateTreeItem(hParent, FALSE))
  925. return FALSE; // Invalid parameter
  926. // Code below assumes at least one kid
  927. cKids = TV_CountKids(hParent);
  928. if (!cKids)
  929. return FALSE;
  930. // Create a DSA for all the extra info we'll need
  931. dsaCmp = DSA_Create(sizeof(TVCOMPARE), cKids);
  932. if (!dsaCmp)
  933. goto Error1;
  934. // Create a DPA containing all the tree items
  935. dpaSort = DPA_Create(cKids);
  936. if (!dpaSort)
  937. goto Error2;
  938. for (hItem = hParent->hKids; hItem; hItem = hItem->hNext)
  939. {
  940. TVCOMPARE sCompare;
  941. int nItem;
  942. // If I can't sort all of them, I don't want to sort any of them
  943. // We want to cache the text callback for default processing
  944. if (!lpfnDPACompare && hItem->lpstr==LPSTR_TEXTCALLBACK)
  945. {
  946. TVITEMEX sItem;
  947. TCHAR szTemp[MAX_PATH];
  948. sItem.pszText = szTemp;
  949. sItem.cchTextMax = ARRAYSIZE(szTemp);
  950. TV_GetItem(pTree, hItem, TVIF_TEXT, &sItem);
  951. sCompare.lpstr = NULL;
  952. sCompare.bCallBack = TRUE;
  953. Str_Set(&sCompare.lpstr, sItem.pszText);
  954. if (!sCompare.lpstr)
  955. {
  956. goto Error3;
  957. }
  958. }
  959. else
  960. {
  961. sCompare.lpstr = hItem->lpstr;
  962. sCompare.bCallBack = FALSE;
  963. }
  964. // Create the pointer for this guy and add it to the DPA list
  965. sCompare.hItem = hItem;
  966. nItem = DSA_AppendItem(dsaCmp, &sCompare);
  967. if (nItem < 0)
  968. {
  969. if (sCompare.bCallBack)
  970. {
  971. Str_Set(&sCompare.lpstr, NULL);
  972. }
  973. goto Error3;
  974. }
  975. if (DPA_AppendPtr(dpaSort, DSA_GetItemPtr(dsaCmp, nItem)) < 0)
  976. {
  977. goto Error3;
  978. }
  979. }
  980. // Sort the DPA, then stick them back under the parent in the new order
  981. DPA_Sort(dpaSort, lpfnDPACompare ? (PFNDPACOMPARE)lpfnDPACompare :
  982. (PFNDPACOMPARE) TV_DefCompare, (LPARAM)pSortCB);
  983. // Look for the first moved item, so we can invalidate a smaller area
  984. ppsCompare = (LPTVCOMPARE FAR *)DPA_GetPtrPtr(dpaSort);
  985. if (hParent->hKids != (*ppsCompare)->hItem)
  986. {
  987. hParent->hKids = (*ppsCompare)->hItem;
  988. hFirstMoved = hParent->hKids;
  989. }
  990. else
  991. {
  992. hFirstMoved = NULL;
  993. }
  994. // We do n-1 iterations here
  995. for (i = DPA_GetPtrCount(dpaSort) - 1; i > 0; --i, ++ppsCompare)
  996. {
  997. hNext = (*(ppsCompare+1))->hItem;
  998. if ((*ppsCompare)->hItem->hNext != hNext && !hFirstMoved)
  999. {
  1000. hFirstMoved = hNext;
  1001. }
  1002. (*ppsCompare)->hItem->hNext = hNext;
  1003. }
  1004. (*ppsCompare)->hItem->hNext = NULL;
  1005. TV_UpdateShownIndexes(pTree, hParent);
  1006. if ((pSortCB->hParent == TVI_ROOT) || !hParent) {
  1007. if (pTree->cShowing < pTree->cFullVisible) {
  1008. pTree->hTop = pTree->hRoot->hKids;
  1009. }
  1010. }
  1011. if (hFirstMoved && (hParent->state & TVIS_EXPANDED))
  1012. {
  1013. RECT rcUpdate;
  1014. TV_GetItemRect(pTree, hFirstMoved, &rcUpdate, FALSE);
  1015. if (hParent->hNext)
  1016. {
  1017. RECT rcTemp;
  1018. TV_GetItemRect(pTree, hParent->hNext, &rcTemp, FALSE);
  1019. rcUpdate.bottom = rcTemp.bottom;
  1020. }
  1021. else
  1022. {
  1023. RECT rcClient;
  1024. GetClientRect(pTree->ci.hwnd, &rcClient);
  1025. // Set to maximal positive number, so the whole rest of
  1026. // the treeview gets invalidated
  1027. rcUpdate.bottom = rcClient.bottom;
  1028. }
  1029. if (pTree->fRedraw)
  1030. InvalidateRect(pTree->ci.hwnd, &rcUpdate, TRUE);
  1031. }
  1032. Error3:
  1033. DPA_Destroy(dpaSort);
  1034. Error2:
  1035. for (i = DSA_GetItemCount(dsaCmp) - 1; i >= 0; --i)
  1036. {
  1037. psCompare = DSA_GetItemPtr(dsaCmp, i);
  1038. if (psCompare->bCallBack)
  1039. {
  1040. Str_Set(&(psCompare->lpstr), NULL);
  1041. }
  1042. }
  1043. DSA_Destroy(dsaCmp);
  1044. Error1:
  1045. #ifdef DEBUG
  1046. TraceMsg(TF_TREEVIEW, "tv.sort: %ld ms; %d cmps", GetTickCount()-dwTime, nCompares);
  1047. #endif
  1048. {
  1049. int wNewPos;
  1050. // restore the scroll position
  1051. if (GetWindowStyle(pTree->ci.hwnd) & WS_VSCROLL) {
  1052. SCROLLINFO si;
  1053. si.cbSize = sizeof(SCROLLINFO);
  1054. si.fMask = SIF_POS;
  1055. wNewPos = 0;
  1056. if (GetScrollInfo(pTree->ci.hwnd, SB_VERT, &si)) {
  1057. wNewPos = si.nPos;
  1058. }
  1059. } else {
  1060. wNewPos = 0;
  1061. }
  1062. if (TV_SetTopItem(pTree, wNewPos))
  1063. UpdateWindow(pTree->ci.hwnd);
  1064. }
  1065. // if the caret is the child of the thing that was sorted, make sure it's
  1066. // visible (but if we're sorting something completely unrelated, don't bother
  1067. if (pTree->hCaret) {
  1068. hItem = pTree->hCaret;
  1069. do {
  1070. // do this first. if hParent is hCaret, we don't want to ensure visible...
  1071. // only if it's an eventual child
  1072. hItem = hItem->hParent;
  1073. if (hParent == hItem) {
  1074. TV_EnsureVisible(pTree, pTree->hCaret);
  1075. }
  1076. } while(hItem && hItem != pTree->hRoot);
  1077. }
  1078. // The items in the view may have moved around; let apps know
  1079. // Do this last because this call might yield
  1080. MyNotifyWinEvent(EVENT_OBJECT_REORDER, pTree->ci.hwnd, OBJID_CLIENT, 0);
  1081. return TRUE;
  1082. }
  1083. BOOL NEAR TV_SortChildrenCB(PTREE pTree, LPTV_SORTCB pSortCB, BOOL bRecurse)
  1084. {
  1085. return(TV_SortCB(pTree, pSortCB, bRecurse, (PFNDPACOMPARE)TV_CompareItems));
  1086. }
  1087. BOOL NEAR TV_SortChildren(PTREE pTree, HTREEITEM hParent, BOOL bRecurse)
  1088. {
  1089. TV_SORTCB sSortCB;
  1090. sSortCB.hParent = hParent;
  1091. return(TV_SortCB(pTree, &sSortCB, bRecurse, NULL));
  1092. }