mirror of https://github.com/tongzx/nt5src
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
4581 lines
144 KiB
4581 lines
144 KiB
// large icon view stuff
|
|
|
|
#include "ctlspriv.h"
|
|
#include "listview.h"
|
|
|
|
#if defined(FE_IME) || !defined(WINNT)
|
|
static TCHAR const szIMECompPos[]=TEXT("IMECompPos");
|
|
#endif
|
|
|
|
__inline int ICONCXLABEL(LV *plv, LISTITEM *pitem)
|
|
{
|
|
if (plv->ci.style & LVS_NOLABELWRAP) {
|
|
ASSERT(pitem->cxSingleLabel == pitem->cxMultiLabel);
|
|
}
|
|
return pitem->cxMultiLabel;
|
|
}
|
|
|
|
int LV_GetNewColWidth(LV* plv, int iFirst, int iLast);
|
|
void LV_AdjustViewRectOnMove(LV* plv, LISTITEM *pitem, int x, int y);
|
|
void ListView_RecalcRegion(LV *plv, BOOL fForce, BOOL fRedraw);
|
|
void ListView_ArrangeOrSnapToGrid(LV *plv);
|
|
extern BOOL g_fSlowMachine;
|
|
|
|
void _GetCurrentItemSize(LV* plv, int * pcx, int *pcy)
|
|
{
|
|
if (ListView_IsSmallView(plv))
|
|
{
|
|
*pcx = plv->cxItem;
|
|
*pcy = plv->cyItem;
|
|
}
|
|
else if (ListView_IsTileView(plv))
|
|
{
|
|
*pcx = plv->sizeTile.cx;
|
|
*pcy = plv->sizeTile.cy;
|
|
}
|
|
else
|
|
{
|
|
*pcx = plv->cxIconSpacing;
|
|
*pcy = plv->cyIconSpacing;
|
|
}
|
|
}
|
|
|
|
BOOL ListView_IDrawItem(PLVDRAWITEM plvdi)
|
|
{
|
|
RECT rcIcon;
|
|
RECT rcLabel;
|
|
RECT rcBounds;
|
|
RECT rcBiasedBounds;
|
|
RECT rcT;
|
|
TCHAR ach[CCHLABELMAX];
|
|
LV_ITEM item;
|
|
int i = (int) plvdi->nmcd.nmcd.dwItemSpec;
|
|
LV* plv = plvdi->plv;
|
|
LISTITEM* pitem;
|
|
BOOL fUnfolded;
|
|
|
|
if (ListView_IsOwnerData(plv))
|
|
{
|
|
LISTITEM litem;
|
|
// moved here to reduce call backs in OWNERDATA case
|
|
item.iItem = i;
|
|
item.iSubItem = 0;
|
|
item.mask = LVIF_TEXT | LVIF_STATE | LVIF_IMAGE;
|
|
item.stateMask = LVIS_ALL;
|
|
item.pszText = ach;
|
|
item.cchTextMax = ARRAYSIZE(ach);
|
|
ListView_OnGetItem(plv, &item);
|
|
|
|
litem.pszText = item.pszText;
|
|
ListView_GetRectsOwnerData(plv, i, &rcIcon, &rcLabel, &rcBounds, NULL, &litem);
|
|
pitem = NULL;
|
|
}
|
|
else
|
|
{
|
|
pitem = ListView_GetItemPtr(plv, i);
|
|
// NOTE this will do a GetItem LVIF_TEXT iff needed
|
|
ListView_GetRects(plv, i, QUERY_DEFAULT, &rcIcon, &rcLabel, &rcBounds, NULL);
|
|
}
|
|
|
|
fUnfolded = FALSE;
|
|
if ( (plvdi->flags & LVDI_UNFOLDED) || ListView_IsItemUnfolded(plv, i))
|
|
{
|
|
ListView_UnfoldRects(plv, i, &rcIcon, &rcLabel, &rcBounds, NULL );
|
|
fUnfolded = TRUE;
|
|
}
|
|
|
|
|
|
rcBiasedBounds = rcBounds;
|
|
if (ListView_IsBorderSelect(plv))
|
|
InflateRect(&rcBiasedBounds, BORDERSELECT_THICKNESS, BORDERSELECT_THICKNESS);
|
|
|
|
if (!plvdi->prcClip || IntersectRect(&rcT, &rcBiasedBounds, plvdi->prcClip))
|
|
{
|
|
RECT rcIconReal;
|
|
UINT fText;
|
|
COLORREF clrIconBk = plv->clrBk;
|
|
if (!ListView_IsOwnerData(plv))
|
|
{
|
|
item.iItem = i;
|
|
item.iSubItem = 0;
|
|
item.mask = LVIF_TEXT | LVIF_STATE | LVIF_IMAGE;
|
|
item.stateMask = LVIS_ALL;
|
|
item.pszText = ach;
|
|
item.cchTextMax = ARRAYSIZE(ach);
|
|
ListView_OnGetItem(plv, &item);
|
|
|
|
// Make sure the listview hasn't been altered during
|
|
// the callback to get the item info
|
|
|
|
if (pitem != ListView_GetItemPtr(plv, i))
|
|
return FALSE;
|
|
}
|
|
|
|
if (plvdi->lpptOrg)
|
|
{
|
|
OffsetRect(&rcIcon, plvdi->lpptOrg->x - rcBounds.left,
|
|
plvdi->lpptOrg->y - rcBounds.top);
|
|
OffsetRect(&rcLabel, plvdi->lpptOrg->x - rcBounds.left,
|
|
plvdi->lpptOrg->y - rcBounds.top);
|
|
OffsetRect(&rcBounds, plvdi->lpptOrg->x - rcBounds.left,
|
|
plvdi->lpptOrg->y - rcBounds.top);
|
|
}
|
|
|
|
fText = ListView_GetTextSelectionFlags(plv, &item, plvdi->flags);
|
|
|
|
if (ListView_IsIconView(plv))
|
|
{
|
|
rcIcon.left += ListView_GetIconBufferX(plv);
|
|
rcIcon.top += ListView_GetIconBufferY(plv);
|
|
|
|
fText = ListView_DrawImageEx(plv, &item, plvdi->nmcd.nmcd.hdc,
|
|
rcIcon.left, rcIcon.top, clrIconBk, plvdi->flags, -1);
|
|
|
|
SetRect(&rcIconReal, rcIcon.left, rcIcon.top, rcIcon.left + plv->cxIcon, rcIcon.top + plv->cyIcon);
|
|
|
|
|
|
if (ListView_IsBorderSelect(plv))
|
|
{
|
|
int cp = 1;
|
|
COLORREF clrOutline = plv->clrOutline;
|
|
if (fText & SHDT_SELECTED || fText & SHDT_SELECTNOFOCUS)
|
|
{
|
|
clrOutline = (fText & SHDT_SELECTED)?g_clrHighlight:g_clrBtnFace;
|
|
cp = BORDERSELECT_THICKNESS;
|
|
InflateRect(&rcIconReal, cp, cp);
|
|
}
|
|
SHOutlineRectThickness(plvdi->nmcd.nmcd.hdc, &rcIconReal, clrOutline, g_clrBtnFace, cp);
|
|
}
|
|
|
|
// If linebreaking needs to happen, then use SHDT_DRAWTEXT.
|
|
// Otherwise, use our (hopefully faster) internal SHDT_ELLIPSES
|
|
if (rcLabel.bottom - rcLabel.top > plv->cyLabelChar)
|
|
fText |= SHDT_DRAWTEXT;
|
|
else
|
|
fText |= SHDT_ELLIPSES;
|
|
|
|
// We only use DT_NOFULLWIDTHCHARBREAK on Korean(949) Memphis and NT5
|
|
if (949 == g_uiACP)
|
|
fText |= SHDT_NODBCSBREAK;
|
|
|
|
}
|
|
else
|
|
{
|
|
SetRect(&rcIconReal, rcIcon.left, rcIcon.top, rcIcon.left + plv->cxIcon, rcIcon.top + plv->cyIcon);
|
|
fText = ListView_DrawImageEx(plv, &item, plvdi->nmcd.nmcd.hdc,
|
|
rcIcon.left, rcIcon.top, clrIconBk, plvdi->flags, -1);
|
|
}
|
|
|
|
if (ListView_HideLabels(plv) &&
|
|
(plvdi->flags & LVDI_FOCUS) &&
|
|
(item.state & LVIS_FOCUSED) &&
|
|
!(CCGetUIState(&(plvdi->plv->ci)) & UISF_HIDEFOCUS))
|
|
{
|
|
DrawFocusRect(plvdi->nmcd.nmcd.hdc, &rcIconReal);
|
|
}
|
|
|
|
// Don't draw label if it's being edited...
|
|
// or if it is hidden due to the HideLabels style.
|
|
//
|
|
if ((plv->iEdit != i) && !ListView_HideLabels(plv))
|
|
{
|
|
HRESULT hr = E_FAIL;
|
|
COLORREF clrTextBk = plvdi->nmcd.clrTextBk;
|
|
// If multiline label, then we need to use DrawText
|
|
if (rcLabel.bottom - rcLabel.top > plv->cyLabelChar)
|
|
{
|
|
fText |= SHDT_DRAWTEXT;
|
|
|
|
// If the text is folded, we need to clip and add ellipses
|
|
|
|
if (!fUnfolded)
|
|
fText |= SHDT_CLIPPED | SHDT_DTELLIPSIS;
|
|
|
|
if ( ListView_IsOwnerData(plv) )
|
|
{
|
|
// If owner data, we have no z-order and if long names they will over lap each
|
|
// other, better to truncate for now...
|
|
if (ListView_IsSmallView(plv))
|
|
fText |= SHDT_ELLIPSES;
|
|
}
|
|
|
|
}
|
|
else
|
|
fText |= SHDT_ELLIPSES;
|
|
|
|
if (plvdi->flags & LVDI_TRANSTEXT)
|
|
fText |= SHDT_TRANSPARENT;
|
|
|
|
if ((fText & SHDT_SELECTED) && (plvdi->flags & LVDI_HOTSELECTED))
|
|
fText |= SHDT_HOTSELECTED;
|
|
|
|
if (item.pszText && (*item.pszText))
|
|
{
|
|
if (plv->pImgCtx || ListView_IsWatermarked(plv))
|
|
clrTextBk = CLR_NONE;
|
|
|
|
if(plv->dwExStyle & WS_EX_RTLREADING)
|
|
fText |= SHDT_RTLREADING;
|
|
|
|
if ((plv->clrBk == CLR_NONE) &&
|
|
!(fText & (SHDT_SELECTED | SHDT_HOTSELECTED | SHDT_SELECTNOFOCUS)) && // And we're not selected
|
|
!(plv->flags & LVF_DRAGIMAGE) && // And this is not during dragdrop.
|
|
!(plv->exStyle & LVS_EX_REGIONAL) && // No need for regional.
|
|
plv->fListviewShadowText) // and enabled
|
|
{
|
|
fText |= SHDT_SHADOWTEXT;
|
|
}
|
|
|
|
SHDrawText(plvdi->nmcd.nmcd.hdc, item.pszText, &rcLabel, LVCFMT_LEFT, fText,
|
|
plv->cyLabelChar, plv->cxEllipses,
|
|
plvdi->nmcd.clrText, clrTextBk);
|
|
|
|
if ((plvdi->flags & LVDI_FOCUS) &&
|
|
(item.state & LVIS_FOCUSED) &&
|
|
!(CCGetUIState(&(plvdi->plv->ci)) & UISF_HIDEFOCUS))
|
|
{
|
|
rcLabel.top -= g_cyCompensateInternalLeading;
|
|
DrawFocusRect(plvdi->nmcd.nmcd.hdc, &rcLabel);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
void ListView_RefoldLabelRect(LV* plv, RECT *prcLabel, LISTITEM *pitem)
|
|
{
|
|
int bottom = pitem->cyUnfoldedLabel;
|
|
bottom = min(bottom, pitem->cyFoldedLabel);
|
|
bottom = min(bottom, CLIP_HEIGHT);
|
|
prcLabel->bottom = prcLabel->top + bottom;
|
|
}
|
|
|
|
|
|
ULONGLONG _GetDistanceToRect(LV* plv, RECT *prcSlot, int x, int y)
|
|
{
|
|
int xSlotCenter = prcSlot->left + RECTWIDTH(*prcSlot) / 2;
|
|
int ySlotCenter = prcSlot->top + RECTHEIGHT(*prcSlot) / 2;
|
|
LONGLONG dx = (LONGLONG)(x - xSlotCenter);
|
|
LONGLONG dy = (LONGLONG)(y - ySlotCenter);
|
|
|
|
return (ULONGLONG)(dx * dx) + (ULONGLONG)(dy * dy);
|
|
}
|
|
|
|
|
|
// prcSlot returned in Listview Coordinates
|
|
void ListView_CalcItemSlotAndRect(LV* plv, LISTITEM* pitem, int* piSlot, RECT* prcSlot)
|
|
{
|
|
int cxScreen, cyScreen, cSlots, iHit;
|
|
POINT pt;
|
|
|
|
// Determine which slot this item is in by calculating the hit slot for the
|
|
// item's x,y position.
|
|
|
|
short iWorkArea = (plv->nWorkAreas > 0) ? pitem->iWorkArea : -1;
|
|
|
|
cSlots = ListView_GetSlotCountEx(plv, TRUE, iWorkArea, &cxScreen, &cyScreen);
|
|
|
|
// Adjust point by current workarea location.
|
|
if (iWorkArea >= 0)
|
|
{
|
|
pt.x = pitem->pt.x - plv->prcWorkAreas[iWorkArea].left;
|
|
pt.y = pitem->pt.y - plv->prcWorkAreas[iWorkArea].top;
|
|
}
|
|
else
|
|
{
|
|
pt = pitem->pt;
|
|
}
|
|
|
|
iHit = ListView_CalcHitSlot(plv, pt, cSlots, cxScreen, cyScreen);
|
|
|
|
if (piSlot)
|
|
*piSlot = iHit;
|
|
|
|
if (prcSlot)
|
|
ListView_CalcSlotRect(plv, pitem, iHit, cSlots, FALSE, cxScreen, cyScreen, prcSlot);
|
|
}
|
|
|
|
int ListView_FindItemInSlot(LV* plv, short iWorkArea, int iSlotToFind)
|
|
{
|
|
int iItemFound = -1;
|
|
int cItems;
|
|
|
|
cItems = ListView_Count(plv);
|
|
|
|
if (cItems == 0 || !ListView_IsRearrangeableView(plv) || plv->hdpaZOrder == NULL || ListView_IsOwnerData( plv ))
|
|
{
|
|
// nothing to check
|
|
}
|
|
else
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < cItems; i++)
|
|
{
|
|
LISTITEM* pitem = ListView_GetItemPtr(plv, i);
|
|
// Only consider items in this workarea.
|
|
if (pitem && ((iWorkArea == -1) || (pitem->iWorkArea == iWorkArea)))
|
|
{
|
|
int iSlot;
|
|
ListView_CalcItemSlotAndRect(plv, pitem, &iSlot, NULL);
|
|
|
|
if (iSlot == iSlotToFind)
|
|
{
|
|
iItemFound = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return iItemFound;
|
|
}
|
|
|
|
BOOL ListView_OnInsertMarkHitTest(LV* plv, int x, int y, LPLVINSERTMARK plvim)
|
|
{
|
|
POINT pt = {x + plv->ptOrigin.x, y + plv->ptOrigin.y};
|
|
short iWorkArea = -1;
|
|
int cItems;
|
|
|
|
if (plvim->cbSize != sizeof(LVINSERTMARK))
|
|
return FALSE;
|
|
|
|
if (plv->nWorkAreas)
|
|
{
|
|
ListView_FindWorkArea(plv, pt, &iWorkArea);
|
|
}
|
|
|
|
cItems = ListView_Count(plv);
|
|
|
|
if (cItems == 0 || !ListView_IsRearrangeableView(plv) || plv->hdpaZOrder == NULL || ListView_IsOwnerData( plv ))
|
|
{
|
|
plvim->dwFlags = 0;
|
|
plvim->iItem = -1;
|
|
}
|
|
else
|
|
{
|
|
ULONGLONG uClosestDistance = (ULONGLONG)-1; // MAX INT
|
|
LISTITEM* pClosestItem = NULL;
|
|
int iClosestItem = -1;
|
|
int iClosestSlot = -1;
|
|
RECT rcClosestSlot;
|
|
int i;
|
|
|
|
for (i = 0; i < cItems; i++)
|
|
{
|
|
// Only consider items in this workarea.
|
|
LISTITEM* pitem = ListView_GetItemPtr(plv, i);
|
|
if (pitem && ((iWorkArea == -1) || (pitem->iWorkArea == iWorkArea)))
|
|
{
|
|
int iSlot;
|
|
RECT rcSlot;
|
|
ListView_CalcItemSlotAndRect(plv, pitem, &iSlot, &rcSlot);
|
|
|
|
if (PtInRect(&rcSlot, pt))
|
|
{
|
|
// Hit it. This is as close as we can get.
|
|
pClosestItem = pitem;
|
|
iClosestItem = i;
|
|
iClosestSlot = iSlot;
|
|
rcClosestSlot = rcSlot;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
// Keep track of closest item in this workarea, in case none are hit.
|
|
ULONGLONG uDistance = _GetDistanceToRect(plv, &rcSlot, pt.x, pt.y);
|
|
if (uDistance < uClosestDistance)
|
|
{
|
|
pClosestItem = pitem;
|
|
iClosestItem = i;
|
|
iClosestSlot = iSlot;
|
|
rcClosestSlot = rcSlot;
|
|
uClosestDistance = uDistance;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (pClosestItem)
|
|
{
|
|
BOOL fVert = !((plv->ci.style & LVS_ALIGNMASK) == LVS_ALIGNTOP); // what about lvs_alignbottom?
|
|
int iDragSlot = -1;
|
|
|
|
// For the drag source case, we need the drag slot to compare against
|
|
if (-1 != plv->iDrag)
|
|
{
|
|
LISTITEM* pitemDrag = ListView_GetItemPtr(plv, plv->iDrag);
|
|
if (pitemDrag)
|
|
ListView_CalcItemSlotAndRect(plv, pitemDrag, &iDragSlot, NULL);
|
|
}
|
|
|
|
// Now that we have the item, calculate before/after
|
|
if (fVert)
|
|
plvim->dwFlags = (pt.y > (rcClosestSlot.top + (RECTHEIGHT(rcClosestSlot))/2)) ? LVIM_AFTER : 0;
|
|
else
|
|
plvim->dwFlags = (pt.x > (rcClosestSlot.left + (RECTWIDTH(rcClosestSlot))/2)) ? LVIM_AFTER : 0;
|
|
|
|
plvim->iItem = iClosestItem;
|
|
|
|
// If this is the drag source (or right next to it) then ignore the hit.
|
|
if (-1 != iDragSlot &&
|
|
((iDragSlot==iClosestSlot) ||
|
|
(iDragSlot==(iClosestSlot-1) && !(plvim->dwFlags & LVIM_AFTER)) ||
|
|
(iDragSlot==(iClosestSlot+1) && (plvim->dwFlags & LVIM_AFTER))))
|
|
{
|
|
plvim->dwFlags = 0;
|
|
plvim->iItem = -1;
|
|
}
|
|
else if ((plv->ci.style & LVS_AUTOARRANGE) && !(plv->exStyle & LVS_EX_SINGLEROW) && !fVert) // auto arrange needs to tweak some beginning/end-of-line cases
|
|
{
|
|
RECT rcViewWorkArea;
|
|
if (-1 != iWorkArea)
|
|
{
|
|
rcViewWorkArea = plv->prcWorkAreas[iWorkArea];
|
|
}
|
|
else
|
|
{
|
|
if (plv->rcView.left == RECOMPUTE)
|
|
ListView_Recompute(plv);
|
|
rcViewWorkArea = plv->rcView;
|
|
}
|
|
|
|
if ((-1 != iDragSlot) && (iClosestSlot > iDragSlot) && !(plvim->dwFlags & LVIM_AFTER))
|
|
{
|
|
// We're after our drag source, if we're at the beginning of a line
|
|
// then the insert mark is actually at the end of the previous line.
|
|
if (rcClosestSlot.left - RECTWIDTH(rcClosestSlot)/2 < rcViewWorkArea.left)
|
|
{
|
|
int iItemPrev = ListView_FindItemInSlot(plv, iWorkArea, iClosestSlot-1);
|
|
if (-1 != iItemPrev)
|
|
{
|
|
plvim->dwFlags = LVIM_AFTER;
|
|
plvim->iItem = iItemPrev;
|
|
}
|
|
}
|
|
}
|
|
else if (((-1 == iDragSlot) || (iClosestSlot < iDragSlot)) && (plvim->dwFlags & LVIM_AFTER))
|
|
{
|
|
// We're before our drag source (or there is no drag source), if we're at end of a line
|
|
// then the insert mark is actually at the beginning of the next line.
|
|
if (rcClosestSlot.right + RECTWIDTH(rcClosestSlot)/2 > rcViewWorkArea.right)
|
|
{
|
|
int iItemNext = ListView_FindItemInSlot(plv, iWorkArea, iClosestSlot+1);
|
|
if (-1 != iItemNext)
|
|
{
|
|
plvim->dwFlags = 0;
|
|
plvim->iItem = iItemNext;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// No insert mark.
|
|
plvim->dwFlags = 0;
|
|
plvim->iItem = -1;
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
int ListView_IItemHitTest(LV* plv, int x, int y, UINT* pflags, int *piSubItem)
|
|
{
|
|
int iHit;
|
|
UINT flags;
|
|
POINT pt;
|
|
RECT rcLabel = {0};
|
|
RECT rcIcon = {0};
|
|
RECT rcState = {0};
|
|
|
|
if (piSubItem)
|
|
*piSubItem = 0;
|
|
|
|
// Map window-relative coordinates to view-relative coords...
|
|
//
|
|
pt.x = x + plv->ptOrigin.x;
|
|
pt.y = y + plv->ptOrigin.y;
|
|
|
|
// If there are any uncomputed items, recompute them now.
|
|
//
|
|
if (plv->rcView.left == RECOMPUTE)
|
|
ListView_Recompute(plv);
|
|
|
|
flags = 0;
|
|
|
|
if (ListView_IsOwnerData( plv ))
|
|
{
|
|
int cSlots;
|
|
POINT ptWnd;
|
|
LISTITEM item;
|
|
int iWidth = 0, iHeight = 0;
|
|
|
|
cSlots = ListView_GetSlotCount( plv, TRUE, &iWidth, &iHeight );
|
|
iHit = ListView_CalcHitSlot( plv, pt, cSlots, iWidth, iHeight );
|
|
if (iHit < ListView_Count(plv))
|
|
{
|
|
ListView_IGetRectsOwnerData( plv, iHit, &rcIcon, &rcLabel, &item, FALSE );
|
|
ptWnd.x = x;
|
|
ptWnd.y = y;
|
|
if (PtInRect(&rcIcon, ptWnd))
|
|
{
|
|
flags = LVHT_ONITEMICON;
|
|
}
|
|
else if (PtInRect(&rcLabel, ptWnd) && !ListView_HideLabels(plv))
|
|
{
|
|
flags = LVHT_ONITEMLABEL;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (iHit = 0; (iHit < ListView_Count(plv)); iHit++)
|
|
{
|
|
LISTITEM* pitem = ListView_FastGetZItemPtr(plv, iHit);
|
|
POINT ptItem;
|
|
RECT rcBounds; // Only used if ListView_IsBorderSelect
|
|
|
|
ptItem.x = pitem->pt.x;
|
|
ptItem.y = pitem->pt.y;
|
|
|
|
rcIcon.top = ptItem.y - g_cyIconMargin;
|
|
|
|
rcLabel.top = ptItem.y + plv->cyIcon + g_cyLabelSpace;
|
|
rcLabel.bottom = rcLabel.top + pitem->cyUnfoldedLabel;
|
|
|
|
|
|
if ( !ListView_IsItemUnfoldedPtr(plv, pitem) )
|
|
ListView_RefoldLabelRect(plv, &rcLabel, pitem);
|
|
|
|
// Quick, easy rejection test...
|
|
//
|
|
if (pt.y < rcIcon.top || pt.y >= rcLabel.bottom)
|
|
continue;
|
|
|
|
rcIcon.left = ptItem.x - g_cxIconMargin;
|
|
rcIcon.right = ptItem.x + plv->cxIcon + g_cxIconMargin;
|
|
// We need to make sure there is no gap between the icon and label
|
|
rcIcon.bottom = rcLabel.top;
|
|
|
|
if (ListView_IsSimpleSelect(plv) &&
|
|
(ListView_IsIconView(plv) || ListView_IsTileView(plv)))
|
|
{
|
|
rcState.top = rcIcon.top;
|
|
rcState.right = rcIcon.right - ((RECTWIDTH(rcIcon) -plv->cxIcon) / 2);
|
|
rcState.left = rcState.right - plv->cxState;
|
|
rcState.bottom = rcState.top + plv->cyState;
|
|
}
|
|
else
|
|
{
|
|
rcState.bottom = ptItem.y + plv->cyIcon;
|
|
rcState.right = ptItem.x;
|
|
rcState.top = rcState.bottom - plv->cyState;
|
|
rcState.left = rcState.right - plv->cxState;
|
|
}
|
|
|
|
if (ListView_HideLabels(plv))
|
|
{
|
|
CopyRect(&rcBounds, &rcIcon);
|
|
}
|
|
else
|
|
{
|
|
rcLabel.left = ptItem.x + (plv->cxIcon / 2) - (ICONCXLABEL(plv, pitem) / 2);
|
|
rcLabel.right = rcLabel.left + ICONCXLABEL(plv, pitem);
|
|
}
|
|
|
|
|
|
if (plv->cxState && PtInRect(&rcState, pt))
|
|
{
|
|
flags = LVHT_ONITEMSTATEICON;
|
|
}
|
|
else if (PtInRect(&rcIcon, pt))
|
|
{
|
|
flags = LVHT_ONITEMICON;
|
|
|
|
if (pt.x < rcIcon.left + RECTWIDTH(rcIcon)/10)
|
|
flags |= LVHT_ONLEFTSIDEOFICON;
|
|
else if (pt.x >= rcIcon.right - RECTWIDTH(rcIcon)/10)
|
|
flags |= LVHT_ONRIGHTSIDEOFICON;
|
|
}
|
|
else if (PtInRect(&rcLabel, pt))
|
|
{
|
|
flags = LVHT_ONITEMLABEL;
|
|
}
|
|
else if (ListView_IsBorderSelect(plv) &&
|
|
(pitem->state & LVIS_SELECTED) &&
|
|
PtInRect(&rcBounds, pt))
|
|
{
|
|
flags = LVHT_ONITEMICON;
|
|
|
|
if (pt.x < rcBounds.left + RECTWIDTH(rcBounds)/10)
|
|
flags |= LVHT_ONLEFTSIDEOFICON;
|
|
else if (pt.x >= rcBounds.right - RECTWIDTH(rcBounds)/10)
|
|
flags |= LVHT_ONRIGHTSIDEOFICON;
|
|
}
|
|
|
|
if (flags)
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (flags == 0)
|
|
{
|
|
flags = LVHT_NOWHERE;
|
|
iHit = -1;
|
|
}
|
|
else
|
|
{
|
|
if (!ListView_IsOwnerData( plv ))
|
|
{
|
|
iHit = DPA_GetPtrIndex(plv->hdpa, ListView_FastGetZItemPtr(plv, iHit));
|
|
}
|
|
}
|
|
|
|
*pflags = flags;
|
|
return iHit;
|
|
}
|
|
|
|
// REARCHITECT raymondc
|
|
// need to pass HDC here isnce it's sometimes called from the paint loop
|
|
// This returns rects in Window Coordinates
|
|
void ListView_IGetRectsOwnerData( LV* plv,
|
|
int iItem,
|
|
RECT* prcIcon,
|
|
RECT* prcLabel,
|
|
LISTITEM* pitem,
|
|
BOOL fUsepitem )
|
|
{
|
|
int itemIconXLabel;
|
|
int cxIconMargin;
|
|
int cSlots;
|
|
|
|
// calculate x, y from iItem
|
|
cSlots = ListView_GetSlotCount( plv, TRUE, NULL, NULL );
|
|
pitem->iWorkArea = 0; // OwnerData doesn't support workareas
|
|
ListView_SetIconPos( plv, pitem, iItem, cSlots );
|
|
|
|
// calculate lable sizes from iItem
|
|
ListView_IRecomputeLabelSize( plv, pitem, iItem, NULL, fUsepitem);
|
|
|
|
if (plv->ci.style & LVS_NOLABELWRAP)
|
|
{
|
|
// use single label
|
|
itemIconXLabel = pitem->cxSingleLabel;
|
|
}
|
|
else
|
|
{
|
|
// use multilabel
|
|
itemIconXLabel = pitem->cxMultiLabel;
|
|
}
|
|
|
|
cxIconMargin = ListView_GetIconBufferX(plv);
|
|
|
|
prcIcon->left = pitem->pt.x - cxIconMargin - plv->ptOrigin.x;
|
|
prcIcon->right = prcIcon->left + plv->cxIcon + 2 * cxIconMargin;
|
|
prcIcon->top = pitem->pt.y - g_cyIconMargin - plv->ptOrigin.y;
|
|
prcIcon->bottom = prcIcon->top + plv->cyIcon + 2 * g_cyIconMargin;
|
|
|
|
prcLabel->left = pitem->pt.x + (plv->cxIcon / 2) - (itemIconXLabel / 2) - plv->ptOrigin.x;
|
|
prcLabel->right = prcLabel->left + itemIconXLabel;
|
|
prcLabel->top = pitem->pt.y + plv->cyIcon + g_cyLabelSpace - plv->ptOrigin.y;
|
|
prcLabel->bottom = prcLabel->top + pitem->cyUnfoldedLabel;
|
|
|
|
|
|
if ( !ListView_IsItemUnfolded(plv, iItem) )
|
|
ListView_RefoldLabelRect(plv, prcLabel, pitem);
|
|
}
|
|
|
|
|
|
// out:
|
|
// prcIcon icon bounds including icon margin area
|
|
// This returns rects in Window Coordinates
|
|
void ListView_IGetRects(LV* plv, LISTITEM* pitem, UINT fQueryLabelRects, RECT* prcIcon, RECT* prcLabel, LPRECT prcBounds)
|
|
{
|
|
int cxIconMargin;
|
|
|
|
ASSERT( !ListView_IsOwnerData( plv ) );
|
|
|
|
if (pitem->pt.x == RECOMPUTE)
|
|
{
|
|
ListView_Recompute(plv);
|
|
}
|
|
|
|
if (pitem->pt.x == RECOMPUTE)
|
|
{
|
|
RECT rcZero = {0};
|
|
*prcIcon = *prcLabel = rcZero;
|
|
return;
|
|
}
|
|
|
|
cxIconMargin = ListView_GetIconBufferX(plv);
|
|
|
|
prcIcon->left = pitem->pt.x - cxIconMargin - plv->ptOrigin.x;
|
|
prcIcon->right = prcIcon->left + plv->cxIcon + 2 * cxIconMargin;
|
|
prcIcon->top = pitem->pt.y - g_cyIconMargin - plv->ptOrigin.y;
|
|
prcIcon->bottom = prcIcon->top + plv->cyIcon + 2 * g_cyIconMargin;
|
|
|
|
prcLabel->left = pitem->pt.x + (plv->cxIcon / 2) - (ICONCXLABEL(plv, pitem) / 2) - plv->ptOrigin.x;
|
|
prcLabel->right = prcLabel->left + ICONCXLABEL(plv, pitem);
|
|
prcLabel->top = pitem->pt.y + plv->cyIcon + g_cyLabelSpace - plv->ptOrigin.y;
|
|
prcLabel->bottom = prcLabel->top + pitem->cyUnfoldedLabel;
|
|
|
|
if (IsQueryFolded(fQueryLabelRects) ||
|
|
(!ListView_IsItemUnfoldedPtr(plv, pitem) && !IsQueryUnfolded(fQueryLabelRects)))
|
|
{
|
|
ListView_RefoldLabelRect(plv, prcLabel, pitem);
|
|
}
|
|
}
|
|
|
|
// fWithoutScrollbars==FALSE means that we assume more items are on the screen than will fit, so we'll have a scrollbar.
|
|
int ListView_GetSlotCountEx(LV* plv, BOOL fWithoutScrollbars, int iWorkArea, int *piWidth, int *piHeight)
|
|
{
|
|
int cxScreen;
|
|
int cyScreen;
|
|
int dxItem;
|
|
int dyItem;
|
|
int iSlots;
|
|
int iSlotsX;
|
|
int iSlotsY;
|
|
|
|
// film strip mode
|
|
if (ListView_SingleRow(plv))
|
|
{
|
|
if(piWidth)
|
|
*piWidth = plv->sizeClient.cx;
|
|
if(piHeight)
|
|
*piHeight = plv->sizeClient.cy;
|
|
|
|
return MAXINT;
|
|
}
|
|
|
|
// Always assume we have a scrollbar when in group view,
|
|
// since our iTotalSlots calculation at the bottom will be wrong in this mode...
|
|
if (plv->fGroupView)
|
|
fWithoutScrollbars = FALSE;
|
|
|
|
// Always use the current client window size to determine
|
|
//
|
|
if ((iWorkArea >= 0 ) && (plv->nWorkAreas > 0))
|
|
{
|
|
ASSERT(iWorkArea < plv->nWorkAreas);
|
|
cxScreen = RECTWIDTH(plv->prcWorkAreas[iWorkArea]);
|
|
cyScreen = RECTHEIGHT(plv->prcWorkAreas[iWorkArea]);
|
|
}
|
|
else
|
|
{
|
|
if (plv->fGroupView)
|
|
{
|
|
cxScreen = plv->sizeClient.cx - plv->rcBorder.left - plv->rcBorder.right - plv->paddingRight - plv->paddingLeft;
|
|
cyScreen = plv->sizeClient.cy - plv->rcBorder.bottom - plv->rcBorder.top - plv->paddingBottom - plv->paddingTop;
|
|
}
|
|
else
|
|
{
|
|
RECT rcClientNoScrollBars;
|
|
ListView_GetClientRect(plv, &rcClientNoScrollBars, FALSE, NULL);
|
|
cxScreen = RECTWIDTH(rcClientNoScrollBars);
|
|
cyScreen = RECTHEIGHT(rcClientNoScrollBars);
|
|
|
|
if (ListView_IsIScrollView(plv) && !(plv->ci.style & LVS_NOSCROLL))
|
|
{
|
|
cxScreen = cxScreen - plv->rcViewMargin.left - plv->rcViewMargin.right;
|
|
cyScreen = cyScreen - plv->rcViewMargin.top - plv->rcViewMargin.bottom;
|
|
}
|
|
}
|
|
|
|
if (cxScreen < 0)
|
|
cxScreen = 0;
|
|
if (cyScreen < 0)
|
|
cyScreen = 0;
|
|
}
|
|
|
|
// If we're assuming the scrollbars are there, shrink width/height as appropriate
|
|
if (!fWithoutScrollbars && !(plv->ci.style & LVS_NOSCROLL))
|
|
{
|
|
switch (plv->ci.style & LVS_ALIGNMASK)
|
|
{
|
|
case LVS_ALIGNBOTTOM:
|
|
case LVS_ALIGNTOP:
|
|
cxScreen -= ListView_GetCxScrollbar(plv);
|
|
break;
|
|
|
|
case LVS_ALIGNRIGHT:
|
|
default:
|
|
case LVS_ALIGNLEFT:
|
|
cyScreen -= ListView_GetCyScrollbar(plv);
|
|
break;
|
|
}
|
|
}
|
|
|
|
_GetCurrentItemSize(plv, &dxItem, &dyItem);
|
|
|
|
if (!dxItem)
|
|
dxItem = 1;
|
|
if (!dyItem)
|
|
dyItem = 1;
|
|
|
|
iSlotsX = max(1, (cxScreen) / dxItem);
|
|
iSlotsY = max(1, (cyScreen) / dyItem);
|
|
|
|
// Lets see which direction the view states
|
|
switch (plv->ci.style & LVS_ALIGNMASK)
|
|
{
|
|
case LVS_ALIGNBOTTOM:
|
|
//The number of slots are the same as ALIGNTOP;
|
|
//So, intentional fall through...
|
|
case LVS_ALIGNTOP:
|
|
iSlots = iSlotsX;
|
|
break;
|
|
|
|
case LVS_ALIGNRIGHT:
|
|
// The number of slots are the same as ALIGNLEFT;
|
|
// So, intentional fall through...
|
|
default:
|
|
case LVS_ALIGNLEFT:
|
|
iSlots = iSlotsY;
|
|
break;
|
|
}
|
|
|
|
if(piWidth)
|
|
*piWidth = cxScreen;
|
|
if(piHeight)
|
|
*piHeight = cyScreen;
|
|
|
|
// if we don't have enough slots total on the screen, we're going to have
|
|
// a scrollbar, so recompute with the scrollbars on
|
|
if (fWithoutScrollbars)
|
|
{
|
|
int iTotalSlots = (iSlotsX * iSlotsY);
|
|
if (iTotalSlots < ListView_Count(plv))
|
|
{
|
|
iSlots = ListView_GetSlotCountEx(plv, FALSE, iWorkArea, piWidth, piHeight);
|
|
}
|
|
}
|
|
|
|
return iSlots;
|
|
}
|
|
|
|
int ListView_GetSlotCount(LV* plv, BOOL fWithoutScrollbars, int *piWidth, int *piHeight)
|
|
{
|
|
// Make sure this function does exactly the same thing as when
|
|
// we had no workareas
|
|
return ListView_GetSlotCountEx(plv, fWithoutScrollbars, -1, piWidth, piHeight);
|
|
}
|
|
|
|
// get the pixel row (or col in left align) of pitem
|
|
int LV_GetItemPixelRow(LV* plv, LISTITEM* pitem)
|
|
{
|
|
DWORD dwAlignment = plv->ci.style & LVS_ALIGNMASK;
|
|
|
|
if((dwAlignment == LVS_ALIGNLEFT) || (dwAlignment == LVS_ALIGNRIGHT))
|
|
return pitem->pt.x;
|
|
else
|
|
return pitem->pt.y;
|
|
}
|
|
|
|
// get the pixel row (or col in left align) of the lowest item
|
|
int LV_GetMaxPlacedItem(LV* plv)
|
|
{
|
|
int i;
|
|
int iMaxPlacedItem = 0;
|
|
|
|
for (i = 0; i < ListView_Count(plv); i++)
|
|
{
|
|
LISTITEM* pitem = ListView_FastGetItemPtr(plv, i);
|
|
if (pitem->pt.y != RECOMPUTE)
|
|
{
|
|
int iRow = LV_GetItemPixelRow(plv, pitem);
|
|
// if the current item is "below" (on right if it's left aligned)
|
|
// the lowest placed item, we can start appending
|
|
if (!i || iRow > iMaxPlacedItem)
|
|
iMaxPlacedItem = iRow;
|
|
}
|
|
}
|
|
|
|
return iMaxPlacedItem;;
|
|
}
|
|
|
|
// Get the buffer around an item for rcView calculations and slot offsets
|
|
int ListView_GetIconBufferX(LV* plv)
|
|
{
|
|
if (ListView_IsIconView(plv))
|
|
{
|
|
return (plv->cxIconSpacing - plv->cxIcon) / 2;
|
|
}
|
|
else if (ListView_IsTileView(plv))
|
|
return g_cxLabelMargin;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
int ListView_GetIconBufferY(LV* plv)
|
|
{
|
|
if (ListView_IsIconView(plv))
|
|
return g_cyIconOffset;
|
|
else if (ListView_IsTileView(plv))
|
|
return g_cyIconMargin;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
|
|
void ListView_AddViewRectBuffer(LV* plv, RECT* prcView)
|
|
{
|
|
if (ListView_IsIconView(plv))
|
|
{
|
|
// we now grow the label size a bit, so we already have the g_cxEdge added/removed
|
|
}
|
|
else
|
|
{
|
|
prcView->right += g_cxEdge;
|
|
prcView->bottom += g_cyEdge;
|
|
}
|
|
}
|
|
|
|
// Calculate rcView, returned in Listview Coordinates
|
|
// Returns FALSE if rcView is not calculatable and fNoRecalc is specified
|
|
BOOL ListView_ICalcViewRect(LV* plv, BOOL fNoRecalc, RECT* prcView)
|
|
{
|
|
int i;
|
|
|
|
ASSERT(ListView_IsIScrollView(plv) && !ListView_IsOwnerData(plv) && !(plv->fGroupView && plv->hdpaGroups));
|
|
|
|
SetRectEmpty(prcView);
|
|
|
|
for (i = 0; i < ListView_Count(plv); i++)
|
|
{
|
|
RECT rcIcon;
|
|
RECT rcLabel;
|
|
RECT rcItem;
|
|
|
|
if (fNoRecalc)
|
|
{
|
|
LISTITEM *pitem = ListView_FastGetItemPtr(plv, i);
|
|
if (pitem->pt.x == RECOMPUTE)
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
ListView_GetRects(plv, i, QUERY_RCVIEW|QUERY_UNFOLDED, &rcIcon, &rcLabel, &rcItem, NULL);
|
|
UnionRect(prcView, prcView, &rcItem);
|
|
}
|
|
|
|
if (!IsRectEmpty(prcView))
|
|
{
|
|
// Convert to listview coordinates
|
|
OffsetRect(prcView, plv->ptOrigin.x, plv->ptOrigin.y);
|
|
|
|
// Grow it a bit
|
|
ListView_AddViewRectBuffer(plv, prcView);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL ListView_FixIScrollPositions(LV* plv, BOOL fNoScrollbarUpdate, RECT* prcClient)
|
|
{
|
|
BOOL fRet = FALSE;
|
|
|
|
// it's possible for the below ListView_GetClientRect() to force a recalc of rcView
|
|
// which can come back to this function. Nothing bad happens, but there's no
|
|
// need to do fix the scroll positions until we unwind.
|
|
if (!plv->fInFixIScrollPositions)
|
|
{
|
|
plv->fInFixIScrollPositions = TRUE;
|
|
|
|
// First, where rcClient is smaller than rcView:
|
|
// * rcView.left <= ptOrigin.x <= ptOrigin.x+rcClient.right <= rcView.right
|
|
// Second, where rcClient is larger than rcView (no scrollbars visible):
|
|
// * ptOrigin.x <= rcView.left <= rcView.right <= ptOrigin.x+rcClient.right
|
|
if (!(plv->ci.style & LVS_NOSCROLL))
|
|
{
|
|
POINT pt = plv->ptOrigin;
|
|
RECT rcClient;
|
|
if (prcClient)
|
|
rcClient = *prcClient; // can be passed in to avoid calling the below function a second time
|
|
else
|
|
ListView_GetClientRect(plv, &rcClient, TRUE, FALSE);
|
|
|
|
ASSERT(plv->rcView.left != RECOMPUTE);
|
|
|
|
if (RECTWIDTH(rcClient) < RECTWIDTH(plv->rcView))
|
|
{
|
|
if (plv->ptOrigin.x < plv->rcView.left)
|
|
plv->ptOrigin.x = plv->rcView.left;
|
|
else if (plv->ptOrigin.x > plv->rcView.right - RECTWIDTH(rcClient))
|
|
plv->ptOrigin.x = plv->rcView.right - RECTWIDTH(rcClient);
|
|
}
|
|
else
|
|
{
|
|
if (plv->rcView.left < plv->ptOrigin.x)
|
|
plv->ptOrigin.x = plv->rcView.left;
|
|
else if (plv->rcView.right - RECTWIDTH(rcClient) > plv->ptOrigin.x)
|
|
plv->ptOrigin.x = plv->rcView.right - RECTWIDTH(rcClient);
|
|
}
|
|
if (RECTHEIGHT(rcClient) < RECTHEIGHT(plv->rcView))
|
|
{
|
|
if (plv->ptOrigin.y < plv->rcView.top)
|
|
plv->ptOrigin.y = plv->rcView.top;
|
|
else if (plv->ptOrigin.y > plv->rcView.bottom - RECTHEIGHT(rcClient))
|
|
plv->ptOrigin.y = plv->rcView.bottom - RECTHEIGHT(rcClient);
|
|
}
|
|
else
|
|
{
|
|
if (plv->rcView.top < plv->ptOrigin.y)
|
|
plv->ptOrigin.y = plv->rcView.top;
|
|
else if (plv->rcView.bottom - RECTHEIGHT(rcClient) > plv->ptOrigin.y)
|
|
plv->ptOrigin.y = plv->rcView.bottom - RECTHEIGHT(rcClient);
|
|
}
|
|
|
|
fRet = (pt.x != plv->ptOrigin.x) || (pt.y != plv->ptOrigin.y);
|
|
}
|
|
|
|
plv->fInFixIScrollPositions = FALSE;
|
|
|
|
if (fRet)
|
|
{
|
|
// Something moved, we need to invalidate
|
|
ListView_InvalidateWindow(plv);
|
|
if (!fNoScrollbarUpdate)
|
|
ListView_UpdateScrollBars(plv);
|
|
}
|
|
}
|
|
|
|
return fRet;
|
|
}
|
|
|
|
|
|
// Go through and recompute any icon positions and optionally
|
|
// icon label dimensions.
|
|
//
|
|
// This function also recomputes the view bounds rectangle.
|
|
//
|
|
// The algorithm is to simply search the list for any items needing
|
|
// recomputation. For icon positions, we scan possible icon slots
|
|
// and check to see if any already-positioned icon intersects the slot.
|
|
// If not, the slot is free. As an optimization, we start scanning
|
|
// icon slots from the previous slot we found.
|
|
//
|
|
BOOL ListView_IRecomputeEx(LV* plv, HDPA hdpaSort, int iFrom, BOOL fForce)
|
|
{
|
|
int i;
|
|
int cGroups = 0;
|
|
int cWorkAreaSlots[LV_MAX_WORKAREAS];
|
|
BOOL fUpdateSB;
|
|
// if all the items are unplaced, we can just keep appending
|
|
BOOL fAppendAtEnd = (((UINT)ListView_Count(plv)) == plv->uUnplaced);
|
|
int iFree;
|
|
int iWidestGroup = 0;
|
|
BOOL fRet = FALSE;
|
|
|
|
if (hdpaSort == NULL)
|
|
hdpaSort = plv->hdpa;
|
|
|
|
plv->uUnplaced = 0;
|
|
|
|
if (!ListView_IsSlotView(plv))
|
|
return FALSE;
|
|
|
|
if (plv->flags & LVF_INRECOMPUTE)
|
|
{
|
|
return FALSE;
|
|
}
|
|
plv->flags |= LVF_INRECOMPUTE;
|
|
|
|
plv->cSlots = ListView_GetSlotCount(plv, FALSE, NULL, NULL);
|
|
|
|
if (plv->nWorkAreas > 0)
|
|
for (i = 0; i < plv->nWorkAreas; i++)
|
|
cWorkAreaSlots[i] = ListView_GetSlotCountEx(plv, FALSE, i, NULL, NULL);
|
|
|
|
// Scan all items for RECOMPUTE, and recompute slot if needed.
|
|
//
|
|
fUpdateSB = (plv->rcView.left == RECOMPUTE);
|
|
|
|
if (!ListView_IsOwnerData( plv ))
|
|
{
|
|
LVFAKEDRAW lvfd; // in case client uses customdraw
|
|
LV_ITEM item; // in case client uses customdraw
|
|
int iMaxPlacedItem = RECOMPUTE;
|
|
|
|
item.mask = LVIF_PARAM;
|
|
item.iSubItem = 0;
|
|
|
|
ListView_BeginFakeCustomDraw(plv, &lvfd, &item);
|
|
|
|
if (!fAppendAtEnd)
|
|
iMaxPlacedItem = LV_GetMaxPlacedItem(plv);
|
|
|
|
if (plv->fGroupView && plv->hdpaGroups)
|
|
{
|
|
int iAccumulatedHeight = 0;
|
|
int cItems = ListView_Count(plv);
|
|
int iGroupItem;
|
|
LISTITEM* pitem;
|
|
|
|
|
|
for (iGroupItem = 0; iGroupItem < cItems; iGroupItem++)
|
|
{
|
|
pitem = ListView_FastGetItemPtr(plv, iGroupItem);
|
|
if (!pitem)
|
|
break;
|
|
if (pitem->cyFoldedLabel == SRECOMPUTE || fForce)
|
|
{
|
|
// Get the item lParam only if we need it for customdraw
|
|
item.iItem = iGroupItem;
|
|
item.lParam = pitem->lParam;
|
|
|
|
if (!LISTITEM_HASASKEDFORGROUP(pitem))
|
|
{
|
|
item.mask = LVIF_GROUPID;
|
|
ListView_OnGetItem(plv, &item);
|
|
}
|
|
|
|
_ListView_RecomputeLabelSize(plv, pitem, iGroupItem, NULL, FALSE);
|
|
}
|
|
}
|
|
|
|
if (iFrom > 0)
|
|
{
|
|
LISTGROUP* pgrpPrev = DPA_FastGetPtr(plv->hdpaGroups, iFrom - 1);
|
|
iAccumulatedHeight = pgrpPrev->rc.bottom + plv->paddingBottom;
|
|
}
|
|
|
|
cGroups = DPA_GetPtrCount(plv->hdpaGroups);
|
|
for (i = iFrom; i < cGroups; i++)
|
|
{
|
|
LISTGROUP* pgrp = DPA_FastGetPtr(plv->hdpaGroups, i);
|
|
if (!pgrp) // Huh?
|
|
break;
|
|
|
|
cItems = DPA_GetPtrCount(pgrp->hdpa);
|
|
|
|
if (cItems == 0)
|
|
{
|
|
SetRect(&pgrp->rc, 0, 0, 0, 0);
|
|
}
|
|
else
|
|
{
|
|
RECT rcBoundsPrev = {0};
|
|
iFree = 0;
|
|
|
|
if (pgrp->pszHeader && (pgrp->cyTitle == 0 || fForce))
|
|
{
|
|
RECT rc = {0, 0, 1000, 0};
|
|
HDC hdc = GetDC(plv->ci.hwnd);
|
|
HFONT hfontOld = SelectObject(hdc, plv->hfontGroup);
|
|
|
|
DrawText(hdc, pgrp->pszHeader, -1, &rc, DT_LV | DT_CALCRECT);
|
|
|
|
SelectObject(hdc, hfontOld);
|
|
ReleaseDC(plv->ci.hwnd, hdc);
|
|
|
|
pgrp->cyTitle = RECTHEIGHT(rc);
|
|
}
|
|
|
|
iAccumulatedHeight += LISTGROUP_HEIGHT(plv, pgrp);
|
|
|
|
SetRect(&pgrp->rc, plv->rcBorder.left + plv->paddingLeft, iAccumulatedHeight,
|
|
plv->sizeClient.cx - plv->rcBorder.right - plv->paddingRight, 0);
|
|
|
|
for (iGroupItem = 0; iGroupItem < cItems; iGroupItem++)
|
|
{
|
|
pitem = DPA_FastGetPtr(pgrp->hdpa, iGroupItem);
|
|
if (!pitem)
|
|
break;
|
|
|
|
if (iGroupItem > 0)
|
|
{
|
|
RECT rcBounds;
|
|
ListView_SetIconPos(plv, pitem, iFree, plv->cSlots);
|
|
_ListView_GetRectsFromItem(plv, ListView_IsSmallView(plv), pitem, QUERY_DEFAULT,
|
|
NULL, NULL, &rcBounds, NULL);
|
|
|
|
if (IntersectRect(&rcBounds, &rcBounds, &rcBoundsPrev))
|
|
iFree++;
|
|
}
|
|
|
|
if (ListView_SetIconPos(plv, pitem, iFree, plv->cSlots))
|
|
fRet = TRUE;
|
|
|
|
if (!fUpdateSB && LV_IsItemOnViewEdge(plv, pitem))
|
|
fUpdateSB = TRUE;
|
|
|
|
if (iFree == 0)
|
|
{
|
|
int cx, cy;
|
|
_GetCurrentItemSize(plv, &cx, &cy);
|
|
iAccumulatedHeight += cy;
|
|
iWidestGroup = max(iWidestGroup, cx);
|
|
}
|
|
else if (iFree % plv->cSlots == 0)
|
|
{
|
|
int cx, cy;
|
|
_GetCurrentItemSize(plv, &cx, &cy);
|
|
iAccumulatedHeight += cy;
|
|
iWidestGroup = max(iWidestGroup, plv->cSlots * cx);
|
|
}
|
|
|
|
_ListView_GetRectsFromItem(plv, ListView_IsSmallView(plv), pitem, QUERY_DEFAULT,
|
|
NULL, NULL, &rcBoundsPrev, NULL);
|
|
iFree++;
|
|
}
|
|
|
|
pgrp->rc.bottom = iAccumulatedHeight;
|
|
iAccumulatedHeight += plv->rcBorder.bottom + plv->paddingBottom;
|
|
}
|
|
}
|
|
|
|
// Now iterate through the items and Reset the position of items that aren't associated with a group
|
|
for (i = 0; i < ListView_Count(plv); i++)
|
|
{
|
|
LISTITEM* pitem = ListView_FastGetItemPtr(plv, i);
|
|
|
|
if (pitem->pGroup == NULL)
|
|
{
|
|
pitem->pt.x = 0;
|
|
pitem->pt.y = 0;
|
|
}
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
// Must keep in local variable because ListView_SetIconPos will keep
|
|
// invalidating the iFreeSlot cache while we're looping
|
|
if (fForce)
|
|
plv->iFreeSlot = -1;
|
|
iFree = plv->iFreeSlot;
|
|
for (i = 0; i < ListView_Count(plv); i++)
|
|
{
|
|
int cRealSlots;
|
|
LISTITEM* pitem = ListView_FastGetItemPtr(plv, i);
|
|
BOOL fRedraw = FALSE;
|
|
|
|
cRealSlots = (plv->nWorkAreas > 0) ? cWorkAreaSlots[pitem->iWorkArea] : plv->cSlots;
|
|
if (pitem->pt.y == RECOMPUTE || fForce)
|
|
{
|
|
if (pitem->cyFoldedLabel == SRECOMPUTE || fForce)
|
|
{
|
|
// Get the item lParam only if we need it for customdraw
|
|
item.iItem = i;
|
|
item.lParam = pitem->lParam;
|
|
|
|
_ListView_RecomputeLabelSize(plv, pitem, i, NULL, FALSE);
|
|
}
|
|
|
|
if (i < ListView_Count(plv)) // Recompute could have nuked some items.
|
|
{
|
|
if (!fForce)
|
|
{
|
|
// (dli) This function gets a new icon postion and then goes
|
|
// through the whole set of items to see if that position is occupied
|
|
// should let it know in the multi-workarea case, it only needs to go
|
|
// through those who are in the same workarea.
|
|
// This is okay for now because we cannot have too many items on the
|
|
// desktop.
|
|
if (lvfd.nmcd.nmcd.hdc)
|
|
{
|
|
int iWidth = 0, iHeight = 0;
|
|
DWORD dwAlignment = (plv->ci.style & LVS_ALIGNMASK);
|
|
|
|
// If we are right or bottom aligned, then..
|
|
// ....we want to get the width and height to be passed to FindFreeSlot.
|
|
if ((dwAlignment == LVS_ALIGNRIGHT) || (dwAlignment == LVS_ALIGNBOTTOM))
|
|
ListView_GetSlotCountEx(plv, FALSE, pitem->iWorkArea, &iWidth, &iHeight);
|
|
|
|
iFree = ListView_FindFreeSlot(plv, i, iFree + 1, cRealSlots, QUERY_FOLDED, &fUpdateSB, &fAppendAtEnd, lvfd.nmcd.nmcd.hdc, iWidth, iHeight);
|
|
}
|
|
ASSERT(iFree != -1);
|
|
}
|
|
else
|
|
{
|
|
iFree++;
|
|
}
|
|
|
|
// If this slot is frozen, then move this item to the next slot!
|
|
if ((plv->iFrozenSlot != -1) && (plv->iFrozenSlot == iFree))
|
|
{
|
|
iFree++; // Skip the frozen slot!
|
|
}
|
|
|
|
if (ListView_SetIconPos(plv, pitem, iFree, cRealSlots))
|
|
fRet = TRUE;
|
|
|
|
if (!fAppendAtEnd)
|
|
{
|
|
//// optimization. each time we calc a new free slot, we iterate through all the items to see
|
|
// if any of the freely placed items collide with this.
|
|
// fAppendAtEnd indicates that iFree is beyond any freely placed item
|
|
//
|
|
// if the current item is "below" (on right if it's left aligned)
|
|
// the lowest placed item, we can start appending
|
|
if (LV_GetItemPixelRow(plv, pitem) > iMaxPlacedItem)
|
|
fAppendAtEnd = TRUE;
|
|
}
|
|
|
|
if (!fUpdateSB && LV_IsItemOnViewEdge(plv, pitem))
|
|
fUpdateSB = TRUE;
|
|
|
|
fRedraw = TRUE;
|
|
}
|
|
}
|
|
|
|
if (fRedraw)
|
|
{
|
|
ListView_InvalidateItem(plv, i, FALSE, RDW_INVALIDATE | RDW_ERASE);
|
|
}
|
|
|
|
// If the item that we just positioned is marked a frozen item...
|
|
if ((pitem == plv->pFrozenItem) && (iFree >= 0))
|
|
{
|
|
//... then we need to ignore that in free slot calculations.
|
|
iFree--;
|
|
}
|
|
}
|
|
plv->iFreeSlot = iFree;
|
|
}
|
|
ListView_EndFakeCustomDraw(&lvfd);
|
|
|
|
}
|
|
// If we changed something, recompute the view rectangle
|
|
// and then update the scroll bars.
|
|
//
|
|
if (fUpdateSB || plv->rcView.left == RECOMPUTE || fForce)
|
|
{
|
|
// NOTE: No infinite recursion results because we're setting
|
|
// plv->rcView.left != RECOMPUTE
|
|
//
|
|
SetRectEmpty(&plv->rcView);
|
|
|
|
if (plv->fGroupView && plv->hdpaGroups && !ListView_IsOwnerData( plv ))
|
|
{
|
|
LISTGROUP* pgrp;
|
|
int iGroup;
|
|
// Find the first group with an item in it...
|
|
for (iGroup = 0; iGroup < cGroups; iGroup++)
|
|
{
|
|
pgrp = DPA_FastGetPtr(plv->hdpaGroups, iGroup);
|
|
if (DPA_GetPtrCount(pgrp->hdpa) > 0)
|
|
{
|
|
plv->rcView.top = pgrp->rc.top - max(plv->rcBorder.top, pgrp->cyTitle + 6) - plv->paddingTop;
|
|
plv->rcView.left = pgrp->rc.left - plv->rcBorder.left - plv->paddingLeft;
|
|
break;
|
|
}
|
|
}
|
|
// ...and the last group with an item in it
|
|
for (iGroup = cGroups - 1; iGroup >= 0; iGroup--)
|
|
{
|
|
pgrp = DPA_FastGetPtr(plv->hdpaGroups, iGroup);
|
|
if (DPA_GetPtrCount(pgrp->hdpa))
|
|
{
|
|
plv->rcView.bottom = pgrp->rc.bottom + plv->rcBorder.bottom + plv->paddingBottom;
|
|
break;
|
|
}
|
|
}
|
|
plv->rcView.right = iWidestGroup;
|
|
}
|
|
else
|
|
{
|
|
if (ListView_IsOwnerData( plv ))
|
|
{
|
|
TraceMsg(TF_GENERAL, "************ LV: Expensive update! ******* ");
|
|
if (ListView_Count( plv ) > 0)
|
|
{
|
|
RECT rcLast;
|
|
RECT rcItem;
|
|
int iSlots;
|
|
int iItem = ListView_Count( plv ) - 1;
|
|
|
|
ListView_GetRects( plv, 0, QUERY_DEFAULT, NULL, NULL, &plv->rcView, NULL );
|
|
ListView_GetRects( plv, iItem, QUERY_DEFAULT, NULL, NULL, &rcLast, NULL );
|
|
plv->rcView.right = rcLast.right;
|
|
plv->rcView.bottom = rcLast.bottom;
|
|
|
|
//
|
|
// calc how far back in the list to check
|
|
//
|
|
iSlots = plv->cSlots + 2;
|
|
|
|
// REVIEW: This cache hint notification causes a spurious
|
|
// hint, since this happens often but is always the last items
|
|
// available. Should this hint be done at all and this information
|
|
// be cached local to the control?
|
|
ListView_NotifyCacheHint( plv, max( 0, iItem - iSlots), iItem );
|
|
|
|
// move backwards from last item until either rc.right or
|
|
// rc.left is greater than the last, then use that value.
|
|
// Note: This code makes very little assumptions about the ordering
|
|
// done. We should be careful as multiple line text fields could
|
|
// mess us up.
|
|
for( iItem--;
|
|
(iSlots > 0) && (iItem >= 0);
|
|
iSlots--, iItem--)
|
|
{
|
|
RECT rcIcon;
|
|
RECT rcLabel;
|
|
|
|
ListView_GetRects( plv, iItem, QUERY_RCVIEW|QUERY_UNFOLDED, &rcIcon, &rcLabel, &rcItem, NULL );
|
|
if (rcItem.right > rcLast.right)
|
|
{
|
|
plv->rcView.right = rcItem.right;
|
|
}
|
|
if (rcItem.bottom > rcLast.bottom)
|
|
{
|
|
plv->rcView.bottom = rcItem.bottom;
|
|
}
|
|
}
|
|
|
|
// The above calculations were done in Window coordinates, convert to Listview coordinates
|
|
OffsetRect(&plv->rcView, plv->ptOrigin.x, plv->ptOrigin.y);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ListView_ICalcViewRect(plv, FALSE, &plv->rcView);
|
|
}
|
|
}
|
|
|
|
//TraceMsg(DM_TRACE, "RECOMPUTE: rcView %x %x %x %x", plv->rcView.left, plv->rcView.top, plv->rcView.right, plv->rcView.bottom);
|
|
//TraceMsg(DM_TRACE, "Origin %x %x", plv->ptOrigin.x, plv->ptOrigin.y);
|
|
|
|
ListView_UpdateScrollBars(plv);
|
|
}
|
|
|
|
ListView_RecalcRegion(plv, FALSE, TRUE);
|
|
// Now state we are out of the recompute...
|
|
plv->flags &= ~LVF_INRECOMPUTE;
|
|
|
|
ASSERT(ListView_ValidatercView(plv, &plv->rcView, TRUE));
|
|
ASSERT(ListView_ValidateScrollPositions(plv, NULL));
|
|
|
|
return fRet;
|
|
}
|
|
|
|
//This function finds out the nearest work area where the given item must fall.
|
|
int NearestWorkArea(LV *plv, int x, int y, int cxItem, int cyItem, int iCurrentWorkArea)
|
|
{
|
|
int iIndex = 0;
|
|
POINT ptItemCenter = {x + (cxItem/2), y + (cyItem/2)}; //Get the center of the item.
|
|
|
|
if(plv->nWorkAreas <= 0) //If this is a single monitor system...
|
|
return -1; //... then return -1 to indicate that.
|
|
|
|
if(plv->nWorkAreas == 1)
|
|
return 0; //There is only one workarea; so, return it's index.
|
|
|
|
//Find the work-area where the center of the icon falls.
|
|
iIndex = iCurrentWorkArea; // This point is most likely to be in the current work area.
|
|
|
|
do // So, start with that work area.
|
|
{
|
|
if(PtInRect(&plv->prcWorkAreas[iIndex], ptItemCenter))
|
|
return iIndex;
|
|
|
|
if(++iIndex == plv->nWorkAreas) //If we have reached the end,...
|
|
iIndex = 0; // ...start from the begining.
|
|
|
|
// If we have completed looking at all workareas...
|
|
// ....quit the loop!
|
|
} while (iIndex != iCurrentWorkArea);
|
|
|
|
return iCurrentWorkArea; //If it does not fall into any of the work areas, then use the current one.
|
|
}
|
|
|
|
// This function modifies *px, *py to to be in slot-like position -- it doesn't actually gurantee it's in a real slot
|
|
// (NOTE: I tried to assert that this way of calculating slots matched with ListView_CalcSlotRect, but the latter
|
|
// function guarantee's it's in a real slot and this can not. That assert flushed out real bugs in callers of listview,
|
|
// but it unfortunately hits valid cases as well.)
|
|
void NearestSlot(LV *plv, LISTITEM* pitem, int *px, int *py, int cxItem, int cyItem, LPRECT prcWork)
|
|
{
|
|
DWORD dwAlignment = plv->ci.style & LVS_ALIGNMASK;
|
|
int iWidth = 0;
|
|
int iHeight = 0;
|
|
int x = *px;
|
|
int y = *py;
|
|
|
|
if (prcWork != NULL)
|
|
{
|
|
x = x - prcWork->left;
|
|
y = y - prcWork->top;
|
|
}
|
|
|
|
//Get the x with respect to the top right corner.
|
|
if (dwAlignment == LVS_ALIGNRIGHT)
|
|
{
|
|
x = (iWidth = (prcWork? RECTWIDTH(*prcWork) : plv->sizeClient.cx)) - x - cxItem;
|
|
}
|
|
else if (dwAlignment == LVS_ALIGNBOTTOM) //Get y with respect to the bottom left corner.
|
|
{
|
|
y = (iHeight = (prcWork? RECTHEIGHT(*prcWork) : plv->sizeClient.cy)) - y - cyItem;
|
|
}
|
|
|
|
#if 0
|
|
// The above iWidth/iHeight calculations are incorrect in some cases, but I don't think that matters...
|
|
// We might want to consider replacing the above code with ListView_GetSlotCountEx...
|
|
if (TRUE)
|
|
{
|
|
int iWidth2, iHeight2;
|
|
ListView_GetSlotCountEx(plv, TRUE, prcWork ? pitem->iWorkArea : -1, &iWidth2, &iHeight2);
|
|
if (dwAlignment == LVS_ALIGNRIGHT)
|
|
{
|
|
ASSERT(iWidth == iWidth2);
|
|
}
|
|
else if (dwAlignment == LVS_ALIGNBOTTOM)
|
|
{
|
|
ASSERT(iHeight == iHeight2);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// Calculate the center point
|
|
if (x < 0)
|
|
x -= cxItem/2;
|
|
else
|
|
x += cxItem/2;
|
|
|
|
if (y < 0)
|
|
y -= cyItem/2;
|
|
else
|
|
y += cyItem/2;
|
|
|
|
// Find the new x,y point
|
|
x = x - (x % cxItem);
|
|
y = y - (y % cyItem);
|
|
|
|
// Get x and y with respect to the top left corner again.
|
|
if (dwAlignment == LVS_ALIGNRIGHT)
|
|
x = iWidth - x - cxItem;
|
|
else if (dwAlignment == LVS_ALIGNBOTTOM)
|
|
y = iHeight - y - cyItem;
|
|
|
|
if (prcWork != NULL)
|
|
{
|
|
x = x + prcWork->left;
|
|
y = y + prcWork->top;
|
|
}
|
|
|
|
*px = x;
|
|
*py = y;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------------------
|
|
//
|
|
//-------------------------------------------------------------------
|
|
|
|
void ListView_CalcMinMaxIndex( LV* plv, PRECT prcBounding, int* iMin, int* iMax )
|
|
{
|
|
POINT pt;
|
|
int cSlots;
|
|
int iWidth = 0, iHeight = 0;
|
|
|
|
cSlots = ListView_GetSlotCount( plv, TRUE, &iWidth, &iHeight );
|
|
|
|
pt.x = prcBounding->left + plv->ptOrigin.x;
|
|
pt.y = prcBounding->top + plv->ptOrigin.y;
|
|
*iMin = ListView_CalcHitSlot( plv, pt, cSlots, iWidth, iHeight );
|
|
|
|
pt.x = prcBounding->right + plv->ptOrigin.x;
|
|
pt.y = prcBounding->bottom + plv->ptOrigin.y;
|
|
*iMax = ListView_CalcHitSlot( plv, pt, cSlots, iWidth, iHeight ) + 1;
|
|
}
|
|
//-------------------------------------------------------------------
|
|
//
|
|
// Function: ListView_CalcHitSlot
|
|
//
|
|
// Summary: Given a point (relative to complete icon view), calculate
|
|
// which slot that point is closest to.
|
|
//
|
|
// Arguments:
|
|
// plv [in] - List view to work with
|
|
// pt [in] - location to check with
|
|
// cslot [in] - number of slots wide the current view is
|
|
//
|
|
// Notes: This does not guarentee that the point is hitting the item
|
|
// located at that slot. That should be checked by comparing rects.
|
|
//
|
|
// History:
|
|
// Nov-1-1994 MikeMi Added to improve Ownerdata hit testing
|
|
// July-11-2000 Sankar Added iWidth and iHeight parameters to support Right alignment.
|
|
//
|
|
//-------------------------------------------------------------------
|
|
|
|
int ListView_CalcHitSlot( LV* plv, POINT pt, int cSlot, int iWidth, int iHeight)
|
|
{
|
|
int cxItem;
|
|
int cyItem;
|
|
int iSlot = 0;
|
|
|
|
ASSERT(plv);
|
|
|
|
if (cSlot < 1)
|
|
cSlot = 1;
|
|
|
|
_GetCurrentItemSize(plv, &cxItem, &cyItem);
|
|
|
|
// Lets see which direction the view states
|
|
switch (plv->ci.style & LVS_ALIGNMASK)
|
|
{
|
|
case LVS_ALIGNBOTTOM:
|
|
iSlot = min (pt.x / cxItem, cSlot - 1) + ((iHeight - pt.y) / cyItem) * cSlot;
|
|
break;
|
|
|
|
case LVS_ALIGNTOP:
|
|
iSlot = min (pt.x / cxItem, cSlot - 1) + (pt.y / cyItem) * cSlot;
|
|
break;
|
|
|
|
case LVS_ALIGNLEFT:
|
|
iSlot = (pt.x / cxItem) * cSlot + min (pt.y / cyItem, cSlot - 1);
|
|
break;
|
|
|
|
case LVS_ALIGNRIGHT:
|
|
iSlot = ((iWidth - pt.x) / cxItem) * cSlot + min (pt.y / cyItem, cSlot - 1);
|
|
break;
|
|
}
|
|
|
|
return( iSlot );
|
|
}
|
|
|
|
DWORD ListView_IApproximateViewRect(LV* plv, int iCount, int iWidth, int iHeight)
|
|
{
|
|
int cxSave = plv->sizeClient.cx;
|
|
int cySave = plv->sizeClient.cy;
|
|
int cxItem;
|
|
int cyItem;
|
|
int cCols;
|
|
int cRows;
|
|
|
|
plv->sizeClient.cx = iWidth;
|
|
plv->sizeClient.cy = iHeight;
|
|
cCols = ListView_GetSlotCount(plv, TRUE, NULL, NULL);
|
|
|
|
plv->sizeClient.cx = cxSave;
|
|
plv->sizeClient.cy = cySave;
|
|
|
|
cCols = min(cCols, iCount);
|
|
if (cCols == 0)
|
|
cCols = 1;
|
|
cRows = (iCount + cCols - 1) / cCols;
|
|
|
|
if (plv->ci.style & (LVS_ALIGNLEFT | LVS_ALIGNRIGHT)) {
|
|
int c;
|
|
|
|
c = cCols;
|
|
cCols = cRows;
|
|
cRows = c;
|
|
}
|
|
|
|
_GetCurrentItemSize(plv, &cxItem, &cyItem);
|
|
|
|
iWidth = cCols * cxItem;
|
|
iHeight = cRows * cyItem;
|
|
|
|
return MAKELONG(iWidth + g_cxEdge, iHeight + g_cyEdge);
|
|
}
|
|
|
|
|
|
// if fBias is specified, slot rect is returned in Window Coordinates
|
|
// otherwise, slot rect is returned in Listview Coordinates
|
|
void ListView_CalcSlotRect(LV* plv, LISTITEM *pItem, int iSlot, int cSlot, BOOL fBias, int iWidth, int iHeight, LPRECT lprc)
|
|
{
|
|
int cxItem, cyItem;
|
|
|
|
ASSERT(plv);
|
|
|
|
if (cSlot < 1)
|
|
cSlot = 1;
|
|
|
|
_GetCurrentItemSize(plv, &cxItem, &cyItem);
|
|
|
|
// Lets see which direction the view states
|
|
switch (plv->ci.style & LVS_ALIGNMASK)
|
|
{
|
|
case LVS_ALIGNBOTTOM:
|
|
lprc->left = (iSlot % cSlot) * cxItem;
|
|
lprc->top = iHeight - ((iSlot / cSlot)+1) * cyItem;
|
|
break;
|
|
|
|
case LVS_ALIGNTOP:
|
|
lprc->left = (iSlot % cSlot) * cxItem;
|
|
lprc->top = (iSlot / cSlot) * cyItem;
|
|
break;
|
|
|
|
case LVS_ALIGNRIGHT:
|
|
lprc->top = (iSlot % cSlot) * cyItem;
|
|
lprc->left = iWidth - (((iSlot / cSlot)+1) * cxItem);
|
|
break;
|
|
|
|
case LVS_ALIGNLEFT:
|
|
lprc->top = (iSlot % cSlot) * cyItem;
|
|
lprc->left = (iSlot / cSlot) * cxItem;
|
|
break;
|
|
|
|
}
|
|
|
|
if (fBias)
|
|
{
|
|
lprc->left -= plv->ptOrigin.x;
|
|
lprc->top -= plv->ptOrigin.y;
|
|
}
|
|
lprc->bottom = lprc->top + cyItem;
|
|
lprc->right = lprc->left + cxItem;
|
|
|
|
// Multi-Workarea case offset from the workarea coordinate to the whole
|
|
// listview coordinate.
|
|
if (plv->nWorkAreas > 0)
|
|
{
|
|
ASSERT(pItem);
|
|
ASSERT(pItem->iWorkArea < plv->nWorkAreas);
|
|
OffsetRect(lprc, plv->prcWorkAreas[pItem->iWorkArea].left, plv->prcWorkAreas[pItem->iWorkArea].top);
|
|
}
|
|
|
|
if (plv->fGroupView)
|
|
{
|
|
if (pItem &&
|
|
LISTITEM_HASGROUP(pItem))
|
|
{
|
|
OffsetRect(lprc, pItem->pGroup->rc.left, pItem->pGroup->rc.top);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Intersect this rectangle with all items in this listview except myself,
|
|
// this will determine if this rectangle overlays any icons.
|
|
BOOL ListView_IsCleanRect(LV * plv, RECT * prc, int iExcept, UINT fQueryLabelRects, BOOL * pfUpdate, HDC hdc)
|
|
{
|
|
int j;
|
|
RECT rc;
|
|
int cItems = ListView_Count(plv);
|
|
for (j = cItems; j-- > 0; )
|
|
{
|
|
if (j == iExcept)
|
|
continue;
|
|
else
|
|
{
|
|
LISTITEM* pitem = ListView_FastGetItemPtr(plv, j);
|
|
if (pitem->pt.y != RECOMPUTE)
|
|
{
|
|
// If the dimensions aren't computed, then do it now.
|
|
//
|
|
if (pitem->cyFoldedLabel == SRECOMPUTE)
|
|
{
|
|
_ListView_RecomputeLabelSize(plv, pitem, j, hdc, FALSE);
|
|
|
|
// Ensure that the item gets redrawn...
|
|
//
|
|
ListView_InvalidateItem(plv, j, FALSE, RDW_INVALIDATE | RDW_ERASE);
|
|
|
|
// Set flag indicating that scroll bars need to be
|
|
// adjusted.
|
|
//
|
|
if (LV_IsItemOnViewEdge(plv, pitem))
|
|
*pfUpdate = TRUE;
|
|
}
|
|
|
|
ListView_GetRects(plv, j, fQueryLabelRects, NULL, NULL, &rc, NULL);
|
|
if (IntersectRect(&rc, &rc, prc))
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
// Find an icon slot that doesn't intersect an icon.
|
|
// Start search for free slot from slot i.
|
|
//
|
|
int ListView_FindFreeSlot(LV* plv, int iItem, int i, int cSlot, UINT fQueryLabelRects, BOOL* pfUpdate,
|
|
BOOL *pfAppend, HDC hdc, int iWidth, int iHeight)
|
|
{
|
|
RECT rcSlot;
|
|
RECT rcItem;
|
|
RECT rc;
|
|
LISTITEM * pItemLooking = ListView_FastGetItemPtr(plv, iItem);
|
|
|
|
ASSERT(!ListView_IsOwnerData( plv ));
|
|
|
|
// Horrible N-squared algorithm:
|
|
// enumerate each slot and see if any items intersect it.
|
|
//
|
|
// REVIEW: This is really slow with long lists (e.g., 1000)
|
|
//
|
|
|
|
//
|
|
// If the Append at end is set, we should be able to simply get the
|
|
// rectangle of the i-1 element and check against it instead of
|
|
// looking at every other item...
|
|
//
|
|
if (*pfAppend)
|
|
{
|
|
int iPrev = iItem - 1;
|
|
|
|
// Be careful about going off the end of the list. (i is a slot
|
|
// number not an item index).
|
|
if (plv->nWorkAreas > 0)
|
|
{
|
|
while (iPrev >= 0)
|
|
{
|
|
LISTITEM * pPrev = ListView_FastGetItemPtr(plv, iPrev);
|
|
if ((pPrev->iWorkArea == pItemLooking->iWorkArea) && (plv->pFrozenItem != pPrev))
|
|
break;
|
|
iPrev--;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
while (iPrev >= 0)
|
|
{
|
|
LISTITEM * pPrev = ListView_FastGetItemPtr(plv, iPrev);
|
|
if (plv->pFrozenItem != pPrev)
|
|
break;
|
|
iPrev--;
|
|
}
|
|
}
|
|
|
|
if (iPrev >= 0)
|
|
ListView_GetRects(plv, iPrev, fQueryLabelRects, NULL, NULL, &rcItem, NULL);
|
|
else
|
|
SetRect(&rcItem, 0, 0, 0, 0);
|
|
}
|
|
|
|
for ( ; ; i++)
|
|
{
|
|
// Compute view-relative slot rectangle...
|
|
//
|
|
ListView_CalcSlotRect(plv, pItemLooking, i, cSlot, TRUE, iWidth, iHeight, &rcSlot);
|
|
|
|
if (*pfAppend)
|
|
{
|
|
if (!IntersectRect(&rc, &rcItem, &rcSlot)) {
|
|
return i; // Found a free slot...
|
|
}
|
|
}
|
|
|
|
if (ListView_IsCleanRect(plv, &rcSlot, iItem, fQueryLabelRects, pfUpdate, hdc))
|
|
break;
|
|
}
|
|
|
|
return i;
|
|
}
|
|
|
|
// Recompute an item's label size (cxLabel/cyLabel). For speed, this function
|
|
// is passed a DC to use for text measurement.
|
|
//
|
|
// If hdc is NULL, then this function will create and initialize a temporary
|
|
// DC, then destroy it. If hdc is non-NULL, then it is assumed to have
|
|
// the correct font already selected into it.
|
|
//
|
|
// fUsepitem means not to use the text of the item. Instead, use the text
|
|
// pointed to by the pitem structure. This is used in two cases.
|
|
//
|
|
// - Ownerdata, because we don't have a real pitem.
|
|
// - Regulardata, where we already found the pitem text (as an optimizatin)
|
|
//
|
|
void ListView_IRecomputeLabelSize(LV* plv, LISTITEM* pitem, int i, HDC hdc, BOOL fUsepitem)
|
|
{
|
|
TCHAR szLabel[CCHLABELMAX + 4];
|
|
TCHAR szLabelFolded[ARRAYSIZE(szLabel) + CCHELLIPSES + CCHELLIPSES];
|
|
int cchLabel;
|
|
RECT rcSingle = {0};
|
|
RECT rcFolded = {0};
|
|
RECT rcUnfolded = {0};
|
|
LVFAKEDRAW lvfd;
|
|
LV_ITEM item;
|
|
|
|
ASSERT(plv);
|
|
|
|
// the following will use the passed in pitem text instead of calling
|
|
// GetItem. This would be two consecutive calls otherwise, in some cases.
|
|
//
|
|
if (fUsepitem && (pitem->pszText != LPSTR_TEXTCALLBACK))
|
|
{
|
|
Str_GetPtr0(pitem->pszText, szLabel, ARRAYSIZE(szLabel));
|
|
item.lParam = pitem->lParam;
|
|
}
|
|
else
|
|
{
|
|
item.mask = LVIF_TEXT | LVIF_PARAM;
|
|
item.iItem = i;
|
|
item.iSubItem = 0;
|
|
item.pszText = szLabel;
|
|
item.cchTextMax = ARRAYSIZE(szLabel);
|
|
item.stateMask = 0;
|
|
szLabel[0] = TEXT('\0'); // In case the OnGetItem fails
|
|
ListView_OnGetItem(plv, &item);
|
|
|
|
if (!item.pszText)
|
|
{
|
|
SetRectEmpty(&rcSingle);
|
|
rcFolded = rcSingle;
|
|
rcUnfolded = rcSingle;
|
|
goto Exit;
|
|
}
|
|
|
|
if (item.pszText != szLabel)
|
|
lstrcpyn(szLabel, item.pszText, ARRAYSIZE(szLabel));
|
|
}
|
|
|
|
cchLabel = lstrlen(szLabel);
|
|
|
|
rcUnfolded.left = rcUnfolded.top = rcUnfolded.bottom = 0;
|
|
rcUnfolded.right = plv->cxIconSpacing - g_cxLabelMargin * 2;
|
|
rcSingle = rcUnfolded;
|
|
rcFolded = rcUnfolded;
|
|
|
|
if (cchLabel > 0)
|
|
{
|
|
UINT flags;
|
|
|
|
lvfd.nmcd.nmcd.hdc = NULL;
|
|
|
|
if (!hdc)
|
|
{ // Set up fake customdraw
|
|
ListView_BeginFakeCustomDraw(plv, &lvfd, &item);
|
|
ListView_BeginFakeItemDraw(&lvfd);
|
|
}
|
|
else
|
|
{
|
|
lvfd.nmcd.nmcd.hdc = hdc; // Use the one the app gave us
|
|
}
|
|
|
|
if (lvfd.nmcd.nmcd.hdc != NULL)
|
|
{
|
|
int align;
|
|
if (plv->dwExStyle & WS_EX_RTLREADING)
|
|
{
|
|
align = GetTextAlign(lvfd.nmcd.nmcd.hdc);
|
|
SetTextAlign(lvfd.nmcd.nmcd.hdc, align | TA_RTLREADING);
|
|
}
|
|
|
|
DrawText(lvfd.nmcd.nmcd.hdc, szLabel, cchLabel, &rcSingle, (DT_LV | DT_CALCRECT));
|
|
|
|
if (plv->ci.style & LVS_NOLABELWRAP)
|
|
{
|
|
flags = DT_LV | DT_CALCRECT;
|
|
}
|
|
else
|
|
{
|
|
flags = DT_LVWRAP | DT_CALCRECT;
|
|
// We only use DT_NOFULLWIDTHCHARBREAK on Korean(949) Memphis and NT5
|
|
if (949 == g_uiACP)
|
|
flags |= DT_NOFULLWIDTHCHARBREAK;
|
|
}
|
|
|
|
DrawText(lvfd.nmcd.nmcd.hdc, szLabel, cchLabel, &rcUnfolded, flags);
|
|
|
|
//
|
|
// DrawText with DT_MODIFYSTRING is quirky when you enable
|
|
// word ellipses. Once it finds anything that requires ellipses,
|
|
// it stops and doesn't return anything else (even if those other
|
|
// things got displayed).
|
|
//
|
|
lstrcpy(szLabelFolded, szLabel);
|
|
DrawText(lvfd.nmcd.nmcd.hdc, szLabelFolded, cchLabel, &rcFolded, flags | DT_WORD_ELLIPSIS | DT_MODIFYSTRING);
|
|
|
|
// If we had to ellipsify, but you can't tell from looking at the
|
|
// rcFolded.bottom and rcUnfolded.bottom, then tweak rcFolded.bottom
|
|
// so the unfoldifier knows that unfolding is worthwhile.
|
|
if (rcFolded.bottom == rcUnfolded.bottom &&
|
|
lstrcmp(szLabel, szLabelFolded))
|
|
{
|
|
// The actual value isn't important, as long as it's greater
|
|
// than rcUnfolded.bottom and CLIP_HEIGHT. We take advantage
|
|
// of the fact that CLIP_HEIGHT is only two lines, so the only
|
|
// problem case is where you have a two-line item and only the
|
|
// first line is ellipsified.
|
|
rcFolded.bottom++;
|
|
}
|
|
|
|
if (plv->dwExStyle & WS_EX_RTLREADING)
|
|
{
|
|
SetTextAlign(lvfd.nmcd.nmcd.hdc, align);
|
|
}
|
|
|
|
if (!hdc)
|
|
{
|
|
// Clean up fake customdraw
|
|
ListView_EndFakeItemDraw(&lvfd);
|
|
ListView_EndFakeCustomDraw(&lvfd);
|
|
}
|
|
|
|
}
|
|
}
|
|
else
|
|
{
|
|
rcFolded.bottom = rcUnfolded.bottom = rcUnfolded.top + plv->cyLabelChar;
|
|
}
|
|
|
|
Exit:
|
|
|
|
if (pitem)
|
|
{
|
|
int cyEdge;
|
|
pitem->cxSingleLabel = (short)((rcSingle.right - rcSingle.left) + 2 * g_cxLabelMargin);
|
|
pitem->cxMultiLabel = (short)((rcUnfolded.right - rcUnfolded.left) + 2 * g_cxLabelMargin);
|
|
|
|
cyEdge = (plv->ci.style & LVS_NOLABELWRAP) ? 0 : g_cyEdge;
|
|
|
|
pitem->cyFoldedLabel = (short)((rcFolded.bottom - rcFolded.top) + cyEdge);
|
|
pitem->cyUnfoldedLabel = (short)((rcUnfolded.bottom - rcUnfolded.top) + cyEdge);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
// Set up an icon slot position. Returns FALSE if position didn't change.
|
|
//
|
|
BOOL ListView_SetIconPos(LV* plv, LISTITEM* pitem, int iSlot, int cSlot)
|
|
{
|
|
RECT rc;
|
|
int iWidth = 0, iHeight = 0;
|
|
DWORD dwAlignment;
|
|
|
|
ASSERT(plv);
|
|
|
|
// We need to compute iWidth and iHeight only if right or bottom aligned.
|
|
dwAlignment = (plv->ci.style & LVS_ALIGNMASK);
|
|
if ((dwAlignment == LVS_ALIGNRIGHT) || (dwAlignment == LVS_ALIGNBOTTOM))
|
|
ListView_GetSlotCountEx(plv, FALSE, pitem->iWorkArea, &iWidth, &iHeight);
|
|
|
|
ListView_CalcSlotRect(plv, pitem, iSlot, cSlot, FALSE, iWidth, iHeight, &rc);
|
|
|
|
// Offset into the slot so the item will draw at the right place
|
|
rc.left += ListView_GetIconBufferX(plv);
|
|
rc.top += ListView_GetIconBufferY(plv);
|
|
|
|
if (rc.left != pitem->pt.x || rc.top != pitem->pt.y)
|
|
{
|
|
LV_AdjustViewRectOnMove(plv, pitem, rc.left, rc.top);
|
|
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
// returns rcView in window coordinates
|
|
void ListView_GetViewRect2(LV* plv, RECT* prcView, int cx, int cy)
|
|
{
|
|
if (plv->rcView.left == RECOMPUTE)
|
|
ListView_Recompute(plv);
|
|
|
|
*prcView = plv->rcView;
|
|
|
|
//
|
|
// Offsets for scrolling.
|
|
//
|
|
if (ListView_IsReportView(plv))
|
|
{
|
|
OffsetRect(prcView, -plv->ptlRptOrigin.x, -plv->ptlRptOrigin.y);
|
|
}
|
|
else
|
|
{
|
|
OffsetRect(prcView, -plv->ptOrigin.x, -plv->ptOrigin.y);
|
|
}
|
|
|
|
// desktop's snaptogrid code and defview's "position in lower right corner"
|
|
// assume rcView includes the entire rcClient... The below violates the
|
|
// scrolling rules, so only do this for noscroll listview.
|
|
if (ListView_IsSlotView(plv) && (plv->ci.style & LVS_NOSCROLL))
|
|
{
|
|
RECT rc;
|
|
|
|
rc.left = 0;
|
|
rc.top = 0;
|
|
rc.right = cx;
|
|
rc.bottom = cy;
|
|
UnionRect(prcView, prcView, &rc);
|
|
}
|
|
}
|
|
|
|
BOOL ListView_OnGetViewRect(LV* plv, RECT* prcView)
|
|
{
|
|
BOOL fRet = FALSE;
|
|
|
|
if (prcView)
|
|
{
|
|
ListView_GetViewRect2(plv, prcView, plv->sizeClient.cx, plv->sizeClient.cy);
|
|
fRet = TRUE;
|
|
}
|
|
|
|
return fRet;
|
|
}
|
|
|
|
// RECTs returned in window coordinates
|
|
DWORD ListView_GetStyleAndClientRectGivenViewRect(LV* plv, RECT *prcViewRect, RECT* prcClient)
|
|
{
|
|
RECT rcClient;
|
|
DWORD style;
|
|
|
|
// do this instead of the #else below because
|
|
// in new versus old apps, you may need to add in g_c?Border because of
|
|
// the one pixel overlap...
|
|
GetWindowRect(plv->ci.hwnd, &rcClient);
|
|
if (GetWindowLong(plv->ci.hwnd, GWL_EXSTYLE) & (WS_EX_CLIENTEDGE | WS_EX_STATICEDGE | WS_EX_WINDOWEDGE))
|
|
{
|
|
rcClient.right -= 2 * g_cxEdge;
|
|
rcClient.bottom -= 2 * g_cyEdge;
|
|
}
|
|
rcClient.right -= rcClient.left;
|
|
rcClient.bottom -= rcClient.top;
|
|
if (rcClient.right < 0)
|
|
rcClient.right = 0;
|
|
if (rcClient.bottom < 0)
|
|
rcClient.bottom = 0;
|
|
rcClient.top = rcClient.left = 0;
|
|
|
|
style = 0L;
|
|
if (prcViewRect)
|
|
{
|
|
ASSERT(!ListView_IsIScrollView(plv) || ListView_ValidatercView(plv, &plv->rcView, FALSE));
|
|
|
|
if ((rcClient.left < rcClient.right) && (rcClient.top < rcClient.bottom))
|
|
{
|
|
RECT rcView = *prcViewRect;
|
|
// IScrollViews ensure scrollpositions based on rectwidth/height,
|
|
// so we can use this current-scroll-position-independant method:
|
|
if (ListView_IsIScrollView(plv))
|
|
{
|
|
do
|
|
{
|
|
if (!(style & WS_HSCROLL) &&
|
|
(RECTWIDTH(rcClient) < RECTWIDTH(rcView)))
|
|
{
|
|
style |= WS_HSCROLL;
|
|
rcClient.bottom -= ListView_GetCyScrollbar(plv);
|
|
}
|
|
if (!(style & WS_VSCROLL) &&
|
|
(RECTHEIGHT(rcClient) < RECTHEIGHT(rcView)))
|
|
{
|
|
style |= WS_VSCROLL;
|
|
rcClient.right -= ListView_GetCxScrollbar(plv);
|
|
}
|
|
}
|
|
while (!(style & WS_HSCROLL) && (RECTWIDTH(rcClient) < RECTWIDTH(rcView)));
|
|
}
|
|
else
|
|
{
|
|
do
|
|
{
|
|
if (!(style & WS_HSCROLL) &&
|
|
(rcView.left < rcClient.left || rcView.right > rcClient.right))
|
|
{
|
|
style |= WS_HSCROLL;
|
|
rcClient.bottom -= ListView_GetCyScrollbar(plv);
|
|
}
|
|
if (!(style & WS_VSCROLL) &&
|
|
(rcView.top < rcClient.top || rcView.bottom > rcClient.bottom))
|
|
{
|
|
style |= WS_VSCROLL;
|
|
rcClient.right -= ListView_GetCxScrollbar(plv);
|
|
}
|
|
}
|
|
while (!(style & WS_HSCROLL) && rcView.right > rcClient.right);
|
|
}
|
|
}
|
|
}
|
|
|
|
*prcClient = rcClient;
|
|
return style;
|
|
}
|
|
|
|
// prcViewRect used only if fSubScroll is TRUE
|
|
// RECTs returned in window coordinates
|
|
DWORD ListView_GetClientRect(LV* plv, RECT* prcClient, BOOL fSubScroll, RECT *prcViewRect)
|
|
{
|
|
RECT rcView;
|
|
|
|
if (fSubScroll)
|
|
{
|
|
ListView_GetViewRect2(plv, &rcView, 0, 0);
|
|
|
|
if (prcViewRect)
|
|
*prcViewRect = rcView;
|
|
else
|
|
prcViewRect = &rcView;
|
|
}
|
|
else
|
|
{
|
|
prcViewRect = NULL;
|
|
}
|
|
|
|
return ListView_GetStyleAndClientRectGivenViewRect(plv, prcViewRect, prcClient);
|
|
}
|
|
|
|
// Note: pitem->iWorkArea must be properly set when calling this. It gets set
|
|
// in LV_AdjustViewRectOnMove().
|
|
int CALLBACK ArrangeIconCompare(LISTITEM* pitem1, LISTITEM* pitem2, LPARAM lParam)
|
|
{
|
|
int v1, v2;
|
|
int iDirection = 1; //Assume "normal" direction
|
|
POINT pt1 = {pitem1->pt.x, pitem1->pt.y};
|
|
POINT pt2 = {pitem2->pt.x, pitem2->pt.y};
|
|
// REVIEW: lParam can be 0 and we fault ... bug in caller, but we might want to be robust here.
|
|
LV* plv = (LV*)lParam;
|
|
int cx, cy;
|
|
|
|
// Are these guys in the same workarea? Normalize with respect to topleft of workarea
|
|
if (plv->nWorkAreas)
|
|
{
|
|
if (pitem1->iWorkArea == pitem2->iWorkArea)
|
|
{
|
|
RECT *prcWorkArea = &plv->prcWorkAreas[pitem1->iWorkArea];
|
|
pt1.x -= prcWorkArea->left;
|
|
pt2.x -= prcWorkArea->left;
|
|
pt1.y -= prcWorkArea->top;
|
|
pt2.y -= prcWorkArea->top;
|
|
}
|
|
}
|
|
|
|
_GetCurrentItemSize(plv, &cx, &cy);
|
|
|
|
switch((WORD)(plv->ci.style & LVS_ALIGNMASK))
|
|
{
|
|
case LVS_ALIGNRIGHT:
|
|
iDirection = -1; //Right alignment results in abonormal direction.
|
|
//Intentional fall through....
|
|
|
|
case LVS_ALIGNLEFT:
|
|
// Vertical arrangement
|
|
v1 = pt1.x / cx;
|
|
v2 = pt2.x / cx;
|
|
|
|
if (v1 > v2)
|
|
return iDirection;
|
|
else if (v1 < v2)
|
|
return -iDirection;
|
|
else
|
|
{
|
|
if (pt1.y > pt2.y)
|
|
return 1;
|
|
else if (pt1.y < pt2.y)
|
|
return -1;
|
|
}
|
|
break;
|
|
|
|
case LVS_ALIGNBOTTOM:
|
|
iDirection = -1; //Bottom alignment results in abnormal direction.
|
|
//Intentional fall through....
|
|
|
|
case LVS_ALIGNTOP:
|
|
v1 = pt1.y / cy;
|
|
v2 = pt2.y / cy;
|
|
|
|
if (v1 > v2)
|
|
return iDirection;
|
|
else if (v1 < v2)
|
|
return -iDirection;
|
|
else
|
|
{
|
|
if (pt1.x > pt2.x)
|
|
return 1;
|
|
else if (pt1.x < pt2.x)
|
|
return -1;
|
|
}
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void ListView_CalcBounds(LV* plv, UINT fQueryLabelRects, RECT *prcIcon, RECT *prcLabel, RECT *prcBounds)
|
|
{
|
|
if ( ListView_HideLabels(plv) )
|
|
{
|
|
*prcBounds = *prcIcon;
|
|
}
|
|
else
|
|
{
|
|
UnionRect(prcBounds, prcIcon, prcLabel);
|
|
|
|
if (IsQueryrcView(fQueryLabelRects))
|
|
{
|
|
if (ListView_IsIScrollView(plv))
|
|
{
|
|
RECT rcLabel = *prcLabel;
|
|
|
|
prcBounds->left -= plv->rcViewMargin.left;
|
|
prcBounds->top -= plv->rcViewMargin.top;
|
|
prcBounds->right += plv->rcViewMargin.right;
|
|
prcBounds->bottom += plv->rcViewMargin.bottom;
|
|
|
|
// If no rcViewMargin is set, then we should make sure the label text
|
|
// doesn't actually hit the edge of the screen...
|
|
InflateRect(&rcLabel, g_cxEdge, g_cyEdge);
|
|
UnionRect(prcBounds, prcBounds, &rcLabel);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// This returns rects in Window Coordinates
|
|
// fQueryLabelRects determins how prcBounds and prcLabel are returned
|
|
void _ListView_GetRectsFromItem(LV* plv, BOOL bSmallIconView,
|
|
LISTITEM *pitem, UINT fQueryLabelRects,
|
|
LPRECT prcIcon, LPRECT prcLabel, LPRECT prcBounds, LPRECT prcSelectBounds)
|
|
{
|
|
RECT rcIcon;
|
|
RECT rcLabel;
|
|
|
|
if (!prcIcon)
|
|
prcIcon = &rcIcon;
|
|
if (!prcLabel)
|
|
prcLabel = &rcLabel;
|
|
|
|
// Test for NULL item passed in
|
|
if (pitem)
|
|
{
|
|
// This routine is called during ListView_Recompute(), while
|
|
// plv->rcView.left may still be == RECOMPUTE. So, we can't
|
|
// test that to see if recomputation is needed.
|
|
//
|
|
if (pitem->pt.y == RECOMPUTE || pitem->cyFoldedLabel == SRECOMPUTE)
|
|
{
|
|
ListView_Recompute(plv);
|
|
}
|
|
|
|
if (bSmallIconView)
|
|
{
|
|
ListView_SGetRects(plv, pitem, prcIcon, prcLabel, prcBounds);
|
|
}
|
|
else if (ListView_IsTileView(plv))
|
|
{
|
|
ListView_TGetRects(plv, pitem, prcIcon, prcLabel, prcBounds);
|
|
}
|
|
else
|
|
{
|
|
ListView_IGetRects(plv, pitem, fQueryLabelRects, prcIcon, prcLabel, prcBounds);
|
|
}
|
|
|
|
if (prcBounds)
|
|
{
|
|
ListView_CalcBounds(plv, fQueryLabelRects, prcIcon, prcLabel, prcBounds);
|
|
|
|
if (!(ListView_IsSimpleSelect(plv) && (ListView_IsIconView(plv) || ListView_IsTileView(plv))) &&
|
|
plv->himlState && (LV_StateImageValue(pitem)))
|
|
{
|
|
prcBounds->left -= plv->cxState + LV_ICONTOSTATECX;
|
|
}
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
SetRectEmpty(prcIcon);
|
|
*prcLabel = *prcIcon;
|
|
if (prcBounds)
|
|
*prcBounds = *prcIcon;
|
|
}
|
|
|
|
if (prcSelectBounds)
|
|
{
|
|
if ( ListView_HideLabels(plv) )
|
|
*prcSelectBounds = *prcIcon;
|
|
else
|
|
UnionRect(prcSelectBounds, prcIcon, prcLabel);
|
|
|
|
if (!(ListView_IsSimpleSelect(plv) &&
|
|
(ListView_IsIconView(plv) || ListView_IsTileView(plv)))
|
|
&& plv->himlState && (LV_StateImageValue(pitem)))
|
|
{
|
|
prcSelectBounds->left -= plv->cxState + LV_ICONTOSTATECX;
|
|
}
|
|
}
|
|
}
|
|
|
|
void _ListView_InvalidateItemPtr(LV* plv, BOOL bSmallIcon, LISTITEM *pitem, UINT fRedraw)
|
|
{
|
|
RECT rcBounds;
|
|
|
|
ASSERT( !ListView_IsOwnerData( plv ));
|
|
|
|
_ListView_GetRectsFromItem(plv, bSmallIcon, pitem, QUERY_DEFAULT, NULL, NULL, &rcBounds, NULL);
|
|
ListView_DebugDrawInvalidRegion(plv, &rcBounds, NULL);
|
|
RedrawWindow(plv->ci.hwnd, &rcBounds, NULL, fRedraw);
|
|
}
|
|
|
|
//
|
|
// return TRUE if things still overlap
|
|
// this only happens if we tried to unstack things, and there was NOSCROLL set and
|
|
// items tried to go off the deep end
|
|
//
|
|
// NOTE: This function is written such that the order of icons in hdpaSort is still valid
|
|
// even after unstacking some icons. This is very important because this function gets
|
|
// called twice (one for each direction) and we need to make sure the sort order does not
|
|
// change between those two calls.
|
|
//
|
|
BOOL ListView_IUnstackOverlaps(LV* plv, HDPA hdpaSort, int iDirection, int xMargin, int yMargin, BOOL *pfIconsUnstacked)
|
|
{
|
|
int i;
|
|
int iCount;
|
|
BOOL bSmallIconView = ListView_IsSmallView(plv);
|
|
RECT rcItem, rcItem2, rcTemp;
|
|
int cxItem, cyItem;
|
|
LISTITEM* pitem;
|
|
LISTITEM* pitem2;
|
|
int iStartIndex, iEndIndex;
|
|
BOOL fAdjustY;
|
|
int iNextPrevCol = 1;
|
|
int iNextPrevRow = 1;
|
|
int iSlots;
|
|
int iCurWorkArea;
|
|
RECT rcCurWorkArea;
|
|
BOOL fRet = FALSE;
|
|
|
|
ASSERT( !ListView_IsOwnerData( plv ) );
|
|
|
|
_GetCurrentItemSize(plv, &cxItem, &cyItem);
|
|
|
|
iCount = ListView_Count(plv);
|
|
|
|
//
|
|
// Get the direction in which we need to move the icons.
|
|
//
|
|
if(iDirection == 1)
|
|
{
|
|
iStartIndex = 0; //We are starting with icon "0"...
|
|
iEndIndex = iCount - 1; //...and moving towards the last icon.
|
|
}
|
|
else
|
|
{
|
|
ASSERT(iDirection == -1);
|
|
iStartIndex = iCount - 1; //We are starting with the last icon...
|
|
iEndIndex = 0; //..and moving towards the "0"th icon.
|
|
}
|
|
|
|
//
|
|
// Look at the alignment of the icons to decide if we need to move them up/down or
|
|
// left/right.
|
|
//
|
|
switch (plv->ci.style & LVS_ALIGNMASK)
|
|
{
|
|
case LVS_ALIGNBOTTOM:
|
|
iNextPrevRow = -1;
|
|
//Intentional fall-through!
|
|
case LVS_ALIGNTOP:
|
|
fAdjustY = FALSE;
|
|
break;
|
|
|
|
case LVS_ALIGNRIGHT:
|
|
iNextPrevCol = -1;
|
|
//Intentional fall-through!
|
|
case LVS_ALIGNLEFT:
|
|
default:
|
|
fAdjustY = TRUE;
|
|
break;
|
|
}
|
|
|
|
*pfIconsUnstacked = FALSE;
|
|
|
|
// Give an unusual value to iCurWorkArea so that we will be forced to compute the
|
|
// rcCurWorkArea when we go through the loop the first time.
|
|
iCurWorkArea = -2;
|
|
|
|
// finally, unstack any overlaps
|
|
for (i = iStartIndex ; i != (iEndIndex + iDirection) ; i += iDirection)
|
|
{
|
|
int j;
|
|
pitem = DPA_GetPtr(hdpaSort, i);
|
|
|
|
if (bSmallIconView)
|
|
{
|
|
_ListView_GetRectsFromItem(plv, bSmallIconView, pitem, QUERY_FOLDED, NULL, NULL, &rcItem, NULL);
|
|
}
|
|
|
|
// move all the items that overlap with pitem
|
|
for (j = i+iDirection ; j != (iEndIndex + iDirection); j += iDirection)
|
|
{
|
|
POINT ptOldPos;
|
|
|
|
pitem2 = DPA_GetPtr(hdpaSort, j);
|
|
ptOldPos = pitem2->pt;
|
|
|
|
//If an item is being newly added, ignore that item from participating
|
|
//in the Unstacking. Otherwise, it results in all items being shuffled
|
|
//around un-necessarrily!
|
|
if((pitem2->pt.x == RECOMPUTE) || (pitem2->pt.y == RECOMPUTE))
|
|
break; //break out of the loop!
|
|
|
|
//
|
|
//Check if pitem and pitem2 overlap; If so, move pitem2 to the next position.
|
|
//
|
|
if (bSmallIconView)
|
|
{
|
|
// for small icons, we need to do an intersect rect
|
|
_ListView_GetRectsFromItem(plv, bSmallIconView, pitem2, QUERY_FOLDED, NULL, NULL, &rcItem2, NULL);
|
|
|
|
if (IntersectRect(&rcTemp, &rcItem, &rcItem2))
|
|
{
|
|
// yes, it intersects. move it out
|
|
*pfIconsUnstacked = TRUE;
|
|
_ListView_InvalidateItemPtr(plv, bSmallIconView, pitem2, RDW_INVALIDATE| RDW_ERASE);
|
|
do
|
|
{
|
|
if(fAdjustY)
|
|
pitem2->pt.y += (cyItem * iDirection);
|
|
else
|
|
pitem2->pt.x += (cxItem * iDirection);
|
|
} while (PtInRect(&rcItem, pitem2->pt));
|
|
}
|
|
else
|
|
{
|
|
// pitem and pitem2 do not overlap...!
|
|
break; //...break out of the loop!
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
// for large icons, just find the ones that share the x,y;
|
|
if (pitem2->pt.x == pitem->pt.x && pitem2->pt.y == pitem->pt.y)
|
|
{
|
|
*pfIconsUnstacked = TRUE;
|
|
_ListView_InvalidateItemPtr(plv, bSmallIconView, pitem2, RDW_INVALIDATE| RDW_ERASE);
|
|
if(fAdjustY)
|
|
pitem2->pt.y += (cyItem * iDirection);
|
|
else
|
|
pitem2->pt.x += (cxItem * iDirection);
|
|
}
|
|
else
|
|
{
|
|
// pitem and pitem2 do not overlap...!
|
|
break; //...break out of the loop!
|
|
}
|
|
}
|
|
|
|
//
|
|
// Now we know that pitem2 overlapped with pitem and therefore pitem2 had been
|
|
// moved to the "next" possible slot!
|
|
//
|
|
// If scrolling is possible, then we don't have to do anything else. But, if
|
|
// NOSCROLL style is there, we need to check if the icon falls outside the
|
|
// client area and if so move it within.
|
|
//
|
|
if (plv->ci.style & LVS_NOSCROLL)
|
|
{
|
|
//Since our list of icons are sorted based on their positions, the work
|
|
//area change occurs only infrequently.
|
|
if(iCurWorkArea != pitem2->iWorkArea)
|
|
{
|
|
iCurWorkArea = pitem2->iWorkArea;
|
|
if((iCurWorkArea == -1) || (plv->prcWorkAreas == NULL) || (plv->nWorkAreas < 1))
|
|
{
|
|
rcCurWorkArea.left = rcCurWorkArea.top = 0;
|
|
rcCurWorkArea.right = plv->sizeClient.cx;
|
|
rcCurWorkArea.bottom = plv->sizeClient.cy;
|
|
}
|
|
else
|
|
{
|
|
ASSERT(plv->nWorkAreas >= 1);
|
|
rcCurWorkArea = plv->prcWorkAreas[iCurWorkArea];
|
|
}
|
|
//Get the number of slots per row/column based on the alignment style!
|
|
iSlots = ListView_GetSlotCountEx(plv, TRUE, iCurWorkArea, NULL, NULL);
|
|
}
|
|
|
|
//No scrolling possible. So, check if the icon lies outside the client area.
|
|
if(fAdjustY)
|
|
{
|
|
if(iDirection == 1)
|
|
{
|
|
//Has it moved below the bottom edge?
|
|
if(pitem2->pt.y > (rcCurWorkArea.bottom - (cyItem/2)))
|
|
{
|
|
//Then, move the item to the next/prev column.
|
|
pitem2->pt.x += iNextPrevCol*cxItem;
|
|
pitem2->pt.y = rcCurWorkArea.top + yMargin;
|
|
|
|
*pfIconsUnstacked = TRUE; // while not "unstacked", they did move
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ASSERT(iDirection == -1);
|
|
//Has it moved above the top edge?
|
|
if(pitem2->pt.y < rcCurWorkArea.top)
|
|
{
|
|
//Then, move it to the next/prev column.
|
|
pitem2->pt.x -= iNextPrevCol*cxItem;
|
|
pitem2->pt.y = rcCurWorkArea.top + yMargin + (iSlots - 1)*cyItem;
|
|
|
|
*pfIconsUnstacked = TRUE; // while not "unstacked", they did move
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(iDirection == 1)
|
|
{
|
|
//Has it been moved to the right of the right-edge?
|
|
if(pitem2->pt.x > (rcCurWorkArea.right - (cxItem/2)))
|
|
{
|
|
//Then move the item to the next/prev row.
|
|
pitem2->pt.x = rcCurWorkArea.left + xMargin;
|
|
pitem2->pt.y += iNextPrevRow*cyItem;
|
|
|
|
*pfIconsUnstacked = TRUE; // while not "unstacked", they did move
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ASSERT(iDirection == -1);
|
|
//Has is moved to the left of the left-edge?
|
|
if(pitem2->pt.x < rcCurWorkArea.left)
|
|
{
|
|
//Then move the item to the prev/next row.
|
|
pitem2->pt.x = rcCurWorkArea.left + xMargin + (iSlots - 1)*cxItem;
|
|
pitem2->pt.y -= iNextPrevRow*cyItem;
|
|
|
|
*pfIconsUnstacked = TRUE; // while not "unstacked", they did move
|
|
}
|
|
}
|
|
}
|
|
// Inspite of all the above adjustments, if it still falls outside the
|
|
// client, then move it back to where it was!
|
|
if (pitem2->pt.x < rcCurWorkArea.left || pitem2->pt.y < rcCurWorkArea.top ||
|
|
pitem2->pt.x > (rcCurWorkArea.right - (cxItem/2))||
|
|
pitem2->pt.y > (rcCurWorkArea.bottom - (cyItem/2)))
|
|
{
|
|
pitem2->pt = ptOldPos;
|
|
fRet = TRUE; // TRUE = >Icons are still overlapped at the corner.
|
|
|
|
//When this happens, we have reached the top-left corner or
|
|
//the bottom-right corner of one work area (depending on the direction
|
|
//and alignment)
|
|
//Once we reach a corner, we can't return immediately because there
|
|
//could be icons in other work-areas that need to be unstacked.
|
|
//So, return only if we are working with a single work area.
|
|
if(plv->nWorkAreas <= 1)
|
|
{
|
|
if (*pfIconsUnstacked)
|
|
plv->rcView.left = RECOMPUTE;
|
|
|
|
return(fRet);
|
|
}
|
|
}
|
|
}
|
|
|
|
// invalidate the new position as well
|
|
_ListView_InvalidateItemPtr(plv, bSmallIconView, pitem2, RDW_INVALIDATE| RDW_ERASE);
|
|
}
|
|
}
|
|
|
|
// NOTE: the above code should call LV_AdjustViewRectOnMove instead
|
|
// of modifying item's points directly, but this is the easier fix. This is
|
|
// also not a perf hit, since it's uncommon for items to be stacked.
|
|
if (*pfIconsUnstacked)
|
|
plv->rcView.left = RECOMPUTE;
|
|
|
|
return fRet;
|
|
}
|
|
|
|
|
|
BOOL ListView_SnapToGrid(LV* plv, HDPA hdpaSort)
|
|
{
|
|
// this algorithm can't fit in the structure of the other
|
|
// arrange loop without becoming n^2 or worse.
|
|
// this algorithm is order n.
|
|
|
|
// iterate through and snap to the nearest grid.
|
|
// iterate through and push aside overlaps.
|
|
|
|
int i;
|
|
int iCount;
|
|
int x,y;
|
|
LISTITEM* pitem;
|
|
int cxItem, cyItem;
|
|
RECT rcClient = {0, 0, plv->sizeClient.cx, plv->sizeClient.cy};
|
|
int xMargin;
|
|
int yMargin;
|
|
BOOL fIconsMoved = FALSE; //Has any icon moved to goto the nearest slot?
|
|
BOOL fIconsUnstacked = FALSE; //Did we unstack any icons?
|
|
|
|
ASSERT( !ListView_IsOwnerData( plv ) );
|
|
|
|
_GetCurrentItemSize(plv, &cxItem, &cyItem);
|
|
|
|
xMargin = ListView_GetIconBufferX(plv);
|
|
yMargin = ListView_GetIconBufferY(plv);
|
|
|
|
iCount = ListView_Count(plv);
|
|
|
|
// first snap to nearest grid
|
|
for (i = 0; i < iCount; i++)
|
|
{
|
|
int iWorkArea = 0;
|
|
LPRECT prcCurWorkArea;
|
|
|
|
pitem = DPA_GetPtr(hdpaSort, i);
|
|
|
|
x = pitem->pt.x;
|
|
y = pitem->pt.y;
|
|
|
|
//If an item is being newly added, ignore that item from participating
|
|
//in the snap-to-grid. Otherwise, it results in all items being shuffled
|
|
//around un-necessarrily!
|
|
if ((x == RECOMPUTE) || (y == RECOMPUTE))
|
|
continue;
|
|
|
|
x -= xMargin;
|
|
y -= yMargin;
|
|
|
|
//Let's find the nearest work area (where this icon should fall)
|
|
iWorkArea = NearestWorkArea(plv, x, y, cxItem, cyItem, pitem->iWorkArea);
|
|
|
|
if(iWorkArea == -1)
|
|
{
|
|
prcCurWorkArea = &rcClient;
|
|
}
|
|
else
|
|
{
|
|
prcCurWorkArea = &plv->prcWorkAreas[iWorkArea];
|
|
pitem->iWorkArea = (short)iWorkArea;
|
|
}
|
|
|
|
NearestSlot(plv, pitem, &x,&y, cxItem, cyItem, prcCurWorkArea);
|
|
|
|
x += xMargin;
|
|
y += yMargin;
|
|
|
|
if (x != pitem->pt.x || y != pitem->pt.y)
|
|
{
|
|
fIconsMoved = TRUE;
|
|
_ListView_InvalidateItemPtr(plv, ListView_IsSmallView(plv), pitem, RDW_INVALIDATE| RDW_ERASE);
|
|
if (plv->ci.style & LVS_NOSCROLL)
|
|
{
|
|
// if it's marked noscroll, make sure it's still on the client region
|
|
while (x > (prcCurWorkArea->right - cxItem + xMargin))
|
|
x -= cxItem;
|
|
|
|
while (x < 0)
|
|
x += cxItem;
|
|
|
|
while (y > (prcCurWorkArea->bottom - cyItem + yMargin))
|
|
y -= cyItem;
|
|
|
|
while (y < 0)
|
|
y += cyItem;
|
|
}
|
|
|
|
LV_AdjustViewRectOnMove(plv, pitem, x, y);
|
|
|
|
_ListView_InvalidateItemPtr(plv, ListView_IsSmallView(plv), pitem, RDW_INVALIDATE| RDW_ERASE);
|
|
}
|
|
}
|
|
|
|
// now resort the dpa
|
|
if (!DPA_Sort(hdpaSort, ArrangeIconCompare, (LPARAM)plv))
|
|
return FALSE;
|
|
|
|
// go in one direction, if there are still overlaps, go in the other
|
|
// direction as well
|
|
if (ListView_IUnstackOverlaps(plv, hdpaSort, 1, xMargin, yMargin, &fIconsUnstacked))
|
|
{
|
|
//The sorting already done by DPA_Sort is still valid!
|
|
BOOL fIconsUnstackedSecondTime = FALSE;
|
|
ListView_IUnstackOverlaps(plv, hdpaSort, -1, xMargin, yMargin, &fIconsUnstackedSecondTime);
|
|
fIconsUnstacked |= fIconsUnstackedSecondTime;
|
|
}
|
|
|
|
// If something moved, make sure the scrollbars are correct
|
|
if ((fIconsMoved || fIconsUnstacked))
|
|
{
|
|
ListView_UpdateScrollBars(plv);
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
BOOL ListView_OnArrange(LV* plv, UINT style)
|
|
{
|
|
HDPA hdpaSort = NULL;
|
|
|
|
if (!ListView_IsAutoArrangeView(plv))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if (ListView_IsOwnerData( plv ))
|
|
{
|
|
if ( style & (LVA_SNAPTOGRID | LVA_SORTASCENDING | LVA_SORTDESCENDING) )
|
|
{
|
|
RIPMSG(0, "LVM_ARRANGE: Cannot combine LVA_SNAPTOGRID or LVA_SORTxxx with owner-data");
|
|
return( FALSE );
|
|
}
|
|
}
|
|
|
|
if (!ListView_IsOwnerData( plv ))
|
|
{
|
|
// we clone plv->hdpa so we don't blow away indices that
|
|
// apps have saved away.
|
|
// we sort here to make the nested for loop below more bearable.
|
|
hdpaSort = DPA_Clone(plv->hdpa, NULL);
|
|
|
|
if (!hdpaSort)
|
|
return FALSE;
|
|
}
|
|
|
|
// Give every item a new position...
|
|
if (ListView_IsOwnerData( plv ))
|
|
{
|
|
ListView_CommonArrange(plv, style, NULL);
|
|
}
|
|
else
|
|
{
|
|
if (!DPA_Sort(hdpaSort, ArrangeIconCompare, (LPARAM)plv))
|
|
return FALSE;
|
|
|
|
ListView_CommonArrange(plv, style, hdpaSort);
|
|
|
|
DPA_Destroy(hdpaSort);
|
|
}
|
|
|
|
NotifyWinEvent(EVENT_OBJECT_REORDER, plv->ci.hwnd, OBJID_CLIENT, 0);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL ListView_CommonArrangeGroup(LV* plv, int cSlots, HDPA hdpa, int iWorkArea, int cWorkAreaSlots[])
|
|
{
|
|
int iItem;
|
|
BOOL fItemMoved = FALSE;
|
|
int iSlot = 0;
|
|
|
|
// For each group, we start as slot zero.
|
|
for (iItem = 0; iItem < DPA_GetPtrCount(hdpa); iItem++)
|
|
{
|
|
int cRealSlots;
|
|
LISTITEM* pitem = DPA_GetPtr(hdpa, iItem);
|
|
|
|
// In the multi-workarea case, if this item is not in our workarea, skip it.
|
|
if (pitem->iWorkArea != iWorkArea)
|
|
continue;
|
|
|
|
// Ignore frozen items.
|
|
if (pitem == plv->pFrozenItem)
|
|
continue;
|
|
|
|
cRealSlots = (plv->nWorkAreas > 0) ? cWorkAreaSlots[pitem->iWorkArea] : cSlots;
|
|
|
|
fItemMoved |= ListView_SetIconPos(plv, pitem, iSlot++, cRealSlots);
|
|
}
|
|
|
|
return fItemMoved;
|
|
}
|
|
|
|
void ListView_InvalidateWindow(LV* plv)
|
|
{
|
|
if (ListView_RedrawEnabled(plv))
|
|
RedrawWindow(plv->ci.hwnd, NULL, NULL, RDW_INVALIDATE | RDW_ERASE);
|
|
else
|
|
{
|
|
ListView_DeleteHrgnInval(plv);
|
|
plv->hrgnInval = (HRGN)ENTIRE_REGION;
|
|
plv->flags |= LVF_ERASE;
|
|
}
|
|
}
|
|
|
|
// Arrange the icons given a sorted hdpa, and arrange them in the sub workareas
|
|
BOOL ListView_CommonArrangeEx(LV* plv, UINT style, HDPA hdpaSort, int iWorkArea)
|
|
{
|
|
if (!ListView_IsOwnerData( plv ))
|
|
{
|
|
BOOL fItemMoved = FALSE;
|
|
BOOL fScrolled = FALSE;
|
|
|
|
// We're going to call FixIScrollPositions at the end of this, so turn off
|
|
// scroll-validation while we re-arrange the world
|
|
ASSERT(!plv->fInFixIScrollPositions);
|
|
plv->fInFixIScrollPositions = TRUE;
|
|
|
|
if (style == LVA_SNAPTOGRID && !plv->fGroupView)
|
|
{
|
|
// ListView_SnapToGrid() has been made multi-mon aware. This needs to be called
|
|
// just once and it snaps to grid all icons in ALL work areas. Since
|
|
// ListView_CommonArrangeEx() gets called for every work area, we want to avoid
|
|
// un-necessary calls to ListView_SnapToGrid(). So, we call it just once for
|
|
// the first work area.
|
|
if (iWorkArea < 1) // For iWorkArea = 0 or -1.
|
|
{
|
|
fItemMoved |= ListView_SnapToGrid(plv, hdpaSort);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int cSlots;
|
|
int cWorkAreaSlots[LV_MAX_WORKAREAS];
|
|
|
|
if (plv->nWorkAreas > 0)
|
|
{
|
|
int i;
|
|
for (i = 0; i < plv->nWorkAreas; i++)
|
|
cWorkAreaSlots[i] = ListView_GetSlotCountEx(plv, TRUE, i, NULL, NULL);
|
|
}
|
|
else
|
|
cSlots = ListView_GetSlotCount(plv, TRUE, NULL, NULL);
|
|
|
|
if (plv->fGroupView && plv->hdpaGroups)
|
|
{
|
|
int iGroup;
|
|
int cGroups = DPA_GetPtrCount(plv->hdpaGroups);
|
|
for (iGroup = 0; iGroup < cGroups; iGroup++)
|
|
{
|
|
LISTGROUP* pgrp = DPA_FastGetPtr(plv->hdpaGroups, iGroup);
|
|
|
|
fItemMoved |= ListView_CommonArrangeGroup(plv, cSlots, pgrp->hdpa, iWorkArea, cWorkAreaSlots);
|
|
}
|
|
|
|
if (fItemMoved)
|
|
{
|
|
ListView_IRecomputeEx(plv, NULL, 0, FALSE);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
fItemMoved |= ListView_CommonArrangeGroup(plv, cSlots, hdpaSort, iWorkArea, cWorkAreaSlots);
|
|
}
|
|
}
|
|
|
|
plv->fInFixIScrollPositions = FALSE;
|
|
|
|
// We might have to adjust the scroll positions to match the new rcView
|
|
if (ListView_IsIScrollView(plv) && !(plv->ci.style & LVS_NOSCROLL))
|
|
{
|
|
RECT rcClient;
|
|
POINT pt;
|
|
|
|
fScrolled |= ListView_FixIScrollPositions(plv, FALSE, NULL);
|
|
|
|
// Find the auto arrange origin
|
|
ListView_GetClientRect(plv, &rcClient, TRUE, FALSE);
|
|
if ((plv->ci.style & LVS_ALIGNMASK) == LVS_ALIGNRIGHT)
|
|
pt.x = plv->rcView.right - RECTWIDTH(rcClient);
|
|
else
|
|
pt.x = plv->rcView.left;
|
|
if ((plv->ci.style & LVS_ALIGNMASK) == LVS_ALIGNBOTTOM)
|
|
pt.y = plv->rcView.bottom - RECTHEIGHT(rcClient);
|
|
else
|
|
pt.y = plv->rcView.top;
|
|
|
|
// If rcView is smaller than rcClient, peg it to the correct side
|
|
if (RECTWIDTH(rcClient) > RECTWIDTH(plv->rcView))
|
|
{
|
|
if (plv->ptOrigin.x != pt.x)
|
|
{
|
|
plv->ptOrigin.x = pt.x;
|
|
fScrolled = TRUE;
|
|
}
|
|
}
|
|
if (RECTHEIGHT(rcClient) > RECTHEIGHT(plv->rcView))
|
|
{
|
|
if (plv->ptOrigin.y != pt.y)
|
|
{
|
|
plv->ptOrigin.y = pt.y;
|
|
fScrolled = TRUE;
|
|
}
|
|
}
|
|
ASSERT(ListView_ValidateScrollPositions(plv, &rcClient));
|
|
}
|
|
|
|
if (fItemMoved || fScrolled)
|
|
{
|
|
int iItem;
|
|
|
|
// We might as well invalidate the entire window to make sure...
|
|
ListView_InvalidateWindow(plv);
|
|
|
|
// ensure important items are visible
|
|
iItem = (plv->iFocus >= 0) ? plv->iFocus : ListView_OnGetNextItem(plv, -1, LVNI_SELECTED);
|
|
|
|
if (ListView_RedrawEnabled(plv))
|
|
ListView_UpdateScrollBars(plv);
|
|
|
|
if (iItem >= 0)
|
|
ListView_OnEnsureVisible(plv, iItem, FALSE);
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
// this arranges the icon given a sorted hdpa.
|
|
// Arrange the workareas one by one in the multi-workarea case.
|
|
BOOL ListView_CommonArrange(LV* plv, UINT style, HDPA hdpaSort)
|
|
{
|
|
if (plv->nWorkAreas < 1)
|
|
{
|
|
if (plv->exStyle & LVS_EX_MULTIWORKAREAS)
|
|
return TRUE;
|
|
else
|
|
return ListView_CommonArrangeEx(plv, style, hdpaSort, 0);
|
|
}
|
|
else
|
|
{
|
|
int i;
|
|
for (i = 0; i < plv->nWorkAreas; i++)
|
|
ListView_CommonArrangeEx(plv, style, hdpaSort, i);
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
void ListView_IUpdateScrollBars(LV* plv)
|
|
{
|
|
// nothing to update if we're in the middle of fixing them up...
|
|
if (!plv->fInFixIScrollPositions)
|
|
{
|
|
RECT rcClient;
|
|
RECT rcView;
|
|
DWORD style;
|
|
DWORD styleOld;
|
|
SCROLLINFO si;
|
|
|
|
styleOld = ListView_GetWindowStyle(plv);
|
|
|
|
style = ListView_GetClientRect(plv, &rcClient, TRUE, &rcView);
|
|
if (ListView_FixIScrollPositions(plv, TRUE, &rcClient))
|
|
{
|
|
RECT rcClient2, rcView2;
|
|
DWORD style2 = ListView_GetClientRect(plv, &rcClient2, TRUE, &rcView2);
|
|
|
|
#ifdef DEBUG
|
|
// Now that ListView_GetClientRect is scroll-position-independent, fixing the scroll
|
|
// positions should have no effect on the size of rcClient and it's style
|
|
//
|
|
ASSERT(style2 == style);
|
|
ASSERT(RECTWIDTH(rcClient)==RECTWIDTH(rcClient2) && RECTHEIGHT(rcClient)==RECTHEIGHT(rcClient2));
|
|
ASSERT(RECTWIDTH(rcView)==RECTWIDTH(rcView2) && RECTHEIGHT(rcView)==RECTHEIGHT(rcView2));
|
|
#endif
|
|
|
|
rcClient = rcClient2;
|
|
rcView = rcView2;
|
|
}
|
|
|
|
si.cbSize = sizeof(SCROLLINFO);
|
|
|
|
if (style & WS_HSCROLL)
|
|
{
|
|
si.fMask = SIF_PAGE | SIF_RANGE | SIF_POS;
|
|
si.nMin = 0;
|
|
si.nMax = rcView.right - rcView.left - 1;
|
|
|
|
si.nPage = rcClient.right - rcClient.left;
|
|
|
|
si.nPos = rcClient.left - rcView.left;
|
|
|
|
// ListView_FixIScrollPositions() ensures that our scroll positions are correct:
|
|
ASSERT(si.nMax >= (int)si.nPage); // otherwise why is WS_HSCROLL set?
|
|
ASSERT(si.nPos >= 0); // rcClient.left isn't left of rcView.left
|
|
ASSERT(si.nPos + (int)si.nPage <= si.nMax + 1); // rcClient.right isn't right of rcView.right
|
|
|
|
ListView_SetScrollInfo(plv, SB_HORZ, &si, TRUE);
|
|
}
|
|
else if (styleOld & WS_HSCROLL)
|
|
{
|
|
ListView_SetScrollRange(plv, SB_HORZ, 0, 0, TRUE);
|
|
}
|
|
|
|
if (style & WS_VSCROLL)
|
|
{
|
|
si.fMask = SIF_PAGE | SIF_RANGE | SIF_POS;
|
|
si.nMin = 0;
|
|
si.nMax = rcView.bottom - rcView.top - 1;
|
|
|
|
si.nPage = rcClient.bottom - rcClient.top;
|
|
|
|
si.nPos = rcClient.top - rcView.top;
|
|
|
|
// ListView_FixIScrollPositions() ensures that our scroll positions are correct:
|
|
ASSERT(si.nMax >= (int)si.nPage); // otherwise why is WS_VSCROLL set?
|
|
ASSERT(si.nPos >= 0); // rcClient.top isn't above rcView.top
|
|
ASSERT(si.nPos + (int)si.nPage <= si.nMax + 1); // rcClient.bottom isn't below of rcView.bottom
|
|
|
|
ListView_SetScrollInfo(plv, SB_VERT, &si, TRUE);
|
|
}
|
|
else if (styleOld & WS_VSCROLL)
|
|
{
|
|
ListView_SetScrollRange(plv, SB_VERT, 0, 0, TRUE);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ListView_ComOnScroll(LV* plv, UINT code, int posNew, int sb,
|
|
int cLine, int cPage)
|
|
{
|
|
int pos;
|
|
SCROLLINFO si;
|
|
BOOL fVert = (sb == SB_VERT);
|
|
UINT uSmooth = SSW_EX_UPDATEATEACHSTEP;
|
|
|
|
si.cbSize = sizeof(SCROLLINFO);
|
|
si.fMask = SIF_PAGE | SIF_RANGE | SIF_POS;
|
|
|
|
if (!ListView_GetScrollInfo(plv, sb, &si)) {
|
|
return;
|
|
}
|
|
|
|
if (cPage != -1)
|
|
si.nPage = cPage;
|
|
|
|
if (si.nPage)
|
|
si.nMax -= (si.nPage - 1);
|
|
|
|
ASSERT(si.nMax >= si.nMin);
|
|
if (si.nMax < si.nMin)
|
|
si.nMax = si.nMin;
|
|
|
|
pos = (int)si.nPos; // current position
|
|
|
|
switch (code)
|
|
{
|
|
case SB_LEFT:
|
|
si.nPos = si.nMin;
|
|
break;
|
|
case SB_RIGHT:
|
|
si.nPos = si.nMax;
|
|
break;
|
|
case SB_PAGELEFT:
|
|
si.nPos = max(si.nMin, si.nPos - (int)si.nPage);
|
|
break;
|
|
case SB_LINELEFT:
|
|
si.nPos = max(si.nMin, si.nPos - cLine);
|
|
break;
|
|
case SB_PAGERIGHT:
|
|
si.nPos = min(si.nMax, si.nPos + (int)si.nPage);
|
|
break;
|
|
case SB_LINERIGHT:
|
|
si.nPos = min(si.nMax, si.nPos + cLine);
|
|
break;
|
|
|
|
case SB_THUMBTRACK:
|
|
si.nPos = posNew;
|
|
uSmooth = SSW_EX_IMMEDIATE;
|
|
break;
|
|
|
|
case SB_ENDSCROLL:
|
|
// When scroll bar tracking is over, ensure scroll bars
|
|
// are properly updated...
|
|
//
|
|
ListView_UpdateScrollBars(plv);
|
|
return;
|
|
|
|
default:
|
|
return;
|
|
}
|
|
|
|
if (plv->iScrollCount >= SMOOTHSCROLLLIMIT)
|
|
uSmooth = SSW_EX_IMMEDIATE;
|
|
|
|
si.fMask = SIF_POS;
|
|
si.nPos = ListView_SetScrollInfo(plv, sb, &si, TRUE);
|
|
|
|
if (pos != si.nPos)
|
|
{
|
|
int delta = (int)si.nPos - pos;
|
|
int dx = 0, dy = 0;
|
|
if (fVert)
|
|
dy = delta;
|
|
else
|
|
dx = delta;
|
|
ListView_SendScrollNotify(plv, TRUE, dx, dy);
|
|
_ListView_Scroll2(plv, dx, dy, uSmooth);
|
|
ListView_SendScrollNotify(plv, FALSE, dx, dy);
|
|
UpdateWindow(plv->ci.hwnd);
|
|
}
|
|
}
|
|
|
|
//
|
|
// We need a smoothscroll callback so our background image draws
|
|
// at the correct origin. If we don't have a background image,
|
|
// then this work is superfluous but not harmful either.
|
|
//
|
|
int CALLBACK ListView_IScroll2_SmoothScroll(
|
|
HWND hwnd,
|
|
int dx,
|
|
int dy,
|
|
CONST RECT *prcScroll,
|
|
CONST RECT *prcClip ,
|
|
HRGN hrgnUpdate,
|
|
LPRECT prcUpdate,
|
|
UINT flags)
|
|
{
|
|
LV* plv = ListView_GetPtr(hwnd);
|
|
if (plv)
|
|
{
|
|
plv->ptOrigin.x -= dx;
|
|
plv->ptOrigin.y -= dy;
|
|
}
|
|
|
|
// Now do what SmoothScrollWindow would've done if we weren't
|
|
// a callback
|
|
|
|
if (ListView_IsWatermarkedBackground(plv) ||
|
|
ListView_IsWatermarked(plv))
|
|
{
|
|
InvalidateRect(plv->ci.hwnd, NULL, TRUE);
|
|
return TRUE;
|
|
}
|
|
else
|
|
return ScrollWindowEx(hwnd, dx, dy, prcScroll, prcClip, hrgnUpdate, prcUpdate, flags);
|
|
}
|
|
|
|
|
|
|
|
void ListView_IScroll2(LV* plv, int dx, int dy, UINT uSmooth)
|
|
{
|
|
if (dx | dy)
|
|
{
|
|
if ((plv->clrBk == CLR_NONE) && (plv->pImgCtx == NULL))
|
|
{
|
|
plv->ptOrigin.x += dx;
|
|
plv->ptOrigin.y += dy;
|
|
LVSeeThruScroll(plv, NULL);
|
|
}
|
|
else
|
|
{
|
|
SMOOTHSCROLLINFO si;
|
|
si.cbSize = sizeof(si);
|
|
si.fMask = SSIF_SCROLLPROC;
|
|
si.hwnd = plv->ci.hwnd;
|
|
si.dx = -dx;
|
|
si.dy = -dy;
|
|
si.lprcSrc = NULL;
|
|
si.lprcClip = NULL;
|
|
si.hrgnUpdate = NULL;
|
|
si.lprcUpdate = NULL;
|
|
si.fuScroll = uSmooth | SW_INVALIDATE | SW_ERASE | SSW_EX_UPDATEATEACHSTEP;
|
|
si.pfnScrollProc = ListView_IScroll2_SmoothScroll;
|
|
SmoothScrollWindow(&si);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ListView_IOnScroll(LV* plv, UINT code, int posNew, UINT sb)
|
|
{
|
|
int cLine;
|
|
|
|
if (sb == SB_VERT)
|
|
{
|
|
cLine = plv->cyIconSpacing / 2;
|
|
}
|
|
else
|
|
{
|
|
cLine = plv->cxIconSpacing / 2;
|
|
}
|
|
|
|
ListView_ComOnScroll(plv, code, posNew, sb, cLine, -1);
|
|
}
|
|
|
|
int ListView_IGetScrollUnitsPerLine(LV* plv, UINT sb)
|
|
{
|
|
int cLine;
|
|
|
|
if (sb == SB_VERT)
|
|
{
|
|
cLine = plv->cyIconSpacing / 2;
|
|
}
|
|
else
|
|
{
|
|
cLine = plv->cxIconSpacing / 2;
|
|
}
|
|
|
|
return cLine;
|
|
}
|
|
|
|
// NOTE: there is very similar code in the treeview
|
|
//
|
|
// Totally disgusting hack in order to catch VK_RETURN
|
|
// before edit control gets it.
|
|
//
|
|
LRESULT CALLBACK ListView_EditWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
LV* plv = ListView_GetPtr(GetParent(hwnd));
|
|
LRESULT lret;
|
|
|
|
ASSERT(plv);
|
|
|
|
#if defined(FE_IME) || !defined(WINNT)
|
|
if ( (g_fDBCSInputEnabled) && LOWORD(GetKeyboardLayout(0L)) == 0x0411 )
|
|
{
|
|
// The following code adds IME awareness to the
|
|
// listview's label editing. Currently just for Japanese.
|
|
//
|
|
DWORD dwGcs;
|
|
|
|
if (msg==WM_SIZE)
|
|
{
|
|
// If it's given the size, tell it to an IME.
|
|
|
|
ListView_SizeIME(hwnd);
|
|
}
|
|
else if (msg == EM_SETLIMITTEXT )
|
|
{
|
|
if (wParam < 13)
|
|
plv->flags |= LVF_DONTDRAWCOMP;
|
|
else
|
|
plv->flags &= ~LVF_DONTDRAWCOMP;
|
|
}
|
|
// Give up to draw IME composition by ourselves in case
|
|
// we're working on SFN. Win95d-5709
|
|
else if (!(plv->flags & LVF_DONTDRAWCOMP ))
|
|
{
|
|
switch (msg)
|
|
{
|
|
|
|
case WM_IME_STARTCOMPOSITION:
|
|
case WM_IME_ENDCOMPOSITION:
|
|
return 0L;
|
|
|
|
|
|
case WM_IME_COMPOSITION:
|
|
|
|
// If lParam has no data available bit, it implies
|
|
// canceling composition.
|
|
// ListView_InsertComposition() tries to get composition
|
|
// string w/ GCS_COMPSTR then remove it from edit control if
|
|
// nothing is available.
|
|
//
|
|
if ( !lParam )
|
|
dwGcs = GCS_COMPSTR;
|
|
else
|
|
dwGcs = (DWORD) lParam;
|
|
|
|
ListView_InsertComposition(hwnd, wParam, dwGcs, plv);
|
|
return 0L;
|
|
|
|
case WM_PAINT:
|
|
lret=CallWindowProc(plv->pfnEditWndProc, hwnd, msg, wParam, lParam);
|
|
ListView_PaintComposition(hwnd,plv);
|
|
return lret;
|
|
|
|
case WM_IME_SETCONTEXT:
|
|
|
|
// We draw composition string.
|
|
//
|
|
lParam &= ~ISC_SHOWUICOMPOSITIONWINDOW;
|
|
break;
|
|
|
|
default:
|
|
// the other messages should simply be processed
|
|
// in this subclass procedure.
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
#endif FE_IME
|
|
|
|
switch (msg)
|
|
{
|
|
case WM_SETTEXT:
|
|
SetWindowID(hwnd, 1);
|
|
break;
|
|
|
|
case WM_KEYDOWN:
|
|
switch (wParam)
|
|
{
|
|
case VK_RETURN:
|
|
ListView_DismissEdit(plv, FALSE);
|
|
return 0L;
|
|
|
|
case VK_ESCAPE:
|
|
ListView_DismissEdit(plv, TRUE);
|
|
return 0L;
|
|
}
|
|
break;
|
|
|
|
case WM_CHAR:
|
|
switch (wParam)
|
|
{
|
|
case VK_RETURN:
|
|
// Eat the character, so edit control wont beep!
|
|
return 0L;
|
|
}
|
|
break;
|
|
|
|
case WM_GETDLGCODE:
|
|
return DLGC_WANTALLKEYS | DLGC_HASSETSEL; /* editing name, no dialog handling right now */
|
|
}
|
|
|
|
return CallWindowProc(plv->pfnEditWndProc, hwnd, msg, wParam, lParam);
|
|
}
|
|
|
|
// Helper routine for SetEditSize
|
|
void ListView_ChangeEditRectForRegion(LV* plv, LPRECT lprc)
|
|
{
|
|
LISTITEM* pitem = ListView_GetItemPtr(plv, plv->iEdit);
|
|
|
|
ASSERT(!ListView_IsOwnerData(plv));
|
|
ASSERT(ListView_IsIconView(plv));
|
|
|
|
if (!EqualRect((CONST RECT *)&pitem->rcTextRgn, (CONST RECT *)lprc))
|
|
{
|
|
// RecalcRegion knows to use rcTextRgn in the case where iEdit != -1,
|
|
// so set it up before calling through.
|
|
CopyRect(&pitem->rcTextRgn, (CONST RECT *)lprc);
|
|
ListView_RecalcRegion(plv, TRUE, TRUE);
|
|
|
|
// Invalidate the entire Edit and force a repaint from the listview
|
|
// on down to make sure we don't leave turds...
|
|
InvalidateRect(plv->hwndEdit, NULL, TRUE);
|
|
UpdateWindow(plv->ci.hwnd);
|
|
}
|
|
}
|
|
|
|
void ListView_SetEditSize(LV* plv)
|
|
{
|
|
RECT rcLabel;
|
|
UINT seips;
|
|
|
|
if (!((plv->iEdit >= 0) && (plv->iEdit < ListView_Count(plv))))
|
|
{
|
|
ListView_DismissEdit(plv, TRUE); // cancel edits
|
|
return;
|
|
}
|
|
|
|
ListView_GetRects(plv, plv->iEdit, QUERY_DEFAULT, NULL, &rcLabel, NULL, NULL);
|
|
|
|
// OffsetRect(&rc, rcLabel.left + g_cxLabelMargin + g_cxBorder,
|
|
// (rcLabel.bottom + rcLabel.top - rc.bottom) / 2 + g_cyBorder);
|
|
// OffsetRect(&rc, rcLabel.left + g_cxLabelMargin , rcLabel.top);
|
|
|
|
// get the text bounding rect
|
|
|
|
if (ListView_IsIconView(plv))
|
|
{
|
|
// We should not adjust y-positoin in case of the icon view.
|
|
InflateRect(&rcLabel, -g_cxLabelMargin, -g_cyBorder);
|
|
}
|
|
else
|
|
{
|
|
// Special case for single-line & centered
|
|
InflateRect(&rcLabel, -g_cxLabelMargin - g_cxBorder, (-(rcLabel.bottom - rcLabel.top - plv->cyLabelChar) / 2) - g_cyBorder);
|
|
}
|
|
|
|
seips = 0;
|
|
if (ListView_IsIconView(plv) && !(plv->ci.style & LVS_NOLABELWRAP))
|
|
seips |= SEIPS_WRAP;
|
|
#ifdef DEBUG
|
|
if (plv->ci.style & LVS_NOSCROLL)
|
|
seips |= SEIPS_NOSCROLL;
|
|
#endif
|
|
|
|
SetEditInPlaceSize(plv->hwndEdit, &rcLabel, plv->hfontLabel, seips);
|
|
|
|
if (plv->exStyle & LVS_EX_REGIONAL)
|
|
ListView_ChangeEditRectForRegion(plv, &rcLabel);
|
|
}
|
|
|
|
// to avoid eating too much stack
|
|
void ListView_DoOnEditLabel(LV *plv, int i, LPTSTR pszInitial)
|
|
{
|
|
TCHAR szLabel[CCHLABELMAX];
|
|
LV_ITEM item;
|
|
|
|
item.mask = LVIF_TEXT;
|
|
item.iItem = i;
|
|
item.iSubItem = 0;
|
|
item.pszText = szLabel;
|
|
item.cchTextMax = ARRAYSIZE(szLabel);
|
|
ListView_OnGetItem(plv, &item);
|
|
|
|
if (!item.pszText)
|
|
return;
|
|
|
|
// Make sure the edited item has the focus.
|
|
if (plv->iFocus != i)
|
|
ListView_SetFocusSel(plv, i, TRUE, TRUE, FALSE);
|
|
|
|
// Make sure the item is fully visible
|
|
ListView_OnEnsureVisible(plv, i, FALSE); // fPartialOK == FALSE
|
|
|
|
// Must subtract one from ARRAYSIZE(szLabel) because Edit_LimitText doesn't include
|
|
// the terminating NULL
|
|
|
|
plv->hwndEdit = CreateEditInPlaceWindow(plv->ci.hwnd,
|
|
pszInitial? pszInitial : item.pszText, ARRAYSIZE(szLabel) - 1,
|
|
ListView_IsIconView(plv) ?
|
|
(WS_BORDER | WS_CLIPSIBLINGS | WS_CHILD | ES_CENTER | ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL) :
|
|
(WS_BORDER | WS_CLIPSIBLINGS | WS_CHILD | ES_LEFT | ES_AUTOHSCROLL), plv->hfontLabel);
|
|
if (plv->hwndEdit)
|
|
{
|
|
LISTITEM* pitem;
|
|
LV_DISPINFO nm;
|
|
|
|
// We create the edit window but have not shown it. Ask the owner
|
|
// if they are interested or not.
|
|
// If we passed in initial text set the ID to be dirty...
|
|
if (pszInitial)
|
|
SetWindowID(plv->hwndEdit, 1);
|
|
|
|
nm.item.mask = LVIF_PARAM;
|
|
nm.item.iItem = i;
|
|
nm.item.iSubItem = 0;
|
|
|
|
if (!ListView_IsOwnerData( plv ))
|
|
{
|
|
if (!(pitem = ListView_GetItemPtr(plv, i)))
|
|
{
|
|
DestroyWindow(plv->hwndEdit);
|
|
plv->hwndEdit = NULL;
|
|
return;
|
|
}
|
|
nm.item.lParam = pitem->lParam;
|
|
}
|
|
else
|
|
nm.item.lParam = (LPARAM)0;
|
|
|
|
|
|
plv->iEdit = i;
|
|
|
|
// if they have LVS_EDITLABELS but return non-FALSE here, stop!
|
|
if ((BOOL)CCSendNotify(&plv->ci, LVN_BEGINLABELEDIT, &nm.hdr))
|
|
{
|
|
plv->iEdit = -1;
|
|
DestroyWindow(plv->hwndEdit);
|
|
plv->hwndEdit = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void RescrollEditWindow(HWND hwndEdit)
|
|
{
|
|
Edit_SetSel(hwndEdit, -1, -1); // move to the end
|
|
Edit_SetSel(hwndEdit, 0, -1); // select all text
|
|
}
|
|
|
|
HWND ListView_OnEditLabel(LV* plv, int i, LPTSTR pszInitialText)
|
|
{
|
|
|
|
// this eats stack
|
|
ListView_DismissEdit(plv, FALSE);
|
|
|
|
if (!(plv->ci.style & LVS_EDITLABELS) || (GetFocus() != plv->ci.hwnd) ||
|
|
(i == -1))
|
|
return(NULL); // Does not support this.
|
|
|
|
ListView_DoOnEditLabel(plv, i, pszInitialText);
|
|
|
|
if (plv->hwndEdit) {
|
|
|
|
plv->pfnEditWndProc = SubclassWindow(plv->hwndEdit, ListView_EditWndProc);
|
|
|
|
#if defined(FE_IME) || !defined(WINNT)
|
|
if (g_fDBCSInputEnabled) {
|
|
if (SendMessage(plv->hwndEdit, EM_GETLIMITTEXT, (WPARAM)0, (LPARAM)0)<13)
|
|
{
|
|
plv->flags |= LVF_DONTDRAWCOMP;
|
|
}
|
|
|
|
}
|
|
#endif
|
|
|
|
ListView_SetEditSize(plv);
|
|
|
|
// Show the window and set focus to it. Do this after setting the
|
|
// size so we don't get flicker.
|
|
SetFocus(plv->hwndEdit);
|
|
ShowWindow(plv->hwndEdit, SW_SHOW);
|
|
ListView_InvalidateItem(plv, i, TRUE, RDW_INVALIDATE | RDW_ERASE);
|
|
|
|
RescrollEditWindow(plv->hwndEdit);
|
|
|
|
/* Due to a bizzare twist of fate, a certain mix of resolution / font size / icon
|
|
/ spacing results in being able to see the previous label behind the edit control
|
|
/ we have just created. Therefore to overcome this problem we ensure that this
|
|
/ label is erased.
|
|
/
|
|
/ As the label is not painted when we have an edit control we just invalidate the
|
|
/ area and the background will be painted. As the window is a child of the list view
|
|
/ we should not see any flicker within it. */
|
|
|
|
if ( ListView_IsIconView( plv ) && !ListView_HideLabels(plv))
|
|
{
|
|
RECT rcLabel;
|
|
|
|
ListView_GetRects( plv, i, QUERY_UNFOLDED, NULL, &rcLabel, NULL, NULL );
|
|
|
|
InvalidateRect( plv->ci.hwnd, &rcLabel, TRUE );
|
|
UpdateWindow( plv->ci.hwnd );
|
|
}
|
|
}
|
|
|
|
return plv->hwndEdit;
|
|
}
|
|
|
|
|
|
BOOL ListView_DismissEdit(LV* plv, BOOL fCancel)
|
|
{
|
|
LISTITEM* pitem = NULL;
|
|
BOOL fOkToContinue = TRUE;
|
|
HWND hwndEdit = plv->hwndEdit;
|
|
HWND hwnd = plv->ci.hwnd;
|
|
int iEdit;
|
|
LV_DISPINFO nm;
|
|
TCHAR szLabel[CCHLABELMAX];
|
|
#if defined(FE_IME) || !defined(WINNT)
|
|
HIMC himc;
|
|
#endif
|
|
|
|
|
|
if (plv->fNoDismissEdit)
|
|
return FALSE;
|
|
|
|
if (!hwndEdit) {
|
|
// Also make sure there are no pending edits...
|
|
ListView_CancelPendingEdit(plv);
|
|
return TRUE; // It is OK to process as normal...
|
|
}
|
|
|
|
// If the window is not visible, we are probably in the process
|
|
// of being destroyed, so assume that we are being destroyed
|
|
if (!IsWindowVisible(plv->ci.hwnd))
|
|
fCancel = TRUE;
|
|
|
|
//
|
|
// We are using the Window ID of the control as a BOOL to
|
|
// state if it is dirty or not.
|
|
switch (GetWindowID(hwndEdit)) {
|
|
case 0:
|
|
// The edit control is not dirty so act like cancel.
|
|
fCancel = TRUE;
|
|
// Fall through to set window so we will not recurse!
|
|
case 1:
|
|
// The edit control is dirty so continue.
|
|
SetWindowID(hwndEdit, 2); // Don't recurse
|
|
break;
|
|
case 2:
|
|
// We are in the process of processing an update now, bail out
|
|
return TRUE;
|
|
}
|
|
|
|
// Bug#94345: this will fail if the program deleted the items out
|
|
// from underneath us (while we are waiting for the edit timer).
|
|
// make delete item invalidate our edit item
|
|
// We uncouple the edit control and hwnd out from under this as
|
|
// to allow code that process the LVN_ENDLABELEDIT to reenter
|
|
// editing mode if an error happens.
|
|
iEdit = plv->iEdit;
|
|
|
|
do
|
|
{
|
|
if (ListView_IsOwnerData( plv ))
|
|
{
|
|
if (!((iEdit >= 0) && (iEdit < plv->cTotalItems)))
|
|
{
|
|
break;
|
|
}
|
|
nm.item.lParam = 0;
|
|
}
|
|
else
|
|
{
|
|
|
|
pitem = ListView_GetItemPtr(plv, iEdit);
|
|
ASSERT(pitem);
|
|
if (pitem == NULL)
|
|
{
|
|
break;
|
|
}
|
|
nm.item.lParam = pitem->lParam;
|
|
}
|
|
|
|
nm.item.iItem = iEdit;
|
|
nm.item.iSubItem = 0;
|
|
nm.item.cchTextMax = 0;
|
|
nm.item.mask = 0;
|
|
|
|
if (fCancel)
|
|
nm.item.pszText = NULL;
|
|
else
|
|
{
|
|
Edit_GetText(hwndEdit, szLabel, ARRAYSIZE(szLabel));
|
|
nm.item.pszText = szLabel;
|
|
nm.item.mask |= LVIF_TEXT;
|
|
nm.item.cchTextMax = ARRAYSIZE(szLabel);
|
|
}
|
|
|
|
//
|
|
// Notify the parent that we the label editing has completed.
|
|
// We will use the LV_DISPINFO structure to return the new
|
|
// label in. The parent still has the old text available by
|
|
// calling the GetItemText function.
|
|
//
|
|
|
|
fOkToContinue = (BOOL)CCSendNotify(&plv->ci, LVN_ENDLABELEDIT, &nm.hdr);
|
|
if (!IsWindow(hwnd)) {
|
|
return FALSE;
|
|
}
|
|
if (fOkToContinue && !fCancel)
|
|
{
|
|
//
|
|
// If the item has the text set as CALLBACK, we will let the
|
|
// ower know that they are supposed to set the item text in
|
|
// their own data structures. Else we will simply update the
|
|
// text in the actual view.
|
|
//
|
|
if (!ListView_IsOwnerData( plv ) &&
|
|
(pitem->pszText != LPSTR_TEXTCALLBACK))
|
|
{
|
|
// Set the item text (everything's set up in nm.item)
|
|
//
|
|
nm.item.mask = LVIF_TEXT;
|
|
ListView_OnSetItem(plv, &nm.item);
|
|
}
|
|
else
|
|
{
|
|
CCSendNotify(&plv->ci, LVN_SETDISPINFO, &nm.hdr);
|
|
|
|
// Also we will assume that our cached size is invalid...
|
|
plv->rcView.left = RECOMPUTE;
|
|
if (!ListView_IsOwnerData( plv ))
|
|
{
|
|
ListView_SetSRecompute(pitem);
|
|
}
|
|
}
|
|
}
|
|
|
|
#if defined(FE_IME) || !defined(WINNT)
|
|
if (g_fDBCSInputEnabled) {
|
|
if (LOWORD(GetKeyboardLayout(0L)) == 0x0411 && (himc = ImmGetContext(hwndEdit)))
|
|
{
|
|
ImmNotifyIME(himc, NI_COMPOSITIONSTR, CPS_COMPLETE, 0L);
|
|
ImmReleaseContext(hwndEdit, himc);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// redraw
|
|
ListView_InvalidateItem(plv, iEdit, FALSE, RDW_INVALIDATE | RDW_ERASE);
|
|
} while (FALSE);
|
|
|
|
// If the hwnedit is still us clear out the variables
|
|
if (hwndEdit == plv->hwndEdit)
|
|
{
|
|
plv->iEdit = -1;
|
|
plv->hwndEdit = NULL; // avoid being reentered
|
|
}
|
|
DestroyWindow(hwndEdit);
|
|
|
|
// We've to recalc the region because the edit in place window has
|
|
// added stuff to the region that we don't know how to remove
|
|
// safely.
|
|
ListView_RecalcRegion(plv, TRUE, TRUE);
|
|
|
|
return fOkToContinue;
|
|
}
|
|
|
|
HWND CreateEditInPlaceWindow(HWND hwnd, LPCTSTR lpText, int cbText, LONG style, HFONT hFont)
|
|
{
|
|
HWND hwndEdit;
|
|
|
|
// Create the window with some nonzero size so margins work properly
|
|
// The caller will do a SetEditInPlaceSize to set the real size
|
|
// But make sure the width is huge so when an app calls SetWindowText,
|
|
// USER won't try to scroll the window.
|
|
hwndEdit = CreateWindowEx(GetWindowLong(hwnd, GWL_EXSTYLE) & WS_EX_RTLREADING,
|
|
TEXT("EDIT"), lpText, style,
|
|
0, 0, 16384, 20, hwnd, NULL, HINST_THISDLL, NULL);
|
|
|
|
if (hwndEdit) {
|
|
|
|
Edit_LimitText(hwndEdit, cbText);
|
|
|
|
Edit_SetSel(hwndEdit, 0, 0); // move to the beginning
|
|
|
|
FORWARD_WM_SETFONT(hwndEdit, hFont, FALSE, SendMessage);
|
|
|
|
}
|
|
|
|
return hwndEdit;
|
|
}
|
|
|
|
|
|
// in:
|
|
// hwndEdit edit control to position in client coords of parent window
|
|
// prc bonding rect of the text, used to position everthing
|
|
// hFont font being used
|
|
// flags
|
|
// SEIPS_WRAP if this is a wrapped type (multiline) edit
|
|
// SEIPS_NOSCROLL if the parent control does not have scrollbars
|
|
//
|
|
// The SEIPS_NOSCROLL flag is used only in DEBUG. Normally, the item
|
|
// being edited should have been scrolled into view, but if the parent
|
|
// doesn't have scrollbars, then clearly that's not possible, so we
|
|
// shouldn't ASSERT in that case.
|
|
//
|
|
// Notes:
|
|
// The top-left corner of the bouding rectangle must be the position
|
|
// the client uses to draw text. We adjust the edit field rectangle
|
|
// appropriately.
|
|
//
|
|
void SetEditInPlaceSize(HWND hwndEdit, RECT *prc, HFONT hFont, UINT seips)
|
|
{
|
|
RECT rc, rcClient, rcFormat;
|
|
TCHAR szLabel[CCHLABELMAX + 1];
|
|
int cchLabel, cxIconTextWidth;
|
|
HDC hdc;
|
|
HWND hwndParent = GetParent(hwndEdit);
|
|
UINT flags;
|
|
|
|
cchLabel = Edit_GetText(hwndEdit, szLabel, ARRAYSIZE(szLabel));
|
|
if (szLabel[0] == 0)
|
|
{
|
|
lstrcpy(szLabel, c_szSpace);
|
|
cchLabel = 1;
|
|
}
|
|
|
|
hdc = GetDC(hwndParent);
|
|
|
|
SelectFont(hdc, hFont);
|
|
|
|
cxIconTextWidth = g_cxIconSpacing - g_cxLabelMargin * 2;
|
|
rc.left = rc.top = rc.bottom = 0;
|
|
rc.right = cxIconTextWidth; // for DT_LVWRAP
|
|
|
|
// REVIEW: we might want to include DT_EDITCONTROL in our DT_LVWRAP
|
|
|
|
if (seips & SEIPS_WRAP)
|
|
{
|
|
flags = DT_LVWRAP | DT_CALCRECT;
|
|
// We only use DT_NOFULLWIDTHCHARBREAK on Korean(949) Memphis and NT5
|
|
if (949 == g_uiACP)
|
|
flags |= DT_NOFULLWIDTHCHARBREAK;
|
|
}
|
|
else
|
|
flags = DT_LV | DT_CALCRECT;
|
|
// If the string is NULL display a rectangle that is visible.
|
|
DrawText(hdc, szLabel, cchLabel, &rc, flags);
|
|
|
|
// Minimum text box size is 1/4 icon spacing size
|
|
if (rc.right < g_cxIconSpacing / 4)
|
|
rc.right = g_cxIconSpacing / 4;
|
|
|
|
// position the text rect based on the text rect passed in
|
|
// if wrapping, center the edit control around the text mid point
|
|
|
|
OffsetRect(&rc,
|
|
(seips & SEIPS_WRAP) ? prc->left + ((prc->right - prc->left) - (rc.right - rc.left)) / 2 : prc->left,
|
|
(seips & SEIPS_WRAP) ? prc->top : prc->top + ((prc->bottom - prc->top) - (rc.bottom - rc.top)) / 2 );
|
|
|
|
// give a little space to ease the editing of this thing
|
|
if (!(seips & SEIPS_WRAP))
|
|
rc.right += g_cxLabelMargin * 4;
|
|
rc.right += g_cyEdge; // try to leave a little more for dual blanks
|
|
|
|
ReleaseDC(hwndParent, hdc);
|
|
|
|
GetClientRect(hwndParent, &rcClient);
|
|
IntersectRect(&rc, &rc, &rcClient);
|
|
|
|
//
|
|
// Inflate it after the clipping, because it's ok to hide border.
|
|
//
|
|
// EM_GETRECT already takes EM_GETMARGINS into account, so don't use both.
|
|
|
|
SendMessage(hwndEdit, EM_GETRECT, 0, (LPARAM)(LPRECT)&rcFormat);
|
|
|
|
// Turn the margins inside-out so we can AdjustWindowRect on them.
|
|
rcFormat.top = -rcFormat.top;
|
|
rcFormat.left = -rcFormat.left;
|
|
AdjustWindowRectEx(&rcFormat, GetWindowStyle(hwndEdit), FALSE,
|
|
GetWindowExStyle(hwndEdit));
|
|
|
|
InflateRect(&rc, -rcFormat.left, -rcFormat.top);
|
|
|
|
HideCaret(hwndEdit);
|
|
|
|
SetWindowPos(hwndEdit, NULL, rc.left, rc.top,
|
|
rc.right - rc.left, rc.bottom - rc.top, SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOCOPYBITS);
|
|
|
|
CopyRect(prc, (CONST RECT *)&rc);
|
|
|
|
InvalidateRect(hwndEdit, NULL, TRUE);
|
|
|
|
ShowCaret(hwndEdit);
|
|
}
|
|
|
|
// draw three pixel wide border for border selection.
|
|
void ListView_DrawBorderSel(HIMAGELIST himl, HWND hwnd, HDC hdc, int x,int y, COLORREF clr)
|
|
{
|
|
int dx, dy;
|
|
RECT rc;
|
|
COLORREF clrSave = SetBkColor(hdc, clr);
|
|
|
|
ImageList_GetIconSize(himl, &dx, &dy);
|
|
//left
|
|
rc.left = x - 4; // 1 pixel seperation + 3 pixel width.
|
|
rc.top = y - 4;
|
|
rc.right = x - 1;
|
|
rc.bottom = y + dy + 4;
|
|
ExtTextOut(hdc,0,0,ETO_OPAQUE,&rc,NULL,0,NULL);
|
|
//top
|
|
rc.left = rc.right;
|
|
rc.right = rc.left + dx + 2;
|
|
rc.bottom = rc.top + 3;
|
|
ExtTextOut(hdc,0,0,ETO_OPAQUE,&rc,NULL,0,NULL);
|
|
//right
|
|
rc.left = rc.right;
|
|
rc.right = rc.left + 3;
|
|
rc.bottom = rc.top + dy + 8; // 2*3 pixel borders + 2*1 pixel seperation = 8
|
|
ExtTextOut(hdc,0,0,ETO_OPAQUE,&rc,NULL,0,NULL);
|
|
// bottom
|
|
rc.top = rc.bottom - 3;
|
|
rc.right = rc.left;
|
|
rc.left = rc.right - dx - 2;
|
|
ExtTextOut(hdc,0,0,ETO_OPAQUE,&rc,NULL,0,NULL);
|
|
|
|
SetBkColor(hdc, clrSave);
|
|
return;
|
|
}
|
|
|
|
UINT ListView_GetTextSelectionFlags(LV* plv, LV_ITEM *pitem, UINT fDraw)
|
|
{
|
|
UINT fText = SHDT_DESELECTED;
|
|
// the item can have one of 4 states, for 3 looks:
|
|
// normal simple drawing
|
|
// selected, no focus light image highlight, no text hi
|
|
// selected w/ focus highlight image & text
|
|
// drop highlighting highlight image & text
|
|
|
|
if ((pitem->state & LVIS_DROPHILITED) ||
|
|
(fDraw & LVDI_SELECTED && (pitem->state & LVIS_SELECTED)) )
|
|
{
|
|
fText = SHDT_SELECTED;
|
|
}
|
|
|
|
if (fDraw & LVDI_SELECTNOFOCUS && (pitem->state & LVIS_SELECTED))
|
|
{
|
|
fText = SHDT_SELECTNOFOCUS;
|
|
}
|
|
|
|
return fText;
|
|
}
|
|
|
|
//
|
|
// If xMax >= 0, then the image will not be drawn past the x-coordinate
|
|
// specified by xMax. This is used only during report view drawing, where
|
|
// we have to clip against our column width.
|
|
//
|
|
UINT ListView_DrawImageEx2(LV* plv, LV_ITEM* pitem, HDC hdc, int x, int y, COLORREF crBk, UINT fDraw, int xMax, int iIconEffect, int iFrame)
|
|
{
|
|
BOOL fBorderSel = ListView_IsBorderSelect(plv);
|
|
UINT fImage;
|
|
COLORREF clr = 0;
|
|
HIMAGELIST himl;
|
|
int cxIcon;
|
|
UINT fText = ListView_GetTextSelectionFlags(plv, pitem, fDraw);
|
|
DWORD fState = iIconEffect;
|
|
|
|
fImage = (pitem->state & LVIS_OVERLAYMASK);
|
|
|
|
if (plv->flags & LVF_DRAGIMAGE)
|
|
{
|
|
fImage |= ILD_PRESERVEALPHA;
|
|
}
|
|
|
|
if (ListView_IsIconView(plv) || ListView_IsTileView(plv))
|
|
{
|
|
himl = plv->himl;
|
|
cxIcon = plv->cxIcon;
|
|
}
|
|
else
|
|
{
|
|
himl = plv->himlSmall;
|
|
cxIcon = plv->cxSmIcon;
|
|
}
|
|
|
|
if (!(plv->flags & LVF_DRAGIMAGE))
|
|
{
|
|
// the item can have one of 4 states, for 3 looks:
|
|
// normal simple drawing
|
|
// selected, no focus light image highlight, no text hi
|
|
// selected w/ focus highlight image & text
|
|
// drop highlighting highlight image & text
|
|
|
|
if ((pitem->state & LVIS_DROPHILITED) ||
|
|
((fDraw & LVDI_SELECTED) && (pitem->state & LVIS_SELECTED)))
|
|
{
|
|
fText = SHDT_SELECTED;
|
|
if (!fBorderSel) // do not effect color of icon on borderselect.
|
|
{
|
|
fImage |= ILD_BLEND50;
|
|
clr = CLR_HILIGHT;
|
|
}
|
|
}
|
|
|
|
if (pitem->state & LVIS_CUT)
|
|
{
|
|
fImage |= ILD_BLEND50;
|
|
clr = plv->clrBk;
|
|
}
|
|
|
|
// Affects only allowed if double buffering
|
|
if (ListView_IsDoubleBuffer(plv))
|
|
{
|
|
if ((pitem->state & LVIS_GLOW || (fDraw & LVDI_GLOW)) && !(fDraw & LVDI_NOEFFECTS))
|
|
{
|
|
crBk = CLR_NONE;
|
|
fState |= ILS_GLOW;
|
|
}
|
|
|
|
if (fDraw & LVDI_SHADOW && !(fDraw & LVDI_NOEFFECTS))
|
|
{
|
|
crBk = CLR_NONE;
|
|
fState |= ILS_SHADOW;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!(fDraw & LVDI_NOIMAGE))
|
|
{
|
|
if (himl)
|
|
{
|
|
if (plv->pImgCtx || ListView_IsWatermarked(plv) || ((plv->exStyle & LVS_EX_REGIONAL) && !g_fSlowMachine))
|
|
{
|
|
crBk = CLR_NONE;
|
|
}
|
|
|
|
if (xMax >= 0)
|
|
cxIcon = min(cxIcon, xMax - x);
|
|
|
|
if (cxIcon > 0)
|
|
{
|
|
IMAGELISTDRAWPARAMS imldp;
|
|
DWORD dwFrame = iFrame;
|
|
|
|
imldp.cbSize = sizeof(imldp);
|
|
imldp.himl = himl;
|
|
imldp.i = pitem->iImage;
|
|
imldp.hdcDst = hdc;
|
|
imldp.x = x;
|
|
imldp.y = y;
|
|
imldp.cx = CCIsHighDPI()?0:cxIcon;
|
|
imldp.cy = 0;
|
|
imldp.xBitmap= 0;
|
|
imldp.yBitmap= 0;
|
|
imldp.rgbBk = crBk;
|
|
imldp.rgbFg = clr;
|
|
imldp.fStyle = fImage;
|
|
imldp.fState = fState;
|
|
imldp.Frame = dwFrame;
|
|
|
|
if (ListView_IsDPIScaled(plv))
|
|
imldp.fStyle |= ILD_DPISCALE;
|
|
|
|
|
|
|
|
ImageList_DrawIndirect(&imldp);
|
|
}
|
|
}
|
|
|
|
if (plv->himlState)
|
|
{
|
|
if (LV_StateImageValue(pitem) &&
|
|
(pitem->iSubItem == 0 || plv->exStyle & LVS_EX_SUBITEMIMAGES)
|
|
)
|
|
{
|
|
int iState = LV_StateImageIndex(pitem);
|
|
int dyImage = 0;
|
|
int xDraw = x - plv->cxState - LV_ICONTOSTATECX;
|
|
|
|
// if we are not rendering checks boxes with toggle select
|
|
// then lets render the state image the old way.
|
|
|
|
if (ListView_IsSimpleSelect(plv) &&
|
|
(ListView_IsIconView(plv) || ListView_IsTileView(plv)))
|
|
{
|
|
xDraw = x+cxIcon -plv->cxState; // align top right
|
|
dyImage = 0;
|
|
}
|
|
else
|
|
{
|
|
if (himl)
|
|
{
|
|
if (ListView_IsIconView(plv))
|
|
dyImage = plv->cyIcon - plv->cyState;
|
|
else if (ListView_IsTileView(plv))
|
|
dyImage = (plv->sizeTile.cy - plv->cyState) / 2; //Center vertically
|
|
else // assume small icon
|
|
dyImage = plv->cySmIcon - plv->cyState;
|
|
}
|
|
}
|
|
|
|
cxIcon = plv->cxState;
|
|
if (xMax >= 0)
|
|
{
|
|
cxIcon = min(cxIcon, xMax - xDraw);
|
|
}
|
|
|
|
if (cxIcon > 0)
|
|
{
|
|
IMAGELISTDRAWPARAMS imldp;
|
|
|
|
imldp.cbSize = sizeof(imldp);
|
|
imldp.himl = plv->himlState;
|
|
imldp.i = iState;
|
|
imldp.hdcDst = hdc;
|
|
imldp.x = xDraw;
|
|
imldp.y = y + dyImage;
|
|
imldp.cx = CCIsHighDPI()?0:cxIcon;
|
|
imldp.cy = 0;
|
|
imldp.xBitmap= 0;
|
|
imldp.yBitmap= 0;
|
|
imldp.rgbBk = crBk;
|
|
imldp.rgbFg = clr;
|
|
imldp.fStyle = fImage;
|
|
imldp.fState = fState;
|
|
imldp.Frame = 0;
|
|
|
|
if (ListView_IsDPIScaled(plv))
|
|
imldp.fStyle |= ILD_DPISCALE;
|
|
|
|
ImageList_DrawIndirect(&imldp);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return fText;
|
|
}
|
|
|
|
UINT ListView_DrawImageEx(LV* plv, LV_ITEM* pitem, HDC hdc, int x, int y, COLORREF crBk, UINT fDraw, int xMax)
|
|
{
|
|
return ListView_DrawImageEx2(plv, pitem, hdc, x, y, crBk, fDraw, xMax, ILD_NORMAL, 0);
|
|
}
|
|
|
|
#if defined(FE_IME) || !defined(WINNT)
|
|
void ListView_SizeIME(HWND hwnd)
|
|
{
|
|
HIMC himc;
|
|
#ifdef _WIN32
|
|
CANDIDATEFORM candf;
|
|
#else
|
|
CANDIDATEFORM16 candf;
|
|
#endif
|
|
RECT rc;
|
|
|
|
// If this subclass procedure is being called with WM_SIZE,
|
|
// This routine sets the rectangle to an IME.
|
|
|
|
GetClientRect(hwnd, &rc);
|
|
|
|
|
|
// Candidate stuff
|
|
candf.dwIndex = 0; // Bogus assumption for Japanese IME.
|
|
candf.dwStyle = CFS_EXCLUDE;
|
|
candf.ptCurrentPos.x = rc.left;
|
|
candf.ptCurrentPos.y = rc.bottom;
|
|
candf.rcArea = rc;
|
|
|
|
if (himc=ImmGetContext(hwnd))
|
|
{
|
|
ImmSetCandidateWindow(himc, &candf);
|
|
ImmReleaseContext(hwnd, himc);
|
|
}
|
|
}
|
|
|
|
#ifndef UNICODE
|
|
LPSTR DoDBCSBoundary(LPTSTR lpsz, int *lpcchMax)
|
|
{
|
|
int i = 0;
|
|
|
|
while (i < *lpcchMax && *lpsz)
|
|
{
|
|
i++;
|
|
|
|
if (IsDBCSLeadByte(*lpsz))
|
|
{
|
|
|
|
if (i >= *lpcchMax)
|
|
{
|
|
--i; // Wrap up without the last leadbyte.
|
|
break;
|
|
}
|
|
|
|
i++;
|
|
lpsz+= 2;
|
|
}
|
|
else
|
|
lpsz++;
|
|
}
|
|
|
|
*lpcchMax = i;
|
|
|
|
return lpsz;
|
|
}
|
|
#endif
|
|
|
|
void DrawCompositionLine(HWND hwnd, HDC hdc, HFONT hfont, LPTSTR lpszComp, LPBYTE lpszAttr, int ichCompStart, int ichCompEnd, int ichStart)
|
|
{
|
|
PTSTR pszCompStr;
|
|
int ichSt,ichEnd;
|
|
DWORD dwPos;
|
|
BYTE bAttr;
|
|
HFONT hfontOld;
|
|
|
|
int fnPen;
|
|
HPEN hPen;
|
|
COLORREF crDrawText;
|
|
COLORREF crDrawBack;
|
|
COLORREF crOldText;
|
|
COLORREF crOldBk;
|
|
|
|
|
|
while (ichCompStart < ichCompEnd)
|
|
{
|
|
|
|
// Get the fragment to draw
|
|
//
|
|
// ichCompStart,ichCompEnd -- index at Edit Control
|
|
// ichSt,ichEnd -- index at lpszComp
|
|
|
|
ichEnd = ichSt = ichCompStart - ichStart;
|
|
bAttr = lpszAttr[ichSt];
|
|
|
|
while (ichEnd < ichCompEnd - ichStart)
|
|
{
|
|
if (bAttr == lpszAttr[ichEnd])
|
|
ichEnd++;
|
|
else
|
|
break;
|
|
}
|
|
|
|
pszCompStr = (PTSTR)LocalAlloc(LPTR, sizeof(TCHAR)*(ichEnd - ichSt + 1 + 1) ); // 1 for NULL.
|
|
|
|
if (pszCompStr)
|
|
{
|
|
lstrcpyn(pszCompStr, &lpszComp[ichSt], ichEnd-ichSt+1);
|
|
pszCompStr[ichEnd-ichSt] = '\0';
|
|
}
|
|
|
|
|
|
// Attribute stuff
|
|
switch (bAttr)
|
|
{
|
|
case ATTR_INPUT:
|
|
fnPen = PS_DOT;
|
|
crDrawText = g_clrWindowText;
|
|
crDrawBack = g_clrWindow;
|
|
break;
|
|
case ATTR_TARGET_CONVERTED:
|
|
case ATTR_TARGET_NOTCONVERTED:
|
|
fnPen = PS_DOT;
|
|
crDrawText = g_clrHighlightText;
|
|
crDrawBack = g_clrHighlight;
|
|
break;
|
|
case ATTR_CONVERTED:
|
|
fnPen = PS_SOLID;
|
|
crDrawText = g_clrWindowText;
|
|
crDrawBack = g_clrWindow;
|
|
break;
|
|
}
|
|
crOldText = SetTextColor(hdc, crDrawText);
|
|
crOldBk = SetBkColor(hdc, crDrawBack);
|
|
|
|
hfontOld= SelectObject(hdc, hfont);
|
|
|
|
// Get the start position of composition
|
|
//
|
|
dwPos = (DWORD) SendMessage(hwnd, EM_POSFROMCHAR, ichCompStart, 0);
|
|
|
|
// Draw it.
|
|
TextOut(hdc, GET_X_LPARAM(dwPos), GET_Y_LPARAM(dwPos), pszCompStr, ichEnd-ichSt);
|
|
#ifndef DONT_UNDERLINE
|
|
// Underline
|
|
hPen = CreatePen(fnPen, 1, crDrawText);
|
|
if( hPen ) {
|
|
|
|
HPEN hpenOld = SelectObject( hdc, hPen );
|
|
int iOldBk = SetBkMode( hdc, TRANSPARENT );
|
|
SIZE size;
|
|
|
|
GetTextExtentPoint(hdc, pszCompStr, ichEnd-ichSt, &size);
|
|
|
|
MoveToEx( hdc, GET_X_LPARAM(dwPos), size.cy + GET_Y_LPARAM(dwPos)-1, NULL);
|
|
|
|
LineTo( hdc, size.cx + GET_X_LPARAM(dwPos), size.cy + GET_Y_LPARAM(dwPos)-1 );
|
|
|
|
SetBkMode( hdc, iOldBk );
|
|
|
|
if( hpenOld ) SelectObject( hdc, hpenOld );
|
|
|
|
DeleteObject( hPen );
|
|
}
|
|
#endif
|
|
|
|
if (hfontOld)
|
|
SelectObject(hdc, hfontOld);
|
|
|
|
SetTextColor(hdc, crOldText);
|
|
SetBkColor(hdc, crOldBk);
|
|
|
|
LocalFree((HLOCAL)pszCompStr);
|
|
|
|
//Next fragment
|
|
//
|
|
ichCompStart += ichEnd-ichSt;
|
|
}
|
|
}
|
|
|
|
void ListView_InsertComposition(HWND hwnd, WPARAM wParam, LPARAM lParam, LV *plv)
|
|
{
|
|
PSTR pszCompStr;
|
|
|
|
int cbComp = 0;
|
|
int cbCompNew;
|
|
int cchMax;
|
|
int cchText;
|
|
DWORD dwSel;
|
|
HIMC himc = (HIMC)0;
|
|
|
|
|
|
// To prevent recursion..
|
|
|
|
if (plv->flags & LVF_INSERTINGCOMP)
|
|
{
|
|
return;
|
|
}
|
|
plv->flags |= LVF_INSERTINGCOMP;
|
|
|
|
// Don't want to redraw edit during inserting.
|
|
//
|
|
SendMessage(hwnd, WM_SETREDRAW, (WPARAM)FALSE, 0);
|
|
|
|
// If we have RESULT STR, put it to EC first.
|
|
|
|
if (himc = ImmGetContext(hwnd))
|
|
{
|
|
#ifdef WIN32
|
|
if (!(dwSel = PtrToUlong(GetProp(hwnd, szIMECompPos))))
|
|
dwSel = Edit_GetSel(hwnd);
|
|
|
|
// Becaues we don't setsel after inserting composition
|
|
// in win32 case.
|
|
Edit_SetSel(hwnd, GET_X_LPARAM(dwSel), GET_Y_LPARAM(dwSel));
|
|
#endif
|
|
if (lParam&GCS_RESULTSTR)
|
|
{
|
|
// ImmGetCompositionString() returns length of buffer in bytes,
|
|
// not in # of character
|
|
cbComp = (int)ImmGetCompositionString(himc, GCS_RESULTSTR, NULL, 0);
|
|
|
|
pszCompStr = (PSTR)LocalAlloc(LPTR, cbComp + sizeof(TCHAR));
|
|
if (pszCompStr)
|
|
{
|
|
ImmGetCompositionString(himc, GCS_RESULTSTR, (PSTR)pszCompStr, cbComp+sizeof(TCHAR));
|
|
|
|
// With ImmGetCompositionStringW, cbComp is # of bytes copied
|
|
// character position must be calculated by cbComp / sizeof(TCHAR)
|
|
//
|
|
*(TCHAR *)(&pszCompStr[cbComp]) = TEXT('\0');
|
|
Edit_ReplaceSel(hwnd, (LPTSTR)pszCompStr);
|
|
LocalFree((HLOCAL)pszCompStr);
|
|
}
|
|
#ifdef WIN32
|
|
// There's no longer selection
|
|
//
|
|
RemoveProp(hwnd, szIMECompPos);
|
|
|
|
// Get current cursor pos so that the subsequent composition
|
|
// handling will do the right thing.
|
|
//
|
|
dwSel = Edit_GetSel(hwnd);
|
|
#endif
|
|
}
|
|
|
|
if (lParam & GCS_COMPSTR)
|
|
{
|
|
// ImmGetCompositionString() returns length of buffer in bytes,
|
|
// not in # of character
|
|
//
|
|
cbComp = (int)ImmGetCompositionString(himc, GCS_COMPSTR, NULL, 0);
|
|
pszCompStr = (PSTR)LocalAlloc(LPTR, cbComp + sizeof(TCHAR));
|
|
if (pszCompStr)
|
|
{
|
|
ImmGetCompositionString(himc, GCS_COMPSTR, pszCompStr, cbComp+sizeof(TCHAR));
|
|
|
|
// Get position of the current selection
|
|
//
|
|
#ifndef WIN32
|
|
dwSel = Edit_GetSel(hwnd);
|
|
#endif
|
|
cchMax = (int)SendMessage(hwnd, EM_GETLIMITTEXT, 0, 0);
|
|
cchText = Edit_GetTextLength(hwnd);
|
|
|
|
// Cut the composition string if it exceeds limit.
|
|
//
|
|
cbCompNew = min((UINT)cbComp,
|
|
sizeof(TCHAR)*(cchMax-(cchText-(HIWORD(dwSel)-LOWORD(dwSel)))));
|
|
|
|
// wrap up the DBCS at the end of string
|
|
//
|
|
if (cbCompNew < cbComp)
|
|
{
|
|
#ifndef UNICODE
|
|
DoDBCSBoundary((LPSTR)pszCompStr, (int *)&cbCompNew);
|
|
#endif
|
|
|
|
*(TCHAR *)(&pszCompStr[cbCompNew]) = TEXT('\0');
|
|
|
|
// Reset composition string if we cut it.
|
|
ImmSetCompositionString(himc, SCS_SETSTR, pszCompStr, cbCompNew, NULL, 0);
|
|
cbComp = cbCompNew;
|
|
}
|
|
|
|
*(TCHAR *)(&pszCompStr[cbComp]) = TEXT('\0');
|
|
|
|
// Replace the current selection with composition string.
|
|
//
|
|
Edit_ReplaceSel(hwnd, (LPTSTR)pszCompStr);
|
|
|
|
LocalFree((HLOCAL)pszCompStr);
|
|
}
|
|
|
|
// Mark the composition string so that we can replace it again
|
|
// for the next time.
|
|
//
|
|
|
|
#ifdef WIN32
|
|
// Don't setsel to avoid flicking
|
|
if (cbComp)
|
|
{
|
|
dwSel = MAKELONG(LOWORD(dwSel),LOWORD(dwSel)+cbComp/sizeof(TCHAR));
|
|
SetProp(hwnd, szIMECompPos, IntToPtr(dwSel));
|
|
}
|
|
else
|
|
RemoveProp(hwnd, szIMECompPos);
|
|
#else
|
|
// Still use SETSEL for 16bit.
|
|
if (cbComp)
|
|
Edit_SetSel(hwnd, LOWORD(dwSel), LOWORD(dwSel)+cbComp);
|
|
#endif
|
|
|
|
}
|
|
|
|
ImmReleaseContext(hwnd, himc);
|
|
}
|
|
|
|
SendMessage(hwnd, WM_SETREDRAW, (WPARAM)TRUE, 0);
|
|
//
|
|
// We want to update the size of label edit just once at
|
|
// each WM_IME_COMPOSITION processing. ReplaceSel causes several EN_UPDATE
|
|
// and it causes ugly flicking too.
|
|
//
|
|
RedrawWindow(hwnd, NULL, NULL, RDW_INTERNALPAINT|RDW_INVALIDATE);
|
|
SetWindowID(plv->hwndEdit, 1);
|
|
ListView_SetEditSize(plv);
|
|
|
|
plv->flags &= ~LVF_INSERTINGCOMP;
|
|
}
|
|
|
|
void ListView_PaintComposition(HWND hwnd, LV * plv)
|
|
{
|
|
BYTE szCompStr[CCHLABELMAX + 1];
|
|
BYTE szCompAttr[CCHLABELMAX + 1];
|
|
|
|
int cchLine, ichLineStart;
|
|
int cbComp = 0;
|
|
int cchComp;
|
|
int nLine;
|
|
int ichCompStart, ichCompEnd;
|
|
DWORD dwSel;
|
|
int cchMax, cchText;
|
|
HIMC himc = (HIMC)0;
|
|
HDC hdc;
|
|
|
|
|
|
if (plv->flags & LVF_INSERTINGCOMP)
|
|
{
|
|
// This is the case that ImmSetCompositionString() generates
|
|
// WM_IME_COMPOSITION. We're not ready to paint composition here.
|
|
return;
|
|
}
|
|
|
|
if (himc = ImmGetContext(hwnd))
|
|
{
|
|
|
|
cbComp=(UINT)ImmGetCompositionString(himc, GCS_COMPSTR, szCompStr, sizeof(szCompStr));
|
|
|
|
ImmGetCompositionString(himc, GCS_COMPATTR, szCompAttr, sizeof(szCompStr));
|
|
ImmReleaseContext(hwnd, himc);
|
|
}
|
|
|
|
if (cbComp)
|
|
{
|
|
|
|
// Get the position of current selection
|
|
//
|
|
#ifdef WIN32
|
|
|
|
if (!(dwSel = PtrToUlong(GetProp(hwnd, szIMECompPos))))
|
|
dwSel = 0L;
|
|
#else
|
|
dwSel = Edit_GetSel(hwnd);
|
|
#endif
|
|
cchMax = (int)SendMessage(hwnd, EM_GETLIMITTEXT, 0, 0);
|
|
cchText = Edit_GetTextLength(hwnd);
|
|
cbComp = min((UINT)cbComp, sizeof(TCHAR)*(cchMax-(cchText-(HIWORD(dwSel)-LOWORD(dwSel)))));
|
|
#ifndef UNICODE
|
|
DoDBCSBoundary((LPTSTR)szCompStr, (int *)&cbComp);
|
|
#endif
|
|
*(TCHAR *)(&szCompStr[cbComp]) = TEXT('\0');
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////
|
|
// //
|
|
// Draw composition string over the sel string.//
|
|
// //
|
|
/////////////////////////////////////////////////
|
|
|
|
|
|
hdc = GetDC(hwnd);
|
|
|
|
|
|
ichCompStart = LOWORD(dwSel);
|
|
|
|
cchComp = cbComp/sizeof(TCHAR);
|
|
while (ichCompStart < (int)LOWORD(dwSel) + cchComp)
|
|
{
|
|
// Get line from each start pos.
|
|
//
|
|
nLine = Edit_LineFromChar(hwnd, ichCompStart);
|
|
ichLineStart = Edit_LineIndex(hwnd, nLine);
|
|
cchLine= Edit_LineLength(hwnd, ichLineStart);
|
|
|
|
// See if composition string is longer than this line.
|
|
//
|
|
if(ichLineStart+cchLine > (int)LOWORD(dwSel)+cchComp)
|
|
ichCompEnd = LOWORD(dwSel)+cchComp;
|
|
else
|
|
{
|
|
// Yes, the composition string is longer.
|
|
// Take the begining of the next line as next start.
|
|
//
|
|
if (ichLineStart+cchLine > ichCompStart)
|
|
ichCompEnd = ichLineStart+cchLine;
|
|
else
|
|
{
|
|
// If the starting position is not proceeding,
|
|
// let's get out of here.
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Draw the line
|
|
//
|
|
DrawCompositionLine(hwnd, hdc, plv->hfontLabel, (LPTSTR)szCompStr, szCompAttr, ichCompStart, ichCompEnd, LOWORD(dwSel));
|
|
|
|
ichCompStart = ichCompEnd;
|
|
}
|
|
|
|
ReleaseDC(hwnd, hdc);
|
|
// We don't want to repaint the window.
|
|
ValidateRect(hwnd, NULL);
|
|
}
|
|
}
|
|
|
|
#endif FE_IME
|