ciderpress/app/Clipboard.cpp
Andy McFadden 51b5f00f5c Large set of changes to restore CiderPress build.
CiderPress and MDC now compile, and execute far enough to open
their respective "about" boxes, but I doubt they'll do much
more than that.

* Switch from MBCS to UNICODE APIs

Microsoft switched to UTF-16 (by way of UCS-2) a long time ago,
and the support for MBCS seems to be getting phased out.  So it's
time to switch to wide strings.

This is a bit awkward for CiderPress because it works with disk
and file archives with 8-bit filenames, and I want NufxLib and
DiskImgLib to continue to work on Linux (which has largely taken
the UTF-8 approach to Unicode).  The libraries will continue to
work with 8-bit filenames, with CiderPress/MDC doing the
conversion at the appropriate point.

There were a couple of places where strings from a structure
handed back by one of the libraries were used directly in the UI,
or vice-versa, which is a problem because we have nowhere to
store the result of the conversion.  These currently have fixed
place-holder "xyzzy" strings.

All UI strings are now wide.

Various format strings now use "%ls" and "%hs" to explicitly
specify wide and narrow.  This doesn't play well with gcc, so
only the Windows-specific parts use those.

* Various updates to vcxproj files

The project-file conversion had some cruft that is now largely
gone.  The build now has a common output directory for the EXEs
and libraries, avoiding the old post-build copy steps.

* Added zlib 1.2.8 and nufxlib 2.2.2 source snapshots

The old "prebuilts" directory is now gone.  The libraries are now
built as part of building the apps.

I added a minimal set of files for zlib, and a full set for nufxlib.
The Linux-specific nufxlib goodies are included for the benefit of
the Linux utilities, which are currently broken (don't build).

* Replace symbols used for include guards

Symbols with a leading "__" are reserved.
2014-11-16 21:01:53 -08:00

1083 lines
34 KiB
C++

/*
* CiderPress
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
* See the file LICENSE for distribution terms.
*/
/*
* Handle clipboard functions (copy, paste).
*/
#include "StdAfx.h"
#include "Main.h"
#include "PasteSpecialDialog.h"
static const WCHAR kClipboardFmtName[] = L"faddenSoft:CiderPress:v1";
const int kClipVersion = 1; // should match "vN" in fmt name
const unsigned short kEntrySignature = 0x4350;
/*
* Win98 is quietly dying on large (20MB-ish) copies. Everything
* runs along nicely and then, the next time we try to interact with
* Windows, the entire system locks up and must be Ctrl-Alt-Deleted.
*
* In tests, it blew up on 16762187 but not 16682322, both of which
* are shy of the 16MB mark (the former by about 15K). This includes the
* text version, which is potentially copied multiple times. Windows doesn't
* create the additional stuff (CF_OEMTEXT and CF_LOCALE; Win2K also creates
* CF_UNICODETEXT) until after the clipboard is closed. We can open & close
* the clipboard to get an exact count, or just multiply by a small integer
* to get a reasonable estimate (1x for alternate text, 2x for UNICODE).
*
* Microsoft Excel limits its clipboard to 4MB on small systems, and 8MB
* on systems with at least 64MB of physical memory. My guess is they
* haven't really tried larger values.
*
* The description of GlobalAlloc suggests that it gets a little weird
* when you go above 4MB, which makes me a little nervous about using
* anything larger. However, it seems to work very reliably until you
* cross 16MB, at which point it seizes up 100% of the time. It's possible
* we're stomping on stuff and just getting lucky, but it's too-reliably
* successful and too-reliably failing for me to believe that.
*/
const long kWin98ClipboardMax = 16 * 1024 * 1024;
const long kWin98NeutralZone = 512; // extra padding
const int kClipTextMult = 4; // CF_OEMTEXT, CF_LOCALE, CF_UNICODETEXT*2
/*
* File collection header.
*/
typedef struct FileCollection {
unsigned short version; // currently 1
unsigned short dataOffset; // offset to start of data
unsigned long length; // total length;
unsigned long count; // #of entries
} FileCollection;
/* what kind of entry is this */
typedef enum EntryKind {
kEntryKindUnknown = 0,
kEntryKindFileDataFork,
kEntryKindFileRsrcFork,
kEntryKindFileBothForks,
kEntryKindDirectory,
kEntryKindDiskImage,
} EntryKind;
/*
* One of these per entry in the collection.
*
* The next file starts at (start + dataOffset + dataLen + rsrcLen + cmmtLen).
*/
typedef struct FileCollectionEntry {
unsigned short signature; // let's be paranoid
unsigned short dataOffset; // offset to start of data
unsigned short fileNameLen; // len of (8-bit) filename, in bytes
unsigned long dataLen; // len of data fork
unsigned long rsrcLen; // len of rsrc fork
unsigned long cmmtLen; // len of comments
unsigned long fileType;
unsigned long auxType;
time_t createWhen;
time_t modWhen;
unsigned char access; // ProDOS access flags
unsigned char entryKind; // GenericArchive::FileDetails::FileKind
unsigned char sourceFS; // DiskImgLib::DiskImg::FSFormat
unsigned char fssep; // filesystem separator char, e.g. ':'
/* data comes next: filename, then data, then resource, then comment */
} FileCollectionEntry;
/*
* ==========================================================================
* Copy
* ==========================================================================
*/
/*
* Copy data to the clipboard.
*/
void
MainWindow::OnEditCopy(void)
{
CString errStr, fileList;
SelectionSet selSet;
UINT myFormat;
bool isOpen = false;
HGLOBAL hGlobal;
LPVOID pGlobal;
unsigned char* buf = nil;
long bufLen = -1;
/* associate a number with the format name */
myFormat = RegisterClipboardFormat(kClipboardFmtName);
if (myFormat == 0) {
errStr.LoadString(IDS_CLIPBOARD_REGFAILED);
ShowFailureMsg(this, errStr, IDS_FAILED);
goto bail;
}
WMSG1("myFormat = %u\n", myFormat);
/* open & empty the clipboard, even if we fail later */
if (OpenClipboard() == false) {
errStr.LoadString(IDS_CLIPBOARD_OPENFAILED);
ShowFailureMsg(this, errStr, IDS_FAILED);
goto bail;
}
isOpen = true;
EmptyClipboard();
/*
* Create a selection set with the entries.
*
* Strictly speaking we don't need the directories, since we recreate
* them as needed. However, storing them explicitly will allow us
* to preserve empty subdirs.
*/
selSet.CreateFromSelection(fpContentList,
GenericEntry::kAnyThread | GenericEntry::kAllowDirectory);
if (selSet.GetNumEntries() == 0) {
errStr.LoadString(IDS_CLIPBOARD_NOITEMS);
MessageBox(errStr, L"No match", MB_OK | MB_ICONEXCLAMATION);
goto bail;
}
/*
* Make a big string with a file listing.
*/
fileList = CreateFileList(&selSet);
/*
* Add the string to the clipboard. The clipboard will own the memory we
* allocate.
*/
size_t neededLen = (fileList.GetLength() + 1) * sizeof(WCHAR);
hGlobal = ::GlobalAlloc(GHND | GMEM_SHARE, neededLen);
if (hGlobal == nil) {
WMSG1("Failed allocating %d bytes\n", neededLen);
errStr.LoadString(IDS_CLIPBOARD_ALLOCFAILED);
ShowFailureMsg(this, errStr, IDS_FAILED);
goto bail;
}
WMSG1(" Allocated %ld bytes for file list on clipboard\n", neededLen);
pGlobal = ::GlobalLock(hGlobal);
ASSERT(pGlobal != nil);
wcscpy((WCHAR*) pGlobal, fileList);
::GlobalUnlock(hGlobal);
SetClipboardData(CF_UNICODETEXT, hGlobal);
/*
* Create a (potentially very large) buffer with the contents of the
* files in it. This may fail for any number of reasons.
*/
hGlobal = CreateFileCollection(&selSet);
if (hGlobal != nil) {
SetClipboardData(myFormat, hGlobal);
// beep annoys me on copy
//SuccessBeep();
}
bail:
CloseClipboard();
}
void
MainWindow::OnUpdateEditCopy(CCmdUI* pCmdUI)
{
pCmdUI->Enable(fpContentList != nil &&
fpContentList->GetSelectedCount() > 0);
}
/*
* Create a list of selected files.
*
* The returned string uses tab-delineated fields with CSV-style quoting
* around the filename (so that double quotes in the filename don't confuse
* applications like MS Excel).
*/
CString
MainWindow::CreateFileList(SelectionSet* pSelSet)
{
SelectionEntry* pSelEntry;
GenericEntry* pEntry;
CString tmpStr, fullStr;
WCHAR fileTypeBuf[ContentList::kFileTypeBufLen];
WCHAR auxTypeBuf[ContentList::kAuxTypeBufLen];
CString fileName, subVol, fileType, auxType, modDate, format, length;
pSelEntry = pSelSet->IterNext();
while (pSelEntry != nil) {
pEntry = pSelEntry->GetEntry();
ASSERT(pEntry != nil);
fileName = DblDblQuote(pEntry->GetPathName());
subVol = pEntry->GetSubVolName();
ContentList::MakeFileTypeDisplayString(pEntry, fileTypeBuf);
fileType = DblDblQuote(fileTypeBuf); // Mac HFS types might have '"'?
ContentList::MakeAuxTypeDisplayString(pEntry, auxTypeBuf);
auxType = DblDblQuote(auxTypeBuf);
FormatDate(pEntry->GetModWhen(), &modDate);
format = pEntry->GetFormatStr();
length.Format(L"%I64d", (LONGLONG) pEntry->GetUncompressedLen());
tmpStr.Format(L"\"%hs\"\t%hs\t\"%hs\"\t\"%hs\"\t%hs\t%hs\t%hs\r\n",
fileName, subVol, fileType, auxType, modDate, format, length);
fullStr += tmpStr;
pSelEntry = pSelSet->IterNext();
}
return fullStr;
}
/*
* Double-up all double quotes.
*/
/*static*/ CString
MainWindow::DblDblQuote(const WCHAR* str)
{
CString result;
WCHAR* buf;
buf = result.GetBuffer(wcslen(str) * 2 +1);
while (*str != '\0') {
if (*str == '"') {
*buf++ = *str;
*buf++ = *str;
} else {
*buf++ = *str;
}
str++;
}
*buf = *str;
result.ReleaseBuffer();
return result;
}
/*
* Compute the size of everything currently on the clipboard.
*/
long
MainWindow::GetClipboardContentLen(void)
{
long len = 0;
UINT format = 0;
HGLOBAL hGlobal;
while ((format = EnumClipboardFormats(format)) != 0) {
hGlobal = GetClipboardData(format);
ASSERT(hGlobal != nil);
len += GlobalSize(hGlobal);
}
return len;
}
/*
* Create the file collection.
*/
HGLOBAL
MainWindow::CreateFileCollection(SelectionSet* pSelSet)
{
SelectionEntry* pSelEntry;
GenericEntry* pEntry;
HGLOBAL hGlobal = nil;
HGLOBAL hResult = nil;
LPVOID pGlobal;
size_t totalLength, numFiles;
long priorLength;
/* get len of text version(s), with kluge to avoid close & reopen */
priorLength = GetClipboardContentLen() * kClipTextMult;
/* add some padding -- textmult doesn't work for fixed-size CF_LOCALE */
priorLength += kWin98NeutralZone;
totalLength = sizeof(FileCollection);
numFiles = 0;
/*
* Compute the amount of space required to hold it all.
*/
pSelSet->IterReset();
pSelEntry = pSelSet->IterNext();
while (pSelEntry != nil) {
pEntry = pSelEntry->GetEntry();
ASSERT(pEntry != nil);
//WMSG1("+++ Examining '%s'\n", pEntry->GetDisplayName());
if (pEntry->GetRecordKind() != GenericEntry::kRecordKindVolumeDir) {
totalLength += sizeof(FileCollectionEntry);
totalLength += wcslen(pEntry->GetPathName()) +1;
numFiles++;
if (pEntry->GetRecordKind() != GenericEntry::kRecordKindDirectory) {
totalLength += (long) pEntry->GetDataForkLen();
totalLength += (long) pEntry->GetRsrcForkLen();
}
}
if (totalLength < 0) {
DebugBreak();
WMSG0("Overflow\n"); // pretty hard to do right now!
return nil;
}
pSelEntry = pSelSet->IterNext();
}
#if 0
{
CString msg;
msg.Format("totalLength is %ld+%ld = %ld",
totalLength, priorLength, totalLength+priorLength);
if (MessageBox(msg, nil, MB_OKCANCEL) == IDCANCEL)
goto bail;
}
#endif
WMSG3("Total length required is %ld + %ld = %ld\n",
totalLength, priorLength, totalLength+priorLength);
if (IsWin9x() && totalLength+priorLength >= kWin98ClipboardMax) {
CString errMsg;
errMsg.Format(IDS_CLIPBOARD_WIN9XMAX,
kWin98ClipboardMax / (1024*1024),
((float) (totalLength+priorLength)) / (1024.0*1024.0));
ShowFailureMsg(this, errMsg, IDS_MB_APP_NAME);
goto bail;
}
/*
* Create a big buffer to hold it all.
*/
hGlobal = ::GlobalAlloc(GHND | GMEM_SHARE, totalLength);
if (hGlobal == nil) {
CString errMsg;
errMsg.Format(L"ERROR: unable to allocate %ld bytes for copy",
totalLength);
WMSG1("%ls\n", (LPCWSTR) errMsg);
ShowFailureMsg(this, errMsg, IDS_FAILED);
goto bail;
}
pGlobal = ::GlobalLock(hGlobal);
ASSERT(pGlobal != nil);
ASSERT(GlobalSize(hGlobal) >= (DWORD) totalLength);
WMSG3("hGlobal=0x%08lx pGlobal=0x%08lx size=%ld\n",
(long) hGlobal, (long) pGlobal, GlobalSize(hGlobal));
/*
* Set up a progress dialog to track it.
*/
ASSERT(fpActionProgress == nil);
fpActionProgress = new ActionProgressDialog;
fpActionProgress->Create(ActionProgressDialog::kActionExtract, this);
fpActionProgress->SetFileName(L"Clipboard");
/*
* Extract the data into the buffer.
*/
long remainingLen;
void* buf;
remainingLen = totalLength - sizeof(FileCollection);
buf = (unsigned char*) pGlobal + sizeof(FileCollection);
pSelSet->IterReset();
pSelEntry = pSelSet->IterNext();
while (pSelEntry != nil) {
CString errStr;
pEntry = pSelEntry->GetEntry();
ASSERT(pEntry != nil);
CString displayName(pEntry->GetDisplayName());
fpActionProgress->SetArcName(displayName);
errStr = CopyToCollection(pEntry, &buf, &remainingLen);
if (!errStr.IsEmpty()) {
ShowFailureMsg(fpActionProgress, errStr, IDS_MB_APP_NAME);
goto bail;
}
//WMSG1("remainingLen now %ld\n", remainingLen);
pSelEntry = pSelSet->IterNext();
}
ASSERT(remainingLen == 0);
ASSERT(buf == (unsigned char*) pGlobal + totalLength);
/*
* Write the header.
*/
FileCollection fileColl;
fileColl.version = kClipVersion;
fileColl.dataOffset = sizeof(FileCollection);
fileColl.length = totalLength;
fileColl.count = numFiles;
memcpy(pGlobal, &fileColl, sizeof(fileColl));
/*
* Success!
*/
::GlobalUnlock(hGlobal);
hResult = hGlobal;
hGlobal = nil;
bail:
if (hGlobal != nil) {
ASSERT(hResult == nil);
::GlobalUnlock(hGlobal);
::GlobalFree(hGlobal);
}
if (fpActionProgress != nil) {
fpActionProgress->Cleanup(this);
fpActionProgress = nil;
}
return hResult;
}
/*
* Copy the contents of the file referred to by "pEntry" into the buffer
* "*pBuf", which has "*pBufLen" bytes in it.
*
* The call fails if "*pBufLen" isn't large enough.
*
* Returns an empty string on success, or an error message on failure.
* On success, "*pBuf" will be advanced past the data added, and "*pBufLen"
* will be reduced by the amount of data copied into "buf".
*/
CString
MainWindow::CopyToCollection(GenericEntry* pEntry, void** pBuf, long* pBufLen)
{
FileCollectionEntry collEnt;
CString errStr, dummyStr;
unsigned char* buf = (unsigned char*) *pBuf;
long remLen = *pBufLen;
errStr.LoadString(IDS_CLIPBOARD_WRITEFAILURE);
if (pEntry->GetRecordKind() == GenericEntry::kRecordKindVolumeDir) {
WMSG0("Not copying volume dir to collection\n");
return "";
}
if (remLen < sizeof(collEnt)) {
ASSERT(false);
return errStr;
}
GenericArchive::FileDetails::FileKind entryKind;
if (pEntry->GetRecordKind() == GenericEntry::kRecordKindDirectory)
entryKind = GenericArchive::FileDetails::kFileKindDirectory;
else if (pEntry->GetHasDataFork() && pEntry->GetHasRsrcFork())
entryKind = GenericArchive::FileDetails::kFileKindBothForks;
else if (pEntry->GetHasDataFork())
entryKind = GenericArchive::FileDetails::kFileKindDataFork;
else if (pEntry->GetHasRsrcFork())
entryKind = GenericArchive::FileDetails::kFileKindRsrcFork;
else if (pEntry->GetHasDiskImage())
entryKind = GenericArchive::FileDetails::kFileKindDiskImage;
else {
ASSERT(false);
return errStr;
}
ASSERT((int) entryKind >= 0 && (int) entryKind <= 255);
memset(&collEnt, 0x99, sizeof(collEnt));
collEnt.signature = kEntrySignature;
collEnt.dataOffset = sizeof(collEnt);
collEnt.fileNameLen = wcslen(pEntry->GetPathName()) +1;
if (pEntry->GetRecordKind() == GenericEntry::kRecordKindDirectory) {
collEnt.dataLen = collEnt.rsrcLen = collEnt.cmmtLen = 0;
} else {
collEnt.dataLen = (unsigned long) pEntry->GetDataForkLen();
collEnt.rsrcLen = (unsigned long) pEntry->GetRsrcForkLen();
collEnt.cmmtLen = 0; // CMMT FIX -- length unknown??
}
collEnt.fileType = pEntry->GetFileType();
collEnt.auxType = pEntry->GetAuxType();
collEnt.createWhen = pEntry->GetCreateWhen();
collEnt.modWhen = pEntry->GetModWhen();
collEnt.access = (unsigned char) pEntry->GetAccess();
collEnt.entryKind = (unsigned char) entryKind;
collEnt.sourceFS = pEntry->GetSourceFS();
collEnt.fssep = pEntry->GetFssep();
/* verify there's enough space to hold everything */
if ((unsigned long) remLen < collEnt.fileNameLen +
collEnt.dataLen + collEnt.rsrcLen + collEnt.cmmtLen)
{
ASSERT(false);
return errStr;
}
memcpy(buf, &collEnt, sizeof(collEnt));
buf += sizeof(collEnt);
remLen -= sizeof(collEnt);
/* copy string with terminating null */
memcpy(buf, pEntry->GetPathName(), collEnt.fileNameLen);
buf += collEnt.fileNameLen;
remLen -= collEnt.fileNameLen;
/*
* Extract data forks, resource forks, and disk images as appropriate.
*/
char* bufCopy;
long lenCopy;
int result, which;
if (pEntry->GetRecordKind() == GenericEntry::kRecordKindDirectory) {
ASSERT(collEnt.dataLen == 0);
ASSERT(collEnt.rsrcLen == 0);
ASSERT(collEnt.cmmtLen == 0);
ASSERT(!pEntry->GetHasRsrcFork());
} else if (pEntry->GetHasDataFork() || pEntry->GetHasDiskImage()) {
bufCopy = (char*) buf;
lenCopy = remLen;
if (pEntry->GetHasDiskImage())
which = GenericEntry::kDiskImageThread;
else
which = GenericEntry::kDataThread;
result = pEntry->ExtractThreadToBuffer(which, &bufCopy, &lenCopy,
&dummyStr);
if (result == IDCANCEL) {
errStr.LoadString(IDS_CANCELLED);
return errStr;
} else if (result != IDOK) {
WMSG0("ExtractThreadToBuffer (data) failed\n");
return errStr;
}
ASSERT(lenCopy == (long) collEnt.dataLen);
buf += collEnt.dataLen;
remLen -= collEnt.dataLen;
} else {
ASSERT(collEnt.dataLen == 0);
}
if (pEntry->GetHasRsrcFork()) {
bufCopy = (char*) buf;
lenCopy = remLen;
which = GenericEntry::kRsrcThread;
result = pEntry->ExtractThreadToBuffer(which, &bufCopy, &lenCopy,
&dummyStr);
if (result == IDCANCEL) {
errStr.LoadString(IDS_CANCELLED);
return errStr;
} else if (result != IDOK) {
WMSG0("ExtractThreadToBuffer (rsrc) failed\n");
return errStr;
}
ASSERT(lenCopy == (long) collEnt.rsrcLen);
buf += collEnt.rsrcLen;
remLen -= collEnt.rsrcLen;
}
if (pEntry->GetHasComment()) {
#if 0 // CMMT FIX
bufCopy = (char*) buf;
lenCopy = remLen;
which = GenericEntry::kCommentThread;
result = pEntry->ExtractThreadToBuffer(which, &bufCopy, &lenCopy);
if (result == IDCANCEL) {
errStr.LoadString(IDS_CANCELLED);
return errStr;
} else if (result != IDOK)
return errStr;
ASSERT(lenCopy == (long) collEnt.cmmtLen);
buf += collEnt.cmmtLen;
remLen -= collEnt.cmmtLen;
#else
ASSERT(collEnt.cmmtLen == 0);
#endif
}
*pBuf = buf;
*pBufLen = remLen;
return "";
}
/*
* ==========================================================================
* Paste
* ==========================================================================
*/
/*
* Paste data from the clipboard, using the configured defaults.
*/
void
MainWindow::OnEditPaste(void)
{
bool pasteJunkPaths = fPreferences.GetPrefBool(kPrPasteJunkPaths);
DoPaste(pasteJunkPaths);
}
void
MainWindow::OnUpdateEditPaste(CCmdUI* pCmdUI)
{
bool dataAvailable = false;
UINT myFormat;
myFormat = RegisterClipboardFormat(kClipboardFmtName);
if (myFormat != 0 && IsClipboardFormatAvailable(myFormat))
dataAvailable = true;
pCmdUI->Enable(fpContentList != nil && !fpOpenArchive->IsReadOnly() &&
dataAvailable);
}
/*
* Paste data from the clipboard, giving the user the opportunity to select
* how the files are handled.
*/
void
MainWindow::OnEditPasteSpecial(void)
{
PasteSpecialDialog dlg;
bool pasteJunkPaths = fPreferences.GetPrefBool(kPrPasteJunkPaths);
// invert the meaning, so non-default mode is default in dialog
if (pasteJunkPaths)
dlg.fPasteHow = PasteSpecialDialog::kPastePaths;
else
dlg.fPasteHow = PasteSpecialDialog::kPasteNoPaths;
if (dlg.DoModal() != IDOK)
return;
switch (dlg.fPasteHow) {
case PasteSpecialDialog::kPastePaths:
pasteJunkPaths = false;
break;
case PasteSpecialDialog::kPasteNoPaths:
pasteJunkPaths = true;
break;
default:
assert(false);
break;
}
DoPaste(pasteJunkPaths);
}
void
MainWindow::OnUpdateEditPasteSpecial(CCmdUI* pCmdUI)
{
OnUpdateEditPaste(pCmdUI);
}
/*
* Do some prep work and then call ProcessClipboard to copy files in.
*/
void
MainWindow::DoPaste(bool pasteJunkPaths)
{
CString errStr, buildStr;
UINT format = 0;
UINT myFormat;
bool isOpen = false;
if (fpContentList == nil || fpOpenArchive->IsReadOnly()) {
ASSERT(false);
return;
}
myFormat = RegisterClipboardFormat(kClipboardFmtName);
if (myFormat == 0) {
errStr.LoadString(IDS_CLIPBOARD_REGFAILED);
ShowFailureMsg(this, errStr, IDS_FAILED);
goto bail;
}
WMSG1("myFormat = %u\n", myFormat);
if (OpenClipboard() == false) {
errStr.LoadString(IDS_CLIPBOARD_OPENFAILED);
ShowFailureMsg(this, errStr, IDS_FAILED);
goto bail;;
}
isOpen = true;
WMSG1("Found %d clipboard formats\n", CountClipboardFormats());
while ((format = EnumClipboardFormats(format)) != 0) {
CString tmpStr;
tmpStr.Format(L" %u", format);
buildStr += tmpStr;
}
WMSG1(" %ls\n", (LPCWSTR) buildStr);
#if 0
if (IsClipboardFormatAvailable(CF_HDROP)) {
errStr.LoadString(IDS_CLIPBOARD_NO_HDROP);
ShowFailureMsg(this, errStr, IDS_FAILED);
goto bail;
}
#endif
/* impossible unless OnUpdateEditPaste was bypassed */
if (!IsClipboardFormatAvailable(myFormat)) {
errStr.LoadString(IDS_CLIPBOARD_NOTFOUND);
ShowFailureMsg(this, errStr, IDS_FAILED);
goto bail;
}
WMSG1("+++ total data on clipboard: %ld bytes\n",
GetClipboardContentLen());
HGLOBAL hGlobal;
LPVOID pGlobal;
hGlobal = GetClipboardData(myFormat);
if (hGlobal == nil) {
ASSERT(false);
goto bail;
}
pGlobal = GlobalLock(hGlobal);
ASSERT(pGlobal != nil);
errStr = ProcessClipboard(pGlobal, GlobalSize(hGlobal), pasteJunkPaths);
fpContentList->Reload();
if (!errStr.IsEmpty())
ShowFailureMsg(this, errStr, IDS_FAILED);
else
SuccessBeep();
GlobalUnlock(hGlobal);
bail:
if (isOpen)
CloseClipboard();
}
/*
* Process the data in the clipboard.
*
* Returns an empty string on success, or an error message on failure.
*/
CString
MainWindow::ProcessClipboard(const void* vbuf, long bufLen, bool pasteJunkPaths)
{
FileCollection fileColl;
CString errMsg, storagePrefix;
const unsigned char* buf = (const unsigned char*) vbuf;
DiskImgLib::A2File* pTargetSubdir = nil;
XferFileOptions xferOpts;
bool xferPrepped = false;
/* set a standard error message */
errMsg.LoadString(IDS_CLIPBOARD_READFAILURE);
/*
* Pull the header out.
*/
if (bufLen < sizeof(fileColl)) {
WMSG0("Clipboard contents too small!\n");
goto bail;
}
memcpy(&fileColl, buf, sizeof(fileColl));
/*
* Verify the length. Win98 seems to like to round things up to 16-byte
* boundaries, which screws up our "bufLen > 0" while condition below.
*/
if ((long) fileColl.length > bufLen) {
WMSG2("GLITCH: stored len=%ld, clip len=%ld\n",
fileColl.length, bufLen);
goto bail;
}
if (bufLen > (long) fileColl.length) {
/* trim off extra */
WMSG2("NOTE: Windows reports excess length (%ld vs %ld)\n",
fileColl.length, bufLen);
bufLen = fileColl.length;
}
buf += sizeof(fileColl);
bufLen -= sizeof(fileColl);
WMSG4("FileCollection found: vers=%d off=%d len=%ld count=%ld\n",
fileColl.version, fileColl.dataOffset, fileColl.length,
fileColl.count);
if (fileColl.count == 0) {
/* nothing to do? */
ASSERT(false);
return errMsg;
}
/*
* Figure out where we want to put the files. For a disk archive
* this can be complicated.
*
* The target DiskFS (which could be a sub-volume) gets tucked into
* the xferOpts.
*/
if (fpOpenArchive->GetArchiveKind() == GenericArchive::kArchiveDiskImage) {
if (!ChooseAddTarget(&pTargetSubdir, &xferOpts.fpTargetFS))
return L"";
}
fpOpenArchive->XferPrepare(&xferOpts);
xferPrepped = true;
if (pTargetSubdir != nil) {
storagePrefix = pTargetSubdir->GetPathName();
WMSG1("--- using storagePrefix '%ls'\n", (LPCWSTR) storagePrefix);
}
/*
* Set up a progress dialog to track it.
*/
ASSERT(fpActionProgress == nil);
fpActionProgress = new ActionProgressDialog;
fpActionProgress->Create(ActionProgressDialog::kActionAdd, this);
fpActionProgress->SetArcName(L"Clipboard data");
/*
* Loop over all files.
*/
WMSG1("+++ Starting paste, bufLen=%ld\n", bufLen);
while (bufLen > 0) {
FileCollectionEntry collEnt;
CString fileName, processErrStr;
/* read the entry info */
if (bufLen < sizeof(collEnt)) {
WMSG2("GLITCH: bufLen=%ld, sizeof(collEnt)=%d\n",
bufLen, sizeof(collEnt));
ASSERT(false);
goto bail;
}
memcpy(&collEnt, buf, sizeof(collEnt));
if (collEnt.signature != kEntrySignature) {
ASSERT(false);
goto bail;
}
/* advance to the start of data */
if (bufLen < collEnt.dataOffset) {
ASSERT(false);
goto bail;
}
buf += collEnt.dataOffset;
bufLen -= collEnt.dataOffset;
/* extract the filename */
if (bufLen < collEnt.fileNameLen) {
ASSERT(false);
goto bail;
}
fileName = buf;
buf += collEnt.fileNameLen;
bufLen -= collEnt.fileNameLen;
//WMSG1("+++ pasting '%s'\n", fileName);
/* strip the path (if requested) and prepend the storage prefix */
ASSERT(fileName.GetLength() == collEnt.fileNameLen -1);
if (pasteJunkPaths && collEnt.fssep != '\0') {
int idx;
idx = fileName.ReverseFind(collEnt.fssep);
if (idx >= 0)
fileName = fileName.Right(fileName.GetLength() - idx -1);
}
if (!storagePrefix.IsEmpty()) {
CString tmpStr, tmpFileName;
tmpFileName = fileName;
if (collEnt.fssep == '\0') {
tmpFileName.Replace(':', '_'); // strip any ':'s in the name
collEnt.fssep = ':'; // define an fssep
}
tmpStr = storagePrefix;
/* storagePrefix fssep is always ':'; change it to match */
if (collEnt.fssep != ':')
tmpStr.Replace(':', collEnt.fssep);
tmpStr += collEnt.fssep;
tmpStr += tmpFileName;
fileName = tmpStr;
}
fpActionProgress->SetFileName(fileName);
/* make sure the data is there */
if (bufLen < (long) (collEnt.dataLen + collEnt.rsrcLen + collEnt.cmmtLen))
{
ASSERT(false);
goto bail;
}
/*
* Process the entry.
*
* If the user hits "cancel" in the progress dialog we'll get thrown
* back out. For the time being I'm just treating it like any other
* failure.
*/
processErrStr = ProcessClipboardEntry(&collEnt, fileName, buf, bufLen);
if (!processErrStr.IsEmpty()) {
errMsg.Format(L"Unable to paste '%ls': %ls.",
(LPCWSTR) fileName, (LPCWSTR) processErrStr);
goto bail;
}
buf += collEnt.dataLen + collEnt.rsrcLen + collEnt.cmmtLen;
bufLen -= collEnt.dataLen + collEnt.rsrcLen + collEnt.cmmtLen;
}
ASSERT(bufLen == 0);
errMsg = "";
bail:
if (xferPrepped) {
if (errMsg.IsEmpty())
fpOpenArchive->XferFinish(this);
else
fpOpenArchive->XferAbort(this);
}
if (fpActionProgress != nil) {
fpActionProgress->Cleanup(this);
fpActionProgress = nil;
}
return errMsg;
}
/*
* Process a single clipboard entry.
*
* On entry, "buf" points to the start of the first chunk of data (either
* data fork or resource fork). If the file has empty forks or is a
* subdirectory, then "buf" is actually pointing at the start of the
* next entry.
*/
CString
MainWindow::ProcessClipboardEntry(const FileCollectionEntry* pCollEnt,
const WCHAR* pathName, const unsigned char* buf, long remLen)
{
GenericArchive::FileDetails::FileKind entryKind;
GenericArchive::FileDetails details;
unsigned char* dataBuf = nil;
unsigned char* rsrcBuf = nil;
long dataLen, rsrcLen, cmmtLen;
CString errMsg;
entryKind = (GenericArchive::FileDetails::FileKind) pCollEnt->entryKind;
WMSG2(" Processing '%ls' (%d)\n", pathName, entryKind);
details.entryKind = entryKind;
details.origName = L"Clipboard";
details.storageName = pathName;
details.fileSysFmt = (DiskImg::FSFormat) pCollEnt->sourceFS;
details.fileSysInfo = pCollEnt->fssep;
details.access = pCollEnt->access;
details.fileType = pCollEnt->fileType;
details.extraType = pCollEnt->auxType;
GenericArchive::UNIXTimeToDateTime(&pCollEnt->createWhen,
&details.createWhen);
GenericArchive::UNIXTimeToDateTime(&pCollEnt->modWhen,
&details.modWhen);
time_t now = time(nil);
GenericArchive::UNIXTimeToDateTime(&now, &details.archiveWhen);
/*
* Because of the way XferFile works, we need to make a copy of
* the data. (For NufxLib, it's going to gather up all of the
* data and flush it all at once, so it needs to own the memory.)
*
* Ideally we'd use a different interface that didn't require a
* data copy -- NufxLib can do it that way as well -- but it's
* not worth maintaining two separate interfaces.
*
* This approach does allow the xfer code to handle DOS high-ASCII
* text conversions in place, though. If we didn't do it this way
* we'd have to make a copy in the xfer code to avoid contaminating
* the clipboard data. That would be more efficient, but probably
* a bit messier.
*
* The stuff below figures out which forks we're expected to have based
* on the file type info. This helps us distinguish between a file
* with a zero-length fork and a file without that kind of fork.
*/
bool hasData = false;
bool hasRsrc = false;
if (details.entryKind == GenericArchive::FileDetails::kFileKindDataFork) {
hasData = true;
details.storageType = kNuStorageSeedling;
} else if (details.entryKind == GenericArchive::FileDetails::kFileKindRsrcFork) {
hasRsrc = true;
details.storageType = kNuStorageExtended;
} else if (details.entryKind == GenericArchive::FileDetails::kFileKindBothForks) {
hasData = hasRsrc = true;
details.storageType = kNuStorageExtended;
} else if (details.entryKind == GenericArchive::FileDetails::kFileKindDiskImage) {
hasData = true;
details.storageType = kNuStorageSeedling;
} else if (details.entryKind == GenericArchive::FileDetails::kFileKindDirectory) {
details.storageType = kNuStorageDirectory;
} else {
ASSERT(false);
return "Internal error.";
}
if (hasData) {
if (pCollEnt->dataLen == 0) {
dataBuf = new unsigned char[1];
dataLen = 0;
} else {
dataLen = pCollEnt->dataLen;
dataBuf = new unsigned char[dataLen];
if (dataBuf == nil)
return "memory allocation failed.";
memcpy(dataBuf, buf, dataLen);
buf += dataLen;
remLen -= dataLen;
}
} else {
ASSERT(dataBuf == nil);
dataLen = -1;
}
if (hasRsrc) {
if (pCollEnt->rsrcLen == 0) {
rsrcBuf = new unsigned char[1];
rsrcLen = 0;
} else {
rsrcLen = pCollEnt->rsrcLen;
rsrcBuf = new unsigned char[rsrcLen];
if (rsrcBuf == nil)
return "Memory allocation failed.";
memcpy(rsrcBuf, buf, rsrcLen);
buf += rsrcLen;
remLen -= rsrcLen;
}
} else {
ASSERT(rsrcBuf == nil);
rsrcLen = -1;
}
if (pCollEnt->cmmtLen > 0) {
cmmtLen = pCollEnt->cmmtLen;
/* CMMT FIX -- not supported by XferFile */
}
ASSERT(remLen >= 0);
errMsg = fpOpenArchive->XferFile(&details, &dataBuf, dataLen,
&rsrcBuf, rsrcLen);
delete[] dataBuf;
delete[] rsrcBuf;
dataBuf = rsrcBuf = nil;
return errMsg;
}