/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "bagel/hodjnpodj/hnplibs/stdafx.h" #include "bagel/hodjnpodj/hnplibs/rules.h" #include "bagel/hodjnpodj/hnplibs/button.h" #include "bagel/hodjnpodj/globals.h" #include "bagel/boflib/sound.h" // namespace Bagel { namespace HodjNPodj { #define BUILD_FOR_DLL false #define COLOR_BUTTONS true #define SCROLL_PIECES 6 // number of mid-scroll segments #define SCROLL_SPEC ".\\art\\lscroll.bmp" // path for scroll DIB on disk #define SCROLL_TOP_SPEC ".\\art\\lscrollt.bmp" // path for scroll's top section DIB on disk #define SCROLL_BOT_SPEC ".\\art\\lscrollb.bmp" // path for scroll's bottom section DIB on disk #define SCROLL_MID_SPEC ".\\art\\lscrollm.bmp" // path for scroll's middle section DIB on disk #define BUTTON_DY 15 // offset for Okay button from scroll base #define SCROLL_STRIP_WIDTH 10 // width of scroll middle to reveal per interval #define SCROLL_STRIP_DELAY 1000 // delay to wait after each partial scroll unfurling #define TEXT_BUFFER_SIZE 1024 // # characters in the text input buffer #define TEXT_LEFT_MARGIN 55 // left margin offset for display of text #define TEXT_TOP_MARGIN 5 // top margin offset for display of text #define TEXT_BOTTOM_MARGIN 20 // bottom margin offset for display of text #define TEXT_WIDTH 435 // width of text display area #define TEXT_MORE_DX 120 // offset of "more" indicator from right margin #define TEXT_MORE_DY 10 // offset of "more" indicator bottom of scroll #define MORE_TEXT_BLURB "[ More ]" // actual text to display for "more" indicator #define MORE_TEXT_LENGTH 8 // # characters in "more" indicator string #define TEXT_NEWLINE '\\' // character that indicates enforced line break LRESULT PrefHookProc(int, WPARAM, LPARAM); // keyboard hook procedure definition static FARPROC pKbdHook = nullptr; // pointer to hook procedure static HHOOK hKbdHook = nullptr; // handle for hook procedure static CRules *pRulesDialog = nullptr; // pointer to our rules dialog box static CWnd *pParentWnd = nullptr; // parent window pointer static CColorButton *pOKButton = nullptr; // OKAY button on scroll static CDibDoc *pScrollTopDIB = nullptr, // DIB for scroll top section *pScrollMidDIB = nullptr, // DIB for scroll mid section *pScrollBotDIB = nullptr; // DIB for scroll bottom section static CBitmap *pScrollTopBitmap = nullptr, // bitmap for scroll top section *pScrollTopBitmapOld = nullptr, // bitmap previously mapped to top section context *pScrollMidBitmap = nullptr, // bitmap for scroll mid section *pScrollMidBitmapOld = nullptr, // bitmap previously mapped to mid section context *pScrollBotBitmap = nullptr, // bitmap for scroll bottom section *pScrollBotBitmapOld = nullptr; // bitmap previously mapped to bottom section context static CBitmap *pScrollTopMask = nullptr, // mask for scroll top section bitmap *pScrollTopMaskOld = nullptr, // bitmap previously mapped to top mask context *pScrollMidMask = nullptr, // mask for scroll mid section bitmap *pScrollMidMaskOld = nullptr, // bitmap previously mapped to mid mask context *pScrollBotMask = nullptr, // mask for scroll bottom section bitmap *pScrollBotMaskOld = nullptr; // bitmap previously mapped to bottom mask context static CBitmap *pScrollBitmap = nullptr, // bitmap for an entirely blank scroll *pScrollBitmapOld = nullptr, // bitmap previously mapped to the scroll context *pBackgroundBitmap = nullptr, // bitmap containing the background for the scroll *pBackgroundBitmapOld = nullptr, // bitmap previously mapped to the background context *pWorkBitmap = nullptr, // bitmap containing the work area for the scroll *pWorkBitmapOld = nullptr; // bitmap previously mapped to the work area context static CPalette *pScrollPalette = nullptr, // palette used for the scroll *pScrollPalOld = nullptr, // previous palette mapped to scroll context *pBackgroundPalOld = nullptr, // previous palette mapped to background context *pScrollTopPalOld = nullptr, // previous palette mapped to top context *pScrollMidPalOld = nullptr, // previous palette mapped to middle context *pScrollBotPalOld = nullptr, // previous palette mapped to bottom context *pScrollTopMaskPalOld = nullptr, // previous palette mapped to top mask context *pScrollMidMaskPalOld = nullptr, // previous palette mapped to middle mask context *pScrollBotMaskPalOld = nullptr, // previous palette mapped to bottom mask context *pWorkPalOld = nullptr; // previous palette mapped to work area context static CDC *pScrollDC = nullptr, // device context for the scroll bitmap *pBackgroundDC = nullptr, // device context for the background bitmap *pScrollTopDC = nullptr, // device context for the top section bitmap *pScrollMidDC = nullptr, // device context for the middle section bitmap *pScrollBotDC = nullptr, // device context for the bottom section bitmap *pScrollTopMaskDC = nullptr, // device context for the top mask bitmap *pScrollMidMaskDC = nullptr, // device context for the middle section bitmap *pScrollBotMaskDC = nullptr, // device context for the bottom section bitmap *pWorkDC = nullptr; // device context for the work area bitmap static CFont *pFont = nullptr; // font to use for displaying rules text static char chPathName[128]; // buffer to hold path name of the rules file static CFile *pHelpFile = nullptr; // the rules file static uint32 nHelpFileSize = 0; // size of rules file static int nHelpPage = 0; // current page of rules text static uint32 dwHelpPagePosition[100]; // position of each page (# chars from file start) static bool dwHelpPageEOL[100]; // whether page starts with enforced line break static bool bHelpEOF = false; // whether end-of-file has been reached static int tabstop = 20 + TEXT_LEFT_MARGIN;// pixels per tab stop static const char *pSoundPath = nullptr; // path spec for rules narration static CSound *pNarrative = nullptr; // sound object static bool first_time = true; static bool bActiveWindow = false; // whether our window is active static bool bBruteForce = false; // whether we can be clever ///////////////////////////////////////////////////////////////////////////// // CRules dialog BEGIN_MESSAGE_MAP(CRules, CDialog) //{{AFX_MSG_MAP(CRules) ON_WM_CREATE() ON_WM_ERASEBKGND() ON_WM_PAINT() ON_WM_SHOWWINDOW() ON_WM_SIZE() ON_WM_MOUSEMOVE() ON_WM_SETCURSOR() ON_WM_NCMOUSEMOVE() ON_WM_LBUTTONDOWN() ON_WM_DESTROY() ON_WM_ACTIVATE() //}}AFX_MSG_MAP END_MESSAGE_MAP() bool CRules::SetupKeyboardHook() { HINSTANCE hInst; pRulesDialog = this; // retain pointer to our dialog box hInst = AfxGetInstanceHandle(); // get our application instance hKbdHook = SetWindowsHookEx(WH_KEYBOARD, PrefHookProc, hInst, GetCurrentTask()); if (hKbdHook == nullptr) // plug in our keyboard hook return false; return true; } void CRules::RemoveKeyboardHook() { if (m_bKeyboardHook) { UnhookWindowsHookEx(hKbdHook); // unhook our keyboard procedure FreeProcInstance(pKbdHook); // release our procedure pointer } pKbdHook = nullptr; pRulesDialog = nullptr; hKbdHook = nullptr; m_bKeyboardHook = false; } LRESULT PrefHookProc(int code, WPARAM wParam, LPARAM lParam) { if (code < 0) // required to punt to next hook return (CallNextHookEx((HHOOK) pKbdHook, code, wParam, lParam)); if (lParam & 0xA0000000) // ignore ALT and key release return false; if (bActiveWindow) switch (wParam) { // process only the keys we are looking for case VK_UP: // ... letting the default dialog procedure case VK_NUMPAD8: // ... deal with all the rest case VK_PRIOR: // go to previous page of text if (nHelpPage > 0) (*pRulesDialog).UpdateScroll(nHelpPage - 1); return true; case VK_DOWN: // go to next page of text case VK_NUMPAD2: case VK_NEXT: if (!bHelpEOF) (*pRulesDialog).UpdateScroll(nHelpPage + 1); return true; case VK_HOME: // go to first page of text if (nHelpPage > 0) (*pRulesDialog).UpdateScroll(0); return true; case VK_END: // go to last page of text while (!bHelpEOF) { (*pRulesDialog).UpdateScroll(nHelpPage + 1); } return true; } return false; } CRules::CRules(CWnd *pParent, const char *pszPathName, CPalette *pPalette, const char *pszSoundPath) : CDialog(CRules::IDD, pParent) { Common::strcpy_s(chPathName, pszPathName); // retain path to rules file on disk pScrollPalette = pPalette; // retain palette to be used pParentWnd = nullptr; // retain parent window pointer pSoundPath = pszSoundPath; // retain path to sound file on disk //{{AFX_DATA_INIT(CRules) // NOTE: the ClassWizard will add member initialization here //}}AFX_DATA_INIT } void CRules::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); //{{AFX_DATA_MAP(CRules) // NOTE: the ClassWizard will add DDX and DDV calls here //}}AFX_DATA_MAP } ///////////////////////////////////////////////////////////////////////////// // CRules message handlers void CRules::OnOK() { ClearDialogImage(); CDialog::EndDialog(IDC_RULES_OKAY); } void CRules::OnCancel() { ClearDialogImage(); CDialog::OnCancel(); } void CRules::OnDestroy() { bool bUpdateNeeded; HCURSOR hNewCursor = nullptr; CWinApp *pMyApp; pMyApp = AfxGetApp(); hNewCursor = (*pMyApp).LoadStandardCursor(IDC_ARROW); //if (hNewCursor != nullptr); SetCursor(hNewCursor); // Free the objects delete pNarrative; pNarrative = nullptr; delete pOKButton; pOKButton = nullptr; delete pFont; pFont = nullptr; (*pHelpFile).Close(); // close and release the rules file delete pHelpFile; pHelpFile = nullptr; if (pBackgroundBitmap != nullptr) { bUpdateNeeded = (*pParentWnd).GetUpdateRect(nullptr, false); if (bUpdateNeeded) (*pParentWnd).ValidateRect(nullptr); } if (!bBruteForce) { ReleaseCompatibleContext(pWorkDC, pWorkBitmap, pWorkBitmapOld, pWorkPalOld); ReleaseCompatibleContext(pScrollDC, pScrollBitmap, pScrollBitmapOld, pScrollPalOld); ReleaseCompatibleContext(pBackgroundDC, pBackgroundBitmap, pBackgroundBitmapOld, pBackgroundPalOld); ReleaseCompatibleContext(pScrollMidDC, pScrollMidBitmap, pScrollMidBitmapOld, pScrollMidPalOld); ReleaseCompatibleContext(pScrollMidMaskDC, pScrollMidMask, pScrollMidMaskOld, pScrollMidMaskPalOld); } if (m_bKeyboardHook) // remove keyboard hook, if present RemoveKeyboardHook(); CDialog::OnDestroy(); } bool CRules::OnInitDialog() { CWnd *pButton; // pointer to the OKAY button CRect myRect; // rectangle that holds the button location int x, y, dx, dy; // used for calculating positioning info bool bSuccess; CDialog::OnInitDialog(); // do basic dialog initialization if (pParentWnd == nullptr) // get our parent window pParentWnd = ((CWnd *) this)->GetParent(); // ... as passed to us or inquired about (*pParentWnd).GetWindowRect(&myRect); x = myRect.left + (((myRect.right - myRect.left) - ScrollRect.right) >> 1); y = myRect.top + (((myRect.bottom - myRect.top) - ScrollRect.bottom) >> 1); MoveWindow(x, y, ScrollRect.right, ScrollRect.bottom); // center the dialog box on the parent pButton = GetDlgItem((int) GetDefID()); // get the window for the okay button ASSERT(pButton != nullptr); // ... and verify we have it (*pButton).GetWindowRect(&myRect); // get the button's position and size dx = myRect.right - myRect.left; // calculate where to place the button x = (ScrollRect.right - dx) >> 1; // ... centered at the bottom edge dy = myRect.bottom - myRect.top; y = ScrollRect.bottom - dy - BUTTON_DY; (*pButton).MoveWindow(x, y, dx, dy); // reposition the button OkayRect.SetRect(x, y, x + dx, y + dy); #if COLOR_BUTTONS pOKButton = new CColorButton(); // build a color QUIT button to let us exit ASSERT(pOKButton != nullptr); (*pOKButton).SetPalette(pScrollPalette); // set the palette to use bSuccess = (*pOKButton).SetControl((int) GetDefID(), this); // tie to the dialog control ASSERT(bSuccess); #endif if (pSoundPath != nullptr) pNarrative = new CSound(this, pSoundPath, SOUND_WAVE | SOUND_ASYNCH | SOUND_AUTODELETE); m_bKeyboardHook = SetupKeyboardHook(); // establish keyboard hook first_time = true; return true; // return true unless focused on a control } void CRules::OnActivate(unsigned int nState, CWnd *pWndOther, bool bMinimized) { bool bUpdateNeeded; switch (nState) { case WA_INACTIVE: bActiveWindow = false; break; case WA_ACTIVE: case WA_CLICKACTIVE: bActiveWindow = true; bUpdateNeeded = GetUpdateRect(nullptr, false); if (bUpdateNeeded) InvalidateRect(nullptr, false); } } void CRules::OnPaint() { CPaintDC dc(this); // device context for painting CPalette *pPalOld = nullptr; CDibDoc *pDibDoc; ShowWaitCursor(); // put up the hourglass cursor if (pScrollPalette != nullptr) { // map in our palette pPalOld = dc.SelectPalette(pScrollPalette, false); dc.RealizePalette(); // .. and make the system use it } if (first_time) { // unfurl scroll visually first_time = false; // ... but only if this is the first time UnfurlScroll(&dc); // ... we are updating the screen ReleaseCompatibleContext(pScrollTopDC, pScrollTopBitmap, pScrollTopBitmapOld, pScrollTopPalOld); ReleaseCompatibleContext(pScrollBotDC, pScrollBotBitmap, pScrollBotBitmapOld, pScrollBotPalOld); ReleaseCompatibleContext(pScrollTopMaskDC, pScrollTopMask, pScrollTopMaskOld, pScrollTopMaskPalOld); ReleaseCompatibleContext(pScrollBotMaskDC, pScrollBotMask, pScrollBotMaskOld, pScrollBotMaskPalOld); if (pNarrative != nullptr) // Play the narration (*pNarrative).play(); } else if (bBruteForce) { // need to paint directly to screen pDibDoc = LoadScrollDIB(SCROLL_SPEC, nullptr); if (pDibDoc != nullptr) { PaintScrollDIB(&dc, pDibDoc); delete pDibDoc; } WritePage(&dc, nHelpPage); UpdateMore(&dc); // update the "more" indicator } else dc.BitBlt( // update the screen with just the 0, 0, // ... current page of text ScrollRect.right, ScrollRect.bottom, pScrollDC, 0, 0, SRCCOPY); if (pScrollPalette != nullptr) // map out our palette dc.SelectPalette(pPalOld, false); DoArrowCursor(); // return to an arrow cursor } bool CRules::OnEraseBkgnd(CDC *pDC) { return true; // do not automatically erase background to white } void CRules::ClearDialogImage() { delete pOKButton; pOKButton = nullptr; if (pBackgroundBitmap != nullptr) ValidateRect(nullptr); RefreshBackground(); } void CRules::RefreshBackground() { CDC *pDC; CPalette *pPalOld = nullptr; if (pBackgroundBitmap == nullptr) return; pDC = GetDC(); // get a context for our window if (pScrollPalette != nullptr) { // map in our palette pPalOld = (*pDC).SelectPalette(pScrollPalette, false); (*pDC).RealizePalette(); // .. and make the system use it } (*pDC).BitBlt( // repaint the background as it was 0, 0, ScrollRect.right, ScrollRect.bottom, pBackgroundDC, 0, 0, SRCCOPY); if (pScrollPalette != nullptr) // map out our palette (*pDC).SelectPalette(pPalOld, false); ReleaseDC(pDC); // release the context } void CRules::UnfurlScroll(CDC *pDC) { int i, j, k, h; int scroll_delta; CRect dstRect, dst2Rect; CDibDoc *pDibDoc; if (!bBruteForce) bBruteForce = !CreateWorkAreas(pDC); // create our work areas if (bBruteForce) { ReleaseCompatibleContext(pWorkDC, pWorkBitmap, pWorkBitmapOld, pWorkPalOld); ReleaseCompatibleContext(pScrollDC, pScrollBitmap, pScrollBitmapOld, pScrollPalOld); ReleaseCompatibleContext(pBackgroundDC, pBackgroundBitmap, pBackgroundBitmapOld, pBackgroundPalOld); ReleaseCompatibleContext(pScrollMidDC, pScrollMidBitmap, pScrollMidBitmapOld, pScrollMidPalOld); ReleaseCompatibleContext(pScrollMidMaskDC, pScrollMidMask, pScrollMidMaskOld, pScrollMidMaskPalOld); pDibDoc = LoadScrollDIB(SCROLL_SPEC, nullptr); if (pDibDoc != nullptr) { PaintScrollDIB(pDC, pDibDoc); delete pDibDoc; } WritePage(pDC, 0); UpdateMore(pDC); ScrollTopCurlRect = ScrollTopRect; ScrollBotCurlRect = ScrollBotRect; ScrollBotCurlRect.top += ScrollTopCurlRect.bottom + ScrollMidRect.bottom * SCROLL_PIECES; ScrollBotCurlRect.bottom += ScrollTopCurlRect.bottom + ScrollMidRect.bottom * SCROLL_PIECES; return; } (*pBackgroundDC).BitBlt( // save the entire background 0, 0, ScrollRect.right, ScrollRect.bottom, pDC, 0, 0, SRCCOPY); (*pScrollDC).BitBlt( // copy background to scroll context 0, 0, // ... so we can construct the scroll ScrollRect.right, // ... on top of it in a masked manner ScrollRect.bottom, pDC, 0, 0, SRCCOPY); // setup the top part of the scroll ScrollTopCurlRect = ScrollTopRect; // record where we're putting the top curl (*pScrollDC).BitBlt( // mask out where the top curl goes 0, 0, ScrollTopRect.right, ScrollTopRect.bottom, pScrollTopMaskDC, 0, 0, SRCAND); (*pScrollTopMaskDC).BitBlt( // invert the top curl mask 0, 0, ScrollTopRect.right, ScrollTopRect.bottom, nullptr, 0, 0, DSTINVERT); (*pScrollTopDC).BitBlt( // remove transparent area from top curl 0, 0, ScrollTopRect.right, ScrollTopRect.bottom, pScrollTopMaskDC, 0, 0, SRCAND); (*pScrollTopMaskDC).BitBlt( // restore top curl mask 0, 0, ScrollTopRect.right, ScrollTopRect.bottom, nullptr, 0, 0, DSTINVERT); (*pScrollDC).BitBlt( // paint top curl into the scroll 0, 0, ScrollTopRect.right, ScrollTopRect.bottom, pScrollTopDC, 0, 0, SRCPAINT); // setup the middle portion of the scroll dstRect = ScrollMidRect; // setup initial destination for a dstRect.top += ScrollTopRect.bottom; // ... mid section strip of the scroll dstRect.bottom += ScrollTopRect.bottom; (*pScrollMidMaskDC).BitBlt( // invert the mid section mask 0, 0, ScrollMidRect.right, ScrollMidRect.bottom, nullptr, 0, 0, DSTINVERT); (*pScrollMidDC).BitBlt( // mask out transparent part of mid section 0, 0, ScrollMidRect.right, ScrollMidRect.bottom, pScrollMidMaskDC, 0, 0, SRCAND); (*pScrollMidMaskDC).BitBlt( // reset the mid section mask 0, 0, ScrollMidRect.right, ScrollMidRect.bottom, nullptr, 0, 0, DSTINVERT); for (i = 0; i < SCROLL_PIECES; i++) { // build the scroll's mid section srip-wise (*pScrollDC).BitBlt( dstRect.left, // mask out where the image will go dstRect.top + ScrollMidRect.bottom * i, ScrollMidRect.right, ScrollMidRect.bottom, pScrollMidMaskDC, 0, 0, SRCAND); (*pScrollDC).BitBlt( // paint in the mid section image dstRect.left, dstRect.top + ScrollMidRect.bottom * i, ScrollMidRect.right, ScrollMidRect.bottom, pScrollMidDC, 0, 0, SRCPAINT); } // setup the bottom part of the scroll dstRect.top += ScrollMidRect.bottom * SCROLL_PIECES; // record where the bottom curl goes dstRect.bottom = dstRect.top + ScrollBotRect.bottom; ScrollBotCurlRect = dstRect; (*pScrollDC).BitBlt( // mask out where the bottom curl image goes dstRect.left, dstRect.top, ScrollBotRect.right, ScrollBotRect.bottom, pScrollBotMaskDC, 0, 0, SRCAND); (*pScrollBotMaskDC).BitBlt( // invert the bottom curl mask 0, 0, ScrollBotRect.right, ScrollBotRect.bottom, nullptr, 0, 0, DSTINVERT); (*pScrollBotDC).BitBlt( // mask out transparent part of curl 0, 0, ScrollBotRect.right, ScrollBotRect.bottom, pScrollBotMaskDC, 0, 0, SRCAND); (*pScrollBotMaskDC).BitBlt( // reset the mask 0, 0, ScrollBotRect.right, ScrollBotRect.bottom, nullptr, 0, 0, DSTINVERT); (*pScrollDC).BitBlt( // paint in the bottom curl dstRect.left, dstRect.top, ScrollBotRect.right, ScrollBotRect.bottom, pScrollBotDC, 0, 0, SRCPAINT); // write the first page of text WritePage(pScrollDC, 0); // construct first page of text // now display what we built, making the scroll look like it is unfurlinf from top // ... and bottom, by increasing the display of the middle section and moving the // ... top and bottom curls further away j = (ScrollMidRect.bottom * SCROLL_PIECES) / SCROLL_STRIP_WIDTH; // number of times to update display k = (ScrollMidRect.bottom * SCROLL_PIECES) % SCROLL_STRIP_WIDTH; // residual not displayed after main loop h = j & 0x1; // whether there is a dangling odd pixel row j = (j >> 1) + h + ((k > 0) ? 1 : 0); // revise loop count based on residual & dangle scroll_delta = SCROLL_STRIP_WIDTH; // current width of the strip to display dstRect.left = 0; dstRect.right = ScrollRect.right; dstRect.top = (ScrollRect.bottom - (ScrollTopRect.bottom + ScrollBotRect.bottom)) >> 1; dstRect.bottom = dstRect.top + ScrollTopRect.bottom + ScrollBotRect.bottom; for (i = 0; i <= j; i++) { // do each segment in turn dst2Rect = dstRect; (*pWorkDC).BitBlt( // splat in the background for top curl 0, 0, ScrollTopRect.right, ScrollTopRect.bottom, pBackgroundDC, dst2Rect.left, dst2Rect.top, SRCCOPY); (*pWorkDC).BitBlt( // mask out where the top curl goes 0, 0, ScrollTopRect.right, ScrollTopRect.bottom, pScrollTopMaskDC, 0, 0, SRCAND); (*pWorkDC).BitBlt( // insert the top curl background 0, 0, ScrollTopRect.right, ScrollTopRect.bottom, pScrollTopDC, 0, 0, SRCPAINT); if (i == 0) { // if first time, just paint curl to display (*pDC).BitBlt( dst2Rect.left, dst2Rect.top, ScrollTopRect.right, ScrollTopRect.bottom, pWorkDC, 0, 0, SRCCOPY); } else { (*pWorkDC).BitBlt( // ... otherwise paint in a new piece of dst2Rect.left, // ... the middle section ScrollTopRect.bottom, dst2Rect.right, scroll_delta, pScrollDC, dst2Rect.left, dst2Rect.top + ScrollTopRect.bottom, SRCCOPY); (*pDC).BitBlt( // ... then paint it and the curl, thus dst2Rect.left, // ... causing the top to seemingly unfurl dst2Rect.top, // ... by one more strip ScrollTopRect.right, ScrollTopRect.bottom + scroll_delta, pWorkDC, 0, 0, SRCCOPY); } dst2Rect.top = dstRect.bottom - ScrollBotRect.bottom - scroll_delta; dst2Rect.bottom = dstRect.bottom; if ((i == 0) || ((i == 1) && (h == 1))) { // if first time or doing last iteration (*pWorkDC).BitBlt( // ... just paint in the bottom curl 0, 0, dst2Rect.right, scroll_delta, pDC, dst2Rect.left, dst2Rect.top, SRCCOPY); } else { // (*pWorkDC).BitBlt( 0, 0, dst2Rect.right, scroll_delta, pScrollDC, dst2Rect.left, dst2Rect.top, SRCCOPY); } (*pWorkDC).BitBlt( 0, scroll_delta, ScrollBotRect.right, ScrollBotRect.bottom, pBackgroundDC, ScrollBotRect.left, dst2Rect.top + scroll_delta, SRCCOPY); (*pWorkDC).BitBlt( 0, scroll_delta, ScrollBotRect.right, ScrollBotRect.bottom, pScrollBotMaskDC, 0, 0, SRCAND); (*pWorkDC).BitBlt( 0, scroll_delta, ScrollBotRect.right, ScrollBotRect.bottom, pScrollBotDC, 0, 0, SRCPAINT); (*pDC).BitBlt( ScrollBotRect.left, dst2Rect.top, ScrollBotRect.right, ScrollBotRect.bottom + scroll_delta, pWorkDC, 0, 0, SRCCOPY); if ((j == 0) && (h == 1)) { dstRect.top -= scroll_delta >> 1; dstRect.bottom += scroll_delta >> 1; } else { dstRect.top -= scroll_delta; dstRect.bottom += scroll_delta; } if ((i == j - 1) && (k != 0)) { scroll_delta = k >> 1; dstRect.top += SCROLL_STRIP_WIDTH - scroll_delta; dstRect.bottom -= SCROLL_STRIP_WIDTH - scroll_delta; } } UpdateMore(pDC); } void CRules::UpdateScroll(int nPage) { int i; CRect dstRect; CDC *pDC; CPalette *pPalOld = nullptr; if (bBruteForce) { nHelpPage = nPage; InvalidateRect(nullptr, false); return; } pDC = GetDC(); if (pScrollPalette != nullptr) { // map in our palette pPalOld = (*pDC).SelectPalette(pScrollPalette, false); (*pDC).RealizePalette(); // .. and make the system use it } dstRect = ScrollMidRect; // setup initial destination for blts dstRect.top += ScrollTopRect.bottom; dstRect.bottom += ScrollTopRect.bottom; for (i = 0; i < SCROLL_PIECES; i++) { // repaint the scroll midsection artwork (*pScrollDC).BitBlt( // ... mask out where artwork goes dstRect.left, dstRect.top + ScrollMidRect.bottom * i, ScrollMidRect.right, ScrollMidRect.bottom, pScrollMidMaskDC, 0, 0, SRCAND); (*pScrollDC).BitBlt( // ... paint in the scroll artwork dstRect.left, dstRect.top + ScrollMidRect.bottom * i, ScrollMidRect.right, ScrollMidRect.bottom, pScrollMidDC, 0, 0, SRCPAINT); } WritePage(pScrollDC, nPage); // construct the new page of text (*pDC).BitBlt( // paint the result to the sceen TEXT_LEFT_MARGIN, ScrollTopRect.bottom + TEXT_TOP_MARGIN, TEXT_WIDTH, (SCROLL_PIECES * ScrollMidRect.bottom) - TEXT_TOP_MARGIN - 1, pScrollDC, TEXT_LEFT_MARGIN, ScrollTopRect.bottom + TEXT_TOP_MARGIN, SRCCOPY); UpdateMore(pDC); // update the "more" indicator if (pScrollPalette != nullptr) // map out our palette (*pDC).SelectPalette(pPalOld, false); ReleaseDC(pDC); } void CRules::WritePage(CDC *pDC, int nPage) { int i, n, x, y; unsigned int nCropped = 0; unsigned int nCount = 0; char chInBuf[TEXT_BUFFER_SIZE]; CFont *pFontOld = nullptr; CSize textInfo; bool bEOL, bEOF; TEXTMETRIC fontMetrics; nHelpPage = nPage; bEOF = bHelpEOF = false; (*pHelpFile).SeekToBegin(); if ((nHelpPage == 0) && (nPage == 0)) { dwHelpPagePosition[nHelpPage] = (*pHelpFile).GetPosition(); bEOL = dwHelpPageEOL[nHelpPage] = true; } else { (*pHelpFile).Seek(dwHelpPagePosition[nPage], CFile::begin); bEOL = dwHelpPageEOL[nHelpPage]; } dwHelpPagePosition[nHelpPage + 1] = dwHelpPagePosition[nHelpPage]; pFontOld = (*pDC).SelectObject(pFont); (*pDC).GetTextMetrics(&fontMetrics); (*pDC).SetBkMode(TRANSPARENT); i = 0; n = 1; x = TEXT_LEFT_MARGIN; y = ScrollTopRect.bottom + TEXT_TOP_MARGIN; while (true) { try_again: if (i >= (int) nCount) { if (bEOF) break; dwHelpPagePosition[nHelpPage + 1] += i + nCropped; nCount = (*pHelpFile).Read(&chInBuf, TEXT_BUFFER_SIZE); if (nCount < TEXT_BUFFER_SIZE) bEOF = true; i = 0; n = 1; nCropped = 0; } if (bEOL && ((chInBuf[i] == '\r') || (chInBuf[i] == '\n'))) { i += 1; goto try_again; } if (!bEOL && ((chInBuf[i] == ' ') || (chInBuf[i] == '\t'))) { i += 1; goto try_again; } bEOL = false; while (true) { if (i + n > (int) nCount) { n = nCount - i; if (bEOF) break; dwHelpPagePosition[nHelpPage + 1] += i + nCropped; strncpy(&chInBuf[0], &chInBuf[i], n); nCount = (*pHelpFile).Read(&chInBuf[n], TEXT_BUFFER_SIZE - n); nCount += n; if (nCount < TEXT_BUFFER_SIZE) bEOF = true; i = 0; n = 1; nCropped = 0; } if (chInBuf[i + n - 1] == '\r') { crop_byte: Common::copy(&chInBuf[i + n], chInBuf + nCount, &chInBuf[i + n - 1]); nCount -= 1; nCropped += 1; continue; } if (chInBuf[i + n - 1] == '\n') { if ((n > 1) && ((chInBuf[i + n - 2] == ' ') || (chInBuf[i + n - 2] == '-'))) goto crop_byte; else chInBuf[i + n - 1] = ' '; } if (chInBuf[i + n - 1] == TEXT_NEWLINE) { bEOL = true; n -= 1; break; } textInfo = (*pDC).GetTextExtent(&chInBuf[i], n); if (textInfo.cx > TEXT_WIDTH - TEXT_LEFT_MARGIN) { if ((chInBuf[i + n - 1] != ' ') && (chInBuf[i + n - 1] != '\t')) while (n > 0) { n -= 1; if (strchr(" \t,;:-", chInBuf[i + n - 1]) != nullptr) break; } break; } n += 1; } // (*pDC).TextOut(x,y,(const char *) &chInBuf[i],n); (*pDC).TabbedTextOut(x, y, (const char *) &chInBuf[i], n, 1, &tabstop, 0); if (chInBuf[i + n] == '\r') i += 2; else if (chInBuf[i + n] == TEXT_NEWLINE) i += 1; i += n; n = 1; y += fontMetrics.tmHeight; if (y - ScrollTopRect.bottom > (SCROLL_PIECES * ScrollMidRect.bottom) - TEXT_BOTTOM_MARGIN) break; } (*pDC).SelectObject(pFontOld); dwHelpPagePosition[nPage + 1] += i + nCropped; dwHelpPageEOL[nPage + 1] = bEOL; while (true) { if (i >= (int) nCount) { if (bEOF) break; nCount = (*pHelpFile).Read(&chInBuf, TEXT_BUFFER_SIZE); if (nCount < TEXT_BUFFER_SIZE) bEOF = true; i = 0; n = 1; } if (bEOL && ((chInBuf[i] == '\r') || (chInBuf[i] == '\n') || (chInBuf[i] == TEXT_NEWLINE))) { i += 1; dwHelpPagePosition[nHelpPage + 1] += 1; } else break; } if (bEOF && (i >= (int) nCount)) bHelpEOF = true; (*pDC).SelectObject(pFontOld); } void CRules::UpdateMore(CDC *pDC) { int x, y, dx, dy; CFont *pFontOld = nullptr; TEXTMETRIC fontMetrics; CSize textInfo; pFontOld = (*pDC).SelectObject(pFont); (*pDC).GetTextMetrics(&fontMetrics); (*pDC).SetBkMode(TRANSPARENT); x = ScrollRect.right - TEXT_MORE_DX; y = ((ScrollTopRect.bottom - fontMetrics.tmHeight) >> 1) + TEXT_MORE_DY - 3; textInfo = (*pDC).GetTextExtent(MORE_TEXT_BLURB, MORE_TEXT_LENGTH); dx = textInfo.cx; dy = fontMetrics.tmHeight; if (nHelpPage == 0) (*pDC).BitBlt( x, y, dx, dy, pScrollDC, x, y, SRCCOPY); else (*pDC).TextOut(x, y, MORE_TEXT_BLURB, MORE_TEXT_LENGTH); y = ScrollRect.bottom - ScrollBotRect.bottom + ((ScrollBotRect.bottom - fontMetrics.tmHeight) >> 1) - TEXT_MORE_DY; if (bHelpEOF) (*pDC).BitBlt( x, y, dx, dy, pScrollDC, x, y, SRCCOPY); else (*pDC).TextOut(x, y, MORE_TEXT_BLURB, MORE_TEXT_LENGTH); (*pDC).SelectObject(pFontOld); } void CRules::OnShowWindow(bool bShow, unsigned int nStatus) { CDialog::OnShowWindow(bShow, nStatus); // TODO: Add your message handler code here } void CRules::OnSize(unsigned int nType, int cx, int cy) { CDialog::OnSize(nType, cx, cy); // TODO: Add your message handler code here } int CRules::OnCreate(LPCREATESTRUCT lpCreateStruct) { bool bSuccess; int dxDIB, dyDIB; pHelpFile = new CFile(); ASSERT(pHelpFile != nullptr); bSuccess = (*pHelpFile).Open(chPathName, CFile::modeRead | CFile::typeBinary, nullptr); ASSERT(bSuccess); if (!bSuccess) return -1; nHelpFileSize = (*pHelpFile).GetLength(); AddFontResource("msserif.fon"); pFont = new CFont(); ASSERT(pFont != nullptr); bSuccess = (*pFont).CreateFont(16, 0, 0, 0, FW_BOLD, 0, 0, 0, 0, OUT_RASTER_PRECIS, 0, PROOF_QUALITY, FF_ROMAN, "MS Sans Serif"); ASSERT(bSuccess); if ((GetFreeSpace(0) >= (unsigned long) 1000000) && (GlobalCompact((unsigned long) 1000000) >= (unsigned long) 750000)) { pScrollTopDIB = LoadScrollDIB(SCROLL_TOP_SPEC, &ScrollTopRect); pScrollMidDIB = LoadScrollDIB(SCROLL_MID_SPEC, &ScrollMidRect); pScrollBotDIB = LoadScrollDIB(SCROLL_BOT_SPEC, &ScrollBotRect); if ((pScrollTopDIB == nullptr) || (pScrollMidDIB == nullptr) || (pScrollBotDIB == nullptr)) bBruteForce = true; else bBruteForce = false; if (bBruteForce) { if (pScrollTopDIB != nullptr) delete pScrollTopDIB; if (pScrollMidDIB != nullptr) delete pScrollMidDIB; if (pScrollBotDIB != nullptr) delete pScrollBotDIB; pScrollTopDIB = nullptr; pScrollMidDIB = nullptr; pScrollBotDIB = nullptr; } } else bBruteForce = true; if (bBruteForce) { dxDIB = 501; dyDIB = 395; ScrollTopRect.SetRect(0, 0, 501, 48); ScrollMidRect.SetRect(0, 0, 501, 50); ScrollBotRect.SetRect(0, 0, 501, 47); } else { dxDIB = ScrollTopRect.right; dyDIB = ScrollTopRect.bottom + (ScrollMidRect.bottom * SCROLL_PIECES) + ScrollBotRect.bottom; } ScrollRect.SetRect(0, 0, dxDIB, dyDIB); if (CDialog::OnCreate(lpCreateStruct) == -1) return -1; return 0; } bool CRules::CreateWorkAreas(CDC *pDC) { bool bSuccess; CRect WorkRect; pScrollTopBitmap = CreateScrollBitmap(pDC, pScrollTopDIB, pScrollPalette); delete pScrollTopDIB; pScrollMidBitmap = CreateScrollBitmap(pDC, pScrollMidDIB, pScrollPalette); delete pScrollMidDIB; pScrollBotBitmap = CreateScrollBitmap(pDC, pScrollBotDIB, pScrollPalette); delete pScrollBotDIB; if ((pScrollTopBitmap == nullptr) || (pScrollMidBitmap == nullptr) || (pScrollBotBitmap == nullptr)) return false; pScrollTopDC = SetupCompatibleContext(pDC, pScrollTopBitmap, pScrollTopBitmapOld, pScrollPalette, pScrollTopPalOld); pScrollMidDC = SetupCompatibleContext(pDC, pScrollMidBitmap, pScrollMidBitmapOld, pScrollPalette, pScrollMidPalOld); pScrollBotDC = SetupCompatibleContext(pDC, pScrollBotBitmap, pScrollBotBitmapOld, pScrollPalette, pScrollBotPalOld); if ((pScrollTopDC == nullptr) || (pScrollMidDC == nullptr) || (pScrollBotDC == nullptr)) return false; pScrollBitmap = new CBitmap(); if (pScrollBitmap == nullptr) return false; bSuccess = (*pScrollBitmap).CreateCompatibleBitmap(pDC, ScrollRect.right, ScrollRect.bottom); if (!bSuccess) return false; pScrollDC = SetupCompatibleContext(pDC, pScrollBitmap, pScrollBitmapOld, pScrollPalette, pScrollPalOld); if (pScrollDC == nullptr) return false; pBackgroundBitmap = new CBitmap(); if (pBackgroundBitmap == nullptr) return false; bSuccess = (*pBackgroundBitmap).CreateCompatibleBitmap(pDC, ScrollRect.right, ScrollRect.bottom); if (!bSuccess) return false; pBackgroundDC = SetupCompatibleContext(pDC, pBackgroundBitmap, pBackgroundBitmapOld, pScrollPalette, pBackgroundPalOld); if (pBackgroundDC == nullptr) return false; WorkRect.UnionRect(&ScrollTopRect, &ScrollMidRect); WorkRect.UnionRect(&WorkRect, &ScrollBotRect); WorkRect.bottom += SCROLL_STRIP_WIDTH; pWorkBitmap = new CBitmap(); if (pWorkBitmap == nullptr) return false; bSuccess = (*pWorkBitmap).CreateCompatibleBitmap(pDC, WorkRect.right, WorkRect.bottom); if (!bSuccess) return false; pWorkDC = SetupCompatibleContext(pDC, pWorkBitmap, pWorkBitmapOld, pScrollPalette, pWorkPalOld); if (pWorkDC == nullptr) return false; pScrollTopMask = new CBitmap(); pScrollMidMask = new CBitmap(); pScrollBotMask = new CBitmap(); if ((pScrollTopMask == nullptr) || (pScrollMidMask == nullptr) || (pScrollBotMask == nullptr)) return false; pScrollTopMaskDC = SetupMask(pDC, pScrollTopDC, pScrollTopMask, pScrollTopMaskOld, &ScrollTopRect); pScrollMidMaskDC = SetupMask(pDC, pScrollMidDC, pScrollMidMask, pScrollMidMaskOld, &ScrollMidRect); pScrollBotMaskDC = SetupMask(pDC, pScrollBotDC, pScrollBotMask, pScrollBotMaskOld, &ScrollBotRect); if ((pScrollTopMaskDC == nullptr) || (pScrollMidMaskDC == nullptr) || (pScrollBotMaskDC == nullptr)) return false; return true; } CDC *CRules::SetupMask(CDC *pDC, CDC *pBitmapDC, CBitmap *pMask, CBitmap * &pMaskOld, CRect *pRect) { CDC *pNewDC = nullptr; (*pMask).CreateBitmap((*pRect).right, (*pRect).bottom, 1, 1, nullptr); pNewDC = new CDC(); if ((pNewDC != nullptr) && (*pNewDC).CreateCompatibleDC(pDC)) { pMaskOld = (*pNewDC).SelectObject(pMask); if (pMaskOld != nullptr) { (*pNewDC).BitBlt( 0, 0, (*pRect).right, (*pRect).bottom, pBitmapDC, 0, 0, SRCCOPY); return (pNewDC); } } return nullptr; } CDC *CRules::SetupCompatibleContext(CDC *pDC, CBitmap *pBitmap, CBitmap * &pBitmapOld, CPalette *pPalette, CPalette * &pPalOld) { CDC *pNewDC = nullptr; pNewDC = new CDC(); if ((pDC != nullptr) && (pNewDC != nullptr) && (*pNewDC).CreateCompatibleDC(pDC)) { if (pPalette) pPalOld = (*pNewDC).SelectPalette(pPalette, false); else pPalOld = nullptr; (*pNewDC).RealizePalette(); pBitmapOld = (*pNewDC).SelectObject(pBitmap); if (pBitmapOld != nullptr) return (pNewDC); } delete pNewDC; return nullptr; } void CRules::ReleaseCompatibleContext(CDC *&pDC, CBitmap * &pBitmap, CBitmap *pBitmapOld, CPalette *pPalOld) { if (pBitmapOld != nullptr) { (*pDC).SelectObject(pBitmapOld); pBitmapOld = nullptr; } if (pPalOld != nullptr) { (*pDC).SelectPalette(pPalOld, false); pPalOld = nullptr; } if (pBitmap != nullptr) { (*pBitmap).DeleteObject(); delete pBitmap; pBitmap = nullptr; } if (pDC != nullptr) { (*pDC).DeleteDC(); delete pDC; pDC = nullptr; } } CDibDoc *CRules::LoadScrollDIB(const char *pszPathName, CRect *pRect) { bool bSuccess; HDIB hDIB; int dxDIB, dyDIB; CDibDoc *pDibDoc = nullptr; // CPalette *pPalette = nullptr; // CBitmap *pBitmap = nullptr; pDibDoc = new CDibDoc(); ASSERT(pDibDoc != nullptr); bSuccess = (*pDibDoc).OpenDocument(pszPathName); ASSERT(bSuccess); hDIB = (*pDibDoc).GetHDIB(); ASSERT(hDIB != nullptr); dxDIB = (int) DIBWidth(hDIB); dyDIB = (int) DIBHeight(hDIB); if (pRect != nullptr) (*pRect).SetRect(0, 0, dxDIB, dyDIB); return (pDibDoc); } bool CRules::PaintScrollDIB(CDC *pDC, CDibDoc *pDibDoc) { bool bSuccess; HDIB hDIB; int dxDIB, dyDIB; CRect myRect; hDIB = (*pDibDoc).GetHDIB(); ASSERT(hDIB != nullptr); dxDIB = (int) DIBWidth(hDIB); dyDIB = (int) DIBHeight(hDIB); myRect.SetRect(0, 0, dxDIB, dyDIB); bSuccess = PaintDIB((*pDC).m_hDC, myRect, hDIB, myRect, pScrollPalette); return bSuccess; } CBitmap *CRules::CreateScrollBitmap(CDC *pDC, CDibDoc *pDibDoc, CPalette *pPalette) { HDIB hDIB; CBitmap *pBitmap = nullptr; hDIB = (*pDibDoc).GetHDIB(); ASSERT(hDIB != nullptr); pBitmap = ConvertDIB(pDC, hDIB, pPalette); return pBitmap; } void CRules::OnLButtonDown(unsigned int nFlags, CPoint point) { if (!bActiveWindow) return; if (ScrollTopCurlRect.PtInRect(point) && (nHelpPage != 0)) UpdateScroll(nHelpPage - 1); else if (ScrollBotCurlRect.PtInRect(point) && !bHelpEOF) UpdateScroll(nHelpPage + 1); OnMouseMove(nFlags, point); CDialog::OnLButtonDown(nFlags, point); } void CRules::OnMouseMove(unsigned int nFlags, CPoint point) { HCURSOR hNewCursor = nullptr; CWinApp *pMyApp; if (!bActiveWindow) return; pMyApp = AfxGetApp(); if (OkayRect.PtInRect(point)) hNewCursor = (*pMyApp).LoadStandardCursor(IDC_ARROW); else if (ScrollTopCurlRect.PtInRect(point)) { if (nHelpPage == 0) hNewCursor = (*pMyApp).LoadCursor(IDC_RULES_INVALID); else hNewCursor = (*pMyApp).LoadCursor(IDC_RULES_ARROWUP); } else if (ScrollBotCurlRect.PtInRect(point)) { if (bHelpEOF) hNewCursor = (*pMyApp).LoadCursor(IDC_RULES_INVALID); else hNewCursor = (*pMyApp).LoadCursor(IDC_RULES_ARROWDN); } else hNewCursor = (*pMyApp).LoadStandardCursor(IDC_ARROW); //if (hNewCursor != nullptr); SetCursor(hNewCursor); CDialog::OnMouseMove(nFlags, point); } bool CRules::OnSetCursor(CWnd *pWnd, unsigned int nHitTest, unsigned int message) { if ((*pWnd).m_hWnd == (*this).m_hWnd) return true; else return false; } void CRules::ShowWaitCursor() { CWinApp *pMyApp; pMyApp = AfxGetApp(); (*pMyApp).BeginWaitCursor(); } void CRules::DoArrowCursor() { CWinApp *pMyApp; pMyApp = AfxGetApp(); (*pMyApp).EndWaitCursor(); } } // namespace HodjNPodj } // namespace Bagel