// report view stuff (details) #include "ctlspriv.h" #include "listview.h" #include void NEAR PASCAL ListView_RInitialize(LV* plv, BOOL fInval) { MEASUREITEMSTRUCT mi; if (plv && (plv->style & LVS_OWNERDRAWFIXED)) { int iOld = plv->cyItem; mi.CtlType = ODT_LISTVIEW; mi.CtlID = GetDlgCtrlID(plv->hwnd); mi.itemHeight = plv->cyItem; // default SendMessage(plv->hwndParent, WM_MEASUREITEM, mi.CtlID, (LPARAM)(MEASUREITEMSTRUCT FAR *)&mi); plv->cyItem = mi.itemHeight; if (fInval && (iOld != plv->cyItem)) { RedrawWindow(plv->hwnd, NULL, NULL, RDW_INVALIDATE | RDW_ERASE); } } } // // Internal function to Get the CXLabel, taking into account if the listview // has no item data and also if RECOMPUTE needs to happen. // SHORT NEAR PASCAL ListView_RGetCXLabel(LV* plv, int i, LISTITEM FAR* pitem, HDC hdc) { SHORT cxLabel; #ifdef WINNT_CAIRO Assert( !ListView_IsOwnerData( plv ) ); #endif #ifndef WINNT_CAIRO if (plv->style & LVS_NOITEMDATA) cxLabel = ListView_NIDGetItemCXLabel(plv, i); else #endif cxLabel = pitem->cxSingleLabel; if (cxLabel == SRECOMPUTE) { HDC hdc2; #ifndef WINNT_CAIRO if (plv->style & LVS_NOITEMDATA) { LISTITEM item; // This function only sets the values so no // need ot initialize lv; hdc2 = ListView_RecomputeLabelSize(plv, &item, i, hdc, FALSE); ListView_NIDSetItemCXLabel(plv, i, item.cxSingleLabel); cxLabel = item.cxSingleLabel; } else #endif { hdc2 = ListView_RecomputeLabelSize(plv, pitem, i, hdc, FALSE); cxLabel = pitem->cxSingleLabel; } if (hdc == NULL) ReleaseDC(HWND_DESKTOP, hdc2); } return(cxLabel); } // // Returns FALSE if no more items to draw. // BOOL NEAR PASCAL ListView_RDrawItem(LV* plv, int i, LISTITEM FAR* pitem, HDC hdc, LPPOINT lpptOrg, RECT FAR* prcClip, UINT fDraw, COLORREF clrText, COLORREF clrTextBk) { RECT rcIcon; RECT rcLabel; RECT rcBounds; RECT rcFullLabel; RECT rcT; int iCol; LV_ITEM item; HD_ITEM hitem; TCHAR ach[CCHLABELMAX]; UINT fText = 0; WORD wItemState; SHORT cxLabel; #ifdef WINNT_CAIRO if (ListView_IsOwnerData( plv )) { LISTITEM litem; HDC hdc; item.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_STATE; item.iItem = i; item.stateMask = LVIS_ALL; item.iSubItem = 0; item.pszText = ach; item.cchTextMax = ARRAYSIZE(ach); ListView_OnGetItem(plv, &item); // save state wItemState = item.state ; litem.pszText = item.pszText; // calculate lable sizes from litem hdc = ListView_RecomputeLabelSize( plv, &litem, i, NULL, TRUE ); ReleaseDC( HWND_DESKTOP, hdc ); cxLabel = litem.cxSingleLabel; ListView_GetRectsOwnerData( plv, i, &rcIcon, &rcFullLabel, &rcBounds, NULL, &litem); } #else if (plv->style & LVS_NOITEMDATA) { wItemState = ListView_NIDGetItemState(plv, i); cxLabel = ListView_NIDGetItemCXLabel(plv, i); ListView_GetRects(plv, i, &rcIcon, &rcFullLabel, &rcBounds, NULL); } #endif else { wItemState = pitem->state; cxLabel = pitem->cxSingleLabel; ListView_GetRects(plv, i, &rcIcon, &rcFullLabel, &rcBounds, NULL); } if (rcBounds.bottom <= plv->yTop) return TRUE; if (prcClip) { if (rcBounds.top >= prcClip->bottom) return FALSE; // no more items need painting. // Probably this condition won't happen very often... if (!IntersectRect(&rcT, &rcBounds, prcClip)) return TRUE; } // REVIEW: this would be faster if we did the GetClientRect // outside the loop. // if (rcBounds.top >= plv->sizeClient.cy) return FALSE; if (lpptOrg) { OffsetRect(&rcIcon, lpptOrg->x - rcBounds.left, lpptOrg->y - rcBounds.top); OffsetRect(&rcFullLabel, lpptOrg->x - rcBounds.left, lpptOrg->y - rcBounds.top); } // if it's owner draw, send off a message and return. // do this after we've collected state information above though if (plv->style & LVS_OWNERDRAWFIXED) { DRAWITEMSTRUCT di; di.CtlType = ODT_LISTVIEW; di.CtlID = GetDlgCtrlID(plv->hwnd); di.itemID = i; di.itemAction = ODA_DRAWENTIRE; di.itemState = 0; di.hwndItem = plv->hwnd; di.hDC = hdc; di.rcItem = rcBounds; di.itemData = pitem->lParam; if (wItemState & LVIS_FOCUSED) { di.itemState |= ODS_FOCUS; } if (wItemState & LVIS_SELECTED) { di.itemState |= ODS_SELECTED; } SendMessage(plv->hwndParent, WM_DRAWITEM, di.CtlID, (LPARAM)(DRAWITEMSTRUCT FAR *)&di); return TRUE; } item.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_STATE; item.iItem = i; item.stateMask = LVIS_ALL; // for first ListView_OnGetItem call item.state = 0; rcLabel = rcFullLabel; for (iCol = 0; iCol < plv->cCol; iCol++) { hitem.mask = HDI_WIDTH | HDI_FORMAT; Header_GetItem(plv->hwndHdr, iCol, &hitem); rcLabel.right = rcLabel.left + hitem.cxy; item.iSubItem = iCol; item.pszText = ach; item.cchTextMax = ARRAYSIZE(ach); ListView_OnGetItem(plv, &item); // Next time through, we only want text for subitems... item.mask = LVIF_TEXT; if (iCol == 0) { fText = ListView_DrawImage(plv, &item, hdc, rcIcon.left, rcIcon.top, fDraw); // Include icon space in column width... // rcLabel.right -= (plv->cxSmIcon + plv->cxState); hitem.cxy -= (plv->cxSmIcon + plv->cxState); } if (item.pszText) { UINT textflags; int cxText; // give all but the first columns extra margins so // left and right justified things don't stick together textflags = (iCol == 0) ? SHDT_ELLIPSES : SHDT_ELLIPSES | SHDT_EXTRAMARGIN; // draw first column of a selected item with its highlight // rectangle limited to the size of the string if (iCol == 0) { textflags |= fText; // if selected or focused, the rectangle is more // meaningful and should correspond to the string if ((fText & SHDT_SELECTED) || (item.state & LVIS_FOCUSED)) { if (cxLabel == SRECOMPUTE) cxLabel = ListView_RGetCXLabel(plv, i, pitem, hdc); // HACK: we know about how SHDrawText() deals with margins! // deal with the margins, first uses normal, // all others use SHDT_EXTRAMARGIN cxText = cxLabel + 2 * g_cxLabelMargin; if (cxText < hitem.cxy) rcLabel.right = rcLabel.left + cxText; } } else { textflags |= SHDT_DESELECTED; } if ((iCol != 0) || (plv->iEdit != i)) { //DebugMsg(DM_TRACE, TEXT("LISTVIEW: SHDrawText called. style = %lx, WS_DISABLED = %lx, plv->clrBk = %lx, plv->clrTextBk = %lx"), (DWORD)plv->style, (DWORD)WS_DISABLED, plv->clrBk, plv->clrTextBk); SHDrawText(hdc, item.pszText, &rcLabel, hitem.fmt & HDF_JUSTIFYMASK, textflags, plv->cyLabelChar, plv->cxEllipses, clrText, clrTextBk); // draw a focus rect on the first column of a focus item if ((iCol == 0) && (fDraw & LVDI_FOCUS) && (item.state & LVIS_FOCUSED)) DrawFocusRect(hdc, &rcLabel); } } rcLabel.left += hitem.cxy; } return TRUE; } BOOL NEAR ListView_CreateHeader(LV* plv) { DWORD dwStyle = HDS_HORZ | WS_CHILD; if (plv->style & LVS_NOCOLUMNHEADER) dwStyle |= HDS_HIDDEN; if (!(plv->style & LVS_NOSORTHEADER)) dwStyle |= HDS_BUTTONS; plv->hwndHdr = CreateWindowEx(0L, c_szHeaderClass, // WC_HEADER, NULL, dwStyle, 0, 0, 0, 0, plv->hwnd, (HMENU)LVID_HEADER, GetWindowInstance(plv->hwnd), NULL); if (plv->hwndHdr) FORWARD_WM_SETFONT(plv->hwndHdr, plv->hfontLabel, FALSE, SendMessage); return (BOOL)plv->hwndHdr; } #ifdef UNICODE int NEAR ListView_OnInsertColumnA(LV* plv, int iCol, LV_COLUMNA * pcol) { LPWSTR pszW = NULL; LPSTR pszC = NULL; int iRet; //HACK ALERT -- this code assumes that LV_COLUMNA is exactly the same // as LV_COLUMNW except for the pointer to the string. Assert(sizeof(LV_COLUMNA) == sizeof(LV_COLUMNW)); if (!pcol) return -1; if ((pcol->mask & LVCF_TEXT) && (pcol->pszText != NULL)) { pszC = pcol->pszText; if ((pszW = ProduceWFromA(pszC)) == NULL) return -1; pcol->pszText = (LPSTR)pszW; } iRet = ListView_OnInsertColumn(plv, iCol, (const LV_COLUMN FAR*) pcol); if (pszW != NULL) { pcol->pszText = pszC; FreeProducedString(pszW); } return iRet; } #endif int NEAR ListView_OnInsertColumn(LV* plv, int iCol, const LV_COLUMN FAR* pcol) { int idpa = -1; HD_ITEM item; Assert(LVCFMT_LEFT == HDF_LEFT); Assert(LVCFMT_RIGHT == HDF_RIGHT); Assert(LVCFMT_CENTER == HDF_CENTER); if (iCol < 0 || !pcol) return -1; if (!plv->hwndHdr && !ListView_CreateHeader(plv)) return -1; item.mask = HDI_ALL; item.pszText = pcol->mask & LVCF_TEXT ? pcol->pszText : (LPTSTR)c_szNULL; item.cxy = pcol->mask & LVCF_WIDTH ? pcol->cx : 10; // some random default item.fmt = ((pcol->mask & LVCF_FMT) && (iCol > 0)) ? pcol->fmt : LVCFMT_LEFT; item.hbm = NULL; item.lParam = pcol->mask & LVCF_SUBITEM ? pcol->iSubItem : 0; // Column 0 refers to the item list. If we've already added a // column, make sure there are plv->cCol - 1 subitem ptr slots // in hdpaSubItems... // if (plv->cCol > 0) { if (!plv->hdpaSubItems) { plv->hdpaSubItems = DPA_CreateEx(8, plv->hheap); if (!plv->hdpaSubItems) return -1; } idpa = DPA_InsertPtr(plv->hdpaSubItems, max(0, iCol - 1), NULL); if (idpa == -1) return -1; } iCol = Header_InsertItem(plv->hwndHdr, iCol, &item); if (iCol == -1) { if (plv->hdpaSubItems && (idpa != -1)) DPA_DeletePtr(plv->hdpaSubItems, idpa); return -1; } plv->xTotalColumnWidth = RECOMPUTE; plv->cCol++; ListView_UpdateScrollBars(plv); return iCol; } void NEAR ListView_FreeColumnData(HDPA hdpa) { int i; for (i = DPA_GetPtrCount(hdpa) - 1; i >= 0; i--) { LPTSTR psz = DPA_FastGetPtr(hdpa, i); if (psz != LPSTR_TEXTCALLBACK) Str_Set(&psz, NULL); } } BOOL NEAR ListView_OnDeleteColumn(LV* plv, int iCol) { if (iCol < 0 || iCol >= plv->cCol) // validate column index { DebugMsg(DM_ERROR, TEXT("ListView: Invalid column index: %d"), iCol); return FALSE; } if (plv->hdpaSubItems) { if (iCol > 0) // can't delete column 0... { HDPA hdpa = (HDPA)DPA_DeletePtr(plv->hdpaSubItems, iCol - 1); if (hdpa) { ListView_FreeColumnData(hdpa); DPA_Destroy(hdpa); } } } if (!Header_DeleteItem(plv->hwndHdr, iCol)) return FALSE; plv->cCol--; plv->xTotalColumnWidth = RECOMPUTE; ListView_UpdateScrollBars(plv); return TRUE; } int NEAR ListView_RGetColumnWidth(LV* plv, int iCol) { HD_ITEM item; item.mask = HDI_WIDTH; Header_GetItem(plv->hwndHdr, iCol, &item); return item.cxy; } BOOL NEAR PASCAL hasVertScroll ( LV* plv ) { RECT rcClient; RECT rcBounds; int cColVis; BOOL fHorSB; // Get the horizontal bounds of the items. ListView_GetClientRect(plv, &rcClient, FALSE, NULL); ListView_RGetRects(plv, 0, NULL, NULL, &rcBounds, NULL); fHorSB = (rcBounds.right - rcBounds.left > rcClient.right); cColVis = (rcClient.bottom - plv->yTop - (fHorSB ? g_cyScrollbar : 0)) / plv->cyItem; // check to see if we need a vert scrollbar if ((int)cColVis < ListView_Count(plv)) return(TRUE); else return(FALSE); } BOOL NEAR ListView_RSetColumnWidth(LV* plv, int iCol, int cx) { HD_ITEM item; HD_ITEM colitem; HDC hdc; SIZE siz; LV_ITEM lviItem; int i; int ItemWidth = 0; int HeaderWidth = 0; TCHAR szLabel[CCHLABELMAX + 4]; // CCHLABLEMAX == MAX_PATH int iBegin; int iEnd; // Should we compute the width based on the widest string? // If we do, include the Width of the Label, and if this is the // Last column, set the width so the right side is at the list view's right edge if (cx <= LVSCW_AUTOSIZE) { hdc = GetDC(plv->hwnd); SelectFont(hdc, plv->hfontLabel); if (cx == LVSCW_AUTOSIZE_USEHEADER) { // Special Cases: // 1) There is only 1 column. Set the width to the width of the listview // 2) This is the rightmost column, set the width so the right edge of the // column coinsides with to right edge of the list view. if (plv->cCol == 1) { RECT rcClient; ListView_GetClientRect(plv, &rcClient, FALSE, NULL); HeaderWidth = rcClient.right - rcClient.left; } else if (iCol == (plv->cCol-1)) { // BUGBUG This will only work if the listview as NOT // been previously horizontally scrolled RECT rcClient; int iX; int colWidth = 0; ListView_GetClientRect(plv, &rcClient, FALSE, NULL); item.mask = HDI_WIDTH; for (iX = 0; iX < (plv->cCol -1); iX++) { Header_GetItem(plv->hwndHdr, iX, &item); colWidth += item.cxy; } // Is if visible if (colWidth < (rcClient.right-rcClient.left)) { HeaderWidth = (rcClient.right-rcClient.left) - colWidth; } } // If we have a header width, then is is one of these special ones, so // we need to account for a vert scroll bar since we are using Client values if (HeaderWidth && hasVertScroll(plv)) { HeaderWidth -= g_cxVScroll; } // Get the Width of the label. colitem.mask = HDI_TEXT; colitem.pszText = szLabel; colitem.cchTextMax = ARRAYSIZE(szLabel); if (Header_GetItem(plv->hwndHdr, iCol, &colitem)) { GetTextExtentPoint(hdc, colitem.pszText, lstrlen(colitem.pszText), &siz); HeaderWidth = max(HeaderWidth, (siz.cx+6*g_cxLabelMargin)); } } iBegin = 0; iEnd = ListView_Count( plv ); #ifdef WINNT_CAIRO // // Loop for each item in the view // if (ListView_IsOwnerData( plv )) { RECT rcClient; GetClientRect( plv->hwnd, &rcClient ); iBegin = (int)((plv->ptlRptOrigin.y + rcClient.top - plv->yTop) / plv->cyItem); iEnd = (int)((plv->ptlRptOrigin.y + rcClient.bottom - plv->yTop) / plv->cyItem) + 1; iBegin = max( 0, iBegin ); iEnd = min( iEnd, ListView_Count( plv ) ); ListView_NotifyCacheHint( plv, iBegin, iEnd-1 ); } #endif // Loop for each item in the List for (i = iBegin; i < iEnd; i++) { lviItem.mask = LVIF_TEXT; lviItem.iItem = i; lviItem.iSubItem = iCol; lviItem.pszText = szLabel; lviItem.cchTextMax = ARRAYSIZE(szLabel); lviItem.stateMask = 0; ListView_OnGetItem(plv, &lviItem); // If there is a Text item, get its width if (lviItem.pszText) { GetTextExtentPoint(hdc, lviItem.pszText, lstrlen(lviItem.pszText), &siz); ItemWidth = max(ItemWidth, siz.cx); } } ReleaseDC(plv->hwnd, hdc); // Adjust by a reasonable border amount. // If col 0, add 2*g_cxLabelMargin + g_szSmIcon. // Otherwise add 6*g_cxLabelMargin. // These amounts are based on Margins added automatically // to the ListView in ShDrawText. // BUGBUG ListView Report format currently assumes and makes // room for a Small Icon. if (iCol == 0) { ItemWidth += plv->cxSmIcon + plv->cxState; ItemWidth += 2*g_cxLabelMargin; } else { ItemWidth += 6*g_cxLabelMargin; } DebugMsg(DM_TRACE, TEXT("ListView: HeaderWidth:%d ItemWidth:%d"), HeaderWidth, ItemWidth); item.cxy = max(HeaderWidth, ItemWidth); } else { // Use supplied width item.cxy = cx; } item.mask = HDI_WIDTH; return Header_SetItem(plv->hwndHdr, iCol, &item); } #ifdef UNICODE BOOL NEAR ListView_OnGetColumnA(LV* plv, int iCol, LV_COLUMNA FAR* pcol) { LPWSTR pszW = NULL; LPSTR pszC = NULL; BOOL fRet; //HACK ALERT -- this code assumes that LV_COLUMNA is exactly the same // as LV_COLUMNW except for the pointer to the string. Assert(sizeof(LV_COLUMNA) == sizeof(LV_COLUMNW)) if (!pcol) return FALSE; if ((pcol->mask & LVCF_TEXT) && (pcol->pszText != NULL)) { pszC = pcol->pszText; pszW = LocalAlloc(LMEM_FIXED, pcol->cchTextMax * sizeof(WCHAR)); if (pszW == NULL) return FALSE; pcol->pszText = (LPSTR)pszW; } fRet = ListView_OnGetColumn(plv, iCol, (LV_COLUMN FAR*) pcol); if (pszW != NULL) { ConvertWToAN(pszC, pcol->cchTextMax, pszW, -1); pcol->pszText = pszC; LocalFree(pszW); } return fRet; } #endif BOOL NEAR ListView_OnGetColumn(LV* plv, int iCol, LV_COLUMN FAR* pcol) { HD_ITEM item; UINT mask; if (!pcol) return FALSE; mask = pcol->mask; if (!mask) return TRUE; item.mask = HDI_FORMAT | HDI_WIDTH | HDI_LPARAM; if (mask & LVCF_TEXT) { Assert(pcol->pszText); item.mask |= HDI_TEXT; item.pszText = pcol->pszText; item.cchTextMax = pcol->cchTextMax; } if (!Header_GetItem(plv->hwndHdr, iCol, &item)) return FALSE; if (mask & LVCF_SUBITEM) pcol->iSubItem = (int)item.lParam; if (mask & LVCF_FMT) pcol->fmt = item.fmt; if (mask & LVCF_WIDTH) pcol->cx = item.cxy; return TRUE; } #ifdef UNICODE BOOL NEAR ListView_OnSetColumnA(LV* plv, int iCol, LV_COLUMNA FAR* pcol) { LPWSTR pszW = NULL; LPSTR pszC = NULL; BOOL fRet; //HACK ALERT -- this code assumes that LV_COLUMNA is exactly the same // as LV_COLUMNW except for the pointer to the string. Assert(sizeof(LV_COLUMNA) == sizeof(LV_COLUMNW)); if (!pcol) return FALSE; if ((pcol->mask & LVCF_TEXT) && (pcol->pszText != NULL)) { pszC = pcol->pszText; if ((pszW = ProduceWFromA(pszC)) == NULL) return FALSE; pcol->pszText = (LPSTR)pszW; } fRet = ListView_OnSetColumn(plv, iCol, (const LV_COLUMN FAR*) pcol); if (pszW != NULL) { pcol->pszText = pszC; FreeProducedString(pszW); } return fRet; } #endif BOOL NEAR ListView_OnSetColumn(LV* plv, int iCol, const LV_COLUMN FAR* pcol) { HD_ITEM item; UINT mask; if (!pcol) return FALSE; mask = pcol->mask; if (!mask) return TRUE; item.mask = 0; if (mask & LVCF_SUBITEM) { item.mask |= HDI_LPARAM; item.lParam = iCol; } if (mask & LVCF_FMT) { item.mask |= HDI_FORMAT; item.fmt = (pcol->fmt | HDF_STRING); } if (mask & LVCF_WIDTH) { item.mask |= HDI_WIDTH; item.cxy = pcol->cx; } if (mask & LVCF_TEXT) { Assert(pcol->pszText); item.mask |= HDI_TEXT; item.pszText = pcol->pszText; item.cchTextMax = 0; } plv->xTotalColumnWidth = RECOMPUTE; return Header_SetItem(plv->hwndHdr, iCol, &item); } BOOL NEAR ListView_SetSubItem(LV* plv, const LV_ITEM FAR* plvi) { LPTSTR psz; int i; int idpa; HDPA hdpa; if (plvi->mask & ~LVIF_TEXT) { DebugMsg(DM_ERROR, TEXT("ListView: Invalid mask: %04x"), plvi->mask); return FALSE; } if (!(plvi->mask & LVIF_TEXT)) return TRUE; i = plvi->iItem; // sub item indices are 1-based... // idpa = plvi->iSubItem - 1; if (idpa < 0 || idpa >= plv->cCol - 1) { DebugMsg(DM_ERROR, TEXT("ListView: Invalid iSubItem: %d"), plvi->iSubItem); return FALSE; } hdpa = ListView_GetSubItemDPA(plv, idpa); if (!hdpa) { hdpa = DPA_CreateEx(LV_HDPA_GROW, plv->hheap); if (!hdpa) return FALSE; DPA_SetPtr(plv->hdpaSubItems, idpa, (void FAR*)hdpa); } psz = NULL; if (plvi->pszText == LPSTR_TEXTCALLBACK) psz = LPSTR_TEXTCALLBACK; else if (!Str_Set(&psz, plvi->pszText)) return FALSE; if (i < DPA_GetPtrCount(hdpa)) { LPTSTR psz = (LPTSTR)DPA_GetPtr(hdpa, i); if (psz != LPSTR_TEXTCALLBACK) Str_Set(&psz, NULL); } if (!DPA_SetPtr(hdpa, i, (void FAR*)psz)) { // REVIEW: Some LPSTR_CALLBACK-aware string handling // functions would be handy... // if (psz != LPSTR_TEXTCALLBACK) Str_Set(&psz, NULL); return FALSE; } // all's well... let's invalidate this if (ListView_IsReportView(plv)) { RECT rc; ListView_RGetRects(plv, plvi->iItem, NULL, NULL, &rc, NULL); RedrawWindow(plv->hwnd, &rc, NULL, RDW_ERASE | RDW_INVALIDATE); } return TRUE; } LPTSTR NEAR ListView_GetSubItemText(LV* plv, int i, int iSubItem) { HDPA hdpa; #ifdef WINNT_CAIRO Assert( !ListView_IsOwnerData( plv )); #endif // Sub items are indexed starting at 1... // AssertMsg(iSubItem > 0 && iSubItem < plv->cCol, TEXT("ListView: Invalid iSubItem: %d"), iSubItem); hdpa = ListView_GetSubItemDPA(plv, iSubItem - 1); if (!hdpa) return NULL; return (LPTSTR)DPA_GetPtr(hdpa, i); } BOOL NEAR ListView_OnGetSubItem(LV* plv, LV_ITEM FAR* plvi) { LPCTSTR psz = NULL; Assert(plvi); if (plvi->mask & ~LVIF_TEXT) { DebugMsg(DM_ERROR, TEXT("ListView: Invalid LV_ITEM mask: %04x"), plvi->mask); return FALSE; } if (plvi->mask & LVIF_TEXT) { Str_GetPtr(ListView_GetSubItemText(plv, plvi->iItem, plvi->iSubItem), plvi->pszText, plvi->cchTextMax); } return TRUE; } void NEAR ListView_RDestroy(LV* plv) { if (plv->hdpaSubItems) { int iCol; for (iCol = plv->cCol - 1; iCol >= 0; iCol--) { HDPA hdpa = (HDPA)DPA_GetPtr(plv->hdpaSubItems, iCol); if (hdpa) { ListView_FreeColumnData(hdpa); DPA_Destroy(hdpa); } } DPA_Destroy(plv->hdpaSubItems); plv->hdpaSubItems = NULL; } } VOID NEAR ListView_RHeaderTrack(LV* plv, HD_NOTIFY FAR * pnm) { // We want to update to show where the column header will be. HDC hdc; HD_ITEM hitem; int iCol; RECT rcBounds; // Statics needed from call to call static int s_xLast = -32767; hdc = GetDC(plv->hwnd); if (hdc == NULL) return; // // First undraw the last marker we drew. // if (s_xLast > 0) { PatBlt(hdc, s_xLast, plv->yTop, g_cxBorder, plv->sizeClient.cy - plv->yTop, PATINVERT); } if (pnm->hdr.code == HDN_ENDTRACK) { s_xLast = -32767; // Some large negative number... } else { // // First we need to calculate the X location of the column // To do this, we will need to know where this column begins // Note: We need the bounding rects to help us know the origin. ListView_GetRects(plv, 0, NULL, NULL, &rcBounds, NULL); for (iCol = 0; iCol < pnm->iItem; iCol++) { hitem.mask = HDI_WIDTH; Header_GetItem(plv->hwndHdr, iCol, &hitem); rcBounds.left += hitem.cxy; } // Draw the new line... s_xLast = rcBounds.left + pnm->pitem->cxy; PatBlt(hdc, s_xLast, plv->yTop, g_cxBorder, plv->sizeClient.cy - plv->yTop, PATINVERT); } ReleaseDC(plv->hwnd, hdc); } // try to use scrollwindow to adjust the columns rather than erasing // and redrawing. void NEAR PASCAL ListView_AdjustColumn(LV * plv, int iWidth) { int x; int i; RECT rcClip, rcScroll; int dx = iWidth - plv->iSelOldWidth; HD_ITEM hitem; if (iWidth == plv->iSelOldWidth) return; // find the x coord of the left side of the iCol for (x = -(int)plv->ptlRptOrigin.x, i = 0; i < plv->iSelCol; i++) { hitem.mask = HDI_WIDTH | HDI_FORMAT; Header_GetItem(plv->hwndHdr, i, &hitem); x += hitem.cxy; } rcClip.left = rcClip.top = 0; rcClip.bottom = plv->sizeClient.cy; rcClip.right = plv->sizeClient.cx; rcScroll = rcClip; rcScroll.left = x + plv->iSelOldWidth; rcClip.left = x + min(plv->iSelOldWidth, iWidth); if (dx < 0) { // make don't leave gaps in between the source and target rect rcScroll.right = rcScroll.left + rcClip.right - rcClip.left; } ScrollWindowEx(plv->hwnd, dx, 0, &rcScroll, &rcClip, NULL, NULL, SW_ERASE | SW_INVALIDATE); // call update because scrollwindowex might have erased the far right // we don't want this invalidate/erase to then enlarge the region // and end up erasing everything. UpdateWindow(plv->hwnd); rcClip.left = x; rcClip.right = max(rcClip.left, x+iWidth); InvalidateRect(plv->hwnd, &rcClip, TRUE); plv->xTotalColumnWidth = RECOMPUTE; ListView_UpdateScrollBars(plv); } BOOL ListView_ForwardHeaderNotify(LV* plv, HD_NOTIFY FAR *pnm) { return SendNotifyEx(plv->hwndParent, pnm->hdr.hwndFrom, pnm->hdr.code, (NMHDR FAR *)pnm, plv->bUnicode); } BOOL NEAR ListView_ROnNotify(LV* plv, int idFrom, NMHDR FAR* pnmhdr) { if (pnmhdr->hwndFrom == plv->hwndHdr) { HD_NOTIFY FAR* pnm = (HD_NOTIFY FAR*)pnmhdr; HD_ITEM hitem; switch (pnm->hdr.code) { case HDN_ITEMCHANGING: if (pnm->pitem->mask & HDI_WIDTH) { hitem.mask = HDI_WIDTH; Header_GetItem(plv->hwndHdr, pnm->iItem, &hitem); plv->iSelCol = pnm->iItem; plv->iSelOldWidth = hitem.cxy; return ListView_ForwardHeaderNotify(plv, pnm); } break; case HDN_ITEMCHANGED: if (pnm->pitem->mask & HDI_WIDTH) { ListView_DismissEdit(plv, FALSE); if (pnm->iItem == plv->iSelCol) { ListView_AdjustColumn(plv, pnm->pitem->cxy); } else { // sanity check. we got screwed, so redraw all RedrawWindow(plv->hwnd, NULL, NULL, RDW_ERASE | RDW_INVALIDATE); } plv->iSelCol == -1; ListView_ForwardHeaderNotify(plv, pnm); } break; case HDN_ITEMCLICK: // // BUGBUG:: Need to pass this and other HDN_ notifications back to // parent. Should we simply pass up the HDN notifications // or should we define equivlent LVN_ notifications... // // Pass column number in iSubItem, not iItem... // ListView_DismissEdit(plv, FALSE); ListView_Notify(plv, -1, pnm->iItem, LVN_COLUMNCLICK); ListView_ForwardHeaderNotify(plv, pnm); SetFocus(plv->hwnd); break; case HDN_TRACK: case HDN_ENDTRACK: ListView_DismissEdit(plv, FALSE); ListView_RHeaderTrack(plv, pnm); ListView_ForwardHeaderNotify(plv, pnm); SetFocus(plv->hwnd); break; case HDN_DIVIDERDBLCLICK: ListView_DismissEdit(plv, FALSE); ListView_RSetColumnWidth(plv, pnm->iItem, -1); ListView_ForwardHeaderNotify(plv, pnm); SetFocus(plv->hwnd); break; case NM_RCLICK: return (UINT)SendNotifyEx(plv->hwndParent, plv->hwndHdr, NM_RCLICK, NULL, plv->bUnicode); } } return FALSE; } /*---------------------------------------------------------------- ** Check for a hit in a report view. ** ** a hit only counts if it's on the icon or the string in the first ** column. so we gotta figure out what this means exactly. yuck. **----------------------------------------------------------------*/ int NEAR ListView_RItemHitTest(LV* plv, int x, int y, UINT FAR* pflags) { int iHit; int i; UINT flags; RECT rcLabel; RECT rcIcon; flags = LVHT_NOWHERE; iHit = -1; i = ListView_RYHitTest(plv, y); if ((i >= 0) && (i < ListView_Count(plv))) { if (plv->style & LVS_OWNERDRAWFIXED) { flags = LVHT_ONITEM; iHit = i; } else { RECT rcSelect; ListView_GetRects(plv, i, &rcIcon, &rcLabel, NULL, &rcSelect); // is the hit in the first column? if ((x < rcIcon.left - g_cxEdge) && x > (rcIcon.left - plv->cxState)) { iHit = i; flags = LVHT_ONITEMSTATEICON; } else if ((x >= rcIcon.left) && (x < rcIcon.right)) { iHit = i; flags = LVHT_ONITEMICON; } else if (x >= rcSelect.left && (x < rcSelect.right)) { iHit = i; flags = LVHT_ONITEMLABEL; } } } *pflags = flags; return iHit; } LPTSTR NEAR ListView_RGetItemText(LV* plv, int i, int iCol) { LPTSTR psz; #ifdef WINNT_CAIRO Assert( !ListView_IsOwnerData( plv )); #endif psz = ListView_GetSubItemText(plv, i, iCol); if (psz) return psz; return LPSTR_TEXTCALLBACK; } // get the rects for report view void NEAR ListView_RGetRects(LV* plv, int iItem, RECT FAR* prcIcon, RECT FAR* prcLabel, RECT FAR* prcBounds, RECT FAR* prcSelectBounds) { RECT rcIcon; RECT rcLabel; int x; int y; LONG ly; int iCol; HD_ITEM item; int cyItem; cyItem = plv->cyItem; // use long math for cases where we have lots-o-items ly = (LONG)iItem * plv->cyItem - plv->ptlRptOrigin.y + plv->yTop; x = - (int)plv->ptlRptOrigin.x; // // Need to check for y overflow into rectangle structure // if so we need to return something reasonable... // For now will simply set it to the max or min that will fit... // if (ly >= (INT_MAX - cyItem)) y = INT_MAX - cyItem; else if ( ly < INT_MIN) y = INT_MIN; else y = (int)ly; rcIcon.left = x + plv->cxState; rcIcon.right = rcIcon.left + plv->cxSmIcon; rcIcon.top = y; rcIcon.bottom = rcIcon.top + cyItem; if (SELECTOROF(prcIcon)) *prcIcon = rcIcon; rcLabel.left = rcIcon.right; rcLabel.right = rcIcon.left; // add in width below rcLabel.top = rcIcon.top; rcLabel.bottom = rcIcon.bottom; // // The label is assumed to be the first column. // if (plv->cCol > 0) { item.mask = HDI_WIDTH; Header_GetItem(plv->hwndHdr, 0, &item); rcLabel.right += item.cxy; } // Save away the label bounds. if (SELECTOROF(prcLabel)) *prcLabel = rcLabel; // See if they also want the Selection bounds of the item if (prcSelectBounds) { int cxLabel; #ifdef WINNT_CAIRO if (ListView_IsOwnerData( plv )) { LISTITEM item; HDC hdc; // calculate lable sizes from iItem hdc = ListView_RecomputeLabelSize( plv, &item, iItem, NULL, FALSE ); ReleaseDC( HWND_DESKTOP, hdc ); cxLabel = item.cxSingleLabel; } else { #endif LISTITEM FAR* pitem; pitem = ListView_FastGetItemPtr(plv, iItem); cxLabel = ListView_RGetCXLabel(plv, iItem, pitem, NULL); #ifdef WINNT_CAIRO } #endif prcSelectBounds->left = rcIcon.left; prcSelectBounds->top = y; prcSelectBounds->right = rcLabel.left + cxLabel + 2 * g_cxLabelMargin; prcSelectBounds->bottom = rcLabel.bottom; if (prcSelectBounds->right > rcLabel.right) prcSelectBounds->right = rcLabel.right; } // And also the Total bounds // // and now for the complete bounds... // if (SELECTOROF(prcBounds)) { // Take care of the easy ones first... prcBounds->left = x; prcBounds->top = y; prcBounds->bottom = rcLabel.bottom; if (plv->xTotalColumnWidth != RECOMPUTE) rcLabel.right = prcBounds->left + plv->xTotalColumnWidth; else { rcLabel.right = x; for (iCol = 0; iCol < plv->cCol; iCol++) { item.mask = HDI_WIDTH; Header_GetItem(plv->hwndHdr, iCol, &item); rcLabel.right += item.cxy; } plv->xTotalColumnWidth = rcLabel.right + (int)plv->ptlRptOrigin.x; } prcBounds->right = rcLabel.right; } } // BUGBUG: this is duplicate code with all the other views! // See whether entire string will fit in *prc; if not, compute number of chars // that will fit, including ellipses. Returns length of string in *pcchDraw. // BOOL NEAR ListView_NeedsEllipses(HDC hdc, LPCTSTR pszText, RECT FAR* prc, int FAR* pcchDraw, int cxEllipses) { int cchText; int cxRect; int ichMin, ichMax, ichMid; SIZE siz; #if !defined(UNICODE) && defined(DBCS) LPTSTR lpsz; #endif cxRect = prc->right - prc->left; cchText = lstrlen(pszText); if (cchText == 0) { *pcchDraw = cchText; return FALSE; } GetTextExtentPoint(hdc, pszText, cchText, &siz); if (siz.cx <= cxRect) { *pcchDraw = cchText; return FALSE; } cxRect -= cxEllipses; // If no room for ellipses, always show first character. // ichMax = 1; if (cxRect > 0) { // Binary search to find character that will fit ichMin = 0; ichMax = cchText; while (ichMin < ichMax) { // Be sure to round up, to make sure we make progress in // the loop if ichMax == ichMin + 1. // ichMid = (ichMin + ichMax + 1) / 2; GetTextExtentPoint(hdc, &pszText[ichMin], ichMid - ichMin, &siz); if (siz.cx < cxRect) { ichMin = ichMid; cxRect -= siz.cx; } else if (siz.cx > cxRect) { ichMax = ichMid - 1; } else { // Exact match up up to ichMid: just exit. // ichMax = ichMid; break; } } // Make sure we always show at least the first character... // if (ichMax < 1) ichMax = 1; } #if !defined(UNICODE) && defined(DBCS) // b#8934 lpsz = &pszText[ichMax]; while ( lpsz-- > pszText ) { if (!IsDBCSLeadByte(*lpsz)) break; } ichMax += ( (&pszText[ichMax] - lpsz) & 1 ) ? 0: 1; #endif *pcchDraw = ichMax; return TRUE; } void NEAR ListView_RUpdateScrollBars(LV* plv) { HD_LAYOUT layout; RECT rcClient; RECT rcBounds; WINDOWPOS wpos; int cColVis, iNewPos, iyDelta = 0, ixDelta = 0; BOOL fHorSB, fReupdate = FALSE; SCROLLINFO si; ListView_GetClientRect(plv, &rcClient, FALSE, NULL); if (!plv->hwndHdr) ListView_CreateHeader(plv); Assert(plv->hwndHdr); layout.pwpos = &wpos; // For now lets try to handle scrolling the header by setting // its window pos. rcClient.left -= (int)plv->ptlRptOrigin.x; layout.prc = &rcClient; Header_Layout(plv->hwndHdr, &layout); rcClient.left += (int)plv->ptlRptOrigin.x; // Move it back over! SetWindowPos(plv->hwndHdr, wpos.hwndInsertAfter, wpos.x, wpos.y, wpos.cx, wpos.cy, wpos.flags | SWP_SHOWWINDOW); // Get the horizontal bounds of the items. ListView_RGetRects(plv, 0, NULL, NULL, &rcBounds, NULL); plv->yTop = rcClient.top; fHorSB = (rcBounds.right - rcBounds.left > rcClient.right); // First guess. cColVis = (rcClient.bottom - rcClient.top - (fHorSB ? g_cyScrollbar : 0)) / plv->cyItem; if (cColVis <= (ListView_Count(plv) - 1)) { //then we're going to have a vertical scrollbar.. make sure our horizontal count is correct rcClient.right -= g_cxScrollbar; if (!fHorSB) { // if we previously thought we weren't going to have a scrollbar, we could be wrong.. // since the vertical bar shrunk our area fHorSB = (rcBounds.right - rcBounds.left > rcClient.right); // First guess. cColVis = (rcClient.bottom - rcClient.top - (fHorSB ? g_cyScrollbar : 0)) / plv->cyItem; } } si.cbSize = sizeof(SCROLLINFO); si.fMask = SIF_PAGE | SIF_RANGE | SIF_POS; si.nPos = (int)(plv->ptlRptOrigin.y / plv->cyItem); si.nPage = cColVis; si.nMin = 0; si.nMax = ListView_Count(plv) - 1; SetScrollInfo(plv->hwnd, SB_VERT, &si, TRUE); // make sure our position and page doesn't hang over max if ((si.nPos + (LONG)si.nPage - 1 > si.nMax) && si.nPos > 0) { iNewPos = (int)si.nMax - (int)si.nPage + 1; if (iNewPos < 0) iNewPos = 0; if (iNewPos != si.nPos) { iyDelta = iNewPos - (int)si.nPos; fReupdate = TRUE; } } si.nPos = (int)plv->ptlRptOrigin.x; si.nPage = rcClient.right - rcClient.left; // We need to subtract 1 here because nMax is 0 based, and nPage is the actual // number of page pixels. So, if nPage and nMax are the same we will get a // horz scroll, since there is 1 more pixel than the page can show, but... rcBounds // is like rcRect, and is the actual number of pixels for the whole thing, so // we need to set nMax so that: nMax - 0 == rcBounds.right - rcBound.left si.nMax = rcBounds.right - rcBounds.left - 1; SetScrollInfo(plv->hwnd, SB_HORZ, &si, TRUE); // make sure our position and page doesn't hang over max if ((si.nPos + (LONG)si.nPage - 1 > si.nMax) && si.nPos > 0) { iNewPos = (int)si.nMax - (int)si.nPage + 1; if (iNewPos < 0) iNewPos = 0; if (iNewPos != si.nPos) { ixDelta = iNewPos - (int)si.nPos; fReupdate = TRUE; } } if (fReupdate) { // we shouldn't recurse because the second time through, si.nPos >0 ListView_RScroll2(plv, ixDelta, iyDelta); ListView_RUpdateScrollBars(plv); DebugMsg(DM_TRACE, TEXT("LISTVIEW: ERROR: We had to recurse!")); } } void FAR PASCAL ListView_RScroll2(LV* plv, int dx, int dy) { LONG ldy; if (dx | dy) { RECT rc; GetClientRect(plv->hwnd, &rc); rc.top = plv->yTop; // We can not do a simple multiply here as we may run into // a case where this will overflow an int.. ldy = (LONG)dy * plv->cyItem; plv->ptlRptOrigin.x += dx; plv->ptlRptOrigin.y += ldy; // handle case where dy is large (greater than int...) if ((ldy > rc.bottom) || (ldy < -rc.bottom)) InvalidateRect(plv->hwnd, NULL, TRUE); else { ScrollWindowEx(plv->hwnd, -dx, (int)-ldy, NULL, &rc, NULL, NULL, SW_INVALIDATE | SW_ERASE); /// this causes horrible flicker/repaint on deletes. // if this is a problem with UI scrolling, we'll have to pass through a // flag when to use this ///UpdateWindow(plv->hwnd); } // if Horizontal scrolling, we should update the location of the // left hand edge of the window... // if (dx != 0) { RECT rcHdr; GetWindowRect(plv->hwndHdr, &rcHdr); MapWindowPoints(HWND_DESKTOP, plv->hwnd, (LPPOINT)&rcHdr, 2); SetWindowPos(plv->hwndHdr, NULL, rcHdr.left - dx, rcHdr.top, rcHdr.right - rcHdr.left + dx, rcHdr.bottom - rcHdr.top, SWP_NOZORDER | SWP_NOACTIVATE); } } } //------------------------------------------------------------------- // Make sure that specified item is visible for report view. // Must handle Large number of items... BOOL NEAR ListView_ROnEnsureVisible(LV* plv, int iItem, BOOL fPartialOK) { LONG dy; LONG yTop; LONG lyTop; yTop = plv->yTop; lyTop = (LONG)iItem * plv->cyItem - plv->ptlRptOrigin.y + plv->yTop; if ((lyTop >= (LONG)yTop) && ((lyTop + plv->cyItem) <= (LONG)plv->sizeClient.cy)) return(TRUE); // we are visible dy = lyTop - yTop; if (dy >= 0) { dy = lyTop + plv->cyItem - plv->sizeClient.cy; if (dy < 0) dy = 0; } if (dy) { int iRound = ((dy > 0) ? 1 : -1) * (plv->cyItem - 1); // Now convert into the number of items to scroll... dy = (dy + iRound) / plv->cyItem; ListView_RScroll2(plv, 0, (int)dy); if (ListView_RedrawEnabled(plv)) { ListView_UpdateScrollBars(plv); } else { ListView_DeleteHrgnInval(plv); plv->hrgnInval = (HRGN)ENTIRE_REGION; plv->flags |= LVF_ERASE; } } return TRUE; } void NEAR ListView_ROnScroll(LV* plv, UINT code, int posNew, UINT sb) { int cLine; cLine = (sb == SB_VERT) ? 1 : plv->cxLabelChar; ListView_ComOnScroll(plv, code, posNew, sb, cLine, -1, ListView_RScroll2); }