// large icon view stuff #include "ctlspriv.h" #include "listview.h" static TCHAR const szIMECompPos[]=TEXT("IMECompPos"); __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); } } 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; } // 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) { StringCchCopy(szLabel, ARRAYSIZE(szLabel), item.pszText); } } 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). // StringCchCopy(szLabelFolded, ARRAYSIZE(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 ( (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; } } } 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 (g_fDBCSInputEnabled) { if (SendMessage(plv->hwndEdit, EM_GETLIMITTEXT, (WPARAM)0, (LPARAM)0)<13) { plv->flags |= LVF_DONTDRAWCOMP; } } 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]; HIMC himc; 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 (g_fDBCSInputEnabled) { if (LOWORD(GetKeyboardLayout(0L)) == 0x0411 && (himc = ImmGetContext(hwndEdit))) { ImmNotifyIME(himc, NI_COMPOSITIONSTR, CPS_COMPLETE, 0L); ImmReleaseContext(hwndEdit, himc); } } // 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) { StringCchCopy(szLabel, ARRAYSIZE(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); } 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); } void ListView_SizeIME(HWND hwnd) { HIMC himc; CANDIDATEFORM candf; 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); } } 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) { StringCchCopy(pszCompStr, ichEnd-ichSt+1, &lpszComp[ichSt]); 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)) { 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)); 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); } // 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); } 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 // 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) { *(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. // // 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); } 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 // if (!(dwSel = PtrToUlong(GetProp(hwnd, szIMECompPos)))) dwSel = 0L; cchMax = (int)SendMessage(hwnd, EM_GETLIMITTEXT, 0, 0); cchText = Edit_GetTextLength(hwnd); cbComp = min((UINT)cbComp, sizeof(TCHAR)*(cchMax-(cchText-(HIWORD(dwSel)-LOWORD(dwSel))))); *(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); } }