/* 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/spacebar/baglib/menu_dlg.h" #include "bagel/spacebar/baglib/pan_window.h" #include "bagel/spacebar/baglib/text_object.h" #include "bagel/spacebar/baglib/bagel.h" #include "bagel/spacebar/baglib/master_win.h" #include "bagel/spacebar/baglib/zoom_pda.h" #include "bagel/boflib/file_functions.h" #include "bagel/boflib/log.h" namespace Bagel { namespace SpaceBar { #define MENU_DFLT_HEIGHT 20 #define TIMER_CLOSE_ID 108 #define TIMER_HACK_ID 109 #define DELAY_DEFAULT 0 CBagObject *CBagMenuDlg::_pSelectedObject; CBofList *CBagMenu::_pUniversalObjectList; // Objects used in every menu int CBagMenu::_nDefaultDelay; bool g_pauseTimerFl = false; extern bool g_bAAOk; #define EXAMINEBMP "$SBARDIR\\GENERAL\\MENUS\\EXAMINE.BMP" void CBagMenuDlg::initialize() { _pSelectedObject = nullptr; } void CBagMenu::initialize() { _pUniversalObjectList = nullptr; _nDefaultDelay = DELAY_DEFAULT; } CBagMenu::CBagMenu() { _nY = 0; static bool bFirstTime = true; // Get option for delay for caption boxes if (bFirstTime) { bFirstTime = false; _nDefaultDelay = DELAY_DEFAULT; CBagel *pBagel = (CBagel *)CBagel::getApp(); if (pBagel != nullptr) { pBagel->getOption("UserOptions", "TextDelay", &_nDefaultDelay, DELAY_DEFAULT); if (_nDefaultDelay < 0) { _nDefaultDelay = -1; } else if (_nDefaultDelay > 0x7FFF) { _nDefaultDelay = 0x7FFF; } } } _nDelay = (int16)_nDefaultDelay; } CBagObject *CBagMenu::onNewSpriteObject(const CBofString &) { CBagSpriteObject *pObj = new CBagSpriteObject(); const CBofPoint pt(0, _nY); pObj->setPosition(pt); pObj->setTransparent(false); return pObj; } bool CBagMenu::trackPopupMenu(uint32 /*nFlags*/, int x, int y, CBofWindow *pWnd, CBofPalette * /*pPal*/, CBofRect * /*lpRect*/) { static int nNumCalls = 0; CBagMenuDlg dlg; CBagObject *pObj; int nObjectPal = -1; int nMenuCount = 0, nRunItems = 0, nBaseMenuLocX = 3; CBofList xObjList; int nNumItems = 0; CBofRect wndRect(80, 10, 80 + 480 - 1, 10 + 360 - 1); CBofRect objSize; CBofSize cWieldMenuSize; CBofSize menuSize(1, 1); CBofPoint menuLoc(4, 1); bool bTextOnly = true; int tmpVal = 0; CBofPoint cMouseDown(x, y); bool bZoomed = false; nNumCalls++; bool bReturn = true; CBofWindow *pParent = pWnd; int nNumWieldChoices = 0; if ((getObjectList()->getCount() == 1) && (getObjectList()->getTail()->getNodeItem()->getType() == TEXT_OBJ) && (((CBagTextObject *)getObjectList()->getTail()->getNodeItem())->isCaption())) { nBaseMenuLocX = 0; } else if (nNumCalls == 1 && _pUniversalObjectList && _pUniversalObjectList != getObjectList()) { for (int i = 0; i < _pUniversalObjectList->getCount(); ++i) { pObj = _pUniversalObjectList->getNodeItem(i); if (pObj->isLocal() && (!pObj->getExpression() || pObj->getExpression()->evaluate(pObj->isNegative()))) { // Only attach if not attached if (pObj->isAttached() == false) { pObj->attach(); // Otherwise, we need to re-calculate the size of the text object, // since we are gonna trash is with our own values soon. } else if (pObj->getType() == TEXT_OBJ) { ((CBagTextObject *)pObj)->recalcTextRect(((CBagTextObject *)pObj)->isCaption()); } if (!pObj->isImmediateRun()) { // Get the next menu items pos objSize = pObj->getRect(); if (menuSize.cx < (objSize.width() + menuLoc.x)) menuSize.cx = (objSize.width() + menuLoc.x); if (menuSize.cy < (objSize.height() + menuLoc.y)) menuSize.cy = (objSize.height() + menuLoc.y); pObj->setPosition(menuLoc); pObj->setHighlight(false); if (!nMenuCount && (pObj->getType() == TEXT_OBJ)) { menuLoc.y += objSize.height(); } else { menuLoc.x += objSize.width(); } xObjList.addToTail(pObj); nMenuCount++; } else { nRunItems++; pObj->runObject(); // This detach may cause problems in the future, if it does delete it // Some object may not work if detached for example midi sound pObj->detach(); } } } // Start non-wield menu on next row menuLoc.y += objSize.height(); nNumWieldChoices = xObjList.getCount(); if (nNumWieldChoices != 0) { cWieldMenuSize = menuSize; } } bool bNoMenu = false; for (int i = 0; i < getObjectList()->getCount(); ++i) { pObj = getObjectList()->getNodeItem(i); if (pObj->isLocal() && (!pObj->getExpression() || pObj->getExpression()->evaluate(pObj->isNegative()))) { // Only attach if not attached if (pObj->isAttached() == false) { pObj->attach(); // Otherwise, we need to re-calculate the size of the text object, // since we are gonna trash is with our own values soon. } else if (pObj->getType() == TEXT_OBJ) { ((CBagTextObject *)pObj)->recalcTextRect(((CBagTextObject *)pObj)->isCaption()); } if (!pObj->isImmediateRun()) { // Get the next menu items pos objSize = pObj->getRect(); // If it is a text object increment next position by its height if (pObj->getType() == TEXT_OBJ) { if (tmpVal) // If we have a value move text to next line menuLoc.y += tmpVal; tmpVal = 0; // Text objects set the next line to be at the very beginning // If we're zoomed, then do things differently CBagTextObject *pTXObj = (CBagTextObject *)pObj; if (pTXObj->isCaption()) { SBZoomPda *pZPDA = (SBZoomPda *)g_SDevManager->getStorageDevice("BPDAZ_WLD"); if (pZPDA && pZPDA->getZoomed()) { bZoomed = true; wndRect = pZPDA->getViewRect(); } } if (bTextOnly) { if (wndRect.height() <= (objSize.height() + menuLoc.y)) { menuLoc.y = 1; nBaseMenuLocX += (menuSize.cx + 2); menuLoc.x = nBaseMenuLocX + 1; } } else if (wndRect.height() <= ((objSize.height() + menuLoc.y) + 41)) { menuLoc.y = 1; nBaseMenuLocX += (menuSize.cx + 2); menuLoc.x = nBaseMenuLocX; } // CHECKME: the previous assignment seems to indicate that the following line should be removed in order to keep this slightly different value menuLoc.x = (1 + nBaseMenuLocX); if (menuSize.cx < (objSize.width() + menuLoc.x)) menuSize.cx = (objSize.width() + menuLoc.x); if (menuSize.cy < (objSize.height() + menuLoc.y)) menuSize.cy = (objSize.height() + menuLoc.y); pObj->setPosition(menuLoc); pObj->setHighlight(false); menuLoc.x = (1 + nBaseMenuLocX); menuLoc.y += (objSize.height() + 1); } else { // Increment next position by its width if (wndRect.height() <= (objSize.height() + menuLoc.y)) { menuLoc.y = 1; nBaseMenuLocX += (menuSize.cx + 2); menuLoc.x = nBaseMenuLocX; } bTextOnly = false; // Continue to grow menu size to max required if (menuSize.cx < (objSize.width() + menuLoc.x)) menuSize.cx = (objSize.width() + menuLoc.x); if (menuSize.cy < (objSize.height() + menuLoc.y)) menuSize.cy = (objSize.height() + menuLoc.y); pObj->setPosition(menuLoc); pObj->setHighlight(false); if ((nObjectPal < 0) && ((pObj->getType() == BMP_OBJ) || (pObj->getType() == SPRITE_OBJ))) nObjectPal = i; menuLoc.x += objSize.width(); tmpVal = objSize.height();//save the obj height for use later if we get a text obj } if (pObj->isNoMenu()) { bNoMenu = true; } xObjList.addToTail(pObj); nNumItems++; nMenuCount++; } else { nRunItems++; pObj->runObject(); if (pObj->getType() == LINK_OBJ) { g_bAAOk = false; } } } // If in local area and activated by expression } // If we ran something and there are no other menu items just return if (!(nRunItems && !nMenuCount)) { if (nMenuCount) { menuLoc.y += objSize.height(); menuLoc.x = 1; nMenuCount = 0; } menuSize.cx++; menuSize.cy++; CBofRect tmpRect(CBofPoint(x, y - menuSize.cy / 2), menuSize); menuSize.cy += 2; // If the menu contains only one object and it is a caption style text object // position the dialog box at the bottom of the Game window screen bool bMoved = false; bool bCaption = false; int nNumChars = 0; if ((nNumItems == 1) && (xObjList.getTail()->getNodeItem()->getType() == TEXT_OBJ) && (((CBagTextObject *)xObjList.getTail()->getNodeItem())->isCaption())) { while (nNumWieldChoices-- != 0) { pObj = xObjList.removeHead(); pObj->detach(); } // If we are not using the wield menu with this menu, then // the menu is smaller menuSize.cy -= cWieldMenuSize.cy; if (cWieldMenuSize.cy != 0) cWieldMenuSize.cy--; bMoved = true; tmpRect = wndRect; tmpRect.top = tmpRect.bottom - menuSize.cy; bCaption = true; nNumChars = ((CBagTextObject *)xObjList.getHead()->getNodeItem())->getText().getLength(); // Bring caption back 3 pixels CBofPoint cPoint; cPoint = xObjList.getHead()->getNodeItem()->getPosition(); cPoint.x = 1; xObjList.getHead()->getNodeItem()->setPosition(cPoint); } else { if (bTextOnly) { while (nNumWieldChoices-- != 0) { pObj = xObjList.removeHead(); pObj->detach(); } // If we are not using the wield menu with this menu, then // the menu is smaller menuSize.cy -= cWieldMenuSize.cy; bMoved = true; } // Use the mouse pos (x,y), and add the menuSize calculated above tmpRect.left = x; tmpRect.top = y; tmpRect.bottom = y + menuSize.cy; tmpRect.right = x + menuSize.cx + 1; // If the menu would go off the screen, adjust it while (tmpRect.top < wndRect.top) { tmpRect.offsetRect(0, (wndRect.top - tmpRect.top)); } while (tmpRect.bottom > wndRect.bottom) { tmpRect.offsetRect(0, (wndRect.bottom - tmpRect.bottom)); } while (tmpRect.left < wndRect.left) { tmpRect.right += (wndRect.left - tmpRect.left); tmpRect.left += (wndRect.left - tmpRect.left); } while (tmpRect.right > wndRect.right) { tmpRect.left -= (tmpRect.right - wndRect.right); tmpRect.right -= (tmpRect.right - wndRect.right); } } bReturn = false; if (xObjList.getCount() && !bNoMenu) { bReturn = true; dlg.setObjectList(&xObjList); for (int i = 0; i < xObjList.getCount(); i++) { pObj = xObjList[i]; if (pObj->getType() == TEXT_OBJ) { int cx = tmpRect.size().cx - 1; int cy = tmpRect.size().cy - 1; if (!bCaption) { cy = ((CBagTextObject *)pObj)->getSize().cy + 2; } pObj->setSize(CBofSize(cx, cy)); // Need to move the menus up when have a wielded item, but // not displaying the wield menu if (bMoved) { CBofPoint cPos; cPos = pObj->getPosition(); cPos.y -= cWieldMenuSize.cy; pObj->setPosition(cPos); } } } CBagPanWindow::flushInputEvents(); // If we were requested to put a dialog over the PDA, then shift it upward // a bit... unless of course the mousedown occurred in the PDA itself. CBagPDA *pPDA = (CBagPDA *)g_SDevManager->getStorageDevice("BPDA_WLD"); if (pPDA != nullptr && (pPDA->isActivated() && bZoomed == false)) { if (!pPDA->isInside(cMouseDown)) { CBofRect cPDARect = pPDA->getRect(); tmpRect.offsetRect(0, (tmpRect.bottom > cPDARect.top ? cPDARect.top - tmpRect.bottom : 0)); // Make sure we didn't go too far if (tmpRect.top < 0) { tmpRect.offsetRect(0, -tmpRect.top); } } } // Force all menus to be created using a specific palette // that we know contains the correct colors for our menu. char szBuf[256]; Common::strcpy_s(szBuf, EXAMINEBMP); CBofString cString(szBuf, 256); fixPathName(cString); CBofPalette xPal; xPal.loadPalette(cString); dlg.createDlg(pParent, &xPal, &tmpRect); if (bCaption) { // We need to move this back to the correct position // because the Boflibs center dialog ALWAYS !!!!!!!! // If the PDA is currently active, then we have to put the caption elsewhere, // or deactivate the PDA (if PDA zoomed, just place at bottom. if (CBagPanWindow::_pPDABmp != nullptr && CBagPanWindow::_pPDABmp->isActivated() && bZoomed == false) { dlg.move(wndRect.left, wndRect.top, true); } else { dlg.move(tmpRect.left, tmpRect.top, true); } // Need to re-save the background because there is a bug with moving // the dialog box. dlg.saveBackground(); // Set this caption to automatically go away after specified delay if (_nDelay > 0) { dlg.setTimer(TIMER_CLOSE_ID, _nDelay + (80 * nNumChars)); } } else { dlg.move(tmpRect.topLeft().x, tmpRect.topLeft().y, true); // Need to re-save the background because there is a bug with moving // the dialog box. dlg.saveBackground(); } CBagPanWindow::flushInputEvents(); g_pauseTimerFl = true; dlg.doModal(); g_pauseTimerFl = false; pObj = dlg._pSelectedObject; dlg.destroy(); for (int i = 0; i < getObjectCount(); ++i) { getObjectByPos(i)->detach(); } // If our current storage device is "AS CUSTOM" then don't allow // the timer to get incremented. CBagStorageDev *pCurSDEV = CBagel::getBagApp()->getMasterWnd()->getCurrentStorageDev(); if (pObj != nullptr) { pObj->runCallBack(); // Selecting this menu item causes a turn to go by if (nNumCalls == 1 && pCurSDEV->isCustom() == false) { g_VarManager->incrementTimers(); } } else if (bCaption && (nNumCalls == 2)) { // Selecting this menu item causes a turn to go by dlg._pSelectedObject = nullptr; if (pCurSDEV->isCustom() == false) { g_VarManager->incrementTimers(); } } } } nNumCalls--; return bReturn; } bool CBagMenu::addItem(CBagObject *pObj, void *(* /*func*/)(int, void *), void */*info*/) { pObj->setPosition(CBofPoint(0, _nY)); _nY = (int16)(_nY + (int16)(pObj->getRect().height() + 1)); addObject(pObj); return true; } bool CBagMenu::deleteItem(const CBofString & /*sLabel*/) { return false; } bool CBagMenu::isChecked(const CBofString & /*sLabel*/, const CBofString & /*sSubLabel*/) { const int nRow = 0; const int nCol = 0; return isCheckedPos(nRow, nCol); } bool CBagMenu::isCheckedPos(int /*nRow*/, int /*nCol*/) { return false; } bool CBagMenu::isChecked(int /*nRefId*/) { return false; } bool CBagMenu::check(const CBofString & /*sLabel*/, const CBofString & /*sSubLabel*/) { return false; } bool CBagMenu::unCheck(const CBofString & /*sLabel*/, const CBofString & /*sSubLabel*/) { return false; } bool CBagMenu::check(int /*nRefId*/) { return true; } bool CBagMenu::unCheck(int /*nRefId*/) { return true; } bool CBagMenu::setUniversalObjectList(CBofList *pObjList) { if (_pUniversalObjectList != nullptr) { removeUniversalObjectList(); } _pUniversalObjectList = pObjList; return true; } bool CBagMenu::removeUniversalObjectList() { if (_pUniversalObjectList == nullptr) { return true; } _pUniversalObjectList = nullptr; return true; } // // // CBagMenuDlg::CBagMenuDlg() { // Remove this SDEV from the storage device list so that it is not deleted // when we switch .WLD files, and there may still be a Dialog open. g_SDevManager->unregisterStorageDev(this); _bAcceptInput = false; _bMultipleDialogs = false; } CBagMenuDlg::~CBagMenuDlg() { assert(isValidObject(this)); } ErrorCode CBagMenuDlg::createDlg(CBofWindow *pWnd, CBofPalette *pPal, CBofRect *pRect) { CBofRect r; _bMultipleDialogs = false; _bAcceptInput = true; _nReturnValue = 0; if (!pRect) { r = pWnd->getWindowRect(); r.offsetRect(-r.left, -r.top); r.bottom = r.top + MENU_DFLT_HEIGHT; } else { r = *pRect; } CBagStorageDevDlg::create("Menu", &r, pWnd, 0); CBofBitmap *pBmp = new CBofBitmap(r.width(), r.height(), pPal); r.offsetRect(-r.left, -r.top); assert(pPal != nullptr); pBmp->fillRect(&r, pPal->getNearestIndex(RGB(82, 82, 82))); pBmp->drawRect(&r, pPal->getNearestIndex(RGB(0, 0, 0))); setBackdrop(pBmp); return _errCode; } void CBagMenuDlg::onLButtonUp(uint32 nFlags, CBofPoint *pPoint, void *) { // We are ignoring all input until the dialog is actually visible if (_bAcceptInput) { _pSelectedObject = nullptr; onClose(); if (_bMultipleDialogs) { CBofRect r = getWindowRect(); r.offsetRect(-r.left, -r.top); if (r.ptInRect(*pPoint)) { CBagStorageDevDlg::onLButtonUp(nFlags, pPoint); _pSelectedObject = getLActiveObject(); } } else { const CBofPoint pt = devPtToViewPort(*pPoint); _pSelectedObject = getObject(pt); if (_pSelectedObject != nullptr) { _pSelectedObject->onLButtonUp(nFlags, pPoint); } } _nReturnValue = (_pSelectedObject != nullptr); } } void CBagMenuDlg::onMouseMove(uint32 /*nFlags*/, CBofPoint *pPoint, void *) { CBagMasterWin::setActiveCursor(0); CBagObject *pObj = getObject(*pPoint); if (pObj != nullptr) { // Switch to that cursor CBagMasterWin::setActiveCursor(pObj->getOverCursor()); if (pObj != getLActiveObject()) { if (pObj->getCallBack() || pObj->getMenuPtr()) { pObj->setHighlight(); if (getLActiveObject()) getLActiveObject()->setHighlight(false); setLActiveObject(pObj); } } } } void CBagMenuDlg::onTimer(uint32 nID) { assert(isValidObject(this)); switch (nID) { // Auto close for text-Captions case TIMER_CLOSE_ID: killTimer(nID); close(); break; // Can now allow user input case TIMER_HACK_ID: killTimer(nID); _bAcceptInput = true; break; default: logWarning("Invalid Timer ID"); break; } } void CBagMenuDlg::onPaint(CBofRect *pRect) { assert(isValidObject(this)); CBagStorageDevDlg::onPaint(pRect); // Don't allow user input until this menu is visible CBagPanWindow::flushInputEvents(); } void CBagMenuDlg::onDeActivate() { assert(isValidObject(this)); close(); } } // namespace SpaceBar } // namespace Bagel