mirror of
https://github.com/fadden/ciderpress.git
synced 2025-01-03 07:30:31 +00:00
920 lines
19 KiB
C++
920 lines
19 KiB
C++
|
/*
|
||
|
* CiderPress
|
||
|
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
|
||
|
* See the file LICENSE for distribution terms.
|
||
|
*/
|
||
|
/*
|
||
|
* Miscellaneous utility classes.
|
||
|
*/
|
||
|
#include "stdafx.h"
|
||
|
#include "Util.h"
|
||
|
#include <stdarg.h>
|
||
|
|
||
|
|
||
|
/*
|
||
|
* ===========================================================================
|
||
|
* CGripper
|
||
|
* ===========================================================================
|
||
|
*/
|
||
|
|
||
|
/*
|
||
|
* Fake out the hit testing so that it thinks we're over the scrollable bit
|
||
|
* when we're over the gripper.
|
||
|
*/
|
||
|
BEGIN_MESSAGE_MAP(CGripper, CScrollBar)
|
||
|
ON_WM_NCHITTEST()
|
||
|
END_MESSAGE_MAP()
|
||
|
|
||
|
UINT
|
||
|
CGripper::OnNcHitTest(CPoint point)
|
||
|
{
|
||
|
UINT ht = CScrollBar::OnNcHitTest(point);
|
||
|
if (ht == HTCLIENT) {
|
||
|
ht = HTBOTTOMRIGHT;
|
||
|
}
|
||
|
return ht;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* ===========================================================================
|
||
|
* RichEditXfer
|
||
|
* ===========================================================================
|
||
|
*/
|
||
|
|
||
|
/*
|
||
|
* At the behest of a RichEditCtrl, copy "cb" bytes of data into "pbBuff".
|
||
|
* The actual number of bytes transferred is placed in "*pcb".
|
||
|
*
|
||
|
* (A complete implementation would allow copying either direction.)
|
||
|
*
|
||
|
* Returns 0 on success, nonzero on error.
|
||
|
*/
|
||
|
DWORD
|
||
|
RichEditXfer::EditStreamCallback(DWORD dwCookie, LPBYTE pbBuff, LONG cb, LONG* pcb)
|
||
|
{
|
||
|
RichEditXfer* pThis = (RichEditXfer*) dwCookie;
|
||
|
|
||
|
ASSERT(dwCookie != nil);
|
||
|
ASSERT(pbBuff != nil);
|
||
|
ASSERT(cb != 0);
|
||
|
ASSERT(pcb != nil);
|
||
|
|
||
|
long copyLen = pThis->fLen;
|
||
|
if (copyLen > cb)
|
||
|
copyLen = cb;
|
||
|
if (copyLen == 0) {
|
||
|
ASSERT(pThis->fLen == 0);
|
||
|
goto bail;
|
||
|
}
|
||
|
|
||
|
::memcpy(pbBuff, pThis->fBuf, copyLen);
|
||
|
|
||
|
pThis->fBuf += copyLen;
|
||
|
pThis->fLen -= copyLen;
|
||
|
|
||
|
//WMSG2("ESC: copyLen=%d, now fLen=%d\n", copyLen, pThis->fLen);
|
||
|
|
||
|
bail:
|
||
|
*pcb = copyLen;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* ===========================================================================
|
||
|
* ExpandBuffer
|
||
|
* ===========================================================================
|
||
|
*/
|
||
|
|
||
|
/*
|
||
|
* Allocate the initial buffer.
|
||
|
*/
|
||
|
int
|
||
|
ExpandBuffer::CreateWorkBuf(void)
|
||
|
{
|
||
|
if (fWorkBuf != nil) {
|
||
|
ASSERT(fWorkMax > 0);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
assert(fInitialSize > 0);
|
||
|
|
||
|
fWorkBuf = new char[fInitialSize];
|
||
|
if (fWorkBuf == nil)
|
||
|
return -1;
|
||
|
|
||
|
fWorkCount = 0;
|
||
|
fWorkMax = fInitialSize;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Let the caller seize control of our buffer. We throw away our pointer to
|
||
|
* the buffer so we don't free it.
|
||
|
*/
|
||
|
void
|
||
|
ExpandBuffer::SeizeBuffer(char** ppBuf, long* pLen)
|
||
|
{
|
||
|
*ppBuf = fWorkBuf;
|
||
|
*pLen = fWorkCount;
|
||
|
|
||
|
fWorkBuf = nil;
|
||
|
fWorkCount = 0;
|
||
|
fWorkMax = 0;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Grow the buffer to the next incremental size. We keep doubling it until
|
||
|
* we reach out maximum rate of expansion.
|
||
|
*
|
||
|
* Returns 0 on success, -1 on failure.
|
||
|
*/
|
||
|
int
|
||
|
ExpandBuffer::GrowWorkBuf(void)
|
||
|
{
|
||
|
int newIncr = fWorkMax;
|
||
|
if (newIncr > kWorkBufMaxIncrement)
|
||
|
newIncr = kWorkBufMaxIncrement;
|
||
|
|
||
|
//WMSG3("Extending buffer by %d (count=%d, max=%d)\n",
|
||
|
// newIncr, fWorkCount, fWorkMax);
|
||
|
|
||
|
fWorkMax += newIncr;
|
||
|
|
||
|
/* debug-only check to catch runaways */
|
||
|
// ASSERT(fWorkMax < 1024*1024*24);
|
||
|
|
||
|
char* newBuf = new char[fWorkMax];
|
||
|
if (newBuf == nil) {
|
||
|
WMSG1("ALLOC FAILURE (%ld)\n", fWorkMax);
|
||
|
ASSERT(false);
|
||
|
fWorkMax -= newIncr; // put it back so we don't overrun
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
memcpy(newBuf, fWorkBuf, fWorkCount);
|
||
|
delete[] fWorkBuf;
|
||
|
fWorkBuf = newBuf;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Write binary data to the buffer.
|
||
|
*/
|
||
|
void
|
||
|
ExpandBuffer::Write(const unsigned char* buf, long len)
|
||
|
{
|
||
|
if (fWorkBuf == nil)
|
||
|
CreateWorkBuf();
|
||
|
while (fWorkCount + len >= fWorkMax) {
|
||
|
if (GrowWorkBuf() != 0)
|
||
|
return;
|
||
|
}
|
||
|
ASSERT(fWorkCount + len < fWorkMax);
|
||
|
memcpy(fWorkBuf + fWorkCount, buf, len);
|
||
|
fWorkCount += len;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Write one character into the buffer.
|
||
|
*/
|
||
|
void
|
||
|
ExpandBuffer::Putc(char ch)
|
||
|
{
|
||
|
Write((const unsigned char*) &ch, 1);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Print a formatted string into the buffer.
|
||
|
*/
|
||
|
void
|
||
|
ExpandBuffer::Printf(const char* format, ...)
|
||
|
{
|
||
|
va_list args;
|
||
|
|
||
|
ASSERT(format != nil);
|
||
|
|
||
|
if (fWorkBuf == nil)
|
||
|
CreateWorkBuf();
|
||
|
|
||
|
va_start(args, format);
|
||
|
|
||
|
if (format != nil) {
|
||
|
int count;
|
||
|
count = _vsnprintf(fWorkBuf + fWorkCount, fWorkMax - fWorkCount,
|
||
|
format, args);
|
||
|
if (count < 0) {
|
||
|
if (GrowWorkBuf() != 0)
|
||
|
return;
|
||
|
|
||
|
/* try one more time, then give up */
|
||
|
count = _vsnprintf(fWorkBuf + fWorkCount, fWorkMax - fWorkCount,
|
||
|
format, args);
|
||
|
ASSERT(count >= 0);
|
||
|
if (count < 0)
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
fWorkCount += count;
|
||
|
ASSERT(fWorkCount <= fWorkMax);
|
||
|
}
|
||
|
|
||
|
va_end(args);
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* ===========================================================================
|
||
|
* Windows helpers
|
||
|
* ===========================================================================
|
||
|
*/
|
||
|
|
||
|
/*
|
||
|
* Enable or disable a control.
|
||
|
*/
|
||
|
void
|
||
|
EnableControl(CDialog* pDlg, int id, bool enable)
|
||
|
{
|
||
|
CWnd* pWnd = pDlg->GetDlgItem(id);
|
||
|
if (pWnd == nil) {
|
||
|
WMSG1("GLITCH: control %d not found in dialog\n", id);
|
||
|
ASSERT(false);
|
||
|
} else {
|
||
|
pWnd->EnableWindow(enable);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Move a control so it maintains its same position relative to the bottom
|
||
|
* and right edges.
|
||
|
*/
|
||
|
void
|
||
|
MoveControl(CDialog* pDlg, int id, int deltaX, int deltaY, bool redraw)
|
||
|
{
|
||
|
CWnd* pWnd;
|
||
|
CRect rect;
|
||
|
|
||
|
pWnd = pDlg->GetDlgItem(id);
|
||
|
ASSERT(pWnd != nil);
|
||
|
if (pWnd == nil)
|
||
|
return;
|
||
|
|
||
|
pWnd->GetWindowRect(&rect);
|
||
|
pDlg->ScreenToClient(&rect);
|
||
|
rect.left += deltaX;
|
||
|
rect.right += deltaX;
|
||
|
rect.top += deltaY;
|
||
|
rect.bottom += deltaY;
|
||
|
pWnd->MoveWindow(&rect, redraw);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Make a control larger by the same delta as the parent window.
|
||
|
*/
|
||
|
void
|
||
|
StretchControl(CDialog* pDlg, int id, int deltaX, int deltaY, bool redraw)
|
||
|
{
|
||
|
CWnd* pWnd;
|
||
|
CRect rect;
|
||
|
|
||
|
pWnd = pDlg->GetDlgItem(id);
|
||
|
ASSERT(pWnd != nil);
|
||
|
if (pWnd == nil)
|
||
|
return;
|
||
|
|
||
|
pWnd->GetWindowRect(&rect);
|
||
|
pDlg->ScreenToClient(&rect);
|
||
|
rect.right += deltaX;
|
||
|
rect.bottom += deltaY;
|
||
|
pWnd->MoveWindow(&rect, redraw);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Stretch and move a control.
|
||
|
*/
|
||
|
void
|
||
|
MoveStretchControl(CDialog* pDlg, int id, int moveX, int moveY,
|
||
|
int stretchX, int stretchY, bool redraw)
|
||
|
{
|
||
|
MoveControl(pDlg, id, moveX, moveY, redraw);
|
||
|
StretchControl(pDlg, id, stretchX, stretchY, redraw);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Move a control so it maintains its same position relative to the bottom
|
||
|
* and right edges.
|
||
|
*/
|
||
|
HDWP
|
||
|
MoveControl(HDWP hdwp, CDialog* pDlg, int id, int deltaX, int deltaY,
|
||
|
bool redraw)
|
||
|
{
|
||
|
CWnd* pWnd;
|
||
|
CRect rect;
|
||
|
|
||
|
pWnd = pDlg->GetDlgItem(id);
|
||
|
ASSERT(pWnd != nil);
|
||
|
if (pWnd == nil)
|
||
|
return hdwp;
|
||
|
|
||
|
pWnd->GetWindowRect(&rect);
|
||
|
pDlg->ScreenToClient(&rect);
|
||
|
rect.left += deltaX;
|
||
|
rect.right += deltaX;
|
||
|
rect.top += deltaY;
|
||
|
rect.bottom += deltaY;
|
||
|
hdwp = DeferWindowPos(hdwp, pWnd->m_hWnd, nil, rect.left, rect.top,
|
||
|
rect.Width(), rect.Height(), 0);
|
||
|
|
||
|
return hdwp;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Make a control larger by the same delta as the parent window.
|
||
|
*/
|
||
|
HDWP
|
||
|
StretchControl(HDWP hdwp, CDialog* pDlg, int id, int deltaX, int deltaY,
|
||
|
bool redraw)
|
||
|
{
|
||
|
CWnd* pWnd;
|
||
|
CRect rect;
|
||
|
|
||
|
pWnd = pDlg->GetDlgItem(id);
|
||
|
ASSERT(pWnd != nil);
|
||
|
if (pWnd == nil)
|
||
|
return hdwp;
|
||
|
|
||
|
pWnd->GetWindowRect(&rect);
|
||
|
pDlg->ScreenToClient(&rect);
|
||
|
rect.right += deltaX;
|
||
|
rect.bottom += deltaY;
|
||
|
hdwp = DeferWindowPos(hdwp, pWnd->m_hWnd, nil, rect.left, rect.top,
|
||
|
rect.Width(), rect.Height(), 0);
|
||
|
|
||
|
return hdwp;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Stretch and move a control.
|
||
|
*/
|
||
|
HDWP
|
||
|
MoveStretchControl(HDWP hdwp, CDialog* pDlg, int id, int moveX, int moveY,
|
||
|
int stretchX, int stretchY, bool redraw)
|
||
|
{
|
||
|
CWnd* pWnd;
|
||
|
CRect rect;
|
||
|
|
||
|
pWnd = pDlg->GetDlgItem(id);
|
||
|
ASSERT(pWnd != nil);
|
||
|
if (pWnd == nil)
|
||
|
return hdwp;
|
||
|
|
||
|
pWnd->GetWindowRect(&rect);
|
||
|
pDlg->ScreenToClient(&rect);
|
||
|
rect.left += moveX;
|
||
|
rect.right += moveX;
|
||
|
rect.top += moveY;
|
||
|
rect.bottom += moveY;
|
||
|
rect.right += stretchX;
|
||
|
rect.bottom += stretchY;
|
||
|
hdwp = DeferWindowPos(hdwp, pWnd->m_hWnd, nil, rect.left, rect.top,
|
||
|
rect.Width(), rect.Height(), 0);
|
||
|
|
||
|
return hdwp;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Get/set the check state of a button in a dialog.
|
||
|
*/
|
||
|
int
|
||
|
GetDlgButtonCheck(CWnd* pWnd, int id)
|
||
|
{
|
||
|
CButton* pButton;
|
||
|
pButton = (CButton*) pWnd->GetDlgItem(id);
|
||
|
ASSERT(pButton != nil);
|
||
|
if (pButton == nil)
|
||
|
return -1;
|
||
|
return pButton->GetCheck();
|
||
|
}
|
||
|
void
|
||
|
SetDlgButtonCheck(CWnd* pWnd, int id, int checkVal)
|
||
|
{
|
||
|
CButton* pButton;
|
||
|
pButton = (CButton*) pWnd->GetDlgItem(id);
|
||
|
ASSERT(pButton != nil);
|
||
|
if (pButton == nil)
|
||
|
return;
|
||
|
pButton->SetCheck(checkVal);
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Create a font, using defaults for most things.
|
||
|
*/
|
||
|
void
|
||
|
CreateSimpleFont(CFont* pFont, CWnd* pWnd, const char* typeFace,
|
||
|
int pointSize)
|
||
|
{
|
||
|
CClientDC dc(pWnd);
|
||
|
int height;
|
||
|
|
||
|
height = -((dc.GetDeviceCaps(LOGPIXELSY) * pointSize) / 72);
|
||
|
pFont->CreateFont(height, 0, 0, 0, FW_NORMAL, 0, 0, 0,
|
||
|
DEFAULT_CHARSET, OUT_CHARACTER_PRECIS, CLIP_CHARACTER_PRECIS,
|
||
|
DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE, typeFace);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Get a Win32 error string for an error code returned by GetLastError.
|
||
|
*/
|
||
|
void
|
||
|
GetWin32ErrorString(DWORD err, CString* pStr)
|
||
|
{
|
||
|
DWORD count;
|
||
|
LPVOID lpMsgBuf;
|
||
|
|
||
|
count = FormatMessage(
|
||
|
FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
||
|
FORMAT_MESSAGE_FROM_SYSTEM |
|
||
|
FORMAT_MESSAGE_IGNORE_INSERTS,
|
||
|
NULL,
|
||
|
err /*GetLastError()*/,
|
||
|
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
|
||
|
(LPTSTR) &lpMsgBuf,
|
||
|
0,
|
||
|
NULL
|
||
|
);
|
||
|
|
||
|
if (!count) {
|
||
|
WMSG1("FormatMessage on err=0x%08lx failed\n", err);
|
||
|
pStr->Format("system error 0x%08lx.\n", err);
|
||
|
} else {
|
||
|
*pStr = (LPCTSTR)lpMsgBuf;
|
||
|
//MessageBox( NULL, (LPCTSTR)lpMsgBuf, "Error", MB_OK | MB_ICONINFORMATION );
|
||
|
LocalFree( lpMsgBuf );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Post a failure message.
|
||
|
*/
|
||
|
void
|
||
|
ShowFailureMsg(CWnd* pWnd, const CString& msg, int titleStrID)
|
||
|
{
|
||
|
CString failed;
|
||
|
|
||
|
failed.LoadString(titleStrID);
|
||
|
pWnd->MessageBox(msg, failed, MB_OK | MB_ICONERROR);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Show context help, based on the control ID.
|
||
|
*/
|
||
|
BOOL
|
||
|
ShowContextHelp(CWnd* pWnd, HELPINFO* lpHelpInfo)
|
||
|
{
|
||
|
pWnd->WinHelp((DWORD) lpHelpInfo->iCtrlId, HELP_CONTEXTPOPUP);
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Returns "true" if we're running on Win9x (Win95, Win98, WinME), "false"
|
||
|
* if not (could be WinNT/2K/XP or even Win31 with Win32s).
|
||
|
*/
|
||
|
bool
|
||
|
IsWin9x(void)
|
||
|
{
|
||
|
OSVERSIONINFO osvers;
|
||
|
BOOL result;
|
||
|
|
||
|
osvers.dwOSVersionInfoSize = sizeof(osvers);
|
||
|
result = ::GetVersionEx(&osvers);
|
||
|
assert(result != FALSE);
|
||
|
|
||
|
if (osvers.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS)
|
||
|
return true;
|
||
|
else
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* ===========================================================================
|
||
|
* Miscellaneous
|
||
|
* ===========================================================================
|
||
|
*/
|
||
|
|
||
|
/*
|
||
|
* Pull a pascal string out of a buffer and stuff it into "*pStr".
|
||
|
*
|
||
|
* Returns the length of the string found, or -1 on error.
|
||
|
*/
|
||
|
int
|
||
|
GetPascalString(const char* buf, long maxLen, CString* pStr)
|
||
|
{
|
||
|
int len = *buf++;
|
||
|
int retLen = len;
|
||
|
*pStr = "";
|
||
|
|
||
|
if (len > maxLen) {
|
||
|
WMSG2("Invalid pascal string -- len=%d, maxLen=%d\n", len, maxLen);
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
while (len--) {
|
||
|
if (*buf == '\0') {
|
||
|
/* this suggests that we're not reading a pascal string */
|
||
|
WMSG0("Found pascal string with '\\0' in it\n");
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
/* not the fastest approach, but it'll do */
|
||
|
*pStr += *buf++;
|
||
|
}
|
||
|
|
||
|
return retLen;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Dump a block of stuff to the log file.
|
||
|
*/
|
||
|
void
|
||
|
LogHexDump(const void* vbuf, long len)
|
||
|
{
|
||
|
const unsigned char* buf = (const unsigned char*) vbuf;
|
||
|
char outBuf[10 + 16*3 +1 +8]; // addr: 00 11 22 ... + 8 bytes slop
|
||
|
bool skipFirst;
|
||
|
long addr;
|
||
|
char* cp = nil;
|
||
|
int i;
|
||
|
|
||
|
WMSG2(" Memory at 0x%08lx %ld bytes:\n", buf, len);
|
||
|
if (len <= 0)
|
||
|
return;
|
||
|
|
||
|
addr = (int)buf & ~0x0f;
|
||
|
if (addr != (int) buf) {
|
||
|
sprintf(outBuf, "%08lx: ", addr);
|
||
|
for (i = addr; i < (int) buf; i++)
|
||
|
strcat(outBuf, " ");
|
||
|
cp = outBuf + strlen(outBuf);
|
||
|
skipFirst = false;
|
||
|
} else {
|
||
|
skipFirst = true;
|
||
|
}
|
||
|
|
||
|
while (len--) {
|
||
|
if (((int) buf & 0x0f) == 0) {
|
||
|
/* output the old, start a new line */
|
||
|
if (skipFirst) {
|
||
|
skipFirst = false;
|
||
|
} else {
|
||
|
WMSG1(" %s\n", outBuf);
|
||
|
addr += 16;
|
||
|
}
|
||
|
sprintf(outBuf, "%08lx: ", addr);
|
||
|
cp = outBuf + strlen(outBuf);
|
||
|
}
|
||
|
|
||
|
sprintf(cp, "%02x ", *buf++);
|
||
|
cp += 3;
|
||
|
}
|
||
|
|
||
|
/* output whatever is left */
|
||
|
WMSG1(" %s\n", outBuf);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Compute a percentage.
|
||
|
*/
|
||
|
int
|
||
|
ComputePercent(LONGLONG part, LONGLONG full)
|
||
|
{
|
||
|
LONGLONG perc;
|
||
|
|
||
|
if (!part && !full)
|
||
|
return 100; /* file is zero bytes long */
|
||
|
|
||
|
if (part < 21474836)
|
||
|
perc = (part * 100) / full;
|
||
|
else
|
||
|
perc = part / (full/100);
|
||
|
|
||
|
/* don't say "0%" if it's not actually zero... it looks dumb */
|
||
|
if (!perc && full)
|
||
|
perc = 1;
|
||
|
|
||
|
return (int) perc;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Format a time_t into a string.
|
||
|
*
|
||
|
* (Should take format as an argument, so we can use global format set by
|
||
|
* user preferences.)
|
||
|
*/
|
||
|
void
|
||
|
FormatDate(time_t when, CString* pStr)
|
||
|
{
|
||
|
if (when == kDateNone) {
|
||
|
*pStr = "[No Date]";
|
||
|
} else if (when == kDateInvalid) {
|
||
|
*pStr = "<invalid>";
|
||
|
} else {
|
||
|
CTime modWhen(when);
|
||
|
*pStr = modWhen.Format("%d-%b-%y %H:%M");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Case-insensitive version of strstr(), pulled from the MSDN stuff that
|
||
|
* comes with VC++6.0.
|
||
|
*
|
||
|
* The isalpha() stuff is an optimization, so they can skip the tolower()
|
||
|
* in the outer loop comparison.
|
||
|
*/
|
||
|
char*
|
||
|
stristr(const char* string1, const char* string2)
|
||
|
{
|
||
|
char *cp1 = (char*) string1, *cp2, *cp1a;
|
||
|
char first; // get the first char in string to find
|
||
|
|
||
|
first = string2[0]; // first char often won't be alpha
|
||
|
if (isalpha(first)) {
|
||
|
first = tolower(first);
|
||
|
for ( ; *cp1 != '\0'; cp1++)
|
||
|
{
|
||
|
if (tolower(*cp1) == first)
|
||
|
{
|
||
|
for (cp1a = &cp1[1], cp2 = (char*) &string2[1]; ;
|
||
|
cp1a++, cp2++)
|
||
|
{
|
||
|
if (*cp2 == '\0')
|
||
|
return cp1;
|
||
|
if (tolower(*cp1a) != tolower(*cp2))
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
for ( ; *cp1 != '\0' ; cp1++)
|
||
|
{
|
||
|
if (*cp1 == first)
|
||
|
{
|
||
|
for (cp1a = &cp1[1], cp2 = (char*) &string2[1]; ;
|
||
|
cp1a++, cp2++)
|
||
|
{
|
||
|
if (*cp2 == '\0')
|
||
|
return cp1;
|
||
|
if (tolower(*cp1a) != tolower(*cp2))
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Break a string down into its component parts.
|
||
|
*
|
||
|
* "mangle" will be mangled (various bits stomped by '\0'), "argv" will
|
||
|
* receive pointers to the strings, and "*pArgc" will hold the number of
|
||
|
* arguments in the vector. The initial value of "*pArgc" should hold the
|
||
|
* maximum "argv" capacity (including program name in argv[0]).
|
||
|
*
|
||
|
* The argv pointers will point into "mangle"; no new storage will be
|
||
|
* allocated.
|
||
|
*/
|
||
|
void
|
||
|
VectorizeString(char* mangle, char** argv, int* pArgc)
|
||
|
{
|
||
|
bool inWhiteSpace = true;
|
||
|
bool inQuote = false;
|
||
|
char* cp = mangle;
|
||
|
int idx = 0;
|
||
|
|
||
|
while (*cp != '\0') {
|
||
|
ASSERT(!inWhiteSpace || !inQuote);
|
||
|
if (!inQuote && (*cp == ' ' || *cp == '\t')) {
|
||
|
if (!inWhiteSpace && !inQuote) {
|
||
|
/* end of token */
|
||
|
*cp = '\0';
|
||
|
}
|
||
|
inWhiteSpace = true;
|
||
|
} else {
|
||
|
if (inWhiteSpace) {
|
||
|
/* start of token */
|
||
|
if (idx >= *pArgc) {
|
||
|
//WMSG2("Max #of args (%d) exceeded, ignoring '%s'\n",
|
||
|
// *pArgc, cp);
|
||
|
break;
|
||
|
}
|
||
|
argv[idx++] = cp;
|
||
|
}
|
||
|
|
||
|
if (*cp == '"') {
|
||
|
/* consume the quote */
|
||
|
memmove(cp, cp+1, strlen(cp)); // move the last '\0' down too
|
||
|
cp--;
|
||
|
inQuote = !inQuote;
|
||
|
}
|
||
|
|
||
|
inWhiteSpace = false;
|
||
|
}
|
||
|
cp++;
|
||
|
}
|
||
|
|
||
|
if (inQuote) {
|
||
|
WMSG0("WARNING: ended in quote\n");
|
||
|
}
|
||
|
|
||
|
*pArgc = idx;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Convert a sub-string to lower case according to rules for English book
|
||
|
* titles. Assumes the initial string is in all caps.
|
||
|
*/
|
||
|
static void
|
||
|
DowncaseSubstring(CString* pStr, int startPos, int endPos,
|
||
|
bool prevWasSpace)
|
||
|
{
|
||
|
static const char* shortWords[] = {
|
||
|
"of", "the", "a", "an", "and", "to", "in"
|
||
|
};
|
||
|
static const char* leaveAlone[] = {
|
||
|
"BBS", "3D"
|
||
|
};
|
||
|
static const char* justLikeThis[] = {
|
||
|
"ProDOS", "IIe", "IIc", "IIgs"
|
||
|
};
|
||
|
CString token;
|
||
|
bool firstCap = true;
|
||
|
int i;
|
||
|
|
||
|
token = pStr->Mid(startPos, endPos - startPos);
|
||
|
//WMSG1(" TOKEN: '%s'\n", token);
|
||
|
|
||
|
/* these words are left alone */
|
||
|
for (i = 0; i < NELEM(leaveAlone); i++) {
|
||
|
if (token.CompareNoCase(leaveAlone[i]) == 0) {
|
||
|
//WMSG1(" Leaving alone '%s'\n", token);
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* words with specific capitalization */
|
||
|
for (i = 0; i < NELEM(justLikeThis); i++) {
|
||
|
if (token.CompareNoCase(justLikeThis[i]) == 0) {
|
||
|
WMSG2(" Setting '%s' to '%s'\n", token, justLikeThis[i]);
|
||
|
for (int j = startPos; j < endPos; j++)
|
||
|
pStr->SetAt(j, justLikeThis[i][j - startPos]);
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* these words are not capitalized unless they start a phrase */
|
||
|
if (prevWasSpace) {
|
||
|
for (i = 0; i < NELEM(shortWords); i++) {
|
||
|
if (token.CompareNoCase(shortWords[i]) == 0) {
|
||
|
//WMSG1(" No leading cap for '%s'\n", token);
|
||
|
firstCap = false;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* check for roman numerals; we leave those capitalized */
|
||
|
CString romanTest = token.SpanIncluding("IVX");
|
||
|
if (romanTest.GetLength() == token.GetLength()) {
|
||
|
//WMSG1(" Looks like roman numerals '%s'\n", token);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (firstCap)
|
||
|
startPos++;
|
||
|
|
||
|
for (i = startPos; i < endPos; i++) {
|
||
|
pStr->SetAt(i, tolower(pStr->GetAt(i)));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Convert parts of the filename to lower case.
|
||
|
*
|
||
|
* If the name already has lowercase characters, do nothing.
|
||
|
*/
|
||
|
void
|
||
|
InjectLowercase(CString* pStr)
|
||
|
{
|
||
|
int len = pStr->GetLength();
|
||
|
static const char* kGapChars = " .:&-+/\\()<>@*";
|
||
|
int startPos, endPos;
|
||
|
|
||
|
//*pStr = "AND PRODOS FOR THE IIGS";
|
||
|
//len = pStr->GetLength();
|
||
|
//WMSG1("InjectLowercase: '%s'\n", *pStr);
|
||
|
|
||
|
for (int i = 0; i < len; i++) {
|
||
|
char ch = pStr->GetAt(i);
|
||
|
if (ch >= 'a' && ch <= 'z') {
|
||
|
WMSG1("Found lowercase 0x%02x, skipping InjectLower\n", ch);
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
startPos = 0;
|
||
|
while (startPos < len) {
|
||
|
/* find start of token */
|
||
|
char ch;
|
||
|
do {
|
||
|
ch = pStr->GetAt(startPos);
|
||
|
if (strchr(kGapChars, ch) == nil)
|
||
|
break;
|
||
|
startPos++;
|
||
|
} while (startPos < len);
|
||
|
if (startPos == len)
|
||
|
break;
|
||
|
|
||
|
/* find end of token */
|
||
|
endPos = startPos + 1;
|
||
|
while (endPos < len) {
|
||
|
ch = pStr->GetAt(endPos);
|
||
|
if (strchr(kGapChars, ch) != nil)
|
||
|
break;
|
||
|
endPos++;
|
||
|
}
|
||
|
|
||
|
/* endPos now points at first char past end of token */
|
||
|
|
||
|
bool prevWasSpace;
|
||
|
if (startPos == 0)
|
||
|
prevWasSpace = false;
|
||
|
else
|
||
|
prevWasSpace = (pStr->GetAt(startPos-1) == ' ');
|
||
|
DowncaseSubstring(pStr, startPos, endPos, prevWasSpace);
|
||
|
|
||
|
startPos = endPos;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Test to see if a sub-string matches a value in a set of strings. The set
|
||
|
* comes from a semicolon-delimited string.
|
||
|
*/
|
||
|
bool
|
||
|
MatchSemicolonList(const CString set, const CString match)
|
||
|
{
|
||
|
const char* cp;
|
||
|
CString mangle(set);
|
||
|
int matchLen = match.GetLength();
|
||
|
|
||
|
if (!matchLen)
|
||
|
return false;
|
||
|
|
||
|
mangle.Remove(' ');
|
||
|
for (cp = mangle; *cp != '\0'; ) {
|
||
|
if (strncasecmp(cp, match, matchLen) == 0 &&
|
||
|
(cp[matchLen] == ';' || cp[matchLen] == '\0'))
|
||
|
{
|
||
|
WMSG2("+++ Found '%s' at '%s'\n", (LPCTSTR) match, cp);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
while (*cp != ';' && *cp != '\0')
|
||
|
cp++;
|
||
|
if (*cp == ';')
|
||
|
cp++;
|
||
|
}
|
||
|
|
||
|
WMSG2("--- No match for '%s' in '%s'\n", (LPCTSTR) match, (LPCTSTR) set);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Like strcpy(), but allocate with new[] instead.
|
||
|
*
|
||
|
* If "str" is nil, or "new" fails, this returns nil.
|
||
|
*/
|
||
|
char*
|
||
|
StrcpyNew(const char* str)
|
||
|
{
|
||
|
char* newStr;
|
||
|
|
||
|
if (str == nil)
|
||
|
return nil;
|
||
|
newStr = new char[strlen(str)+1];
|
||
|
if (newStr != nil)
|
||
|
strcpy(newStr, str);
|
||
|
return newStr;
|
||
|
}
|