mirror of
https://github.com/fadden/ciderpress.git
synced 2024-12-22 20:29:51 +00:00
00e6b3ab5e
This updates GenericEntry's filename handling to be more careful about Mac OS Roman vs. Unicode. Most of the work is still done with a CP-1252 conversion instead of MOR, but we now do a proper conversion on the "display name", so we see the right thing in the content list and file viewer. Copy & paste, disk-to-file-archive, and file-archive-to-disk conversions should work (correctly) as before. Extracted files will still have "_" or "%AA" instead of a Unicode TRADE MARK SIGN, but that's fine for now -- we can extract and re-add the files losslessly. The filenames are now stored in CStrings rather than WCHAR*. Also, fixed a bad initializer in the file-archive-to-disk conversion dialog.
968 lines
29 KiB
C++
968 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)
|
|
{
|
|
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;
|
|
}
|