1333 lines
42 KiB
C
1333 lines
42 KiB
C
#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;
|
|
}
|