#include "ctlspriv.h" #include "listview.h" #include "image.h" #ifndef LVS_SHOWSELALWAYS #define LVS_SHOWSELALWAYS 0x008 #endif // BUGBUG -- penwin.h is screwy; define local stuff for now #define HN_BEGINDIALOG 40 // Lens/EditText/garbage detection dialog is about // to come up on this hedit/bedit #define HN_ENDDIALOG 41 // Lens/EditText/garbage detection dialog has // just been destroyed //--------------------------------------------------------- // no way am I gonna make TWO function calls where I can do FOUR comparisons! // #define RECTS_IN_SIZE( sz, r2 ) (!RECTS_NOT_IN_SIZE( sz, r2 )) #define RECTS_NOT_IN_SIZE( sz, r2 ) (\ ( (sz).cx <= (r2).left ) ||\ ( 0 >= (r2).right ) ||\ ( (sz).cy <= (r2).top ) ||\ ( 0 >= (r2).bottom ) ) //--------------------------------------------------------- void NEAR ListView_OnUpdate(LV* plv, int i); void NEAR ListView_OnDestroy(LV* plv); BOOL NEAR PASCAL ListView_ValidateScrollParams(LV* plv, int FAR * dx, int FAR *dy); #ifdef FOLDING_NAMES void NEAR ListView_InvalidateFoldedItem(LV* plv, int iItem, BOOL fSelectionOnly, UINT fRedraw); #endif LRESULT WINAPI SendNotifyEx(HWND hwndTo, HWND hwndFrom, int code, NMHDR FAR* pnmhdr, BOOL bUnicode) { NMHDR nmhdr; int id; #ifdef UNICODE LPVOID pvThunk1 = NULL; LPVOID pvThunk2 = NULL; DWORD dwThunkSize = 0; LRESULT lRet; BOOL bSet = FALSE; #endif // // If hwndFrom is -1, then a WM_NOTIFY is being forwared // from one control to a parent. EG: Tooltips sent // a WM_NOTIFY to toolbar, and toolbar is forwarding it // to the real parent window. // if (hwndFrom != (HWND) -1) { id = hwndFrom ? GetDlgCtrlID(hwndFrom) : 0; if (!pnmhdr) pnmhdr = &nmhdr; pnmhdr->hwndFrom = hwndFrom; pnmhdr->idFrom = id; pnmhdr->code = code; } else { id = pnmhdr->idFrom; code = pnmhdr->code; } #ifdef UNICODE /* * All the thunking for Notify Messages happens here */ if (!bUnicode) { switch( code ) { #ifdef WINNT_CAIRO case LVN_ODFINDITEMW: { LV_FINDINFO *plvfi; pnmhdr->code = LVN_ODFINDITEMA; // Hack Alert! This code assumes that all fields of LV_FINDINFOA and // LV_FINDINFOW are exactly the same except for the string pointers. Assert(sizeof(LV_FINDINFOA) == sizeof(LV_FINDINFOW)); // Since WCHARs are bigger than char, we will just use the // wchar buffer to hold the chars, and not worry about the extra // room at the end. Assert(sizeof(WCHAR) >= sizeof(char)); plvfi = &((PNM_FINDITEM)pnmhdr)->lvfi; pvThunk1 = (PVOID)plvfi->psz; dwThunkSize = lstrlen(pvThunk1) + 1; plvfi->psz = (LPWSTR)ProduceAFromW(plvfi->psz); } break; #endif case LVN_GETDISPINFOW: { LV_ITEMW *pitem; pnmhdr->code = LVN_GETDISPINFOA; // Hack Alert! This code assumes that all fields of LV_DISPINFOA and // LV_DISPINFOW are exactly the same except for the string pointers. Assert(sizeof(LV_DISPINFOA) == sizeof(LV_DISPINFOW)); // Since WCHARs are bigger than char, we will just use the // wchar buffer to hold the chars, and not worry about the extra // room at the end. Assert(sizeof(WCHAR) >= sizeof(char)); // // Some sleazebag code (shell32.dll) just changes the pszText // pointer to point to the name, so capture the original pointer // so we can detect this and not smash their data. // pitem = &(((LV_DISPINFOW *)pnmhdr)->item); if (!IsFlagPtr(pitem) && (pitem->mask & LVIF_TEXT) && !IsFlagPtr(pitem->pszText)) { pvThunk1 = pitem->pszText; dwThunkSize = pitem->cchTextMax; } break; } case LVN_ENDLABELEDITW: pnmhdr->code = LVN_ENDLABELEDITA; bSet = TRUE; // Fall through... case LVN_BEGINLABELEDITW: if (!bSet) { pnmhdr->code = LVN_BEGINLABELEDITA; bSet = TRUE; } // Fall through... case LVN_SETDISPINFOW: { LV_ITEMW *pitem; if (!bSet) { pnmhdr->code = LVN_SETDISPINFOA; } pitem = &(((LV_DISPINFOW *)pnmhdr)->item); if (!IsFlagPtr(pitem) && (pitem->mask & LVIF_TEXT) && !IsFlagPtr(pitem->pszText)) { pvThunk1 = pitem->pszText; dwThunkSize = pitem->cchTextMax; pvThunk2 = ProduceAFromW(pitem->pszText); pitem->pszText = (LPWSTR)pvThunk2; } break; } case TVN_SELCHANGINGW: pnmhdr->code = TVN_SELCHANGINGA; bSet = TRUE; case TVN_SELCHANGEDW: if (!bSet) { pnmhdr->code = TVN_SELCHANGEDA; bSet = TRUE; } /* * These msgs have a NM_TREEVIEW with both TV_ITEMs filled in * * FALL THROUGH TO TVN_DELETEITEM to thunk itemOld then go on for * the other structure. */ case TVN_DELETEITEMW: { /* * This message has a NM_TREEVIEW in lParam with itemOld filled in */ LPTV_ITEMW pitem; if (!bSet) { pnmhdr->code = TVN_DELETEITEMA; bSet = TRUE; } pitem = &(((LPNM_TREEVIEWW)pnmhdr)->itemOld); // thunk itemOld if ( (pitem->mask & TVIF_TEXT) && !IsFlagPtr(pitem->pszText)) { pvThunk2 = pitem->pszText; pitem->pszText = (LPWSTR)ProduceAFromW(pvThunk2); } // if this is deleteitem then we are done if (pnmhdr->code == TVN_DELETEITEMA) break; /* FALL THROUGH TO TVN_ITEMEXPANDING to thunk itemNew */ } case TVN_ITEMEXPANDINGW: if (!bSet) { pnmhdr->code = TVN_ITEMEXPANDINGA; bSet = TRUE; } case TVN_ITEMEXPANDEDW: if (!bSet) { pnmhdr->code = TVN_ITEMEXPANDEDA; bSet = TRUE; } case TVN_BEGINDRAGW: if (!bSet) { pnmhdr->code = TVN_BEGINDRAGA; bSet = TRUE; } case TVN_BEGINRDRAGW: { /* these msgs have a NM_TREEVIEW with itemNew TV_ITEM filled in */ LPTV_ITEMW pitem; if (!bSet) { pnmhdr->code = TVN_BEGINRDRAGA; } pitem = &(((LPNM_TREEVIEWW)pnmhdr)->itemNew); if ( (pitem->mask & TVIF_TEXT) && !IsFlagPtr(pitem->pszText)) { pvThunk1 = pitem->pszText; pitem->pszText = (LPWSTR)ProduceAFromW(pvThunk1); } break; } case TVN_SETDISPINFOW: pnmhdr->code = TVN_SETDISPINFOA; bSet = TRUE; case TVN_BEGINLABELEDITW: if (!bSet) { pnmhdr->code = TVN_BEGINLABELEDITA; bSet = TRUE; } case TVN_ENDLABELEDITW: { /* * All these messages have a TV_DISPINFO in lParam. */ LPTV_ITEMW pitem; if (!bSet) { pnmhdr->code = TVN_ENDLABELEDITA; } pitem = &(((TV_DISPINFOW *)pnmhdr)->item); if ((pitem->mask & TVIF_TEXT) && !IsFlagPtr(pitem->pszText)) { pvThunk1 = pitem->pszText; dwThunkSize = pitem->cchTextMax; pvThunk2 = ProduceAFromW(pitem->pszText); pitem->pszText = (LPWSTR)pvThunk2; } break; } case TVN_GETDISPINFOW: { /* * All these messages have a TV_DISPINFO in lParam. */ LPTV_ITEMW pitem; pnmhdr->code = TVN_GETDISPINFOA; pitem = &(((TV_DISPINFOW *)pnmhdr)->item); if ((pitem->mask & TVIF_TEXT) && !IsFlagPtr(pitem->pszText) && pitem->cchTextMax) { pvThunk1 = pitem->pszText; dwThunkSize = pitem->cchTextMax; pvThunk2 = LocalAlloc(LMEM_FIXED, pitem->cchTextMax * sizeof(char)); pitem->pszText = pvThunk2; pitem->pszText[0] = TEXT('\0'); } break; } case HDN_ITEMCHANGINGW: pnmhdr->code = HDN_ITEMCHANGINGA; bSet = TRUE; case HDN_ITEMCHANGEDW: if (!bSet) { pnmhdr->code = HDN_ITEMCHANGEDA; bSet = TRUE; } case HDN_ITEMCLICKW: if (!bSet) { pnmhdr->code = HDN_ITEMCLICKA; bSet = TRUE; } case HDN_ITEMDBLCLICKW: if (!bSet) { pnmhdr->code = HDN_ITEMDBLCLICKA; bSet = TRUE; } case HDN_DIVIDERDBLCLICKW: if (!bSet) { pnmhdr->code = HDN_DIVIDERDBLCLICKA; bSet = TRUE; } case HDN_BEGINTRACKW: if (!bSet) { pnmhdr->code = HDN_BEGINTRACKA; bSet = TRUE; } case HDN_ENDTRACKW: if (!bSet) { pnmhdr->code = HDN_ENDTRACKA; bSet = TRUE; } case HDN_TRACKW: { HD_ITEMW *pitem; if (!bSet) { pnmhdr->code = HDN_TRACKA; } pitem = ((HD_NOTIFY *)pnmhdr)->pitem; if ( !IsFlagPtr(pitem) && (pitem->mask & HDI_TEXT) && !IsFlagPtr(pitem->pszText)) { pvThunk1 = pitem->pszText; dwThunkSize = pitem->cchTextMax; pitem->pszText = (LPWSTR)ProduceAFromW(pvThunk1); } break; } case TBN_GETBUTTONINFOW: { LPTBNOTIFYW pTBNW; pnmhdr->code = TBN_GETBUTTONINFOA; pTBNW = (LPTBNOTIFYW)pnmhdr; pvThunk1 = pTBNW->pszText; dwThunkSize = pTBNW->cchText; pvThunk2 = GlobalAlloc (GPTR, pTBNW->cchText * sizeof(char)); if (!pvThunk2) { break; } pTBNW->pszText = pvThunk2; WideCharToMultiByte(CP_ACP, 0, (LPWSTR)pvThunk1, -1, (LPSTR)pTBNW->pszText, pTBNW->cchText, NULL, NULL); } break; case TTN_NEEDTEXTW: { LPTOOLTIPTEXTA lpTTTA; LPTOOLTIPTEXTW lpTTTW = (LPTOOLTIPTEXTW) pnmhdr; lpTTTA = GlobalAlloc(GPTR, sizeof(TOOLTIPTEXTA)); if (!lpTTTA) return 0; lpTTTA->hdr.hwndFrom = lpTTTW->hdr.hwndFrom; lpTTTA->hdr.idFrom = lpTTTW->hdr.idFrom; lpTTTA->hdr.code = TTN_NEEDTEXTA; lpTTTA->lpszText = lpTTTA->szText; lpTTTA->hinst = lpTTTW->hinst; lpTTTA->uFlags = lpTTTW->uFlags; pvThunk1 = pnmhdr; pnmhdr = (NMHDR FAR *)lpTTTA; } break; default: /* No thunking needed */ break; } lRet = SendMessage(hwndTo, WM_NOTIFY, (WPARAM)id, (LPARAM)pnmhdr); /* * All the thunking for Notify Messages happens here */ switch(pnmhdr->code) { #ifdef WINNT_CAIRO case LVN_ODFINDITEMA: { LV_FINDINFO *plvfi; plvfi = &((PNM_FINDITEM)pnmhdr)->lvfi; FreeProducedString( (LPWSTR)plvfi->psz); plvfi->psz = pvThunk1; } break; #endif case LVN_GETDISPINFOA: { LPWSTR pszW; LV_ITEMW *pitem; pitem = &(((LV_DISPINFOW *)pnmhdr)->item); if (!IsFlagPtr(pitem) && (pitem->mask & LVIF_TEXT) && !IsFlagPtr(pitem->pszText)) { if (pvThunk1 == pitem->pszText) { pszW = ProduceWFromA((LPSTR)(pitem->pszText)); if (pszW) lstrcpy( pitem->pszText, pszW ); FreeProducedString(pszW); } else { // // The pointer has been changed out from underneath us, copy // unicode back into the original buffer. // ConvertAToWN(pvThunk1, dwThunkSize, (LPSTR)pitem->pszText, -1); pitem->pszText = pvThunk1; } } break; } case LVN_ENDLABELEDITA: case LVN_BEGINLABELEDITA: case LVN_SETDISPINFOA: { LV_ITEMW *pitem; if (!IsFlagPtr(pvThunk1)) { pitem = &(((LV_DISPINFOW *)pnmhdr)->item); if (!IsFlagPtr((LPSTR)pitem->pszText)) { ConvertAToWN(pvThunk1, dwThunkSize, (LPSTR)(pitem->pszText), -1); } FreeProducedString(pvThunk2); pitem->pszText = pvThunk1; } break; } case TVN_SELCHANGINGA: case TVN_SELCHANGEDA: case TVN_DELETEITEMA: { LPTV_ITEMW pitem; if ( !IsFlagPtr(pvThunk2) ) { pitem = &(((LPNM_TREEVIEWW)pnmhdr)->itemOld); FreeProducedString(pitem->pszText); pitem->pszText = pvThunk2; } // if this is delitem, then we are done if (code == TVN_DELETEITEM) break; /* FALL THROUGH TO TVN_ITEMEXPANDING to unthunk itemNew */ } case TVN_ITEMEXPANDINGA: case TVN_ITEMEXPANDEDA: case TVN_BEGINDRAGA: case TVN_BEGINRDRAGA: { /* these msgs have a NM_TREEVIEW with itemNew TV_ITEM filled in */ LPTV_ITEMW pitem; if (!IsFlagPtr(pvThunk1)) { pitem = &(((LPNM_TREEVIEWW)pnmhdr)->itemNew); FreeProducedString(pitem->pszText); pitem->pszText = pvThunk1; } break; } case TVN_SETDISPINFOA: case TVN_BEGINLABELEDITA: case TVN_ENDLABELEDITA: { /* All these messages have a TV_DISPINFO in lParam */ LPTV_ITEMW pitem; if (!IsFlagPtr(pvThunk1)) { pitem = &(((TV_DISPINFOW *)pnmhdr)->item); if (!IsFlagPtr((LPSTR)pitem->pszText)) { ConvertAToWN(pvThunk1, dwThunkSize, (LPSTR)(pitem->pszText), -1); } FreeProducedString(pvThunk2); pitem->pszText = pvThunk1; } break; } case TVN_GETDISPINFOA: { /* * This message has a TV_DISPINFO in lParam that wass filled in * during the callback and needs to be unthunked. */ LPTV_ITEMW pitem; pitem = &(((TV_DISPINFOW *)pnmhdr)->item); if (!IsFlagPtr(pvThunk1) && (pitem->mask & TVIF_TEXT) && !IsFlagPtr(pitem->pszText)) { ConvertAToWN(pvThunk1, dwThunkSize, (LPSTR)pitem->pszText, -1); pitem->pszText = pvThunk1; LocalFree(pvThunk2); } break; } case HDN_ITEMCHANGINGA: case HDN_ITEMCHANGEDA: case HDN_ITEMCLICKA: case HDN_ITEMDBLCLICKA: case HDN_DIVIDERDBLCLICKA: case HDN_BEGINTRACKA: case HDN_ENDTRACKA: case HDN_TRACKA: { HD_ITEMW *pitem; pitem = ((HD_NOTIFY *)pnmhdr)->pitem; if ( !IsFlagPtr(pitem) && (pitem->mask & HDI_TEXT) && !IsFlagPtr(pvThunk1)) { ConvertAToWN(pvThunk1, dwThunkSize, (LPSTR)(pitem->pszText), -1); FreeProducedString(pitem->pszText); pitem->pszText = pvThunk1; } break; } case TBN_GETBUTTONINFOA: { LPTBNOTIFYW pTBNW; pTBNW = (LPTBNOTIFYW)pnmhdr; ConvertAToWN(pvThunk1, dwThunkSize, (LPSTR)(pTBNW->pszText), -1); pTBNW->pszText = pvThunk1; GlobalFree(pvThunk2); } break; case TTN_NEEDTEXTA: { LPTOOLTIPTEXTA lpTTTA = (LPTOOLTIPTEXTA) pnmhdr; LPTOOLTIPTEXTW lpTTTW = (LPTOOLTIPTEXTW) pvThunk1; ThunkToolTipTextAtoW (lpTTTA, lpTTTW); GlobalFree(lpTTTA); } break; default: /* No thunking needed */ break; } return lRet; } else #endif return(SendMessage(hwndTo, WM_NOTIFY, (WPARAM)id, (LPARAM)pnmhdr)); } LRESULT WINAPI SendNotify(HWND hwndTo, HWND hwndFrom, int code, NMHDR FAR* pnmhdr) { // // SendNotify is obsolete. New code should call SendNotifyEx // instead. However, if something does call SendNotify, // it will call SendNotifyEx with FALSE as the Unicode parameter, // because it probably is ANSI code. // return SendNotifyEx(hwndTo, hwndFrom, code, pnmhdr, FALSE); } #ifndef WIN31 // we only want SendNotify for prop-sheets #pragma code_seg(CODESEG_INIT) BOOL FAR ListView_Init(HINSTANCE hinst) { WNDCLASS wc; if (!GetClassInfo(hinst, c_szListViewClass, &wc)) { #ifndef WIN32 LRESULT CALLBACK _ListView_WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); wc.lpfnWndProc = _ListView_WndProc; #else wc.lpfnWndProc = ListView_WndProc; #endif wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hIcon = NULL; wc.lpszMenuName = NULL; wc.hInstance = hinst; wc.lpszClassName = c_szListViewClass; wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); // NULL; wc.style = CS_DBLCLKS | CS_GLOBALCLASS; wc.cbWndExtra = sizeof(LV*); wc.cbClsExtra = 0; return RegisterClass(&wc); } return TRUE; } #pragma code_seg() #ifdef WINNT_CAIRO BOOL NEAR ListView_NotifyCacheHint(LV* plv, int iFrom, int iTo) { NM_CACHEHINT nm; Assert( iFrom <= iTo ); if (iFrom <= iTo) { nm.iFrom = iFrom; nm.iTo = iTo; return !(BOOL)SendNotifyEx(plv->hwndParent, plv->hwnd, LVN_ODCACHEHINT, &nm.hdr, plv->bUnicode); } return FALSE; } int NEAR ListView_RequestFindItem(LV* plv, CONST LV_FINDINFO* plvfi, int iStart) { NM_FINDITEM nm; nm.lvfi = *plvfi; nm.iStart = iStart; return SendNotifyEx(plv->hwndParent, plv->hwnd, LVN_ODFINDITEM, &nm.hdr, plv->bUnicode); } #endif BOOL NEAR ListView_SendChange(LV* plv, int i, int iSubItem, int code, UINT oldState, UINT newState, UINT changed, int x, int y, LPARAM lParam) { NM_LISTVIEW nm; nm.iItem = i; nm.iSubItem = iSubItem; nm.uNewState = newState; nm.uOldState = oldState; nm.uChanged = changed; nm.ptAction.x = x; nm.ptAction.y = y; nm.lParam = lParam; return !(BOOL)SendNotifyEx(plv->hwndParent, plv->hwnd, code, &nm.hdr, plv->bUnicode); } BOOL NEAR ListView_Notify(LV* plv, int i, int iSubItem, int code) { NM_LISTVIEW nm; nm.iItem = i; nm.iSubItem = iSubItem; nm.uNewState = nm.uOldState = 0; nm.uChanged = 0; nm.lParam = 0; #ifdef WINNT_CAIRO if (!ListView_IsOwnerData( plv )) { #endif if (code == LVN_DELETEITEM) { LISTITEM FAR * pItem = ListView_GetItemPtr(plv, i); if (pItem) { nm.lParam = pItem->lParam; } } #ifdef WINNT_CAIRO } #endif return (BOOL)SendNotifyEx(plv->hwndParent, plv->hwnd, code, &nm.hdr, plv->bUnicode); } int NEAR ListView_OnSetItemCount(LV *plv, int iItems) { BOOL frt = TRUE; #ifdef WINNT_CAIRO if (ListView_IsOwnerData( plv )) { int iItem; if ((iItems >= 0) && (iItems <= MAX_LISTVIEWITEMS)) { LONG cChange; plv->cTotalItems = iItems; // check focus if (plv->iFocus >= iItems) plv->iFocus = -1; // check mark if (plv->iMark >= iItems) plv->iMark = -1; // make sure no selections above number of items cChange = SelRange_ExcludeRange( plv->hselrange, iItems, SELRANGE_MAXVALUE ); if (SELRANGE_ERROR == cChange) { //BUGBUG: Return low memory status //MemoryLowDlg( plv->hwnd ); return FALSE; } else { plv->nSelected -= cChange; } plv->rcView.left = RECOMPUTE; // recompute view rect if ( ListView_IsSmallView(plv) || ListView_IsIconView(plv) ) { // Call off to the arrange function. ListView_OnArrange(plv, LVA_DEFAULT); } else { ListView_Recompute(plv); // what is the important item iItem = (plv->iFocus >= 0) ? plv->iFocus : ListView_OnGetNextItem(plv, -1, LVNI_SELECTED); iItem = max(0, iItem); // We may try to resize the column ListView_MaybeResizeListColumns(plv, 0, ListView_Count(plv)-1); // make important item visable ListView_OnEnsureVisible(plv, iItem, FALSE); } InvalidateRect(plv->hwnd, NULL, TRUE); ListView_UpdateScrollBars(plv); } else { frt = FALSE; } } else { #endif if (plv->hdpaSubItems) { int iCol; for (iCol = plv->cCol - 1; iCol >= 0; iCol--) { HDPA hdpa = ListView_GetSubItemDPA(plv, iCol); if (hdpa) // this is optional, call backs don't have them DPA_Grow(hdpa, iItems); } } DPA_Grow(plv->hdpa, iItems); DPA_Grow(plv->hdpaZOrder, iItems); #ifdef WINNT_CAIRO } #endif return frt; } typedef struct _LVSortInfo { LV* plv; BOOL fSortIndices; PFNLVCOMPARE pfnCompare; LPARAM lParam; } LVSortInfo; int CALLBACK ListView_SortCallback(LPVOID dw1, LPVOID dw2, LPARAM lParam) { LISTITEM FAR *pitem1; LISTITEM FAR *pitem2; LVSortInfo FAR *pSortInfo = (LVSortInfo FAR *)lParam; #ifdef WINNT_CAIRO Assert(!ListView_IsOwnerData(pSortInfo->plv)); #endif // determine whether dw1 and dw2 are indices or the real items // and assign pitem? accordingly if (pSortInfo->fSortIndices) { pitem1 = ListView_GetItemPtr(pSortInfo->plv, (UINT)(DWORD)dw1); pitem2 = ListView_GetItemPtr(pSortInfo->plv, (UINT)(DWORD)dw2); } else { pitem1 = (LISTITEM FAR *)dw1; pitem2 = (LISTITEM FAR *)dw2; } if (!pSortInfo->pfnCompare) { // bugbug, should allow callbacks in text if (pitem1->pszText && (pitem1->pszText != LPSTR_TEXTCALLBACK) && pitem2->pszText && (pitem2->pszText != LPSTR_TEXTCALLBACK) ) { return lstrcmpi(pitem1->pszText, pitem2->pszText); } } else { return(pSortInfo->pfnCompare(pitem1->lParam, pitem2->lParam, pSortInfo->lParam)); } Assert(0); return -1; } BOOL NEAR PASCAL ListView_SortAllColumns(LV* plv, LVSortInfo FAR * psi) { #ifdef WINNT_CAIRO Assert(!ListView_IsOwnerData(plv)); #endif if ((!plv->hdpaSubItems) || !DPA_GetPtrCount(plv->hdpaSubItems)) { psi->fSortIndices = FALSE; return (DPA_Sort(plv->hdpa, ListView_SortCallback, (LPARAM)psi)); } else { // if we need to sort several hdpa's, create one DPA of just indices // and sort that, then fix up all the dpa's BOOL fReturn = FALSE; HDPA hdpa; int i; int iMax; void FAR * FAR * ph; void FAR * FAR *pNewIndices; // initialize the hdpa with indices hdpa = DPA_Clone(plv->hdpa, NULL); if (hdpa) { Assert(DPA_GetPtrCount(plv->hdpa) == DPA_GetPtrCount(hdpa)); ph = pNewIndices = DPA_GetPtrPtr(hdpa); iMax = DPA_GetPtrCount(hdpa); for( i = 0; i < iMax; ph++, i++) { *ph = (LPVOID)(HANDLE)i; } psi->fSortIndices = TRUE; if (DPA_Sort(hdpa, ListView_SortCallback, (LPARAM)psi)) { #ifdef WIN32 ph = LocalAlloc(LPTR, sizeof(LPVOID) * iMax); #else ph = Alloc(sizeof(LPVOID) * iMax); #endif if (ph) { int j; void FAR * FAR *pSubItems; for (i = DPA_GetPtrCount(plv->hdpaSubItems) - 1; i >= 0; i--) { HDPA hdpaSubItem = ListView_GetSubItemDPA(plv, i); if (hdpaSubItem) { // make sure it's of the right size while (DPA_GetPtrCount(hdpaSubItem) < iMax) { if (DPA_InsertPtr(hdpaSubItem, iMax, NULL) == -1) goto Bail; } // actually copy across the dpa with the new indices pSubItems = DPA_GetPtrPtr(hdpaSubItem); for (j = 0; j < iMax; j++) { ph[j] = pSubItems[(UINT)(DWORD)pNewIndices[j]]; } // finally, copy it all back to the pSubItems; hmemcpy(pSubItems, ph, sizeof(LPVOID) * iMax); } } // now do the main hdpa pSubItems = DPA_GetPtrPtr(plv->hdpa); for (j = 0; j < iMax; j++) { ph[j] = pSubItems[(int)(DWORD)pNewIndices[j]]; } // finally, copy it all back to the pSubItems; hmemcpy(pSubItems, ph, sizeof(LPVOID) * iMax); fReturn = TRUE; Bail: #ifdef WIN32 LocalFree(ph); #else Free(ph); #endif } } DPA_Destroy(hdpa); } return fReturn; } } BOOL NEAR PASCAL ListView_OnSortItems(LV *plv, LPARAM lParam, PFNLVCOMPARE pfnCompare) { LVSortInfo SortInfo; LISTITEM FAR *pitemFocused; SortInfo.pfnCompare = pfnCompare; SortInfo.lParam = lParam; SortInfo.plv = plv; #ifdef WINNT_CAIRO if (ListView_IsOwnerData(plv)) { Assert(!ListView_IsOwnerData(plv)); return FALSE; } #endif // we're going to screw with the indices, so stash away the pointer to the // focused item. if (plv->iFocus != -1) { pitemFocused = ListView_GetItemPtr(plv, plv->iFocus); } else pitemFocused = NULL; if (ListView_SortAllColumns(plv, &SortInfo)) { // restore the focused item. if (pitemFocused) { int i; for (i = ListView_Count(plv) - 1; i >= 0 ; i--) { if (ListView_GetItemPtr(plv, i) == pitemFocused) { plv->iFocus = i; plv->iMark = i; } } } if (ListView_IsSmallView(plv) || ListView_IsIconView(plv)) { ListView_CommonArrange(plv, LVA_DEFAULT, plv->hdpa); } else if (ListView_IsReportView(plv) || ListView_IsListView(plv)) { InvalidateRect(plv->hwnd, NULL, TRUE); } return(TRUE); } return FALSE; } void PASCAL ListView_EnableWindow(LV* plv, BOOL wParam) { if (wParam) { if (plv->style & WS_DISABLED) { plv->style &= ~WS_DISABLED; // enabled ListView_OnSetBkColor(plv, plv->clrBkSave); } } else { if (!(plv->style & WS_DISABLED)) { plv->clrBkSave = plv->clrBk; plv->style |= WS_DISABLED; // disabled ListView_OnSetBkColor(plv, g_clrBtnFace); } } RedrawWindow(plv->hwnd, NULL, NULL, RDW_INVALIDATE | RDW_ERASE); } void NEAR PASCAL LV_OnShowWindow(LV* plv, BOOL fShow) { if (fShow) { if (!(plv->flags & LVF_VISIBLE)) { plv->flags |= LVF_VISIBLE; ListView_UpdateScrollBars(plv); } } else plv->flags &= ~LVF_VISIBLE; } LRESULT NEAR PASCAL ListView_OnHelp(LV* plv, LPHELPINFO lpHelpInfo) { // If we're seeing WM_HELP because of our child header control, then // munge the HELPINFO structure to use the ListView's control id. // win\core\user\combo.c has similiar code to handle the child edit // control of a combo box. if ((lpHelpInfo != NULL) && ((plv->style & LVS_TYPEMASK) == LVS_REPORT) && (lpHelpInfo->iCtrlId == LVID_HEADER)) { lpHelpInfo->hItemHandle = plv->hwnd; lpHelpInfo->iCtrlId = GetWindowID(plv->hwnd); // Shouldn't have to do this: USER would have filled in the appropriate // context id by walking up the parent hwnd chain. //lpHelpInfo->dwContextId = GetContextHelpId(hwnd); } return DefWindowProc(plv->hwnd, WM_HELP, 0, (LPARAM)lpHelpInfo); } LRESULT CALLBACK ListView_WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { LV* plv = ListView_GetPtr(hwnd); if (plv == NULL) { if (msg == WM_NCCREATE) { InitGlobalMetrics(0); plv = (LV*)NearAlloc(sizeof(LV)); if (!plv) { DebugMsg(DM_ERROR, TEXT("ListView: Out of near memory")); return 0L; // fail the window create } plv->hwnd = hwnd; plv->flags = LVF_REDRAW; // assume that redrawing enabled! if (GetWindowLong(hwnd, GWL_STYLE) & WS_VISIBLE) plv->flags |= LVF_VISIBLE; plv->iFocus = -1; // no focus plv->iMark = -1; plv->iSelCol = -1; #ifdef WIN32 plv->hheap = GetProcessHeap(); #else // plv->hheap = NULL; // not used in 16 bits... #endif ListView_SetPtr(hwnd, plv); } else goto DoDefault; } if (msg == WM_NCDESTROY) { LRESULT result = HANDLE_WM_NCDESTROY(plv, wParam, lParam, ListView_OnNCDestroy); NearFree(plv); ListView_SetPtr(hwnd, NULL); return result; } switch (msg) { HANDLE_MSG(plv, WM_CREATE, ListView_OnCreate); HANDLE_MSG(plv, WM_DESTROY, ListView_OnDestroy); HANDLE_MSG(plv, WM_ERASEBKGND, ListView_OnEraseBkgnd); HANDLE_MSG(plv, WM_COMMAND, ListView_OnCommand); HANDLE_MSG(plv, WM_SETFOCUS, ListView_OnSetFocus); HANDLE_MSG(plv, WM_KILLFOCUS, ListView_OnKillFocus); HANDLE_MSG(plv, WM_HSCROLL, ListView_OnHScroll); HANDLE_MSG(plv, WM_VSCROLL, ListView_OnVScroll); HANDLE_MSG(plv, WM_GETDLGCODE, ListView_OnGetDlgCode); HANDLE_MSG(plv, WM_SETFONT, ListView_OnSetFont); HANDLE_MSG(plv, WM_GETFONT, ListView_OnGetFont); #ifndef FOLDING_NAMES HANDLE_MSG(plv, WM_NOTIFY, ListView_ROnNotify); #endif HANDLE_MSG(plv, WM_TIMER, ListView_OnTimer); HANDLE_MSG(plv, WM_SETREDRAW, ListView_OnSetRedraw); case WM_WINDOWPOSCHANGED: HANDLE_WM_WINDOWPOSCHANGED(plv, wParam, lParam, ListView_OnWindowPosChanged); break; case WM_MBUTTONDOWN: SetFocus(hwnd); RelayToToolTips(plv->hwndToolTips, hwnd, msg, wParam, lParam); break; case WM_LBUTTONDBLCLK: case WM_RBUTTONDBLCLK: ListView_OnButtonDown(plv, TRUE, (int)LOWORD(lParam), (int)HIWORD(lParam), wParam); RelayToToolTips(plv->hwndToolTips, hwnd, msg, wParam, lParam); break; case WM_LBUTTONDOWN: case WM_RBUTTONDOWN: ListView_OnButtonDown(plv, FALSE, (int)LOWORD(lParam), (int)HIWORD(lParam), wParam); // Fall through... case WM_LBUTTONUP: case WM_RBUTTONUP: case WM_MBUTTONUP: case WM_NCMOUSEMOVE: RelayToToolTips(plv->hwndToolTips, hwnd, msg, wParam, lParam); break; case WM_PRINTCLIENT: case WM_PAINT: ListView_OnPaint(plv, (HDC)wParam); return(0); case WM_SHOWWINDOW: LV_OnShowWindow(plv, wParam); break; case WM_KEYDOWN: HANDLE_WM_KEYDOWN(plv, wParam, lParam, ListView_OnKey); break; #ifdef FE_IME case WM_IME_COMPOSITION: // Now only Korean version is interested in incremental search with composition string. if (((DWORD)GetKeyboardLayout(0L) & 0xF000FFFFL) == 0xE0000412L) { if (ListView_OnImeComposition(plv, wParam, lParam)) { lParam &= ~GCS_RESULTSTR; break; } else return 0; } break; #endif case WM_CHAR: if (plv->iPuntChar) { plv->iPuntChar--; return TRUE; } else { return HANDLE_WM_CHAR(plv, wParam, lParam, ListView_OnChar); } case WM_WININICHANGE: ListView_OnWinIniChange(plv, wParam); break; case WM_NOTIFYFORMAT: if (lParam == NF_QUERY) { #ifdef UNICODE return NFR_UNICODE; #else return NFR_ANSI; #endif } else if (lParam == NF_REQUERY) { UINT uiResult; uiResult = SendMessage (plv->hwndParent, WM_NOTIFYFORMAT, (WPARAM)hwnd, NF_QUERY); plv->bUnicode = (uiResult == NFR_UNICODE); return uiResult; } return 0; case WM_ENABLE: // HACK: we don't get WM_STYLECHANGE on EnableWindow() ListView_EnableWindow(plv, wParam); break; case WM_SYSCOLORCHANGE: InitGlobalColors(); if (plv->style & WS_DISABLED) { if (!(plv->flags & LVF_USERBKCLR)) plv->clrBkSave = g_clrWindow; ListView_OnSetBkColor(plv, g_clrBtnFace); goto DoInvalidation; } else if (!(plv->flags & LVF_USERBKCLR)) { ListView_OnSetBkColor(plv, g_clrWindow); DoInvalidation: InvalidateRect(plv->hwnd, NULL, TRUE); } break; // don't use HANDLE_MSG because this needs to go to the default handler case WM_SYSKEYDOWN: HANDLE_WM_SYSKEYDOWN(plv, wParam, lParam, ListView_OnKey); break; #ifdef UNICODE case LVM_GETITEMA: return (LRESULT)ListView_OnGetItemA(plv, (LV_ITEMA *)lParam); case LVM_SETITEMA: return (LRESULT)ListView_OnSetItemA(plv, (LV_ITEMA *)lParam); case LVM_INSERTITEMA: return (LRESULT)ListView_OnInsertItemA(plv, (LV_ITEMA *)lParam); case LVM_FINDITEMA: return (LRESULT)ListView_OnFindItemA(plv, (int)wParam, (LV_FINDINFOA *)lParam); case LVM_GETSTRINGWIDTHA: return (LRESULT)ListView_OnGetStringWidthA(plv, (LPCSTR)lParam); case LVM_GETCOLUMNA: return (LRESULT)ListView_OnGetColumnA(plv, (int)wParam, (LV_COLUMNA *)lParam); case LVM_SETCOLUMNA: return (LRESULT)ListView_OnSetColumnA(plv, (int)wParam, (LV_COLUMNA *)lParam); case LVM_INSERTCOLUMNA: return (LRESULT)ListView_OnInsertColumnA(plv, (int)wParam, (LV_COLUMNA *)lParam); case LVM_GETITEMTEXTA: return (LRESULT)ListView_OnGetItemTextA(plv, (int)wParam, (LV_ITEMA FAR *)lParam); case LVM_SETITEMTEXTA: if (!lParam) return FALSE; return (LRESULT)ListView_OnSetItemTextA(plv, (int)wParam, ((LV_ITEMA *)lParam)->iSubItem, (LPCSTR)((LV_ITEMA FAR *)lParam)->pszText); #endif case WM_STYLECHANGED: ListView_OnStyleChanged(plv, wParam, (LPSTYLESTRUCT)lParam); return 0L; case WM_HELP: return ListView_OnHelp(plv, (LPHELPINFO)lParam); case LVM_GETIMAGELIST: return (LRESULT)(UINT)(ListView_OnGetImageList(plv, (int)wParam)); case LVM_SETIMAGELIST: return (LRESULT)(UINT)ListView_OnSetImageList(plv, (HIMAGELIST)lParam, (int)wParam); case LVM_GETBKCOLOR: return (LRESULT)(plv->style & WS_DISABLED ? plv->clrBkSave : plv->clrBk); case LVM_SETBKCOLOR: plv->flags |= LVF_USERBKCLR; if (plv->style & WS_DISABLED) { plv->clrBkSave = (COLORREF)lParam; return TRUE; } else { return (LRESULT)ListView_OnSetBkColor(plv, (COLORREF)lParam); } case LVM_GETTEXTCOLOR: return (LRESULT)plv->clrText; case LVM_SETTEXTCOLOR: plv->clrText = (COLORREF)lParam; return TRUE; case LVM_GETTEXTBKCOLOR: return (LRESULT)plv->clrTextBk; case LVM_SETTEXTBKCOLOR: plv->clrTextBk = (COLORREF)lParam; return TRUE; case LVM_GETITEMCOUNT: return (LRESULT)ListView_Count(plv); case LVM_GETITEM: return (LRESULT)ListView_OnGetItem(plv, (LV_ITEM FAR*)lParam); case LVM_GETITEMSTATE: return (LRESULT)ListView_OnGetItemState(plv, (int)wParam, (UINT)lParam); case LVM_SETITEMSTATE: if (!lParam) return FALSE; return (LRESULT)ListView_OnSetItemState(plv, (int)wParam, ((LV_ITEM FAR *)lParam)->state, ((LV_ITEM FAR *)lParam)->stateMask); case LVM_SETITEMTEXT: if (!lParam) return FALSE; return (LRESULT)ListView_OnSetItemText(plv, (int)wParam, ((LV_ITEM FAR *)lParam)->iSubItem, (LPCTSTR)((LV_ITEM FAR *)lParam)->pszText); case LVM_GETITEMTEXT: return (LRESULT)ListView_OnGetItemText(plv, (int)wParam, (LV_ITEM FAR *)lParam); case LVM_SETITEM: return (LRESULT)ListView_OnSetItem(plv, (const LV_ITEM FAR*)lParam); case LVM_INSERTITEM: return (LRESULT)ListView_OnInsertItem(plv, (const LV_ITEM FAR*)lParam); case LVM_DELETEITEM: return (LRESULT)ListView_OnDeleteItem(plv, (int)wParam); case LVM_UPDATE: ListView_OnUpdate(plv, (int)wParam); return TRUE; case LVM_DELETEALLITEMS: return (LRESULT)ListView_OnDeleteAllItems(plv); case LVM_GETITEMRECT: return (LRESULT)ListView_OnGetItemRect(plv, (int)wParam, (RECT FAR*)lParam); #ifdef UNICODE case LVM_GETISEARCHSTRINGA: if (GetFocus() == plv->hwnd) return (LRESULT)GetIncrementSearchStringA((LPSTR)lParam); else return 0; #endif case LVM_GETISEARCHSTRING: if (GetFocus() == plv->hwnd) return (LRESULT)GetIncrementSearchString((LPTSTR)lParam); else return 0; case LVM_GETITEMSPACING: if (wParam) return MAKELONG(plv->cxItem, plv->cyItem); else return MAKELONG(lv_cxIconSpacing, lv_cyIconSpacing); case LVM_GETNEXTITEM: return (LRESULT)ListView_OnGetNextItem(plv, (int)wParam, (UINT)lParam); case LVM_FINDITEM: return (LRESULT)ListView_OnFindItem(plv, (int)wParam, (const LV_FINDINFO FAR*)lParam); case LVM_GETITEMPOSITION: return (LRESULT)ListView_OnGetItemPosition(plv, (int)wParam, (POINT FAR*)lParam); case LVM_SETITEMPOSITION: return (LRESULT)ListView_OnSetItemPosition(plv, (int)wParam, (SHORT)LOWORD(lParam), (SHORT)HIWORD(lParam)); case LVM_SETITEMPOSITION32: if (!lParam) return FALSE; return (LRESULT)ListView_OnSetItemPosition(plv, (int)wParam, ((LPPOINT)lParam)->x, ((LPPOINT)lParam)->y); case LVM_SCROLL: { int dx = (int)wParam; int dy = (int)lParam; return (LRESULT) (ListView_ValidateScrollParams(plv, &dx, &dy) && ListView_OnScroll(plv, dx, dy)); } case LVM_ENSUREVISIBLE: return (LRESULT)ListView_OnEnsureVisible(plv, (int)wParam, (BOOL)lParam); case LVM_REDRAWITEMS: return (LRESULT)ListView_OnRedrawItems(plv, (int)wParam, (int)lParam); case LVM_ARRANGE: return (LRESULT)ListView_OnArrange(plv, (UINT)wParam); case LVM_GETEDITCONTROL: return (LRESULT)(UINT)plv->hwndEdit; #ifdef UNICODE case LVM_EDITLABELA: { LPWSTR lpEditString = NULL; HWND hRet; if (lParam) { lpEditString = ProduceWFromA((LPSTR)lParam); } hRet = ListView_OnEditLabel(plv, (int)wParam, lpEditString); if (lpEditString) { FreeProducedString(lpEditString); } return (LRESULT)hRet; } #endif case LVM_EDITLABEL: return (LRESULT)(UINT)ListView_OnEditLabel(plv, (int)wParam, (LPTSTR)lParam); case LVM_HITTEST: return (LRESULT)ListView_OnHitTest(plv, (LV_HITTESTINFO FAR*)lParam); case LVM_GETSTRINGWIDTH: return (LRESULT)ListView_OnGetStringWidth(plv, (LPCTSTR)lParam); case LVM_GETCOLUMN: return (LRESULT)ListView_OnGetColumn(plv, (int)wParam, (LV_COLUMN FAR*)lParam); case LVM_SETCOLUMN: return (LRESULT)ListView_OnSetColumn(plv, (int)wParam, (const LV_COLUMN FAR*)lParam); case LVM_INSERTCOLUMN: return (LRESULT)ListView_OnInsertColumn(plv, (int)wParam, (const LV_COLUMN FAR*)lParam); case LVM_DELETECOLUMN: return (LRESULT)ListView_OnDeleteColumn(plv, (int)wParam); case LVM_CREATEDRAGIMAGE: return (LRESULT)(UINT)ListView_OnCreateDragImage(plv, (int)wParam, (LPPOINT)lParam); case LVM_GETVIEWRECT: if (!lParam) return FALSE; ListView_GetViewRect2(plv, (RECT FAR*)lParam, plv->sizeClient.cx, plv->sizeClient.cy); return (LPARAM)TRUE; case LVM_GETCOLUMNWIDTH: return (LPARAM)ListView_OnGetColumnWidth(plv, (int)wParam); case LVM_SETCOLUMNWIDTH: return (LPARAM)ListView_ISetColumnWidth(plv, (int)wParam, (int)(short)LOWORD(lParam), TRUE); case LVM_SETCALLBACKMASK: plv->stateCallbackMask = (UINT)wParam; return (LPARAM)TRUE; case LVM_GETCALLBACKMASK: return (LPARAM)(UINT)plv->stateCallbackMask; case LVM_GETTOPINDEX: return (LPARAM)ListView_OnGetTopIndex(plv); case LVM_GETCOUNTPERPAGE: return (LPARAM)ListView_OnGetCountPerPage(plv); case LVM_GETORIGIN: return (LPARAM)ListView_OnGetOrigin(plv, (POINT FAR*)lParam); case LVM_SETITEMCOUNT: return ListView_OnSetItemCount(plv, (int)wParam); case LVM_GETSELECTEDCOUNT: return plv->nSelected; case LVM_SORTITEMS: return ListView_OnSortItems(plv, (LPARAM)wParam, (PFNLVCOMPARE)lParam); #ifdef FOLDING_NAMES /* Resize the tooltip control so that it covers the entire area of the window / when its parent gets resized. */ case WM_SIZE: { TOOLINFO ti; if (!plv || !plv->hwndToolTips) { break; } ti.cbSize = sizeof(ti); ti.hwnd = plv ->hwnd; ti.uId = 0; GetClientRect( plv ->hwnd, &ti.rect ); SendMessage( plv ->hwndToolTips, TTM_NEWTOOLRECT, 0, (LPARAM) &ti ); break; } /* Break down the WM_NOTIFY message and pass to the relevant handler / based on the view state. */ case WM_NOTIFY: { int idCtrl = (int) wParam; LPNMHDR pnmh = (LPNMHDR) lParam; LPTOOLTIPTEXT lpttt = (LPTOOLTIPTEXT) lParam; DWORD dwPos = GetMessagePos(); POINT ptPos; UINT uFlags; LISTITEM FAR* pItem; LV_ITEM item; RECT rcLabel; if ( ListView_IsIconView( plv ) && !ListView_IsOwnerData( plv )) { if ( plv ->hwndToolTips != pnmh ->hwndFrom ) break; switch ( pnmh ->code ) { /* Aquire the text for the control, this is passed back to the tooltip, if / we don't want to display a tip then pass a NULL message. */ case TTN_NEEDTEXT: { ptPos.x = LOWORD( dwPos ); ptPos.y = HIWORD( dwPos ); if ( ScreenToClient( plv ->hwnd, &ptPos ) ) { plv ->iTTLastHit = ListView_IItemHitTest( plv, ptPos.x, ptPos.y, &uFlags ); if ( plv ->iTTLastHit != -1 ) { pItem = ListView_GetItemPtr( plv, plv ->iTTLastHit ); Assert( pItem ); /* Must be clipped, not unfolded and not being edited! */ if ( ( pItem ->cyMultiLabel > CLIP_HEIGHT ) && ( !UNFOLD_ITEM( plv, pItem ) ) && ( plv ->iEdit == -1 ) ) { if (!plv->lpTip) { plv->lpTip = LocalAlloc (LPTR, MAX_PATH * sizeof(TCHAR)); } if (plv->lpTip) { item.iItem = plv ->iTTLastHit; item.iSubItem = 0; item.mask = LVIF_TEXT; item.stateMask = LVIS_ALL; item.pszText = plv->lpTip; item.cchTextMax = MAX_PATH; ListView_OnGetItem(plv, &item); lpttt ->lpszText = item.pszText; } else { lpttt ->lpszText = NULL; } } else { lpttt ->lpszText = NULL; } } } return 0; } /* Custom draw gives us a chance to modify the way that the tooltip is formatted/displayed, / therefore change it to match the formatting of a multi-line control. */ case NM_CUSTOMDRAW: { LPNMTTCUSTOMDRAW pnm = (LPNMTTCUSTOMDRAW) lParam; if (pnm->nmcd.dwDrawStage == CDDS_PREPAINT) { pnm->uDrawFlags &= ~(DT_SINGLELINE|DT_LEFT); pnm->uDrawFlags |= DT_CENTER|DT_LVWRAP; if ( pnm->uDrawFlags & DT_CALCRECT ) { pnm->nmcd.rc.right = pnm->nmcd.rc.left + (lv_cxIconSpacing - g_cxLabelMargin * 2);; pnm->nmcd.rc.bottom = pnm->nmcd.rc.top + 0x10000; // big number, no limit! } } } return 0; /* When we get the show message reposition the tip window so that it aligns / with the original position of the list view items text. */ case TTN_SHOW: { if ( plv ->iTTLastHit != -1 ) { ListView_GetRects(plv, plv->iTTLastHit, NULL, &rcLabel, NULL, NULL); ClientToScreen( plv ->hwnd, (LPPOINT) &rcLabel.left ); SetWindowPos( pnmh ->hwndFrom, HWND_TOPMOST, rcLabel.left - g_cxEdge, rcLabel.top - g_cyEdge, 0, 0, SWP_NOACTIVATE|SWP_NOSIZE|SWP_HIDEWINDOW ); } break; } } } else { return HANDLE_WM_NOTIFY(plv, wParam, lParam, ListView_ROnNotify); } break; } /* The mouse has moved check that it still intersects with the item, if not and the / tooltip is displayed then pop it! */ case WM_MOUSEMOVE: { int iHit; UINT uFlags; RelayToToolTips(plv->hwndToolTips, hwnd, msg, wParam, lParam); if ( ListView_IsIconView( plv ) ) { iHit = ListView_IItemHitTest( plv, LOWORD(lParam), HIWORD(lParam), &uFlags ); if ( iHit != plv ->iTTLastHit ) SendMessage( plv ->hwndToolTips, TTM_POP, 0, 0 ); } break; } #endif default: break; } DoDefault: return DefWindowProc(hwnd, msg, wParam, lParam); } void NEAR ListView_OnWinIniChange(LV* plv, WPARAM wParam) { // BUGBUG: will this also catch sysparametersinfo? // we need a general way of handling this, not // just relying on the listview. InitGlobalMetrics(wParam); if (!wParam || (wParam == SPI_SETNONCLIENTMETRICS) || (wParam == SPI_SETICONTITLELOGFONT)) { if (plv->flags & LVF_FONTCREATED) ListView_OnSetFont(plv, NULL, TRUE); } // If we are in an Iconic view and the user is in autoarrange mode, // then we need to arrange the items. // if (( #ifdef WINNT_CAIRO ListView_IsOwnerData( plv ) || #endif (plv->style & LVS_AUTOARRANGE)) && (ListView_IsSmallView(plv) || ListView_IsIconView(plv))) { // Call off to the arrange function. ListView_OnArrange(plv, LVA_DEFAULT); } } BOOL NEAR ListView_OnCreate(LV* plv, CREATESTRUCT FAR* lpCreateStruct) { plv->hwndParent = lpCreateStruct->hwndParent; plv->style = lpCreateStruct->style; plv->bUnicode = (SendMessage (plv->hwndParent, WM_NOTIFYFORMAT, (WPARAM)plv->hwnd, NF_QUERY) == NFR_UNICODE); #ifdef WINNT_CAIRO if (ListView_IsOwnerData(plv)) { // ownerdata initialization plv->hselrange = SelRange_Create(); if (NULL == plv->hselrange) { goto error0; } } else #else { // // REVIEW: // // We want to reserve the LVS_NOITEMDATA // style for the new LVS_OWNERDATA style // to be merged post PPC. To ensure that // no one else tries to use this style, // fail the call. // if (plv->style & LVS_NOITEMDATA) { DebugMsg(DM_ERROR, TEXT("ListView: Style 0x1000 is reserved for future use.")); goto error1; } } #endif { plv->hdpa = DPA_CreateEx(LV_HDPA_GROW, plv->hheap); if (!plv->hdpa) goto error0; plv->hdpaZOrder = DPA_CreateEx(LV_HDPA_GROW, plv->hheap); if (!plv->hdpaZOrder) goto error1; } // start out NULL -- if someone wants them, do LVM_SETIMAGELIST plv->himl = plv->himlSmall = NULL; //plv->hwndEdit = NULL; plv->iEdit = -1; plv->iFocus = -1; plv->iDrag = -1; plv->rcView.left = RECOMPUTE; plv->sizeClient.cx = lpCreateStruct->cx; plv->sizeClient.cy = lpCreateStruct->cy; #ifdef FOLDING_NAMES plv->hwndToolTips = NULL; plv->iTTLastHit = -1; plv->lpTip = NULL; #endif //plv->ptOrigin.x = 0; //plv->ptOrigin.y = 0; // Setup flag to say if positions are in small or large view if (ListView_IsSmallView(plv)) plv->flags |= LVF_ICONPOSSML; // force calculation of listview metrics ListView_OnSetFont(plv, NULL, FALSE); //plv->xOrigin = 0; plv->cxItem = 16 * plv->cxLabelChar + plv->cxSmIcon; // if we're in ownerdraw report mode, the size got saved to cyItemSave // at creation time, both need to have this if ((plv->style & LVS_OWNERDRAWFIXED) && ListView_IsReportView(plv)) plv->cyItem = plv->cyItemSave; else plv->cyItemSave = plv->cyItem; ListView_UpdateScrollBars(plv); // sets plv->cItemCol //plv->hbrBk = NULL; plv->clrBk = CLR_NONE; plv->clrText = CLR_DEFAULT; plv->clrTextBk = CLR_DEFAULT; // create the bk brush, and set the imagelists colors if needed ListView_OnSetBkColor(plv, g_clrWindow); // Initialize report view fields //plv->yTop = 0; //plv->ptlRptOrigin.x = 0; //plv->ptlRptOrigin.y = 0; //plv->hwndHdr = NULL; plv->xTotalColumnWidth = RECOMPUTE; if (ListView_IsReportView(plv)) ListView_RInitialize(plv, FALSE); if (plv->style & WS_DISABLED) { plv->style &= ~WS_DISABLED; ListView_EnableWindow(plv, FALSE); } #ifdef FOLDING_NAMES /* Provide a tooltip control for the view if we support folding names / the control then has a single tooltip control for the entire work area / of the window added. */ plv ->hwndToolTips = CreateWindow( TOOLTIPS_CLASS, NULL, WS_POPUP|TTS_ALWAYSTIP|TTS_NOPREFIX, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, plv ->hwnd, NULL, g_hinst, NULL ); if ( plv ->hwndToolTips ) { TOOLINFO ti; ti.cbSize = sizeof(ti); ti.uFlags = TTF_TRANSPARENT; ti.hwnd = plv ->hwnd; ti.uId = 0; ti.hinst = NULL; ti.lpszText = LPSTR_TEXTCALLBACK; GetClientRect( plv ->hwnd, &ti.rect ); SendMessage( plv->hwndToolTips, TTM_ADDTOOL, 0, (LPARAM) &ti ); /* Ensure that the tooltips use the same font as the view */ FORWARD_WM_SETFONT(plv->hwndToolTips, plv->hfontLabel, FALSE, SendMessage); } else { Assert( FALSE ); // failed to create the tooltip window goto error1; } #endif return TRUE; error1: DPA_Destroy(plv->hdpa); error0: return FALSE; } void NEAR PASCAL ListView_DeleteHrgnInval(LV* plv) { if (plv->hrgnInval && plv->hrgnInval != (HRGN)ENTIRE_REGION) DeleteObject(plv->hrgnInval); plv->hrgnInval = NULL; } void NEAR ListView_OnDestroy(LV* plv) { #ifdef FOLDING_NAMES // // The tooltip window may or may not exist at this point. It // depends if the owning window of the tips is also being destroy. // If so, then the tips are gone already. // if (plv->hwndToolTips) { DestroyWindow (plv->hwndToolTips); plv->hwndToolTips = NULL; } if (plv->lpTip) { LocalFree (plv->lpTip); } #endif #ifdef WINNT_CAIRO if (ListView_IsOwnerData(plv)) { SelRange_Delete( plv->hselrange ); } else { #endif // Make sure to notify the app ListView_OnDeleteAllItems(plv); #ifdef WINNT_CAIRO } #endif if (plv->hwndHdr) { DestroyWindow(plv->hwndHdr); plv->hwndHdr = NULL; } if ((plv->flags & LVF_FONTCREATED) && plv->hfontLabel) { DeleteObject(plv->hfontLabel); // plv->flags &= ~LVF_FONTCREATED; // plv->hwfontLabel = NULL; } ListView_DeleteHrgnInval(plv); } void NEAR ListView_OnNCDestroy(LV* plv) { if (!(plv->style & LVS_SHAREIMAGELISTS)) { if (plv->himl) ImageList_Destroy(plv->himl); if (plv->himlSmall) ImageList_Destroy(plv->himlSmall); if (plv->himlState) ImageList_Destroy(plv->himlState); } if (plv->hbrBk) DeleteBrush(plv->hbrBk); if (plv->hdpa) DPA_Destroy(plv->hdpa); if (plv->hdpaZOrder) DPA_Destroy(plv->hdpaZOrder); ListView_RDestroy(plv); } // sets the background color for the listview // // this creats the brush for drawing the background as well // as sets the imagelists background color if needed BOOL NEAR ListView_OnSetBkColor(LV* plv, COLORREF clrBk) { if (plv->clrBk != clrBk) { if (plv->hbrBk) { DeleteBrush(plv->hbrBk); plv->hbrBk = NULL; } if (clrBk != CLR_NONE) { plv->hbrBk = CreateSolidBrush(clrBk); if (!plv->hbrBk) return FALSE; } // don't mess with the imagelist color if things are shared if (!(plv->style & LVS_SHAREIMAGELISTS)) { if (plv->himl) ImageList_SetBkColor(plv->himl, clrBk); if (plv->himlSmall) ImageList_SetBkColor(plv->himlSmall, clrBk); if (plv->himlState) ImageList_SetBkColor(plv->himlState, clrBk); } plv->clrBk = clrBk; } return TRUE; } void PASCAL InitBrushOrg(LV* plv, HDC hdc) { int x; if (ListView_IsSmallView(plv) || ListView_IsIconView(plv)) { x = plv->ptOrigin.x; } else if (ListView_IsListView(plv)) { x = plv->xOrigin; } else { x = (int)plv->ptlRptOrigin.x; } #ifdef WIN32 { POINT pt; SetBrushOrgEx(hdc, -x, 0, &pt); } #else SetBrushOrg(hdc, -x, 0); #endif } void NEAR PASCAL ListView_InvalidateRegion(LV* plv, HRGN hrgn) { if (hrgn) { if (plv->hrgnInval == NULL) { plv->hrgnInval = hrgn; } else { // union it in if the entire region isn't marked for invalidate if (plv->hrgnInval != (HRGN)ENTIRE_REGION) { UnionRgn(plv->hrgnInval, plv->hrgnInval, hrgn); } DeleteObject(hrgn); } } } void NEAR ListView_OnPaint(LV* plv, HDC hdc) { PAINTSTRUCT ps; RECT rcUpdate; // Before handling WM_PAINT, go ensure everything's recomputed... // if (plv->rcView.left == RECOMPUTE) ListView_Recompute(plv); // If we're in report view, update the header window: it looks // better this way... // if (ListView_IsReportView(plv) && plv->hwndHdr) UpdateWindow(plv->hwndHdr); // If nothing to do (i.e., we recieved a WM_PAINT because // of an RDW_INTERNALPAINT, and we didn't invalidate anything) // don't bother with the Begin/EndPaint. // if (hdc || GetUpdateRect(plv->hwnd, &rcUpdate, FALSE)) { if (!(plv->flags & LVF_VISIBLE)) { plv->flags |= LVF_VISIBLE; // We may try to resize the column ListView_MaybeResizeListColumns(plv, 0, ListView_Count(plv)-1); ListView_UpdateScrollBars(plv); } // this needs to be done before the beginpaint because it clears // out the update region if (!(plv->flags & LVF_REDRAW)) { // add this region to our local invalidate region HRGN hrgn = CreateRectRgn(0, 0, 0,0); if (hrgn) { // ok if GetUpdateRgn fails... then hrgn will still be // and empty region.. GetUpdateRgn(plv->hwnd, hrgn, FALSE); ListView_InvalidateRegion(plv, hrgn); } } if (hdc) { InitBrushOrg(plv, hdc); SetRect(&ps.rcPaint, 0, 0, plv->sizeClient.cx, plv->sizeClient.cy); if (ListView_RedrawEnabled(plv)) ListView_Redraw(plv, hdc, &ps.rcPaint); } else { hdc = BeginPaint(plv->hwnd, &ps); InitBrushOrg(plv, hdc); if (ListView_RedrawEnabled(plv)) ListView_Redraw(plv, hdc, &ps.rcPaint); EndPaint(plv->hwnd, &ps); } } } BOOL NEAR ListView_OnEraseBkgnd(LV* plv, HDC hdc) { if (plv->clrBk != CLR_NONE) { // // If we have a background color, erase with it. // RECT rc; GetClipBox(hdc, &rc); FillRect(hdc, &rc, plv->hbrBk); } else { // // If not, pass it up to the parent. // SendMessage(plv->hwndParent, WM_ERASEBKGND, (UINT)hdc, 0); } return TRUE; } void NEAR ListView_OnCommand(LV* plv, int id, HWND hwndCtl, UINT codeNotify) { if (hwndCtl == plv->hwndEdit) { switch (codeNotify) { case EN_UPDATE: #ifdef FE_IME // We don't want flicker during replacing current selection // as we use selection for IME composition. // if (plv->flags & LVF_INSERTINGCOMP) break; #endif // We will use the ID of the window as a Dirty flag... SetWindowID(plv->hwndEdit, 1); ListView_SetEditSize(plv); break; case EN_KILLFOCUS: // We lost focus, so dismiss edit and do not commit changes // as if the validation fails and we attempt to display // an error message will cause the system to hang! if (!ListView_DismissEdit(plv, FALSE)) return; break; case HN_BEGINDIALOG: // pen windows is bringing up a dialog Assert(GetSystemMetrics(SM_PENWINDOWS)); // only on a pen system plv->fNoDismissEdit = TRUE; break; case HN_ENDDIALOG: // pen windows has destroyed dialog Assert(GetSystemMetrics(SM_PENWINDOWS)); // only on a pen system plv->fNoDismissEdit = FALSE; break; } // Forward edit control notifications up to parent // if (IsWindow(hwndCtl)) FORWARD_WM_COMMAND(plv->hwndParent, id, hwndCtl, codeNotify, SendMessage); } } void NEAR ListView_OnWindowPosChanged(LV* plv, const WINDOWPOS FAR* lpwpos) { if (!(lpwpos->flags & SWP_NOSIZE)) { RECT rc; GetClientRect(plv->hwnd, &rc); plv->sizeClient.cx = rc.right; plv->sizeClient.cy = rc.bottom; if ((plv->style & LVS_AUTOARRANGE) && (ListView_IsSmallView(plv) || ListView_IsIconView(plv))) { // Call off to the arrange function. ListView_OnArrange(plv, LVA_DEFAULT); } #ifdef WINNT_CAIRO if (ListView_IsOwnerData(plv)) { plv->rcView.left = RECOMPUTE; ListView_Recompute(plv); ListView_DismissEdit(plv, FALSE); } #endif ListView_RInitialize(plv, TRUE); // Always make sure the scrollbars are updated to the new size ListView_UpdateScrollBars(plv); } } #ifdef WINNT_CAIRO void ListView_InvalidateSelectedOwnerData(LV* plv) { UINT rdwFlags = RDW_INVALIDATE; UINT flags; int cItem = ListView_Count( plv ); DWORD dwType = plv->style & LVS_TYPEMASK; int i; RECT rcView; Assert(ListView_IsOwnerData(plv)); Assert(plv ); GetClientRect( plv->hwnd, &rcView ); if (plv->himl && (plv->clrBk != ImageList_GetBkColor(plv->himl))) { rdwFlags |= RDW_ERASE; } // calculate start of items and end of items visible on the view // switch (dwType) { case LVS_REPORT: i = ListView_RYHitTest(plv, rcView.top); cItem = ListView_RYHitTest(plv, rcView.bottom) + 1; break; case LVS_LIST: i = ListView_LCalcViewItem(plv, rcView.left, rcView.top ); cItem = ListView_LCalcViewItem( plv, rcView.right, rcView.bottom ) + 1; break; default: ListView_CalcMinMaxIndex( plv, &rcView, &i, &cItem ); break; } i = max( i, 0 ); cItem = min( ListView_Count( plv ), cItem ); if (cItem > i) { ListView_NotifyCacheHint( plv, i, cItem-1 ); } for (; i < cItem; i++) { if (SelRange_IsSelected( plv->hselrange, i )) { ListView_InvalidateItem(plv, i, TRUE, rdwFlags); } } } #endif void NEAR ListView_RedrawSelection(LV* plv) { int i = -1; #ifdef WINNT_CAIRO if (ListView_IsOwnerData(plv)) { ListView_InvalidateSelectedOwnerData( plv ); } else { #endif while ((i = ListView_OnGetNextItem(plv, i, LVNI_SELECTED)) != -1) { ListView_InvalidateItem(plv, i, TRUE, RDW_INVALIDATE); } #ifdef WINNT_CAIRO } #endif UpdateWindow( plv->hwnd ); } void NEAR ListView_OnSetFocus(LV* plv, HWND hwndOldFocus) { // due to the way listview call SetFocus on themselves on buttondown, // the window can get a strange sequence of focus messages: first // set, then kill, and then set again. since these are not really // focus changes, ignore them and only handle "real" cases. if (hwndOldFocus == plv->hwnd) return; #ifdef FOLDING_NAMES plv->flags |= LVF_FOCUSED | LVF_UNFOLDED; #else plv->flags |= LVF_FOCUSED; #endif if (IsWindowVisible(plv->hwnd)) { if (plv->iFocus != -1) ListView_InvalidateItem(plv, plv->iFocus, TRUE, RDW_INVALIDATE); ListView_RedrawSelection(plv); } // Let the parent window know that we are getting the focus. SendNotifyEx(plv->hwndParent, plv->hwnd, NM_SETFOCUS, NULL, plv->bUnicode); } void NEAR ListView_OnKillFocus(LV* plv, HWND hwndNewFocus) { // due to the way listview call SetFocus on themselves on buttondown, // the window can get a strange sequence of focus messages: first // set, then kill, and then set again. since these are not really // focus changes, ignore them and only handle "real" cases. if (!plv || hwndNewFocus == plv->hwnd) return; #ifdef FOLDING_NAMES plv->flags &= ~(LVF_FOCUSED|LVF_UNFOLDED); #else plv->flags &= ~LVF_FOCUSED; #endif // Blow this off if we are not currently visible (being destroyed!) if (IsWindowVisible(plv->hwnd)) { if (plv->iFocus != -1) #ifdef FOLDING_NAMES ListView_InvalidateFoldedItem( plv, plv ->iFocus, TRUE, RDW_INVALIDATE ); #else ListView_InvalidateItem(plv, plv->iFocus, TRUE, RDW_INVALIDATE); #endif if (!(plv->style & LVS_SHOWSELALWAYS)) ListView_RedrawSelection(plv); } // Let the parent window know that we are losing the focus. SendNotifyEx(plv->hwndParent, plv->hwnd, NM_KILLFOCUS, NULL, plv->bUnicode); IncrementSearchString(0, NULL); } void NEAR ListView_DeselectAll(LV* plv, int iDontDeselect) { int i = -1; int nSkipped = 0; #ifdef WINNT_CAIRO if (ListView_IsOwnerData(plv)) { ListView_InvalidateSelectedOwnerData(plv); if (!SelRange_Clear(plv->hselrange)) { // BUGBUG: low memory status // MemoryLowDlg( plv->hwnd ); } plv->nSelected = 0; if (iDontDeselect >= 0) { if (SELRANGE_ERROR == SelRange_IncludeRange(plv->hselrange, iDontDeselect, iDontDeselect)) { // BUGBUG: return low memory status //MemoryLowDlg(plv->hwnd); return; } plv->nSelected = 1; nSkipped = 1; } } else { #endif if (iDontDeselect != plv->iFocus) { ListView_OnSetItemState(plv, plv->iFocus, 0, LVIS_SELECTED); } while ((plv->nSelected - nSkipped) && (i = ListView_OnGetNextItem(plv, i, LVNI_SELECTED)) != -1) { if (i != iDontDeselect) { ListView_OnSetItemState(plv, i, 0, LVIS_SELECTED); } else { if (ListView_OnGetItemState(plv, i, LVIS_SELECTED)) { nSkipped++; } } } #ifdef WINNT_CAIRO } #endif Assert((plv->nSelected - nSkipped) == 0); plv->nSelected = nSkipped; } // toggle the selection state of an item void NEAR ListView_ToggleSelection(LV* plv, int iItem) { UINT cur_state; if (iItem != -1) { cur_state = ListView_OnGetItemState(plv, iItem, LVIS_SELECTED); ListView_OnSetItemState(plv, iItem, cur_state ^ LVIS_SELECTED, LVIS_SELECTED); } } // Selects (or toggles) a range of items in the list. // The curent iFocus is the starting location // iItem - is the ending item // fToggle - Well set all of the selection state of all of the items to // inverse the starting location // void NEAR ListView_SelectRangeTo(LV* plv, int iItem, BOOL fResetRest) { int iMin, iMax; int i = -1; UINT uSelVal = LVIS_SELECTED; if (plv->iMark == -1) { ListView_SetFocusSel(plv, iItem, TRUE, TRUE, FALSE); return; } if (!fResetRest) uSelVal = ListView_OnGetItemState(plv, plv->iMark, LVIS_SELECTED); // If we are in report view or list view we simply walk through the // indexes to see which items to select or deselect. otherwise it // is is based off of the location of the objects being within the // rectangle that is defined by if (ListView_IsListView(plv) || ListView_IsReportView(plv)) { iMin = min(iItem, plv->iMark); iMax = max(iItem, plv->iMark); #ifdef WINNT_CAIRO if (ListView_IsOwnerData( plv )) { // considerable speed increase less than 100 to do this method // while over 100, the other method works faster if ( (iMax - iMin) > 100) { LONG cChange; if (fResetRest) { ListView_DeselectAll( plv, -1 ); } if (LVIS_SELECTED & uSelVal) { cChange = SelRange_IncludeRange( plv->hselrange, iMin, iMax ); if (SELRANGE_ERROR == cChange) { // BUGBUG: return low memory status //MemoryLowDlg( plv->hwnd ); return; } else { plv->nSelected += cChange; } } else { cChange = SelRange_ExcludeRange( plv->hselrange, iMin, iMax ); if (SELRANGE_ERROR == cChange) { // BUGBUG: return low memory status //MemoryLowDlg( plv->hwnd ); return; } else { plv->nSelected -= cChange; } } InvalidateRect( plv->hwnd, NULL, FALSE ); } else { if (fResetRest) { ListView_DeselectAll( plv, -1 ); } while (iMin <= iMax) { ListView_OnSetItemState(plv, iMin, uSelVal, LVIS_SELECTED); iMin++; } } } else { #endif if (fResetRest) { while ((i = ListView_OnGetNextItem(plv, i, LVNI_SELECTED)) != -1) { if (i < iMin || i > iMax) ListView_OnSetItemState(plv, i, 0, LVIS_SELECTED); } } while (iMin <= iMax) { ListView_OnSetItemState(plv, iMin, uSelVal, LVIS_SELECTED); iMin++; } #ifdef WINNT_CAIRO } #endif } else { // Iconic views first calculate the bounding rectangle of the two // items. RECT rcTemp; RECT rcTemp2; RECT rcBounding; POINT pt; // ListView_GetRects(plv, plv->iMark, NULL, NULL, NULL, &rcTemp); ListView_GetRects(plv, iItem, NULL, NULL, NULL, &rcTemp2); UnionRect(&rcBounding, &rcTemp, &rcTemp2); #ifdef WINNT_CAIRO // since ownerdata icon views are always arranged, we can assume that // all items are in order and we can search only those between the // indexes found using the bouding rect // if (ListView_IsOwnerData( plv )) { ListView_CalcMinMaxIndex( plv, &rcBounding, &iMin, &iMax ); if (fResetRest) { ListView_DeselectAll( plv, -1 ); } iMax = min( iMax, ListView_Count( plv ) ); iMin = max( iMin, 0 ); } else { #endif iMax = ListView_Count(plv); iMin = 0; #ifdef WINNT_CAIRO } // considerable speed increase over 100 to do this method // while under 100, the other method works faster if (ListView_IsOwnerData(plv) && ((iMax - iMin) > 100)) { LONG cChange; if (LVIS_SELECTED & uSelVal) { cChange = SelRange_IncludeRange( plv->hselrange, iMin, iMax - 1 ); if (SELRANGE_ERROR == cChange) { //BUGBUG: return low memory status //MemoryLowDlg( plv->hwnd ); return; } else { plv->nSelected += cChange; } } else { cChange = SelRange_ExcludeRange( plv->hselrange, iMin, iMax - 1 ); if (SELRANGE_ERROR == cChange) { // BUGBUG: return low memory status //MemoryLowDlg( plv->hwnd ); return; } else { plv->nSelected -= cChange; } } InvalidateRect( plv->hwnd, NULL, FALSE ); } else { #endif for (i = iMin; i < iMax; i++) { ListView_GetRects(plv, i, NULL, NULL, NULL, &rcTemp2); pt.x = (rcTemp2.right + rcTemp2.left) / 2; // center of item pt.y = (rcTemp2.bottom + rcTemp2.top) / 2; if (PtInRect(&rcBounding, pt)) { int iZ; #ifdef WINNT_CAIRO if (!ListView_IsOwnerData( plv )) #endif { iZ = ListView_ZOrderIndex(plv, i); if (iZ > 0) DPA_InsertPtr(plv->hdpaZOrder, 0, DPA_DeletePtr(plv->hdpaZOrder, iZ)); } ListView_OnSetItemState(plv, i, uSelVal, LVIS_SELECTED); } else if (fResetRest) ListView_OnSetItemState(plv, i, 0, LVIS_SELECTED); } #ifdef WINNT_CAIRO } #endif } } // makes an item the focused item and optionally selects it // // in: // iItem item to get the focus // fSelectAlso select this item as well as set it as the focus // fDeselectAll deselect all items first // fToggleSel toggle the selection state of the item // // returns: // index of focus item (if focus change was refused) // Bugbug::this is getting to have a lot of parameters int NEAR ListView_SetFocusSel(LV* plv, int iItem, BOOL fSelectAlso, BOOL fDeselectAll, BOOL fToggleSel) { UINT flags; if (plv->style & LVS_SINGLESEL) { // we know there's only one thing selected, so go deselect it. if (iItem != plv->iFocus) { ListView_OnSetItemState(plv, plv->iFocus, 0, LVIS_SELECTED); } } else if (fDeselectAll) ListView_DeselectAll(plv, -1); if (iItem != plv->iFocus) { // remove the old focus if (plv->iFocus != -1) { // If he refuses to give up the focus, bail out. if (!ListView_OnSetItemState(plv, plv->iFocus, 0, LVIS_FOCUSED)) return plv->iFocus; } } #ifdef WINNT_CAIRO if (!ListView_IsOwnerData( plv )) { #endif if (fSelectAlso) { if (ListView_IsIconView(plv) || ListView_IsSmallView(plv)) { int iZ = ListView_ZOrderIndex(plv, iItem); if (iZ > 0) DPA_InsertPtr(plv->hdpaZOrder, 0, DPA_DeletePtr(plv->hdpaZOrder, iZ)); } } #ifdef WINNT_CAIRO } #endif #ifdef FOLDING_NAMES /* Ensure that when moving focus that we refresh the previous focus / owner properly. */ if ( plv ->iFocus != -1 && ( plv->flags & LVF_UNFOLDED ) ) ListView_InvalidateFoldedItem( plv, plv ->iFocus, FALSE, RDW_INVALIDATE ); #endif plv->iFocus = iItem; if (plv->iMark == -1) plv->iMark = iItem; SetTimer(plv->hwnd, IDT_SCROLLWAIT, GetDoubleClickTime(), NULL); plv->flags |= LVF_SCROLLWAIT; if (fToggleSel) { ListView_ToggleSelection(plv, iItem); ListView_OnSetItemState(plv, plv->iFocus, LVIS_FOCUSED, LVIS_FOCUSED); } else { flags = (fSelectAlso ? (LVIS_SELECTED | LVIS_FOCUSED) : LVIS_FOCUSED); ListView_OnSetItemState(plv, plv->iFocus, flags, flags); } return iItem; } void NEAR ListView_OnKey(LV* plv, UINT vk, BOOL fDown, int cRepeat, UINT flags) { UINT lvni = 0; int iNewFocus; BOOL fCtlDown; BOOL fShiftDown; LV_KEYDOWN nm; if (!fDown) return; // Notify nm.wVKey = vk; nm.flags = flags; if (SendNotifyEx(plv->hwndParent, plv->hwnd, LVN_KEYDOWN, &nm.hdr, plv->bUnicode)) { plv->iPuntChar++; return; } else if (plv->iPuntChar) { // this is tricky... if we want to punt the char, just increment the // count. if we do NOT, then we must clear the queue of WM_CHAR's // this is to preserve the iPuntChar to mean "punt the next n WM_CHAR messages MSG msg; while(plv->iPuntChar && PeekMessage(&msg, plv->hwnd, WM_CHAR, WM_CHAR, PM_REMOVE)) { plv->iPuntChar--; } Assert(!plv->iPuntChar); } if (ListView_Count(plv) == 0) // don't blow up on empty list return; fCtlDown = GetKeyState(VK_CONTROL) < 0; fShiftDown = GetKeyState(VK_SHIFT) < 0; switch (vk) { case VK_SPACE: // If shift (extend) or control (disjoint) select, // then toggle selection state of focused item. if (fCtlDown) { plv->iMark = plv->iFocus; ListView_ToggleSelection(plv, plv->iFocus); plv->iPuntChar++; } // BUGBUG: Implement me if ( fShiftDown) { ListView_SelectRangeTo(plv, plv->iFocus, TRUE); } return; case VK_RETURN: SendNotifyEx(plv->hwndParent, plv->hwnd, NM_RETURN, NULL, plv->bUnicode); return; case VK_ADD: if (ListView_IsReportView(plv) && (GetKeyState(VK_CONTROL) < 0)) { HCURSOR hcurPrev; int i; hcurPrev = SetCursor(LoadCursor(NULL, IDC_WAIT)); for (i=0; i < plv->cCol; i++) { ListView_RSetColumnWidth(plv, i, -1); } SetCursor(hcurPrev); return; } } if (GetKeyState(VK_MENU) < 0) return; // For a single selection listview, disable extending the selection // by turning off the keyboard modifiers. if (plv->style & LVS_SINGLESEL) { fCtlDown = FALSE; fShiftDown = FALSE; } // // Let the Arrow function attempt to process the key. // iNewFocus = ListView_Arrow(plv, plv->iFocus, vk); // If control (disjoint) selection, don't change selection. // If shift (extend) or control selection, don't deselect all. // if (iNewFocus != -1) { if (fShiftDown) { ListView_SelectRangeTo(plv, iNewFocus, TRUE); ListView_SetFocusSel(plv, iNewFocus, FALSE, FALSE, FALSE); } else { if (!fCtlDown) plv->iMark = iNewFocus; ListView_SetFocusSel(plv, iNewFocus, !fCtlDown, !fShiftDown && !fCtlDown, FALSE); } IncrementSearchString(0, NULL); } // on keyboard movement, scroll immediately. if (ListView_CancelScrollWait(plv)) { ListView_OnEnsureVisible(plv, plv->iFocus, FALSE); UpdateWindow(plv->hwnd); } } #ifdef FE_IME // Now only Korean version is interested in incremental search with composition string. #define GET_COMP_STRING(hImc, dwFlags, pszCompStr) \ { \ int iNumComp; \ (pszCompStr) = (PSTR)LocalAlloc(LPTR, 1); \ if (iNumComp = (int)ImmGetCompositionString((hImc), (dwFlags), NULL, 0)) \ if ((pszCompStr) = (PSTR)LocalReAlloc((pszCompStr), iNumComp+1, LMEM_MOVEABLE)) \ { \ ImmGetCompositionString((hImc), (dwFlags), (pszCompStr), iNumComp+1); \ (pszCompStr)[iNumComp] = '\0'; \ } \ } #define FREE_COMP_STRING(pszCompStr) LocalFree((HLOCAL)(pszCompStr)) BOOL NEAR ListView_OnImeComposition(LV* plv, WPARAM wParam, LPARAM lParam) { LPSTR lpsz; LV_FINDINFO lvfi; int i; int iStartFrom = -1; int iLen; int iCount; HIMC hImc; char *pszCompStr; BOOL fRet = TRUE; iCount = ListView_Count(plv); if (!iCount || plv->iFocus == -1) return fRet; if (hImc = ImmGetContext(plv->hwnd)) { if (lParam & GCS_RESULTSTR) { fRet = FALSE; GET_COMP_STRING(hImc, GCS_RESULTSTR, pszCompStr); if (pszCompStr) { IncrementSearchImeCompStr(FALSE, pszCompStr, &lpsz); FREE_COMP_STRING(pszCompStr); } } if (lParam & GCS_COMPSTR) { fRet = TRUE; GET_COMP_STRING(hImc, GCS_COMPSTR, pszCompStr); if (pszCompStr) { if (IncrementSearchImeCompStr(TRUE, pszCompStr, &lpsz)) iStartFrom = plv->iFocus; else iStartFrom = ((plv->iFocus - 1) + iCount)% iCount; lvfi.flags = LVFI_SUBSTRING | LVFI_STRING | LVFI_WRAP; lvfi.psz = lpsz; iLen = lstrlen(lpsz); // special case space as the first character if ((iLen == 1) && (*lpsz == ' ')) { if (plv->iFocus != -1) { ListView_OnSetItemState(plv, plv->iFocus, LVIS_SELECTED, LVIS_SELECTED); IncrementSearchString(0, NULL); } return fRet; } i = ListView_OnFindItem(plv, iStartFrom, &lvfi); #ifdef LVDEBUG DebugMsg(DM_TRACE, "CIme listsearch %08lX %s %d", (LPSTR)lpsz, (LPSTR)lpsz, i); #endif if (i != -1) { // if they're hitting the same char, make sure there's not a closer match // (find "new" before "nn") if (iLen > 2 && SameDBCSChars(lpsz, (WORD)((BYTE)lpsz[0] << 8 | (BYTE)lpsz[1]))) { int i2; lvfi.psz = lpsz + iLen - 2; i2 = ListView_OnFindItem(plv, (plv->iFocus) % iCount, &lvfi); // see if i2 is between iStartFrom and i if (i < plv->iFocus) i += iCount; if (i2 < plv->iFocus) i2 += iCount; if (i2 < i && i2 > plv->iFocus) i = i2; i %= iCount; #ifdef LVDEBUG DebugMsg(DM_TRACE, "CIme listsearch2 %d %d", i2, i); #endif } ListView_SetFocusSel(plv, i, TRUE, TRUE, FALSE); plv->iMark = i; if (ListView_CancelScrollWait(plv)) ListView_OnEnsureVisible(plv, i, FALSE); } else { // if they hit the same key twice in a row at the beginning of // the search, and there was no item found, they likely meant to // retstart the search if (iLen > 2 && SameDBCSChars(lpsz, (WORD)((BYTE)lpsz[0] << 8 | (BYTE)lpsz[1]))) { // first clear out the string so that we won't recurse again IncrementSearchString(0, NULL); ListView_OnImeComposition(plv, wParam, lParam); } else { // Don't beep on spaces, we use it for selection. if (!g_iIncrSearchFailed) MessageBeep(0); g_iIncrSearchFailed++; } } FREE_COMP_STRING(pszCompStr); } } ImmReleaseContext(plv->hwnd, hImc); } return fRet; } BOOL FAR PASCAL SameDBCSChars(LPSTR lpsz, WORD w) { while (*lpsz) { if (IsDBCSLeadByte((BYTE)*lpsz) == FALSE) return FALSE; if ((WORD)((BYTE)*lpsz++ << 8 | (BYTE)*lpsz++) != w) return FALSE; } return TRUE; } #endif // REVIEW: We will want to reset ichCharBuf to 0 on certain conditions, // such as: focus change, ENTER, arrow key, mouse click, etc. // void NEAR ListView_OnChar(LV* plv, UINT ch, int cRepeat) { LPTSTR lpsz; LV_FINDINFO lvfi; int i; int iStartFrom = -1; int iLen; int iCount; iCount = ListView_Count(plv); if (!iCount || plv->iFocus == -1) return; // Don't search for chars that cannot be in a file name (like ENTER and TAB) if (ch < TEXT(' ') || GetKeyState(VK_CONTROL) < 0) { IncrementSearchString(0, NULL); return; } if (IncrementSearchString(ch, &lpsz)) iStartFrom = plv->iFocus; else iStartFrom = ((plv->iFocus - 1) + iCount)% iCount; lvfi.flags = LVFI_SUBSTRING | LVFI_STRING | LVFI_WRAP; lvfi.psz = lpsz; iLen = lstrlen(lpsz); // special case space as the first character if ((iLen == 1) && (*lpsz == ' ')) { if (plv->iFocus != -1) { ListView_OnSetItemState(plv, plv->iFocus, LVIS_SELECTED, LVIS_SELECTED); IncrementSearchString(0, NULL); } return; } i = ListView_OnFindItem(plv, iStartFrom, &lvfi); if (i >= iCount) { i = -1; } #ifdef LVDEBUG DebugMsg(DM_TRACE, TEXT("listsearch %d %s %d"), (LPTSTR)lpsz, (LPTSTR)lpsz, i); #endif if (i != -1) { // if they're hitting the same char, make sure there's not a closer match // (find "new" before "nn") if (iLen > 1 && SameChars(lpsz, lpsz[0])) { int i2; lvfi.psz = lpsz + iLen - 1; i2 = ListView_OnFindItem(plv, (plv->iFocus) % iCount, &lvfi); // see if i2 is between iStartFrom and i if (i < plv->iFocus) i += iCount; if (i2 < plv->iFocus) i2 += iCount; if (i2 < i && i2 > plv->iFocus) i = i2; i %= iCount; #ifdef LVDEBUG DebugMsg(DM_TRACE, TEXT("listsearch2 %d %d"), i2, i); #endif } ListView_SetFocusSel(plv, i, TRUE, TRUE, FALSE); plv->iMark = i; if (ListView_CancelScrollWait(plv)) ListView_OnEnsureVisible(plv, i, FALSE); } else { // if they hit the same key twice in a row at the beginning of // the search, and there was no item found, they likely meant to // retstart the search if (iLen > 1 && SameChars(lpsz, lpsz[0])) { // first clear out the string so that we won't recurse again IncrementSearchString(0, NULL); ListView_OnChar(plv, ch, cRepeat); } else { // Don't beep on spaces, we use it for selection. if (!g_iIncrSearchFailed) MessageBeep(0); g_iIncrSearchFailed++; } } } BOOL FAR PASCAL SameChars(LPTSTR lpsz, TCHAR c) { while (*lpsz) { if (*lpsz++ != c) return FALSE; } return TRUE; } UINT NEAR ListView_OnGetDlgCode(LV* plv, MSG FAR* lpmsg) { return DLGC_WANTARROWS | DLGC_WANTCHARS; } void NEAR ListView_InvalidateCachedLabelSizes(LV* plv) { int i; // Label wrapping has changed, so we need to invalidate the // size of the items, such that they will be recomputed. // #ifdef WINNT_CAIRO if (!ListView_IsOwnerData( plv )) { #else { if (plv->style & LVS_NOITEMDATA) { for (i = ListView_Count(plv) - 1; i >= 0; i--) { ListView_NIDSetItemCXLabel(plv, i, SRECOMPUTE); } } else #endif { for (i = ListView_Count(plv) - 1; i >= 0; i--) { LISTITEM FAR* pitem = ListView_FastGetItemPtr(plv, i); pitem->cxSingleLabel = pitem->cxMultiLabel = pitem->cyMultiLabel = SRECOMPUTE; } } } plv->rcView.left = RECOMPUTE; if ((plv->style & LVS_OWNERDRAWFIXED) && ListView_IsReportView(plv)) plv->cyItemSave = max(plv->cyLabelChar, plv->cySmIcon) + g_cyBorder; else { plv->cyItem = max(plv->cyLabelChar, plv->cySmIcon) + g_cyBorder; } } void NEAR ListView_OnStyleChanged(LV* plv, UINT gwl, LPSTYLESTRUCT pinfo) { // Style changed: redraw everything... // // try to do this smartly, avoiding unnecessary redraws if (gwl == GWL_STYLE) { BOOL fRedraw = FALSE, fShouldScroll = FALSE; DWORD changeFlags, styleOld; ListView_DismissEdit(plv, FALSE); // Cancels edits changeFlags = plv->style ^ pinfo->styleNew; styleOld = plv->style; plv->style = pinfo->styleNew; // change our version if (changeFlags & LVS_NOLABELWRAP) { ListView_InvalidateCachedLabelSizes(plv); fShouldScroll = TRUE; fRedraw = TRUE; } if (changeFlags & LVS_TYPEMASK) { ListView_TypeChange(plv, styleOld); fShouldScroll = TRUE; fRedraw = TRUE; } if ((changeFlags & LVS_AUTOARRANGE) && (plv->style & LVS_AUTOARRANGE)) { ListView_OnArrange(plv, LVA_DEFAULT); fRedraw = TRUE; } // bugbug, previously, this was the else to // (changeFlags & LVS_AUTOARRANGE && (plv->style & LVS_AUTOARRANGE)) // I'm not sure that was really the right thing.. if (fShouldScroll) { // Else we would like to make the most important item to still // be visible. So first we will look for a cursorered item // if this fails, we will look for the first selected item, // else we will simply ask for the first item (assuming the // count > 0 // int i; // And make sure the scrollbars are up to date Note this // also updates some variables that some views need ListView_UpdateScrollBars(plv); i = (plv->iFocus >= 0) ? plv->iFocus : ListView_OnGetNextItem(plv, -1, LVNI_SELECTED); if ((i == -1) && (ListView_Count(plv) > 0)) i = 0; if (i != -1) ListView_OnEnsureVisible(plv, i, TRUE); } if (fRedraw) RedrawWindow(plv->hwnd, NULL, NULL, RDW_INVALIDATE | RDW_ERASE); } } void NEAR ListView_TypeChange(LV* plv, DWORD styleOld) { RECT rc; switch (styleOld & LVS_TYPEMASK) { case LVS_REPORT: ShowWindow(plv->hwndHdr, SW_HIDE); if (styleOld & LVS_OWNERDRAWFIXED) { // swap cyItem and cyFixed; int temp = plv->cyItem; plv->cyItem = plv->cyItemSave; plv->cyItemSave = temp; } break; default: break; } // Now handle any special setup needed for the new view switch (plv->style & LVS_TYPEMASK) { case (UINT)LVS_ICON: ListView_ScaleIconPositions(plv, FALSE); break; case (UINT)LVS_SMALLICON: ListView_ScaleIconPositions(plv, TRUE); break; case (UINT)LVS_LIST: // We may need to resize the columns ListView_MaybeResizeListColumns(plv, 0, ListView_Count(plv)-1); break; case (UINT)LVS_REPORT: // if it's owner draw fixed, we may have to do funky stuff if ((styleOld & LVS_TYPEMASK) != LVS_REPORT) { plv->cyItemSave = plv->cyItem; } ListView_RInitialize(plv, FALSE); break; default: break; } GetClientRect(plv->hwnd, &rc); plv->sizeClient.cx = rc.right; plv->sizeClient.cy = rc.bottom; } int NEAR ListView_OnHitTest(LV* plv, LV_HITTESTINFO FAR* pinfo) { UINT flags; int x, y; if (!pinfo) return -1; x = pinfo->pt.x; y = pinfo->pt.y; pinfo->iItem = -1; flags = 0; if (x < 0) flags |= LVHT_TOLEFT; else if (x >= plv->sizeClient.cx) flags |= LVHT_TORIGHT; if (y < 0) flags |= LVHT_ABOVE; else if (y >= plv->sizeClient.cy) flags |= LVHT_BELOW; if (flags == 0) { if (ListView_IsSmallView(plv)) pinfo->iItem = ListView_SItemHitTest(plv, x, y, &flags); else if (ListView_IsListView(plv)) pinfo->iItem = ListView_LItemHitTest(plv, x, y, &flags); else if (ListView_IsIconView(plv)) pinfo->iItem = ListView_IItemHitTest(plv, x, y, &flags); else if (ListView_IsReportView(plv)) pinfo->iItem = ListView_RItemHitTest(plv, x, y, &flags); } pinfo->flags = flags; return pinfo->iItem; } int NEAR ScrollAmount(int large, int iSmall, int unit) { return (((large - iSmall) + (unit - 1)) / unit) * unit; } // NOTE: this is duplicated in shell32.dll // // checks to see if we are at the end position of a scroll bar // to avoid scrolling when not needed (avoid flashing) // // in: // code SB_VERT or SB_HORZ // bDown FALSE is up or left // TRUE is down or right BOOL NEAR PASCAL CanScroll(HWND hwnd, int code, BOOL bDown) { SCROLLINFO si; si.cbSize = sizeof(SCROLLINFO); si.fMask = SIF_RANGE | SIF_PAGE | SIF_POS; if (GetScrollInfo(hwnd, code, &si)) { if (bDown) { if (si.nPage) si.nMax -= si.nPage - 1; return si.nPos < si.nMax; } else { return si.nPos > si.nMin; } } else { return FALSE; } } // detect if we should auto scroll the window // // in: // pt cursor pos in hwnd's client coords // out: // pdx, pdy ammount scrolled in x and y // // REVIEW, this should make sure a certain amount of time has passed // before scrolling. void NEAR ScrollDetect(LV* plv, POINT pt, int FAR *pdx, int FAR *pdy) { int dx, dy; *pdx = *pdy = 0; if (!(plv->style & (WS_HSCROLL | WS_VSCROLL))) return; dx = dy = plv->cyIcon / 2; if (ListView_IsReportView(plv)) { dy = plv->cyItem; // we scroll in units of items... if (!dx) dx = plv->cxSmIcon; } if (ListView_IsListView(plv)) dx = plv->cxItem; if (!dx) dx = 1; if (!dy) dy = 1; // we need to check if we can scroll before acutally doing it // since the selection rect is adjusted based on how much // we scroll by if (plv->style & WS_VSCROLL) { // scroll vertically? if (pt.y >= plv->sizeClient.cy) { if (CanScroll(plv->hwnd, SB_VERT, TRUE)) *pdy = ScrollAmount(pt.y, plv->sizeClient.cy, dy); // down } else if (pt.y <= 0) { if (CanScroll(plv->hwnd, SB_VERT, FALSE)) *pdy = -ScrollAmount(0, pt.y, dy); // up } } if (plv->style & WS_HSCROLL) { // horizontally if (pt.x >= plv->sizeClient.cx) { if (CanScroll(plv->hwnd, SB_HORZ, TRUE)) *pdx = ScrollAmount(pt.x, plv->sizeClient.cx, dx); // right } else if (pt.x <= 0) { if (CanScroll(plv->hwnd, SB_HORZ, FALSE)) *pdx = -ScrollAmount(0, pt.x, dx); // left } } // BUGBUG: this will potentially scroll outside the bounds of the // listview. we should bound the scroll amount in CanScroll() // or ScrollAmount(). if (*pdx || *pdy) { ListView_ValidateScrollParams(plv, pdx, pdy); } } #define swap(pi1, pi2) {int i = *(pi1) ; *(pi1) = *(pi2) ; *(pi2) = i ;} void NEAR OrderRect(RECT FAR *prc) { if (prc->left > prc->right) swap(&prc->left, &prc->right); if (prc->bottom < prc->top) swap(&prc->bottom, &prc->top); } // in: // x, y starting point in client coords #define SCROLL_FREQ (GetDoubleClickTime()/2) // 1/5 of a second between scrolls //---------------------------------------------------------------------------- BOOL ShouldScroll(LV *plv, LPPOINT ppt, LPRECT lprc) { Assert(ppt); if (plv->style & WS_VSCROLL) { if (ppt->y >= lprc->bottom) { if (CanScroll(plv->hwnd, SB_VERT, TRUE)) return TRUE; } else if (ppt->y <= lprc->top) { if (CanScroll(plv->hwnd, SB_VERT, FALSE)) return TRUE; } } if (plv->style & WS_HSCROLL) { if (ppt->x >= lprc->right) { if (CanScroll(plv->hwnd, SB_HORZ, TRUE)) return TRUE; } else if (ppt->x <= lprc->left) { if (CanScroll(plv->hwnd, SB_HORZ, FALSE)) return TRUE; } } return FALSE; } //---------------------------------------------------------------------------- void NEAR ListView_DragSelect(LV *plv, int x, int y) { RECT rc, rcWindow, rcOld, rcUnion, rcTemp2; POINT pt; MSG32 msg32; HDC hdc; HWND hwnd = plv->hwnd; int i, iEnd, dx, dy; BOOL bInOld, bInNew = FALSE; DWORD dwTime, dwNewTime; rc.left = rc.right = x; rc.top = rc.bottom = y; rcOld = rc; SetCapture(hwnd); hdc = GetDC(hwnd); DrawFocusRect(hdc, &rc); GetWindowRect(hwnd, &rcWindow); dwTime = GetTickCount(); for (;;) { if (!PeekMessage32(&msg32, NULL, 0, 0, PM_REMOVE, TRUE)) { // if the cursor is outside of the window rect // we need to generate messages to make autoscrolling // keep going if (!PtInRect(&rcWindow, msg32.pt)) { // If we may be able to scroll, generate a mouse move. if (ShouldScroll(plv, &msg32.pt, &rcWindow)) SetCursorPos(msg32.pt.x, msg32.pt.y); } continue; } // WM_CANCELMODE messages will unset the capture, in that // case I want to exit this loop if (GetCapture() != hwnd) { break; } // See if the application wants to process the message... if (CallMsgFilter32(&msg32, MSGF_COMMCTRL_DRAGSELECT, TRUE) != 0) continue; switch (msg32.message) { case WM_LBUTTONUP: case WM_RBUTTONUP: case WM_LBUTTONDOWN: case WM_MBUTTONDOWN: case WM_MBUTTONUP: case WM_RBUTTONDOWN: ReleaseCapture(); goto EndOfLoop; case WM_TIMER: if (msg32.wParam != IDT_MARQUEE) goto DoDefault; // else fall through case WM_MOUSEMOVE: { int dMax = -1; pt = msg32.pt; ScreenToClient(hwnd, &pt); dwNewTime = GetTickCount(); if ((dwNewTime - dwTime) > SCROLL_FREQ) { dwTime = dwNewTime; // reset scroll timer ScrollDetect(plv, pt, &dx, &dy); } else { dx = dy = 0; } SetTimer(plv->hwnd, IDT_MARQUEE, SCROLL_FREQ, NULL); y -= dy; // scroll up/down x -= dx; // scroll left/right rc.left = x; rc.top = y; rc.right = pt.x; rc.bottom = pt.y; OrderRect(&rc); if (EqualRect(&rc, &rcOld)) break; // move the old rect DrawFocusRect(hdc, &rcOld); // erase old if (dx || dy) ListView_OnScroll(plv, dx, dy); OffsetRect(&rcOld, -dx, -dy); // // For Report and List view, we can speed things up by // only searching through those items that are visible. We // use the hittest to calculate the first item to paint. // BUGBUG:: We are using state specific info here... // UnionRect(&rcUnion, &rc, &rcOld); if (ListView_IsReportView(plv)) { i = (int)((plv->ptlRptOrigin.y + rcUnion.top - plv->yTop) / plv->cyItem); iEnd = (int)((plv->ptlRptOrigin.y + rcUnion.bottom - plv->yTop) / plv->cyItem) + 1; } else if (ListView_IsListView(plv)) { i = ((plv->xOrigin + rcUnion.left)/ plv->cxItem) * plv->cItemCol + rcUnion.top / plv->cyItem; iEnd = ((plv->xOrigin + rcUnion.right)/ plv->cxItem) * plv->cItemCol + rcUnion.bottom / plv->cyItem + 1; } else { #ifdef WINNT_CAIRO if (ListView_IsOwnerData( plv )) { ListView_CalcMinMaxIndex( plv, &rcUnion, &i, &iEnd ); } else #endif { i = 0; iEnd = ListView_Count(plv); } } // make sure our endpoint is in range. if (iEnd > ListView_Count(plv)) iEnd = ListView_Count(plv); if (i < 0) i = 0; #ifdef WINNT_CAIRO if (ListView_IsOwnerData(plv) && (i < iEnd)) { ListView_NotifyCacheHint(plv, i, iEnd-1); } #endif if (bInNew && !(msg32.wParam & (MK_CONTROL | MK_SHIFT))) { plv->iMark = -1; } for (; i < iEnd; i++) { RECT dummy; ListView_GetRects(plv, i, NULL, NULL, NULL, &rcTemp2); InflateRect(&rcTemp2, -(rcTemp2.right - rcTemp2.left) / 4, -(rcTemp2.bottom - rcTemp2.top) / 4); bInOld = (IntersectRect(&dummy, &rcOld, &rcTemp2) != 0); bInNew = (IntersectRect(&dummy, &rc, &rcTemp2) != 0); if (msg32.wParam & MK_CONTROL) { if (bInOld != bInNew) { ListView_ToggleSelection(plv, i); } } else { // was there a change? if (bInOld != bInNew) { ListView_OnSetItemState(plv, i, bInOld ? 0 : LVIS_SELECTED, LVIS_SELECTED); } // if no alternate keys are down.. set the mark to // the item furthest from the cursor if (bInNew && !(msg32.wParam & (MK_CONTROL | MK_SHIFT))) { int dItem; dItem = (rcTemp2.left - pt.x) * (rcTemp2.left - pt.x) + (rcTemp2.top - pt.y) * (rcTemp2.top - pt.y); // if it's further away, set this as the mark //DebugMsg(DM_TRACE, "dItem = %d, dMax = %d", dItem, dMax); if (dItem > dMax) { //DebugMsg(DM_TRACE, "taking dItem .. iMark = %d", i); dMax = dItem; plv->iMark = i; } } } } //DebugMsg(DM_TRACE, "Final iMark = %d", plv->iMark); UpdateWindow(plv->hwnd); // make selection draw DrawFocusRect(hdc, &rc); rcOld = rc; break; } case WM_KEYDOWN: switch (msg32.wParam) { case VK_ESCAPE: ListView_DeselectAll(plv, -1); goto EndOfLoop; } case WM_CHAR: case WM_KEYUP: // don't process thay keyboard stuff during marquee break; default: DoDefault: TranslateMessage32(&msg32, TRUE); DispatchMessage32(&msg32, TRUE); } } EndOfLoop: DrawFocusRect(hdc, &rcOld); // erase old ReleaseDC(hwnd, hdc); } #define SHIFT_DOWN(keyFlags) (keyFlags & MK_SHIFT) #define CONTROL_DOWN(keyFlags) (keyFlags & MK_CONTROL) #define RIGHTBUTTON(keyFlags) (keyFlags & MK_RBUTTON) void NEAR ListView_OnButtonDown(LV* plv, BOOL fDoubleClick, int x, int y, UINT keyFlags) { int iItem; BOOL bSelected; LV_HITTESTINFO ht; BOOL fNotifyReturn = FALSE; int click = RIGHTBUTTON(keyFlags) ? NM_RCLICK : NM_CLICK; int drag = RIGHTBUTTON(keyFlags) ? LVN_BEGINRDRAG : LVN_BEGINDRAG; #ifdef LVDEBUG DebugMsg(DM_TRACE, TEXT("ListView_OnButtonDown %d"), fDoubleClick); #endif #if 0 if (!ListView_DismissEdit(plv, FALSE)) // end any previous editing (accept it) return; // Something happened such that we should not process button down #endif SetCapture(plv->hwnd); if (!ListView_DismissEdit(plv, FALSE) && GetCapture() != plv->hwnd) return; ReleaseCapture(); // REVIEW: right button implies no shift or control stuff // Single selection style also implies no modifiers //if (RIGHTBUTTON(keyFlags) || (plv->style & LVS_SINGLESEL)) if ((plv->style & LVS_SINGLESEL)) keyFlags &= ~(MK_SHIFT | MK_CONTROL); ht.pt.x = x; ht.pt.y = y; iItem = ListView_OnHitTest(plv, &ht); bSelected = (iItem >= 0) && ListView_OnGetItemState(plv, iItem, LVIS_SELECTED); if (fDoubleClick) { // // Cancel any name editing that might happen. // ListView_CancelPendingEdit(plv); KillTimer(plv->hwnd, IDT_SCROLLWAIT); #if 0 if ((ht.flags & (LVHT_ONITEMLABEL | LVHT_ONITEMICON)) && !bSelected && !SHIFT_DOWN(keyFlags) && !CONTROL_DOWN(keyFlags)) { ListView_SetFocusSel(plv, iItem, TRUE, TRUE, FALSE); } #endif SendNotifyEx(plv->hwndParent, plv->hwnd, RIGHTBUTTON(keyFlags) ? NM_RDBLCLK : NM_DBLCLK, NULL, plv->bUnicode); return; } if (ht.flags & (LVHT_ONITEMLABEL | LVHT_ONITEMICON)) { if (SHIFT_DOWN(keyFlags)) { ListView_SelectRangeTo(plv, iItem, !CONTROL_DOWN(keyFlags)); ListView_SetFocusSel(plv, iItem, TRUE, FALSE, FALSE); } else if (!CONTROL_DOWN(keyFlags)) { ListView_SetFocusSel(plv, iItem, TRUE, !bSelected, FALSE); } if (CheckForDragBegin(plv->hwnd, x, y)) { // Before we start dragging, make it sure that it is // selected and has the focus. ListView_SetFocusSel(plv, iItem, TRUE, FALSE, FALSE); // Then, we need to update the window before start dragging // to show the selection chagne. UpdateWindow(plv->hwnd); // let the caller start dragging ListView_SendChange(plv, iItem, 0, drag, 0, 0, 0, x, y, 0); return; } else { // button came up and we are not dragging if (CONTROL_DOWN(keyFlags)) { // do this on the button up so that ctrl-dragging a range // won't toggle the select. if (SHIFT_DOWN(keyFlags)) ListView_SetFocusSel(plv, iItem, FALSE, FALSE, FALSE); else { ListView_SetFocusSel(plv, iItem, TRUE, FALSE, TRUE); } } if (!SHIFT_DOWN(keyFlags)) plv->iMark = iItem; SetFocus(plv->hwnd); // activate this window // now do the deselect stuff if (!SHIFT_DOWN(keyFlags) && !CONTROL_DOWN(keyFlags) && !RIGHTBUTTON(keyFlags)) { ListView_DeselectAll(plv, iItem); if ((ht.flags & LVHT_ONITEMLABEL) && bSelected) { // Click on item label. It was selected and // no modifier keys were pressed and no drag operation // So setup for name edit mode. Still need to wait // to make sure user is not doing double click. // ListView_SetupPendingNameEdit(plv); } } fNotifyReturn = !SendNotifyEx(plv->hwndParent, plv->hwnd, click, NULL, plv->bUnicode); } } else if (ht.flags & LVHT_ONITEMSTATEICON) { // Should activate window and send notificiation to parent... SetFocus(plv->hwnd); // activate this window fNotifyReturn = !SendNotifyEx(plv->hwndParent, plv->hwnd, click, NULL, plv->bUnicode); } else if (ht.flags & LVHT_NOWHERE) { if (!SHIFT_DOWN(keyFlags) && !CONTROL_DOWN(keyFlags)) ListView_DeselectAll(plv, -1); SetFocus(plv->hwnd); // activate this window // If single-select listview, disable marquee selection. if (!(plv->style & LVS_SINGLESEL) && CheckForDragBegin(plv->hwnd, x, y)) { ListView_DragSelect(plv, x, y); } fNotifyReturn = !SendNotifyEx(plv->hwndParent, plv->hwnd, click, NULL, plv->bUnicode); } if (fNotifyReturn && (click == NM_RCLICK)) SendMessage(plv->hwndParent, WM_CONTEXTMENU, (WPARAM)plv->hwnd, GetMessagePos()); } #define ListView_CancelPendingEdit(plv) ListView_CancelPendingTimer(plv, LVF_NMEDITPEND, IDT_NAMEEDIT) #define ListView_CancelScrollWait(plv) ListView_CancelPendingTimer(plv, LVF_SCROLLWAIT, IDT_SCROLLWAIT) BOOL NEAR ListView_CancelPendingTimer(LV* plv, UINT fFlags, int idTimer) { if (plv->flags & fFlags) { KillTimer(plv->hwnd, idTimer); plv->flags &= ~fFlags; return TRUE; } return FALSE; } // // ListView_OnTimer: // process the WM_TIMER message. If the timer id is thta // of the name editing, we should then start the name editing mode. // void NEAR ListView_OnTimer(LV* plv, UINT id) { if (id == IDT_NAMEEDIT) { // Kill the timer as we wont need any more messages from it. if (ListView_CancelPendingEdit(plv)) { // And start name editing mode. if (!ListView_OnEditLabel(plv, plv->iFocus, NULL)) { ListView_DismissEdit(plv, FALSE); ListView_SetFocusSel(plv, plv->iFocus, TRUE, TRUE, FALSE); } } } else if (id == IDT_SCROLLWAIT) { if (ListView_CancelScrollWait(plv)) { ListView_OnEnsureVisible(plv, plv->iFocus, TRUE); } } KillTimer(plv->hwnd, id); } // // ListView_SetupPendingNameEdit: // Sets up a timer to begin name editing at a delayed time. This // will allow the user to double click on the already selected item // without going into name editing mode, which is especially important // in those views that only show a small icon. // void NEAR ListView_SetupPendingNameEdit(LV* plv) { SetTimer(plv->hwnd, IDT_NAMEEDIT, GetDoubleClickTime(), NULL); plv->flags |= LVF_NMEDITPEND; } void NEAR PASCAL _ListView_OnScroll(LV* plv, UINT code, int pos, int sb) { #ifdef SIF_TRACKPOS SCROLLINFO si; si.cbSize = sizeof(SCROLLINFO); si.fMask = SIF_TRACKPOS; // if we're in 32bits, don't trust the pos since it's only 16bit's worth if (GetScrollInfo(plv->hwnd, sb, &si)) pos = (int)si.nTrackPos; #endif ListView_DismissEdit(plv, FALSE); if (ListView_IsIconView(plv) || ListView_IsSmallView(plv)) ListView_IOnScroll(plv, code, pos, sb); else if (ListView_IsListView(plv)) ListView_LOnScroll(plv, code, pos); else if (ListView_IsReportView(plv)) ListView_ROnScroll(plv, code, pos, sb); } void NEAR ListView_OnVScroll(LV* plv, HWND hwndCtl, UINT code, int pos) { _ListView_OnScroll(plv, code, pos, SB_VERT); } void NEAR ListView_OnHScroll(LV* plv, HWND hwndCtl, UINT code, int pos) { _ListView_OnScroll(plv, code, pos, SB_HORZ); } int ListView_ValidateOneScrollParam(LV* plv, int iDirection, int dx) { SCROLLINFO si; si.cbSize = sizeof(SCROLLINFO); si.fMask = SIF_PAGE | SIF_RANGE | SIF_POS; if (!GetScrollInfo(plv->hwnd, iDirection, &si)) return 0; si.nMax -= (si.nPage - 1); si.nPos += dx; if (si.nPos < si.nMin) { dx += (int)(si.nMin - si.nPos); } else if (si.nPos > si.nMax) { dx -= (int)(si.nPos - si.nMax); } return dx; } BOOL NEAR PASCAL ListView_ValidateScrollParams(LV* plv, int FAR * pdx, int FAR *pdy) { int dx = *pdx; int dy = *pdy; if (plv->style & LVS_NOSCROLL) return FALSE; if (ListView_IsListView(plv)) { ListView_MaybeResizeListColumns(plv, 0, ListView_Count(plv)-1); #ifdef COLUMN_VIEW if (dx < 0) dx = (dx - plv->cxItem - 1) / plv->cxItem; else dx = (dx + plv->cxItem - 1) / plv->cxItem; if (dy) return FALSE; #else if (dy < 0) dy = (dy - plv->cyItem - 1) / plv->cyItem; else dy = (dy + plv->cyItem - 1) / plv->cyItem; if (dx) return FALSE; #endif } else if (ListView_IsReportView(plv)) { // // Note: This function expects that dy is in number of lines // and we are working with pixels so do a conversion use some // rounding up and down to make it right if (dy > 0) dy = (dy + plv->cyItem/2) / plv->cyItem; else dy = (dy - plv->cyItem/2) / plv->cyItem; } if (dy) { dy = ListView_ValidateOneScrollParam(plv, SB_VERT, dy); if (ListView_IsReportView(plv) #ifndef COLUMN_VIEW || ListView_IsListView(plv) #endif ) { // convert back to pixels dy *= plv->cyItem; } *pdy = dy; } if (dx) { dx = ListView_ValidateOneScrollParam(plv, SB_HORZ, dx); #ifdef COLUMN_VIEW if (ListView_IsListView(plv)) { dx *= plv->cxItem; } #endif *pdx = dx; } return TRUE; } BOOL NEAR ListView_OnScroll(LV* plv, int dx, int dy) { if (plv->style & LVS_NOSCROLL) return FALSE; if (ListView_IsIconView(plv)) { ListView_IScroll2(plv, dx, dy); } else if (ListView_IsSmallView(plv)) { ListView_IScroll2(plv, dx, dy); } else if (ListView_IsListView(plv)) { // Scale pixel count to column count // #ifdef COLUMN_VIEW if (dx < 0) dx -= plv->cxItem - 1; else dx += plv->cxItem - 1; dx = dx / plv->cxItem; if (dy) return FALSE; ListView_LScroll2(plv, dx, 0); #else if (dy < 0) dy -= plv->cyItem - 1; else dy += plv->cyItem - 1; dy = dy / plv->cyItem; if (dx) return FALSE; ListView_LScroll2(plv, 0, dy); #endif } else if (ListView_IsReportView(plv)) { // // Note: This function expects that dy is in number of lines // and we are working with pixels so do a conversion use some // rounding up and down to make it right if (dy > 0) dy = (dy + plv->cyItem/2) / plv->cyItem; else dy = (dy - plv->cyItem/2) / plv->cyItem; ListView_RScroll2(plv, dx, dy); } ListView_UpdateScrollBars(plv); return TRUE; } BOOL NEAR ListView_OnEnsureVisible(LV* plv, int i, BOOL fPartialOK) { RECT rcBounds; RECT rc; int dx, dy; if (i < 0 || i >= ListView_Count(plv) || plv->style & LVS_NOSCROLL) return FALSE; // we need to do this again inside because some callers don't do it. // other callers that do this need to do it outside so that // they can know not to call us if there's not wait pending ListView_CancelScrollWait(plv); if (ListView_IsReportView(plv)) return ListView_ROnEnsureVisible(plv, i, fPartialOK); ListView_GetRects(plv, i, &rc, NULL, &rcBounds, NULL); if (!fPartialOK) rc = rcBounds; // If any part of rc is outside of rcClient, then // scroll so that all of rcBounds is visible. // dx = 0; if (rc.left < 0 || rc.right >= plv->sizeClient.cx) { dx = rcBounds.left - 0; if (dx >= 0) { dx = rcBounds.right - plv->sizeClient.cx; if (dx <= 0) dx = 0; else if ((rcBounds.left - dx) < 0) dx = rcBounds.left - 0; // Not all fits... } } dy = 0; if (rc.top < 0 || rc.bottom >= plv->sizeClient.cy) { dy = rcBounds.top - 0; if (dy >= 0) { dy = rcBounds.bottom - plv->sizeClient.cy; if (dy < 0) dy = 0; } } if (dx | dy) return ListView_OnScroll(plv, dx, dy); return TRUE; } void NEAR ListView_UpdateScrollBars(LV* plv) { RECT rc; DWORD dwStyle; if ((plv->style & LVS_NOSCROLL) || (!(ListView_RedrawEnabled(plv)))) return; if (ListView_IsIconView(plv) || ListView_IsSmallView(plv)) ListView_IUpdateScrollBars(plv); else if (ListView_IsListView(plv)) ListView_LUpdateScrollBars(plv); else if (ListView_IsReportView(plv)) ListView_RUpdateScrollBars(plv); GetClientRect(plv->hwnd, &rc); plv->sizeClient.cx = rc.right; plv->sizeClient.cy = rc.bottom; dwStyle = GetWindowLong(plv->hwnd, GWL_STYLE); plv->style = (plv->style & ~(WS_HSCROLL | WS_VSCROLL)) | (dwStyle & WS_HSCROLL | WS_VSCROLL); } // BUGBUG: does not deal with hfont == NULL void NEAR ListView_OnSetFont(LV* plv, HFONT hfont, BOOL fRedraw) { HDC hdc; SIZE siz; if ((plv->flags & LVF_FONTCREATED) && plv->hfontLabel) { DeleteObject(plv->hfontLabel); plv->flags &= ~LVF_FONTCREATED; } if (hfont == NULL) { LOGFONT lf; SystemParametersInfo(SPI_GETICONTITLELOGFONT, sizeof(lf), &lf, FALSE); hfont = CreateFontIndirect(&lf); plv->flags |= LVF_FONTCREATED; } hdc = GetDC(HWND_DESKTOP); SelectFont(hdc, hfont); GetTextExtentPoint(hdc, TEXT("0"), 1, &siz); plv->cyLabelChar = siz.cy; plv->cxLabelChar = siz.cx; GetTextExtentPoint(hdc, c_szEllipses, CCHELLIPSES, &siz); plv->cxEllipses = siz.cx; ReleaseDC(HWND_DESKTOP, hdc); plv->hfontLabel = hfont; ListView_InvalidateCachedLabelSizes(plv); #ifdef FOLDING_NAMES /* Ensure that our tooltip control uses the same font as the list view is using, therefore / avoiding any nasty formatting problems. */ if ( plv ->hwndToolTips ) { FORWARD_WM_SETFONT( plv ->hwndToolTips, plv ->hfontLabel, FALSE, SendMessage ); } #endif // If we have a header window, we need to forward this to it also // as we have destroyed the hfont that they are using... if (plv->hwndHdr) { FORWARD_WM_SETFONT(plv->hwndHdr, plv->hfontLabel, FALSE, SendMessage); ListView_UpdateScrollBars(plv); } if (fRedraw) RedrawWindow(plv->hwnd, NULL, NULL, RDW_INVALIDATE | RDW_ERASE); } HFONT NEAR ListView_OnGetFont(LV* plv) { return plv->hfontLabel; } // This function process the WM_SETREDRAW message by setting or clearing // a bit in the listview structure, which several places in the code will // check... // // REVIEW: Should probably forward to DefWindowProc() // void NEAR ListView_OnSetRedraw(LV* plv, BOOL fRedraw) { if (fRedraw) { BOOL fChanges = FALSE; // Only do work if we're turning redraw back on... // if (!(plv->flags & LVF_REDRAW)) { plv->flags |= LVF_REDRAW; // deal with any accumulated invalid regions if (plv->hrgnInval) { UINT fRedraw = (plv->flags & LVF_ERASE) ? RDW_INVALIDATE | RDW_ERASE | RDW_UPDATENOW : RDW_UPDATENOW|RDW_INVALIDATE; if (plv->hrgnInval == (HRGN)ENTIRE_REGION) plv->hrgnInval = NULL; RedrawWindow(plv->hwnd, NULL, plv->hrgnInval, fRedraw); ListView_DeleteHrgnInval(plv); fChanges = TRUE; } plv->flags &= ~LVF_ERASE; // now deal with the optimized stuff if (ListView_IsListView(plv) || ListView_IsReportView(plv)) { if (plv->iFirstChangedNoRedraw != -1) { // We may try to resize the column if (!ListView_MaybeResizeListColumns(plv, plv->iFirstChangedNoRedraw, ListView_Count(plv)-1)) ListView_OnUpdate(plv, plv->iFirstChangedNoRedraw); } else ListView_UpdateScrollBars(plv); } else { int iCount; if (plv->iFirstChangedNoRedraw != -1) { for (iCount = ListView_Count(plv) ; plv->iFirstChangedNoRedraw < iCount; plv->iFirstChangedNoRedraw++) { ListView_InvalidateItem(plv, plv->iFirstChangedNoRedraw, FALSE, RDW_INVALIDATE); } fChanges = TRUE; } if ((plv->style & LVS_AUTOARRANGE) && fChanges) { ListView_OnUpdate(plv, plv->iFirstChangedNoRedraw); } else { ListView_UpdateScrollBars(plv); } } } } else { plv->iFirstChangedNoRedraw = -1; plv->flags &= ~LVF_REDRAW; } } HIMAGELIST NEAR ListView_OnGetImageList(LV* plv, int iImageList) { switch (iImageList) { case LVSIL_NORMAL: return plv->himl; case LVSIL_SMALL: return plv->himlSmall; case LVSIL_STATE: return plv->himlState; } Assert(0); return NULL; } HIMAGELIST NEAR ListView_OnSetImageList(LV* plv, HIMAGELIST himl, int iImageList) { HIMAGELIST hImageOld = NULL; switch (iImageList) { case LVSIL_NORMAL: hImageOld = plv->himl; plv->himl = himl; if (himl) ImageList_GetIconSize(himl, &plv->cxIcon , &plv->cyIcon); break; case LVSIL_SMALL: hImageOld = plv->himlSmall; plv->himlSmall = himl; if (himl) ImageList_GetIconSize(himl, &plv->cxSmIcon , &plv->cySmIcon); plv->cxItem = 16 * plv->cxLabelChar + plv->cxSmIcon; plv->cyItem = max(plv->cyLabelChar, plv->cySmIcon) + g_cyBorder; break; case LVSIL_STATE: if (himl) { ImageList_GetIconSize(himl, &plv->cxState , &plv->cyState); } else { plv->cxState = 0; } hImageOld = plv->himlState; plv->himlState = himl; break; default: #ifdef LVDEBUG DebugMsg(DM_TRACE, TEXT("sh TR - LVM_SETIMAGELIST: unrecognized iImageList")); #endif break; } if (himl && !(plv->style & LVS_SHAREIMAGELISTS)) ImageList_SetBkColor(himl, plv->clrBk); if (ListView_Count(plv) > 0) InvalidateRect(plv->hwnd, NULL, TRUE); return hImageOld; } #ifdef UNICODE BOOL NEAR ListView_OnGetItemA(LV* plv, LV_ITEMA *plvi) { LPWSTR pszW = NULL; LPSTR pszC = NULL; BOOL fRet; //HACK ALERT -- this code assumes that LV_ITEMA is exactly the same // as LV_ITEMW except for the pointer to the string. Assert(sizeof(LV_ITEMA) == sizeof(LV_ITEMW)) if (!plvi) return FALSE; if ((plvi->mask & LVIF_TEXT) && (plvi->pszText != NULL)) { pszC = plvi->pszText; pszW = LocalAlloc(LMEM_FIXED, plvi->cchTextMax * sizeof(WCHAR)); if (pszW == NULL) return FALSE; plvi->pszText = (LPSTR)pszW; } fRet = ListView_OnGetItem(plv, (LV_ITEM *) plvi); if (pszW != NULL) { ConvertWToAN(pszC, plvi->cchTextMax, (LPWSTR)plvi->pszText, -1); plvi->pszText = pszC; LocalFree(pszW); } return fRet; } #endif BOOL NEAR ListView_OnGetItem(LV* plv, LV_ITEM FAR* plvi) { UINT mask; LISTITEM FAR* pitem = NULL; LV_DISPINFO nm; if (!plvi) return FALSE; nm.item.mask = 0; mask = plvi->mask; #ifdef WINNT_CAIRO if (!ListView_IsOwnerData(plv)) #else if ((plv->style & LVS_NOITEMDATA) == 0) #endif { // Standard listviews pitem = ListView_GetItemPtr(plv, plvi->iItem); if (pitem == NULL) return FALSE; // number was out of range! // Handle sub-item cases for report view // if (plvi->iSubItem != 0) { if (mask & ~LVIF_TEXT) { #ifdef LVDEBUG DebugMsg(DM_ERROR, TEXT("ListView: Invalid LV_ITEM mask: %04x"), mask); #endif return FALSE; } if (mask & LVIF_TEXT) { LPTSTR psz = ListView_RGetItemText(plv, plvi->iItem, plvi->iSubItem); if (psz != LPSTR_TEXTCALLBACK) { Str_GetPtr(psz, plvi->pszText, plvi->cchTextMax); return TRUE; } nm.item.mask |= LVIF_TEXT; } } if (mask & LVIF_TEXT) { if (pitem->pszText != LPSTR_TEXTCALLBACK) Str_GetPtr(pitem->pszText, plvi->pszText, plvi->cchTextMax); else nm.item.mask |= LVIF_TEXT; } if (mask & LVIF_PARAM) plvi->lParam = pitem->lParam; if (mask & LVIF_STATE) { plvi->state = (pitem->state & plvi->stateMask); if (plv->stateCallbackMask) { nm.item.stateMask = (plvi->stateMask & plv->stateCallbackMask); if (nm.item.stateMask) { nm.item.mask |= LVIF_STATE; nm.item.state = 0; } } } if (mask & LVIF_IMAGE) { if (pitem->iImage == I_IMAGECALLBACK) nm.item.mask |= LVIF_IMAGE; else plvi->iImage = pitem->iImage; } } else { // Complete call back for info... // Handle sub-item cases for report view // if (plvi->iSubItem != 0) { if (mask & ~LVIF_TEXT) { #ifdef LVDEBUG DebugMsg(DM_ERROR, TEXT("ListView: Invalid LV_ITEM mask: %04x"), mask); #endif return FALSE; } } if (mask & LVIF_PARAM) plvi->lParam = 0L; // Dont have any to return now... if (mask & LVIF_STATE) { #ifdef WINNT_CAIRO plvi->state = 0; if (plvi->iItem == plv->iFocus) { plvi->state |= LVIS_FOCUSED; } if (SelRange_IsSelected(plv->hselrange, plvi->iItem)) { plvi->state |= LVIS_SELECTED; } plvi->state &= plvi->stateMask; #else plvi->state = (ListView_NIDGetItemState(plv, plvi->iItem) & plvi->stateMask); #endif if (plv->stateCallbackMask) { nm.item.stateMask = (plvi->stateMask & plv->stateCallbackMask); if (nm.item.stateMask) { nm.item.mask |= LVIF_STATE; nm.item.state = 0; } } } nm.item.mask |= (mask & (LVIF_TEXT | LVIF_IMAGE)); } if (nm.item.mask) { nm.item.iItem = plvi->iItem; nm.item.iSubItem = plvi->iSubItem; #ifdef WINNT_CAIRO if (ListView_IsOwnerData( plv )) #else if (plv->style & LVS_NOITEMDATA) #endif nm.item.lParam = 0L; else nm.item.lParam = pitem->lParam; // just in case LVIF_IMAGE is set and callback doesn't fill it in // ... we'd rather have a -1 than whatever garbage is on the stack nm.item.iImage = -1; if (nm.item.mask & LVIF_TEXT) { Assert(plvi->pszText); nm.item.pszText = plvi->pszText; nm.item.cchTextMax = plvi->cchTextMax; // Make sure the buffer is zero terminated... if (nm.item.cchTextMax) *nm.item.pszText = 0; } SendNotifyEx(plv->hwndParent, plv->hwnd, LVN_GETDISPINFO, &nm.hdr, plv->bUnicode); #ifdef WINNT_CAIRO // make sure that they copied the text into the buffer and // not pointed it to their data if (ListView_IsOwnerData( plv ) && (nm.item.pszText != plvi->pszText)) { DebugMsg(DM_ERROR, TEXT("ListView: LVN_GETDISPINFO (OWNERDATA), client modified text ptr rather than copy text\n") ); Assert(0); } #endif // use nm.item.mask to give the app a chance to change values if (nm.item.mask & LVIF_STATE) plvi->state ^= ((plvi->state ^ nm.item.state) & nm.item.stateMask); if (nm.item.mask & LVIF_IMAGE) plvi->iImage = nm.item.iImage; if (nm.item.mask & LVIF_TEXT) plvi->pszText = nm.item.pszText; if (pitem && (nm.item.mask & LVIF_DI_SETITEM)) { //DebugMsg(DM_TRACE, "SAVING ITEMS!"); if (nm.item.mask & LVIF_IMAGE) pitem->iImage = nm.item.iImage; if ((nm.item.mask & LVIF_TEXT) && (nm.item.iSubItem == 0)) if (nm.item.pszText && (nm.item.pszText != LPSTR_TEXTCALLBACK)) { if (pitem->pszText == LPSTR_TEXTCALLBACK) pitem->pszText = NULL; Str_Set(&pitem->pszText, nm.item.pszText); } if (nm.item.mask & LVIF_STATE) pitem->state ^= ((pitem->state ^ nm.item.state) & nm.item.stateMask); } } return TRUE; } #ifdef UNICODE BOOL NEAR ListView_OnSetItemA(LV* plv, LV_ITEMA FAR* plvi) { LPWSTR pszW = NULL; LPSTR pszC = NULL; BOOL fRet; #ifdef WINNT_CAIRO if (ListView_IsOwnerData(plv)) { Assert(0); return FALSE; } #endif //HACK ALERT -- this code assumes that LV_ITEMA is exactly the same // as LV_ITEMW except for the pointer to the string. Assert(sizeof(LV_ITEMA) == sizeof(LV_ITEMW)); if (!plvi) return FALSE; if ((plvi->mask & LVIF_TEXT) && (plvi->pszText != NULL)) { pszC = plvi->pszText; pszW = ProduceWFromA(pszC); if (pszW == NULL) return FALSE; plvi->pszText = (LPSTR)pszW; } fRet = ListView_OnSetItem(plv, (const LV_ITEM FAR*) plvi); if (pszW != NULL) { plvi->pszText = pszC; FreeProducedString(pszW); } return fRet; } #endif BOOL NEAR ListView_OnSetItem(LV* plv, const LV_ITEM FAR* plvi) { LISTITEM FAR* pitem = NULL; UINT mask; UINT maskChanged; UINT rdwFlags=RDW_INVALIDATE; int i; UINT stateOld, stateNew; BOOL fHasItemData = TRUE; #ifdef WINNT_CAIRO if (ListView_IsOwnerData(plv)) { Assert(0); return FALSE; } #else fHasItemData = ((plv->style & LVS_NOITEMDATA) == 0); #endif if (!plvi) return FALSE; Assert(plvi->iSubItem >= 0); if (plv->himl && (plv->clrBk != ImageList_GetBkColor(plv->himl))) rdwFlags |= RDW_ERASE; mask = plvi->mask; if (!mask) return TRUE; // If we do not have item data, we only allow the user to change // state if (!fHasItemData && (mask != LVIF_STATE)) return(FALSE); // If we're setting a subitem, handle it elsewhere... // if (plvi->iSubItem > 0) return ListView_SetSubItem(plv, plvi); i = plvi->iItem; if (fHasItemData) { pitem = ListView_GetItemPtr(plv, i); if (!pitem) return FALSE; //REVIEW: This is a BOGUS HACK, and should be fixed. //This incorrectly calculates the old state (since we may // have to send LVN_GETDISPINFO to get it). // stateOld = stateNew = 0; if (mask & LVIF_STATE) { stateOld = pitem->state & plvi->stateMask; stateNew = plvi->state & plvi->stateMask; } } else { if (i >= ListView_Count(plv)) return FALSE; //REVIEW: Same hack as above // stateOld = stateNew = 0; if (mask & LVIF_STATE) { stateOld = ListView_NIDGetItemState(plv, i) & plvi->stateMask; stateNew = plvi->state & plvi->stateMask; } } // Prevent multiple selections in a single-select listview. if ((plv->style & LVS_SINGLESEL) && (mask & LVIF_STATE) && (stateNew & LVIS_SELECTED)) ListView_DeselectAll(plv, i); if (!ListView_SendChange(plv, i, 0, LVN_ITEMCHANGING, stateOld, stateNew, mask, 0, 0, pitem->lParam)) return FALSE; maskChanged = 0; if (mask & LVIF_STATE) { if (fHasItemData) { UINT change = (pitem->state ^ plvi->state) & plvi->stateMask; if (change) { pitem->state ^= change; maskChanged |= LVIF_STATE; // the selection state has changed.. update selected count if (change & LVIS_SELECTED) { if (pitem->state & LVIS_SELECTED) { plv->nSelected++; } else { if (plv->nSelected > 0) plv->nSelected--; } } // For some bits we can only invert the label area... // fSelectOnlyChange = ((change & ~(LVIS_SELECTED | LVIS_FOCUSED | LVIS_DROPHILITED)) == 0); // fEraseItem = ((change & ~(LVIS_SELECTED | LVIS_DROPHILITED)) != 0); // try to steal focus from the previous guy. if ((change & LVIS_FOCUSED) && (plv->iFocus != i)) { if ((plv->iFocus == -1) || ListView_OnSetItemState(plv, plv->iFocus, 0, LVIS_FOCUSED)) { pitem->state |= LVIS_FOCUSED; plv->iFocus = i; if (plv->iMark == -1) plv->iMark = i; } } if (change & LVIS_CUT) rdwFlags |= RDW_ERASE; } } else { WORD wState = ListView_NIDGetItemState(plv, i); WORD change = (wState ^ plvi->state) & plvi->stateMask; if (change) { wState ^= change; maskChanged |= LVIF_STATE; if (change & LVIS_SELECTED) { if (wState & LVIS_SELECTED) { plv->nSelected++; } else { if (plv->nSelected > 0) plv->nSelected--; } } // try to steal focus from the previous guy. if (change & LVIS_FOCUSED && plv->iFocus != -1 && plv->iFocus != i) if (ListView_OnSetItemState(plv, plv->iFocus, 0, LVIS_FOCUSED)) wState |= LVIS_FOCUSED; // // We need to update the data in the DPA ListView_NIDSetItemState(plv, i, wState); if (change & LVIS_CUT) rdwFlags |= RDW_ERASE; } } } if (mask & LVIF_TEXT) { RECT rc; // need to do this now because we're changing the text // so we need to get the rect of the thing before the text changes ListView_GetRects(plv, i, NULL, &rc, NULL, NULL); // don't redraw the item we are currently painting if (plv->iItemDrawing != i) InvalidateRect(plv->hwnd, &rc, TRUE); // invalidate with erase if (plvi->pszText == LPSTR_TEXTCALLBACK) { if (pitem->pszText != LPSTR_TEXTCALLBACK) Str_Set(&pitem->pszText, NULL); pitem->pszText = LPSTR_TEXTCALLBACK; } else { if (pitem->pszText == LPSTR_TEXTCALLBACK) pitem->pszText = NULL; if (!Str_Set(&pitem->pszText, plvi->pszText)) return FALSE; } plv->rcView.left = RECOMPUTE; pitem->cyMultiLabel = pitem->cxSingleLabel = pitem->cxMultiLabel = SRECOMPUTE; maskChanged |= LVIF_TEXT; } if (mask & LVIF_IMAGE) { if (pitem->iImage != plvi->iImage) { pitem->iImage = plvi->iImage; maskChanged |= LVIF_IMAGE; // erase if there was a set image if (pitem->iImage != I_IMAGECALLBACK) rdwFlags |= RDW_ERASE; } } if (mask & LVIF_PARAM) { if (pitem->lParam != plvi->lParam) { pitem->lParam = plvi->lParam; maskChanged |= LVIF_PARAM; } } if (maskChanged) { // don't redraw the item we are currently painting if (plv->iItemDrawing != i) ListView_InvalidateItem(plv, i, FALSE, rdwFlags); ListView_SendChange(plv, i, 0, LVN_ITEMCHANGED, stateOld, stateNew, maskChanged, 0, 0, pitem->lParam); } return TRUE; } UINT NEAR PASCAL ListView_OnGetItemState(LV* plv, int i, UINT mask) { LV_ITEM lvi; lvi.mask = LVIF_STATE; lvi.stateMask = mask; lvi.iItem = i; lvi.iSubItem = 0; if (!ListView_OnGetItem(plv, &lvi)) return 0; return lvi.state; } BOOL NEAR PASCAL ListView_OnSetItemState(LV* plv, int i, UINT data, UINT mask) { LV_ITEM lvi; lvi.mask = LVIF_STATE; lvi.state = data; lvi.stateMask = mask; lvi.iItem = i; lvi.iSubItem = 0; // HACK? // if the item is -1, we will do it for all items. We special case // a few cases here as to speed it up. For example if the mask is // LVIS_SELECTED and data is zero it implies that we will deselect // all items... // if (i != -1) { #ifdef WINNT_CAIRO if (ListView_IsOwnerData( plv )) { UINT rdwFlags = RDW_INVALIDATE; if (plv->himl && (plv->clrBk != ImageList_GetBkColor(plv->himl))) { rdwFlags |= RDW_ERASE; } // request selection state change if (mask & LVIS_SELECTED) { if (data & LVIS_SELECTED) { // set selection if ((plv->style & LVS_SINGLESEL)) { // Prevent multiple selections in a single-select listview. if (!SelRange_Clear( plv->hselrange )) { // BUGBUG: return low memory status //MemoryLowDlg( plv->hwnd ); return FALSE; } plv->nSelected = 0; } if (SELRANGE_ERROR == SelRange_IncludeRange( plv->hselrange, i, i )) { // BUGBUG: return low memory status //MemoryLowDlg( plv->hwnd ); return FALSE; } plv->nSelected++; } else { // clear selection if (SELRANGE_ERROR == SelRange_ExcludeRange( plv->hselrange, i, i )) { // BUGBUG: return low memory status //MemoryLowDlg( plv->hwnd ); return( FALSE ); } plv->nSelected--; } } // request focus state change if (mask & LVIS_FOCUSED) { if (data & LVIS_FOCUSED) { // set focus if ((i != plv->iFocus) && (-1 != plv->iFocus)) // -1 means no selection { plv->iFocus = i; } } else { // clear focus if (i == plv->iFocus) { plv->iFocus = -1; } } } #ifdef FOLDING_NAMES ListView_InvalidateFoldedItem( plv, i, TRUE, rdwFlags ); #else ListView_InvalidateItem(plv, i, TRUE, rdwFlags); #endif return( TRUE ); } #endif return ListView_OnSetItem(plv, &lvi); } else { UINT flags = LVNI_ALL; #ifdef WINNT_CAIRO if (ListView_IsOwnerData( plv )) { UINT rdwFlags = RDW_INVALIDATE; if (plv->himl && (plv->clrBk != ImageList_GetBkColor(plv->himl))) { rdwFlags |= RDW_ERASE; } // request selection state change for all if (mask & LVIS_SELECTED) { if (data & LVIS_SELECTED) { // set selection if ((plv->style & LVS_SINGLESEL)) { // cant make multiple selections in a single-select listview. return( FALSE ); } if (SELRANGE_ERROR == SelRange_IncludeRange( plv->hselrange, 0, plv->cTotalItems )) { // BUGBUG: return low memory status //MemoryLowDlg( plv->hwnd ); return( FALSE ); } plv->nSelected = plv->cTotalItems; } else { // clear selection if (!SelRange_Clear( plv->hselrange )) { // BUGBUG: return low memory status //MemoryLowDlg( plv->hwnd ); return( FALSE ); } plv->nSelected = 0; } } // request focus state change if (mask & LVIS_FOCUSED) { if (data & LVIS_FOCUSED) { // cant set focus to all return( FALSE ); } else { // clear focus plv->iFocus = -1; } } RedrawWindow( plv->hwnd, NULL, NULL, rdwFlags ); return( TRUE ); } #endif if (data == 0) { switch (mask) { case LVIS_SELECTED: flags = LVNI_SELECTED; break; case LVIS_CUT: flags = LVNI_CUT; break; } } else if ((plv->style & LVS_SINGLESEL) && (mask == LVIS_SELECTED)) return FALSE; /* can't select all in single-select listview */ // // Now iterate over all of the items that match our criteria and // set their new value. // while ((lvi.iItem = ListView_OnGetNextItem(plv, lvi.iItem, flags)) != -1) { ListView_OnSetItem(plv, &lvi); } return(TRUE); } } #ifdef UNICODE int NEAR PASCAL ListView_OnGetItemTextA(LV* plv, int i, LV_ITEMA FAR *plvi) { LPSTR psz = NULL; int cchW; // Hack Alert! -- This code assumes that all fields of LV_ITEMA and // LV_ITEMW coorispond 1 for 1 except the char pointers. Assert( sizeof(LV_ITEMA) == sizeof(LV_ITEMW) ); if (!plvi) return 0; if (plvi->pszText != NULL && plvi->cchTextMax != 0) { psz = plvi->pszText; plvi->pszText = LocalAlloc( LMEM_FIXED, plvi->cchTextMax * sizeof(WCHAR)); if (plvi->pszText == NULL ) { plvi->pszText = psz; return 0; } } if ((cchW = ListView_OnGetItemText(plv, i, (LV_ITEMW FAR *)plvi)) ) { cchW = ConvertWToAN( psz, plvi->cchTextMax, (LPWSTR)(plvi->pszText), cchW + 1 ); } if (plvi->pszText != NULL) LocalFree(plvi->pszText); plvi->pszText = psz; /* * ConvertWToAN return value includes the Z-term. Remvoe it from the * count before returning */ if (cchW > 0) --cchW; return cchW; } #endif int NEAR PASCAL ListView_OnGetItemText(LV* plv, int i, LV_ITEM FAR *plvi) { if (!plvi) return 0; Assert(plvi->pszText); plvi->mask = LVIF_TEXT; plvi->iItem = i; if (!ListView_OnGetItem(plv, plvi)) return 0; return lstrlen(plvi->pszText); } #ifdef UNICODE BOOL WINAPI ListView_OnSetItemTextA(LV* plv, int i, int iSubItem, LPCSTR pszText) { LPWSTR pszW = NULL; BOOL fRet; #ifdef WINNT_CAIRO if (ListView_IsOwnerData(plv)) { Assert(0); return FALSE; } #endif if (pszText != NULL) { pszW = ProduceWFromA(pszText); if (pszW == NULL) { return FALSE; } } fRet = ListView_OnSetItemText(plv, i, iSubItem, pszW); FreeProducedString(pszW); return fRet; } #endif BOOL WINAPI ListView_OnSetItemText(LV* plv, int i, int iSubItem, LPCTSTR pszText) { LV_ITEM lvi; #ifdef WINNT_CAIRO if (ListView_IsOwnerData(plv)) { Assert(0); return FALSE; } #endif lvi.mask = LVIF_TEXT; lvi.pszText = (LPTSTR)pszText; lvi.iItem = i; lvi.iSubItem = iSubItem; return ListView_OnSetItem(plv, &lvi); } // Add/remove/replace item BOOL NEAR ListView_FreeItem(LV* plv, LISTITEM FAR* pitem) { #ifdef WINNT_CAIRO Assert( !ListView_IsOwnerData(plv)); #endif if (pitem) { if (pitem->pszText != LPSTR_TEXTCALLBACK) Str_Set(&pitem->pszText, NULL); // NOTE: We never remove items from the image list; that's // the app's responsibility. // REVIEW: Should we do this? Or should we just provide // a message that will adjust image indices for the guy // when one is removed? // ControlFree(plv->hheap, pitem); } return FALSE; } LISTITEM FAR* NEAR ListView_CreateItem(LV* plv, const LV_ITEM FAR* plvi) { LISTITEM FAR* pitem = ControlAlloc(plv->hheap, sizeof(LISTITEM)); #ifdef WINNT_CAIRO Assert(!ListView_IsOwnerData(plv)); #endif if (pitem) { if (plvi->mask & LVIF_STATE) { if (plvi->state & ~LVIS_ALL) { DebugMsg(DM_ERROR, TEXT("ListView: Invalid state: %04x"), plvi->state); return NULL; } // If adding a selected item to a single-select listview, deselect // any other items. if ((plv->style & LVS_SINGLESEL) && (plvi->state & LVIS_SELECTED)) ListView_DeselectAll(plv, -1); pitem->state = (plvi->state & ~(LVIS_FOCUSED | LVIS_SELECTED)); } if (plvi->mask & LVIF_PARAM) pitem->lParam = plvi->lParam; if (plvi->mask & LVIF_IMAGE) pitem->iImage = plvi->iImage; plv->rcView.left = pitem->pt.x = pitem->pt.y = RECOMPUTE; pitem->cxSingleLabel = pitem->cxMultiLabel = pitem->cyMultiLabel = SRECOMPUTE; pitem->pszText = NULL; if (plvi->mask & LVIF_TEXT) { if (plvi->pszText == LPSTR_TEXTCALLBACK) { pitem->pszText = LPSTR_TEXTCALLBACK; } else if (!Str_Set(&pitem->pszText, plvi->pszText)) { ListView_FreeItem(plv, pitem); return NULL; } } } return pitem; } void ListView_LRInvalidateBelow(LV* plv, int i) { if (ListView_IsListView(plv) || ListView_IsReportView(plv)) { RECT rcItem; if (i >= 0) { ListView_GetRects(plv, i, NULL, NULL, &rcItem, NULL); } else { rcItem.left = rcItem.top = 0; rcItem.right = plv->sizeClient.cx; rcItem.bottom = plv->sizeClient.cy; } // For both List and report view need to erase the item and // below. Note: do simple test to see if there is anything // to redraw // we can't check for bottom/right > 0 because if we nuked something // above or to the left of the view, it may affect us all if ((rcItem.top <= plv->sizeClient.cy) && (rcItem.left <= plv->sizeClient.cx)) { rcItem.bottom = plv->sizeClient.cy; RedrawWindow(plv->hwnd, &rcItem, NULL, RDW_INVALIDATE | RDW_ERASE); if (ListView_IsListView(plv)) { RECT rcClient; // For Listview we need to erase the other columns... rcClient.left = rcItem.right; rcClient.top = 0; rcClient.bottom = plv->sizeClient.cy; rcClient.right = plv->sizeClient.cx; RedrawWindow(plv->hwnd, &rcClient, NULL, RDW_INVALIDATE | RDW_ERASE); } } } } void NEAR ListView_OnUpdate(LV* plv, int i) { // If in icon/small view, don't call InvalidateItem, since that'll force // FindFreeSlot to get called, which is pig-like. Instead, just // force a WM_PAINT message, which we'll catch and call Recompute with. // if (ListView_IsIconView(plv) || ListView_IsSmallView(plv)) { if (plv->style & LVS_AUTOARRANGE) ListView_OnArrange(plv, LVA_DEFAULT); else RedrawWindow(plv->hwnd, NULL, NULL, RDW_INTERNALPAINT | RDW_NOCHILDREN); } else { ListView_LRInvalidateBelow(plv, i); } ListView_UpdateScrollBars(plv); } #ifdef UNICODE int NEAR ListView_OnInsertItemA(LV* plv, LV_ITEMA FAR* plvi) { LPWSTR pszW = NULL; LPSTR pszC = NULL; int iRet; //HACK ALERT -- this code assumes that LV_ITEMA is exactly the same // as LV_ITEMW except for the pointer to the string. Assert(sizeof(LV_ITEMA) == sizeof(LV_ITEMW)); if (!plvi) { return -1; } if ((plvi->mask & LVIF_TEXT) && (plvi->pszText != NULL)) { pszC = plvi->pszText; pszW = ProduceWFromA(pszC); if (pszW == NULL) return -1; plvi->pszText = (LPSTR)pszW; } iRet = ListView_OnInsertItem(plv, (const LV_ITEM FAR*) plvi); if (pszW != NULL) { plvi->pszText = pszC; FreeProducedString(pszW); } return iRet; } #endif int NEAR ListView_OnInsertItem(LV* plv, const LV_ITEM FAR* plvi) { int iItem; if (!plvi || (plvi->iSubItem != 0)) // can only insert the 0th item { DebugMsg(DM_ERROR, TEXT("ListView_InsertItem: iSubItem must be 0")); return -1; } // If sorted, then insert sorted. // if (plv->style & (LVS_SORTASCENDING | LVS_SORTDESCENDING) #ifdef WINNT_CAIRO && !ListView_IsOwnerData( plv ) #endif ) { if (plvi->pszText == LPSTR_TEXTCALLBACK) { DebugMsg(DM_ERROR, TEXT("Don't use LPSTR_TEXTCALLBACK with LVS_SORTASCENDING or LVS_SORTDESCENDING")); return -1; } iItem = ListView_LookupString(plv, plvi->pszText, LVFI_SUBSTRING | LVFI_NEARESTXY, 0); } else iItem = plvi->iItem; #ifdef WINNT_CAIRO if ( !ListView_IsOwnerData(plv)) #else if ((plv->style & LVS_NOITEMDATA) == 0) #endif { int iZ; LISTITEM FAR *pitem = ListView_CreateItem(plv, plvi); UINT uSelMask = plvi->mask & LVIF_STATE ? (plvi->state & (LVIS_FOCUSED | LVIS_SELECTED)) : 0; if (!pitem) return -1; iItem = DPA_InsertPtr(plv->hdpa, iItem, pitem); if (iItem == -1) { ListView_FreeItem(plv, pitem); return -1; } if (plv->hdpaSubItems) { int iCol; // slide all the colum DPAs down to match the location of the // inserted item // for (iCol = plv->cCol - 1; iCol >= 0; iCol--) { HDPA hdpa = ListView_GetSubItemDPA(plv, iCol); if (hdpa) // this is optional, call backs don't have them { // insert a blank item (REVIEW: should this be callback?) if (DPA_InsertPtr(hdpa, iItem, NULL) != iItem) goto Failure; Assert(ListView_Count(plv) == DPA_GetPtrCount(hdpa)); } } } // Add item to end of z order // iZ = DPA_InsertPtr(plv->hdpaZOrder, ListView_Count(plv), (LPVOID)iItem); if (iZ == -1) { Failure: DebugMsg(DM_TRACE, TEXT("ListView_OnInsertItem() failed")); DPA_DeletePtr(plv->hdpa, iItem); ListView_FreeItem(plv, pitem); return -1; } // If the item was not added at the end of the list we need // to update the other indexes in the list if (iItem != ListView_Count(plv) - 1) { int i2; for (i2 = iZ - 1; i2 >= 0; i2--) { int iItemZ = (int)(DWORD)DPA_FastGetPtr(plv->hdpaZOrder, i2); if (iItemZ >= iItem) DPA_SetPtr(plv->hdpaZOrder, i2, (LPVOID)(DWORD)(iItemZ + 1)); } } if (uSelMask) { // we masked off these in the createitem above. // because turning these on means more than setting the bits. ListView_OnSetItemState(plv, iItem, uSelMask, uSelMask); } } else { #ifdef WINNT_CAIRO // // simply adjust selection and count // if ((iItem >= 0) && (iItem <= MAX_LISTVIEWITEMS)) { if (!SelRange_InsertItem( plv->hselrange, iItem )) { return( -1 ); } plv->cTotalItems++; plv->rcView.left = RECOMPUTE; ListView_Recompute(plv); InvalidateRect( plv->hwnd, NULL, FALSE ); } #else // The item has no data associated with it. Simply insert RECOMPUTE and // state = 0 for the item. // For now we wont insert in the zorder one as we wont support those // views that need it... iItem = DPA_InsertPtr(plv->hdpa, iItem, (void *)MAKELONG(0, SRECOMPUTE)); if (iItem == -1) return -1; #endif } #ifdef WINNT_CAIRO if (!ListView_IsOwnerData(plv)) #endif Assert(ListView_Count(plv) == DPA_GetPtrCount(plv->hdpaZOrder)); if (ListView_RedrawEnabled(plv)) { // The Maybe resize colmns may resize things in which case the next call // to Update is not needed. if (!ListView_MaybeResizeListColumns(plv, iItem, iItem)) ListView_OnUpdate(plv, iItem); } else { // // Special case code to make using SetRedraw work reasonably well // for adding items to a listview which is in a non layout mode... // if ((plv->iFirstChangedNoRedraw == -1) || (iItem < plv->iFirstChangedNoRedraw)) plv->iFirstChangedNoRedraw = iItem; } ListView_Notify(plv, iItem, 0, LVN_INSERTITEM); return iItem; } BOOL NEAR ListView_OnDeleteItem(LV* plv, int iItem) { int iCount = ListView_Count(plv); if ((iItem < 0) || (iItem >= iCount)) return FALSE; // out of range ListView_DismissEdit(plv, FALSE); ListView_OnSetItemState(plv, iItem, 0, LVIS_SELECTED); #ifdef WINNT_CAIRO if (!ListView_IsOwnerData(plv)) #else if ((plv->style & LVS_NOITEMDATA) == 0) #endif { LISTITEM FAR* pitem; int iZ; if ((plv->rcView.left != RECOMPUTE) && (ListView_IsIconView(plv) || ListView_IsSmallView(plv))) { RECT rc; ListView_GetRects(plv, iItem, NULL, NULL, &rc, NULL); if (plv->rcView.left == rc.left || plv->rcView.right == rc.right || plv->rcView.top == rc.top || plv->rcView.bottom == rc.bottom) { plv->rcView.left = RECOMPUTE; } } ListView_InvalidateItem(plv, iItem, FALSE, RDW_INVALIDATE | RDW_ERASE); // this notify must be done AFTER the Invalidate because some items need callbacks // to calculate the rect, but the notify might free it out ListView_Notify(plv, iItem, 0, LVN_DELETEITEM); pitem = DPA_DeletePtr(plv->hdpa, iItem); // if (!pitem) // we validate iItem is in range // return FALSE; // so this is not necessary // remove from the z-order, this is a linear search to find this! DPA_DeletePtr(plv->hdpaZOrder, ListView_ZOrderIndex(plv, iItem)); // // As the Z-order hdpa is a set of indexes we also need to decrement // all indexes that exceed the one we are deleting. // for (iZ = ListView_Count(plv) - 1; iZ >= 0; iZ--) { int iItemZ = (int)(DWORD)DPA_FastGetPtr(plv->hdpaZOrder, iZ); if (iItemZ > iItem) DPA_SetPtr(plv->hdpaZOrder, iZ, (LPVOID)(DWORD)(iItemZ - 1)); } // remove from sub item DPAs if necessary if (plv->hdpaSubItems) { int iCol; for (iCol = plv->cCol - 1; iCol >= 0; iCol--) { HDPA hdpa = ListView_GetSubItemDPA(plv, iCol); if (hdpa) { // this is optional, call backs don't have them LPTSTR psz = DPA_DeletePtr(hdpa, iItem); if (psz != LPSTR_TEXTCALLBACK) Str_Set(&psz, NULL); Assert(ListView_Count(plv) == DPA_GetPtrCount(hdpa)); } } } ListView_FreeItem(plv, pitem); // ... finaly the item pointer } else { #ifdef WINNT_CAIRO // // simply notify and then fixup selection state and count // if ((iItem >= 0) && (iItem <= MAX_LISTVIEWITEMS)) { BOOL fSelected; ListView_Notify(plv, iItem, 0, LVN_DELETEITEM); if (!SelRange_RemoveItem( plv->hselrange, iItem, &fSelected )) { // BUGBUG: return out of memory status //MemoryLowDlg( plv->hwnd ); return FALSE; } if (fSelected) { plv->nSelected--; } plv->cTotalItems--; plv->rcView.left = RECOMPUTE; ListView_Recompute(plv); InvalidateRect( plv->hwnd, NULL, FALSE ); } else { return FALSE; } #else // For items that have no data we simply notify and remove the item // iItem was already validated so this should not fail // ListView_Notify(plv, iItem, 0, LVN_DELETEITEM); DPA_DeletePtr(plv->hdpa, iItem); #endif } iCount = ListView_Count(plv); // regrab count incase someone updated item... #ifdef WINNT_CAIRO if (!ListView_IsOwnerData(plv)) #endif Assert(ListView_Count(plv) == DPA_GetPtrCount(plv->hdpaZOrder)); if (plv->iFocus == iItem) { // deleted the focus item if (plv->iFocus >= iCount) // did we nuke the last item? plv->iFocus = iCount - 1; } if (plv->iFocus > iItem) { plv->iFocus--; // slide the focus index down } // same with the mark if (plv->iMark == iItem) { // deleted the mark item if (plv->iMark >= iCount) // did we nuke the last item? plv->iMark = iCount - 1; } else if (plv->iMark > iItem) plv->iMark--; // slide the mark index down if (ListView_RedrawEnabled(plv)) ListView_OnUpdate(plv, iItem); else { ListView_LRInvalidateBelow(plv, iItem); // // Special case code to make using SetRedraw work reasonably well // for adding items to a listview which is in a non layout mode... // if ((plv->iFirstChangedNoRedraw != -1) && (iItem < plv->iFirstChangedNoRedraw)) plv->iFirstChangedNoRedraw--; } return TRUE; } BOOL NEAR ListView_OnDeleteAllItems(LV* plv) { int i; BOOL bAlreadyNotified; BOOL fHasItemData; #ifdef WINNT_CAIRO fHasItemData = !ListView_IsOwnerData(plv); #else fHasItemData = ((plv->style & LVS_NOITEMDATA) == 0); #endif ListView_DismissEdit(plv, FALSE); // cancel edits bAlreadyNotified = (BOOL)ListView_Notify(plv, -1, 0, LVN_DELETEALLITEMS); if (fHasItemData || !bAlreadyNotified) { for (i = ListView_Count(plv) - 1; i >= 0; i--) { if (!bAlreadyNotified) ListView_Notify(plv, i, 0, LVN_DELETEITEM); if (fHasItemData) ListView_FreeItem(plv, ListView_FastGetItemPtr(plv, i)); } } #ifdef WINNT_CAIRO if (ListView_IsOwnerData( plv )) { if (!SelRange_Clear( plv->hselrange )) { // BUGBUG: return low memory status //MemoryLowDlg( plv->hwnd ); } plv->cTotalItems = 0; } else #endif { DPA_DeleteAllPtrs(plv->hdpa); DPA_DeleteAllPtrs(plv->hdpaZOrder); if (plv->hdpaSubItems) { int iCol; for (iCol = plv->cCol - 1; iCol >= 0; iCol--) { HDPA hdpa = ListView_GetSubItemDPA(plv, iCol); if (hdpa) { ListView_FreeColumnData(hdpa); DPA_DeleteAllPtrs(hdpa); } } } } plv->rcView.left = RECOMPUTE; plv->ptOrigin.x = plv->ptOrigin.y = 0; plv->xOrigin = 0; plv->iMark = plv->iFocus = -1; plv->nSelected = 0; plv->ptlRptOrigin.x = 0; plv->ptlRptOrigin.y = 0; // reset the cxItem width if (!(plv->flags & LVF_COLSIZESET)) plv->cxItem = 16 * plv->cxLabelChar + plv->cxSmIcon; RedrawWindow(plv->hwnd, NULL, NULL, RDW_INVALIDATE | RDW_ERASE); ListView_UpdateScrollBars(plv); return TRUE; } int PASCAL ListView_IFindNearestItem(LV* plv, int left, int top, UINT vk) { int iMin = -1; #ifdef WINNT_CAIRO if (ListView_IsOwnerData( plv )) { POINT pt; int cSlots; Assert( !ListView_IsReportView( plv ) && !ListView_IsListView( plv ) ); pt.x = left + plv->ptOrigin.x; pt.y = top + plv->ptOrigin.y; cSlots = ListView_GetSlotCount( plv, TRUE ); iMin = ListView_CalcHitSlot( plv, pt, cSlots ); switch( vk ) { case VK_HOME: iMin = 0; break; case VK_END: iMin = ListView_Count( plv ) - 1; break; case VK_LEFT: if (iMin % cSlots) iMin -= 1; break; case VK_RIGHT: if ((iMin + 1) % cSlots) iMin += 1; break; case VK_UP: if (iMin >= cSlots) iMin -= cSlots; break; case VK_DOWN: if (iMin + cSlots < ListView_Count( plv )) iMin += cSlots; break; default: ; } iMin = max( 0, iMin ); iMin = min( ListView_Count( plv ) - 1, iMin ); } else #endif { DWORD dMin = 0; int cyItem; int yEnd = 0, yLimit = 0, xEnd = 0; int iCount; int i; if (ListView_IsIconView(plv)) { cyItem = plv->cyIcon; } else { cyItem = plv->cyItem; } iCount = ListView_Count(plv); if (iCount == 1) return 0; if (vk == VK_HOME) { yEnd = yLimit = plv->rcView.bottom; xEnd = plv->rcView.right; } else if (vk == VK_END) { yEnd = yLimit = plv->rcView.top; xEnd = plv->rcView.left; } for (i = 0; i < iCount; i++) { RECT rc; int dx; DWORD dxAbs, dyAbs; int dy; DWORD dOffset; ListView_GetRects(plv, i, &rc, NULL, NULL, NULL); dx = rc.left - left; dxAbs = (DWORD)(dx < 0 ? -dx : dx); dy = rc.top - top; dyAbs = (DWORD)(dy < 0 ? -dy : dy); if ((vk == VK_LEFT) && (dxAbs < dyAbs || dx >= 0)) continue; else if ((vk == VK_RIGHT) && (dxAbs < dyAbs || dx <= 0)) continue; else if ((vk == VK_UP) && (dxAbs > dyAbs || dy >= 0)) continue; else if ((vk == VK_DOWN) && (dxAbs > dyAbs || dy <= 0)) continue; if (vk == VK_HOME || vk == VK_END) { // home is not the nearest to the top corner, it's the leftmost of the top row. // ditto (reversed) for end. thus we can't use the stuff below. bummer if (vk == VK_HOME) { if ((rc.top + cyItem < yEnd) || // if it's fully above the highest line so far, take it! ((rc.top < yLimit) && // if it's on the same row as the top item to date (rc.left < xEnd))) { iMin = i; xEnd = rc.left; yEnd = rc.top; if (rc.top + cyItem < yLimit) yLimit = rc.top + cyItem; } } else { if ((rc.top > yEnd) || //if it's full below the lowest row ((rc.top + cyItem > yLimit) && // if it's on the same row (rc.right > xEnd))) { iMin = i; xEnd = rc.right; yEnd = rc.top; if (rc.top > yLimit) yLimit = rc.top; } } } else { dOffset = ((dxAbs * dxAbs) + (dyAbs * dyAbs)); if (iMin == -1 || (dMin > dOffset)) { dMin = dOffset; iMin = i; } } } } return iMin; } int NEAR ListView_Arrow(LV* plv, int iStart, UINT vk) { RECT rcFocus; int i; int dx; int iCount; // // The algorithm to find which item depends if we are in a view // that is arrange(layout) oriented or a sorted (list) view. // For the sorted views we will use some optimizations to make // it faster // iCount = ListView_Count(plv); if (ListView_IsReportView(plv) || ListView_IsListView(plv)) { // // For up and down arrows, simply increment or decrement the // index. Note: in listview this will cause it to wrap columns // which is fine as it is compatible with the file manager // // Assumes only one of these flags is set... switch (vk) { case VK_LEFT: if (ListView_IsReportView(plv)) { ListView_ROnScroll(plv, SB_LINELEFT, 0, SB_HORZ); } else iStart -= plv->cItemCol; break; case VK_RIGHT: if (ListView_IsReportView(plv)) { // Make this horizontally scroll the report view ListView_ROnScroll(plv, SB_LINERIGHT, 0, SB_HORZ); } else iStart += plv->cItemCol; break; case VK_UP: iStart--; break; case VK_DOWN: iStart++; break; case VK_HOME: iStart = 0; break; case VK_END: iStart = iCount -1; break; case VK_NEXT: if (ListView_IsReportView(plv)) { i = iStart; // save away to make sure we dont go wrong way! // First go to end of page... iStart = (int)(((LONG)(plv->sizeClient.cy - (plv->cyItem - 1) - plv->yTop) + plv->ptlRptOrigin.y) / plv->cyItem); // If Same item, increment by page size. if (iStart <= i) iStart = i + max( (plv->sizeClient.cy - plv->yTop)/ plv->cyItem - 1, 1); if (iStart >= iCount) iStart = iCount - 1; } else { // multiply by 2/3 to give a good feel.. when the item is mostly shown // you want to go to the next column dx = (plv->sizeClient.cx + (plv->cxItem*2)/3) / plv->cxItem; if (!dx) dx = 1; iStart += plv->cItemCol * dx; if (plv->cItemCol) { while (iStart >= iCount) iStart -= plv->cItemCol; } } break; case VK_PRIOR: if (ListView_IsReportView(plv)) { i = iStart; // save away to make sure we dont go wrong way! // First go to end of page... iStart = (int)(plv->ptlRptOrigin.y / plv->cyItem); // If Same item, increment by page size. if (iStart >= i) iStart = i - max( (plv->sizeClient.cy - plv->yTop)/ plv->cyItem - 1, 1); if (iStart < 0) iStart = 0; } else { dx = (plv->sizeClient.cx + (plv->cxItem*2)/3) / plv->cxItem; if (!dx) dx = 1; iStart -= plv->cItemCol * dx; if (plv->cItemCol) { while (iStart < 0) iStart += plv->cItemCol; } } break; default: return -1; // Out of range } // Make sure it is in range!. if ((iStart >= 0) && (iStart < iCount)) return iStart; else if (iCount == 1) return 0; else return -1; } else { // // Layout type view. we need to use the position of the items // to figure out the next item // #ifdef WINNT_CAIRO if (ListView_IsOwnerData( plv )) { iStart = max( 0, iStart ); // if it does not matches any of the entries in the case statement below // this is done to skip the call back by the GetRects // if ( vk != VK_LEFT && vk != VK_RIGHT && vk != VK_UP && vk != VK_DOWN && vk != VK_HOME && vk != VK_END && vk != VK_NEXT && vk != VK_PRIOR ) { return -1; } ListView_GetRects(plv, iStart, &rcFocus, NULL, NULL, NULL); } else #endif { if (iStart != -1) { ListView_GetRects(plv, iStart, &rcFocus, NULL, NULL, NULL); } } switch (vk) { // For standard arrow keys just fall out of here. case VK_LEFT: case VK_RIGHT: case VK_UP: case VK_DOWN: #ifdef WINNT_CAIRO if (ListView_IsOwnerData( plv )) { break; } else #endif { if (iStart != -1) { // all keys map to VK_HOME except VK_END break; } // Fall through vk = VK_HOME; } case VK_HOME: rcFocus.left = - plv->ptOrigin.x; rcFocus.top = - plv->ptOrigin.y; break; case VK_END: rcFocus.left = plv->rcView.right; rcFocus.top = plv->rcView.bottom; break; case VK_NEXT: rcFocus.top += plv->sizeClient.cy; vk = VK_UP; break; case VK_PRIOR: vk = VK_DOWN; rcFocus.top -= plv->sizeClient.cy; break; default: return -1; // Out of range } return ListView_IFindNearestItem(plv, rcFocus.left, rcFocus.top, vk); } } int NEAR ListView_OnGetNextItem(LV* plv, int i, UINT flags) { int cItemMax = ListView_Count(plv); if (i < -1 || i >= cItemMax) return -1; #ifdef WINNT_CAIRO if (ListView_IsOwnerData( plv )) { if (flags & (LVNI_CUT | LVNI_DROPHILITED | LVNI_PREVIOUS)) { return( -1 ); } } #endif while (TRUE) { // BUGBUG: does anyone call this now??? if (flags & (LVNI_ABOVE | LVNI_BELOW | LVNI_TORIGHT | LVNI_TOLEFT)) { UINT vk; if (flags & LVNI_ABOVE) vk = VK_UP; else if (flags & LVNI_BELOW) vk = VK_DOWN; else if (flags & LVNI_TORIGHT) vk = VK_RIGHT; else vk = VK_LEFT; if (i != -1) i = ListView_Arrow(plv, i, vk); if (i == -1) return i; } else { i++; if (i == cItemMax) return -1; } // See if any other restrictions are set if (flags & ~(LVNI_ABOVE | LVNI_BELOW | LVNI_TORIGHT | LVNI_TOLEFT)) { WORD wItemState; #ifdef WINNT_CAIRO if (ListView_IsOwnerData( plv )) { if (flags & LVNI_FOCUSED) { i = plv->iFocus; if ((i != -1) && (flags & LVNI_SELECTED)) { if (!SelRange_IsSelected( plv->hselrange, i )) { i = -1; } } } else if (flags & LVNI_SELECTED) { i = max( i, 0 ); i = SelRange_NextSelected( plv->hselrange, i ); } else { i = -1; } } else #endif { #ifndef WINNT_CAIRO if ((plv->style & LVS_NOITEMDATA) == 0) #endif { LISTITEM FAR* pitem = ListView_FastGetItemPtr(plv, i); wItemState = pitem->state; } #ifndef WINNT_CAIRO else wItemState = ListView_NIDGetItemState(plv, i); #endif if ((flags & LVNI_FOCUSED) && !(wItemState & LVIS_FOCUSED)) continue; if ((flags & LVNI_SELECTED) && !(wItemState & LVIS_SELECTED)) continue; if ((flags & LVNI_CUT) && !(wItemState & LVIS_CUT)) continue; if ((flags & LVNI_DROPHILITED) && !(wItemState & LVIS_DROPHILITED)) continue; } } return i; } } int NEAR ListView_CompareString(LV* plv, int i, LPCTSTR pszFind, UINT flags, int iLen) { // BUGBUG: non protected globals int cb; TCHAR ach[CCHLABELMAX]; LV_ITEM item; #ifdef WINNT_CAIRO Assert(!ListView_IsOwnerData(plv)); #endif Assert(pszFind); item.iItem = i; item.iSubItem = 0; item.mask = LVIF_TEXT; item.pszText = ach; item.cchTextMax = ARRAYSIZE(ach); ListView_OnGetItem(plv, &item); if (!(flags & (LVFI_PARTIAL | LVFI_SUBSTRING))) return lstrcmpi(item.pszText, pszFind); // REVIEW: LVFI_SUBSTRING is not really implemented yet. cb = lstrlen(pszFind); if (iLen && (cb > iLen)) { cb = iLen; } // // If the sub strings not equal then return the ordering based // on the entire string. // return IntlStrEqNI(item.pszText, pszFind, cb) ? 0 : lstrcmp(item.pszText, pszFind); } #ifdef UNICODE int NEAR ListView_OnFindItemA(LV* plv, int iStart, LV_FINDINFOA * plvfi) { LPWSTR pszW = NULL; LPCSTR pszC = NULL; int iRet; //HACK ALERT -- this code assumes that LV_FINDINFOA is exactly the same // as LV_FINDINFOW except for the pointer to the string. Assert(sizeof(LV_FINDINFOA) == sizeof(LV_FINDINFOW)); if (!plvfi) return -1; if (!(plvfi->flags & LVFI_PARAM) && !(plvfi->flags & LVFI_NEARESTXY)) { pszC = plvfi->psz; if ((pszW = ProduceWFromA(pszC)) == NULL) return -1; plvfi->psz = (LPSTR)pszW; } iRet = ListView_OnFindItem(plv, iStart, (const LV_FINDINFO FAR *)plvfi); if (pszW != NULL) { plvfi->psz = pszC; FreeProducedString(pszW); } return iRet; } #endif int NEAR ListView_OnFindItem(LV* plv, int iStart, const LV_FINDINFO FAR* plvfi) { int i; int j; int cItem; UINT flags; if (!plvfi) return -1; if (plvfi->flags & LVFI_NEARESTXY) { if (ListView_IsIconView(plv) || ListView_IsSmallView(plv)) { return ListView_IFindNearestItem(plv, plvfi->pt.x, plvfi->pt.y, plvfi->vkDirection); } else return -1; } if (iStart < -1 || iStart >= ListView_Count(plv)) return -1; #ifdef WINNT_CAIRO if (ListView_IsOwnerData( plv )) { // call back to owner for search return( ListView_RequestFindItem( plv, plvfi, iStart + 1 ) ); } else #endif { flags = plvfi->flags; i = iStart; cItem = ListView_Count(plv); if (flags & LVFI_PARAM) { LPARAM lParam = plvfi->lParam; // Linear search with wraparound... // for (j = cItem; j-- != 0; ) { ++i; if (i == cItem) { if (flags & LVFI_WRAP) i = 0; else break; } if (ListView_FastGetItemPtr(plv, i)->lParam == lParam) return i; } } else // if (flags & (LVFI_STRING | LVFI_SUBSTRING | LVFI_PARTIAL)) { LPCTSTR pszFind = plvfi->psz; if (!pszFind) return -1; if (plv->style & (LVS_SORTASCENDING | LVS_SORTDESCENDING)) return ListView_LookupString(plv, pszFind, flags, i + 1); for (j = cItem; j-- != 0; ) { ++i; if (i == cItem) { if (flags & LVFI_WRAP) i = 0; else break; } if (ListView_CompareString(plv, i, pszFind, (flags & (LVFI_PARTIAL | LVFI_SUBSTRING)), 0) == 0) { return i; } } } } return -1; } BOOL NEAR ListView_OnGetItemRect(LV* plv, int i, RECT FAR* prc) { LPRECT pRects[LVIR_MAX]; // validate parameters if (i < 0 || i >= ListView_Count(plv) || !prc || prc->left >= LVIR_MAX || prc->left < 0) { DebugMsg(DM_ERROR, TEXT("ListView: invalid index or rect pointer")); return FALSE; } pRects[0] = NULL; pRects[1] = NULL; pRects[2] = NULL; pRects[3] = NULL; pRects[prc->left] = prc; ListView_GetRects(plv, i, pRects[LVIR_ICON], pRects[LVIR_LABEL], pRects[LVIR_BOUNDS], pRects[LVIR_SELECTBOUNDS]); return TRUE; } // // in: // plv // iItem MUST be a valid item index (in range) // out: // prcIcon icon bounding rect // prcLabel label text bounding rect, for details this is the first column // prcBounds entire item (all text and icon), including columns in details // prcSelectionBounds union of icon and label rects, does NOT include columns // in details view void NEAR ListView_GetRects(LV* plv, int iItem, RECT FAR* prcIcon, RECT FAR* prcLabel, RECT FAR* prcBounds, RECT FAR* prcSelectBounds) { Assert(plv); if (ListView_IsReportView(plv)) { ListView_RGetRects(plv, iItem, prcIcon, prcLabel, prcBounds, prcSelectBounds); } else if (ListView_IsListView(plv)) { ListView_LGetRects(plv, iItem, prcIcon, prcLabel, prcBounds, prcSelectBounds); } else { #ifdef WINNT_CAIRO if (ListView_IsOwnerData( plv )) { RECT rcIcon; RECT rcTextBounds; LISTITEM item; if (ListView_IsIconView(plv)) ListView_IGetRectsOwnerData(plv, iItem, &rcIcon, &rcTextBounds, &item, FALSE); else if (ListView_IsSmallView(plv)) ListView_SGetRectsOwnerData(plv, iItem, &rcIcon, &rcTextBounds, &item, FALSE); if (prcIcon) *prcIcon = rcIcon; if (prcLabel) *prcLabel = rcTextBounds; if (prcBounds) UnionRect(prcBounds, &rcIcon, &rcTextBounds); if (prcSelectBounds) UnionRect(prcSelectBounds, &rcIcon, &rcTextBounds); } else #endif { LISTITEM FAR *pitem = ListView_GetItemPtr(plv, iItem); _ListView_GetRectsFromItem(plv, ListView_IsSmallView(plv), pitem, prcIcon, prcLabel, prcBounds, prcSelectBounds); } } } #ifdef WINNT_CAIRO void NEAR ListView_GetRectsOwnerData(LV* plv, int iItem, RECT FAR* prcIcon, RECT FAR* prcLabel, RECT FAR* prcBounds, RECT FAR* prcSelectBounds, LISTITEM* pitem) { Assert(plv); if (ListView_IsReportView(plv)) { ListView_RGetRects(plv, iItem, prcIcon, prcLabel, prcBounds, prcSelectBounds); } else if (ListView_IsListView(plv)) { ListView_LGetRects(plv, iItem, prcIcon, prcLabel, prcBounds, prcSelectBounds); } else { RECT rcIcon; RECT rcTextBounds; if (ListView_IsIconView(plv)) ListView_IGetRectsOwnerData(plv, iItem, &rcIcon, &rcTextBounds, pitem, TRUE); else if (ListView_IsSmallView(plv)) ListView_SGetRectsOwnerData(plv, iItem, &rcIcon, &rcTextBounds, pitem, TRUE); /* Don't need to check for folding here, as will have been handled in user data / rectangle fetching functions. */ if (prcIcon) *prcIcon = rcIcon; if (prcLabel) *prcLabel = rcTextBounds; if (prcBounds) UnionRect(prcBounds, &rcIcon, &rcTextBounds); if (prcSelectBounds) UnionRect(prcSelectBounds, &rcIcon, &rcTextBounds); } } #endif BOOL NEAR ListView_OnRedrawItems(LV* plv, int iFirst, int iLast) { int iCount = ListView_Count(plv); if (iFirst < iCount) { if (iLast >= iCount) iLast = iCount - 1; while (iFirst <= iLast) ListView_InvalidateItem(plv, iFirst++, FALSE, RDW_INVALIDATE | RDW_ERASE); } return TRUE; } // fSelectionOnly use the selection bounds only, ie. don't include // columns in invalidation if in details view // void NEAR ListView_InvalidateItem(LV* plv, int iItem, BOOL fSelectionOnly, UINT fRedraw) { RECT rcBounds, rcSelectBounds; LPRECT prcToUse = fSelectionOnly ? &rcSelectBounds : &rcBounds; if (ListView_RedrawEnabled(plv)) { ListView_GetRects(plv, iItem, NULL, NULL, &rcBounds, &rcSelectBounds); if( RECTS_IN_SIZE( plv->sizeClient, *prcToUse ) ) RedrawWindow(plv->hwnd, prcToUse, NULL, fRedraw); } else { // if we're not visible, we'll get a full // erase bk when we do become visible, so only do this stuff when // we're on setredraw false if (!(plv->flags & LVF_REDRAW)) { // if we're invalidating that's new (thus hasn't been painted yet) // blow it off if (plv->iFirstChangedNoRedraw != -1 && iItem >= plv->iFirstChangedNoRedraw) return; ListView_GetRects(plv, iItem, NULL, NULL, &rcBounds, &rcSelectBounds); // if it had the erase bit, add it to our region if (RECTS_IN_SIZE(plv->sizeClient, *prcToUse)) { HRGN hrgn = CreateRectRgnIndirect(prcToUse); ListView_InvalidateRegion(plv, hrgn); if (fRedraw & RDW_ERASE) plv->flags |= LVF_ERASE; } } } } BOOL NEAR ListView_OnSetItemPosition(LV* plv, int i, int x, int y) { LISTITEM FAR* pitem; if (ListView_IsListView(plv)) return FALSE; #ifdef WINNT_CAIRO if (ListView_IsOwnerData( plv )) { Assert(0); return FALSE; } #else if (plv->style & LVS_NOITEMDATA) { Assert(FALSE); return(FALSE); } #endif pitem = ListView_GetItemPtr(plv, i); if (!pitem) return FALSE; // erase old // Don't invalidate if it hasn't got a position yet if (pitem->pt.y != RECOMPUTE) ListView_InvalidateItem(plv, i, FALSE, RDW_INVALIDATE | RDW_ERASE); pitem->pt.x = x; pitem->pt.y = y; plv->rcView.left = RECOMPUTE; // and draw at new position ListView_InvalidateItem(plv, i, FALSE, RDW_INVALIDATE); // If autoarrange is turned on, do it now... if (ListView_RedrawEnabled(plv)) { if (plv->style & LVS_AUTOARRANGE) ListView_OnArrange(plv, LVA_DEFAULT); else ListView_UpdateScrollBars(plv); } return TRUE; } BOOL NEAR ListView_OnGetItemPosition(LV* plv, int i, POINT FAR* ppt) { LISTITEM FAR* pitem; Assert(ppt); // // This needs to handle all views as it is used to figure out // where the item is during drag and drop and the like // if (!ppt) return FALSE; if (ListView_IsListView(plv) || ListView_IsReportView(plv) #ifdef WINNT_CAIRO || ListView_IsOwnerData( plv ) #endif ) { RECT rcIcon; ListView_GetRects(plv, i, &rcIcon, NULL, NULL, NULL); ppt->x = rcIcon.left; ppt->y = rcIcon.top; } else { #ifndef WINNT_CAIRO if (plv->style & LVS_NOITEMDATA) { Assert(FALSE); return(FALSE); } #endif pitem = ListView_GetItemPtr(plv, i); if (!pitem) return FALSE; if (pitem->pt.x == RECOMPUTE) ListView_Recompute(plv); ppt->x = pitem->pt.x; ppt->y = pitem->pt.y; } return TRUE; } BOOL NEAR ListView_OnGetOrigin(LV* plv, POINT FAR* ppt) { if (!ppt) { DebugMsg(DM_ERROR, TEXT("ListView_OnGetOrigin: ppt is NULL")); return FALSE; } if (ListView_IsListView(plv) || ListView_IsReportView(plv)) return FALSE; *ppt = plv->ptOrigin; return TRUE; } #ifdef UNICODE int NEAR ListView_OnGetStringWidthA(LV* plv, LPCSTR psz) { LPWSTR pszW = NULL; int iRet; if (!psz) return 0; if ((psz != NULL) && (pszW = ProduceWFromA(psz)) == NULL) return 0; iRet = ListView_OnGetStringWidth(plv, pszW); FreeProducedString(pszW); return iRet; } #endif int NEAR ListView_OnGetStringWidth(LV* plv, LPCTSTR psz) { HDC hdc; SIZE siz; if (!psz) return 0; hdc = GetDC(plv->hwnd); SelectFont(hdc, plv->hfontLabel); GetTextExtentPoint(hdc, psz, lstrlen(psz), &siz); ReleaseDC(plv->hwnd, hdc); return siz.cx; } int NEAR ListView_OnGetColumnWidth(LV* plv, int iCol) { if (ListView_IsReportView(plv)) return ListView_RGetColumnWidth(plv, iCol); else if (ListView_IsListView(plv)) return plv->cxItem; return 0; } BOOL FAR PASCAL ListView_ISetColumnWidth(LV* plv, int iCol, int cx, BOOL fExplicit) { if (ListView_IsListView(plv)) { if (iCol != 0) return FALSE; if (plv->cxItem != cx) { // REVIEW: Should optimize what gets invalidated here... //int iCol = plv->xOrigin / plv->cxItem; plv->cxItem = cx; //plv->xOrigin = iCol * cx; if (fExplicit) plv->flags |= LVF_COLSIZESET; // Set the fact that we explictly set size!. RedrawWindow(plv->hwnd, NULL, NULL, RDW_INVALIDATE | RDW_ERASE); ListView_UpdateScrollBars(plv); } return TRUE; } else if (ListView_IsReportView(plv)) { return ListView_RSetColumnWidth(plv, iCol, cx); } return FALSE; } void NEAR ListView_Redraw(LV* plv, HDC hdc, RECT FAR* prcClip) { int i; UINT flags; int cItem = ListView_Count(plv); DWORD dwType = plv->style & LVS_TYPEMASK; NMCUSTOMDRAW nmcd; SetBkMode(hdc, TRANSPARENT); SelectFont(hdc, plv->hfontLabel); nmcd.hdc = hdc; /// not implemented yet //if (ptb->ci.hwnd == GetFocus()) //nmcd.uItemState = CDIS_FOCUS; //else nmcd.uItemState = 0; nmcd.lItemlParam = 0; plv->dwCustom = CICustomDrawNotify((LPCONTROLINFO)plv, CDDS_PREPAINT, &nmcd, plv->bUnicode); if (!(plv->dwCustom & CDRF_SKIPDEFAULT)) { // // For list view and report view, we can save a lot of time // by calculating the index of the first item that may need // painting... // switch (dwType) { case LVS_REPORT: i = ListView_RYHitTest(plv, prcClip->top); #ifdef WINNT_CAIRO cItem = ListView_RYHitTest(plv, prcClip->bottom) + 1; #endif break; case LVS_LIST: #ifdef WINNT_CAIRO i = ListView_LCalcViewItem(plv, prcClip->left, prcClip->top ); cItem = ListView_LCalcViewItem( plv, prcClip->right, prcClip->bottom ) + 1; #else i = ListView_LItemHitTest(plv, prcClip->left, prcClip->top, &flags); #endif break; default: #ifdef WINNT_CAIRO if (ListView_IsOwnerData( plv )) { ListView_CalcMinMaxIndex( plv, prcClip, &i, &cItem ); break; } else #endif { // REVIEW: we can keep a flag which tracks whether the view is // presently in pre-arranged order and bypass Zorder when it is i = 0; // Icon views no such hint } } if (i < 0) i = 0; #ifdef WINNT_CAIRO cItem = min( ListView_Count( plv ), cItem ); if (ListView_IsOwnerData( plv ) && (cItem > i)) { ListView_NotifyCacheHint( plv, i, cItem-1 ); } #endif for (; i < cItem; i++) { int i2; if ((dwType == LVS_ICON || dwType == LVS_SMALLICON) #ifdef WINNT_CAIRO && (!ListView_IsOwnerData(plv)) #endif ) { // Icon views: Draw back-to-front mapped through // Z-order array for proper Z order appearance - If autoarrange // is on, we don't need to do this as our arrange code is setup // to not overlap items! // // For the cases where we might have overlap, we sped this up, // by converting the hdpaZorder into a list of indexes instead // of pointers. This ovoids the costly convert pointer to // index call. // i2 = (int)(DWORD)DPA_FastGetPtr(plv->hdpaZOrder, (cItem - 1) -i); } else i2 = i; plv->iItemDrawing = i2; if (!ListView_DrawItem(plv, i2, hdc, NULL, prcClip, 0)) break; } plv->iItemDrawing = -1; // notify parent afterwards if they want us to if (plv->dwCustom & CDRF_NOTIFYPOSTPAINT) { CICustomDrawNotify((LPCONTROLINFO)plv, CDDS_POSTPAINT, &nmcd, plv->bUnicode); } } } BOOL NEAR ListView_DrawItem(LV* plv, int i, HDC hdc, LPPOINT lpptOrg, RECT FAR* prcClip, UINT flags) { BOOL bRet = TRUE; DWORD dwRet; NMLVCUSTOMDRAW nmcd; LISTITEM FAR* pitem; WORD state; #ifdef WINNT_CAIRO if (ListView_IsOwnerData( plv )) { pitem = NULL; } else #endif { pitem = ListView_FastGetItemPtr(plv, i); } // notify on custom draw then do it! nmcd.nmcd.hdc = hdc; nmcd.nmcd.dwItemSpec = i; nmcd.nmcd.uItemState = 0; if (pitem) { nmcd.nmcd.lItemlParam = pitem->lParam; } if (!(flags & LVDI_NOWAYFOCUS)) { if (plv->flags & LVF_FOCUSED) { // if we're ownerdraw or asked to callback, go // fetch the state if (!pitem || (plv->stateCallbackMask & (LVIS_SELECTED | LVIS_FOCUSED))) { state = ListView_OnGetItemState(plv, i, LVIS_SELECTED | LVIS_FOCUSED); } else { state = pitem->state; } if (state & LVIS_FOCUSED) { nmcd.nmcd.uItemState |= CDIS_FOCUS; } if (state & LVIS_SELECTED) { nmcd.nmcd.uItemState |= CDIS_SELECTED; } } if (plv->style & LVS_SHOWSELALWAYS) { nmcd.nmcd.uItemState |= CDIS_SELECTED; } } nmcd.clrText = plv->clrText; nmcd.clrTextBk = (plv->style & WS_DISABLED ? plv->clrBk : plv->clrTextBk); SelectFont(hdc, plv->hfontLabel); dwRet = CICustomDrawNotify((LPCONTROLINFO)plv, CDDS_ITEMPREPAINT, &nmcd.nmcd, plv->bUnicode); flags &= ~(LVDI_FOCUS | LVDI_SELECTED); if (nmcd.nmcd.uItemState & CDIS_FOCUS) flags |= LVDI_FOCUS; if (nmcd.nmcd.uItemState & CDIS_SELECTED) { if ((plv->flags & LVF_FOCUSED) || (plv->style & LVS_SHOWSELALWAYS)) flags |= LVDI_SELECTED; } if (!(dwRet & CDRF_SKIPDEFAULT)) { if (ListView_IsReportView(plv)) { return ListView_RDrawItem(plv, i, pitem, hdc, lpptOrg, prcClip, flags, nmcd.clrText, nmcd.clrTextBk); } else if (ListView_IsListView(plv)) { ListView_LDrawItem(plv, i, pitem, hdc, lpptOrg, prcClip, flags, nmcd.clrText, nmcd.clrTextBk); } else if (ListView_IsSmallView(plv)) { ListView_SDrawItem(plv, i, hdc, lpptOrg, prcClip, flags, nmcd.clrText, nmcd.clrTextBk); } else { ListView_IDrawItem(plv, i, hdc, lpptOrg, prcClip, flags, nmcd.clrText, nmcd.clrTextBk); } if (dwRet & CDRF_NOTIFYPOSTPAINT) { CICustomDrawNotify((LPCONTROLINFO)plv, CDDS_ITEMPOSTPAINT, &nmcd.nmcd, plv->bUnicode); } } return TRUE; } // NOTE: this function requires a properly selected font. // void WINAPI SHDrawText(HDC hdc, LPCTSTR pszText, RECT FAR* prc, int fmt, UINT flags, int cyChar, int cxEllipses, COLORREF clrText, COLORREF clrTextBk) { int cchText; COLORREF clrSave, clrSaveBk = 0; RECT rc; UINT uETOFlags = 0; TCHAR ach[CCHLABELMAX + CCHELLIPSES]; // REVIEW: Performance idea: // We could cache the currently selected text color // so we don't have to set and restore it each time // when the color is the same. // if (!pszText) return; rc = *prc; // If needed, add in a little extra margin... // if (flags & SHDT_EXTRAMARGIN) { rc.left += g_cxLabelMargin * 3; rc.right -= g_cxLabelMargin * 3; } else { rc.left += g_cxLabelMargin; rc.right -= g_cxLabelMargin; } if ((flags & SHDT_ELLIPSES) && ListView_NeedsEllipses(hdc, pszText, &rc, &cchText, cxEllipses)) { // BUG 12945 - In some cases cchText was comming back bigger than // ARRYASIZE(ach), so we need to make sure we don't overflow the buffer // if cchText is too big for the buffer, truncate it down to size if (cchText >= ARRAYSIZE(ach) - CCHELLIPSES) cchText = ARRAYSIZE(ach) - CCHELLIPSES - 1; hmemcpy(ach, pszText, cchText * sizeof(TCHAR)); lstrcpy(ach + cchText, c_szEllipses); pszText = ach; // Left-justify, in case there's no room for all of ellipses // fmt = LVCFMT_LEFT; cchText += CCHELLIPSES; } else { cchText = lstrlen(pszText); } if (flags & SHDT_TRANSPARENT) clrSave = SetTextColor(hdc, 0x000000); else { uETOFlags |= ETO_OPAQUE; if (flags & SHDT_SELECTED) { clrSave = SetTextColor(hdc, g_clrHighlightText); clrSaveBk = SetBkColor(hdc, g_clrHighlight); if( flags & SHDT_DRAWTEXT ) { FillRect(hdc, prc, g_hbrHighlight); } } else { if (clrText == CLR_DEFAULT && clrTextBk == CLR_DEFAULT) { clrSave = SetTextColor(hdc, g_clrWindowText); clrSaveBk = SetBkColor(hdc, g_clrWindow); if( ( flags & (SHDT_DRAWTEXT | SHDT_DESELECTED) ) == (SHDT_DRAWTEXT | SHDT_DESELECTED) ) { FillRect(hdc, prc, g_hbrWindow); } } else { HBRUSH hbr; if (clrText == CLR_DEFAULT) clrText = g_clrWindowText; if (clrTextBk == CLR_DEFAULT) clrTextBk = g_clrWindow; clrSave = SetTextColor(hdc, clrText); clrSaveBk = SetBkColor(hdc, clrTextBk); if( ( flags & (SHDT_DRAWTEXT | SHDT_DESELECTED) ) == (SHDT_DRAWTEXT | SHDT_DESELECTED) ) { hbr = CreateSolidBrush(GetNearestColor(hdc, clrTextBk)); if (hbr) { FillRect(hdc, prc, hbr); DeleteObject(hbr); } else FillRect(hdc, prc, GetStockObject( WHITE_BRUSH ) ); } } } } // If we want the item to display as if it was depressed, we will // offset the text rectangle down and to the left if (flags & SHDT_DEPRESSED) OffsetRect(&rc, g_cxBorder, g_cyBorder); if (flags & SHDT_DRAWTEXT) { #ifdef FOLDING_NAMES UINT uDTFlags = DT_LVWRAP; uDTFlags |= ( flags & SHDT_DTELLIPSIS ) ? DT_END_ELLIPSIS : DT_WORD_ELLIPSIS; #else #ifdef WIN32 UINT uDTFlags = DT_LVWRAP | DT_WORD_ELLIPSIS; #else UINT uDTFlags = DT_LVWRAP; #endif // WIN32 #endif // FOLDING_NAMES if( !( flags & SHDT_CLIPPED ) ) uDTFlags |= DT_NOCLIP; DrawText(hdc, pszText, cchText, &rc, uDTFlags ); } else { if (fmt != LVCFMT_LEFT) { SIZE siz; GetTextExtentPoint(hdc, pszText, cchText, &siz); if (fmt == LVCFMT_CENTER) rc.left = (rc.left + rc.right - siz.cx) / 2; else // fmt == LVCFMT_RIGHT rc.left = rc.right - siz.cx; } // Center vertically in case the bitmap (to the left) is larger than // the height of one line rc.top += (rc.bottom - rc.top - cyChar) / 2; if( flags & SHDT_CLIPPED ) uETOFlags |= ETO_CLIPPED; ExtTextOut(hdc, rc.left, rc.top, uETOFlags, prc, pszText, cchText, NULL); } if (flags & (SHDT_SELECTED | SHDT_DESELECTED | SHDT_TRANSPARENT)) { SetTextColor(hdc, clrSave); if (! (flags & SHDT_TRANSPARENT)) SetBkColor(hdc, clrSaveBk); } } /*---------------------------------------------------------------- ** Create an imagelist to be used for dragging. ** ** 1) create mask and image bitmap matching the select bounds size ** 2) draw the text to both bitmaps (in black for now) ** 3) create an imagelist with these bitmaps ** 4) make a dithered copy of the image onto the new imagelist **----------------------------------------------------------------*/ HIMAGELIST NEAR ListView_OnCreateDragImage(LV *plv, int iItem, LPPOINT lpptUpLeft) { HWND hwndLV = plv->hwnd; #ifdef FOLDING_NAMES RECT rcBounds, rcImage, rcLabel; #else RECT rcBounds, rcImage; #endif HDC hdcMem = NULL; HBITMAP hbmImage = NULL; HBITMAP hbmMask = NULL; HBITMAP hbmOld; HIMAGELIST himl = NULL; int dx, dy; HIMAGELIST himlSrc; LV_ITEM item; POINT ptOrg; if (!lpptUpLeft) return NULL; #ifdef FOLDING_NAMES ListView_GetRects(plv, iItem, &rcImage, &rcLabel, NULL, &rcBounds); ListView_UnfoldRects( plv, iItem, &rcImage, &rcLabel, NULL, &rcBounds ); #else ListView_GetRects(plv, iItem, &rcImage, NULL, NULL, &rcBounds); #endif if (ListView_IsIconView(plv)) InflateRect(&rcImage, -g_cxIconMargin, -g_cyIconMargin); ptOrg.x = 0; // chop off any extra filler above icon ptOrg.y = rcBounds.top - rcImage.top; dx = rcBounds.right - rcBounds.left; dy = rcBounds.bottom - rcBounds.top + ptOrg.y; lpptUpLeft->x = rcBounds.left - ptOrg.x; lpptUpLeft->y = rcBounds.top - ptOrg.y; if (!(hdcMem = CreateCompatibleDC(NULL))) goto CDI_Exit; if (!(hbmImage = CreateColorBitmap(dx, dy))) goto CDI_Exit; if (!(hbmMask = CreateMonoBitmap(dx, dy))) goto CDI_Exit; // prepare for drawing the item SelectObject(hdcMem, plv->hfontLabel); SetBkMode(hdcMem, TRANSPARENT); /* ** draw the text to both bitmaps */ hbmOld = SelectObject(hdcMem, hbmImage); // fill image with black for transparency PatBlt(hdcMem, 0, 0, dx, dy, BLACKNESS); ListView_DrawItem(plv, iItem, hdcMem, &ptOrg, NULL, #ifdef FOLDING_NAMES LVDI_NOIMAGE | LVDI_TRANSTEXT | LVDI_NOWAYFOCUS | LVDI_UNFOLDED ); #else LVDI_NOIMAGE | LVDI_TRANSTEXT | LVDI_NOWAYFOCUS ); #endif SelectObject(hdcMem, hbmMask); // fill mask with white for transparency PatBlt(hdcMem, 0, 0, dx, dy, WHITENESS); ListView_DrawItem(plv, iItem, hdcMem, &ptOrg, NULL, #ifdef FOLDING_NAMES LVDI_NOIMAGE | LVDI_TRANSTEXT | LVDI_NOWAYFOCUS | LVDI_UNFOLDED ); #else LVDI_NOIMAGE | LVDI_TRANSTEXT | LVDI_NOWAYFOCUS ); #endif // unselect objects that we used SelectObject(hdcMem, hbmOld); SelectObject(hdcMem, g_hfontSystem); /* ** make an image list that for now only has the text */ // // BUGBUG: To fix a pri-1 M7 bug, we create a shared image list. // if (!(himl = ImageList_Create(dx, dy, ILC_MASK, 1, 0))) goto CDI_Exit; ImageList_SetBkColor(himl, CLR_NONE); ImageList_Add(himl, hbmImage, hbmMask); /* ** make a dithered copy of the image part onto our bitmaps ** (need both bitmap and mask to be dithered) */ himlSrc = ListView_OnGetImageList(plv, !(ListView_IsIconView(plv))); if (himlSrc) { item.iItem = iItem; item.iSubItem = 0; item.mask = LVIF_IMAGE |LVIF_STATE; item.stateMask = LVIS_OVERLAYMASK; ListView_OnGetItem(plv, &item); ImageList_CopyDitherImage(himl, 0, rcImage.left - rcBounds.left, 0, himlSrc, item.iImage, item.state & LVIS_OVERLAYMASK); } CDI_Exit: if (hdcMem) DeleteObject(hdcMem); if (hbmImage) DeleteObject(hbmImage); if (hbmMask) DeleteObject(hbmMask); return himl; } //------------------------------------------------------------------- // ListView_OnGetTopIndex -- Gets the index of the first visible item // For list view and report view this calculates the actual index // for iconic views it alway returns 0 // int NEAR ListView_OnGetTopIndex(LV* plv) { if (ListView_IsReportView(plv)) return (int)((plv->ptlRptOrigin.y) / plv->cyItem); else if (ListView_IsListView(plv)) return (plv->xOrigin / plv->cxItem) * plv->cItemCol; else return(0); } //------------------------------------------------------------------- // ListView_OnGetCountPerPage -- Gets the count of items that will fit // on a page For list view and report view this calculates the // count depending on the size of the window and for Iconic views it // will always return the count of items in the list view. // int NEAR ListView_OnGetCountPerPage(LV* plv) { if (ListView_IsReportView(plv)) return (plv->sizeClient.cy - plv->yTop) / plv->cyItem; else if (ListView_IsListView(plv)) return ((plv->sizeClient.cx)/ plv->cxItem) * plv->cItemCol; else return (ListView_Count(plv)); } #endif // !WIN31 #ifdef FOLDING_NAMES /*---------------------------------------------------------------------------- / ListView_InvalidateFoldedItem implementation / ----------------------------- / Purpose: / Provides support for invalidating items within list views. / / Notes: / Copes with invalidating the extra region in the list view that requires / us to erase the background. Design to optimise out the ERASURE of the / background. / / For details on the API see ListView_InvalidateItem. / / In: / plv ->ListView structure to work with / iItem = item number / bSrelectionOnly = refesh the selection / fRedraw = Flags for RedrawWindow / / Out: / - /----------------------------------------------------------------------------*/ void NEAR ListView_InvalidateFoldedItem(LV* plv, int iItem, BOOL fSelectionOnly, UINT fRedraw) { RECT rcLabel; LISTITEM FAR* pItem; if (ListView_IsOwnerData( plv )) return; pItem = ListView_GetItemPtr(plv, iItem); ListView_InvalidateItem( plv, iItem, fSelectionOnly, fRedraw ); if ( pItem ) { if ( !( UNFOLD_ITEM( plv, pItem ) || ( fRedraw & RDW_ERASE ) ) ) { if ( pItem ->cyMultiLabel > CLIP_HEIGHT ) { ListView_GetRects( plv, iItem, NULL, &rcLabel, NULL, NULL); rcLabel.top = rcLabel.bottom; rcLabel.bottom = ( rcLabel.top + pItem->cyMultiLabel ) - CLIP_HEIGHT; RedrawWindow( plv ->hwnd, &rcLabel, NULL, fRedraw | RDW_ERASE ); } } } } /*---------------------------------------------------------------------------- / ListView_UnfoldedRects implementation / ---------------------- / Purpose: / Having previously called get rects, then call this function to ensure / that they are correctly unfolded. / / Notes: / - / / In: / plv -> list view to unfold on / iItem = item number / prcIcon -> icon bounding box / prcLabel -> rectangle for the label structure / prcBounds -> bounds rectangle / == NULL for none / These are currently the same for large icons / prcSelectBounds -> selection bounds / == NULL / / / Out: / - /----------------------------------------------------------------------------*/ void NEAR ListView_UnfoldRects(LV* plv, int iItem, RECT FAR* prcIcon, RECT FAR* prcLabel, RECT FAR* prcBounds, RECT FAR* prcSelectBounds) { LISTITEM FAR* pitem; if (ListView_IsOwnerData( plv )) return; pitem = ListView_GetItemPtr(plv, iItem); Assert( pitem ); if ( pitem ) { /* Expand the label if required */ if ( prcLabel ) prcLabel ->bottom = prcLabel ->top + pitem ->cyMultiLabel; /* If we want bounds & all parameters present then do the magic */ if ( prcBounds && prcIcon && prcLabel ) UnionRect( prcBounds, prcIcon, prcLabel ); if ( prcSelectBounds && prcIcon && prcLabel ) UnionRect( prcSelectBounds, prcIcon, prcLabel ); } } #endif