#include "shellprv.h" #include "defviewp.h" #include "ViewState.h" CViewState::CViewState() { ASSERT(_lParamSort == NULL); ASSERT(_iDirection == 0); ASSERT(_iLastColumnClick == 0); ASSERT(_ViewMode == 0); ASSERT(_ptScroll.x == 0 && _ptScroll.y == 0); ASSERT(_guidGroupID == GUID_NULL); ASSERT(_scidDetails.fmtid == GUID_NULL); ASSERT(_scidDetails.pid == 0); ASSERT(_hdsaColumnOrder == NULL); ASSERT(_hdsaColumnWidths == NULL); ASSERT(_hdsaColumnStates == NULL); ASSERT(_hdpaItemPos == NULL); ASSERT(_pbPositionData == NULL); _iDirection = 1; _fFirstViewed = TRUE; // Assume this is the first time we are looking at a folder. } CViewState::~CViewState() { if (_hdsaColumnOrder) DSA_Destroy(_hdsaColumnOrder); if (_hdsaColumnWidths) DSA_Destroy(_hdsaColumnWidths); if (_hdsaColumnStates) DSA_Destroy(_hdsaColumnStates); if (_hdsaColumns) DSA_Destroy(_hdsaColumns); ClearPositionData(); LocalFree(_pbPositionData); // accepts NULL } // When initializing a new DefView, see if we can // propogate information from the previous one. void CViewState::InitFromPreviousView(IUnknown* pPrevView) { CDefView *pdsvPrev; if (SUCCEEDED(pPrevView->QueryInterface(IID_PPV_ARG(CDefView, &pdsvPrev)))) { // preserve stuff like sort order _lParamSort = pdsvPrev->_vs._lParamSort; _iDirection = pdsvPrev->_vs._iDirection; _iLastColumnClick = pdsvPrev->_vs._iLastColumnClick; pdsvPrev->Release(); } } void CViewState::InitFromHeader(DVSAVEHEADER_COMBO* pdv) { _lParamSort = pdv->dvSaveHeader.dvState.lParamSort; _iDirection = pdv->dvSaveHeader.dvState.iDirection; // Patch this up. I guess at one time we persisted this wrong. if (_iDirection == 0) _iDirection = 1; _iLastColumnClick = pdv->dvSaveHeader.dvState.iLastColumnClick; _ViewMode = pdv->dvSaveHeader.ViewMode; _ptScroll = pdv->dvSaveHeader.ptScroll; } void CViewState::GetDefaults(CDefView* pdv, LPARAM* plParamSort, int* piDirection, int* piLastColumnClick) { SHELLSTATE ss; SHGetSetSettings(&ss, SSF_SORTCOLUMNS, FALSE); if (plParamSort) *plParamSort = ss.lParamSort; if (piDirection) *piDirection = ss.iSortDirection ? ss.iSortDirection : 1; if (piLastColumnClick) *piLastColumnClick = -1; pdv->CallCB(SFVM_GETSORTDEFAULTS, (LPARAM)piDirection, (WPARAM)plParamSort); } void CViewState::InitWithDefaults(CDefView* pdv) { GetDefaults(pdv, &_lParamSort, &_iDirection, &_iLastColumnClick); } int CALLBACK CViewState::_SavedItemCompare(void *p1, void *p2, LPARAM lParam) { CDefView *pdv = reinterpret_cast(lParam); UNALIGNED VIEWSTATE_POSITION *pdvi1 = (UNALIGNED VIEWSTATE_POSITION *)p1; UNALIGNED VIEWSTATE_POSITION *pdvi2 = (UNALIGNED VIEWSTATE_POSITION *)p2; // manually terminate these pidls because they are packed together // in the save buffer LPITEMIDLIST pFakeEnd1 = _ILNext(&pdvi1->idl); USHORT uSave1 = pFakeEnd1->mkid.cb; pFakeEnd1->mkid.cb = 0; LPITEMIDLIST pFakeEnd2 = _ILNext(&pdvi2->idl); USHORT uSave2 = pFakeEnd2->mkid.cb; pFakeEnd2->mkid.cb = 0; int nCmp = pdv->_Compare(&pdvi1->idl, &pdvi2->idl, reinterpret_cast(pdv)); pFakeEnd2->mkid.cb = uSave2; pFakeEnd1->mkid.cb = uSave1; return nCmp; } BOOL CViewState::SyncPositions(CDefView* pdv) { if (_ViewMode != pdv->_fs.ViewMode) { return FALSE; } if (_hdpaItemPos == NULL || DPA_GetPtrCount(_hdpaItemPos) == 0) { return FALSE; } if (DPA_Sort(_hdpaItemPos, _SavedItemCompare, (LPARAM)pdv)) { UNALIGNED VIEWSTATE_POSITION * UNALIGNED * ppDVItem = (UNALIGNED VIEWSTATE_POSITION * UNALIGNED *)DPA_GetPtrPtr(_hdpaItemPos); UNALIGNED VIEWSTATE_POSITION * UNALIGNED *ppEndDVItems = ppDVItem + DPA_GetPtrCount(_hdpaItemPos); // Turn off auto-arrange and snap-to-grid if it's on at the mo. DWORD dwStyle = GetWindowStyle(pdv->_hwndListview); if (dwStyle & LVS_AUTOARRANGE) SetWindowLong(pdv->_hwndListview, GWL_STYLE, dwStyle & ~LVS_AUTOARRANGE); DWORD dwLVExStyle = ListView_GetExtendedListViewStyle(pdv->_hwndListview); if (dwLVExStyle & LVS_EX_SNAPTOGRID) ListView_SetExtendedListViewStyle(pdv->_hwndListview, dwLVExStyle & ~LVS_EX_SNAPTOGRID); HDSA hdsaPositionlessItems = NULL; int iCount = ListView_GetItemCount(pdv->_hwndListview); for (int i = 0; i < iCount; i++) { LPCITEMIDLIST pidl = pdv->_GetPIDL(i); // need to check for pidl because this could be on a background // thread and an fsnotify could be coming through to blow it away for ( ; pidl ; ) { int nCmp; if (ppDVItem < ppEndDVItems) { // We terminate the IDList manually after saving // the needed information. Note we will not GP fault // since we added sizeof(ITEMIDLIST) onto the Alloc LPITEMIDLIST pFakeEnd = _ILNext(&(*ppDVItem)->idl); USHORT uSave = pFakeEnd->mkid.cb; pFakeEnd->mkid.cb = 0; nCmp = pdv->_Compare(&((*ppDVItem)->idl), (void *)pidl, (LPARAM)pdv); pFakeEnd->mkid.cb = uSave; } else { // do this by default. this prevents overlap of icons // // i.e. if we've run out of saved positions information, // we need to just loop through and set all remaining items // to position 0x7FFFFFFFF so that when it's really shown, // the listview will pick a new (unoccupied) spot. // breaking out now would leave it were the _Sort // put it, but another item with saved state info could // have come and be placed on top of it. nCmp = 1; } if (nCmp > 0) { // We did not find the item // reset it's position to be recomputed if (NULL == hdsaPositionlessItems) hdsaPositionlessItems = DSA_Create(sizeof(int), 16); if (hdsaPositionlessItems) DSA_AppendItem(hdsaPositionlessItems, (void*)&i); break; } else if (nCmp == 0) // They are equal { UNALIGNED VIEWSTATE_POSITION * pDVItem = *ppDVItem; pdv->_SetItemPosition(i, pDVItem->pt.x, pDVItem->pt.y); ppDVItem++; // move on to the next break; } ppDVItem++; // move to the next } } if (hdsaPositionlessItems) { for (i = 0; i < DSA_GetItemCount(hdsaPositionlessItems); i++) { int* pIndex = (int*)DSA_GetItemPtr(hdsaPositionlessItems, i); pdv->_SetItemPosition(*pIndex, 0x7FFFFFFF, 0x7FFFFFFF); } DSA_Destroy(hdsaPositionlessItems); } // Turn auto-arrange and snap to grid back on if needed... if (dwLVExStyle & LVS_EX_SNAPTOGRID) ListView_SetExtendedListViewStyle(pdv->_hwndListview, dwLVExStyle); if (dwStyle & LVS_AUTOARRANGE) SetWindowLong(pdv->_hwndListview, GWL_STYLE, dwStyle); } return TRUE; } void CViewState::LoadPositionBlob(CDefView* pdv, DWORD cbSizeofStream, IStream* pstm) { // Allocate a blob of memory to hold the position info. if (_pbPositionData) LocalFree(_pbPositionData); _pbPositionData = (BYTE*)LocalAlloc(LPTR, cbSizeofStream); if (_pbPositionData == NULL) return; // Read into that blob. if (SUCCEEDED(pstm->Read(_pbPositionData, cbSizeofStream, NULL))) { // Walk the blob, and append to the DPA. UNALIGNED VIEWSTATE_POSITION *pDVItem = (UNALIGNED VIEWSTATE_POSITION *)(_pbPositionData); UNALIGNED VIEWSTATE_POSITION *pDVEnd = (UNALIGNED VIEWSTATE_POSITION *)(_pbPositionData + cbSizeofStream - sizeof(VIEWSTATE_POSITION)); ClearPositionData(); // destroy _hdpaItemPos // Grow every 16 items _hdpaItemPos = DPA_Create(16); if (_hdpaItemPos) { for ( ; ; pDVItem = (UNALIGNED VIEWSTATE_POSITION *)_ILNext(&pDVItem->idl)) { if (pDVItem > pDVEnd) { break; // Invalid list } // End normally when we reach a NULL IDList if (pDVItem->idl.mkid.cb == 0) { break; } if (DPA_AppendPtr(_hdpaItemPos, pDVItem) < 0) { break; } } } } } HRESULT CViewState::SavePositionBlob(CDefView* pdv, IStream* pstm) { HRESULT hr = S_FALSE; // success, but did nothing if (pdv->_fUserPositionedItems && pdv->_IsPositionedView()) { VIEWSTATE_POSITION dvitem = {0}; int iCount = ListView_GetItemCount(pdv->_hwndListview); for (int i = 0; SUCCEEDED(hr) && (i < iCount); i++) { ListView_GetItemPosition(pdv->_hwndListview, i, &dvitem.pt); hr = pstm->Write(&dvitem.pt, sizeof(dvitem.pt), NULL); if (SUCCEEDED(hr)) { LPCITEMIDLIST pidl = pdv->_GetPIDL(i); if (pidl) hr = pstm->Write(pidl, pidl->mkid.cb, NULL); else hr = E_FAIL; } } if (SUCCEEDED(hr)) { // Terminate the list with a NULL IDList dvitem.idl.mkid.cb = 0; hr = pstm->Write(&dvitem, sizeof(dvitem), NULL); } } return hr; } void CViewState::ClearPositionData() { if (_hdpaItemPos) { DPA_Destroy(_hdpaItemPos); _hdpaItemPos = NULL; } } UINT CViewState::GetColumnCount() { if (!_hdsaColumns) return 0; return DSA_GetItemCount(_hdsaColumns); } DWORD CViewState::GetColumnState(UINT uCol) { if (_hdsaColumns && (uCol < GetColumnCount())) { COL_INFO* pci = (COL_INFO*)DSA_GetItemPtr(_hdsaColumns, uCol); return pci->csFlags; } return 0; } DWORD CViewState::GetTransientColumnState(UINT uCol) { if (_hdsaColumns && (uCol < GetColumnCount())) { COL_INFO* pci = (COL_INFO*)DSA_GetItemPtr(_hdsaColumns, uCol); return pci->tsFlags; } return 0; } void CViewState::SetColumnState(UINT uCol, DWORD dwMask, DWORD dwNewBits) { if (_hdsaColumns && uCol < GetColumnCount()) { COL_INFO* pci = (COL_INFO*)DSA_GetItemPtr(_hdsaColumns, uCol); pci->csFlags = (pci->csFlags & ~dwMask) | (dwNewBits & dwMask); } } void CViewState::SetTransientColumnState(UINT uCol, DWORD dwMask, DWORD dwNewBits) { if (_hdsaColumns && uCol < GetColumnCount()) { COL_INFO* pci = (COL_INFO*)DSA_GetItemPtr(_hdsaColumns, uCol); pci->tsFlags = (pci->tsFlags & ~dwMask) | (dwNewBits & dwMask); } } LPTSTR CViewState::GetColumnName(UINT uCol) { if (_hdsaColumns && (uCol < GetColumnCount())) { COL_INFO* pci = (COL_INFO*)DSA_GetItemPtr(_hdsaColumns, uCol); return pci->szName; } return NULL; } UINT CViewState::GetColumnCharCount(UINT uCol) { if (_hdsaColumns && (uCol < GetColumnCount())) { COL_INFO* pci = (COL_INFO*)DSA_GetItemPtr(_hdsaColumns, uCol); return pci->cChars; } return 0; } int CViewState::GetColumnFormat(UINT uCol) { if (_hdsaColumns && (uCol < GetColumnCount())) { COL_INFO* pci = (COL_INFO*)DSA_GetItemPtr(_hdsaColumns, uCol); return pci->fmt; } return 0; } HRESULT CViewState::InitializeColumns(CDefView* pdv) { if (_hdsaColumns != NULL) return S_OK; _hdsaColumns = DSA_Create(sizeof(COL_INFO), 6); if (!_hdsaColumns) return E_OUTOFMEMORY; for (UINT iReal = 0; ; iReal++) { DETAILSINFO di = {0}; di.fmt = LVCFMT_LEFT; di.cxChar = 20; di.str.uType = (UINT)-1; if (SUCCEEDED(pdv->_GetDetailsHelper(iReal, &di))) { COL_INFO ci = {0}; StrRetToBuf(&di.str, NULL, ci.szName, ARRAYSIZE(ci.szName)); ci.cChars = di.cxChar; ci.csFlags = pdv->_DefaultColumnState(iReal); ci.fmt = di.fmt; DSA_AppendItem(_hdsaColumns, &ci); } else break; } // Set up saved column state only if the saved state // contains information other than "nothing". if (_hdsaColumnStates) { UINT cStates = DSA_GetItemCount(_hdsaColumnStates); if (cStates > 0) { // 99/02/05 vtan: If there is a saved column state then // clear all the column "on" states to "off" and only // display what columns are specified. Start at 1 so // that name is always on. for (iReal = 1; iReal < GetColumnCount(); iReal++) { COL_INFO* pci = (COL_INFO*)DSA_GetItemPtr(_hdsaColumns, iReal); pci->csFlags &= ~SHCOLSTATE_ONBYDEFAULT; } for (UINT i = 0; i < cStates; i++) { DWORD dw; DSA_GetItem(_hdsaColumnStates, i, &dw); SetColumnState(dw, SHCOLSTATE_ONBYDEFAULT, SHCOLSTATE_ONBYDEFAULT); } } } return S_OK; } // When Loading or Saving from the View State Stream HRESULT CViewState::LoadFromStream(CDefView* pdv, IStream* pstm) { ULONG cbRead; DVSAVEHEADER_COMBO dv; ULARGE_INTEGER libStartPos; LARGE_INTEGER dlibMove = {0}; pstm->Seek(dlibMove, STREAM_SEEK_CUR, &libStartPos); // See what format the persisted view is in: HRESULT hr = pstm->Read(&dv, sizeof(dv), &cbRead); if (SUCCEEDED(hr) && sizeof(DVSAVEHEADER_COMBO) == cbRead && dv.dvSaveHeader.cbSize == sizeof(WIN95HEADER) && dv.dvSaveHeader.cbColOffset == 0 && dv.dvSaveHeaderEx.dwSignature == IE4HEADER_SIGNATURE && dv.dvSaveHeaderEx.cbSize >= sizeof(IE4HEADER)) { InitFromHeader(&dv); if (dv.dvSaveHeaderEx.wVersion < IE4HEADER_VERSION) { // We used to store szExtended in here -- not any more dv.dvSaveHeaderEx.dwUnused = 0; } if (dv.dvSaveHeaderEx.cbColOffset >= sizeof(dv)) { dlibMove.QuadPart = libStartPos.QuadPart + dv.dvSaveHeaderEx.cbColOffset; hr = pstm->Seek(dlibMove, STREAM_SEEK_SET, NULL); if (SUCCEEDED(hr)) { hr = LoadColumns(pdv, pstm); } } if (SUCCEEDED(hr)) { dlibMove.QuadPart = libStartPos.QuadPart + dv.dvSaveHeader.cbPosOffset; hr = pstm->Seek(dlibMove, STREAM_SEEK_SET, NULL); if (SUCCEEDED(hr)) { LoadPositionBlob(pdv, dv.dvSaveHeaderEx.cbStreamSize, pstm); } } } return S_OK; } void SetSize(ULARGE_INTEGER libCurPosition, IStream* pstm) { LARGE_INTEGER dlibMove; dlibMove.QuadPart = libCurPosition.QuadPart; pstm->Seek(dlibMove, STREAM_SEEK_SET, NULL); pstm->SetSize(libCurPosition); } DWORD CViewState::_GetStreamSize(IStream* pstm) { DWORD dwRet = 0; ULARGE_INTEGER uli; if (SUCCEEDED(IStream_Size(pstm, &uli))) { if (0 == uli.HighPart) { dwRet = uli.LowPart; } } return dwRet; } HRESULT CViewState::SaveToStream(CDefView* pdv, IStream* pstm) { ULONG ulWrite; DVSAVEHEADER_COMBO dv = {0}; LARGE_INTEGER dlibMove = {0}; ULARGE_INTEGER libCurPosition; // Get the current info. Sync(pdv, FALSE); // Position the stream right after the headers, and save the starting // position at the same time dlibMove.QuadPart = sizeof(dv); pstm->Seek(dlibMove, STREAM_SEEK_CUR, &libCurPosition); // Avoid 2 calls to seek by just subtracting libCurPosition.QuadPart -= sizeof(dv); // Save column order and size info HRESULT hr = SaveColumns(pdv, pstm); if (SUCCEEDED(hr)) { dv.dvSaveHeader.cbSize = sizeof(dv.dvSaveHeader); // We save the view mode to determine if the scroll positions are // still valid on restore dv.dvSaveHeader.ViewMode = _ViewMode; dv.dvSaveHeader.ptScroll.x = _ptScroll.x; dv.dvSaveHeader.ptScroll.y = _ptScroll.y; dv.dvSaveHeader.dvState.lParamSort = (LONG)_lParamSort; dv.dvSaveHeader.dvState.iDirection = _iDirection; dv.dvSaveHeader.dvState.iLastColumnClick = _iLastColumnClick; // dvSaveHeaderEx.cbColOffset holds the true offset. // Win95 gets confused when cbColOffset points to the new // format. Zeroing this out tells Win95 to use default widths // (after uninstall of ie40). // // dv.dvSaveHeader.cbColOffset = 0; dv.dvSaveHeaderEx.dwSignature = IE4HEADER_SIGNATURE; dv.dvSaveHeaderEx.cbSize = sizeof(dv.dvSaveHeaderEx); dv.dvSaveHeaderEx.wVersion = IE4HEADER_VERSION; ULARGE_INTEGER libPosPosition; // Save the Position Information dlibMove.QuadPart = 0; pstm->Seek(dlibMove, STREAM_SEEK_CUR, &libPosPosition); dv.dvSaveHeaderEx.cbColOffset = sizeof(dv); dv.dvSaveHeader.cbPosOffset = (USHORT)(libPosPosition.QuadPart - libCurPosition.QuadPart); // Save potision info, currently stream is positioned immediately after column info hr = SavePositionBlob(pdv, pstm); if (SUCCEEDED(hr)) { ULARGE_INTEGER libEndPosition; // Win95 expects cbPosOffset to be at the end of the stream -- // don't change it's value and never store anything after // the position information. // Calculate size of total information saved. // This is needed when we read the stream. dlibMove.QuadPart = 0; if (SUCCEEDED(pstm->Seek(dlibMove, STREAM_SEEK_CUR, &libEndPosition))) { dv.dvSaveHeaderEx.cbStreamSize = (DWORD)(libEndPosition.QuadPart - libCurPosition.QuadPart); } // Now save the header information dlibMove.QuadPart = libCurPosition.QuadPart; pstm->Seek(dlibMove, STREAM_SEEK_SET, NULL); hr = pstm->Write(&dv, sizeof(dv), &ulWrite); if (FAILED(hr) || ulWrite != sizeof(dv)) { SetSize(libCurPosition, pstm); hr = S_OK; } // Make sure we save all information written so far libCurPosition.QuadPart += dv.dvSaveHeaderEx.cbStreamSize; } } return hr; } HRESULT CViewState::SaveToPropertyBag(CDefView* pdv, IPropertyBag* ppb) { // Get the current info. Sync(pdv, FALSE); SHPropertyBag_WriteDWORD(ppb, VS_PROPSTR_MODE, _ViewMode); SHPropertyBag_WritePOINTSScreenRes(ppb, VS_PROPSTR_SCROLL, &_ptScroll); SHPropertyBag_WriteDWORD(ppb, VS_PROPSTR_SORT, static_cast(_lParamSort)); SHPropertyBag_WriteInt(ppb, VS_PROPSTR_SORTDIR, _iDirection); SHPropertyBag_WriteInt(ppb, VS_PROPSTR_COL, _iLastColumnClick); IStream* pstm = SHCreateMemStream(NULL, 0); if (pstm) { if (S_OK == SaveColumns(pdv, pstm)) { SHPropertyBag_WriteStream(ppb, VS_PROPSTR_COLINFO, pstm); } else { SHPropertyBag_Delete(ppb, VS_PROPSTR_COLINFO); } pstm->Release(); } pstm = SHCreateMemStream(NULL, 0); if (pstm) { if (S_OK == SavePositionBlob(pdv, pstm)) { SHPropertyBag_WriteStreamScreenRes(ppb, VS_PROPSTR_ITEMPOS, pstm); } else { SHPropertyBag_DeleteScreenRes(ppb, VS_PROPSTR_ITEMPOS); } pstm->Release(); } return S_OK; } HRESULT CViewState::LoadFromPropertyBag(CDefView* pdv, IPropertyBag* ppb) { SHPropertyBag_ReadDWORDDef(ppb, VS_PROPSTR_MODE, &_ViewMode, FVM_ICON); SHPropertyBag_ReadDWORDDef(ppb, VS_PROPSTR_SORT, reinterpret_cast(&_lParamSort), 0); SHPropertyBag_ReadIntDef(ppb, VS_PROPSTR_SORTDIR, &_iDirection, 1); SHPropertyBag_ReadIntDef(ppb, VS_PROPSTR_COL, &_iLastColumnClick, -1); if (FAILED(SHPropertyBag_ReadPOINTSScreenRes(ppb, VS_PROPSTR_SCROLL, &_ptScroll))) { _ptScroll.x = _ptScroll.y = 0; } IStream* pstm; if (SUCCEEDED(SHPropertyBag_ReadStream(ppb, VS_PROPSTR_COLINFO, &pstm))) { LoadColumns(pdv, pstm); pstm->Release(); } if (SUCCEEDED(SHPropertyBag_ReadStreamScreenRes(ppb, VS_PROPSTR_ITEMPOS, &pstm))) { LoadPositionBlob(pdv, _GetStreamSize(pstm), pstm); pstm->Release(); } return S_OK; } HDSA DSA_CreateFromStream(DWORD cbSize, int cItems, IStream* pstm) { HDSA hdsa = DSA_Create(cbSize, cItems); if (hdsa) { BYTE* pb = (BYTE*)LocalAlloc(LPTR, cbSize); if (pb) { BOOL fFailedToRead = FALSE; ULONG cbRead; while (cItems--) { if (SUCCEEDED(pstm->Read(pb, cbSize, &cbRead) && cbRead == cbSize)) { DSA_AppendItem(hdsa, pb); } else { fFailedToRead = TRUE; } } LocalFree(pb); if (fFailedToRead) { // The stream is probrably corrupt. DSA_Destroy(hdsa); hdsa = NULL; } } } return hdsa; } // When Loading from a View Callback provided stream. HRESULT CViewState::LoadColumns(CDefView* pdv, IStream* pstm) { // Read the extended View state header HRESULT hr; ULONG cbRead; VIEWSTATEHEADER vsh; ULARGE_INTEGER libStartPos; LARGE_INTEGER dlibMove = {0}; // Store off the current stream pointer. If we are called directly, this is probrably Zero, // However this method gets called from ::Load, so this it definitly not zero in that case. pstm->Seek(dlibMove, STREAM_SEEK_CUR, &libStartPos); // The VSH struct has many "Substructs" indicating the version of ths struct we are reading. // There is probrably a more efficient mechanism of version discovery, but this is easiest to read and understand. hr = pstm->Read(&vsh.Version1, sizeof(vsh.Version1), &cbRead); if (SUCCEEDED(hr) && sizeof(vsh.Version1) == cbRead && // Fail if we didn't read enough VIEWSTATEHEADER_SIGNATURE == vsh.Version1.dwSignature) // Fail if the signature is bogus { if (vsh.Version1.uVersion >= VIEWSTATEHEADER_VERSION_1) { if (vsh.Version1.uCols > 0) { // Load the Column Ordering dlibMove.QuadPart = libStartPos.QuadPart + vsh.Version1.uOffsetColOrder; pstm->Seek(dlibMove, STREAM_SEEK_SET, NULL); if (_hdsaColumnOrder) DSA_Destroy(_hdsaColumnOrder); _hdsaColumnOrder = DSA_CreateFromStream(sizeof(int), vsh.Version1.uCols, pstm); // Load the Column Widths dlibMove.QuadPart = libStartPos.QuadPart + vsh.Version1.uOffsetWidths; pstm->Seek(dlibMove, STREAM_SEEK_SET, NULL); if (_hdsaColumnWidths) DSA_Destroy(_hdsaColumnWidths); _hdsaColumnWidths = DSA_CreateFromStream(sizeof(USHORT), vsh.Version1.uCols, pstm); } if (vsh.Version1.uVersion >= VIEWSTATEHEADER_VERSION_2 && vsh.Version1.uCols > 0) { DWORD dwRead; // Seek to read the rest of the header dlibMove.QuadPart = libStartPos.QuadPart + sizeof(vsh.Version1); pstm->Seek(dlibMove, STREAM_SEEK_SET, NULL); hr = pstm->Read(&vsh.Version2, sizeof(vsh.Version2), &cbRead); if (SUCCEEDED(hr) && sizeof(vsh.Version2) == cbRead && vsh.Version2.uOffsetColStates) { // Load the Column States dlibMove.QuadPart = libStartPos.QuadPart + vsh.Version2.uOffsetColStates; pstm->Seek(dlibMove, STREAM_SEEK_SET, NULL); // This one is funky: There is a terminating sentinal.... if (_hdsaColumnStates) DSA_Destroy(_hdsaColumnStates); _hdsaColumnStates = DSA_Create(sizeof(DWORD), 5); if (_hdsaColumnStates) { do { if (SUCCEEDED(pstm->Read(&dwRead, sizeof(DWORD), &cbRead)) && cbRead == sizeof(DWORD) && dwRead != 0xFFFFFFFF) { DSA_AppendItem(_hdsaColumnStates, &dwRead); } else { break; } } while (dwRead != 0xFFFFFFFF); } } } if (vsh.Version1.uVersion >= VIEWSTATEHEADER_VERSION_3) { // Seek to read the rest of the header dlibMove.QuadPart = libStartPos.QuadPart + sizeof(vsh.Version1) + sizeof(vsh.Version2); pstm->Seek(dlibMove, STREAM_SEEK_SET, NULL); hr = pstm->Read(&vsh.Version3, sizeof(vsh.Version3), &cbRead); if (SUCCEEDED(hr) && sizeof(vsh.Version3) == cbRead && vsh.Version3.uOffsetGroup) { GROUP_PERSIST gp; dlibMove.QuadPart = libStartPos.QuadPart + vsh.Version3.uOffsetGroup; pstm->Seek(dlibMove, STREAM_SEEK_SET, NULL); hr = pstm->Read(&gp, sizeof(gp), &cbRead); if (SUCCEEDED(hr) && sizeof(gp) == cbRead) { _guidGroupID = gp.guidGroupID; _scidDetails = gp.scidDetails; } } _fFirstViewed = FALSE; } ///////////////////////////////////////////////////////////////////////////////////// // ***** NEW Data ***** // 1) Add a version to the VIEWSTATEHEADER // 2) Add a version to VIEWSTATEHEADER_VERSION_* // 3) Check that version here ///////////////////////////////////////////////////////////////////////////////////// } } return hr; } HRESULT CViewState::SaveColumns(CDefView* pdv, IStream* pstm) { HRESULT hr; USHORT uOffset; VIEWSTATEHEADER vsh = {0}; ULARGE_INTEGER libStartPos = {0}; LARGE_INTEGER dlibMove = {0}; // No point in persisting, if there aren't any columns around. // this is true for folders that are just opened and closed if (!pdv->_psd && !pdv->_pshf2 && !pdv->HasCB()) { return S_FALSE; } // First, we persist a known bad quantity, just in case we wax the stream pstm->Seek(g_li0, STREAM_SEEK_CUR, &libStartPos); hr = pstm->Write(&vsh, sizeof(vsh), NULL); if (SUCCEEDED(hr)) { vsh.Version1.dwSignature = VIEWSTATEHEADER_SIGNATURE; vsh.Version1.uVersion = VIEWSTATEHEADER_VERSION_CURRENT; vsh.Version1.uCols = _hdsaColumnOrder? (UINT) DSA_GetItemCount(_hdsaColumnOrder) : 0; uOffset = sizeof(VIEWSTATEHEADER); // No point in persisting if we don't have any columns if (vsh.Version1.uCols) { // Note- dependent on DSA storing data internally as byte-packed. if (_hdsaColumnOrder) { vsh.Version1.uOffsetColOrder = uOffset; uOffset += (USHORT)(sizeof(UINT) * DSA_GetItemCount(_hdsaColumnOrder)); hr = pstm->Write(DSA_GetItemPtr(_hdsaColumnOrder, 0), sizeof(UINT) * DSA_GetItemCount(_hdsaColumnOrder), NULL); } if (_hdsaColumnWidths && SUCCEEDED(hr)) { vsh.Version1.uOffsetWidths = uOffset; uOffset += (USHORT)(sizeof(USHORT) * DSA_GetItemCount(_hdsaColumnWidths)); hr = pstm->Write(DSA_GetItemPtr(_hdsaColumnWidths, 0), sizeof(USHORT) * DSA_GetItemCount(_hdsaColumnWidths), NULL); } if (_hdsaColumnStates && SUCCEEDED(hr)) { vsh.Version2.uOffsetColStates = uOffset; uOffset += (USHORT)(sizeof(DWORD) * DSA_GetItemCount(_hdsaColumnStates)); pstm->Write(DSA_GetItemPtr(_hdsaColumnStates, 0), sizeof(DWORD) * DSA_GetItemCount(_hdsaColumnStates), NULL); } } if (SUCCEEDED(hr)) { GROUP_PERSIST gp = {0}; vsh.Version3.uOffsetGroup = uOffset; uOffset += sizeof(GROUP_PERSIST); if (pdv->_fGroupView) { gp.guidGroupID = _guidGroupID; gp.scidDetails = _scidDetails; } hr = pstm->Write(&gp, sizeof(gp), NULL); } ///////////////////////////////////////////////////////////////////////////////////// // ***** NEW Data ***** // 1) Add a version to the VIEWSTATEHEADER // 2) Add a version to VIEWSTATEHEADER_VERSION_* // 3) Add a "Loader" for your value // 4) Set the offset to uOffset. // 5) Write your data. // 6) Update the running total of dwOffset. ///////////////////////////////////////////////////////////////////////////////////// dlibMove.QuadPart = libStartPos.QuadPart; // Store off the current position pstm->Seek(g_li0, STREAM_SEEK_CUR, &libStartPos); // Move to the beginning pstm->Seek(dlibMove, STREAM_SEEK_SET, NULL); // Write out the correct header hr = pstm->Write(&vsh, sizeof(vsh), NULL); if (SUCCEEDED(hr)) { // Reset the current pos dlibMove.QuadPart = libStartPos.QuadPart; pstm->Seek(dlibMove, STREAM_SEEK_SET, NULL); } } return hr; } BOOL CViewState::AppendColumn(UINT uCol, USHORT uWidth, INT uOrder) { if (_hdsaColumnOrder == NULL || _hdsaColumnWidths == NULL) { return FALSE; } // Slide every index above this one up for (INT u = 0; u < DSA_GetItemCount(_hdsaColumnOrder); u++) { UINT *p = (UINT *) DSA_GetItemPtr(_hdsaColumnOrder, u); if (!p) break; // safety... if (*p >= uCol) (*p)++; } DSA_AppendItem(_hdsaColumnWidths, &uWidth); DSA_AppendItem(_hdsaColumnOrder, &uOrder); // maybe we should store column ordering as absolute numbers return TRUE; } BOOL CViewState::RemoveColumn(UINT uCol) { if (_hdsaColumnWidths == NULL || _hdsaColumnWidths == NULL) { return FALSE; } if ((int)uCol >= DSA_GetItemCount(_hdsaColumnWidths)) return FALSE; // Slide every index above this one down for (INT u = 0; u < DSA_GetItemCount(_hdsaColumnOrder); u++) { UINT *p = (UINT *) DSA_GetItemPtr(_hdsaColumnOrder, u); if (!p) break; // safety... if (*p > uCol) (*p)--; } DSA_DeleteItem(_hdsaColumnWidths, uCol); DSA_DeleteItem(_hdsaColumnOrder, uCol); return TRUE; } UINT CViewState::GetColumnWidth(UINT uCol, UINT uDefWid) { if (!_hdsaColumnWidths) return uDefWid; USHORT uWidth = 0; if (uCol < (UINT) DSA_GetItemCount(_hdsaColumnWidths)) { DSA_GetItem(_hdsaColumnWidths, uCol, &uWidth); } return uWidth ? uWidth : uDefWid; // disallow zero width columns } BOOL CViewState::SyncColumnOrder(CDefView* pdv, BOOL fSetListViewState) { UINT cCols = pdv->_GetHeaderCount(); if (fSetListViewState) { if (!_hdsaColumnOrder) return FALSE; if (cCols != (UINT) DSA_GetItemCount(_hdsaColumnOrder)) { // this is a normal case if a folder is opened and there is no saved state. no need to spew. return TRUE; } UINT *pCols = (UINT *)LocalAlloc(LPTR, cCols * sizeof(*pCols)); if (pCols) { for (UINT u = 0; u < cCols; u++) { DSA_GetItem(_hdsaColumnOrder, u, pCols + u); } ListView_SetColumnOrderArray(pdv->_hwndListview, cCols, pCols); LocalFree(pCols); } } else { BOOL bDefaultOrder = TRUE; if (cCols) { if (!_hdsaColumnOrder) _hdsaColumnOrder = DSA_Create(sizeof(UINT), 6); if (_hdsaColumnOrder) { UINT *pCols = (UINT *)LocalAlloc(LPTR, cCols * sizeof(*pCols)); if (pCols) { ListView_GetColumnOrderArray(pdv->_hwndListview, cCols, pCols); DSA_DeleteAllItems(_hdsaColumnOrder); for (UINT u = 0; u < cCols; u++) { DSA_AppendItem(_hdsaColumnOrder, &pCols[u]); if (pCols[u] != u) { bDefaultOrder = FALSE; } } LocalFree(pCols); } } } return bDefaultOrder; } return TRUE; } BOOL CViewState::SyncColumnWidths(CDefView* pdv, BOOL fSetListViewState) { UINT cCols = pdv->_GetHeaderCount(); if (fSetListViewState) { return FALSE; } else { USHORT us; LV_COLUMN lvc; BOOL bOk = TRUE; if (!cCols) return TRUE; HDSA dsaNewWidths = DSA_Create(sizeof(USHORT), cCols); if (!dsaNewWidths) return TRUE; for (UINT u = 0; u < cCols && bOk; ++u) { lvc.mask = LVCF_WIDTH; bOk = ListView_GetColumn(pdv->_hwndListview, u, &lvc); us = (USHORT) lvc.cx; // make sure its a short DSA_AppendItem(dsaNewWidths, &us); // TraceMsg(TF_DEFVIEW, " saving col %d width of %d", u, us); } if (bOk) { if (_hdsaColumnWidths) DSA_Destroy(_hdsaColumnWidths); _hdsaColumnWidths = dsaNewWidths; } else DSA_Destroy(dsaNewWidths); return !bOk; } } BOOL CViewState::SyncColumnStates(CDefView* pdv, BOOL fSetListViewstate) { if (fSetListViewstate) { return FALSE; } else { // Save off Column States if (_hdsaColumnStates) { DSA_Destroy(_hdsaColumnStates); _hdsaColumnStates = NULL; } UINT cCol = GetColumnCount(); if (cCol) { DWORD i; _hdsaColumnStates = DSA_Create(sizeof(DWORD), 5); if (_hdsaColumnStates) { for (i = 0; i < cCol; i++) { if (pdv->_IsDetailsColumn(i)) DSA_AppendItem(_hdsaColumnStates, &i); } i = 0xFFFFFFFF; // Terminating Sentinal DSA_AppendItem(_hdsaColumnStates,&i); } } } return TRUE; } // Syncronizes ListView with the current View State. // TRUE means take the view state object and set it into the listview. HRESULT CViewState::Sync(CDefView* pdv, BOOL fSetListViewState) { SyncColumnWidths(pdv, fSetListViewState); SyncColumnOrder(pdv, fSetListViewState); SyncColumnStates(pdv, fSetListViewState); if (fSetListViewState) { // Only do this the first time. if (pdv->_pcat == NULL) { if (_fFirstViewed) { // See if the desktop.ini specifies one pdv->_LoadCategory(&_guidGroupID); if (IsEqualGUID(_guidGroupID, GUID_NULL)) { ICategoryProvider* pcp; if (SUCCEEDED(pdv->_pshf->CreateViewObject(NULL, IID_PPV_ARG(ICategoryProvider, &pcp)))) { pcp->GetDefaultCategory(&_guidGroupID, &_scidDetails); pcp->Release(); } } } if (!IsEqualGUID(_guidGroupID, GUID_NULL) || !IsEqualGUID(_scidDetails.fmtid, GUID_NULL)) pdv->_CategorizeOnGUID(&_guidGroupID, &_scidDetails); } // this is only needed to sort the items who's positions are not known // it would be nice to optimize this case and only sort then pdv->_Sort(); SyncPositions(pdv); } else { // Take what Listview has, and save it to me. _ViewMode = pdv->_fs.ViewMode; _ptScroll.x = (SHORT) GetScrollPos(pdv->_hwndListview, SB_HORZ); _ptScroll.y = (SHORT) GetScrollPos(pdv->_hwndListview, SB_VERT); } return S_OK; }