/* * CiderPress * Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved. * See the file LICENSE for distribution terms. */ /* * Implementation of list control showing archive contents. */ #include "stdafx.h" #include "Main.h" #include "ContentList.h" const LPARAM kDescendingFlag = 0x0100; BEGIN_MESSAGE_MAP(ContentList, CListCtrl) ON_WM_CREATE() ON_WM_DESTROY() ON_WM_SYSCOLORCHANGE() //ON_WM_MOUSEWHEEL() ON_NOTIFY_REFLECT(NM_DBLCLK, OnDoubleClick) ON_NOTIFY_REFLECT(NM_RCLICK, OnRightClick) ON_NOTIFY_REFLECT(LVN_COLUMNCLICK, OnColumnClick) ON_NOTIFY_REFLECT(LVN_GETDISPINFO, OnGetDispInfo) END_MESSAGE_MAP() #if 0 afx_msg BOOL ContentList::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt) { LOGI("MOUSE WHEEL"); return CWnd::OnMouseWheel(nFlags, zDelta, pt); // return TRUE; } #endif BOOL ContentList::PreCreateWindow(CREATESTRUCT& cs) { if (!CListCtrl::PreCreateWindow(cs)) return FALSE; cs.style &= ~LVS_TYPEMASK; cs.style |= LVS_REPORT; cs.style |= LVS_SHOWSELALWAYS; cs.dwExStyle |= WS_EX_CLIENTEDGE; return TRUE; } void ContentList::PostNcDestroy(void) { LOGI("ContentList PostNcDestroy"); delete this; } static inline int MaxVal(int a, int b) { return a > b ? a : b; } int ContentList::OnCreate(LPCREATESTRUCT lpcs) { CString colHdrs[kNumVisibleColumns] = { L"Pathname", L"Type", L"Aux", L"Mod Date", L"Format", L"Size", L"Ratio", L"Packed", L"Access" }; // these should come from string table, not hard-coded static int colFmt[kNumVisibleColumns] = { LVCFMT_LEFT, LVCFMT_LEFT, LVCFMT_LEFT, LVCFMT_LEFT, LVCFMT_LEFT, LVCFMT_RIGHT, LVCFMT_RIGHT, LVCFMT_RIGHT, LVCFMT_LEFT }; if (CListCtrl::OnCreate(lpcs) == -1) return -1; /* * Create all of the columns with an initial width of 1, then set * them to the correct values with NewColumnWidths() (which handles * defaulted values). */ for (int i = 0; i < kNumVisibleColumns; i++) InsertColumn(i, colHdrs[i], colFmt[i], 1); NewColumnWidths(); /* add images for list; this MUST be loaded before header images */ LoadListImages(); SetImageList(&fListImageList, LVSIL_SMALL); /* add our up/down arrow bitmaps */ LoadHeaderImages(); CHeaderCtrl* pHeader = GetHeaderCtrl(); if (pHeader == NULL) LOGW("GLITCH: couldn't get header ctrl"); ASSERT(pHeader != NULL); pHeader->SetImageList(&fHdrImageList); /* load the data and sort it */ if (LoadData() != 0) { MessageBox(L"Not all entries were loaded.", L"Error", MB_OK | MB_ICONSTOP); /* keep going with what we've got; the error only affects display */ } NewSortOrder(); /* grab the focus so we get keyboard and mouse wheel messages */ SetFocus(); /* highlight/select entire line, not just filename */ ListView_SetExtendedListViewStyleEx(m_hWnd, LVS_EX_FULLROWSELECT, LVS_EX_FULLROWSELECT); return 0; } void ContentList::OnDestroy(void) { LOGD("ContentList OnDestroy"); ExportColumnWidths(); CListCtrl::OnDestroy(); } void ContentList::OnSysColorChange(void) { fHdrImageList.DeleteImageList(); LoadHeaderImages(); } void ContentList::OnColumnClick(NMHDR* pnmh, LRESULT* pResult) { NM_LISTVIEW* pnmlv = (NM_LISTVIEW*) pnmh; LOGD("ContentList OnColumnClick"); if (fpLayout->GetSortColumn() == pnmlv->iSubItem) fpLayout->SetAscending(!fpLayout->GetAscending()); else { fpLayout->SetSortColumn(pnmlv->iSubItem); fpLayout->SetAscending(true); } NewSortOrder(); *pResult = 0; } void ContentList::ExportColumnWidths(void) { //LOGI("ExportColumnWidths"); for (int i = 0; i < kNumVisibleColumns; i++) fpLayout->SetColumnWidth(i, GetColumnWidth(i)); } void ContentList::NewColumnWidths(void) { for (int i = 0; i < kNumVisibleColumns; i++) { int width = fpLayout->GetColumnWidth(i); if (width == ColumnLayout::kWidthDefaulted) { width = GetDefaultWidth(i); LOGD("Defaulting width %d to %d", i, width); fpLayout->SetColumnWidth(i, width); } SetColumnWidth(i, width); } } void ContentList::Reload(bool saveSelection) { LOGI("Reloading ContentList"); CWaitCursor waitc; // fInvalid = false; fpArchive->ClearReloadFlag(); long* savedSel = NULL; long selCount = 0; if (saveSelection) { /* get the serials for the current selection (if any) */ savedSel = GetSelectionSerials(&selCount); } /* get the item that's currently at the top of the page */ int top = GetTopIndex(); int bottom = top + GetCountPerPage() -1; /* reload the list */ LoadData(); NewSortOrder(); if (savedSel != NULL) { /* restore the selection */ RestoreSelection(savedSel, selCount); delete[] savedSel; } /* try to put us back in the same place */ EnsureVisible(bottom, false); EnsureVisible(top, false); } long* ContentList::GetSelectionSerials(long* pSelCount) { long* savedSel = NULL; long maxCount; maxCount = GetSelectedCount(); LOGD("GetSelectionSerials (maxCount=%d)", maxCount); if (maxCount > 0) { savedSel = new long[maxCount]; int idx = 0; POSITION posn; posn = GetFirstSelectedItemPosition(); ASSERT(posn != NULL); if (posn == NULL) return NULL; while (posn != NULL) { int num = GetNextSelectedItem(posn); GenericEntry* pEntry = (GenericEntry*) GetItemData(num); if (idx == maxCount) { ASSERT(false); break; } savedSel[idx++] = pEntry->GetSelectionSerial(); } ASSERT(idx == maxCount); } *pSelCount = maxCount; return savedSel; } void ContentList::RestoreSelection(const long* savedSel, long selCount) { LOGI("RestoreSelection (selCount=%d)", selCount); if (savedSel == NULL) return; int i, j; for (i = GetItemCount()-1; i >= 0; i--) { GenericEntry* pEntry = (GenericEntry*) GetItemData(i); for (j = 0; j < selCount; j++) { if (pEntry->GetSelectionSerial() == savedSel[j] && pEntry->GetSelectionSerial() != -1) { /* match! */ if (SetItemState(i, LVIS_SELECTED, LVIS_SELECTED) == FALSE) { LOGW("WHOA: unable to set selected on item=%d", i); } break; } } } } void ContentList::NewSortOrder(void) { CWaitCursor wait; // automatically changes mouse to hourglass int column; column = fpLayout->GetSortColumn(); if (!fpLayout->GetAscending()) column |= kDescendingFlag; SetSortIcon(); SortItems(CompareFunc, column); } /*static*/ void ContentList::MakeFileTypeDisplayString(const GenericEntry* pEntry, WCHAR* buf) { bool isDir = pEntry->GetRecordKind() == GenericEntry::kRecordKindVolumeDir || pEntry->GetRecordKind() == GenericEntry::kRecordKindDirectory; if (pEntry->GetSourceFS() == DiskImg::kFormatMacHFS && isDir) { /* HFS directories don't have types; fake it */ wcscpy(buf, L"DIR/"); } else if (!(pEntry->GetFileType() >= 0 && pEntry->GetFileType() <= 0xff)) { /* oversized type; assume it's HFS */ WCHAR typeBuf[kFileTypeBufLen]; MakeMacTypeString(pEntry->GetFileType(), typeBuf); switch (pEntry->GetRecordKind()) { case GenericEntry::kRecordKindFile: wcscpy(buf, typeBuf); break; case GenericEntry::kRecordKindForkedFile: wsprintf(buf, L"%ls+", typeBuf); break; case GenericEntry::kRecordKindUnknown: // shouldn't happen wsprintf(buf, L"%ls-", typeBuf); break; case GenericEntry::kRecordKindVolumeDir: case GenericEntry::kRecordKindDirectory: case GenericEntry::kRecordKindDisk: default: ASSERT(FALSE); wcscpy(buf, L"!!!"); break; } } else { /* typical ProDOS-style stuff */ switch (pEntry->GetRecordKind()) { case GenericEntry::kRecordKindVolumeDir: case GenericEntry::kRecordKindDirectory: wsprintf(buf, L"%ls/", pEntry->GetFileTypeString()); break; case GenericEntry::kRecordKindFile: wsprintf(buf, L"%ls", pEntry->GetFileTypeString()); break; case GenericEntry::kRecordKindForkedFile: wsprintf(buf, L"%ls+", pEntry->GetFileTypeString()); break; case GenericEntry::kRecordKindDisk: wcscpy(buf, L"Disk"); break; case GenericEntry::kRecordKindUnknown: // usually a GSHK-archived empty data file does this wsprintf(buf, L"%ls-", pEntry->GetFileTypeString()); break; default: ASSERT(FALSE); wcscpy(buf, L"!!!"); break; } } } /*static*/ void ContentList::MakeMacTypeString(unsigned long val, WCHAR* buf) { /* expand longword with ASCII type bytes */ buf[0] = (unsigned char) (val >> 24); buf[1] = (unsigned char) (val >> 16); buf[2] = (unsigned char) (val >> 8); buf[3] = (unsigned char) val; buf[4] = '\0'; /* sanitize */ while (*buf != '\0') { *buf = DiskImg::MacToASCII((unsigned char)*buf); buf++; } } /*static*/ void ContentList::MakeAuxTypeDisplayString(const GenericEntry* pEntry, WCHAR* buf) { bool isDir = pEntry->GetRecordKind() == GenericEntry::kRecordKindVolumeDir || pEntry->GetRecordKind() == GenericEntry::kRecordKindDirectory; if (pEntry->GetSourceFS() == DiskImg::kFormatMacHFS && isDir) { /* HFS directories don't have types; fake it */ wcscpy(buf, L" "); } else if (!(pEntry->GetFileType() >= 0 && pEntry->GetFileType() <= 0xff)) { /* oversized type; assume it's HFS */ MakeMacTypeString(pEntry->GetAuxType(), buf); } else { if (pEntry->GetRecordKind() == GenericEntry::kRecordKindDisk) wsprintf(buf, L"%I64dk", pEntry->GetUncompressedLen() / 1024); else wsprintf(buf, L"$%04lX", pEntry->GetAuxType()); } } void ContentList::MakeRatioDisplayString(const GenericEntry* pEntry, WCHAR* buf, int* pPerc) { LONGLONG totalLen, totalCompLen; totalLen = pEntry->GetUncompressedLen(); totalCompLen = pEntry->GetCompressedLen(); if ((!totalLen && totalCompLen) || (totalLen && !totalCompLen)) { wcscpy(buf, L"---"); /* weird */ *pPerc = -1; } else if (totalLen < totalCompLen) { wcscpy(buf, L">100%"); /* compression failed? */ *pPerc = 101; } else { *pPerc = ComputePercent(totalCompLen, totalLen); wsprintf(buf, L"%d%%", *pPerc); } } void ContentList::OnGetDispInfo(NMHDR* pnmh, LRESULT* pResult) { static const WCHAR kAccessBits[] = L"dnb iwr"; LV_DISPINFO* plvdi = (LV_DISPINFO*) pnmh; CString str; if (fpArchive->GetReloadFlag()) { wcscpy(plvdi->item.pszText, L""); *pResult = 0; return; } //LOGI("OnGetDispInfo"); if (plvdi->item.mask & LVIF_TEXT) { GenericEntry* pEntry = (GenericEntry*) plvdi->item.lParam; //GenericEntry* pEntry = fpArchive->GetEntry(plvdi->item.iItem); switch (plvdi->item.iSubItem) { case 0: // pathname if ((int) wcslen(pEntry->GetDisplayName()) > plvdi->item.cchTextMax) { // looks like current limit is 264 chars, which we could hit wcsncpy(plvdi->item.pszText, pEntry->GetDisplayName(), plvdi->item.cchTextMax); plvdi->item.pszText[plvdi->item.cchTextMax-1] = '\0'; } else { wcscpy(plvdi->item.pszText, pEntry->GetDisplayName()); } #if 0 // no longer needed -- "display names" are converted to Unicode /* * Sanitize the string. This is really only necessary for * HFS, which has 8-bit "Macintosh Roman" filenames. The Win32 * controls can deal with it, but it looks better if we massage * it a little. */ { WCHAR* str = plvdi->item.pszText; while (*str != '\0') { *str = DiskImg::MacToASCII((unsigned char) (*str)); str++; } } #endif break; case 1: // type MakeFileTypeDisplayString(pEntry, plvdi->item.pszText); break; case 2: // auxtype MakeAuxTypeDisplayString(pEntry, plvdi->item.pszText); break; case 3: // mod date { CString modDate; FormatDate(pEntry->GetModWhen(), &modDate); ::lstrcpy(plvdi->item.pszText, (LPCWSTR) modDate); } break; case 4: // format ASSERT(pEntry->GetFormatStr() != NULL); wcscpy(plvdi->item.pszText, pEntry->GetFormatStr()); break; case 5: // size wsprintf(plvdi->item.pszText, L"%I64d", pEntry->GetUncompressedLen()); break; case 6: // ratio int crud; MakeRatioDisplayString(pEntry, plvdi->item.pszText, &crud); break; case 7: // packed wsprintf(plvdi->item.pszText, L"%I64d", pEntry->GetCompressedLen()); break; case 8: // access WCHAR bitLabels[sizeof(kAccessBits)]; int i, j, mask; for (i = 0, j = 0, mask = 0x80; i < 8; i++, mask >>= 1) { if (pEntry->GetAccess() & mask) bitLabels[j++] = kAccessBits[i]; } bitLabels[j] = '\0'; ASSERT(j < sizeof(bitLabels)); //::sprintf(plvdi->item.pszText, "0x%02x", pEntry->GetAccess()); wcscpy(plvdi->item.pszText, bitLabels); break; case 9: // NuRecordIdx [hidden] break; default: ASSERT(false); break; } } //if (plvdi->item.mask & LVIF_IMAGE) { // LOGI("IMAGE req item=%d subitem=%d", // plvdi->item.iItem, plvdi->item.iSubItem); //} *pResult = 0; } /* * Helper functions for sort routine. */ static inline int CompareUnsignedLong(uint32_t u1, uint32_t u2) { if (u1 < u2) return -1; else if (u1 > u2) return 1; else return 0; } static inline int CompareLONGLONG(LONGLONG u1, LONGLONG u2) { if (u1 < u2) return -1; else if (u1 > u2) return 1; else return 0; } static inline int CompareTime(time_t t1, time_t t2) { if (t1 < t2) return -1; else if (t1 > t2) return 1; else return 0; } int CALLBACK ContentList::CompareFunc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort) { const GenericEntry* pEntry1 = (const GenericEntry*) lParam1; const GenericEntry* pEntry2 = (const GenericEntry*) lParam2; WCHAR tmpBuf1[16]; // needs >= 5 for file type compare, and WCHAR tmpBuf2[16]; // >= 7 for ratio string int result; /* for descending order, flip the parameters */ if (lParamSort & kDescendingFlag) { const GenericEntry* tmp; lParamSort &= ~(kDescendingFlag); tmp = pEntry1; pEntry1 = pEntry2; pEntry2 = tmp; } switch (lParamSort) { case 0: // pathname result = wcsicmp(pEntry1->GetDisplayName(), pEntry2->GetDisplayName()); break; case 1: // file type MakeFileTypeDisplayString(pEntry1, tmpBuf1); MakeFileTypeDisplayString(pEntry2, tmpBuf2); result = wcsicmp(tmpBuf1, tmpBuf2); if (result != 0) break; /* else fall through to case 2 */ case 2: // aux type if (pEntry1->GetRecordKind() == GenericEntry::kRecordKindDisk) { if (pEntry2->GetRecordKind() == GenericEntry::kRecordKindDisk) { result = pEntry1->GetAuxType() - pEntry2->GetAuxType(); } else { result = -1; } } else if (pEntry2->GetRecordKind() == GenericEntry::kRecordKindDisk) { result = 1; } else { result = pEntry1->GetAuxType() - pEntry2->GetAuxType(); } break; case 3: // mod date result = CompareTime(pEntry1->GetModWhen(), pEntry2->GetModWhen()); break; case 4: // format result = ::lstrcmp(pEntry1->GetFormatStr(), pEntry2->GetFormatStr()); break; case 5: // size result = CompareLONGLONG(pEntry1->GetUncompressedLen(), pEntry2->GetUncompressedLen()); break; case 6: // ratio int perc1, perc2; MakeRatioDisplayString(pEntry1, tmpBuf1, &perc1); MakeRatioDisplayString(pEntry2, tmpBuf2, &perc2); result = perc1 - perc2; break; case 7: // packed result = CompareLONGLONG(pEntry1->GetCompressedLen(), pEntry2->GetCompressedLen()); break; case 8: // access result = CompareUnsignedLong(pEntry1->GetAccess(), pEntry2->GetAccess()); break; case kNumVisibleColumns: // file-order sort default: result = pEntry1->GetIndex() - pEntry2->GetIndex(); break; } return result; } int ContentList::LoadData(void) { GenericEntry* pEntry; LV_ITEM lvi; int dirCount = 0; int idx = 0; DeleteAllItems(); // for Reload case pEntry = fpArchive->GetEntries(); while (pEntry != NULL) { pEntry->SetIndex(idx); lvi.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_PARAM; lvi.iItem = idx++; lvi.iSubItem = 0; if (pEntry->GetDamaged()) lvi.iImage = kListIconDamaged; else if (pEntry->GetSuspicious()) lvi.iImage = kListIconSuspicious; else if (pEntry->GetHasNonEmptyComment()) lvi.iImage = kListIconNonEmptyComment; else if (pEntry->GetHasComment()) lvi.iImage = kListIconComment; else lvi.iImage = kListIconNone; lvi.pszText = LPSTR_TEXTCALLBACK; lvi.lParam = (LPARAM) pEntry; if (InsertItem(&lvi) == -1) { ASSERT(false); return -1; } pEntry = pEntry->GetNext(); } LOGI("ContentList got %d entries (%d files + %d unseen directories)", idx, idx - dirCount, dirCount); return 0; } int ContentList::GetDefaultWidth(int col) { int retval; switch (col) { case 0: // pathname retval = 200; break; case 1: // type (need "$XY" and long HFS types) retval = MaxVal(GetStringWidth(L"XXMMMM+"), GetStringWidth(L"XXType")); break; case 2: // auxtype (hex or long HFS type) retval = MaxVal(GetStringWidth(L"XX$CCCC"), GetStringWidth(L"XXAux")); break; case 3: // mod date retval = GetStringWidth(L"XX88-MMM-88 88:88"); break; case 4: // format retval = GetStringWidth(L"XXUncompr"); break; case 5: // uncompressed size retval = GetStringWidth(L"XX88888888"); break; case 6: // ratio retval = MaxVal(GetStringWidth(L"XXRatio"), GetStringWidth(L"XX100%")); break; case 7: // packed retval = GetStringWidth(L"XX88888888"); break; case 8: // access retval = MaxVal(GetStringWidth(L"XXAccess"), GetStringWidth(L"XXdnbiwr")); break; default: ASSERT(false); retval = 0; } return retval; } void ContentList::SetSortIcon(void) { CHeaderCtrl* pHeader = GetHeaderCtrl(); ASSERT(pHeader != NULL); HDITEM curItem; /* update all column headers */ for (int i = 0; i < kNumVisibleColumns; i++) { curItem.mask = HDI_IMAGE | HDI_FORMAT; pHeader->GetItem(i, &curItem); if (fpLayout->GetSortColumn() != i) { curItem.fmt &= ~(HDF_IMAGE | HDF_BITMAP_ON_RIGHT); } else { //LOGI(" Sorting on %d", i); curItem.fmt |= HDF_IMAGE | HDF_BITMAP_ON_RIGHT; if (fpLayout->GetAscending()) curItem.iImage = 0; else curItem.iImage = 1; } pHeader->SetItem(i, &curItem); } } void ContentList::OnDoubleClick(NMHDR*, LRESULT* pResult) { /* test */ DWORD dwPos = ::GetMessagePos(); CPoint point ((int) LOWORD(dwPos), (int) HIWORD(dwPos)); ScreenToClient(&point); int idx = HitTest(point); if (idx != -1) { CString str = GetItemText(idx, 0); LOGI("%ls was double-clicked", (LPCWSTR) str); } ((MainWindow*) ::AfxGetMainWnd())->HandleDoubleClick(); *pResult = 0; } void ContentList::OnRightClick(NMHDR*, LRESULT* pResult) { /* * -The first item in the menu performs the double-click action on the * -item clicked on. The rest of the menu is simply a mirror of the items * -in the "Actions" menu. To make this work, we let the main window handle * -everything, but save a copy of the index of the menu item that was * -clicked on. * * [We do this differently now?? ++ATM 20040722] */ DWORD dwPos = ::GetMessagePos(); CPoint point ((int) LOWORD(dwPos), (int) HIWORD(dwPos)); ScreenToClient(&point); #if 0 int idx = HitTest(point); if (idx != -1) { CString str = GetItemText(idx, 0); LOGI("%ls was right-clicked", (LPCWSTR) str); //fRightClickItem = idx; #else { #endif CMenu menu; menu.LoadMenu(IDR_RIGHTCLICKMENU); CMenu* pContextMenu = menu.GetSubMenu(0); ClientToScreen(&point); pContextMenu->TrackPopupMenu(TPM_LEFTALIGN | TPM_LEFTBUTTON | TPM_RIGHTBUTTON, point.x, point.y, ::AfxGetMainWnd()); } *pResult = 0; } void ContentList::SelectAll(void) { int i; for (i = GetItemCount()-1; i >= 0; i--) { if (!SetItemState(i, LVIS_SELECTED, LVIS_SELECTED)) { LOGI("Glitch: SetItemState failed on %d", i); } } } void ContentList::InvertSelection(void) { int i, oldState; for (i = GetItemCount()-1; i >= 0; i--) { oldState = GetItemState(i, LVIS_SELECTED); if (!SetItemState(i, oldState ? 0 : LVIS_SELECTED, LVIS_SELECTED)) { LOGI("Glitch: SetItemState failed on %d", i); } } } void ContentList::SelectSubdirContents(void) { /* * We do the selection by prefix matching on the display name. This means * we do one pass through the list for the contents of a subdir, including * all of its subdirs. However, the subdirs we select as we're going will * be indistinguishable from subdirs selected by the user, which could * result in O(n^2) behavior. * * We mark the user's selection with LVIS_CUT, process them all, then go * back and clear all of the LVIS_CUT flags. Of course, if they select * the entire archive, we're approach O(n^2) anyway. If efficiency is a * problem we will need to sort the list, do some work, then sort it back * the way it was. * * This doesn't work for volume directories, because their display name * isn't quite right. That's okay for now -- we document that we don't * allow deletion of the volume directory. (We don't currently have a test * to see if a GenericEntry is a volume dir; might want to add one.) */ POSITION posn; posn = GetFirstSelectedItemPosition(); if (posn == NULL) { LOGI("SelectSubdirContents: nothing is selected"); return; } /* mark all selected items with LVIS_CUT */ while (posn != NULL) { int num = GetNextSelectedItem(/*ref*/ posn); SetItemState(num, LVIS_CUT, LVIS_CUT); } /* for each LVIS_CUT entry, select all prefix matches */ CString prefix; for (int i = GetItemCount()-1; i >= 0; i--) { GenericEntry* pEntry = (GenericEntry*) GetItemData(i); bool origSel; origSel = GetItemState(i, LVIS_CUT) != 0; if (origSel && (pEntry->GetRecordKind() == GenericEntry::kRecordKindDirectory || pEntry->GetRecordKind() == GenericEntry::kRecordKindVolumeDir)) { prefix = pEntry->GetDisplayName(); prefix += pEntry->GetFssep(); SelectSubdir(prefix); } // if (!SetItemState(i, oldState ? 0 : LVIS_SELECTED, LVIS_SELECTED)) { // LOGI("GLITCH: SetItemState failed on %d", i); // } } /* clear the LVIS_CUT flags */ posn = GetFirstSelectedItemPosition(); while (posn != NULL) { int num = GetNextSelectedItem(/*ref*/ posn); SetItemState(num, 0, LVIS_CUT); } } void ContentList::SelectSubdir(const WCHAR* displayPrefix) { LOGI(" ContentList selecting all in '%ls'", displayPrefix); int len = wcslen(displayPrefix); for (int i = GetItemCount()-1; i >= 0; i--) { GenericEntry* pEntry = (GenericEntry*) GetItemData(i); if (wcsnicmp(displayPrefix, pEntry->GetDisplayName(), len) == 0) SetItemState(i, LVIS_SELECTED, LVIS_SELECTED); } } void ContentList::ClearSelection(void) { for (int i = GetItemCount()-1; i >= 0; i--) SetItemState(i, 0, LVIS_SELECTED); } void ContentList::FindNext(const WCHAR* str, bool down, bool matchCase, bool wholeWord) { POSITION posn; int i, num; bool found = false; LOGI("FindNext '%ls' d=%d c=%d w=%d", str, down, matchCase, wholeWord); posn = GetFirstSelectedItemPosition(); num = GetNextSelectedItem(/*ref*/ posn); if (num < 0) { // num will be -1 if nothing is selected if (down) num = -1; else num = GetItemCount(); } LOGI(" starting search from entry %d", num); if (down) { for (i = num+1; i < GetItemCount(); i++) { found = CompareFindString(i, str, matchCase, wholeWord); if (found) break; } if (!found) { // wrap for (i = 0; i <= num; i++) { found = CompareFindString(i, str, matchCase, wholeWord); if (found) break; } } } else { for (i = num-1; i >= 0; i--) { found = CompareFindString(i, str, matchCase, wholeWord); if (found) break; } if (!found) { // wrap for (i = GetItemCount()-1; i >= num; i--) { found = CompareFindString(i, str, matchCase, wholeWord); if (found) break; } } } if (found) { LOGI("Found, i=%d", i); ClearSelection(); SetItemState(i, LVIS_SELECTED, LVIS_SELECTED); EnsureVisible(i, false); } else { LOGI("Not found"); MainWindow* pMain = (MainWindow*)::AfxGetMainWnd(); pMain->FailureBeep(); } } bool ContentList::CompareFindString(int num, const WCHAR* str, bool matchCase, bool wholeWord) { GenericEntry* pEntry = (GenericEntry*) GetItemData(num); char fssep = pEntry->GetFssep(); const WCHAR* (*pSubCompare)(const WCHAR* str, const WCHAR* subStr) = NULL; if (matchCase) pSubCompare = wcsstr; else pSubCompare = Stristr; if (wholeWord) { const WCHAR* src = pEntry->GetDisplayName(); const WCHAR* start = src; size_t strLen = wcslen(str); /* scan forward, looking for a match that starts & ends on fssep */ while (*start != '\0') { const WCHAR* match; match = (*pSubCompare)(start, str); if (match == NULL) break; if ((match == src || *(match-1) == fssep) && (match[strLen] == '\0' || match[strLen] == fssep)) { return true; } start++; } } else { if ((*pSubCompare)(pEntry->GetDisplayName(), str) != NULL) return true; } return false; }