#include "ctlspriv.h" #include "treeview.h" #include "image.h" extern void TruncateString(char *sz, int cch); void NEAR TV_GetBackgroundBrush(PTREE pTree, HDC hdc) { if (pTree->clrBk == (COLORREF)-1) { if (pTree->ci.style & WS_DISABLED) pTree->hbrBk = FORWARD_WM_CTLCOLORSTATIC(pTree->ci.hwndParent, hdc, pTree->ci.hwnd, SendMessage); else pTree->hbrBk = FORWARD_WM_CTLCOLOREDIT(pTree->ci.hwndParent, hdc, pTree->ci.hwnd, SendMessage); } } // ---------------------------------------------------------------------------- // // Draws a horizontal or vertical dotted line from the given (x,y) location // for the given length (c). // // ---------------------------------------------------------------------------- void NEAR TV_DrawDottedLine(HDC hdc, int x, int y, int c, BOOL fVert) { while (c > 0) { PatBlt(hdc, x, y, 1, 1, PATCOPY); if (fVert) y += 2; else x += 2; c -= 2; } } // ---------------------------------------------------------------------------- // // Draws a plus or minus sign centered around the given (x,y) location and // extending out from that location the given distance (c). // // ---------------------------------------------------------------------------- void NEAR TV_DrawPlusMinus(HDC hdc, int x, int y, int c, HBRUSH hbrSign, HBRUSH hbrBox, HBRUSH hbrBk, BOOL fPlus) { int n; int p = (c * 7) / 10; n = p * 2 + 1; SelectObject(hdc, hbrSign); if (p >= 5) { PatBlt(hdc, x - p, y - 1, n, 3, PATCOPY); if (fPlus) PatBlt(hdc, x - 1, y - p, 3, n, PATCOPY); SelectObject(hdc, hbrBk); p--; n -= 2; } PatBlt(hdc, x - p, y, n, 1, PATCOPY); if (fPlus) PatBlt(hdc, x, y - p, 1, n, PATCOPY); n = c * 2 + 1; SelectObject(hdc, hbrBox); PatBlt(hdc, x - c, y - c, n, 1, PATCOPY); PatBlt(hdc, x - c, y - c, 1, n, PATCOPY); PatBlt(hdc, x - c, y + c, n, 1, PATCOPY); PatBlt(hdc, x + c, y - c, 1, n, PATCOPY); } // ---------------------------------------------------------------------------- // // Create the bitmaps for the indent area of the tree as follows // if fHasLines && fHasButtons --> 7 bitmaps // if fHasLines && !fHasButtons --> 3 bitmaps // if !fHasLines && fHasButtons --> 2 bitmaps // // sets hStartBmp, hBmp, hdcBits // // If "has lines" then there are three basic bitmaps. // // | | | // | +--- +--- // | | // // (The plan vertical line does not get buttons.) // // Otherwise, there are no lines, so the basic bitmaps are blank. // // If "has buttons", then the basic bitmaps are augmented with buttons. // // [+] [-] // // And if you have "lines at root", you get // // __ // // // And if you have "lines at root" with "has buttons", then you also get // // --[+] --[-] // // So, there are twelve image types. Here they are, with the code names // written underneath. // // | | | | | | | // | +--- +--- [+]-- [+]-- [-]-- [-]-- // | | | | // // "|" "|-" "L" "|-+" "L+" "|--" "L-" // // --- [+]-- [-]-- [+] [-] // // ".-" ".-+" ".--" "+" "-" // // And the master table of which styles get which images. // // // LINES BTNS ROOT | |- L |-+ L+ |-- L- .- .-+ .-- + - // // x 0 1 // x 0 1 2 3 // x 0 1 2 3 // x x 0 1 2 3 4 5 6 // x x 0 1 2 3 // x x x 0 1 2 3 4 5 6 7 8 9 // // ---------------------------------------------------------------------------- void NEAR TV_CreateIndentBmps(PTREE pTree) { int cnt; RECT rc; HBRUSH hbrOld; int xMid, yMid; int x, c; HBITMAP hBmpOld; HBRUSH hbrLine; HBRUSH hbrText; HDC hdc; if (pTree->fRedraw) InvalidateRect(pTree->ci.hwnd, NULL, TRUE); if (pTree->ci.style & TVS_HASLINES) { if (pTree->ci.style & TVS_HASBUTTONS) cnt = 7; // | |- L |-+ L+ |-- L- else cnt = 3; // | |- L if (pTree->ci.style & TVS_LINESATROOT) { if (pTree->ci.style & TVS_HASBUTTONS) cnt += 3; // - -+ -- else cnt += 1; // - } } else if (pTree->ci.style & TVS_HASBUTTONS) cnt = 2; else return; if (!pTree->hdcBits) pTree->hdcBits = CreateCompatibleDC(NULL); hdc = pTree->hdcBits; // Get a new background brush, just like an Edit does. TV_GetBackgroundBrush(pTree, hdc); hBmpOld = pTree->hBmp; pTree->hBmp = CreateColorBitmap(cnt * pTree->cxIndent, pTree->cyItem); if (hBmpOld) { SelectObject(hdc, pTree->hBmp); DeleteObject(hBmpOld); } else pTree->hStartBmp = SelectObject(hdc, pTree->hBmp); if (pTree->clrLine != CLR_DEFAULT) hbrLine = CreateSolidBrush(pTree->clrLine); else hbrLine = g_hbrGrayText; if (pTree->clrText != (COLORREF)-1) hbrText = CreateSolidBrush(pTree->clrText); else hbrText = g_hbrWindowText; hbrOld = SelectObject(hdc, hbrLine); rc.top = 0; rc.left = 0; rc.right = cnt * pTree->cxIndent; rc.bottom = pTree->cyItem; FillRect(hdc, &rc, pTree->hbrBk); x = 0; if (pTree->hImageList) xMid = (pTree->cxImage - MAGIC_INDENT) / 2; else xMid = pTree->cxIndent / 2; yMid = ((pTree->cyItem / 2) + 1) & ~1; c = (min(xMid, yMid)) / 2; if (pTree->ci.style & TVS_HASLINES) { TV_DrawDottedLine(hdc, x + xMid, 0, pTree->cyItem, TRUE); x += pTree->cxIndent; TV_DrawDottedLine(hdc, x + xMid, 0, pTree->cyItem, TRUE); TV_DrawDottedLine(hdc, x + xMid, yMid, pTree->cxIndent - xMid, FALSE); x += pTree->cxIndent; TV_DrawDottedLine(hdc, x + xMid, 0, yMid, TRUE); TV_DrawDottedLine(hdc, x + xMid, yMid, pTree->cxIndent - xMid, FALSE); x += pTree->cxIndent; } if (pTree->ci.style & TVS_HASBUTTONS) { BOOL fPlus = TRUE; x += xMid; doDrawPlusMinus: TV_DrawPlusMinus(hdc, x, yMid, c, hbrText, hbrLine, pTree->hbrBk, fPlus); if (pTree->ci.style & TVS_HASLINES) { TV_DrawDottedLine(hdc, x, 0, yMid - c, TRUE); TV_DrawDottedLine(hdc, x + c, yMid, pTree->cxIndent - xMid - c, FALSE); TV_DrawDottedLine(hdc, x, yMid + c, yMid - c, TRUE); x += pTree->cxIndent; TV_DrawPlusMinus(hdc, x, yMid, c, hbrText, hbrLine, pTree->hbrBk, fPlus); TV_DrawDottedLine(hdc, x, 0, yMid - c, TRUE); TV_DrawDottedLine(hdc, x + c, yMid, pTree->cxIndent - xMid - c, FALSE); } x += pTree->cxIndent; if (fPlus) { fPlus = FALSE; goto doDrawPlusMinus; } x -= xMid; } if (pTree->ci.style & TVS_LINESATROOT) { // - TV_DrawDottedLine(hdc, x + xMid, yMid, pTree->cxIndent - xMid, FALSE); x += pTree->cxIndent; if (pTree->ci.style & TVS_HASBUTTONS) { x += xMid; TV_DrawPlusMinus(hdc, x, yMid, c, hbrText, hbrLine, pTree->hbrBk, TRUE); TV_DrawDottedLine(hdc, x + c, yMid, pTree->cxIndent - xMid - c, FALSE); x += pTree->cxIndent; TV_DrawPlusMinus(hdc, x, yMid, c, hbrText, hbrLine, pTree->hbrBk, FALSE); TV_DrawDottedLine(hdc, x + c, yMid, pTree->cxIndent - xMid - c, FALSE); // uncomment if there's more to be added //x += pTree->cxIndent - xMid; } } if (hbrOld) SelectObject(pTree->hdcBits, hbrOld); if (pTree->clrLine != CLR_DEFAULT) DeleteObject(hbrLine); if (pTree->clrText != (COLORREF)-1) DeleteObject(hbrText); } // ---------------------------------------------------------------------------- // // fills in a TVITEM structure based by coying data from the item or // by calling the callback to get it. // // in: // hItem item to get TVITEM struct for // mask which bits of the TVITEM struct you want (TVIF_ flags) // out: // lpItem TVITEM filled in // // ---------------------------------------------------------------------------- void NEAR TV_GetItem(PTREE pTree, HTREEITEM hItem, UINT mask, LPTVITEMEX lpItem) { TV_DISPINFO nm; if (!hItem || !lpItem) return; DBG_ValidateTreeItem(hItem, FALSE); nm.item.mask = 0; // We need to check the mask to see if lpItem->pszText is valid // And even then, it might not be, so be paranoid if ((mask & TVIF_TEXT) && lpItem->pszText && lpItem->cchTextMax) { if (hItem->lpstr == LPSTR_TEXTCALLBACK) { nm.item.mask |= TVIF_TEXT; // caller had to fill in pszText and cchTextMax with valid data nm.item.pszText = lpItem->pszText; nm.item.cchTextMax = lpItem->cchTextMax; nm.item.pszText[0] = 0; } else { ASSERT(hItem->lpstr); // we could do this but this is dangerous (when responding // to TVM_GETITEM we would be giving the app a pointer to our data) // lpItem->pszText = hItem->lpstr; lstrcpyn(lpItem->pszText, hItem->lpstr, lpItem->cchTextMax); #ifndef UNICODE // only call truncate string if the source string is larger than the dest buffer // this is to deal with corel draw who passes in a bogus cchTextMax value // // We used to always call TruncateString when cchTextMax is MAX_PATH, but // McAfee Virus program (QFE1381) passes MAX_PATH with a smaller than MAX_PATH buffer // so we must always check the string length first. They luck out // and lstrlen(hItem->lpstr) is smaller than max path so we don't truncate. // if (lstrlen(hItem->lpstr) >= lpItem->cchTextMax) { // takes care of broken dbcs sequence, note lstrcpyn puts nul at // cchTextMax-1 if exceeded TruncateString(lpItem->pszText, lpItem->cchTextMax); } #endif } } if (mask & TVIF_IMAGE) { if (hItem->iImage == (WORD)I_IMAGECALLBACK) nm.item.mask |= TVIF_IMAGE; else lpItem->iImage = hItem->iImage; } if (mask & TVIF_SELECTEDIMAGE) { if (hItem->iSelectedImage == (WORD)I_IMAGECALLBACK) nm.item.mask |= TVIF_SELECTEDIMAGE; else lpItem->iSelectedImage = hItem->iSelectedImage; } if (mask & TVIF_INTEGRAL) { lpItem->iIntegral = hItem->iIntegral; } if (mask & TVIF_CHILDREN) { switch (hItem->fKids) { case KIDS_COMPUTE: lpItem->cChildren = hItem->hKids ? 1 : 0;// the actual count doesn't matter break; case KIDS_FORCE_YES: lpItem->cChildren = 1;// the actual count doesn't matter break; case KIDS_FORCE_NO: lpItem->cChildren = 0; break; case KIDS_CALLBACK: nm.item.mask |= TVIF_CHILDREN; break; } } // copy out constant parameters (and prepare for callback) // IE4 and IE5.0 did this unconditionally lpItem->state = nm.item.state = hItem->state; // // NOTICE! We do not set TVIF_STATE nm.item.mask and we do not // check for TVIF_STATE in the "any items need to be filled in // by callback?" test a few lines below. This is necessary for // backwards compat. IE5 and earlier did not call the app back // if the only thing you asked for was TVIF_STATE. You can't // change this behavior unless you guard it with a version check, or // apps will break. (They'll get callbacks when they didn't used to.) // Besides, nobody knows that they can customize the state, so it's // not like we're missing out on anything. // #ifdef DEBUG_TEST_BOLD if ((((int)hItem) / 100) % 2) lpItem->state |= TVIS_BOLD; if (!pTree->hFontBold) TV_CreateBoldFont(pTree); #endif lpItem->lParam = nm.item.lParam = hItem->lParam; // any items need to be filled in by callback? if (nm.item.mask & (TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_CHILDREN)) { nm.item.hItem = hItem; CCSendNotify(&pTree->ci, TVN_GETDISPINFO, &nm.hdr); // copy out things that may have been filled in on the callback if (nm.item.mask & TVIF_CHILDREN) lpItem->cChildren = nm.item.cChildren; if (nm.item.mask & TVIF_IMAGE) lpItem->iImage = nm.item.iImage; if (nm.item.mask & TVIF_SELECTEDIMAGE) lpItem->iSelectedImage = nm.item.iSelectedImage; // callback may have redirected pszText to point into its own buffer if (nm.item.mask & TVIF_TEXT) { if (mask & TVIF_TEXT) // did the *original* mask specify TVIF_TEXT? lpItem->pszText = CCReturnDispInfoText(nm.item.pszText, lpItem->pszText, lpItem->cchTextMax); else lpItem->pszText = nm.item.pszText; // do what we used to do } if (nm.item.mask & TVIF_STATE) { lpItem->state = (nm.item.state & nm.item.stateMask) | (lpItem->state & ~nm.item.stateMask); if ((lpItem->state & TVIS_BOLD) && !pTree->hFontBold) TV_CreateBoldFont(pTree); } if (nm.item.mask & TVIF_DI_SETITEM) { if (nm.item.mask & TVIF_TEXT) if (nm.item.pszText) { ASSERT(hItem->lpstr == LPSTR_TEXTCALLBACK); Str_Set(&hItem->lpstr, nm.item.pszText); } if (nm.item.mask & TVIF_STATE) { // if the bold bit changed, then the width changed if ((hItem->state ^ lpItem->state) & TVIS_BOLD) hItem->iWidth = 0; hItem->state = (WORD) lpItem->state; } if (nm.item.mask & TVIF_IMAGE) hItem->iImage = (WORD) lpItem->iImage; if (nm.item.mask & TVIF_SELECTEDIMAGE) hItem->iSelectedImage = (WORD) lpItem->iSelectedImage; if (nm.item.mask & TVIF_CHILDREN) { switch(nm.item.cChildren) { case I_CHILDRENCALLBACK: hItem->fKids = KIDS_CALLBACK; break; case 0: hItem->fKids = KIDS_FORCE_NO; break; default: hItem->fKids = KIDS_FORCE_YES; break; } } } } } // ---------------------------------------------------------------------------- // // Draws the given item starting at the given (x,y) and extending down and to // the right. // // ---------------------------------------------------------------------------- BOOL NEAR TV_ShouldItemDrawBlue(PTREE pTree, TVITEMEX *ti, UINT flags) { return ( (ti->state & TVIS_DROPHILITED) || (!pTree->hDropTarget && !(flags & TVDI_GRAYCTL) && (ti->state & TVIS_SELECTED) && pTree->fFocus)); } #define TV_ShouldItemDrawDisabled(pTree, pti, flags) (flags & TVDI_GRAYCTL) // // Caution: Depending on the user's color scheme, a Gray item may // end up looking Blue if Gray would otherwise be invisible. So make // sure that there are other cues that the user can use to tell whether // the item is "Really Blue" or "Gray masquerading as Blue". // // For example, you might get both is if the treeview is // participating in drag/drop while it is not the active window, // because the selected item gets "Gray masquerading as Blue" and // the drop target gets "Really Blue". But we special-case that // and turn off the selection while we are worrying about drag/drop, // so there is no confusion after all. // BOOL TV_ShouldItemDrawGray(PTREE pTree, TVITEMEX *pti, UINT flags) { return ((flags & TVDI_GRAYCTL) || (!pTree->hDropTarget && ((pti->state & TVIS_SELECTED) && (!pTree->fFocus && (pTree->ci.style & TVS_SHOWSELALWAYS)) ))); } // // Draw a descender line for the item. It is the caller's job to // draw the appropriate glyph at level 0. // void TV_DrawDescender(PTREE pTree, HDC hdc, int x, int y, HTREEITEM hItem) { int i; for (i = 1; i < hItem->iIntegral; i++) BitBlt(hdc, x, y + i * pTree->cyItem, pTree->cxIndent, pTree->cyItem, pTree->hdcBits, 0, 0, SRCCOPY); } // // Erase any previous descender line for the item. // void TV_EraseDescender(PTREE pTree, HDC hdc, int x, int y, HTREEITEM hItem) { RECT rc; rc.left = x; rc.right = x + pTree->cxIndent; rc.top = y + pTree->cyItem; rc.bottom = y + hItem->iIntegral * pTree->cyItem; FillRect(hdc, &rc, pTree->hbrBk); } // // Draw (or erase) descenders for siblings and children. // void TV_DrawKinDescender(PTREE pTree, HDC hdc, int x, int y, HTREEITEM hItem, UINT state) { if (hItem->hNext) // Connect to next sibling TV_DrawDescender(pTree, hdc, x, y, hItem); else TV_EraseDescender(pTree, hdc, x, y, hItem); // If any bonus images, then need to connect the image to the kids. if (pTree->himlState || pTree->hImageList) { if (state & (TVIS_EXPANDED | TVIS_EXPANDPARTIAL)) // Connect to expanded kids TV_DrawDescender(pTree, hdc, x + pTree->cxIndent, y, hItem); else TV_EraseDescender(pTree, hdc, x + pTree->cxIndent, y, hItem); } } void NEAR TV_DrawItem(PTREE pTree, HTREEITEM hItem, HDC hdc, int x, int y, UINT flags) { UINT cxIndent = pTree->cxIndent; COLORREF rgbOldBack = 0, rgbOldText; COLORREF clrBk = CLR_DEFAULT; RECT rc; int iBack, iText; HTREEITEM hItemSave = hItem; LPTSTR lpstr; int cch; UINT etoFlags = ETO_OPAQUE | ETO_CLIPPED; TVITEMEX ti; TCHAR szTemp[MAX_PATH]; int iState = 0; HFONT hFont; //$BOLD DWORD dwRet; NMTVCUSTOMDRAW nmcd; BOOL fItemFocused = ((pTree->fFocus) && (hItem == pTree->hCaret)); DWORD clrTextTemp, clrTextBkTemp; BOOL fSelectedIcon = FALSE; rc.top = y; rc.bottom = rc.top + (pTree->cyItem * hItem->iIntegral); rc.left = 0; rc.right = pTree->cxWnd; if (flags & TVDI_ERASE) { // Opaque the whole item FillRect(hdc, &rc, pTree->hbrBk); } // make sure the callbacks don't invalidate this item pTree->hItemPainting = hItem; ti.pszText = szTemp; ti.cchTextMax = ARRAYSIZE(szTemp); ti.stateMask = TVIS_OVERLAYMASK | TVIS_CUT | TVIS_BOLD; //$BOLD TV_GetItem(pTree, hItem, TVIF_IMAGE | TVIF_STATE | TVIF_SELECTEDIMAGE | TVIF_TEXT | TVIF_CHILDREN | TVIF_PARAM, &ti); pTree->hItemPainting = NULL; //////////////// // set up the HDC if (TV_ShouldItemDrawBlue(pTree,&ti,flags)) { // selected iBack = COLOR_HIGHLIGHT; iText = COLOR_HIGHLIGHTTEXT; } else if (TV_ShouldItemDrawDisabled(pTree, &pti, flags)) { iBack = COLOR_3DFACE; iText = COLOR_GRAYTEXT; } else if (TV_ShouldItemDrawGray(pTree, &ti, flags)) { // On some color schemes, the BTNFACE color equals the WINDOW color, // and our gray comes out invisible. In such case, change from gray // to blue so you can see it at all. if (GetSysColor(COLOR_WINDOW) != GetSysColor(COLOR_BTNFACE)) { iBack = COLOR_BTNFACE; iText = COLOR_BTNTEXT; } else { iBack = COLOR_HIGHLIGHT; iText = COLOR_HIGHLIGHTTEXT; } } else { // not selected iBack = COLOR_WINDOW; iText = COLOR_WINDOWTEXT; if (hItem == pTree->hHot) { iText = COLOR_HOTLIGHT; } } if (iBack == COLOR_WINDOW && (pTree->clrBk != (COLORREF)-1)) nmcd.clrTextBk = clrTextBkTemp = pTree->clrBk; else nmcd.clrTextBk = clrTextBkTemp = GetSysColor(iBack); if (iText == COLOR_WINDOWTEXT && (pTree->clrText != (COLORREF)-1)) nmcd.clrText = clrTextTemp = pTree->clrText; else nmcd.clrText = clrTextTemp = GetSysColor(iText); // if forcing black and transparent, do so. dc's BkMode should // already be set to TRANSPARENT by caller if (flags & TVDI_TRANSTEXT) { nmcd.clrText = clrTextTemp = 0x000000; etoFlags = 0; // don't opaque nothin' } rgbOldBack = SetBkColor(hdc, nmcd.clrTextBk); rgbOldText = SetTextColor(hdc, nmcd.clrText); #ifdef WINDOWS_ME if (pTree->ci.style & TVS_RTLREADING) etoFlags |= ETO_RTLREADING; #endif // Figure out which font to use. if (ti.state & TVIS_BOLD) { hFont = pTree->hFontBold; if (hItem == pTree->hHot) { hFont = CCGetHotFont(pTree->hFontBold, &pTree->hFontBoldHot); } } else { hFont = pTree->hFont; if (hItem == pTree->hHot) { hFont = CCGetHotFont(pTree->hFont, &pTree->hFontHot); } } hFont = SelectObject(hdc, hFont); // End HDC setup //////////////// // notify on custom draw then do it! nmcd.nmcd.hdc = hdc; nmcd.nmcd.dwItemSpec = (DWORD_PTR)hItem; nmcd.nmcd.uItemState = 0; nmcd.nmcd.rc = rc; if (flags & TVDI_NOTREE) nmcd.iLevel = 0; else nmcd.iLevel = hItem->iLevel; if (ti.state & TVIS_SELECTED) { fSelectedIcon = TRUE; if (pTree->fFocus || (pTree->ci.style & TVS_SHOWSELALWAYS)) nmcd.nmcd.uItemState |= CDIS_SELECTED; } if (fItemFocused) nmcd.nmcd.uItemState |= CDIS_FOCUS; if (hItem == pTree->hHot) nmcd.nmcd.uItemState |= CDIS_HOT; #ifdef KEYBOARDCUES #if 0 // BUGBUG: Custom draw stuff for UISTATE (stephstm) if (CCGetUIState(&(pTree->ci), KC_TBD)) nmcd.nmcd.uItemState |= CDIS_SHOWKEYBOARDCUES; #endif #endif nmcd.nmcd.lItemlParam = ti.lParam; dwRet = CICustomDrawNotify(&pTree->ci, CDDS_ITEMPREPAINT, &nmcd.nmcd); if (dwRet & CDRF_SKIPDEFAULT) return; fItemFocused = (nmcd.nmcd.uItemState & CDIS_FOCUS); if (nmcd.nmcd.uItemState & CDIS_SELECTED) ti.state |= TVIS_SELECTED; else { ti.state &= ~TVIS_SELECTED; } if (nmcd.clrTextBk != clrTextBkTemp) SetBkColor(hdc, nmcd.clrTextBk); if (nmcd.clrText != clrTextTemp) SetTextColor(hdc, nmcd.clrText); if (pTree->ci.style & TVS_FULLROWSELECT && !(flags & TVDI_TRANSTEXT)) { FillRectClr(hdc, &nmcd.nmcd.rc, GetBkColor(hdc)); etoFlags |= ETO_OPAQUE; clrBk = CLR_NONE; } if (!(flags & TVDI_NOTREE)) { if ((pTree->ci.style & (TVS_HASLINES | TVS_HASBUTTONS)) && (pTree->ci.style & TVS_LINESATROOT)) // Make room for the "plus" at the front of the tree x += cxIndent; } // deal with margin, etc. x += (pTree->cxBorder + (nmcd.iLevel * cxIndent)); y += pTree->cyBorder; // draw image if ((!(flags & TVDI_NOTREE) && !(dwRet & TVCDRF_NOIMAGES)) || (flags & TVDI_FORCEIMAGE)) { int dx, dy; // to clip the images within the borders. COLORREF clrImage = CLR_HILIGHT; COLORREF clrBkImage = clrBk; if (flags & TVDI_NOBK) { clrBkImage = CLR_NONE; } if (pTree->himlState) { iState = TV_StateIndex(&ti); // go figure. in the treeview, 0 for the state image index // means draw nothing... the 0th item is unused. // the listview is 0 based and uses the 0th item. if (iState) { dx = min(pTree->cxState, pTree->cxMax - pTree->cxBorder - x); dy = min(pTree->cyState, pTree->cyItem - (2 * pTree->cyBorder)); ImageList_DrawEx(pTree->himlState, iState, hdc, x, y + max(pTree->cyItem - pTree->cyState, 0), dx, dy, clrBk, CLR_DEFAULT, ILD_NORMAL); x += pTree->cxState; } } if (pTree->hImageList) { UINT fStyle = 0; int i = (fSelectedIcon) ? ti.iSelectedImage : ti.iImage; if (ti.state & TVIS_CUT) { fStyle |= ILD_BLEND50; clrImage = ImageList_GetBkColor(pTree->hImageList); } dx = min(pTree->cxImage - MAGIC_INDENT, pTree->cxMax - pTree->cxBorder - x); dy = min(pTree->cyImage, pTree->cyItem - (2 * pTree->cyBorder)); ImageList_DrawEx(pTree->hImageList, i, hdc, x, y + (max(pTree->cyItem - pTree->cyImage, 0) / 2), dx, dy, clrBkImage, clrImage, fStyle | (ti.state & TVIS_OVERLAYMASK)); } } if (pTree->hImageList) { // even if not drawing image, draw text in right place x += pTree->cxImage; } // draw text lpstr = ti.pszText; cch = lstrlen(lpstr); if (!hItem->iWidth || (hItem->lpstr == LPSTR_TEXTCALLBACK)) { TV_ComputeItemWidth(pTree, hItem, hdc); //$BOLD } rc.left = x; rc.top = y + pTree->cyBorder; rc.right = min((x + hItem->iWidth), (pTree->cxMax - pTree->cxBorder)); rc.bottom-= pTree->cyBorder; // Draw the text, unless it's the one we are editing if (pTree->htiEdit != hItem || !IsWindow(pTree->hwndEdit) || !IsWindowVisible(pTree->hwndEdit)) { ExtTextOut(hdc, x + g_cxLabelMargin, y + ((pTree->cyItem - pTree->cyText) / 2) + g_cyBorder, etoFlags, &rc, lpstr, cch, NULL); // Draw the focus rect, if appropriate. if (pTree->fFocus && (fItemFocused) && !(pTree->ci.style & TVS_FULLROWSELECT) && !(flags & (TVDI_TRANSTEXT | TVDI_GRAYCTL)) #ifdef KEYBOARDCUES && !(CCGetUIState(&(pTree->ci)) & UISF_HIDEFOCUS) #endif ) DrawFocusRect(hdc, &rc); } SetBkColor(hdc, rgbOldBack); SetTextColor(hdc, rgbOldText); // Restore the original font. //$BOLD SelectObject(hdc, hFont); //$BOLD // Notice that we should have opaque'd the rest of the line above if no tree if (!(flags & TVDI_NOTREE)) { int dx, dy; if (pTree->hImageList) x -= pTree->cxImage; if (iState) x -= pTree->cxState; if (pTree->ci.style & TVS_HASLINES) { int i; x -= cxIndent; if (nmcd.iLevel-- || (pTree->ci.style & TVS_LINESATROOT)) { // HACK: Special case the first root // We will draw a "last" sibling button upside down if (nmcd.iLevel == -1 && hItem == hItem->hParent->hKids) { if (hItem->hNext) { i = 2; // "L" if (ti.cChildren && (pTree->ci.style & TVS_HASBUTTONS)) { i += 2; // "L+" if ((ti.state & (TVIS_EXPANDED | TVIS_EXPANDPARTIAL)) == TVIS_EXPANDED) i += 2; // "L-" } dx = min((int)cxIndent, pTree->cxMax - pTree->cxBorder - x); dy = pTree->cyItem - (2 * pTree->cyBorder); StretchBlt(hdc, x, y + pTree->cyItem, cxIndent, -pTree->cyItem, pTree->hdcBits , i * cxIndent, 0, dx, dy, SRCCOPY); i = -1; } else { // first root no siblings // if there's no other item, draw just the button if button mode, if (pTree->ci.style & TVS_HASBUTTONS) { if (ti.cChildren) { // hasbuttons, has lines, lines at root i = ((ti.state & (TVIS_EXPANDED | TVIS_EXPANDPARTIAL)) == TVIS_EXPANDED) ? 9 : 8; // ".--" : ".-+" } else { i = 7; // ".-" } } else { i = 3; // ".-" } } } else { i = (hItem->hNext) ? 1 : 2; // "|-" (rep) : "L" if (ti.cChildren && (pTree->ci.style & TVS_HASBUTTONS)) { i += 2; // "|-+" (rep) : "L+" if ((ti.state & (TVIS_EXPANDED | TVIS_EXPANDPARTIAL)) == TVIS_EXPANDED) i += 2; // "|--" (rep) : "L-" } } if (hItem->iIntegral > 1) TV_DrawKinDescender(pTree, hdc, x, y, hItem, ti.state); if (i != -1) { dx = min((int)cxIndent, pTree->cxMax - pTree->cxBorder - x); dy = pTree->cyItem - (2 * pTree->cyBorder); if ((dx > 0) && (dy > 0)) BitBlt(hdc, x, y, dx, dy, pTree->hdcBits , i * cxIndent, 0, SRCCOPY); } while ((--nmcd.iLevel >= 0) || ((pTree->ci.style & TVS_LINESATROOT) && nmcd.iLevel >= -1)) { hItem = hItem->hParent; x -= cxIndent; if (hItem->hNext) { dx = min((int)cxIndent, (pTree->cxMax - pTree->cxBorder - x)); dy = min(pTree->cyItem, pTree->cyWnd - pTree->cyBorder - y); if ((dx > 0) && (dy > 0)) BitBlt(hdc, x, y, dx, dy, pTree->hdcBits, 0, 0, SRCCOPY); TV_DrawDescender(pTree, hdc, x, y, hItemSave); } } } } else { // no lines if ((pTree->ci.style & TVS_HASBUTTONS) && (nmcd.iLevel || pTree->ci.style & TVS_LINESATROOT) && ti.cChildren) { int i = (ti.state & TVIS_EXPANDED) ? cxIndent : 0; x -= cxIndent; dx = min((int)cxIndent, pTree->cxMax - pTree->cxBorder - x); dy = min(pTree->cyItem, pTree->cyWnd - pTree->cyBorder - y); if ((dx > 0) && (dy > 0)) BitBlt(hdc, x, y, dx, dy, pTree->hdcBits, i, 0, SRCCOPY); } } } if (dwRet & CDRF_NOTIFYPOSTPAINT) { nmcd.nmcd.dwItemSpec = (DWORD_PTR)hItemSave; CICustomDrawNotify(&pTree->ci, CDDS_ITEMPOSTPAINT, &nmcd.nmcd); } } #define INSERTMARKSIZE 6 BOOL TV_GetInsertMarkRect(PTREE pTree, LPRECT prc) { ASSERT(pTree); if(pTree->htiInsert && TV_GetItemRect(pTree, pTree->htiInsert, prc, TRUE)) { if (pTree->fInsertAfter) prc->top = prc->bottom; else prc->bottom = prc->top; prc->top -= INSERTMARKSIZE/2; prc->bottom += INSERTMARKSIZE/2 + 1; prc->right = pTree->cxWnd - INSERTMARKSIZE; // should always go all the way to right with pad. prc->left -= pTree->cxImage; return TRUE; } return FALSE; } // this is implemented in toolbar.c, but we should be able to use // as well as long as we always set fHorizMode to FALSE void PASCAL DrawInsertMark(HDC hdc, LPRECT prc, BOOL fHorizMode, COLORREF clr); __inline COLORREF TV_GetInsertMarkColor(PTREE pTree) { if (pTree->clrim == CLR_DEFAULT) return g_clrWindowText; else return pTree->clrim; } void NEAR TV_DrawTree(PTREE pTree, HDC hdc, BOOL fErase, LPRECT lprc) { int x; int iStart, iCnt; UINT uFlags; RECT rc; NMCUSTOMDRAW nmcd; if (!pTree->fRedraw) return; if (pTree->ci.style & TVS_CHECKBOXES) if (!pTree->himlState) TV_InitCheckBoxes(pTree); x = -pTree->xPos; TV_GetBackgroundBrush(pTree, hdc); rc = *lprc; #ifdef MAINWIN if (lprc->top <= 0) { /* fix microsoft BUG */ iStart = 0; } else { iStart = lprc->top / pTree->cyItem; } #else iStart = lprc->top / pTree->cyItem; #endif if (pTree->cItems && pTree->hTop) { ASSERT(ITEM_VISIBLE(pTree->hTop)); iCnt = pTree->cShowing - pTree->hTop->iShownIndex; } else { iCnt = 0; // Nothing to draw } nmcd.hdc = hdc; /// not implemented yet //if (ptb->ci.hwnd == GetFocus()) //nmcd.uItemState = CDIS_FOCUS; //else nmcd.uItemState = 0; nmcd.lItemlParam = 0; nmcd.rc = rc; pTree->ci.dwCustom = CICustomDrawNotify(&pTree->ci, CDDS_PREPAINT, &nmcd); if (!(pTree->ci.dwCustom & CDRF_SKIPDEFAULT)) { if (iStart < iCnt) { HTREEITEM hItem; HFONT hOldFont; RECT rcT; int y = 0; for (hItem = pTree->hTop; hItem; ) { if (iStart > hItem->iIntegral) { iStart -= hItem->iIntegral; y += hItem->iIntegral * pTree->cyItem; hItem = TV_GetNextVisItem(hItem); } else break; } hOldFont = pTree->hFont ? SelectObject(hdc, pTree->hFont) : NULL; // TVDI_* for all items uFlags = (pTree->ci.style & WS_DISABLED) ? TVDI_GRAYCTL : 0; if (fErase) uFlags |= TVDI_ERASE; // loop from the first visible item until either all visible items are // drawn or there are no more items to draw for ( ; hItem && y < lprc->bottom; hItem = TV_GetNextVisItem(hItem)) { TV_DrawItem(pTree, hItem, hdc, x, y, uFlags); y += pTree->cyItem * hItem->iIntegral; } // // handle drawing the InsertMark next to this item. // if(TV_GetInsertMarkRect(pTree, &rcT)) DrawInsertMark(hdc, &rcT, FALSE, TV_GetInsertMarkColor(pTree)); if (hOldFont) SelectObject(hdc, hOldFont); rc.top = y; } if (fErase) // Opaque out everything we have not drawn explicitly FillRect(hdc, &rc, pTree->hbrBk); // notify parent afterwards if they want us to if (pTree->ci.dwCustom & CDRF_NOTIFYPOSTPAINT) { CICustomDrawNotify(&pTree->ci, CDDS_POSTPAINT, &nmcd); } } } // ---------------------------------------------------------------------------- // // Set up for paint, call DrawTree, and clean up after paint. // // ---------------------------------------------------------------------------- void NEAR TV_Paint(PTREE pTree, HDC hdc) { PAINTSTRUCT ps; if (hdc) { // hdc != 0 indicates a subclassed paint -- use the hdc passed in SetRect(&ps.rcPaint, 0, 0, pTree->cxWnd, pTree->cyWnd); TV_DrawTree(pTree, hdc, TRUE, &ps.rcPaint); } else { BeginPaint(pTree->ci.hwnd, &ps); TV_DrawTree(pTree, ps.hdc, ps.fErase, &ps.rcPaint); EndPaint(pTree->ci.hwnd, &ps); } } // ---------------------------------------------------------------------------- // Create an imagelist to be used for dragging. // // 1) create mask and image bitmap matching the select bounds size // 2) draw the text to both bitmaps (in black for now) // 3) create an imagelist with these bitmaps // 4) make a dithered copy of the image onto the new imagelist // ---------------------------------------------------------------------------- HIMAGELIST NEAR TV_CreateDragImage(PTREE pTree, HTREEITEM hItem) { HDC hdcMem = NULL; HBITMAP hbmImage = NULL; HBITMAP hbmMask = NULL; HBITMAP hbmOld; HIMAGELIST himl = NULL; BOOL bMirroredWnd = (pTree->ci.dwExStyle&RTL_MIRRORED_WINDOW); int dx, dy; int iSrc; TVITEMEX ti; if (!pTree->hImageList) return NULL; if (hItem == NULL) hItem = pTree->htiDrag; if (hItem == NULL) return NULL; // BUGBUG??? we know it's already been drawn, so is iWidth valid??? dx = hItem->iWidth + pTree->cxImage; dy = pTree->cyItem; if (!(hdcMem = CreateCompatibleDC(NULL))) goto CDI_Exit; if (!(hbmImage = CreateColorBitmap(dx, dy))) goto CDI_Exit; if (!(hbmMask = CreateMonoBitmap(dx, dy))) goto CDI_Exit; // // Mirror the memory DC so that the transition from // mirrored(memDC)->non-mirrored(imagelist DCs)->mirrored(screenDC) // is consistent. [samera] // if (bMirroredWnd) { SET_DC_RTL_MIRRORED(hdcMem); } // prepare for drawing the item if (pTree->hFont) SelectObject(hdcMem, pTree->hFont); SetBkMode(hdcMem, TRANSPARENT); /* ** draw the text to both bitmaps */ hbmOld = SelectObject(hdcMem, hbmImage); // fill image with black for transparency PatBlt(hdcMem, 0, 0, dx, dy, BLACKNESS); TV_DrawItem(pTree, hItem, hdcMem, 0, 0, TVDI_NOIMAGE | TVDI_NOTREE | TVDI_TRANSTEXT); // // If the header is RTL mirrored, then // mirror the Memory DC, so that when copying back // we don't get any image-flipping. [samera] // if (bMirroredWnd) MirrorBitmapInDC(hdcMem, hbmImage); SelectObject(hdcMem, hbmMask); // fill mask with white for transparency PatBlt(hdcMem, 0, 0, dx, dy, WHITENESS); TV_DrawItem(pTree, hItem, hdcMem, 0, 0, TVDI_NOIMAGE | TVDI_NOTREE | TVDI_TRANSTEXT); // // If the header is RTL mirrored, then // mirror the Memory DC, so that when copying back // we don't get any image-flipping. [samera] // if (bMirroredWnd) MirrorBitmapInDC(hdcMem, hbmMask); // unselect objects that we used SelectObject(hdcMem, hbmOld); SelectObject(hdcMem, g_hfontSystem); /* ** make an image list that for now only has the text */ // // BUGBUG: To fix a pri-1 M7 bug, we create a shared image list. // if (!(himl = ImageList_Create(dx, dy, ILC_MASK, 1, 0))) goto CDI_Exit; ImageList_SetBkColor(himl, CLR_NONE); ImageList_Add(himl, hbmImage, hbmMask); /* ** make a dithered copy of the image part onto our bitmaps ** (need both bitmap and mask to be dithered) */ TV_GetItem(pTree, hItem, TVIF_IMAGE, &ti); iSrc = ti.iImage; ImageList_CopyDitherImage(himl, 0, 0, (pTree->cyItem - pTree->cyImage) / 2, pTree->hImageList, iSrc, ((pTree->ci.dwExStyle & dwExStyleRTLMirrorWnd) ? ILD_MIRROR : 0L) | (hItem->state & TVIS_OVERLAYMASK)); CDI_Exit: if (hdcMem) DeleteObject(hdcMem); if (hbmImage) DeleteObject(hbmImage); if (hbmMask) DeleteObject(hbmMask); return himl; } #define COLORKEY RGB(0xF4, 0x0, 0x0) LRESULT TV_GenerateDragImage(PTREE pTree, SHDRAGIMAGE* pshdi) { LRESULT lRet = 0; HBITMAP hbmpOld = NULL; HTREEITEM hItem = pTree->htiDrag; RECT rc; HDC hdcDragImage; if (hItem == NULL) return FALSE; hdcDragImage = CreateCompatibleDC(NULL); if (!hdcDragImage) return 0; // After this rc contains the bounds of all the items in Client Coordinates. // // Mirror the the DC, if the listview is mirrored. // if (pTree->ci.dwExStyle & RTL_MIRRORED_WINDOW) { SET_DC_RTL_MIRRORED(hdcDragImage); } TV_GetItemRect(pTree, hItem, &rc, TRUE); // Subtract off the image... rc.left -= pTree->cxImage; pshdi->sizeDragImage.cx = RECTWIDTH(rc); pshdi->sizeDragImage.cy = RECTHEIGHT(rc); pshdi->hbmpDragImage = CreateBitmap( pshdi->sizeDragImage.cx, pshdi->sizeDragImage.cy, GetDeviceCaps(hdcDragImage, PLANES), GetDeviceCaps(hdcDragImage, BITSPIXEL), NULL); if (pshdi->hbmpDragImage) { COLORREF clrBkSave; RECT rcImage = {0, 0, pshdi->sizeDragImage.cx, pshdi->sizeDragImage.cy}; hbmpOld = SelectObject(hdcDragImage, pshdi->hbmpDragImage); pshdi->crColorKey = COLORKEY; FillRectClr(hdcDragImage, &rcImage, pshdi->crColorKey); // Calculate the offset... The cursor should be in the bitmap rect. if (pTree->ci.dwExStyle & RTL_MIRRORED_WINDOW) pshdi->ptOffset.x = rc.right - pTree->ptCapture.x; else pshdi->ptOffset.x = pTree->ptCapture.x - rc.left; pshdi->ptOffset.y = pTree->ptCapture.y - rc.top; clrBkSave = pTree->clrBk; pTree->clrBk = COLORKEY; TV_DrawItem(pTree, hItem, hdcDragImage, 0, 0, TVDI_NOTREE | TVDI_TRANSTEXT | TVDI_FORCEIMAGE | TVDI_NOBK); pTree->clrBk = clrBkSave; SelectObject(hdcDragImage, hbmpOld); DeleteDC(hdcDragImage); // We're passing back the created HBMP. return 1; } return lRet; }