text: #include "ctlspriv.h"
  #include "treeview.h"
  #include "listview.h"
  
  // penwin.h is messed up; define local stuff for now
  #define HN_BEGINDIALOG                  40              // Lens/EditText/garbage detection dialog is about
                                      // to come up on this hedit/bedit
  #define HN_ENDDIALOG                       41           // Lens/EditText/garbage detection dialog has
                                      // just been destroyed
  
  //---------------------------------------------------------
  #define IDT_SCROLLWAIT 43
  
  //-----------------------
  // ToolTip stuff...
  //
  #define REPEATTIME      SendMessage(pTree->hwndToolTips,TTM_GETDELAYTIME,(WPARAM)TTDT_RESHOW, 0)
  #define CHECKFOCUSTIME  (REPEATTIME)
  #define IDT_TOOLTIPWAIT   2
  #define IDT_FOCUSCHANGE   3
  // in tooltips.c
  BOOL ChildOfActiveWindow(HWND hwnd);
  void TV_HandleStateIconClick(PTREE pTree, HTREEITEM hItem);
  
  HWND TV_EditLabel(PTREE pTree, HTREEITEM hItem, LPTSTR pszInitial);
  void TV_CancelEditTimer(PTREE pTree);
  BOOL TV_SetItem(PTREE pTree, LPCTVITEMEX ptvi);
  void TV_DeleteHotFonts(PTREE pTree);
  BOOL TV_IsShowing(HTREEITEM hItem);
  
  LRESULT TV_OnScroll(PTREE ptv, LPNMHDR pnm);
  
  #define TVBD_FROMWHEEL      0x0001
  #define TVBD_WHEELFORWARD   0x0002
  #define TVBD_WHEELBACK      0x0004
  
  BOOL ValidateTreeItem(TREEITEM * hItem, UINT flags)
  {
      BOOL fValid = TRUE;
  
      /*
       *  Check the values to make sure the new Win64-compatible values
       *  are consistent with the old Win32 values.
       */
      COMPILETIME_ASSERT(
             (DWORD)(ULONG_PTR)TVI_ROOT  == 0xFFFF0000 &&
             (DWORD)(ULONG_PTR)TVI_FIRST == 0xFFFF0001 &&
             (DWORD)(ULONG_PTR)TVI_LAST  == 0xFFFF0002 &&
             (DWORD)(ULONG_PTR)TVI_SORT  == 0xFFFF0003);
  
      if (hItem)
      {
          if (HIWORD64(hItem) == HIWORD64(TVI_ROOT))
          {
              switch (LOWORD(hItem))
              {
                  case LOWORD(TVI_ROOT):
                  case LOWORD(TVI_FIRST):
                  case LOWORD(TVI_LAST):
                  case LOWORD(TVI_SORT):
                      break;
  
                  default:
                      AssertMsg(FALSE, TEXT("ValidateTreeItem() Invalid special item"));
                      fValid = FALSE;
                      break;
              }
          }
          else
          {
              __try
              {
                  // Use "volatile" to force memory access at start of struct
                  *(volatile void **)hItem;
                  fValid = hItem->wSignature == TV_SIG;
              } __except(EXCEPTION_EXECUTE_HANDLER)
              {
                  fValid = FALSE;
              } __endexcept
          }
  
      }
      else if (!flags)
      {
          // The only flag is VTI_NULLOK
          RIPMSG(FALSE, "ValidateTreeItem(): NULL HTREEITEM");
          fValid = FALSE;
      }
  
      return fValid;
  }
  
  // ----------------------------------------------------------------------------
  //
  //  Initialize TreeView on library entry -- register SysTreeView class
  //
  // ----------------------------------------------------------------------------
  
  BOOL TV_Init(HINSTANCE hinst)
  {
      WNDCLASS wc;
  
      wc.lpfnWndProc     = TV_WndProc;
      wc.hCursor         = LoadCursor(NULL, IDC_ARROW);
      wc.hIcon           = NULL;
      wc.lpszMenuName    = NULL;
      wc.hInstance       = hinst;
      wc.lpszClassName   = c_szTreeViewClass;
      wc.hbrBackground   = NULL;
      wc.style           = CS_DBLCLKS | CS_GLOBALCLASS;
      wc.cbWndExtra      = sizeof(PTREE);
      wc.cbClsExtra      = 0;
  
      return (RegisterClass(&wc) || (GetLastError() == ERROR_CLASS_ALREADY_EXISTS));
  }
  
  // ----------------------------------------------------------------------------
  //
  // If the tooltip bubble is up, then pop it.
  //
  // ----------------------------------------------------------------------------
  
  void TV_PopBubble(PTREE pTree)
  {
      if (pTree->hwndToolTips && pTree->hToolTip)
      {
          pTree->hToolTip = NULL;
          SendMessage(pTree->hwndToolTips, TTM_POP, 0L, 0L);
      }
  }
  
  
  // ----------------------------------------------------------------------------
  //
  //  Sends a TVN_BEGINDRAG or TVN_BEGINRDRAG notification with information in the ptDrag and
  //  itemNew fields of an NM_TREEVIEW structure
  //
  // ----------------------------------------------------------------------------
  
  BOOL TV_SendBeginDrag(PTREE pTree, int code, TREEITEM * hItem, int x, int y)
  {
      NM_TREEVIEW nm;
          
      TV_PopBubble(pTree);            // dismiss the infotip if we start to drag
  
      nm.itemNew.hItem = hItem;
      nm.itemNew.state = hItem->state;
      nm.itemNew.lParam = hItem->lParam;
      nm.itemNew.mask = (TVIF_HANDLE | TVIF_STATE | TVIF_PARAM);
      nm.itemOld.mask = 0;
      nm.ptDrag.x = x;
      nm.ptDrag.y = y;
  
      return (BOOL)CCSendNotify(&pTree->ci, code, &nm.hdr);
  }
  
  
  // ----------------------------------------------------------------------------
  //
  //  Sends a TVN_ITEMEXPANDING or TVN_ITEMEXPANDED notification with information
  //  in the action and itemNew fields of an NM_TREEVIEW structure
  //
  //  Returns FALSE to allow processing to continue, or TRUE to stop.
  //
  //  If the hItem is destroyed by the callback, then we always return TRUE.
  //
  //  Note that the application cannot stop a TVN_ITEMEXPANDED, so the only
  //  way a TVN_ITEMEXPANDED can return "Stop" is if the item got destroyed.
  //
  // ----------------------------------------------------------------------------
  
  BOOL TV_SendItemExpand(PTREE pTree, int code, TREEITEM * hItem, WPARAM action)
  {
      NM_TREEVIEW nm;
      TVWATCHEDITEM wi;
      BOOL fResult;
      BOOL fWatched;
  
      ASSERT(code == TVN_ITEMEXPANDING || code == TVN_ITEMEXPANDED);
  
      nm.itemNew.mask = 0;
      nm.itemNew.hItem = hItem;
      if (hItem == TVI_ROOT)
          hItem = pTree->hRoot;
      nm.itemNew.state = hItem->state;
      nm.itemNew.lParam = hItem->lParam;
      nm.itemNew.iImage = hItem->iImage;
      nm.itemNew.iSelectedImage = hItem->iSelectedImage;
      switch(hItem->fKids) {
          case KIDS_CALLBACK:
          case KIDS_FORCE_YES:
              nm.itemNew.cChildren = 1;
              nm.itemNew.mask = TVIF_CHILDREN;
              break;
          case KIDS_FORCE_NO:
              nm.itemNew.cChildren = 0;
              nm.itemNew.mask = TVIF_CHILDREN;
              break;
      }
      nm.itemNew.mask |= (TVIF_HANDLE | TVIF_STATE | TVIF_PARAM | TVIF_IMAGE | TVIF_SELECTEDIMAGE);
      nm.itemOld.mask = 0;
  
      nm.action = (UINT)(action & TVE_ACTIONMASK);
  
      //
      //  Some apps will delete the item while it is being expanded, since
      //  during expansion, they will realize, "Hey, the thing represented
      //  by this item no longer exists, I'd better delete it."  (E.g,.
      //  Explorer.)  So keep an eye on the item so we don't fault when
      //  this happens.
      //
  
      // If we can't start a watch, then tough, just send the notification
      // the unsafe way.
      fWatched = TV_StartWatch(pTree, &wi, hItem);
  
      fResult = (BOOL)CCSendNotify(&pTree->ci, code, &nm.hdr);
  
      // The app return code from TVN_ITEMEXPANDED is ignored.
      // You can't stop a TVN_ITEMEXPANDED; it's already happened.
      if (code == TVN_ITEMEXPANDED)
          fResult = FALSE;                // Continue processing
  
      if (fWatched) {
          if (!TV_IsWatchValid(pTree, &wi))
              fResult = TRUE;             // Oh no!  Stop!
  
          TV_EndWatch(pTree, &wi);
      }
  
      return fResult;
  }
  
  
  // ----------------------------------------------------------------------------
  //
  //  Sends a TVN_SELCHANGING or TVN_SELCHANGED notification with information in
  //  the itemOld and itemNew fields of an NM_TREEVIEW structure
  //
  // ----------------------------------------------------------------------------
  
  BOOL TV_SendSelChange(PTREE pTree, int code, TREEITEM * hOldItem, TREEITEM * hNewItem, UINT action)
  {
      NM_TREEVIEW nm;
  
      nm.action = action;
  
      nm.itemNew.hItem = hNewItem;
      nm.itemNew.state = hNewItem ? hNewItem->state : 0;
      nm.itemNew.lParam = hNewItem ? hNewItem->lParam : 0;
      nm.itemNew.mask = (TVIF_HANDLE | TVIF_STATE | TVIF_PARAM);
  
      nm.itemOld.hItem = hOldItem;
      nm.itemOld.state = hOldItem ? hOldItem->state : 0;
      nm.itemOld.lParam = hOldItem ? hOldItem->lParam : 0;
      nm.itemOld.mask = (TVIF_HANDLE | TVIF_STATE | TVIF_PARAM);
  
      return (BOOL)CCSendNotify(&pTree->ci, code, &nm.hdr);
  }
  // ----------------------------------------------------------------------------
  //
  //  Returns the first visible item above the given item in the tree.
  //
  // ----------------------------------------------------------------------------
  
  TREEITEM * TV_GetPrevVisItem(TREEITEM * hItem)
  {
      TREEITEM * hParent = hItem->hParent;
      TREEITEM * hWalk;
  
      DBG_ValidateTreeItem(hItem, 0);
  
      if (hParent->hKids == hItem)
          return VISIBLE_PARENT(hItem);
  
      for (hWalk = hParent->hKids; hWalk->hNext != hItem; hWalk = hWalk->hNext);
  
  checkKids:
      if (hWalk->hKids && (hWalk->state & TVIS_EXPANDED))
      {
          for (hWalk = hWalk->hKids; hWalk->hNext; hWalk = hWalk->hNext);
  
          goto checkKids;
      }
      return(hWalk);
  }
  
  
  // ----------------------------------------------------------------------------
  //
  //  Returns the first visible item below the given item in the tree.
  //
  // ----------------------------------------------------------------------------
  
  TREEITEM * TV_GetNextVisItem(TREEITEM * hItem)
  {
      DBG_ValidateTreeItem(hItem, 0);
  
      if (hItem->hKids && (hItem->state & TVIS_EXPANDED))
          return hItem->hKids;
  
  checkNext:
      if (hItem->hNext)
          return(hItem->hNext);
  
      hItem = hItem->hParent;
      if (hItem)
          goto checkNext;
  
      return NULL;
  }
  
  
  // ----------------------------------------------------------------------------
  //
  //  Determine what part of what item is at the given (x,y) location in the
  //  tree's client area.  If the location is outside the client area, NULL is
  //  returned with the TVHT_TOLEFT, TVHT_TORIGHT, TVHT_ABOVE, and/or TVHT_BELOW
  //  flags set in the wHitCode as appropriate.  If the location is below the
  //  last item, NULL is returned with wHitCode set to TVHT_NOWHERE.  Otherwise,
  //  the item is returned with wHitCode set to either TVHT_ONITEMINDENT,
  //  TVHT_ONITEMBUTTON, TVHT_ONITEMICON, TVHT_ONITEMLABEL, or TVHT_ONITEMRIGHT
  //
  // ----------------------------------------------------------------------------
  
  TREEITEM * TV_CheckHit(PTREE pTree, int x, int y, UINT *wHitCode)
  {
      TREEITEM * hItem = pTree->hTop;
      int cxState;
  
      TVITEMEX sItem;
  
      *wHitCode = 0;
  
      if (x < 0)
          *wHitCode |= TVHT_TOLEFT;
      else if (x > (int) pTree->cxWnd)
          *wHitCode |= TVHT_TORIGHT;
  
      if (y < 0)
          *wHitCode |= TVHT_ABOVE;
      else if (y > (int) pTree->cyWnd)
          *wHitCode |= TVHT_BELOW;
  
      if (*wHitCode)
          return NULL;
  
      {
          int index = y / pTree->cyItem;
  
          while (hItem && index >= hItem->iIntegral) {
              index -= hItem->iIntegral;
              hItem = TV_GetNextVisItem(hItem);
          }
      }
  
      if (!hItem)
      {
          *wHitCode = TVHT_NOWHERE;
          return NULL;
      }
  
      x -= (pTree->cxBorder + (hItem->iLevel * pTree->cxIndent));
      x += pTree->xPos;
  
      if ((pTree->ci.style & (TVS_HASLINES | TVS_HASBUTTONS)) &&
          (pTree->ci.style &TVS_LINESATROOT))
      {
          // Subtract some more to make up for the pluses at the root
          x -= pTree->cxIndent;
      }
  
      TV_GetItem(pTree, hItem, TVIF_CHILDREN, &sItem);
      cxState = TV_StateIndex(&sItem) ? pTree->cxState : 0;
      if (x <= (int) (hItem->iWidth + pTree->cxImage + cxState))
      {
  
          if (x >= 0) {
              if (pTree->himlState &&  (x < cxState)) {
                  *wHitCode = TVHT_ONITEMSTATEICON;
              } else if (pTree->hImageList && (x < (int) pTree->cxImage + cxState)) {
                  *wHitCode = TVHT_ONITEMICON;
              } else {
                  *wHitCode = TVHT_ONITEMLABEL;
              }
          } else if ((x >= -pTree->cxIndent) && sItem.cChildren && (pTree->ci.style & TVS_HASBUTTONS))
              *wHitCode = TVHT_ONITEMBUTTON;
          else
              *wHitCode = TVHT_ONITEMINDENT;
      }
      else
          *wHitCode = TVHT_ONITEMRIGHT;
  
      return hItem;
  }
  
  //  This is tricky because CheckForDragBegin yields and the app may have
  //  destroyed the item we are thinking about dragging
  //
  //  To give the app some feedback, we give the hItem the drop highlight
  //  if it isn't already the caret.  This also allows us to check if the
  //  item got deleted behind our back - TV_DeleteItemRecurse makes sure
  //  that deleted items are never the hCaret or hDropTarget.
  //
  //  After TV_CheckForDragBegin, the caller must call TV_FinishCheckDrag
  //  to clean up the UI changes that TV_CheckForDragBegin temporarily
  //  performed.
  //
  BOOL TV_CheckForDragBegin(PTREE pTree, HTREEITEM hItem, int x, int y)
  {
      BOOL fDrag;
  
      //
      //  If the item is not the caret, then make it the (temporary)
      //  drop target so the user gets some feedback.
      //
      //  Bug#94368 raymondc - If hItem == pTree->hCaret, it still might not
      //  be visible if the control doesn't yet have focus and the treeview
      //  is not marked showselalways.  Maybe we should just always set
      //  hItem to DROPHILITE.
      //
      if (hItem == pTree->hCaret)
      {
          pTree->hOldDrop = NULL;
          pTree->fRestoreOldDrop = FALSE;
      }
      else
      {
          pTree->hOldDrop = pTree->hDropTarget;
          pTree->fRestoreOldDrop = TRUE;
          TV_SelectItem(pTree, TVGN_DROPHILITE, hItem, 0, TVC_BYMOUSE);
          ASSERT(hItem == pTree->hDropTarget);
      }
  
      //
      //  We are dragging the hItem if CheckForDragBegin says okay,
      //  and TV_DeleteItemRecurse didn't wipe us out.
      //
      fDrag = CheckForDragBegin(pTree->ci.hwnd, x, y) &&
             (hItem == pTree->hDropTarget || hItem == pTree->hCaret);
  
      return fDrag;
  }
  
  void TV_FinishCheckDrag(PTREE pTree)
  {
      //
      //  Clean up our temporary UI changes that happened when we started
      //  dragging.
      //
      if (pTree->fRestoreOldDrop)
      {
          HTREEITEM hOldDrop = pTree->hOldDrop;
          pTree->fRestoreOldDrop = FALSE;
          pTree->hOldDrop = NULL;
          TV_SelectItem(pTree, TVGN_DROPHILITE, hOldDrop, 0, TVC_BYMOUSE);
      }
  }
  
  void TV_SendRButtonDown(PTREE pTree, int x, int y)
  {
      BOOL fRet = FALSE;
      UINT wHitCode;
      TREEITEM * hItem = TV_CheckHit(pTree, x, y, &wHitCode);
      HWND hwnd = pTree->ci.hwnd;
  
      if (!TV_DismissEdit(pTree, FALSE))   // end any previous editing (accept it)
          return;     // Something happened such that we should not process button down
  
      //
      // Need to see if the user is going to start a drag operation
      //
  
      GetMessagePosClient(pTree->ci.hwnd, &pTree->ptCapture);
  
      if (TV_CheckForDragBegin(pTree, hItem, x, y))
      {
          // let them start dragging
          if (hItem)
          {
              pTree->htiDrag = hItem;
              TV_SendBeginDrag(pTree, TVN_BEGINRDRAG, hItem, x, y);
          }
      }
      else if (!IsWindow(hwnd))
      {
          return;             // bail!
      }
      else
      {
          SetFocus(pTree->ci.hwnd);  // Activate this window like listview...
          fRet = !CCSendNotify(&pTree->ci, NM_RCLICK, NULL);
      }
  
      // Don't finish the CheckForDragBegin until after the NM_RCLICK
      // because apps want to display the context menu while the
      // temporary drag UI is still active.
      TV_FinishCheckDrag(pTree);
  
      if (fRet)
          SendMessage(pTree->ci.hwndParent, WM_CONTEXTMENU, (WPARAM)pTree->ci.hwnd, GetMessagePos());
  }
  
  
  // ----------------------------------------------------------------------------
  //
  //  If the given item is visible in the client area, the rectangle that
  //  surrounds that item is invalidated
  //
  // ----------------------------------------------------------------------------
  
  void TV_InvalidateItem(PTREE pTree, TREEITEM * hItem, UINT fRedraw)
  {
      RECT rc;
  
      if (hItem && pTree->fRedraw && TV_GetItemRect(pTree, hItem, &rc, FALSE))
      {
          RedrawWindow(pTree->ci.hwnd, &rc, NULL, fRedraw);
      }
  }
  
  //
  //  Given an item, compute where the text of this item ends up being painted.
  //  Basically, stare at TV_DrawItem and dutifully reproduce all the code that
  //  messes with the x-coordinate.
  //
  int ITEM_OFFSET(PTREE pTree, HTREEITEM hItem)
  {
      int x = pTree->cxBorder + (hItem->iLevel * pTree->cxIndent);
  
      // state image
      if (pTree->himlState && TV_StateIndex(hItem))
          x += pTree->cxState;
  
      // image
      if (pTree->hImageList) {
          // even if not drawing image, draw text in right place
          x += pTree->cxImage;
      }
      
      // "plus" at the front of the tree
      if ((pTree->ci.style & TVS_LINESATROOT) &&
          (pTree->ci.style & (TVS_HASLINES | TVS_HASBUTTONS)))
          x += pTree->cxIndent;
  
  
      return x;
  }
  
  // ----------------------------------------------------------------------------
  //
  //  If the given item is visible in the client area, the rectangle that
  //  surrounds that item is filled into lprc
  //
  //  Returns TRUE if the item is shown, FALSE otherwise
  //
  // ----------------------------------------------------------------------------
  
  BOOL TV_GetItemRect(PTREE pTree, TREEITEM * hItem, LPRECT lprc, BOOL bItemRect)
  {
      UINT iOffset;
  
      if (!hItem)
          return FALSE;
  
      DBG_ValidateTreeItem(hItem, 0);
  
      if (!ITEM_VISIBLE(hItem))
          return FALSE;
  
      iOffset = hItem->iShownIndex - pTree->hTop->iShownIndex;
  
      if (bItemRect)
      {
          // Calculate where X position should start...
          lprc->left = -pTree->xPos + ITEM_OFFSET(pTree, hItem);
          lprc->right = lprc->left + hItem->iWidth;
      }
      else 
      {
          lprc->left = 0;
          lprc->right = pTree->cxWnd;
      }
  
      lprc->top = iOffset * pTree->cyItem;
      lprc->bottom = lprc->top + (pTree->cyItem * hItem->iIntegral) ;
  
      return TRUE;
  }
  
  void TV_OnSetRedraw(PTREE pTree, BOOL fRedraw)
  {
      pTree->fRedraw = TRUE && fRedraw;
      if (pTree->fRedraw)
      {
          // This use to only refresh the items from hTop down, this is bad as if items are inserted
          // before the visible point within the tree then we would fail!
          if ( pTree->hRoot )
              pTree->cShowing = TV_UpdateShownIndexes(pTree,pTree->hRoot);
  
          //  Must force recalculation of all tree items to get the right cxMax.
          TV_ScrollBarsAfterSetWidth(pTree, NULL);
          InvalidateRect(pTree->ci.hwnd, NULL, TRUE); //REVIEW: could be smarter
      }
  }
  
  //  Treeview item watching implementation
  //
  //  You need to "watch" an item any time you hold onto its HTREEITEM
  //  and then yield control to the application.  If you didn't watch
  //  the item, then if the app deletes the item, you end up with a
  //  stale HTREEITEM pointer and fault.
  //
  //  To begin watching an item, call TV_StartWatch with the item you
  //  want to start watching.  When finished watching, call TV_EndWatch.
  //
  //  In between, you can call TV_IsWatchStale() which tells you if the
  //  item has been deleted behind your back and you shouldn't use it.
  //  Alternatively, use TV_IsWatchValid() which says if it's okay.
  //
  //  Additional bonus behavior for enumeration:  If the watched item
  //  is deleted, we cache the hNext item so that you can step to the
  //  item after the one that got deleted.  Note that this works even
  //  if the hNext item gets deleted before you get a chance to look,
  //  because we just move the cached item to the hNext's hNext.
  //
  //  Sample usage for watching:
  //
  //  TVWATCHEDITEM wi;
  //  if (TV_StartWatch(pTree, &wi, htiStartHere)) {
  //      FunctionThatYields();
  //      if (TV_IsWatchValid(pTree, &wi)) {
  //          KeepUsing(htiStartHere);
  //      } else {
  //          // item was deleted while we yielded; stop using it
  //      }
  //      TV_EndWatch(pTree, &wi);
  //  }
  //
  //  Sample usage for enumerating:
  //
  //  TVWATCHEDITEM wi;
  //  if (TV_StartWatch(pTree, &wi, htiFirst)) {
  //      while (TV_GetWatchItem(pTree, &wi)) {
  //          FunctionThatYields(TV_GetWatchItem(pTree, &wi));
  //          if (TV_IsWatchValid(pTree, &wi)) {
  //              KeepUsing(htiStartHere);
  //          } else {
  //              // item was deleted while we yielded; stop using it
  //          }
  //          TV_NextWatchItem(pTree, &wi);
  //      }
  //      TV_EndWatch(pTree, &wi);
  //  }
  //
  //
  //
  
  //
  //  TV_StartWatch - Begin watching an item.
  //
  //  Returns FALSE if out of memory.
  //
  BOOL TV_StartWatch(PTREE pTree, PTVWATCHEDITEM pwi, HTREEITEM htiStart)
  {
      pwi->hti = htiStart;
      pwi->fStale = FALSE;
      return DPA_AppendPtr(pTree->hdpaWatch, pwi) != -1;
  }
  
  //
  //  TV_EndWatch - Remove the item from the watch list.
  //
  BOOL TV_EndWatch(PTREE pTree, PTVWATCHEDITEM pwi)
  {
      int i = DPA_GetPtrCount(pTree->hdpaWatch);
      while (--i >= 0)
      {
          PTVWATCHEDITEM pwiT = DPA_FastGetPtr(pTree->hdpaWatch, i);
          ASSERT(pwiT);
          if (pwi == pwiT)
          {
              DPA_DeletePtr(pTree->hdpaWatch, i);
              return TRUE;
          }
      }
      ASSERT(!"TV_EndWatch: Item not in list");
      return FALSE;
  }
  
  //  End of treeview item watching implementation
  
  void TV_SetItemRecurse(PTREE pTree, TREEITEM *hItem, LPTVITEMEX ptvi)
  {
      // Note:  This code assumes nobody will try to delete an item
      //        during a SetItem notification.
      while (hItem) {
          ptvi->hItem = hItem;
          TV_SetItem(pTree, ptvi);
          if (hItem->hKids) {
              TV_SetItemRecurse(pTree, hItem->hKids, ptvi);
          }
  
          hItem = hItem->hNext;
      }
  }
  
  BOOL TV_DoExpandRecurse(PTREE pTree, TREEITEM *hItem, BOOL fNotify)
  {
      TVWATCHEDITEM wi;
      BOOL fRc = FALSE;
  
      if (TV_StartWatch(pTree, &wi, hItem))
      {
          while ((hItem = TV_GetWatchItem(pTree, &wi))) {
  
              // was the escape key pressed at any point since the last check?
              if (GetAsyncKeyState(VK_ESCAPE) & 0x1)
                  goto failed;
  
              TV_Expand(pTree, TVE_EXPAND, hItem, fNotify); // yields
              if (TV_IsWatchValid(pTree, &wi)) {
                  if (hItem->hKids) {
                      if (!TV_DoExpandRecurse(pTree, hItem->hKids, fNotify))
                          goto failed;
                  }
              }
              TV_NextWatchItem(pTree, &wi);
          }
          fRc = TRUE;
      failed:
          TV_EndWatch(pTree, &wi);
      }
      return fRc;
  }
  
  
  void TV_ExpandRecurse(PTREE pTree, TREEITEM *hItem, BOOL fNotify)
  {
      BOOL fRedraw = pTree->fRedraw;
  
      TV_OnSetRedraw(pTree, FALSE);
      
      // we're going to check this after each expand so clear it first
      GetAsyncKeyState(VK_ESCAPE);
      
      TV_Expand(pTree, TVE_EXPAND, hItem, fNotify);
      // Bug#94345 hItem may have gone bad during that TV_Expand
      TV_DoExpandRecurse(pTree, hItem->hKids, fNotify);
      TV_OnSetRedraw(pTree, fRedraw);
  }
  
  void TV_ExpandParents(PTREE pTree, TREEITEM *hItem, BOOL fNotify)
  {
      if (hItem == TVI_ROOT || hItem == NULL) // Root has no parents
      {
          return;
      }
  
      hItem = hItem->hParent;
      if (hItem) {
          TVWATCHEDITEM wi;
          if (TV_StartWatch(pTree, &wi, hItem)) {
              TV_ExpandParents(pTree, hItem, fNotify);
  
              // Item may have gone invalid during expansion
              if (TV_IsWatchValid(pTree, &wi) &&
  
                  // make sure this item is not in a collapsed branch
                  !(hItem->state & TVIS_EXPANDED)) {
  
                  TV_Expand(pTree, TVE_EXPAND, hItem, fNotify);
              }
              TV_EndWatch(pTree, &wi);
          }
      }
  }
  
  // makes sure an item is expanded and scrolled into view
  
  BOOL TV_EnsureVisible(PTREE pTree, TREEITEM * hItem)
  {
      TV_ExpandParents(pTree, hItem, TRUE);
      return TV_ScrollIntoView(pTree, hItem);
  }
  
  //
  //  Walk up the tree towards the root until we find the item at level iLevel.
  //  Note the cast to (char) because iLevel is a BYTE, so the root's level is
  //  0xFF.  Casting to (char) turns 0xFF it into -1.
  //
  HTREEITEM TV_WalkToLevel(HTREEITEM hWalk, int iLevel)
  {
      int i;
      for (i = (char)hWalk->iLevel - iLevel; i > 0; i--)
          hWalk = hWalk->hParent;
      return hWalk;
  }
  
  // this is to handle single expand mode.
  // The new selection is toggled, and the old selection is collapsed
  
  // assume that parents of hNewSel are already fully expanded
  // to do this, we build a parent dpa for the old and new
  // then go through find the first parent node of the old selection that's not in
  // the new sel tree.  and expand that.
  void TV_ExpandOnSelChange(PTREE pTree, TREEITEM *hNewSel, TREEITEM *hOldSel)
  {
      LRESULT dwAbort;
      NM_TREEVIEW nm;
      BOOL fCollapsing;
      TVWATCHEDITEM wiOld, wiNew;
  
      // Revalidate hNewSel and hOldSel since they may have been deleted
      // during all the notifications that occurred in the meantime.
      if (!ValidateTreeItem(hOldSel, VTI_NULLOK) ||
          !ValidateTreeItem(hNewSel, VTI_NULLOK))
          return;
  
      if (TV_StartWatch(pTree, &wiOld, hOldSel))
      {
          if (TV_StartWatch(pTree, &wiNew, hNewSel))
          {
              // Let the app clean up after itself
              nm.itemOld.hItem = hOldSel;
              if (hOldSel)
                  nm.itemOld.lParam = hOldSel->lParam;
              nm.itemOld.mask = (TVIF_HANDLE | TVIF_PARAM);
  
              nm.itemNew.hItem = hNewSel;
              if (hNewSel)
                  nm.itemNew.lParam = hNewSel->lParam;
              nm.itemNew.mask = (TVIF_HANDLE | TVIF_PARAM);
  
              dwAbort = CCSendNotify(&pTree->ci, TVN_SINGLEEXPAND, &nm.hdr);
  
              UpdateWindow(pTree->ci.hwnd);
  
              // Revalidate hNewSel and hOldSel since they may have been deleted
              // by that notification.
              if (!TV_IsWatchValid(pTree, &wiOld) ||
                  !TV_IsWatchValid(pTree, &wiNew))
                  goto cleanup;
  
              // Collapse if the NewSel currently expanded.
              fCollapsing = hNewSel && (hNewSel->state & TVIS_EXPANDED);
  
              // Note that Ctrl+select allows the user to suppress the collapse
              // of the old selection.
              if (!(dwAbort & TVNRET_SKIPOLD)  && hOldSel && GetKeyState(VK_CONTROL) >= 0)
              {
                  if (!(pTree->dwExStyle & TVS_EX_NOSINGLECOLLAPSE) )
              	{
                      //
                      //  Collapse parents until we reach the common ancestor between
                      //  hOldSel and hNewSel.  Note carefully that we don't cache
                      //  any HTREEITEMs to avoid revalidation problems.
                      //
  
                      //
                      //  Find the common ancestor, which might be the tree root.
                      //
                      int iLevelCommon;
  
                      if (!hNewSel)
                          iLevelCommon = -1;          // common ancestor is root
                      else
                      {
                          HTREEITEM hItemO, hItemN;
                          iLevelCommon = min((char)hOldSel->iLevel, (char)hNewSel->iLevel);
                          hItemO = TV_WalkToLevel(hOldSel, iLevelCommon);
                          hItemN = TV_WalkToLevel(hNewSel, iLevelCommon);
                          while (iLevelCommon >= 0 && hItemO != hItemN) {
                              iLevelCommon--;
                              hItemO = hItemO->hParent;
                              hItemN = hItemN->hParent;
                          }
                      }
  
                      //
                      //  Now walk up the tree from hOldSel, collapsing everything
                      //  until we reach the common ancestor.  Do not collapse the
                      //  common ancestor.
                      //
  
                      while ((char)hOldSel->iLevel > iLevelCommon)
                      {
                          TV_Expand(pTree, TVE_COLLAPSE, hOldSel, TRUE);
                          if (!TV_IsWatchValid(pTree, &wiOld))
                              break;
                          hOldSel = hOldSel->hParent;
                          TV_RestartWatch(pTree, &wiOld, hOldSel);
                      }
                  }
                  else if (hNewSel && hOldSel->hParent == hNewSel->hParent) // Only if are direct siblings
              	{
                      TV_Expand(pTree, TVE_COLLAPSE, hOldSel, TRUE);
                  }
              }
  
              if ((!(dwAbort & TVNRET_SKIPNEW)) && hNewSel && TV_IsWatchValid(pTree, &wiNew) && 
                  (!(pTree->dwExStyle & TVS_EX_NOSINGLECOLLAPSE) || !(hNewSel->state & TVIS_EXPANDED)))
              {
              	WPARAM wParam = pTree->dwExStyle & TVS_EX_NOSINGLECOLLAPSE ? TVE_EXPAND : TVE_TOGGLE;
                  TV_Expand(pTree, wParam, hNewSel, TRUE);
                  UpdateWindow(pTree->ci.hwnd);
              }
  
  cleanup:
              TV_EndWatch(pTree, &wiNew);
          }
          TV_EndWatch(pTree, &wiOld);
      }
  }
  
  // ----------------------------------------------------------------------------
  //
  //  Notify the parent that the selection is about to change.  If the change is
  //  accepted, de-select the current selected item and select the given item
  //
  //  sets hCaret
  //
  // in:
  //      hItem   item to become selected
  //      wType   TVGN_ values (TVGN_CARET, TVGN_DROPHILIGHT are only valid values)
  //      flags   combination of flags
  //          TVSIFI_NOTIFY        - send notify to parent window
  //          TVSIFI_UPDATENOW     - do UpdateWindow() to force sync painting
  //          TVSIFI_NOSINGLEEXPAND- don't do single-expand stuff
  //      action  action code to send identifying how selection is being made
  //
  //  NOTE: Multiple Selection still needs to be added -- this multiplesel code
  //        is garbage
  //
  // ----------------------------------------------------------------------------
  
  BOOL TV_SelectItem(PTREE pTree, WPARAM wType, TREEITEM * hItem, UINT flags, UINT action)
  {
      UINT uRDWFlags = RDW_INVALIDATE;
  
      if (pTree->hImageList && (ImageList_GetBkColor(pTree->hImageList) == (COLORREF)-1))
          uRDWFlags |= RDW_ERASE;
  
      if (!ValidateTreeItem(hItem, VTI_NULLOK))
          return FALSE;                   // Invalid parameter
  
      switch (wType) {
  
      case TVGN_FIRSTVISIBLE:
          if (!hItem)
              return FALSE;
  
          TV_EnsureVisible(pTree, hItem);
          if (pTree->fVert) TV_SetTopItem(pTree, hItem->iShownIndex);
          break;
  
      case TVGN_DROPHILITE:
  
          ASSERT(hItem == NULL || ITEM_VISIBLE(hItem));
  
          if (hItem != pTree->hDropTarget) {
              if (pTree->hDropTarget) {
                  pTree->hDropTarget->state &= ~TVIS_DROPHILITED;
                  TV_InvalidateItem(pTree, pTree->hDropTarget, uRDWFlags);
              }
  
              if (hItem) {
                  hItem->state |= TVIS_DROPHILITED;
                  TV_InvalidateItem(pTree, hItem, uRDWFlags);
              }
              pTree->hDropTarget = hItem;
  
              if (pTree->hCaret) {
                  TV_InvalidateItem(pTree, pTree->hCaret, uRDWFlags);
              }
  
  
              if (flags & TVSIFI_UPDATENOW)
                  UpdateWindow(pTree->ci.hwnd);
          }
          break;
  
      case TVGN_CARET:
  
          // REVIEW: we may want to scroll into view in this case
          // it's already the selected item, just return
          if (pTree->hCaret != hItem) 
          {
              TREEITEM * hOldSel;
  
              if ((flags & TVSIFI_NOTIFY) && TV_SendSelChange(pTree, TVN_SELCHANGING, pTree->hCaret, hItem, action))
                  return FALSE;
  
              if (pTree->hCaret) {
                  pTree->hCaret->state &= ~TVIS_SELECTED;
                  TV_InvalidateItem(pTree, pTree->hCaret, uRDWFlags);
              }
  
              hOldSel = pTree->hCaret;
              pTree->hCaret = hItem;
  
              if (hItem) 
              {
                  hItem->state |= TVIS_SELECTED;
  
                  // make sure this item is not in a collapsed branch
                  TV_ExpandParents(pTree, hItem, (flags & TVSIFI_NOTIFY));
  
                  TV_InvalidateItem(pTree, hItem, uRDWFlags );
  
                  if (action == TVC_BYMOUSE) {
                      // if selected by mouse, let's wait a doubleclick sec before scrolling
                      SetTimer(pTree->ci.hwnd, IDT_SCROLLWAIT, GetDoubleClickTime(), NULL);
                      pTree->fScrollWait = TRUE;
                  } else if (pTree->fRedraw)
                      TV_ScrollVertIntoView(pTree, hItem);
              }
              if (pTree->hwndToolTips)
                  TV_Timer(pTree, IDT_TOOLTIPWAIT);
  
              if (flags & TVSIFI_NOTIFY)
                  TV_SendSelChange(pTree, TVN_SELCHANGED, hOldSel, hItem, action);
  
              if ((pTree->ci.style & TVS_SINGLEEXPAND) &&
                  !(flags & TVSIFI_NOSINGLEEXPAND) &&
                  action != TVC_BYKEYBOARD &&
                  hOldSel)
              {
                  TV_ExpandOnSelChange(pTree, pTree->hCaret, hOldSel);
              }
  
              if (flags & TVSIFI_UPDATENOW)
                  UpdateWindow(pTree->ci.hwnd);
  
              NotifyWinEvent(EVENT_OBJECT_FOCUS, pTree->ci.hwnd, OBJID_CLIENT,
                  TV_GetAccId(hItem));
              NotifyWinEvent(EVENT_OBJECT_SELECTION, pTree->ci.hwnd, OBJID_CLIENT,
                  TV_GetAccId(hItem));
          }
          break;
  
      default:
          DebugMsg(DM_TRACE, TEXT("Invalid type passed to TV_SelectItem"));
          return FALSE;
      }
  
      return TRUE;        // success
  }
  
  // remove all the children, but pretend they are still there
  
  BOOL TV_ResetItem(PTREE pTree, HTREEITEM hItem)
  {
      TV_DeleteItem(pTree, hItem, TVDI_CHILDRENONLY);
  
      hItem->state &= ~TVIS_EXPANDEDONCE;
      hItem->fKids = KIDS_FORCE_YES;      // force children
  
      return TRUE;
  }
  
  
  // ----------------------------------------------------------------------------
  //
  //  Expand or collapse an item's children
  //  Returns TRUE if any change took place and FALSE if unchanged
  //
  // ----------------------------------------------------------------------------
  
  BOOL TV_Expand(PTREE pTree, WPARAM wCode, TREEITEM * hItem, BOOL fNotify)
  {
      WORD fOldState;
      UINT cntVisDescendants;
      TVITEMEX sItem;
      TREEITEM * hItemExpanding;
  
  // deal with the evil invisible root for multiple root trees.
      hItemExpanding = hItem;
      if ((hItem == NULL) || (hItem == TVI_ROOT))
          hItem = pTree->hRoot;
  
      DBG_ValidateTreeItem(hItem, 0);
  
      TV_GetItem(pTree, hItem, TVIF_CHILDREN, &sItem);
  
      if (!(wCode & TVE_ACTIONMASK) || sItem.cChildren == 0)
          return FALSE;           // no children to expand or collapse
  
      if ((wCode & TVE_ACTIONMASK) == TVE_TOGGLE) {
          wCode = (wCode & ~TVE_ACTIONMASK);
  
          // if it's not expaned, or not fully expanded, expand now
          wCode |=
              (((!(hItem->state & TVIS_EXPANDED)) ||
                hItem->state & TVIS_EXPANDPARTIAL) ?
               TVE_EXPAND : TVE_COLLAPSE);
      }
  
      if (((wCode & TVE_ACTIONMASK) == TVE_EXPAND) && !(hItem->state & TVIS_EXPANDEDONCE))
      {
          // if its the first expand, ALWAYS notify the parent
          fNotify = TRUE;
      }
  
      // at this point the children may be added if they aren't already there (callback)
  
      if (fNotify && TV_SendItemExpand(pTree, TVN_ITEMEXPANDING, hItemExpanding, wCode))
          return FALSE;
  
      // if (!hItem->hKids && (hItem->fKids == KIDS_FORCE_NO))    // this may be right, but I don't
                                                                  // have proof now.
      if (!hItem->hKids)
      {
          // kids we removed, or never there
          TV_InvalidateItem(pTree, hItem, RDW_INVALIDATE);
          return FALSE;
      }
  
      fOldState = hItem->state;
  
      if (hItem->hParent) // never turn off TVIS_EXPANED for the invisible root
      {
          if ((wCode & TVE_ACTIONMASK) == TVE_EXPAND)
             hItem->state |= TVIS_EXPANDED;
          else
             hItem->state &= ~(TVIS_EXPANDED | TVIS_EXPANDPARTIAL);
  
          if (wCode & TVE_EXPANDPARTIAL) {
              hItem->state |= TVIS_EXPANDPARTIAL;
          } else {
              hItem->state &= ~(TVIS_EXPANDPARTIAL);
          }
      }
  
      // if we're not changing the expanded state
      // check to see if we're supposed to collapse reset
      if (!(fOldState & TVIS_EXPANDED) &&
          !(hItem->state & TVIS_EXPANDED))
      {
          if ((wCode & (TVE_ACTIONMASK | TVE_COLLAPSERESET)) == (TVE_COLLAPSE | TVE_COLLAPSERESET))
          {
              TV_ResetItem(pTree, hItem);
          }
  
          return FALSE;
      }
  
      // if we changed expaneded states, recalc the scrolling
      if ((fOldState ^ hItem->state) & TVIS_EXPANDED) {
  
          cntVisDescendants = TV_ScrollBelow(pTree, hItem, TRUE, hItem->state & TVIS_EXPANDED);
  
          if (hItem->state & TVIS_EXPANDED)
          {
              UINT wNewTop, wTopOffset, wLastKid;
  
              TV_ScrollBarsAfterExpand(pTree, hItem);
  
              wNewTop = pTree->hTop->iShownIndex;
              wTopOffset = hItem->iShownIndex - wNewTop;
  
              wLastKid = wTopOffset + cntVisDescendants + 1;
  
              if (wLastKid > pTree->cFullVisible)
              {
                  wNewTop += min(wLastKid - pTree->cFullVisible, wTopOffset);
                  TV_SetTopItem(pTree, wNewTop);
              }
          }
          else
          {
              TV_ScrollBarsAfterCollapse(pTree, hItem);
              TV_ScrollVertIntoView(pTree, hItem);
  
              // If we collapsed the subtree that contains the caret, then
              // pop the caret back to the last visible ancestor
              // Pass TVIS_NOSINGLEEXPAND so we won't expand an item right
              // after we collapsed it (d'oh!)
              if (pTree->hCaret)
              {
                  TREEITEM * hWalk = TV_WalkToLevel(pTree->hCaret, hItem->iLevel);
  
                  if (hWalk == hItem)
                  {
                      TV_SelectItem(pTree, TVGN_CARET, hItem, (fNotify ? TVSIFI_NOTIFY : 0) | TVSIFI_UPDATENOW | TVSIFI_NOSINGLEEXPAND, TVC_UNKNOWN);
                  }
              }
          }
      } else if ((fOldState ^ hItem->state) & TVIS_EXPANDPARTIAL) {
          // we didn't change the expanded state, only the expand partial
          TV_InvalidateItem(pTree, hItem, RDW_INVALIDATE);
      }
  
      if (fNotify && TV_SendItemExpand(pTree, TVN_ITEMEXPANDED, hItem, wCode))
          return FALSE;
  
      hItem->state |= TVIS_EXPANDEDONCE;
  
      if ((wCode & (TVE_ACTIONMASK | TVE_COLLAPSERESET)) == (TVE_COLLAPSE | TVE_COLLAPSERESET))
      {
          TV_ResetItem(pTree, hItem);
      }
  
      // Bug#94368 raymondc v6 we generate a notification even if nothing happened,
      // which confuses accessibility.  E.g., app tried to expand something
      // that was already expanded.  Explorer Band does this when you navigate.
      NotifyWinEvent(EVENT_OBJECT_STATECHANGE, pTree->ci.hwnd, OBJID_CLIENT,
          TV_GetAccId(hItem));
  
      return TRUE;
  }
  
  BOOL BetweenItems(PTREE pTree, HTREEITEM hItem, HTREEITEM hItemStart, HTREEITEM hItemEnd)
  {
      if (hItemStart) {
          while ((hItemStart = TV_GetNextVisItem(hItemStart)) && (hItemEnd != hItemStart))
          {
              if (hItem == hItemStart)
                  return TRUE;
          }
      }
      return FALSE;
  }
  
  // Now only Korean version is interested in incremental search with composition string.
  
  #define FREE_COMP_STRING(pszCompStr)    LocalFree((HLOCAL)(pszCompStr))
  
  BOOL TV_OnImeComposition(PTREE pTree, WPARAM wParam, LPARAM lParam)
  {
      LPTSTR lpsz;
      int iCycle = 0;
      HTREEITEM hItem;
      TCHAR szTemp[MAXLABELTEXT];
      TVITEMEX ti;
      LPTSTR lpszAlt = NULL; // use only if SameChar
      int iLen;
      HIMC hImc;
      TCHAR *pszCompStr;
      BOOL fRet = TRUE;
  
      if (hImc = ImmGetContext(pTree->ci.hwnd))
      {
          if (lParam & GCS_RESULTSTR)
          {
              fRet = FALSE;
              pszCompStr = GET_COMP_STRING(hImc, GCS_RESULTSTR);
              if (pszCompStr)
              {
                  IncrementSearchImeCompStr(&pTree->is, FALSE, pszCompStr, &lpsz);
                  FREE_COMP_STRING(pszCompStr);
              }
          }
  
          if (lParam & GCS_COMPSTR)
          {
              fRet = TRUE;
              pszCompStr = GET_COMP_STRING(hImc, GCS_COMPSTR);
              if (pszCompStr)
              {
                  if (IncrementSearchImeCompStr(&pTree->is, TRUE, pszCompStr, &lpsz))
                  {
                      if (pTree->hCaret)
                      {
                          pTree->htiSearch = pTree->hCaret;
                      } 
                      else if (pTree->hRoot && pTree->hRoot->hKids)
                      {
                          pTree->htiSearch = pTree->hRoot->hKids;
                      } else
                          return fRet;
                  }
  
                  if (!lpsz || !*lpsz || !pTree->hRoot || !pTree->hRoot->hKids)
                      return fRet;
  
                  hItem = pTree->htiSearch;
                  ti.cchTextMax  = sizeof(szTemp);
                  iLen = lstrlen(lpsz);
                  if (iLen > 1 && SameChars(lpsz, lpsz[0]))
                      lpszAlt = lpsz + iLen - 1;
  
                  do 
                  {
                      ti.pszText = szTemp;
                      hItem = TV_GetNextVisItem(hItem);
                      if (!hItem) 
                      {
                          iCycle++;
                          hItem = pTree->hRoot->hKids;
                      }
  
                      TV_GetItem(pTree, hItem, TVIF_TEXT, &ti);
                      if ((ti.pszText != LPSTR_TEXTCALLBACK) &&
                          HIWORD64(ti.pszText)) 
                      {
                          // DebugMsg(DM_TRACE, "treesearch %d %s %s", (LPSTR)lpsz, (LPSTR)lpsz, (LPSTR)ti.pszText);
                          if (IntlStrEqNI(lpsz, ti.pszText, iLen) ||
                              (lpszAlt && IntlStrEqNI(lpszAlt, ti.pszText, 1) &&
                               BetweenItems(pTree, hItem, pTree->hCaret, pTree->htiSearch)))
                          {
                              DebugMsg(DM_TRACE, TEXT("Selecting"));
                              TV_SelectItem(pTree, TVGN_CARET, hItem, TVSIFI_NOTIFY | TVSIFI_UPDATENOW, TVC_BYKEYBOARD);
                              //notify of navigation key usage
                              CCNotifyNavigationKeyUsage(&(pTree->ci), UISF_HIDEFOCUS);
                              return fRet;
                          }
                      }
                  }  while(iCycle < 2);
  
                  // if they hit the same key twice in a row at the beginning of
                  // the search, and there was no item found, they likely meant to
                  // retstart the search
                  if (lpszAlt)
                  {
  
                      // first clear out the string so that we won't recurse again
                      IncrementSearchString(&pTree->is, 0, NULL);
                      TV_OnImeComposition(pTree, wParam, lParam);
                  } 
                  else 
                  {
                      IncrementSearchBeep(&pTree->is);
                  }
                  //notify of navigation key usage
                  CCNotifyNavigationKeyUsage(&(pTree->ci), UISF_HIDEFOCUS);
                  FREE_COMP_STRING(pszCompStr);
              }
          }
          ImmReleaseContext(pTree->ci.hwnd, hImc);
      }
      return fRet;
  }
  
  
  void TV_OnChar(PTREE pTree, UINT ch, int cRepeat)
  {
      LPTSTR lpsz = NULL;
      int iCycle = 0;
      HTREEITEM hItem;
      TCHAR szTemp[MAXLABELTEXT];
      TVITEMEX ti;
      LPTSTR lpszAlt = NULL; // use only if SameChar
      int iLen;
  
      if (IncrementSearchString(&pTree->is, ch, &lpsz) || !pTree->htiSearch) 
      {
          if (pTree->hCaret) 
          {
              pTree->htiSearch = pTree->hCaret;
          } 
          else if (pTree->hRoot && pTree->hRoot->hKids) 
          {
              pTree->htiSearch = pTree->hRoot->hKids;
          } else
              return;
      }
  
      if (!lpsz || !*lpsz || !pTree->hRoot || !pTree->hRoot->hKids)
          return;
  
      hItem = pTree->htiSearch;
      ti.cchTextMax  = ARRAYSIZE(szTemp);
      iLen = lstrlen(lpsz);
      if (iLen > 1 && SameChars(lpsz, lpsz[0]))
          lpszAlt = lpsz + iLen - 1;
  
      do {
          ti.pszText = szTemp;
          hItem = TV_GetNextVisItem(hItem);
          if (!hItem) {
              iCycle++;
              hItem = pTree->hRoot->hKids;
          }
  
          TV_GetItem(pTree, hItem, TVIF_TEXT, &ti);
          if ((ti.pszText != LPSTR_TEXTCALLBACK) &&
              HIWORD64(ti.pszText)) {
              // DebugMsg(DM_TRACE, TEXT("treesearch %d %s %s"), (LPTSTR)lpsz, (LPTSTR)lpsz, (LPTSTR)ti.pszText);
              if (IntlStrEqNI(lpsz, ti.pszText, iLen) ||
                  (lpszAlt && IntlStrEqNI(lpszAlt, ti.pszText, 1) &&
                   BetweenItems(pTree, hItem, pTree->hCaret, pTree->htiSearch)))
              {
                  DebugMsg(DM_TRACE, TEXT("Selecting"));
                  TV_SelectItem(pTree, TVGN_CARET, hItem, TVSIFI_NOTIFY | TVSIFI_UPDATENOW, TVC_BYKEYBOARD);
  
                  //notify of navigation key usage
                  CCNotifyNavigationKeyUsage(&(pTree->ci), UISF_HIDEFOCUS);
                  return;
              }
          }
      }  while(iCycle < 2);
  
      // if they hit the same key twice in a row at the beginning of
      // the search, and there was no item found, they likely meant to
      // retstart the search
      if (lpszAlt) 
      {
  
          // first clear out the string so that we won't recurse again
          IncrementSearchString(&pTree->is, 0, NULL);
          TV_OnChar(pTree, ch, cRepeat);
      } 
      else 
      {
          IncrementSearchBeep(&pTree->is);
      }
  
      //notify of navigation key usage
      CCNotifyNavigationKeyUsage(&(pTree->ci), UISF_HIDEFOCUS);
  }
  
  // ----------------------------------------------------------------------------
  //
  //  Handle WM_KEYDOWN messages
  //  If control key is down, treat keys as scroll codes; otherwise, treat keys
  //  as caret position changes.
  //
  // ----------------------------------------------------------------------------
  
  BOOL TV_KeyDown(PTREE pTree, WPARAM wKey, LPARAM dwKeyData)
  {
      TREEITEM * hItem;
      UINT wShownIndex;
      TV_KEYDOWN nm;
      BOOL fPuntChar;
      BOOL ret = TRUE;
  
      // Notify
      nm.wVKey = (WORD)wKey;
      fPuntChar = (BOOL)CCSendNotify(&pTree->ci, TVN_KEYDOWN, &nm.hdr);
  
      wKey = RTLSwapLeftRightArrows(&pTree->ci, wKey);
  
      if (GetKeyState(VK_CONTROL) < 0)
      {
          // control key is down
          UINT wScrollCode;
  
          switch (wKey)
          {
              case VK_LEFT:
                  TV_HorzScroll(pTree, SB_LINEUP, 0);
                  break;
  
              case VK_RIGHT:
                  TV_HorzScroll(pTree, SB_LINEDOWN, 0);
                  break;
  
              case VK_PRIOR:
                  wScrollCode = SB_PAGEUP;
                  goto kdVertScroll;
  
              case VK_HOME:
                  wScrollCode = SB_TOP;
                  goto kdVertScroll;
  
              case VK_NEXT:
                  wScrollCode = SB_PAGEDOWN;
                  goto kdVertScroll;
  
              case VK_END:
                  wScrollCode = SB_BOTTOM;
                  goto kdVertScroll;
  
              case VK_UP:
                  wScrollCode = SB_LINEUP;
                  goto kdVertScroll;
  
              case VK_DOWN:
                  wScrollCode = SB_LINEDOWN;
  kdVertScroll:
                  TV_VertScroll(pTree, wScrollCode, 0);
                  break;
  
              default:
                  ret = FALSE;
          }
  
      } else {
  
          switch (wKey)
          {
          case VK_RETURN:
              fPuntChar = (BOOL)CCSendNotify(&pTree->ci, NM_RETURN, NULL);
              break;
  
          case VK_PRIOR:
              if (pTree->hCaret && (pTree->hCaret->iShownIndex > (pTree->cFullVisible - 1)))
              {
                  wShownIndex = pTree->hCaret->iShownIndex - (pTree->cFullVisible - 1);
                  goto selectIndex;
              }
              // fall thru
  
          case VK_HOME:
              wShownIndex = 0;
              goto selectIndex;
  
          case VK_NEXT:
              if (!pTree->hCaret)
              {
                  wShownIndex = 0;
                  goto selectIndex;
              }
              wShownIndex = pTree->hCaret->iShownIndex + (pTree->cFullVisible - 1);
              if (wShownIndex < pTree->cShowing)
                  goto selectIndex;
              // fall thru
  
          case VK_END:
              wShownIndex = pTree->cShowing - 1;
  selectIndex:
              hItem = TV_GetShownIndexItem(pTree->hRoot->hKids, wShownIndex);
              goto kdSetCaret;
              break;
  
          case VK_SUBTRACT:
              if (pTree->hCaret) {
                  fPuntChar = TRUE;
                  TV_Expand(pTree, TVE_COLLAPSE, pTree->hCaret, TRUE);
              }
              break;
  
          case VK_ADD:
              if (pTree->hCaret) {
                  fPuntChar = TRUE;
                  TV_Expand(pTree, TVE_EXPAND, pTree->hCaret, TRUE);
              }
              break;
  
          case VK_MULTIPLY:
              if (pTree->hCaret) {
                  fPuntChar = TRUE;
                  TV_ExpandRecurse(pTree, pTree->hCaret, TRUE);
              }
              break;
  
          case VK_LEFT:
              if (pTree->hCaret && (pTree->hCaret->state & TVIS_EXPANDED)) {
                  TV_Expand(pTree, TVE_COLLAPSE, pTree->hCaret, TRUE);
                  break;
              } else if (pTree->hCaret) {
                  hItem = VISIBLE_PARENT(pTree->hCaret);
                  goto kdSetCaret;
              }
              break;
  
          case VK_BACK:
              // get the parent, avoiding the root item
              fPuntChar = TRUE;
              if (pTree->hCaret) {
                  hItem = VISIBLE_PARENT(pTree->hCaret);
                  goto kdSetCaret;
              }
              break;
  
          case VK_UP:
              if (pTree->hCaret)
                  hItem = TV_GetPrevVisItem(pTree->hCaret);
              else
                  hItem = pTree->hRoot->hKids;
  
              goto kdSetCaret;
              break;
  
  
          case VK_RIGHT:
              if (pTree->hCaret && !(pTree->hCaret->state & TVIS_EXPANDED)) {
                  TV_Expand(pTree, TVE_EXPAND, pTree->hCaret, TRUE);
                  break;
              } // else fall through
  
          case VK_DOWN:
              if (pTree->hCaret)
                  hItem = TV_GetNextVisItem(pTree->hCaret);
              else
                  hItem = pTree->hRoot->hKids;
  
  kdSetCaret:
              if (hItem)
                  TV_SelectItem(pTree, TVGN_CARET, hItem, TVSIFI_NOTIFY | TVSIFI_UPDATENOW, TVC_BYKEYBOARD);
  
              break;
  
          case VK_SPACE:
              if ((pTree->ci.style & TVS_CHECKBOXES) && pTree->hCaret)
              {
                  TV_HandleStateIconClick(pTree, pTree->hCaret);
                  fPuntChar = TRUE; // don't beep
              }
              break;
  
          default:
              ret = FALSE;
          }
      }
  
      if (fPuntChar) {
          pTree->iPuntChar++;
      } else if (pTree->iPuntChar){
          // this is tricky...  if we want to punt the char, just increment the
          // count.  if we do NOT, then we must clear the queue of WM_CHAR's
          // this is to preserve the iPuntChar to mean "punt the next n WM_CHAR messages
          MSG msg;
          while((pTree->iPuntChar > 0) && PeekMessage(&msg, pTree->ci.hwnd, WM_CHAR, WM_CHAR, PM_REMOVE)) {
              pTree->iPuntChar--;
          }
          //ASSERT(!pTree->iPuntChar);
      }
  
  
      if ( VK_MENU!=wKey )
      {
          //notify of navigation key usage
          CCNotifyNavigationKeyUsage(&(pTree->ci), UISF_HIDEFOCUS);
      }
  
      return ret;
  
  }
  
  
  // ----------------------------------------------------------------------------
  //
  //  Sets the tree's indent width per hierarchy level and recompute widths.
  //
  //  sets cxIndent
  //
  // ----------------------------------------------------------------------------
  
  void TV_SetIndent(PTREE pTree, WPARAM cxIndent)
  {
      if (pTree->hImageList) {
          if ((SHORT)cxIndent < pTree->cxImage)
              cxIndent = pTree->cxImage;
      }
  
      if ((SHORT)cxIndent < pTree->cyText)
          cxIndent = pTree->cyText;
  
      if (cxIndent < MAGIC_MININDENT)
          cxIndent = MAGIC_MININDENT;
  
      pTree->cxIndent = (SHORT)cxIndent;
  
      TV_CreateIndentBmps(pTree);
      TV_ScrollBarsAfterSetWidth(pTree, NULL);
  }
  
  // ----------------------------------------------------------------------------
  //
  //  Sets the tree's item height to be the maximum of the image height and text
  //  height.  Then recompute the tree's full visible count.
  //
  //  sets cyItem, cFullVisible
  //
  // ----------------------------------------------------------------------------
  
  void TV_SetItemHeight(PTREE pTree)
  {
      // height MUST be even with TVS_HASLINES -- go ahead and make it always even
      if (!pTree->fCyItemSet)
          pTree->cyItem = (max(pTree->cyImage, pTree->cyText) + 1);
      // height not always even not, only on haslines style.
      if (pTree->cyItem <= 1) {
          pTree->cyItem = 1;          // Don't let it go zero or negative!
      } else if (!(pTree->ci.style & TVS_NONEVENHEIGHT))
          pTree->cyItem &= ~1;
  
      pTree->cFullVisible = pTree->cyWnd / pTree->cyItem;
  
      TV_CreateIndentBmps(pTree);
      TV_CalcScrollBars(pTree);
  }
  
  HIMAGELIST TV_SetImageList(PTREE pTree, HIMAGELIST hImage, int iImageIndex)
  {
      HIMAGELIST hImageOld = NULL;
      int cx;
      int cy;
  
      switch (iImageIndex) 
      {
  
          case TVSIL_STATE:
  
              hImageOld = pTree->himlState;
              pTree->himlState = hImage;
              if (hImage) 
              {
                  CCGetIconSize(&pTree->ci, hImage, &pTree->cxState , &pTree->cyState);
              } 
              else 
              {
                  pTree->cxState = 0;
              }
              break;
  
          case TVSIL_NORMAL:
              hImageOld = pTree->hImageList;
              if (hImage && CCGetIconSize(&pTree->ci, hImage, &cx, &cy))
              {
                  pTree->cxNativeImage = (SHORT)cx;
                  pTree->cyNativeImage = (SHORT)cy;
  
                  pTree->cxImage = (SHORT)((pTree->cxNativeImage) + MAGIC_INDENT);
                  pTree->cyImage = (SHORT)(pTree->cyNativeImage);
  
                  if (pTree->cxIndent < pTree->cxImage)
                      TV_SetIndent(pTree, pTree->cxImage);
                  pTree->hImageList = hImage;
  
                  if (!hImageOld && pTree->ci.style & TVS_CHECKBOXES)
                  {
                      TV_InitCheckBoxes(pTree);
                  }
              }
              else
              {
                  pTree->cxImage = 0;
                  pTree->cyImage = 0;
                  pTree->cxNativeImage = 0;
                  pTree->cyNativeImage = 0;
                  pTree->hImageList = NULL;
              }
              break;
  
          default:
              DebugMsg(DM_TRACE, TEXT("sh TR - TVM_SETIMAGELIST: unrecognized iImageList"));
              break;
  
      }
  
      TV_ScrollBarsAfterSetWidth(pTree, NULL);
      TV_SetItemHeight(pTree);
  
      return hImageOld;
  }
  
  // Bug#94345: does not deal with hfont == NULL
  
  void TV_OnSetFont(PTREE pTree, HFONT hNewFont, BOOL fRedraw)
  {
      HDC hdc;
      HFONT hfontSel;
      TCHAR c = TEXT('J');       // for bog
      SIZE size;
  
      if (pTree->fCreatedFont && pTree->hFont)
      {
          DeleteObject(pTree->hFont);
          pTree->fCreatedFont = FALSE;
      }
  
      if (hNewFont == NULL)
      {
          LOGFONT lf;
          HRESULT hr = E_FAIL;
  
          // Create identical theme font. Although theme manager APIs will be used for drawing text,
          // create a copy of the normal state font. The control will be sized based on this font's dimensions
          if (pTree->hTheme)
          {
              ZeroMemory(&lf, SIZEOF(lf));
              hr = GetThemeFont(pTree->hTheme, NULL, TVP_TREEITEM, TREIS_NORMAL, TMT_FONT, &lf);
          }
  
          if (FAILED(hr))
          {
              SystemParametersInfo(SPI_GETICONTITLELOGFONT, sizeof(lf), &lf, FALSE);
          }
  
          hNewFont = CreateFontIndirect(&lf);
          pTree->fCreatedFont = TRUE;         // make sure we delete it
      }
  
      hdc = GetDC(pTree->ci.hwnd);
  
      hfontSel = hNewFont ? SelectObject(hdc, hNewFont) : NULL;
  
      // Office9 Setup had a bug where they installed a bogus font,
      // which created okay but all APIs against it (e.g., GetTextExtentPoint)
      // failed!  Protect against failure by pre-setting the value to something
      // non-garbage.
      size.cy = 0;
      GetTextExtentPoint(hdc, &c, 1, &size);
  
      pTree->cyText = (SHORT)(size.cy + (g_cyBorder * 2));
  
      if (hfontSel)
          SelectObject(hdc, hfontSel);
  
      ReleaseDC(pTree->ci.hwnd, hdc);
  
      pTree->hFont = hNewFont;
      if (pTree->hFontBold)
          TV_CreateBoldFont(pTree);
  
      pTree->ci.uiCodePage = GetCodePageForFont(hNewFont);
  
      TV_DeleteHotFonts(pTree);
  
      TV_SetIndent(pTree, 16 /*g_cxSmIcon*/ + MAGIC_INDENT);
      TV_SetImageList(pTree, pTree->hImageList, TVSIL_NORMAL);
  
      TV_ScrollBarsAfterSetWidth(pTree, NULL);
      TV_SetItemHeight(pTree);
  
      if (pTree->hwndToolTips)
          SendMessage(pTree->hwndToolTips, WM_SETFONT, (WPARAM)pTree->hFont, (LPARAM)TRUE);
  
      // REVIEW: does this happen as a result of the above?
      // if (fRedraw)
      //    RedrawWindow(pTree->ci.hwnd, NULL, NULL, RDW_INVALIDATE | RDW_ERASE);
  }
  
  void TV_CreateBoldFont(PTREE pTree)
  {
      LOGFONT lf;
  
      if (pTree->hFontBold)
          DeleteObject (pTree->hFontBold);
  
      GetObject(pTree->hFont, sizeof (lf), &lf);
      CCAdjustForBold(&lf);
      pTree->hFontBold = CreateFontIndirect(&lf);
  }
  
  // ----------------------------------------------------------------------------
  //
  //  Gets the item with the described relationship to the given item, NULL if
  //  no item can be found with that relationship.
  //
  // ----------------------------------------------------------------------------
  
  TREEITEM * TV_GetNextItem(PTREE pTree, TREEITEM * hItem, WPARAM wGetCode)
  {
      switch (wGetCode) {
      case TVGN_ROOT:
          return pTree->hRoot->hKids;
  
      case TVGN_DROPHILITE:
          return pTree->hDropTarget;
  
      case TVGN_CARET:
          return pTree->hCaret;
  
      case TVGN_FIRSTVISIBLE:
          return pTree->hTop;
  
      case TVGN_LASTVISIBLE:
          return TV_GetShownIndexItem(pTree->hRoot->hKids, pTree->cShowing-1);
  
      case TVGN_CHILD:
          if (!hItem || (hItem == TVI_ROOT))
              return pTree->hRoot->hKids;
          break;
      }
  
      // all of these require a valid hItem
      if (!ValidateTreeItem(hItem, 0))
          return NULL;
  
      switch (wGetCode) {
      case TVGN_NEXTVISIBLE:
          return TV_GetNextVisItem(hItem);
  
      case TVGN_PREVIOUSVISIBLE:
          return TV_GetPrevVisItem(hItem);
  
      case TVGN_NEXT:
          return hItem->hNext;
  
      case TVGN_PREVIOUS:
          if (hItem->hParent->hKids == hItem)
              return NULL;
          else {
              TREEITEM * hWalk;
              for (hWalk = hItem->hParent->hKids; hWalk->hNext != hItem; hWalk = hWalk->hNext);
              return hWalk;
          }
  
      case TVGN_PARENT:
          return VISIBLE_PARENT(hItem);
  
      case TVGN_CHILD:
          return hItem->hKids;
      }
  
      return NULL;
  }
  
  
  // ----------------------------------------------------------------------------
  //
  //  Returns the number of items (including the partially visible item at the
  //  bottom based on the given flag) that fit in the tree's client window.
  //
  // ----------------------------------------------------------------------------
  
  LRESULT TV_GetVisCount(PTREE pTree, BOOL fIncludePartial)
  {
      int  i;
  
      if (!fIncludePartial)
          return(MAKELRESULTFROMUINT(pTree->cFullVisible));
  
      i = pTree->cFullVisible;
  
      if (pTree->cyWnd - (i * pTree->cyItem))
          i++;
  
      return i;
  }
  
  
  void TV_InvalidateInsertMarkRect(PTREE pTree, BOOL fErase)
  {
      RECT rc;
      if (TV_GetInsertMarkRect(pTree, &rc))
          InvalidateRect(pTree->ci.hwnd, &rc, fErase);
  }
  
  // ----------------------------------------------------------------------------
  //
  //  recomputes tree's fields that rely on the tree's client window size
  //
  //  sets cxWnd, cyWnd, cFullVisible
  //
  // ----------------------------------------------------------------------------
  
  BOOL TV_SizeWnd(PTREE pTree, UINT cxWnd, UINT cyWnd)
  {
      RECT rc;
      UINT cxOld = pTree->cxWnd;
      if (!cxWnd || !cyWnd)
      {
          GetClientRect(pTree->ci.hwnd, &rc);
          cxWnd = rc.right;
          cyWnd = rc.bottom;
      }
      pTree->cxWnd = (SHORT)cxWnd;
      pTree->cyWnd = (SHORT)cyWnd;
      pTree->cFullVisible = cyWnd / pTree->cyItem;
      
      if (pTree->ci.style & TVS_NOSCROLL)
          pTree->cxMax = (WORD) cxWnd;
      
      TV_CalcScrollBars(pTree);
      if (pTree->cxBorder)
      {
          rc.top = 0;
          rc.bottom = cyWnd;
          rc.right = cxOld;
          rc.left = cxOld - pTree->cxBorder;
          if (rc.left < (int)cxWnd) {
              // invalidate so clipping happens on right on size.
              InvalidateRect(pTree->ci.hwnd, &rc, TRUE);  
          }
      }
  
      TV_InvalidateInsertMarkRect(pTree, TRUE);
  
      return TRUE;
  }
  
  
  void TV_HandleStateIconClick(PTREE pTree, HTREEITEM hItem)
  {
      TVITEMEX tvi;
      int iState;
  
      tvi.stateMask = TVIS_STATEIMAGEMASK;
      TV_GetItem(pTree, hItem, TVIF_STATE, &tvi);
  
      iState = STATEIMAGEMASKTOINDEX(tvi.state & tvi.stateMask);
      iState %= (ImageList_GetImageCount(pTree->himlState) - 1);
      iState++;
  
      tvi.mask = TVIF_STATE;
      tvi.state = INDEXTOSTATEIMAGEMASK(iState);
      tvi.hItem = hItem;
      TV_SetItem(pTree, &tvi);
  
  }
  
  
  //
  //  Eudora is a piece of work.
  //
  //  When they get a NM_DBLCLK notification from a treeview, they say,
  //  "Oh, I know that treeview allocates its NMHDR from the stack, and
  //  there's this local variable on Treeview's stack I'm really interested
  //  in, so I'm going to hard-code an offset from the pnmhdr and read the
  //  DWORD at that location so I can get at the local variable.  I will then
  //  stop working if this value is zero."
  //
  //  The conversion to UNICODE changed our stack layout enough that they
  //  end up always getting zero -- it's the NULL parameter which is the
  //  final argument to CCSendNotify.  Since all this stack layout stuff is
  //  sensitive to how the compiler's optimizer feels today, we create a
  //  special notify structure Just For Eudora which mimics the stack layout
  //  they expected to see in Win95.
  //
  typedef struct NMEUDORA {
      NMHDR   nmhdr;
      BYTE    Padding[48];
      DWORD   MustBeNonzero;      // Eudora fails to install if this is zero
  } NMEUDORA;
  
  // ----------------------------------------------------------------------------
  //
  //  WM_LBUTTONDBLCLK message -- toggle expand/collapse state of item's children
  //  WM_LBUTTONDOWN message -- on item's button, do same as WM_LBUTTONDBLCLK,
  //  otherwise select item and ensure that item is fully visible
  //
  // ----------------------------------------------------------------------------
  
  void TV_ButtonDown(PTREE pTree, UINT wMsg, UINT wFlags, int x, int y, UINT TVBD_flags)
  {
      UINT wHitCode;
      TREEITEM * hItem;
      HWND hwndTree;
      LRESULT lResult;
  #ifdef _X86_
      NMEUDORA nmeu;
      nmeu.MustBeNonzero = 1;
      COMPILETIME_ASSERT(FIELD_OFFSET(NMEUDORA, MustBeNonzero) == 0x3C);
  #endif
  
      GetMessagePosClient(pTree->ci.hwnd, &pTree->ptCapture);
  
      if (!TV_DismissEdit(pTree, FALSE))   // end any previous editing (accept it)
          return;     // Something happened such that we should not process button down
  
  
      hItem = TV_CheckHit(pTree, x, y, &wHitCode);
  
      // Excel likes to destroy the entire tree when it gets a double-click
      // so we need to watch the item in case it vanishes behind our back.
      hwndTree = pTree->ci.hwnd;
  
      if (wMsg == WM_LBUTTONDBLCLK)
      {
          //
          // Cancel any name editing that might happen.
          //
  
          TV_CancelEditTimer(pTree);
  
          if (wHitCode & (TVHT_ONITEM | TVHT_ONITEMBUTTON)) {
              goto ExpandItem;
          }
  
          //
          // Collapses node above the line double clicked on
          //
          else if ((pTree->ci.style & TVS_HASLINES) && (wHitCode & TVHT_ONITEMINDENT) &&
              (abs(x % pTree->cxIndent - pTree->cxIndent/2) <= g_cxDoubleClk)) {
  
              int i;
  
              for (i = hItem->iLevel - x/pTree->cxIndent + ((pTree->ci.style & TVS_LINESATROOT)?1:0); i > 1; i--)
                  hItem = hItem->hParent;
  
  ExpandItem:
  #ifdef _X86_
              lResult = CCSendNotify(&pTree->ci, wFlags & MK_RBUTTON ? NM_RDBLCLK : NM_DBLCLK, &nmeu.nmhdr);
  #else
              lResult = CCSendNotify(&pTree->ci, wFlags & MK_RBUTTON ? NM_RDBLCLK : NM_DBLCLK, NULL);
  #endif
              if (!IsWindow(hwndTree))
                  goto bail;
              if (!lResult) {
                  // don't auto expand this if we're in single expand mode because the first click did it already
                  if (!(pTree->ci.style & TVS_SINGLEEXPAND))
                      TV_Expand(pTree, TVE_TOGGLE, hItem, TRUE);
              }
  
          }
  
          pTree->fScrollWait = FALSE;
  
      } else {    // WM_LBUTTONDOWN
  
          if (wHitCode == TVHT_ONITEMBUTTON)
          {
              if (!CCSendNotify(&pTree->ci, NM_CLICK, NULL)) {
                  if (TVBD_flags & TVBD_FROMWHEEL)
                      TV_Expand(pTree, (TVBD_flags & TVBD_WHEELFORWARD) ? TVE_EXPAND : TVE_COLLAPSE, hItem, TRUE);
                  else
                      TV_Expand(pTree, TVE_TOGGLE, hItem, TRUE);
              }
          }
          else if (wHitCode & TVHT_ONITEM ||
                  ((pTree->ci.style & TVS_FULLROWSELECT) && (wHitCode & (TVHT_ONITEMRIGHT | TVHT_ONITEMINDENT))))
          {
              BOOL fSameItem, bDragging;
  
              ASSERT(hItem);
  
              fSameItem = (hItem == pTree->hCaret);
  
              if (TVBD_flags & TVBD_FROMWHEEL)
                  bDragging = FALSE;
              else if (pTree->ci.style & TVS_DISABLEDRAGDROP)
                  bDragging = FALSE;
              else {
                  bDragging = TV_CheckForDragBegin(pTree, hItem, x, y);
                  TV_FinishCheckDrag(pTree);
              }
  
              if (bDragging)
              {
                  pTree->htiDrag = hItem;
                  TV_SendBeginDrag(pTree, TVN_BEGINDRAG, hItem, x, y);
                  return;
              }
  
              if (!CCSendNotify(&pTree->ci, NM_CLICK, NULL)) {
  
                  if (wHitCode == TVHT_ONITEMSTATEICON &&
                      (pTree->ci.style & TVS_CHECKBOXES)) {
                      TV_HandleStateIconClick(pTree, hItem);
                  } else {
  
                      // Only set the caret (selection) if not dragging
                      TV_SelectItem(pTree, TVGN_CARET, hItem, TVSIFI_NOTIFY | TVSIFI_UPDATENOW, TVC_BYMOUSE);
  
                      if (fSameItem && (wHitCode & TVHT_ONITEMLABEL) && pTree->fFocus &&
                          !(pTree->ci.style & TVS_SINGLEEXPAND))
                      {
                          //
                          // The item and window are currently selected and user clicked
                          // on label.  Try to enter into name editing mode.
                          //
                          SetTimer(pTree->ci.hwnd, IDT_NAMEEDIT, GetDoubleClickTime(), NULL);
                          pTree->fNameEditPending = TRUE;
                      }
  
                      if (fSameItem && (pTree->ci.style & TVS_SINGLEEXPAND) &&
                          (!(pTree->dwExStyle & TVS_EX_NOSINGLECOLLAPSE) || !(hItem->state & TVIS_EXPANDED)))
                      {
                          WPARAM wParam = pTree->dwExStyle & TVS_EX_NOSINGLECOLLAPSE ? TVE_EXPAND : TVE_TOGGLE;
                          TV_Expand(pTree, wParam, pTree->hCaret, TRUE);
                      }
                  }
              }
          }
          else
          {
              CCSendNotify(&pTree->ci, NM_CLICK, NULL);
          }
      }
  
      if (!pTree->fFocus)
          SetFocus(pTree->ci.hwnd);
  
  bail:;
  }
  
  
  // ----------------------------------------------------------------------------
  //
  //  Gets the item's text, data, and/or image.
  //
  // ----------------------------------------------------------------------------
  BOOL TV_OnGetItem(PTREE pTree, LPTVITEMEX ptvi)
  {
      if (!ptvi)
          return FALSE;
  
      if (!ValidateTreeItem(ptvi->hItem, 0))
          return FALSE;           // Invalid parameter
  
      TV_GetItem(pTree, ptvi->hItem, ptvi->mask, ptvi);
  
      return TRUE;        // success
  }
  
  BOOL TV_OnGetItemA(PTREE pTree, LPTVITEMEXA ptvi)
  {
      BOOL bRet;
      LPSTR pszA = NULL;
      LPWSTR pszW = NULL;
  
      //HACK Alert!  This code assumes that TVITEMA is exactly the same
      // as TVITEMW except for the text pointer in the TVITEM
      ASSERT(sizeof(TVITEMA) == sizeof(TVITEMW));
  
      if (!IsFlagPtr(ptvi) && (ptvi->mask & TVIF_TEXT) && !IsFlagPtr(ptvi->pszText)) {
          pszA = ptvi->pszText;
          pszW = LocalAlloc(LMEM_FIXED, ptvi->cchTextMax * sizeof(WCHAR));
          if (pszW == NULL) {
              return FALSE;
          }
          ptvi->pszText = (LPSTR)pszW;
      }
      bRet = TV_OnGetItem(pTree, (LPTVITEMEXW)ptvi);
      if (pszA) {
          if (bRet && ptvi->cchTextMax)
              ConvertWToAN(pTree->ci.uiCodePage, pszA, ptvi->cchTextMax, (LPWSTR)(ptvi->pszText), -1);
          LocalFree(pszW);
          ptvi->pszText = pszA;
      }
      return bRet;
  }
  
  // ----------------------------------------------------------------------------
  //
  //  Sets the item's text, data, and/or image.
  //
  // ----------------------------------------------------------------------------
  
  BOOL TV_SetItemA(PTREE pTree, LPTVITEMEXA ptvi)
  {
      LPSTR pszA = NULL;
      BOOL lRet;
  
      //HACK Alert!  This code assumes that TVITEMA is exactly the same
      // as TVITEMW except for the text pointer in the TVITEM
      ASSERT(sizeof(TVITEMA) == sizeof(TVITEMW));
  
      if (!IsFlagPtr(ptvi) && (ptvi->mask & TVIF_TEXT) && !IsFlagPtr(ptvi->pszText))
      {
          pszA = ptvi->pszText;
          ptvi->pszText = (LPSTR)ProduceWFromA(pTree->ci.uiCodePage, pszA);
  
          if (ptvi->pszText == NULL)
          {
              ptvi->pszText = pszA;
              return -1;
          }
      }
  
      lRet = TV_SetItem(pTree, (LPCTVITEMEX)ptvi);
  
      if (pszA) {
          FreeProducedString(ptvi->pszText);
          ptvi->pszText = pszA;
      }
  
      return lRet;
  }
  
  BOOL TV_SetItem(PTREE pTree, LPCTVITEMEX ptvi)
  {
      UINT uRDWFlags = RDW_INVALIDATE;
      BOOL fEraseIfTransparent = FALSE;
      HTREEITEM hItem;
      BOOL bActualChange = FALSE; // HACK: We want to keep track of which
                                  // attributes were changed from CALLBACK to
                                  // "real", and don't invalidate if those were
                                  // the only changes
      int iIntegralPrev;
      BOOL fName = FALSE;
      BOOL fFocusSel = FALSE;
      BOOL fRecalcWidth = FALSE;
      BOOL fStateImageChange = FALSE;
  
      if (!ptvi)
          return FALSE;
  
      hItem = ptvi->hItem;
  
      // deal with the evil invisible root for multiple root trees.
      if (hItem == TVI_ROOT)
      {
          hItem = pTree->hRoot;
      }
  
      if (!ValidateTreeItem(hItem, 0))
          return FALSE;
  
      iIntegralPrev = hItem->iIntegral;
  
      // Bug#94368: send ITEMCHANING and ITEMCHANGED msgs
  
      if (ptvi->mask & TVIF_TEXT)
      {
          uRDWFlags |= RDW_ERASE;
          bActualChange = TRUE;
  
          if (!ptvi->pszText)
          {
              Str_Set(&hItem->lpstr, LPSTR_TEXTCALLBACK);
          }
          else
          {
  #ifdef DEBUG
              ASSERTMSG(!pTree->fInTextCallback, "set item (text) getting called while we are in text callback!");
  #endif
              if (!Str_Set(&hItem->lpstr, ptvi->pszText))
              {
                  //
                  // Memory allocation failed -  The best we can do now
                  // is to set the item back to callback, and hope that
                  // the top level program can handle it.
                  //
                  DebugMsg(DM_ERROR, TEXT("TreeView: Out of memory"));
                  hItem->lpstr = LPSTR_TEXTCALLBACK;
              }
          }
  
          fRecalcWidth = TRUE;
          fName = TRUE;
      }
  
      if (ptvi->mask & TVIF_PARAM)
      {
          bActualChange = TRUE;
          hItem->lParam = ptvi->lParam;
      }
  
      if (ptvi->mask & TVIF_IMAGE)
      {
          if (hItem->iImage != (WORD)I_IMAGECALLBACK) {
              bActualChange = TRUE;
              fEraseIfTransparent = TRUE;
              if (pTree->hImageList && (ImageList_GetBkColor(pTree->hImageList) == (COLORREF)-1))
                  uRDWFlags |= RDW_ERASE;
  
          }
          hItem->iImage = (SHORT)ptvi->iImage;
      }
  
      if (ptvi->mask & TVIF_SELECTEDIMAGE)
      {
          if (hItem->iSelectedImage != (WORD)I_IMAGECALLBACK)
              bActualChange = TRUE;
          hItem->iSelectedImage = (SHORT)ptvi->iSelectedImage;
      }
  
      if (ptvi->mask & TVIF_CHILDREN)
      {
          uRDWFlags |= RDW_ERASE;
          
          if (hItem->fKids != KIDS_CALLBACK)
              bActualChange = TRUE;
  
          switch (ptvi->cChildren)
          {
          case I_CHILDRENAUTO:
              hItem->fKids = KIDS_COMPUTE;
              break;
             
          case I_CHILDRENCALLBACK:
              hItem->fKids = KIDS_CALLBACK;
              break;
                   
          case 0:
              hItem->fKids = KIDS_FORCE_NO;
              break;
  
          default:
              hItem->fKids = KIDS_FORCE_YES;
              break;
          }
  
          //
          // If this item currently has no kid, reset the item.
          //
          if ((ptvi->cChildren == I_CHILDRENCALLBACK) && (hItem->hKids == NULL))
          {
              hItem->state &= ~TVIS_EXPANDEDONCE;
              if (hItem->hParent)
                  hItem->state &= ~TVIS_EXPANDED;
          }
      }
  
      if (ptvi->mask & TVIF_INTEGRAL)
      {
          if (LOWORD(ptvi->iIntegral) > 0)
              hItem->iIntegral = LOWORD(ptvi->iIntegral);
      }
  
      if (ptvi->mask & TVIF_STATE)
      {
          // don't & ptvi->state with TVIS_ALL because win95 didn't
          // and setting TVIS_FOCUS was retrievable even though we don't use it
          UINT change = (hItem->state ^ ptvi->state) & ptvi->stateMask;
  
          if (change)
          {
              // Bug#94368: (TVIS_SELECTED | TVIS_DROPHILITED) changes
              // should effect tree state
              hItem->state ^= change;
              bActualChange = TRUE;
              fEraseIfTransparent = TRUE;
  
              if (hItem->state & TVIS_BOLD) {
                  if (!pTree->hFontBold)
                      TV_CreateBoldFont(pTree);
               }
  
              if (change & TVIS_BOLD){
                  // do this because changing the boldness
                  uRDWFlags |= RDW_ERASE;
                  fRecalcWidth = TRUE;
              }
  
              fStateImageChange = change & TVIS_STATEIMAGEMASK;
              if (fStateImageChange) {
                  uRDWFlags |= RDW_ERASE;
                  // Adding/removing a state image changes the ITEM_OFFSET
                  // If old image was 0, then we are adding.
                  // If new image is 0, then we are removing.
                  // (If old=new, then we don't get into this code path, so we
                  // don't have to worry about that case.)
                  if (!(hItem->state & TVIS_STATEIMAGEMASK) || // new
                      !((hItem->state ^ change) & TVIS_STATEIMAGEMASK)) { // old
                      fRecalcWidth = TRUE;
                  }
              }
  
              fFocusSel = ((change & TVIS_SELECTED) != 0);
          }
      }
  
      if (fRecalcWidth) {
          hItem->iWidth = 0;          // Invalidate old width
          if (TV_IsShowing(hItem)) {
              TV_ScrollBarsAfterSetWidth(pTree, hItem);
          }
      }
  
      // force a redraw if something changed AND if we are not
      // inside of a paint of this guy (callbacks will set the
      // item on the paint callback to implement lazy data schemes)
  
      if (bActualChange && (pTree->hItemPainting != hItem))
      {
          if (fEraseIfTransparent) {
              if (pTree->hImageList) {
                  if (ImageList_GetBkColor(pTree->hImageList) == CLR_NONE) {
                      uRDWFlags |= RDW_ERASE;
                  }
              }
  
          }
  
          // If item height changed, then we've got a lot of cleaning up
          // to do.
          if (hItem->iIntegral != iIntegralPrev)
          {
              TV_ScrollBarsAfterResize(pTree, hItem, iIntegralPrev, uRDWFlags);
          }
          else
          {
              TV_InvalidateItem(pTree, hItem, uRDWFlags);
          }
  
          // REVIEW: we might need to update the scroll bars if the
          // text length changed!
      }
  
      if (bActualChange)
      {
          if (fName)
              NotifyWinEvent(EVENT_OBJECT_NAMECHANGE, pTree->ci.hwnd, OBJID_CLIENT,
                  TV_GetAccId(hItem));
  
          if (fFocusSel)
          {
              NotifyWinEvent(EVENT_OBJECT_FOCUS, pTree->ci.hwnd, OBJID_CLIENT,
                  TV_GetAccId(hItem));
              NotifyWinEvent(((hItem->state & TVIS_SELECTED) ?
                  EVENT_OBJECT_SELECTIONADD : EVENT_OBJECT_SELECTIONREMOVE),
                  pTree->ci.hwnd, OBJID_CLIENT, TV_GetAccId(hItem));
          }
  
          if (fStateImageChange)
              NotifyWinEvent(EVENT_OBJECT_STATECHANGE, pTree->ci.hwnd, OBJID_CLIENT,
                  TV_GetAccId(hItem));
      }
      return TRUE;
  }
  
  
  // ----------------------------------------------------------------------------
  //
  //  Calls TV_CheckHit to get the hit test results and then package it in a
  //  structure back to the app.
  //
  // ----------------------------------------------------------------------------
  
  HTREEITEM TV_OnHitTest(PTREE pTree, LPTV_HITTESTINFO lptvh)
  {
      if (!lptvh)
          return 0; //Bug#94345: Validate LPTVHITTEST
  
      lptvh->hItem = TV_CheckHit(pTree, lptvh->pt.x, lptvh->pt.y, &lptvh->flags);
  
      return lptvh->hItem;
  }
  
  BOOL TV_IsItemTruncated(PTREE pTree, TREEITEM *hItem, LPRECT lprc)
  {
      if (TV_GetItemRect(pTree,hItem,lprc,TRUE)) {
          lprc->left -= g_cxEdge;
          lprc->top -= g_cyBorder;
          if ((lprc->left + hItem->iWidth) > pTree->cxWnd) {
              return TRUE;
          }
      }
      return FALSE;
  }
  
  BOOL TV_HandleTTNShow(PTREE pTree, LPNMHDR lpnm)
  {
      if (pTree->hToolTip && pTree->fPlaceTooltip) {
          LPNMTTSHOWINFO psi = (LPNMTTSHOWINFO)lpnm;
          RECT rc;
          TVITEMEX item;
  
          // Now get the text associated with that item
          item.stateMask = TVIS_BOLD;
          TV_GetItem(pTree, pTree->hToolTip, TVIF_STATE, &item);
          SendMessage(pTree->hwndToolTips, WM_SETFONT, (WPARAM)((item.state & TVIS_BOLD) ? pTree->hFontBold : pTree->hFont), 0);
  
          TV_GetItemRect(pTree, pTree->hToolTip, &rc, TRUE);
  
          MapWindowRect(pTree->ci.hwnd, HWND_DESKTOP, &rc);
          // We draw the text with margins, so take those into account too.
          // These values come from TV_DrawItem...
          rc.top += g_cyBorder;
          rc.left += g_cxLabelMargin;
  
          //
          //  At this point, (rc.left, rc.top) are the coordinates we pass
          //  to DrawText.  Ask the tooltip how we should position it so the
          //  tooltip text shows up in precisely the same location.
          //
          // Bug#94368 raymondc v6: wrong coordinates if app has used TVM_SETITEMHEIGHT
  
          SendMessage(pTree->hwndToolTips, TTM_ADJUSTRECT, TRUE, (LPARAM)&rc);
          SetWindowPos(pTree->hwndToolTips, NULL, rc.left, rc.top,0,0,
                       SWP_NOACTIVATE|SWP_NOSIZE|SWP_NOZORDER);
          // This is an inplace tooltip, so disable animation.
          psi->dwStyle |= TTS_NOANIMATE;
          // handled!
          return TRUE;
      }
  
      return FALSE;
  }
  
  //
  //  Copy the font from the treeview item into the tooltip so the tooltip
  //  shows up in the correct font.
  //
  BOOL TV_HandleTTCustomDraw(PTREE pTree, LPNMTTCUSTOMDRAW pnm)
  {
      if (pTree->hToolTip && pTree->fPlaceTooltip &&
          (pnm->nmcd.dwDrawStage == CDDS_PREPAINT ||
           pnm->nmcd.dwDrawStage == CDDS_ITEMPREPAINT))
      {
          //
          //  Set up the customdraw DC to match the font of the TV item.
          //
          TVFAKEDRAW tvfd;
          DWORD dwCustom = 0;
          TreeView_BeginFakeCustomDraw(pTree, &tvfd);
          dwCustom = TreeView_BeginFakeItemDraw(&tvfd, pTree->hToolTip);
  
          // If client changed the font, then transfer the font
          // from our private hdc into the tooltip's HDC.  We use
          // a private HDC because we only want to let the app change
          // the font, not the colors or anything else.
          if (dwCustom & CDRF_NEWFONT)
          {
              SelectObject(pnm->nmcd.hdc, GetCurrentObject(tvfd.nmcd.nmcd.hdc, OBJ_FONT));
          }
          TreeView_EndFakeItemDraw(&tvfd);
          TreeView_EndFakeCustomDraw(&tvfd);
  
          // Don't return other wacky flags to TT, since all we
          // did was change the font (if even that)
          return dwCustom & CDRF_NEWFONT;
  
      }
      return CDRF_DODEFAULT;
  
  }
  
  BOOL TV_SetToolTipTarget(PTREE pTree, HTREEITEM hItem)
  {
      // update the item we're showing the bubble for...
      if (pTree->hToolTip != hItem) {
          // the hide will keep us from flashing
          ShowWindow(pTree->hwndToolTips, SW_HIDE);
          UpdateWindow(pTree->hwndToolTips);
          pTree->hToolTip = hItem;
          SendMessage(pTree->hwndToolTips, TTM_UPDATE, 0, 0);
          return TRUE;
      }
      return FALSE;
  }
  
  TREEITEM* TV_ItemAtCursor(PTREE pTree, LPRECT prc)
  {
      RECT rc;
      UINT wHitCode;
      TREEITEM* hItem;
  
      GetCursorPos((LPPOINT)&rc);
      ScreenToClient(pTree->ci.hwnd, (LPPOINT)&rc);
      hItem = TV_CheckHit(pTree,rc.left,rc.top,&wHitCode);
  
      if (prc)
          *prc = rc;
      if (!(wHitCode & TVHT_ONITEM))
          hItem = NULL;
  
      return hItem;
  }
  
  BOOL TV_UpdateToolTipTarget(PTREE pTree)
  {
      RECT rc;
      TREEITEM *hItem = TV_ItemAtCursor(pTree, &rc);
  
      if (!(pTree->ci.style & TVS_NOTOOLTIPS) 
              && !TV_IsItemTruncated(pTree, hItem, &rc)
              && !(pTree->ci.style & TVS_INFOTIP))
          hItem = NULL;
  //    else if (!(pTree->ci.style & TVS_NOTOOLTIPS)
  //                    || (pTree->ci.style & TVS_INFOTIP))
      return TV_SetToolTipTarget(pTree, hItem);
  }
  
  BOOL TV_UpdateToolTip(PTREE pTree)
  {
      if (pTree->hwndToolTips && pTree->fRedraw)
         return (TV_UpdateToolTipTarget(pTree));
      return TRUE;
  }
  
  BOOL TV_SetInsertMark(PTREE pTree, HTREEITEM hItem, BOOL fAfter)
  {
      if (!ValidateTreeItem(hItem, VTI_NULLOK))   // NULL means remove insert mark
          return FALSE;
  
      TV_InvalidateInsertMarkRect(pTree, TRUE); // Make sure the old one gets erased
  
      pTree->fInsertAfter = BOOLIFY(fAfter);
      pTree->htiInsert = hItem;
  
      TV_InvalidateInsertMarkRect(pTree, FALSE); // Make sure the new one gets drawn
  
      return TRUE;
  }
  
  BOOL TV_GetInfoTip(PTREE pTree, LPTOOLTIPTEXT lpttt, HTREEITEM hti, LPTSTR szBuf, int cch)
  {
      NMTVGETINFOTIP git;
  
      szBuf[0] = 0;
      git.pszText = szBuf;
      git.cchTextMax = cch;
      git.hItem = hti;
      git.lParam = hti->lParam;
  
      // for folded items pszText is prepopulated with the
      // item text, clients should append to this
  
      CCSendNotify(&pTree->ci, TVN_GETINFOTIP, &git.hdr);
  
      CCSetInfoTipWidth(pTree->ci.hwnd, pTree->hwndToolTips);
      Str_Set(&pTree->pszTip, git.pszText);
      lpttt->lpszText = pTree->pszTip;
      if(pTree->ci.style & TVS_RTLREADING)
      {
          lpttt->uFlags |= TTF_RTLREADING;
      }
  
      return lpttt->lpszText && lpttt->lpszText[0];
  }
  
  
  
  
  void TV_HandleNeedText(PTREE pTree, LPTOOLTIPTEXT lpttt)
  {
      TVITEMEX tvItem;
      TCHAR szBuf[INFOTIPSIZE];
      RECT rc;
      HTREEITEM hItem;
  
      // No distracting tooltips while in-place editing, please
      if (pTree->htiEdit)
      {
          return;
      }
  
      // If the cursor isn't over anything, then stop
      hItem = TV_ItemAtCursor(pTree, &rc);
      if (!hItem)
          return;
  
      // If the item has an infotip, then use it
      if (pTree->ci.style & TVS_INFOTIP) {
          if (hItem && TV_GetInfoTip(pTree, lpttt, hItem, szBuf, ARRAYSIZE(szBuf))) {
              pTree->fPlaceTooltip = FALSE;
              pTree->hToolTip = hItem;
              return;
          }
      }
  
      // Else it isn't an infotip
      CCResetInfoTipWidth(pTree->ci.hwnd, pTree->hwndToolTips);
  
      // If the item is not truncated, then no need for a tooltip
      if (!TV_IsItemTruncated(pTree, hItem, &rc))
      {
          tvItem.hItem = NULL;
          return;
      }
  
      // Display an in-place tooltip for the item
      pTree->fPlaceTooltip = TRUE;
      pTree->hToolTip = hItem;
      tvItem.hItem = hItem;
      tvItem.mask = TVIF_TEXT | TVIF_STATE;
      tvItem.pszText = szBuf;
      tvItem.stateMask = TVIS_DROPHILITED | TVIS_SELECTED;
      COMPILETIME_ASSERT(MAXLABELTEXT <= ARRAYSIZE(szBuf));
      tvItem.cchTextMax = MAXLABELTEXT;
      TV_OnGetItem(pTree,&tvItem);
  
      Str_Set(&pTree->pszTip, tvItem.pszText);
      lpttt->lpszText = pTree->pszTip;
      DebugMsg(DM_TRACE, TEXT("TV_HandleNeedText for %d returns %s"), tvItem.hItem, lpttt->szText);
  }
  
  //
  //  Visual Studio 5.0 Books Online (part of VB 5.0) subclasses
  //  us and responds NFR_ANSI, so we end up getting TTN_NEEDTEXTA
  //  instead of TTN_NEEDTEXTW.  We can't risk forcing the tooltip
  //  to UNICODE because some other apps may have done this on purpose
  //  (because they intend to intercept TTN_NEEDTEXTA and do custom tooltips).
  //  So support the ANSI tooltip notification so VB stays happy.
  //  Note: This doesn't have to be efficient, as it's an error case anyway.
  //
  void TV_HandleNeedTextA(PTREE pTree, LPTOOLTIPTEXTA lptttA)
  {
      TOOLTIPTEXT ttt;
      ttt.szText[0] = TEXT('\0');
      ttt.hdr       = lptttA->hdr;
      ttt.lpszText  = ttt.szText;
      ttt.hinst     = lptttA->hinst;
      ttt.uFlags    = lptttA->uFlags;
      ttt.lParam    = lptttA->lParam;
  
      TV_HandleNeedText(pTree, &ttt);
      if (pTree->pszTipA)
          LocalFree(pTree->pszTipA);
      pTree->pszTipA = ProduceAFromW(pTree->ci.uiCodePage, ttt.lpszText);
      lptttA->lpszText = pTree->pszTipA;
      lptttA->uFlags  = ttt.uFlags;
  }
  
  // ----------------------------------------------------------------------------
  //
  //  TV_Timer
  //
  //  Checks to see if it is our name editing timer.  If so it  calls of to
  //  do name editing
  //
  // ----------------------------------------------------------------------------
  LRESULT TV_Timer(PTREE pTree, UINT uTimerId)
  {
      switch (uTimerId)
      {
          case IDT_NAMEEDIT:
              // Kill the timer as we wont need any more messages from it.
              KillTimer(pTree->ci.hwnd, IDT_NAMEEDIT);
  
              if (pTree->fNameEditPending)
              {
                  // And start name editing mode.
                  if (!TV_EditLabel(pTree, pTree->hCaret, NULL))
                  {
                      TV_DismissEdit(pTree, FALSE);
                  }
  
                  // remove the flag...
                  pTree->fNameEditPending = FALSE;
              }
              break;
              
          case IDT_SCROLLWAIT:
              KillTimer(pTree->ci.hwnd, IDT_SCROLLWAIT);
              if (pTree->fScrollWait)
              {
                  if (pTree->hCaret) {
                      TV_ScrollVertIntoView(pTree, pTree->hCaret);
                  }
                  pTree->fScrollWait = FALSE;
              }
              break;
  
  
      }
      return 0;
  }
  
  // ----------------------------------------------------------------------------
  //
  //  TV_Command
  //
  //  Process the WM_COMMAND.  See if it is an input from our edit windows.
  //  if so we may want to dismiss it, and or set it is being dirty...
  //
  // ----------------------------------------------------------------------------
  void TV_Command(PTREE pTree, int id, HWND hwndCtl, UINT codeNotify)
  {
      if ((pTree != NULL) && (hwndCtl == pTree->hwndEdit))
      {
          switch (codeNotify)
          {
          case EN_UPDATE:
              // We will use the ID of the window as a Dirty flag...
              SetWindowID(pTree->hwndEdit, 1);
              TV_SetEditSize(pTree);
              break;
  
          case EN_KILLFOCUS:
              // We lost focus, so dismiss edit and save changes
              // (Note that the owner might reject the change and restart
              // edit mode, which traps the user.  Owners need to give the
              // user a way to get out.)
  
              //
              //  Fix horrible undocumented hanging problem:  LVN_ENDLABELEDIT
              //  is sent in response to EN_KILLFOCUS, which is send in response
              //  to WM_KILLFOCUS, and it is undocumented that you cannot display
              //  UI during WM_KILLFOCUS when a journal record hook is active,
              //  because the presence of a hook forces serialization of activation,
              //  and so when you put up UI, you generate activation changes, which
              //  get stuck because you haven't finished responding to the previous
              //  WM_KILLFOCUS message yet.
              //
              //  See NT bug 414634.
              //
              if (InSendMessage())
                  ReplyMessage(0);
  
              if (!TV_DismissEdit(pTree, FALSE))
                 return;
              break;
  
          case HN_BEGINDIALOG: // penwin is bringing up a dialog
              ASSERT(GetSystemMetrics(SM_PENWINDOWS)); // only on a pen system
              pTree->fNoDismissEdit = TRUE;
              break;
  
          case HN_ENDDIALOG: // penwin has destroyed dialog
              ASSERT(GetSystemMetrics(SM_PENWINDOWS)); // only on a pen system
              pTree->fNoDismissEdit = FALSE;
              break;
          }
  
          // Forward edit control notifications up to parent
          //
          if (IsWindow(hwndCtl))
              FORWARD_WM_COMMAND(pTree->ci.hwndParent, id, hwndCtl, codeNotify, SendMessage);
      }
  }
  
  HIMAGELIST CreateCheckBoxImagelist(HIMAGELIST himl, BOOL fTree, BOOL fUseColorKey, BOOL fMirror);
  void TV_CreateToolTips(PTREE pTree);
  
  void TV_InitCheckBoxes(PTREE pTree)
  {
      HIMAGELIST himl;
      TVITEMEX ti;
  
      himl = CreateCheckBoxImagelist(pTree->hImageList, TRUE, TRUE, IS_WINDOW_RTL_MIRRORED(pTree->ci.hwnd));
      if (pTree->hImageList) 
      {
          COLORREF cr = ImageList_GetBkColor(pTree->hImageList);
          ImageList_SetBkColor(himl, IsUsingCleartype()? (CLR_NONE) : (cr));
      }
  
      TV_SetImageList(pTree, himl, TVSIL_STATE);
  
      ti.mask = TVIF_STATE;
      ti.state = INDEXTOSTATEIMAGEMASK(1);
      ti.stateMask = TVIS_STATEIMAGEMASK;
      TV_SetItemRecurse(pTree, pTree->hRoot, &ti);
  }
  
  void TV_OnStyleChanged(PTREE pTree, WPARAM gwl, LPSTYLESTRUCT pinfo)
  {
      // Style changed: redraw everything...
      //
      // try to do this smartly, avoiding unnecessary redraws
      if (gwl == GWL_STYLE)
      {
          DWORD changeFlags;
          DWORD styleNew;
  
          TV_DismissEdit(pTree, FALSE);
  
          // You cannot combine TVS_HASLINES and TVS_FULLROWSELECT
          // because it doesn't work
          styleNew = pinfo->styleNew;
          if (styleNew & TVS_HASLINES) 
          {
              if (styleNew & TVS_FULLROWSELECT)
              {
                  RIPMSG(FALSE, "TV_OnStyleChanged(): Cannot combine TVS_HASLINES and TVS_FULLROWSELECT");
              }
              styleNew &= ~TVS_FULLROWSELECT;
          }
  
          changeFlags = pTree->ci.style ^ styleNew; // those that changed
          pTree->ci.style = styleNew;               // change our version
  
          pTree->ci.style &= ~TVS_RTLREADING;
          pTree->ci.style |= (pinfo->styleNew & TVS_RTLREADING);       
  
          if (changeFlags & (TVS_HASBUTTONS | TVS_HASLINES | TVS_LINESATROOT))
              TV_CreateIndentBmps(pTree);
  
          if (changeFlags & TVS_CHECKBOXES)
          {
              if (pTree->ci.style & TVS_CHECKBOXES)
              {
                  TV_InitCheckBoxes(pTree);
              }
          }
  
          if (changeFlags & TVS_NOTOOLTIPS)
          {
              if (pTree->ci.style & TVS_NOTOOLTIPS)
              {
                  DestroyWindow(pTree->hwndToolTips);
                  pTree->hwndToolTips = NULL;
              }
              else
              {
                  TV_CreateToolTips(pTree);
              }
          }
  
          if (changeFlags & TVS_TRACKSELECT)
          {
              if (!(pTree->ci.style & TVS_TRACKSELECT))
              {
                  if (pTree->hHot) 
                  {
                      TV_InvalidateItem(pTree, pTree->hHot, RDW_INVALIDATE | RDW_ERASE);
                      pTree->hHot = NULL;
                  }
              }
          }
          // Checkboxes and stuff may have changed width - go recompute
          TV_ScrollBarsAfterSetWidth(pTree, NULL);
      }
      else if (gwl == GWL_EXSTYLE)
      {
          DWORD changeFlags;
          changeFlags = (pinfo->styleNew & WS_EX_RTLREADING) ?TVS_RTLREADING :0;
  
          if (changeFlags ^ (pTree->ci.style & TVS_RTLREADING))
          {
              pTree->ci.style ^= TVS_RTLREADING;
              TV_DismissEdit(pTree, FALSE);   // Cancels edits
  
              DestroyWindow(pTree->hwndToolTips);
              pTree->hwndToolTips = NULL;
              TV_CreateToolTips(pTree);
          }
      }
  
  }
  
  void TV_OnMouseMove(PTREE pTree, DWORD dwPos, WPARAM wParam)
  {
      if (pTree->ci.style & TVS_TRACKSELECT) {
          POINT pt;
          HTREEITEM hHot;
          UINT wHitCode;
  
          pt.x = GET_X_LPARAM(dwPos);
          pt.y = GET_Y_LPARAM(dwPos);
  
          hHot = TV_CheckHit(pTree,pt.x,pt.y,&wHitCode);
  
          if (!(pTree->ci.style & TVS_FULLROWSELECT) &&
              !(wHitCode & TVHT_ONITEM)) {
              hHot = NULL;
          }
  
          if (hHot != pTree->hHot) {
              TV_InvalidateItem(pTree, pTree->hHot, RDW_INVALIDATE);
              TV_InvalidateItem(pTree, hHot, RDW_INVALIDATE);
              pTree->hHot = hHot;
              // update now so that we won't have an invalid area
              // under the tooltips
              UpdateWindow(pTree->ci.hwnd);
          }
      }
  
      if (pTree->hwndToolTips) {
  
          if (!TV_UpdateToolTip(pTree)) {
              RelayToToolTips(pTree->hwndToolTips, pTree->ci.hwnd, WM_MOUSEMOVE, wParam, dwPos);
          }
      }
  }
  
  void TV_OnWinIniChange(PTREE pTree, WPARAM wParam)
  {
      if (!wParam ||
          (wParam == SPI_SETNONCLIENTMETRICS) ||
          (wParam == SPI_SETICONTITLELOGFONT)) {
  
          if (pTree->fCreatedFont)
              TV_OnSetFont(pTree, NULL, TRUE);
  
          if (!pTree->fIndentSet) {
              // this will validate against the minimum
              TV_SetIndent(pTree, 0);
          }
      }
  }
  
  void TV_OnSetBkColor(PTREE pTree, COLORREF clr)
  {
      if (pTree->clrBk != (COLORREF)-1) 
      {
          DeleteObject(pTree->hbrBk);
      }
  
      pTree->clrBk = clr;
      if (clr != (COLORREF)-1) 
      {
          pTree->hbrBk = CreateSolidBrush(clr);
      }
      TV_CreateIndentBmps(pTree); // This also invalidates
  
      pTree->clrBkNonTheme = clr;
  }
  
  DWORD TV_SetExtendedStyle(PTREE pTree, DWORD dwNewStyle, DWORD dwExMask)
  {
      DWORD dwOldStyle = pTree->dwExStyle;
  
      if (dwExMask)
          dwNewStyle = (pTree->dwExStyle & ~ dwExMask) | (dwNewStyle & dwExMask);
  
      // do validation of the new flags here...
  
      pTree->dwExStyle = dwNewStyle;
  
      // if ((dwOldStyle ^ dwNewStyle) & TVS_EX_NOSINGLECOLLAPSE) ... do whatever (no need to invalidate rect, this is behavior style)
  
      return dwOldStyle;
  }
  
  BOOL TV_TranslateAccelerator(HWND hwnd, LPMSG lpmsg)
  {
      if (!lpmsg)
          return FALSE;
  
      if (GetFocus() != hwnd)
          return FALSE;
  
      switch (lpmsg->message) {
  
      case WM_KEYUP:
      case WM_KEYDOWN:
  
          if (GetKeyState(VK_CONTROL) < 0) {
              switch (lpmsg->wParam) {
              case VK_LEFT:
              case VK_RIGHT:
              case VK_PRIOR:
              case VK_HOME:
              case VK_NEXT:
              case VK_END:
              case VK_UP:
              case VK_DOWN:
                  TranslateMessage(lpmsg);
                  DispatchMessage(lpmsg);
                  return TRUE;
              }
          } else {
  
              switch (lpmsg->wParam) {
  
              case VK_RETURN:
              case VK_PRIOR:
              case VK_HOME:
              case VK_NEXT:
              case VK_END:
              case VK_SUBTRACT:
              case VK_ADD:
              case VK_MULTIPLY:
              case VK_LEFT:
              case VK_BACK:
              case VK_UP:
              case VK_RIGHT:
              case VK_DOWN:
              case VK_SPACE:
                  TranslateMessage(lpmsg);
                  DispatchMessage(lpmsg);
                  return TRUE;
              }
          }
          break;
      }
  
      return FALSE;
  }
  
  HTREEITEM TV_FindAccId(HTREEITEM hItem, DWORD dwAccId)
  {
      HTREEITEM hKid;
      HTREEITEM hNext;
  
      if (hItem->dwAccId == dwAccId)
          return hItem;
  
      for (hKid = hItem->hKids; hKid; hKid = hNext) 
      {
          HTREEITEM hItemFound;
          hNext = hKid->hNext;
  
          // recurse on each child
          hItemFound = TV_FindAccId(hKid, dwAccId);
  
          if (hItemFound)
              return hItemFound;
      }
  
      return NULL;
  }
  
  LRESULT TV_MapAccIDToHTREEITEM(PTREE pTree, DWORD dwAccId)
  {
      return (LRESULT)TV_FindAccId(pTree->hRoot, dwAccId);
  }
  
  LRESULT TV_MapHTREEITEMToAccID(PTREE pTree, HTREEITEM hItem)
  {
      DBG_ValidateTreeItem(hItem, 0);
  
      return (LRESULT)(hItem->dwAccId);
  }
  
  // ----------------------------------------------------------------------------
  //
  //  TV_WndProc
  //
  //  Take a guess.
  //
  // ----------------------------------------------------------------------------
  
  LRESULT CALLBACK TV_WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
  {
      PTREE pTree = (PTREE)GetWindowPtr(hwnd, 0);
  
      if (pTree) 
      {
          if ((uMsg >= WM_MOUSEFIRST) && (uMsg <= WM_MOUSELAST) &&
              (pTree->ci.style & TVS_TRACKSELECT) && !pTree->fTrackSet) 
          {
  
              TRACKMOUSEEVENT tme;
  
              pTree->fTrackSet = TRUE;
              tme.cbSize = sizeof(tme);
              tme.hwndTrack = pTree->ci.hwnd;
              tme.dwFlags = TME_LEAVE;
  
              TrackMouseEvent(&tme);
          }
          else if (uMsg == g_uDragImages)
          {
              return TV_GenerateDragImage(pTree, (SHDRAGIMAGE*)lParam);
          }
          else if (uMsg == WM_THEMECHANGED)  // Check for theme changes
          {
              HTHEME hTheme;
              if (pTree->hTheme)
                  CloseThemeData(pTree->hTheme);
  
              pTree->hTheme = NULL;
  
  
              hTheme = OpenThemeData(pTree->ci.hwnd, L"TreeView");
  
              // Reset cached brushes
              if (hTheme)
              {
                  TV_InitThemeMetrics(pTree, hTheme);
              }
              else
              {
                  // Background color (system)
                  SendMessage(pTree->ci.hwnd, TVM_SETBKCOLOR, 0, pTree->clrBkNonTheme);
  
                  // Line color (system)
                  SendMessage(pTree->ci.hwnd, TVM_SETLINECOLOR, 0, pTree->clrLineNonTheme);
              }
  
              pTree->hTheme = hTheme;
  
              InvalidateRect(pTree->ci.hwnd, NULL, TRUE);
          }
      } 
      else 
      {
          if (uMsg == WM_CREATE) 
              return TV_OnCreate(hwnd, (LPCREATESTRUCT)lParam);
  
          goto DoDefault;
      }
  
  
  
      switch (uMsg)
      {
          case WM_MOUSELEAVE:
              pTree->fTrackSet = FALSE;
              TV_InvalidateItem(pTree, pTree->hHot, RDW_INVALIDATE);
              pTree->hHot = NULL;
              TV_PopBubble(pTree);
              break;
              
          case TVMP_CALCSCROLLBARS:
              TV_CalcScrollBars(pTree);
              break;
  
          case TVM_MAPACCIDTOHTREEITEM:
              return TV_MapAccIDToHTREEITEM(pTree, (DWORD)wParam);
              break;
  
          case TVM_MAPHTREEITEMTOACCID:
              return TV_MapHTREEITEMToAccID(pTree, (HTREEITEM)wParam);
              break;
  
          case TVM_GETITEMSTATE:
              {
                  TVITEMEX tvi;
  
                  tvi.mask = TVIF_STATE;
                  tvi.stateMask = (UINT) lParam;
                  tvi.hItem = (HTREEITEM)wParam;
                  if (!TV_OnGetItem(pTree, &tvi))
                      return 0;
  
                  return tvi.state;
              }
              
          case TVM_SETBKCOLOR:
          {
              LRESULT lres = (LRESULT)pTree->clrBk;
              TV_OnSetBkColor(pTree, (COLORREF)lParam);
              return lres;
          }
  
          case TVM_SETTEXTCOLOR:
          {
              LRESULT lres = (LRESULT)pTree->clrText;
              pTree->clrText = (COLORREF)lParam;
              if (pTree->hbrText && pTree->hbrText != g_hbrWindowText)
              {
                  DeleteObject(pTree->hbrText);
              }
  
              if (pTree->clrText == (COLORREF)-1)
              {
                  pTree->hbrText = NULL;
              }
              else
              {
                  pTree->hbrText = CreateSolidBrush(pTree->clrText);
              }
  
              if (!pTree->hbrText)
                  pTree->hbrText = g_hbrWindowText;
              TV_CreateIndentBmps(pTree); // This also invalidates
              return lres;
          }
  
          case TVM_GETBKCOLOR:
              return (LRESULT)pTree->clrBk;
  
          case TVM_GETTEXTCOLOR:
              return (LRESULT)pTree->clrText;
  
          case TVM_GETSCROLLTIME:
              return (LRESULT)pTree->uMaxScrollTime;
  
          case TVM_SETSCROLLTIME:
          {
              UINT u = pTree->uMaxScrollTime;
              pTree->uMaxScrollTime = (UINT)wParam;
              return (LRESULT)u;
          }
  
  
          case TVM_INSERTITEMA:
              if (!lParam)
                  return 0;
  
              return (LRESULT)TV_InsertItemA(pTree, (LPTV_INSERTSTRUCTA)lParam);
  
          case TVM_GETITEMA:
              if (!lParam)
                  return 0;
  
              return (LRESULT)TV_OnGetItemA(pTree, (LPTVITEMEXA)lParam);
  
          case TVM_SETITEMA:
              if (!lParam)
                  return 0;
  
              return (LRESULT)TV_SetItemA(pTree, (LPTVITEMEXA)lParam);
  
          case TVM_INSERTITEM:
              return (LRESULT)TV_InsertItem(pTree, (LPTV_INSERTSTRUCT)lParam);
  
          case TVM_DELETEITEM:
              // Assume if items are being deleted that name editing is invalid.
              TV_DismissEdit(pTree, TRUE);
              return TV_DeleteItem(pTree, (TREEITEM *)lParam, TVDI_NORMAL);
  
          case TVM_GETNEXTITEM:
              return (LRESULT)TV_GetNextItem(pTree, (TREEITEM *)lParam, wParam);
  
          case TVM_GETITEMRECT:
              // lParam points to hItem to get rect from on input
              if (!lParam)
                  return 0;
              if (!ValidateTreeItem(*(HTREEITEM *)lParam, 0))
                  return 0;               // Invalid parameter
              return (LRESULT)TV_GetItemRect(pTree, *(HTREEITEM *)lParam, (LPRECT)lParam, (BOOL)wParam);
  
          case TVM_GETITEM:
              return (LRESULT)TV_OnGetItem(pTree, (LPTVITEMEX)lParam);
  
          case TVM_SETITEM:
              return (LRESULT)TV_SetItem(pTree, (LPCTVITEMEX)lParam);
  
          case TVM_ENSUREVISIBLE:
              if (!ValidateTreeItem((HTREEITEM)lParam, 0))
                  return 0;
              return TV_EnsureVisible(pTree, (TREEITEM *)lParam);
  
          case TVM_SETIMAGELIST:
              return (LRESULT)(ULONG_PTR)TV_SetImageList(pTree, (HIMAGELIST)lParam, (int)wParam);
  
          case TVM_EXPAND:
              if (!ValidateTreeItem((HTREEITEM)lParam, 0))
                  return FALSE;               // invalid parameter
              return TV_Expand(pTree, wParam, (TREEITEM *)lParam, FALSE);
  
          case TVM_HITTEST:
              return (LRESULT)TV_OnHitTest(pTree, (LPTV_HITTESTINFO)lParam);
  
          case TVM_GETCOUNT:
              return MAKELRESULTFROMUINT(pTree->cItems);
  
          case TVM_GETIMAGELIST:
              switch (wParam) {
              case TVSIL_NORMAL:
                  return MAKELRESULTFROMUINT(pTree->hImageList);
              case TVSIL_STATE:
                  return MAKELRESULTFROMUINT(pTree->himlState);
              default:
                  return 0;
              }
  
          case TVM_GETISEARCHSTRINGA:
              if (GetFocus() == pTree->ci.hwnd)
                  return (LRESULT)GetIncrementSearchStringA(&pTree->is, pTree->ci.uiCodePage, (LPSTR)lParam);
              else
                  return 0;
  
          case TVM_GETISEARCHSTRING:
              if (GetFocus() == pTree->ci.hwnd)
                  return (LRESULT)GetIncrementSearchString(&pTree->is, (LPTSTR)lParam);
              else
                  return 0;
  
          case TVM_EDITLABELA:
          {
              LPWSTR lpEditString = NULL;
              HWND   hRet;
  
              if (wParam)
              {
                  lpEditString = ProduceWFromA(pTree->ci.uiCodePage, (LPSTR)wParam);
              }
  
              hRet = TV_EditLabel(pTree, (HTREEITEM)lParam, lpEditString);
  
              if (lpEditString)
              {
                  FreeProducedString(lpEditString);
              }
  
              return MAKELRESULTFROMUINT(hRet);
          }
  
          case TVM_EDITLABEL:
              return MAKELRESULTFROMUINT(TV_EditLabel(pTree, (HTREEITEM)lParam,
                      (LPTSTR)wParam));
  
  
          case TVM_GETVISIBLECOUNT:
              return TV_GetVisCount(pTree, (BOOL) wParam);
  
          case TVM_SETINDENT:
              TV_SetIndent(pTree, wParam);
              pTree->fIndentSet = TRUE;
              break;
  
          case TVM_GETINDENT:
              return MAKELRESULTFROMUINT(pTree->cxIndent);
  
          case TVM_CREATEDRAGIMAGE:
              return MAKELRESULTFROMUINT(TV_CreateDragImage(pTree, (TREEITEM *)lParam));
  
          case TVM_GETEDITCONTROL:
              return (LRESULT)(ULONG_PTR)pTree->hwndEdit;
  
          case TVM_SORTCHILDREN:
              return TV_SortChildren(pTree, (TREEITEM *)lParam, (BOOL)wParam);
  
          case TVM_SORTCHILDRENCB:
              return TV_SortChildrenCB(pTree, (TV_SORTCB *)lParam, (BOOL)wParam);
  
          case TVM_SELECTITEM:
              // wParam: separate action flags (TVGN) and select item flags (TVSIF)
              return TV_SelectItem(pTree, (wParam & TVGN_VALID),
                                  (TREEITEM *)lParam,
                                   TVSIFI_NOTIFY | TVSIFI_UPDATENOW | ((wParam & TVSI_NOSINGLEEXPAND) ? TVSIFI_NOSINGLEEXPAND : 0),
                                   TVC_UNKNOWN);
  
          case TVM_ENDEDITLABELNOW:
              return TV_DismissEdit(pTree, (BOOL)wParam);
  
          case TVM_GETTOOLTIPS:
              return (LRESULT)(ULONG_PTR)pTree->hwndToolTips;
  
          case TVM_SETTOOLTIPS:{
              HWND hwndOld = pTree->hwndToolTips;
  
              pTree->hwndToolTips = (HWND)wParam;
              return (LRESULT)(ULONG_PTR)hwndOld;
          }
  
          case TVM_GETITEMHEIGHT:
              return pTree->cyItem;
  
          case TVM_SETITEMHEIGHT:
          {
              int iOld = pTree->cyItem;
              pTree->fCyItemSet = (wParam != (WPARAM)-1);
              pTree->cyItem = (SHORT)wParam; // must be even
              TV_SetItemHeight(pTree);
              return iOld;
          }
          case TVM_SETBORDER:
          {
              int cyOld = pTree->cyBorder
                  , cxOld = pTree->cxBorder;
  
              if (wParam & TVSBF_YBORDER)
                  pTree->cyBorder = HIWORD(lParam);
              if (wParam & TVSBF_XBORDER)
                  pTree->cxBorder = LOWORD(lParam);
  
              TV_CalcScrollBars(pTree);
              return MAKELONG(cxOld, cyOld);
          }
          case TVM_GETBORDER:
              return MAKELONG(pTree->cxBorder, pTree->cyBorder);
          case TVM_SETINSERTMARK:
              return TV_SetInsertMark(pTree, (TREEITEM *)lParam, (BOOL) wParam);
          
          case TVM_SETINSERTMARKCOLOR:
          {
              LRESULT lres = (LRESULT)pTree->clrim;
              pTree->clrim = (COLORREF) lParam;
              TV_InvalidateInsertMarkRect(pTree, FALSE); // Repaint in new color
              return lres;
          }
          case TVM_GETINSERTMARKCOLOR:
              return pTree->clrim;
  
          case TVM_TRANSLATEACCELERATOR:
              return TV_TranslateAccelerator(hwnd, (LPMSG)lParam);
  
          case TVM_SETLINECOLOR:
          {
              LRESULT lres = (LRESULT)pTree->clrLine;
              pTree->clrLineNonTheme = (COLORREF)lParam;
  
              pTree->clrLine = (COLORREF)lParam;
              if (pTree->hbrLine && pTree->hbrLine != g_hbrGrayText)
              {
                  DeleteObject(pTree->hbrLine);
              }
  
              if (pTree->clrLine == CLR_DEFAULT)
              {
                  pTree->hbrLine = NULL;
              }
              else
              {
                  pTree->hbrLine = CreateSolidBrush(pTree->clrLine);
              }
  
              if (!pTree->hbrLine)
                  pTree->hbrLine = g_hbrGrayText;
  
              TV_CreateIndentBmps(pTree); // This also invalidates
  
              return lres;
          }
  
          case TVM_GETLINECOLOR:
              return (LRESULT)pTree->clrLine;
  
          case TVM_SETEXTENDEDSTYLE:
              return TV_SetExtendedStyle(pTree, (DWORD)lParam, (DWORD)wParam);
  
          case TVM_GETEXTENDEDSTYLE:
              return pTree->dwExStyle;
  
          case WM_IME_COMPOSITION:
              // Now only Korean version is interested in incremental search with composition string.
              if (g_fDBCSInputEnabled) {
              if (((ULONG_PTR)GetKeyboardLayout(0L) & 0xF000FFFFL) == 0xE0000412L)
              {
                  if (TV_OnImeComposition(pTree, wParam, lParam))
                  {
                      lParam &= ~GCS_RESULTSTR;
                      goto DoDefault;
                  }
                  else
                      break;
              }
              }
              goto DoDefault;
  
          case WM_CHAR:
              if (pTree->iPuntChar) 
              {
                  pTree->iPuntChar--;
                  return TRUE;
              } 
              else 
              {
                  return HANDLE_WM_CHAR(pTree, wParam, lParam, TV_OnChar);
              }
  
          case WM_DESTROY:
              TV_DestroyTree(pTree);
              break;
  
          case WM_SETCURSOR:
              {
                  NMMOUSE nm;
                  HTREEITEM hItem;
                  nm.dwHitInfo = lParam;
                  hItem = TV_ItemAtCursor(pTree, NULL);
                  if(hItem)
                  {
                      nm.dwItemSpec = (ULONG_PTR)hItem;
                      nm.dwItemData = (ULONG_PTR)(hItem->lParam);
                  }
                  else
                  {
                      nm.dwItemSpec = 0;
                      nm.dwItemData = 0;
                  }
                               
                  if (CCSendNotify(&pTree->ci, NM_SETCURSOR, &nm.hdr)) 
                  {
                      return 0;
                  }
              }
              if (pTree->ci.style & TVS_TRACKSELECT)
              {
                  if (pTree->hHot)
                  {
                      if (!pTree->hCurHot)
                      {
                          pTree->hCurHot = LoadCursor(NULL, IDC_HAND);
                      }
                      SetCursor(pTree->hCurHot);
                      return TRUE;
                  }
              }
              goto DoDefault;
              break;
  
          case WM_WININICHANGE:
              TV_OnWinIniChange(pTree, wParam);
              break;
  
          case WM_STYLECHANGED:
              TV_OnStyleChanged(pTree, wParam, (LPSTYLESTRUCT)lParam);
              break;
  
          case WM_SETREDRAW:
              TV_OnSetRedraw(pTree, (BOOL)wParam);
              break;
  
          case WM_PRINTCLIENT:
          case WM_PAINT:
              TV_Paint(pTree, (HDC)wParam);
              break;
  
          case WM_NCPAINT:
              if (pTree->hTheme && pTree->ci.dwExStyle & WS_EX_CLIENTEDGE)
              {
                  HRGN hrgn = (wParam != 1) ? (HRGN)wParam : NULL;
  
                  if (CCDrawNonClientTheme(pTree->hTheme, hwnd, hrgn, pTree->hbrBk, 0, 0))
                  {
                      return TRUE;
                  }
              }
              goto DoDefault;
  
  
          case WM_ERASEBKGND:
              {
                  RECT rc;
  
                  TV_GetBackgroundBrush(pTree, (HDC) wParam);
                  GetClipBox((HDC) wParam, &rc);
                  FillRect((HDC)wParam, &rc, pTree->hbrBk);
              }
              return TRUE;
  
          case WM_GETDLGCODE:
              return (LRESULT) (DLGC_WANTARROWS | DLGC_WANTCHARS);
  
          case WM_HSCROLL:
              TV_HorzScroll(pTree, GET_WM_HSCROLL_CODE(wParam, lParam), GET_WM_HSCROLL_POS(wParam, lParam));
              break;
  
          case WM_VSCROLL:
              TV_VertScroll(pTree, GET_WM_VSCROLL_CODE(wParam, lParam), GET_WM_VSCROLL_POS(wParam, lParam));
              break;
  
          case WM_KEYDOWN:
              if (TV_KeyDown(pTree, wParam, lParam))
                  IncrementSearchString(&pTree->is, 0, NULL);
                  goto DoDefault;
  
  
          case WM_LBUTTONDBLCLK:
          case WM_LBUTTONDOWN:
              TV_ButtonDown(pTree, uMsg, (UINT) wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), 0);
              break;
  
          case WM_KILLFOCUS:
              // Reset wheel scroll amount
              gcWheelDelta = 0;
  
              pTree->fFocus = FALSE;
              if (pTree->hCaret)
              {
                  TV_InvalidateItem(pTree, pTree->hCaret, RDW_INVALIDATE);
                  UpdateWindow(pTree->ci.hwnd);
              }
              CCSendNotify(&pTree->ci, NM_KILLFOCUS, NULL);
              IncrementSearchString(&pTree->is, 0, NULL);
              break;
  
          case WM_SETFOCUS:
              ASSERT(gcWheelDelta == 0);
  
              pTree->fFocus = TRUE;
              if (pTree->hCaret)
              {
                  TV_InvalidateItem(pTree, pTree->hCaret, RDW_INVALIDATE);
                  NotifyWinEvent(EVENT_OBJECT_FOCUS, hwnd, OBJID_CLIENT, TV_GetAccId(pTree->hCaret));
              }
              else
                  TV_SelectItem(pTree, TVGN_CARET, pTree->hTop, TVSIFI_NOTIFY | TVSIFI_UPDATENOW, TVC_INTERNAL);
  
              CCSendNotify(&pTree->ci, NM_SETFOCUS, NULL);
              break;
  
          case WM_GETFONT:
              return MAKELRESULTFROMUINT(pTree->hFont);
  
          case WM_SETFONT:
              TV_OnSetFont(pTree, (HFONT) wParam, (BOOL) lParam);
              break;
  
          case WM_SIZE:
              TV_SizeWnd(pTree, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
              break;
  
          case WM_ENABLE:
              // HACK: we don't get WM_STYLECHANGE on EnableWindow()
              if (wParam)
                  pTree->ci.style &= ~WS_DISABLED;        // enabled
              else
                  pTree->ci.style |= WS_DISABLED; // disabled
              TV_CreateIndentBmps(pTree); // This invalidates the whole window!
              break;
  
          case WM_SYSCOLORCHANGE:
              InitGlobalColors();
              TV_CreateIndentBmps(pTree); // This invalidates the whole window!
              break;
  
          case WM_RBUTTONDOWN:
              TV_SendRButtonDown(pTree, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
              break;
  
          case WM_TIMER:
              TV_Timer(pTree, (UINT) wParam);
              break;
  
      case WM_MOUSEMOVE:
          TV_OnMouseMove(pTree, (DWORD) lParam, wParam);
          break;
  
          case WM_COMMAND:
              TV_Command(pTree, (int)GET_WM_COMMAND_ID(wParam, lParam), GET_WM_COMMAND_HWND(wParam, lParam),
                      (UINT)GET_WM_COMMAND_CMD(wParam, lParam));
              break;
  
          case WM_NOTIFY:
              {
              LPNMHDR lpnm = (LPNMHDR)lParam;
  
              if ((lpnm->code <= PGN_FIRST) && (PGN_LAST <= lpnm->code))
              {
                  LRESULT TV_OnPagerControlNotify(PTREE pTree, LPNMHDR pnm);
  
                  return TV_OnPagerControlNotify(pTree, lpnm);
              }
              if (lpnm->hwndFrom == pTree->hwndToolTips)
              {
                  switch (lpnm->code)
                  {
                  case TTN_NEEDTEXT:
                      TV_HandleNeedText(pTree, (LPTOOLTIPTEXT)lpnm);
                      break;
  
                  case TTN_NEEDTEXTA:
                      TV_HandleNeedTextA(pTree, (LPTOOLTIPTEXTA)lpnm);
                      break;
  
                  case TTN_SHOW:
                      return TV_HandleTTNShow(pTree, lpnm);
  
                  case NM_CUSTOMDRAW:
                      return TV_HandleTTCustomDraw(pTree, (LPNMTTCUSTOMDRAW)lpnm);
                  }
              }
              break;
          }
  
          case WM_NOTIFYFORMAT:
          return CIHandleNotifyFormat(&pTree->ci, lParam);
  
          case WM_MBUTTONDOWN:
              SetFocus(hwnd);
              goto DoDefault;
  
          case WM_GETOBJECT:
              if( lParam == OBJID_QUERYCLASSNAMEIDX )
                  return MSAA_CLASSNAMEIDX_TREEVIEW;
              goto DoDefault;
  
          case WM_UPDATEUISTATE:
          {
              DWORD dwUIStateMask = MAKEWPARAM(0xFFFF, UISF_HIDEFOCUS);
  
              if (CCOnUIState(&(pTree->ci), WM_UPDATEUISTATE, wParam & dwUIStateMask, lParam))
                  if (pTree->hCaret)
                      TV_InvalidateItem(pTree, pTree->hCaret, TRUE);
  
              goto DoDefault;
          }
  
          case WM_SYSKEYDOWN:
              TV_KeyDown(pTree, wParam, lParam);
              //fall through
  
          default:
              // Special handling of magellan mouse message
              if (uMsg == g_msgMSWheel) 
              {
                  DWORD dwStyle;
                  int   cScrollLines;
                  int   cPage;
                  int   pos;
                  int   cDetants;
                  int   iWheelDelta = (int)(short)HIWORD(wParam);
                  BOOL  fScroll = !(wParam & (MK_SHIFT | MK_CONTROL));
                  BOOL  fDataZoom = (BOOL) (wParam & MK_SHIFT);
  
                  // Update count of scroll amount
                  gcWheelDelta -= iWheelDelta;
                  cDetants = gcWheelDelta / WHEEL_DELTA;
                  if (cDetants != 0) 
                  {
                      gcWheelDelta %= WHEEL_DELTA;
                  }
  
                  if (fScroll) 
                  {
                      if (    g_ucScrollLines > 0 &&
                              cDetants != 0 &&
                              (WS_VSCROLL | WS_HSCROLL) & (dwStyle = GetWindowStyle(hwnd)))
                      {
  
                          if (dwStyle & WS_VSCROLL) 
                          {
                              cPage = max(1, (pTree->cFullVisible - 1));
                              cScrollLines =
                                            cDetants *
                                            min(g_ucScrollLines, (UINT) cPage);
  
                              pos = max(0, pTree->hTop->iShownIndex + cScrollLines);
                              TV_VertScroll(pTree, SB_THUMBPOSITION, pos);
                          } 
                          else 
                          {
                              cPage = max(MAGIC_HORZLINE,
                                          (pTree->cxWnd - MAGIC_HORZLINE)) /
                                      MAGIC_HORZLINE;
  
                              cScrollLines =
                                            cDetants *
                                            (int) min((ULONG) cPage, g_ucScrollLines) *
                                            MAGIC_HORZLINE;
  
                              pos = max(0, pTree->xPos + cScrollLines);
                              TV_HorzScroll(pTree, SB_THUMBPOSITION, pos);
                          }
                      }
                      return 1;
                  } 
                  else if (fDataZoom)
                  {
                      UINT wHitCode;
                      POINT pt;
  
                      pt.x = GET_X_LPARAM(lParam);
                      pt.y = GET_Y_LPARAM(lParam);
                      ScreenToClient(hwnd, &pt);
  
                      // If we are rolling forward and hit an item then navigate into that
                      // item or expand tree (simulate lbuttondown which will do it).  We
                      // also need to handle rolling backwards over the ITEMBUTTON so
                      // that we can collapse the tree in that case.  Otherwise
                      // just fall through so it isn't handled.  In that case if we
                      // are being hosted in explorer it will do a backwards
                      // history navigation.
                      if (TV_CheckHit(pTree, pt.x, pt.y, &wHitCode) &&
                          (wHitCode & (TVHT_ONITEM | TVHT_ONITEMBUTTON))) {
                          UINT uFlags = TVBD_FROMWHEEL;
                          uFlags |= (iWheelDelta > 0) ? TVBD_WHEELFORWARD : TVBD_WHEELBACK;
  
                          if ((uFlags & TVBD_WHEELFORWARD) || (wHitCode == TVHT_ONITEMBUTTON)) {
                              TV_ButtonDown(pTree, WM_LBUTTONDOWN, 0, pt.x, pt.y, uFlags);
                              return 1;
                          }
                      }
                      // else fall through
                  }
              }
              else
              {
                  LRESULT lres;
                  if (CCWndProc(&pTree->ci, uMsg, wParam, lParam, &lres))
                      return lres;
              }
  
  DoDefault:
              return(DefWindowProc(hwnd, uMsg, wParam, lParam));
      }
  
      return(0L);
  }
  
  // NOTE: there is very similar code in the listview
  //
  // Totally disgusting hack in order to catch VK_RETURN
  // before edit control gets it.
  //
  LRESULT CALLBACK TV_EditWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
  {
      PTREE pTree = (PTREE)GetWindowInt(GetParent(hwnd), 0);
      ASSERT(pTree);
  
      if (!pTree)
          return 0L;  // wierd cases can get here...
  
      switch (msg) 
      {
      case WM_KEYDOWN:
          switch (wParam) 
          {
          case VK_RETURN:
              TV_DismissEdit(pTree, FALSE);
              return 0L;
  
          case VK_ESCAPE:
              TV_DismissEdit(pTree, TRUE);
              return 0L;
          }
          break;
  
      case WM_CHAR:
          switch (wParam)
          {
          case VK_RETURN:
              // Eat the character, so edit control wont beep!
              return 0L;
          }
  
          // 97903: Localization says we need this for DBCS chars
      case WM_IME_CHAR:
          msg = WM_CHAR;
          break;
  
      }
  
      return CallWindowProc(pTree->pfnEditWndProc, hwnd, msg, wParam, lParam);
  }
  
  
  void TV_SetEditSize(PTREE pTree)
  {
      RECT rcLabel;
      UINT seips;
  
      if (pTree->htiEdit == NULL)
          return;
  
      TV_GetItemRect(pTree, pTree->htiEdit, &rcLabel, TRUE);
  
      // get exact the text bounds (acount for borders used when drawing)
  
      InflateRect(&rcLabel, -g_cxLabelMargin, -g_cyBorder);
  
      seips = 0;
  #ifdef DEBUG
      // If we are in one of the no-scroll modes then it's possible for the
      // resulting rectangle not to be visible.  Similarly, if the item itself
      // isn't visible, then the resulting rectangle is definitely not visible.
      // Tell SetEditInPlaceSize not to get upset in those cases.
      if ((pTree->ci.style & (TVS_NOSCROLL | TVS_NOHSCROLL)) ||
          !ITEM_VISIBLE(pTree->htiEdit))
          seips |= SEIPS_NOSCROLL;
  #endif
  
      SetEditInPlaceSize(pTree->hwndEdit, &rcLabel, (HFONT)SendMessage(pTree->hwndEdit, WM_GETFONT, 0, 0), seips);
  }
  
  
  void TV_CancelEditTimer(PTREE pTree)
  {
      if (pTree->fNameEditPending)
      {
          KillTimer(pTree->ci.hwnd, IDT_NAMEEDIT);
          pTree->fNameEditPending = FALSE;
      }
  }
  
  HWND TV_EditLabel(PTREE pTree, HTREEITEM hItem, LPTSTR pszInitial)
  {
      TCHAR szLabel[MAXLABELTEXT];
      TV_DISPINFO nm;
  
      if (!(pTree->ci.style & TVS_EDITLABELS))
          return NULL;
  
      if (!ValidateTreeItem(hItem, 0))
          return NULL;
  
      TV_DismissEdit(pTree, FALSE);
  
  
      // Now get the text associated with that item
      nm.item.pszText = szLabel;
      nm.item.cchTextMax = ARRAYSIZE(szLabel);
      nm.item.stateMask = TVIS_BOLD;
      // this cast is ok as long as TVIF_INTEGRAL or anything past it isn't asked for
      TV_GetItem(pTree, hItem, TVIF_TEXT | TVIF_STATE, (LPTVITEMEX)&nm.item);
  
      // Must subtract one from ARRAYSIZE(szLabel) because Edit_LimitText
      // doesn't include the terminating NULL
      pTree->hwndEdit = CreateEditInPlaceWindow(pTree->ci.hwnd,
          pszInitial? pszInitial : nm.item.pszText, ARRAYSIZE(szLabel) - 1,
          WS_BORDER | WS_CLIPSIBLINGS | WS_CHILD | ES_LEFT | ES_AUTOHSCROLL,
          (nm.item.state & TVIS_BOLD) ? pTree->hFontBold : pTree->hFont);
  
      if (pTree->hwndEdit) {
          if (pszInitial)     // if initialized, it's dirty.
              SetWindowID(pTree->hwndEdit, 1);
          //
          // Now notify the parent of this window and see if they want it.
          // We do it after we cretae the window, but before we show it
          // such that our parent can query for it and do things like limit
          // the number of characters that are input
          nm.item.hItem = hItem;
          nm.item.state = hItem->state;
          nm.item.lParam = hItem->lParam;
          nm.item.mask = (TVIF_HANDLE | TVIF_STATE | TVIF_PARAM | TVIF_TEXT);
  
          if ((BOOL)CCSendNotify(&pTree->ci, TVN_BEGINLABELEDIT, &nm.hdr))
          {
              DestroyWindow(pTree->hwndEdit);
              pTree->hwndEdit = NULL;
              return NULL;
          }
  
          TV_PopBubble(pTree);
  
          TV_ScrollIntoView(pTree, hItem);
  
          pTree->pfnEditWndProc = SubclassWindow(pTree->hwndEdit, TV_EditWndProc);
  
          pTree->htiEdit = hItem;
  
          TV_SetEditSize(pTree);
  
          // Show the window and set focus to it.  Do this after setting the
          // size so we don't get flicker.
          SetFocus(pTree->hwndEdit);
          ShowWindow(pTree->hwndEdit, SW_SHOW);
          TV_InvalidateItem(pTree, hItem, RDW_INVALIDATE | RDW_ERASE);
  
          RescrollEditWindow(pTree->hwndEdit);
      }
  
      return pTree->hwndEdit;
  }
  
  
  BOOL TV_DismissEdit(PTREE pTree, BOOL fCancel)
  {
      HWND hwndEdit;
      BOOL fOkToContinue = TRUE;
      HTREEITEM htiEdit;
  
      if (pTree->fNoDismissEdit)
          return FALSE;
  
      hwndEdit = pTree->hwndEdit;
  
      if (!hwndEdit) {
          // Also make sure there are no pending edits...
          TV_CancelEditTimer(pTree);
          return TRUE;
      }
  
      // Assume that if we are not visible that the window is in the
      // process of being destroyed and we should not process the
      // editing of the window...
      if (!IsWindowVisible(pTree->ci.hwnd))
          fCancel = TRUE;
  
      //
      // We are using the Window ID of the control as a BOOL to
      // state if it is dirty or not.
      switch (GetWindowID(hwndEdit)) {
      case 0:
          // The edit control is not dirty so act like cancel.
          fCancel = TRUE;
          //  FALL THROUGH
      case 1:
          // The edit control is dirty so continue.
          SetWindowID(hwndEdit, 2);    // Don't recurse
          break;
      case 2:
          // We are in the process of processing an update now, bail out
          return TRUE;
      }
  
      // TV_DeleteItemRecurse will set htiEdit to NULL if the program
      // deleted the items out from underneath us (while we are waiting
      // for the edit timer).
      htiEdit = pTree->htiEdit;
  
      if (htiEdit != NULL)
      {
          TV_DISPINFO nm;
          TCHAR szLabel[MAXLABELTEXT];
  
          DBG_ValidateTreeItem(htiEdit, 0);
  
          // Initialize notification message.
          nm.item.hItem = htiEdit;
          nm.item.lParam = htiEdit->lParam;
          nm.item.mask = 0;
  
          if (fCancel)
              nm.item.pszText = NULL;
          else {
              Edit_GetText(hwndEdit, szLabel, ARRAYSIZE(szLabel));
              nm.item.pszText = szLabel;
              nm.item.cchTextMax = ARRAYSIZE(szLabel);
              nm.item.mask |= TVIF_TEXT;
          }
  
          // Make sure the text redraws properly
          TV_InvalidateItem(pTree, htiEdit, RDW_INVALIDATE | RDW_ERASE);
          pTree->fNoDismissEdit = TRUE; // this is so that we don't recurse due to killfocus
          ShowWindow(hwndEdit, SW_HIDE);
          pTree->fNoDismissEdit = FALSE;
  
          //
          // Notify the parent that we the label editing has completed.
          // We will use the LV_DISPINFO structure to return the new
          // label in.  The parent still has the old text available by
          // calling the GetItemText function.
          //
  
          fOkToContinue = (BOOL)CCSendNotify(&pTree->ci, TVN_ENDLABELEDIT, &nm.hdr);
          if (fOkToContinue && !fCancel)
          {
              // Bug#94368 raymondc: The caller might have deleted the item in
              // response to the edit.  We should revalidate here (or make
              // delete item invalidate our edit item).  Treat a deletion
              // as if it were a rejected edit.
  
              //
              // If the item has the text set as CALLBACK, we will let the
              // ower know that they are supposed to set the item text in
              // their own data structures.  Else we will simply update the
              // text in the actual view.
              //
              // Note: The callee may have set the handle to null to tell
              // us that the handle to item is no longer valid.
              if (nm.item.hItem != NULL)
              {
                  if (htiEdit->lpstr != LPSTR_TEXTCALLBACK)
                  {
                      // Set the item text (everything's set up in nm.item)
                      //
                      nm.item.mask = TVIF_TEXT;
                      TV_SetItem(pTree, (LPTVITEMEX)&nm.item);
                  }
                  else
                  {
                      CCSendNotify(&pTree->ci, TVN_SETDISPINFO, &nm.hdr);
                  }
              }
          }
      }
  
      // If we did not reenter edit mode before now reset the edit state
      // variables to NULL
      if (hwndEdit == pTree->hwndEdit)
      {
          pTree->htiEdit = NULL;
          pTree->hwndEdit = NULL; // so we don't get reentered on the kill focus
      }
  
      // done with the edit control
      DestroyWindow(hwndEdit);
  
      return fOkToContinue;
  }
  
  LRESULT TV_OnCalcSize(PTREE pTree, LPNMHDR pnm)
  {
      LPNMPGCALCSIZE pcalcsize = (LPNMPGCALCSIZE)pnm;
  
      switch(pcalcsize->dwFlag) {
      case PGF_CALCHEIGHT:
          pcalcsize->iHeight = pTree->cShowing * pTree->cyItem;
          TraceMsg(TF_WARNING, "tv.PGF_CALCHEIGHT: cShow=%d cShow*cyItem=%d AWR()=%d",
              pTree->cShowing, pTree->cShowing * pTree->cyItem, pcalcsize->iHeight);
          break;
  
      case PGF_CALCWIDTH:
          break;
      }
      return 0L;
  }
  
  LRESULT TV_OnPagerControlNotify(PTREE pTree, LPNMHDR pnm)
  {
      switch(pnm->code) {
      case PGN_SCROLL:
          return TV_OnScroll(pTree, pnm);
          break;
      case PGN_CALCSIZE:
          return TV_OnCalcSize(pTree, pnm);
          break;
      }
      return 0L;
  }
  
  LRESULT TV_OnScroll(PTREE pTree, LPNMHDR pnm)
  {
    
      LPNMPGSCROLL pscroll = (LPNMPGSCROLL)pnm;
      RECT rc = pscroll->rcParent;
      RECT rcTemp;
      int iDir = pscroll->iDir;
      int dyScroll = pscroll->iScroll;
      TREEITEM * hItem;
      UINT uCode;
      int parentsize;
      TREEITEM *  hPrevItem;
      TREEITEM *  hNextItem;
      int y;
      
      POINT pt = {pscroll->iXpos, pscroll->iYpos};
      POINT ptTemp = pt;
      TREEITEM *  hCurrentItem = TV_CheckHit(pTree, pt.x + 1, pt.y + 1 , &uCode);
  
      switch(iDir)
      {
          case PGF_SCROLLUP:
              //Check if any Item is partially visible at the left/top. if so then set the bottom 
              // of that Item to be our current offset and then scroll. This avoids skipping over
              // certain Items when partial Items are displayed at the left or top
              y = pt.y;       
              TV_GetItemRect(pTree,hCurrentItem,&rcTemp, TRUE);
   
              if (rcTemp.top  <  y-1)
              {
                  hCurrentItem =TV_GetNextItem(pTree,hCurrentItem,TVGN_NEXTVISIBLE);
              }
  
              // Now do the calculation
              parentsize = RECTHEIGHT(rc);
  
              //if  the control key is down and we have more than parentsize size of child window
              // then scroll by that amount
              if ((pscroll->fwKeys & PGK_CONTROL) && ((pt.y - parentsize) > 0))
              {
                  dyScroll = parentsize;
              } else if ((pt.y - pTree->cyItem) > 0) {
              // we dont have control key down so scroll by one buttonsize    
                  dyScroll = pTree->cyItem;
              } else {
                  pscroll->iScroll = pt.y;
                  return 0L;
              }
              ptTemp.y -= dyScroll;
              hItem = TV_CheckHit(pTree, ptTemp.x, ptTemp.y, &uCode);
  
              if (hItem)
              {
                  // if  the hit test gives us the same Item as our CurrentItem then set the Item 
                  // to one Item to the top/left  of the  CurrentItem 
  
                  hPrevItem = TV_GetNextItem(pTree,hCurrentItem, TVGN_PREVIOUSVISIBLE);
                  if ((hItem == hCurrentItem) && ( hPrevItem != NULL))
                  {
                      hItem = hPrevItem;
                  }
  
                  //When scrolling left if we end up in the middle of some Item then we align it to the 
                  //right of that Item this is to avoid scrolling more than the pager window width but if the
                  // Item happens to be the left Item of  our current Item then we end up in not scrolling
                  //if thats the case then move one more Item to the left.
  
  
                  if (hItem == hPrevItem) 
                  {
                      hItem = TV_GetNextItem(pTree, hItem, TVGN_PREVIOUSVISIBLE);
                      if(!hItem)
                      {
                          dyScroll = pt.y;
                          break;
                      }
                  }
  
                  TV_GetItemRect(pTree,hItem,&rcTemp, TRUE);
                  dyScroll = pt.y - rcTemp.bottom;
              }
              break;
          case PGF_SCROLLDOWN:
          {
              RECT rcChild;
              int childsize;
  
              GetWindowRect(pTree->ci.hwnd, &rcChild);
              childsize = RECTHEIGHT(rcChild);
              parentsize = RECTHEIGHT(rc);
  
              //if  the control key is down and we have more than parentsize size of child window
              // then scroll by that amount
              if ((pscroll->fwKeys & PGK_CONTROL) && ((childsize - pt.y - parentsize) > parentsize))
              {
                  dyScroll = parentsize;
              } else if ( (childsize - pt.y - parentsize) > (pTree->cyItem * hCurrentItem->iIntegral) ) {
              // we dont have control key down so scroll by one buttonsize    
                  dyScroll = pTree->cyItem * hCurrentItem->iIntegral;
              } else {
                  pscroll->iScroll = childsize - pt.y - parentsize;
                  return 0L;
              }
              ptTemp.y += dyScroll;
  
              hItem = TV_CheckHit(pTree, ptTemp.x, ptTemp.y, &uCode);
  
              if (hItem)
              {
                  if ((hItem == hCurrentItem) && 
                      ((hNextItem = TV_GetNextItem(pTree,hItem,TVGN_NEXTVISIBLE)) != NULL))
                  {
                      hItem = hNextItem;
                  }
                  TV_GetItemRect(pTree, hItem, &rcTemp, TRUE);
                  dyScroll = rcTemp.top  - pt.y ;
              }
  
              break;
          }
      }
      //Set the scroll value
      pscroll->iScroll = dyScroll;
      return 0L;
   }
  