windows-nt/Source/XPSP1/NT/shell/comctl32/v5/tvpaint.c

1333 lines
42 KiB
C
Raw Normal View History

2020-09-26 03:20:57 -05:00
#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;
}