Files
2026-02-02 04:50:13 +01:00

693 lines
18 KiB
C++

/* 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 <http://www.gnu.org/licenses/>.
*
*/
#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<CBagObject *> *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<CBagObject *> 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<CBagObject *> *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