mirror of
https://github.com/fadden/ciderpress.git
synced 2024-11-16 12:04:27 +00:00
1151 lines
29 KiB
C++
1151 lines
29 KiB
C++
|
/*
|
||
|
* 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)
|
||
|
{
|
||
|
WMSG0("MOUSE WHEEL\n");
|
||
|
return CWnd::OnMouseWheel(nFlags, zDelta, pt);
|
||
|
// return TRUE;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Put the window into "report" mode, and add a client edge since we're not
|
||
|
* using one on the frame window.
|
||
|
*/
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Auto-cleanup the object.
|
||
|
*/
|
||
|
void
|
||
|
ContentList::PostNcDestroy(void)
|
||
|
{
|
||
|
WMSG0("ContentList PostNcDestroy\n");
|
||
|
delete this;
|
||
|
}
|
||
|
|
||
|
static inline int
|
||
|
MaxVal(int a, int b)
|
||
|
{
|
||
|
return a > b ? a : b;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Create and populate list control.
|
||
|
*/
|
||
|
int
|
||
|
ContentList::OnCreate(LPCREATESTRUCT lpcs)
|
||
|
{
|
||
|
CString colHdrs[kNumVisibleColumns] = {
|
||
|
"Pathname", "Type", "Aux", "Mod Date",
|
||
|
"Format", "Size", "Ratio", "Packed", "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 == nil)
|
||
|
WMSG0("GLITCH: couldn't get header ctrl\n");
|
||
|
ASSERT(pHeader != NULL);
|
||
|
pHeader->SetImageList(&fHdrImageList);
|
||
|
|
||
|
/* load the data and sort it */
|
||
|
if (LoadData() != 0) {
|
||
|
MessageBox("Not all entries were loaded.", "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;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* If we're being shut down, save off the column width info before the window
|
||
|
* gets destroyed.
|
||
|
*/
|
||
|
void
|
||
|
ContentList::OnDestroy(void)
|
||
|
{
|
||
|
WMSG0("ContentList OnDestroy\n");
|
||
|
|
||
|
ExportColumnWidths();
|
||
|
CListCtrl::OnDestroy();
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* The system colors are changing; delete the image list and re-load it.
|
||
|
*/
|
||
|
void
|
||
|
ContentList::OnSysColorChange(void)
|
||
|
{
|
||
|
fHdrImageList.DeleteImageList();
|
||
|
LoadHeaderImages();
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* They've clicked on a header. Figure out what kind of sort order we want
|
||
|
* to use.
|
||
|
*/
|
||
|
void
|
||
|
ContentList::OnColumnClick(NMHDR* pnmh, LRESULT* pResult)
|
||
|
{
|
||
|
NM_LISTVIEW* pnmlv = (NM_LISTVIEW*) pnmh;
|
||
|
|
||
|
WMSG0("OnColumnClick!!\n");
|
||
|
|
||
|
if (fpLayout->GetSortColumn() == pnmlv->iSubItem)
|
||
|
fpLayout->SetAscending(!fpLayout->GetAscending());
|
||
|
else {
|
||
|
fpLayout->SetSortColumn(pnmlv->iSubItem);
|
||
|
fpLayout->SetAscending(true);
|
||
|
}
|
||
|
|
||
|
NewSortOrder();
|
||
|
*pResult = 0;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Copy the current column widths out to the Preferences object.
|
||
|
*/
|
||
|
void
|
||
|
ContentList::ExportColumnWidths(void)
|
||
|
{
|
||
|
//WMSG0("ExportColumnWidths\n");
|
||
|
for (int i = 0; i < kNumVisibleColumns; i++)
|
||
|
fpLayout->SetColumnWidth(i, GetColumnWidth(i));
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Call this when the column widths are changed programmatically (e.g. by
|
||
|
* the preferences page enabling or disabling columns).
|
||
|
*
|
||
|
* We want to set any defaulted entries to actual values so that, if the
|
||
|
* font properties change, column A doesn't resize when column B is tweaked
|
||
|
* in the Preferences dialog. (If it's still set to "default", then when
|
||
|
* we say "update all widths" the defaultedness will be re-evaluated.)
|
||
|
*/
|
||
|
void
|
||
|
ContentList::NewColumnWidths(void)
|
||
|
{
|
||
|
for (int i = 0; i < kNumVisibleColumns; i++) {
|
||
|
int width = fpLayout->GetColumnWidth(i);
|
||
|
if (width == ColumnLayout::kWidthDefaulted) {
|
||
|
width = GetDefaultWidth(i);
|
||
|
WMSG2("Defaulting width %d to %d\n", i, width);
|
||
|
fpLayout->SetColumnWidth(i, width);
|
||
|
}
|
||
|
SetColumnWidth(i, width);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#if 0 // replaced by GenericArchive reload flag
|
||
|
/*
|
||
|
* If we're in the middle of an update, invalidate the contents of the list
|
||
|
* so that we don't try to redraw from underlying storage that is no longer
|
||
|
* there.
|
||
|
*
|
||
|
* If we call DeleteAllItems the list will immediately blank itself. This
|
||
|
* rather sucks. Instead, we just mark it as invalid and have the "virtual"
|
||
|
* list goodies return empty strings. If the window has to redraw it won't
|
||
|
* do so properly, but most of the time it looks good and it beats flashing
|
||
|
* blank or crashing.
|
||
|
*/
|
||
|
void
|
||
|
ContentList::Invalidate(void)
|
||
|
{
|
||
|
fInvalid = true;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
/*
|
||
|
* The archive contents have changed. Reload the list from the
|
||
|
* GenericArchive.
|
||
|
*
|
||
|
* Reloading causes the current selection and view position to be lost. This
|
||
|
* is sort of annoying if all we did is add a comment, so we try to save the
|
||
|
* selection and reapply it. To do this correctly we need some sort of
|
||
|
* unique identifier so we can spot the records that have come back.
|
||
|
*
|
||
|
* Nothing in GenericArchive should be considered valid at this point.
|
||
|
*/
|
||
|
void
|
||
|
ContentList::Reload(bool saveSelection)
|
||
|
{
|
||
|
WMSG0("Reloading ContentList\n");
|
||
|
CWaitCursor waitc;
|
||
|
|
||
|
// fInvalid = false;
|
||
|
fpArchive->ClearReloadFlag();
|
||
|
|
||
|
long* savedSel = nil;
|
||
|
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 != nil) {
|
||
|
/* restore the selection */
|
||
|
RestoreSelection(savedSel, selCount);
|
||
|
delete[] savedSel;
|
||
|
}
|
||
|
|
||
|
/* try to put us back in the same place */
|
||
|
EnsureVisible(bottom, false);
|
||
|
EnsureVisible(top, false);
|
||
|
}
|
||
|
|
||
|
#if 1
|
||
|
/*
|
||
|
* Get the "selection serials" from the list of selected items.
|
||
|
*
|
||
|
* The caller is responsible for delete[]ing the return value.
|
||
|
*/
|
||
|
long*
|
||
|
ContentList::GetSelectionSerials(long* pSelCount)
|
||
|
{
|
||
|
long* savedSel = nil;
|
||
|
long maxCount;
|
||
|
|
||
|
maxCount = GetSelectedCount();
|
||
|
WMSG1("GetSelectionSerials (maxCount=%d)\n", maxCount);
|
||
|
|
||
|
if (maxCount > 0) {
|
||
|
savedSel = new long[maxCount];
|
||
|
int idx = 0;
|
||
|
|
||
|
POSITION posn;
|
||
|
posn = GetFirstSelectedItemPosition();
|
||
|
ASSERT(posn != nil);
|
||
|
if (posn == nil)
|
||
|
return nil;
|
||
|
while (posn != nil) {
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Restore the selection from the "savedSel" list.
|
||
|
*/
|
||
|
void
|
||
|
ContentList::RestoreSelection(const long* savedSel, long selCount)
|
||
|
{
|
||
|
WMSG1("RestoreSelection (selCount=%d)\n", selCount);
|
||
|
if (savedSel == nil)
|
||
|
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) {
|
||
|
WMSG1("WHOA: unable to set selected on item=%d\n", i);
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Call this when the sort order changes.
|
||
|
*/
|
||
|
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);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Get the file type display string.
|
||
|
*
|
||
|
* "buf" must be able to hold at least 4 characters plus the NUL (i.e. 5).
|
||
|
* Use kFileTypeBufLen.
|
||
|
*/
|
||
|
/*static*/ void
|
||
|
ContentList::MakeFileTypeDisplayString(const GenericEntry* pEntry, char* 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 */
|
||
|
::lstrcpy(buf, "DIR/");
|
||
|
} else if (!(pEntry->GetFileType() >= 0 && pEntry->GetFileType() <= 0xff))
|
||
|
{
|
||
|
/* oversized type; assume it's HFS */
|
||
|
char typeBuf[kFileTypeBufLen];
|
||
|
MakeMacTypeString(pEntry->GetFileType(), typeBuf);
|
||
|
|
||
|
switch (pEntry->GetRecordKind()) {
|
||
|
case GenericEntry::kRecordKindFile:
|
||
|
::lstrcpy(buf, typeBuf);
|
||
|
break;
|
||
|
case GenericEntry::kRecordKindForkedFile:
|
||
|
::sprintf(buf, "%s+", typeBuf);
|
||
|
break;
|
||
|
case GenericEntry::kRecordKindUnknown:
|
||
|
// shouldn't happen
|
||
|
::sprintf(buf, "%s-", typeBuf);
|
||
|
break;
|
||
|
case GenericEntry::kRecordKindVolumeDir:
|
||
|
case GenericEntry::kRecordKindDirectory:
|
||
|
case GenericEntry::kRecordKindDisk:
|
||
|
default:
|
||
|
ASSERT(FALSE);
|
||
|
::lstrcpy(buf, "!!!");
|
||
|
break;
|
||
|
}
|
||
|
} else {
|
||
|
/* typical ProDOS-style stuff */
|
||
|
switch (pEntry->GetRecordKind()) {
|
||
|
case GenericEntry::kRecordKindVolumeDir:
|
||
|
case GenericEntry::kRecordKindDirectory:
|
||
|
::sprintf(buf, "%s/", pEntry->GetFileTypeString());
|
||
|
break;
|
||
|
case GenericEntry::kRecordKindFile:
|
||
|
::sprintf(buf, "%s", pEntry->GetFileTypeString());
|
||
|
break;
|
||
|
case GenericEntry::kRecordKindForkedFile:
|
||
|
::sprintf(buf, "%s+", pEntry->GetFileTypeString());
|
||
|
break;
|
||
|
case GenericEntry::kRecordKindDisk:
|
||
|
::lstrcpy(buf, "Disk");
|
||
|
break;
|
||
|
case GenericEntry::kRecordKindUnknown:
|
||
|
// usually a GSHK-archived empty data file does this
|
||
|
::sprintf(buf, "%s-", pEntry->GetFileTypeString());
|
||
|
break;
|
||
|
default:
|
||
|
ASSERT(FALSE);
|
||
|
::lstrcpy(buf, "!!!");
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Convert an HFS file/creator type into a string.
|
||
|
*
|
||
|
* "buf" must be able to hold at least 4 characters plus the NUL. Use
|
||
|
* kFileTypeBufLen.
|
||
|
*/
|
||
|
/*static*/ void
|
||
|
ContentList::MakeMacTypeString(unsigned long val, char* 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(*buf);
|
||
|
buf++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Get the aux type display string.
|
||
|
*
|
||
|
* "buf" must be able to hold at least 5 characters plus the NUL (i.e. 6).
|
||
|
* Use kFileTypeBufLen.
|
||
|
*/
|
||
|
/*static*/ void
|
||
|
ContentList::MakeAuxTypeDisplayString(const GenericEntry* pEntry, char* 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 */
|
||
|
::lstrcpy(buf, " ");
|
||
|
} else if (!(pEntry->GetFileType() >= 0 && pEntry->GetFileType() <= 0xff))
|
||
|
{
|
||
|
/* oversized type; assume it's HFS */
|
||
|
MakeMacTypeString(pEntry->GetAuxType(), buf);
|
||
|
} else {
|
||
|
if (pEntry->GetRecordKind() == GenericEntry::kRecordKindDisk)
|
||
|
::sprintf(buf, "%dk", pEntry->GetUncompressedLen() / 1024);
|
||
|
else
|
||
|
::sprintf(buf, "$%04lX", pEntry->GetAuxType());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Generate the funky ratio display string. While we're at it, return a
|
||
|
* numeric value that we can sort on.
|
||
|
*
|
||
|
* "buf" must be able to hold at least 6 chars plus the NULL.
|
||
|
*/
|
||
|
void
|
||
|
ContentList::MakeRatioDisplayString(const GenericEntry* pEntry, char* buf,
|
||
|
int* pPerc)
|
||
|
{
|
||
|
LONGLONG totalLen, totalCompLen;
|
||
|
totalLen = pEntry->GetUncompressedLen();
|
||
|
totalCompLen = pEntry->GetCompressedLen();
|
||
|
|
||
|
if ((!totalLen && totalCompLen) || (totalLen && !totalCompLen)) {
|
||
|
::lstrcpy(buf, "---"); /* weird */
|
||
|
*pPerc = -1;
|
||
|
} else if (totalLen < totalCompLen) {
|
||
|
::lstrcpy(buf, ">100%"); /* compression failed? */
|
||
|
*pPerc = 101;
|
||
|
} else {
|
||
|
*pPerc = ComputePercent(totalCompLen, totalLen);
|
||
|
::sprintf(buf, "%d%%", *pPerc);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Return the value for a particular row and column.
|
||
|
*
|
||
|
* This gets called *a lot* while the list is being drawn, scrolled, etc.
|
||
|
* Don't do anything too expensive.
|
||
|
*/
|
||
|
void
|
||
|
ContentList::OnGetDispInfo(NMHDR* pnmh, LRESULT* pResult)
|
||
|
{
|
||
|
//static const char kAccessBits[] = "DNB IWR";
|
||
|
static const char kAccessBits[] = "dnb iwr";
|
||
|
LV_DISPINFO* plvdi = (LV_DISPINFO*) pnmh;
|
||
|
CString str;
|
||
|
|
||
|
if (fpArchive->GetReloadFlag()) {
|
||
|
::lstrcpy(plvdi->item.pszText, "");
|
||
|
*pResult = 0;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
//WMSG0("OnGetDispInfo\n");
|
||
|
|
||
|
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)strlen(pEntry->GetDisplayName()) > plvdi->item.cchTextMax) {
|
||
|
// looks like current limit is 264 chars, which we could hit
|
||
|
::strncpy(plvdi->item.pszText, pEntry->GetDisplayName(),
|
||
|
plvdi->item.cchTextMax);
|
||
|
plvdi->item.pszText[plvdi->item.cchTextMax-1] = '\0';
|
||
|
} else {
|
||
|
::lstrcpy(plvdi->item.pszText, pEntry->GetDisplayName());
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* 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.
|
||
|
*/
|
||
|
{
|
||
|
unsigned char* str = (unsigned char*) plvdi->item.pszText;
|
||
|
|
||
|
while (*str != '\0') {
|
||
|
*str = DiskImg::MacToASCII(*str);
|
||
|
str++;
|
||
|
}
|
||
|
}
|
||
|
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, (LPCTSTR) modDate);
|
||
|
}
|
||
|
break;
|
||
|
case 4: // format
|
||
|
ASSERT(pEntry->GetFormatStr() != nil);
|
||
|
::lstrcpy(plvdi->item.pszText, pEntry->GetFormatStr());
|
||
|
break;
|
||
|
case 5: // size
|
||
|
::sprintf(plvdi->item.pszText, "%ld", pEntry->GetUncompressedLen());
|
||
|
break;
|
||
|
case 6: // ratio
|
||
|
int crud;
|
||
|
MakeRatioDisplayString(pEntry, plvdi->item.pszText, &crud);
|
||
|
break;
|
||
|
case 7: // packed
|
||
|
::sprintf(plvdi->item.pszText, "%ld", pEntry->GetCompressedLen());
|
||
|
break;
|
||
|
case 8: // access
|
||
|
char 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());
|
||
|
::lstrcpy(plvdi->item.pszText, bitLabels);
|
||
|
break;
|
||
|
case 9: // NuRecordIdx [hidden]
|
||
|
break;
|
||
|
default:
|
||
|
ASSERT(false);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//if (plvdi->item.mask & LVIF_IMAGE) {
|
||
|
// WMSG2("IMAGE req item=%d subitem=%d\n",
|
||
|
// plvdi->item.iItem, plvdi->item.iSubItem);
|
||
|
//}
|
||
|
|
||
|
*pResult = 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Helper functions for sort routine.
|
||
|
*/
|
||
|
static inline
|
||
|
CompareUnsignedLong(unsigned long u1, unsigned long u2)
|
||
|
{
|
||
|
if (u1 < u2)
|
||
|
return -1;
|
||
|
else if (u1 > u2)
|
||
|
return 1;
|
||
|
else
|
||
|
return 0;
|
||
|
}
|
||
|
static inline
|
||
|
CompareLONGLONG(LONGLONG u1, LONGLONG u2)
|
||
|
{
|
||
|
if (u1 < u2)
|
||
|
return -1;
|
||
|
else if (u1 > u2)
|
||
|
return 1;
|
||
|
else
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Static comparison function for list sorting.
|
||
|
*/
|
||
|
int CALLBACK
|
||
|
ContentList::CompareFunc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
|
||
|
{
|
||
|
const GenericEntry* pEntry1 = (const GenericEntry*) lParam1;
|
||
|
const GenericEntry* pEntry2 = (const GenericEntry*) lParam2;
|
||
|
char tmpBuf1[16]; // needs >= 5 for file type compare, and
|
||
|
char 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 = ::stricmp(pEntry1->GetDisplayName(), pEntry2->GetDisplayName());
|
||
|
break;
|
||
|
case 1: // file type
|
||
|
MakeFileTypeDisplayString(pEntry1, tmpBuf1);
|
||
|
MakeFileTypeDisplayString(pEntry2, tmpBuf2);
|
||
|
result = ::stricmp(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 = CompareUnsignedLong(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;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Fill the columns with data from the archive entries. We use a "virtual"
|
||
|
* list control to avoid storing everything multiple times. However, we
|
||
|
* still create one item per entry so that the list control will do most
|
||
|
* of the sorting for us (otherwise we have to do the sorting ourselves).
|
||
|
*
|
||
|
* Someday we should probably move to a wholly virtual list view.
|
||
|
*/
|
||
|
int
|
||
|
ContentList::LoadData(void)
|
||
|
{
|
||
|
GenericEntry* pEntry;
|
||
|
LV_ITEM lvi;
|
||
|
int dirCount = 0;
|
||
|
int idx = 0;
|
||
|
|
||
|
DeleteAllItems(); // for Reload case
|
||
|
|
||
|
pEntry = fpArchive->GetEntries();
|
||
|
while (pEntry != nil) {
|
||
|
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();
|
||
|
}
|
||
|
|
||
|
WMSG3("ContentList got %d entries (%d files + %d unseen directories)\n",
|
||
|
idx, idx - dirCount, dirCount);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Return the default width for the specified column.
|
||
|
*/
|
||
|
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("XXMMM+"), GetStringWidth("XXType"));
|
||
|
retval = MaxVal(GetStringWidth("XXMMMM+"), GetStringWidth("XXType"));
|
||
|
break;
|
||
|
case 2: // auxtype (hex or long HFS type)
|
||
|
//retval = MaxVal(GetStringWidth("XX$8888"), GetStringWidth("XXAux"));
|
||
|
retval = MaxVal(GetStringWidth("XX$CCCC"), GetStringWidth("XXAux"));
|
||
|
break;
|
||
|
case 3: // mod date
|
||
|
retval = GetStringWidth("XX88-MMM-88 88:88");
|
||
|
break;
|
||
|
case 4: // format
|
||
|
retval = GetStringWidth("XXUncompr");
|
||
|
break;
|
||
|
case 5: // uncompressed size
|
||
|
retval = GetStringWidth("XX88888888");
|
||
|
break;
|
||
|
case 6: // ratio
|
||
|
retval = MaxVal(GetStringWidth("XXRatio"), GetStringWidth("XX100%"));
|
||
|
break;
|
||
|
case 7: // packed
|
||
|
retval = GetStringWidth("XX88888888");
|
||
|
break;
|
||
|
case 8: // access
|
||
|
retval = MaxVal(GetStringWidth("XXAccess"), GetStringWidth("XXdnbiwr"));
|
||
|
break;
|
||
|
default:
|
||
|
ASSERT(false);
|
||
|
retval = 0;
|
||
|
}
|
||
|
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Set the up/down sorting arrow as appropriate.
|
||
|
*/
|
||
|
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 {
|
||
|
//WMSG1(" Sorting on %d\n", i);
|
||
|
curItem.fmt |= HDF_IMAGE | HDF_BITMAP_ON_RIGHT;
|
||
|
if (fpLayout->GetAscending())
|
||
|
curItem.iImage = 0;
|
||
|
else
|
||
|
curItem.iImage = 1;
|
||
|
}
|
||
|
|
||
|
pHeader->SetItem(i, &curItem);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Handle a double-click on an item.
|
||
|
*
|
||
|
* The double-click should single-select the item, so we can throw it
|
||
|
* straight into the viewer. However, there are some uses for bulk
|
||
|
* double-clicking.
|
||
|
*/
|
||
|
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);
|
||
|
WMSG1("%s was double-clicked\n", str);
|
||
|
}
|
||
|
|
||
|
((MainWindow*) ::AfxGetMainWnd())->HandleDoubleClick();
|
||
|
*pResult = 0;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Handle a right-click on an item.
|
||
|
*
|
||
|
* -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]
|
||
|
*/
|
||
|
void
|
||
|
ContentList::OnRightClick(NMHDR*, LRESULT* pResult)
|
||
|
{
|
||
|
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);
|
||
|
//TRACE1("%s was right-clicked\n", str);
|
||
|
WMSG1("%s was right-clicked\n", 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;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Mark everything as selected.
|
||
|
*/
|
||
|
void
|
||
|
ContentList::SelectAll(void)
|
||
|
{
|
||
|
int i;
|
||
|
|
||
|
for (i = GetItemCount()-1; i >= 0; i--) {
|
||
|
if (!SetItemState(i, LVIS_SELECTED, LVIS_SELECTED)) {
|
||
|
WMSG1("Glitch: SetItemState failed on %d\n", i);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Toggle the "selected" state flag.
|
||
|
*/
|
||
|
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)) {
|
||
|
WMSG1("Glitch: SetItemState failed on %d\n", i);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Select the contents of any selected subdirs.
|
||
|
*
|
||
|
* 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.)
|
||
|
*/
|
||
|
void
|
||
|
ContentList::SelectSubdirContents(void)
|
||
|
{
|
||
|
POSITION posn;
|
||
|
posn = GetFirstSelectedItemPosition();
|
||
|
if (posn == nil) {
|
||
|
WMSG0("SelectSubdirContents: nothing is selected\n");
|
||
|
return;
|
||
|
}
|
||
|
/* mark all selected items with LVIS_CUT */
|
||
|
while (posn != nil) {
|
||
|
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)) {
|
||
|
// WMSG1("GLITCH: SetItemState failed on %d\n", i);
|
||
|
// }
|
||
|
}
|
||
|
|
||
|
/* clear the LVIS_CUT flags */
|
||
|
posn = GetFirstSelectedItemPosition();
|
||
|
while (posn != nil) {
|
||
|
int num = GetNextSelectedItem(/*ref*/ posn);
|
||
|
SetItemState(num, 0, LVIS_CUT);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Select every entry whose display name has "displayPrefix" as a prefix.
|
||
|
*/
|
||
|
void
|
||
|
ContentList::SelectSubdir(const char* displayPrefix)
|
||
|
{
|
||
|
WMSG1(" ContentList selecting all in '%s'\n", displayPrefix);
|
||
|
int len = strlen(displayPrefix);
|
||
|
|
||
|
for (int i = GetItemCount()-1; i >= 0; i--) {
|
||
|
GenericEntry* pEntry = (GenericEntry*) GetItemData(i);
|
||
|
|
||
|
if (strncasecmp(displayPrefix, pEntry->GetDisplayName(), len) == 0)
|
||
|
SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Mark all items as unselected.
|
||
|
*/
|
||
|
void
|
||
|
ContentList::ClearSelection(void)
|
||
|
{
|
||
|
for (int i = GetItemCount()-1; i >= 0; i--)
|
||
|
SetItemState(i, 0, LVIS_SELECTED);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Find the next matching entry. We start after the first selected item.
|
||
|
* If we find a matching entry, we clear the current selection and select it.
|
||
|
*/
|
||
|
void
|
||
|
ContentList::FindNext(const char* str, bool down, bool matchCase,
|
||
|
bool wholeWord)
|
||
|
{
|
||
|
POSITION posn;
|
||
|
int i, num;
|
||
|
bool found = false;
|
||
|
|
||
|
WMSG4("FindNext '%s' d=%d c=%d w=%d\n", 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();
|
||
|
}
|
||
|
|
||
|
WMSG1(" starting search from entry %d\n", 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) {
|
||
|
WMSG1("Found, i=%d\n", i);
|
||
|
ClearSelection();
|
||
|
SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
|
||
|
EnsureVisible(i, false);
|
||
|
} else {
|
||
|
WMSG0("Not found\n");
|
||
|
MainWindow* pMain = (MainWindow*)::AfxGetMainWnd();
|
||
|
pMain->FailureBeep();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Compare "str" against the contents of entry "num".
|
||
|
*/
|
||
|
bool
|
||
|
ContentList::CompareFindString(int num, const char* str, bool matchCase,
|
||
|
bool wholeWord)
|
||
|
{
|
||
|
GenericEntry* pEntry = (GenericEntry*) GetItemData(num);
|
||
|
char fssep = pEntry->GetFssep();
|
||
|
char* (*pSubCompare)(const char* str, const char* subStr) = nil;
|
||
|
|
||
|
if (matchCase)
|
||
|
pSubCompare = strstr;
|
||
|
else
|
||
|
pSubCompare = stristr;
|
||
|
|
||
|
if (wholeWord) {
|
||
|
const char* src = pEntry->GetDisplayName();
|
||
|
const char* start = src;
|
||
|
int strLen = strlen(str);
|
||
|
|
||
|
/* scan forward, looking for a match that starts & ends on fssep */
|
||
|
while (*start != '\0') {
|
||
|
const char* match;
|
||
|
|
||
|
match = (*pSubCompare)(start, str);
|
||
|
|
||
|
if (match == nil)
|
||
|
break;
|
||
|
if ((match == src || *(match-1) == fssep) &&
|
||
|
(match[strLen] == '\0' || match[strLen] == fssep))
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
start++;
|
||
|
}
|
||
|
} else {
|
||
|
if ((*pSubCompare)(pEntry->GetDisplayName(), str) != nil)
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|