/* * 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 /* * =========================================================================== * 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() LRESULT 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 != NULL); ASSERT(pbBuff != NULL); ASSERT(cb != 0); ASSERT(pcb != NULL); 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; //LOGI("ESC: copyLen=%d, now fLen=%d", copyLen, pThis->fLen); bail: *pcb = copyLen; return 0; } /* * =========================================================================== * ExpandBuffer * =========================================================================== */ int ExpandBuffer::CreateWorkBuf(void) { if (fWorkBuf != NULL) { ASSERT(fWorkMax > 0); return 0; } assert(fInitialSize > 0); fWorkBuf = new char[fInitialSize]; if (fWorkBuf == NULL) return -1; fWorkCount = 0; fWorkMax = fInitialSize; return 0; } void ExpandBuffer::SeizeBuffer(char** ppBuf, long* pLen) { *ppBuf = fWorkBuf; *pLen = fWorkCount; fWorkBuf = NULL; // discard pointer so we don't free it fWorkCount = 0; fWorkMax = 0; } int ExpandBuffer::GrowWorkBuf(void) { int newIncr = fWorkMax; if (newIncr > kWorkBufMaxIncrement) newIncr = kWorkBufMaxIncrement; LOGV("Extending buffer by %d (count=%d, max=%d)", newIncr, fWorkCount, fWorkMax); fWorkMax += newIncr; /* debug-only check to catch runaways */ // ASSERT(fWorkMax < 1024*1024*24); char* newBuf = new char[fWorkMax]; if (newBuf == NULL) { LOGE("ALLOC FAILURE (%ld)", 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; } void ExpandBuffer::Write(const unsigned char* buf, long len) { if (fWorkBuf == NULL) CreateWorkBuf(); while (fWorkCount + len >= fWorkMax) { if (GrowWorkBuf() != 0) return; } ASSERT(fWorkCount + len < fWorkMax); memcpy(fWorkBuf + fWorkCount, buf, len); fWorkCount += len; } void ExpandBuffer::Putc(char ch) { Write((const unsigned char*) &ch, 1); } void ExpandBuffer::Printf(_Printf_format_string_ const char* format, ...) { va_list args; ASSERT(format != NULL); if (fWorkBuf == NULL) CreateWorkBuf(); va_start(args, format); if (format != NULL) { 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 * =========================================================================== */ void EnableControl(CDialog* pDlg, int id, bool enable) { CWnd* pWnd = pDlg->GetDlgItem(id); if (pWnd == NULL) { LOGI("GLITCH: control %d not found in dialog", id); ASSERT(false); } else { pWnd->EnableWindow(enable); } } void MoveControl(CDialog* pDlg, int id, int deltaX, int deltaY, bool redraw) { CWnd* pWnd; CRect rect; pWnd = pDlg->GetDlgItem(id); ASSERT(pWnd != NULL); if (pWnd == NULL) return; pWnd->GetWindowRect(&rect); pDlg->ScreenToClient(&rect); rect.left += deltaX; rect.right += deltaX; rect.top += deltaY; rect.bottom += deltaY; pWnd->MoveWindow(&rect, redraw); } void StretchControl(CDialog* pDlg, int id, int deltaX, int deltaY, bool redraw) { CWnd* pWnd; CRect rect; pWnd = pDlg->GetDlgItem(id); ASSERT(pWnd != NULL); if (pWnd == NULL) return; pWnd->GetWindowRect(&rect); pDlg->ScreenToClient(&rect); rect.right += deltaX; rect.bottom += deltaY; pWnd->MoveWindow(&rect, redraw); } 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); } HDWP MoveControl(HDWP hdwp, CDialog* pDlg, int id, int deltaX, int deltaY, bool redraw) { CWnd* pWnd; CRect rect; pWnd = pDlg->GetDlgItem(id); ASSERT(pWnd != NULL); if (pWnd == NULL) 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, NULL, rect.left, rect.top, rect.Width(), rect.Height(), 0); return hdwp; } HDWP StretchControl(HDWP hdwp, CDialog* pDlg, int id, int deltaX, int deltaY, bool redraw) { CWnd* pWnd; CRect rect; pWnd = pDlg->GetDlgItem(id); ASSERT(pWnd != NULL); if (pWnd == NULL) return hdwp; pWnd->GetWindowRect(&rect); pDlg->ScreenToClient(&rect); rect.right += deltaX; rect.bottom += deltaY; hdwp = DeferWindowPos(hdwp, pWnd->m_hWnd, NULL, rect.left, rect.top, rect.Width(), rect.Height(), 0); return hdwp; } 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 != NULL); if (pWnd == NULL) 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, NULL, rect.left, rect.top, rect.Width(), rect.Height(), 0); return hdwp; } int GetDlgButtonCheck(CWnd* pWnd, int id) { CButton* pButton; pButton = (CButton*) pWnd->GetDlgItem(id); ASSERT(pButton != NULL); if (pButton == NULL) return -1; return pButton->GetCheck(); } void SetDlgButtonCheck(CWnd* pWnd, int id, int checkVal) { CButton* pButton; pButton = (CButton*) pWnd->GetDlgItem(id); ASSERT(pButton != NULL); if (pButton == NULL) return; pButton->SetCheck(checkVal); } void CreateSimpleFont(CFont* pFont, CWnd* pWnd, const WCHAR* 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); } void GetWin32ErrorString(DWORD err, CString* pStr) { DWORD count; LPVOID lpMsgBuf = NULL; 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) { LOGI("FormatMessage on err=0x%08lx failed", err); pStr->Format(L"system error 0x%08lx.\n", err); } else { *pStr = (LPCTSTR)lpMsgBuf; //MessageBox( NULL, (LPCTSTR)lpMsgBuf, "Error", MB_OK | MB_ICONINFORMATION ); LocalFree( lpMsgBuf ); } } void ShowFailureMsg(CWnd* pWnd, const CString& msg, int titleStrID) { CString title; CheckedLoadString(&title, titleStrID); pWnd->MessageBox(msg, title, MB_OK | MB_ICONERROR); } void ShowFailureMsg(CWnd* pWnd, int msgId, int titleStrID) { CString msg, title; CheckedLoadString(&title, titleStrID); CheckedLoadString(&msg, msgId); pWnd->MessageBox(msg, title, MB_OK | MB_ICONERROR); } bool IsWin9x(void) { return false; } void CheckedLoadString(CString* pString, UINT nID) { if (!pString->LoadString(nID)) { LOGW("WARNING: failed to load string %u", nID); *pString = L"!Internal failure!"; } } float GetDesktopScaleFactor() { // The current scale factor for controls is determined by the settings // for the main display at the time the application was launched. If we // don't do this, the controls will get all scrunched together if the // scale is over 100% (issue #53). The settings API doesn't seem to // allow scaling things down, so we're only worried about expansion here. // // https://docs.microsoft.com/en-us/windows-hardware/manufacture/desktop/dpi-related-apis-and-registry-settings?view=windows-11#primary-display-dpi-scale-factor // Get desktop dc HDC desktopDc = ::GetDC(NULL); // Get native resolution int horizontalDPI = GetDeviceCaps(desktopDc, LOGPIXELSX); int verticalDPI = GetDeviceCaps(desktopDc, LOGPIXELSY); float scaleFactor = horizontalDPI / 96.0f; LOGD("Native DPI: %dx%d, scaleFactor=%.3f", horizontalDPI, verticalDPI, scaleFactor); return scaleFactor; } /* * =========================================================================== * Miscellaneous * =========================================================================== */ int GetPascalString(const uint8_t* buf, long maxLen, CString* pStr) { int len = *buf++; int retLen = len; *pStr = ""; if (len > maxLen) { LOGW("Invalid pascal string -- len=%d, maxLen=%d", len, maxLen); return -1; } while (len--) { if (*buf == '\0') { /* this suggests that we're not reading a pascal string */ LOGW("Found pascal string with '\\0' in it"); return -1; } /* not the fastest approach, but it'll do */ *pStr += *buf++; } return retLen; } 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; uintptr_t addr; char* cp = NULL; int i; LOGI(" Memory at 0x%p %ld bytes:", buf, len); if (len <= 0) return; addr = (uintptr_t)buf & ~0x0f; if (addr != (uintptr_t) 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 (((uintptr_t) buf & 0x0f) == 0) { /* output the old, start a new line */ if (skipFirst) { skipFirst = false; } else { LOGI(" %hs", outBuf); addr += 16; } sprintf(outBuf, "%08lx: ", addr); cp = outBuf + strlen(outBuf); } ASSERT(cp != NULL); sprintf(cp, "%02x ", *buf++); cp += 3; } /* output whatever is left */ LOGI(" %hs", outBuf); } 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; } void FormatDate(time_t when, CString* pStr) { if (when == kDateNone) { *pStr = L"[No Date]"; } else if (when == kDateInvalid) { *pStr = L""; } else { CTime modWhen(when); *pStr = modWhen.Format(L"%d-%b-%y %H:%M"); } } const WCHAR* Stristr(const WCHAR* string1, const WCHAR* string2) { WCHAR *cp1 = (WCHAR*)string1, *cp2, *cp1a; WCHAR first; // get the first char in string to find first = string2[0]; // first char often won't be alpha if (iswalpha(first)) { first = towlower(first); for ( ; *cp1 != '\0'; cp1++) { if (towlower(*cp1) == first) { for (cp1a = &cp1[1], cp2 = (WCHAR*) &string2[1];; cp1a++, cp2++) { if (*cp2 == '\0') return cp1; if (towlower(*cp1a) != towlower(*cp2)) break; } } } } else { for ( ; *cp1 != '\0' ; cp1++) { if (*cp1 == first) { for (cp1a = &cp1[1], cp2 = (WCHAR*) &string2[1];; cp1a++, cp2++) { if (*cp2 == '\0') return cp1; if (towlower(*cp1a) != towlower(*cp2)) break; } } } } return NULL; } void VectorizeString(WCHAR* mangle, WCHAR** argv, int* pArgc) { bool inWhiteSpace = true; bool inQuote = false; WCHAR* 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) { //LOGI("Max #of args (%d) exceeded, ignoring '%ls'", // *pArgc, cp); break; } argv[idx++] = cp; } if (*cp == '"') { /* consume the quote; move the last '\0' down too */ memmove(cp, cp+1, wcslen(cp) * sizeof(WCHAR)); cp--; inQuote = !inQuote; } inWhiteSpace = false; } cp++; } if (inQuote) { LOGW("WARNING: ended in quote"); } *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(CStringA* 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" }; CStringA token; bool firstCap = true; int i; token = pStr->Mid(startPos, endPos - startPos); LOGV(" TOKEN: '%s'", (LPCSTR) token); /* these words are left alone */ for (i = 0; i < NELEM(leaveAlone); i++) { if (token.CompareNoCase(leaveAlone[i]) == 0) { LOGV(" Leaving alone '%s'", (LPCSTR) token); return; } } /* words with specific capitalization */ for (i = 0; i < NELEM(justLikeThis); i++) { if (token.CompareNoCase(justLikeThis[i]) == 0) { LOGI(" Setting '%s' to '%s'", (LPCSTR) 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) { LOGV(" No leading cap for '%s'", (LPCSTR) token); firstCap = false; break; } } } /* check for roman numerals; we leave those capitalized */ CString romanTest = token.SpanIncluding("IVX"); if (romanTest.GetLength() == token.GetLength()) { LOGV(" Looks like roman numerals '%s'", (LPCSTR) token); return; } if (firstCap) startPos++; for (i = startPos; i < endPos; i++) { pStr->SetAt(i, tolower(pStr->GetAt(i))); } } void InjectLowercase(CStringA* pStr) { int len = pStr->GetLength(); static const char kGapChars[] = " .:&-+/\\()<>@*"; int startPos, endPos; //*pStr = "AND PRODOS FOR THE IIGS"; //len = pStr->GetLength(); //LOGI("InjectLowercase: '%ls'", (LPCWSTR) *pStr); for (int i = 0; i < len; i++) { char ch = pStr->GetAt(i); if (ch >= 'a' && ch <= 'z') { LOGI("Found lowercase 0x%04x, skipping InjectLower", ch); return; } } startPos = 0; while (startPos < len) { /* find start of token */ char ch; do { ch = pStr->GetAt(startPos); if (strchr(kGapChars, ch) == NULL) 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) != NULL) 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; } } bool MatchSemicolonList(const CString set, const CString match) { const WCHAR* cp; CString mangle(set); int matchLen = match.GetLength(); if (!matchLen) return false; mangle.Remove(' '); for (cp = mangle; *cp != '\0'; ) { if (wcsnicmp(cp, match, matchLen) == 0 && (cp[matchLen] == ';' || cp[matchLen] == '\0')) { LOGI("+++ Found '%ls' at '%ls'", (LPCWSTR) match, cp); return true; } while (*cp != ';' && *cp != '\0') cp++; if (*cp == ';') cp++; } LOGD("--- No match for '%ls' in '%ls'", (LPCWSTR) match, (LPCWSTR) set); return false; } char* StrcpyNew(const char* str) { char* newStr; if (str == NULL) return NULL; newStr = new char[strlen(str)+1]; if (newStr != NULL) strcpy(newStr, str); return newStr; }