Files
scummvm-cursorfix/engines/bagel/spacebar/baglib/text_object.cpp
2026-02-02 04:50:13 +01:00

614 lines
14 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/text_object.h"
#include "bagel/spacebar/baglib/master_win.h"
#include "bagel/spacebar/baglib/pan_window.h"
#include "bagel/spacebar/baglib/zoom_pda.h"
#include "bagel/spacebar/baglib/base_pda.h"
#include "bagel/spacebar/baglib/menu_dlg.h"
#include "bagel/spacebar/baglib/bagel.h"
#include "bagel/spacebar/baglib/rp_object.h"
#include "bagel/spacebar/boflib/gfx/text.h"
namespace Bagel {
namespace SpaceBar {
extern bool g_pauseTimerFl;
CBagTextObject::CBagTextObject() : CBagObject() {
_xObjType = TEXT_OBJ;
_nDX = 80;
_nDY = 20;
_psText = nullptr;
CBagObject::setOverCursor(1); // Switch to cursor 1, 4 doesn't exist.
_nPointSize = 16;
_nFGColor = CTEXT_COLOR;
_psInitInfo = nullptr;
_bCaption = false;
_bTitle = false;
_bReAttach = false;
_nTextFont = FONT_DEFAULT;
setRPObject(nullptr);
}
CBagTextObject::~CBagTextObject() {
delete _psInitInfo;
_psInitInfo = nullptr;
CBagTextObject::detach();
}
CBofRect CBagTextObject::getRect() {
CBofPoint p = getPosition();
CBofSize s = getSize();
CBofRect r = CBofRect(p, s);
return r;
}
ErrorCode CBagTextObject::update(CBofBitmap *pBmp, CBofPoint pt, CBofRect *pSrcRect, int) {
assert(isValidObject(this));
assert(pBmp != nullptr);
assert(pSrcRect != nullptr);
// assume no error
ErrorCode errorCode = ERR_NONE;
if ((pBmp != nullptr) && isAttached() && !(getText().isEmpty())) {
if (pBmp->getRect().ptInRect(pt)) {
CBofRect r(pt, pSrcRect->size());
int nPointSize = _nPointSize;
int nFormat = FORMAT_CENTER_LEFT;
if (!_bTitle) {
byte c1 = 3;
byte c2 = 9;
CBofRect cBevel;
cBevel.intersectRect(pBmp->getRect(), r);
int left = cBevel.left;
int top = cBevel.top;
int right = cBevel.right;
int bottom = cBevel.bottom;
r.left += 6;
r.top += 3;
r.right -= 5;
r.bottom -= 5;
int i;
for (i = 1; i <= 3; i++) {
pBmp->line(left + i, bottom - i, right - i, bottom - i, c1);
pBmp->line(right - i, bottom - i, right - i, top + i - 1, c1);
}
for (i = 1; i <= 3; i++) {
pBmp->line(left + i, bottom - i, left + i, top + i - 1, c2);
pBmp->line(left + i, top + i - 1, right - i, top + i - 1, c2);
}
nPointSize = 16;
nFormat = FORMAT_TOP_LEFT;
} else {
r.left += 1;
}
errorCode = paintText(pBmp, &r, getText(), mapWindowsPointSize(nPointSize), TEXT_NORMAL, _nFGColor, JUSTIFY_WRAP, nFormat, _nTextFont);
// This object does not need to be updated now...
setDirty(false);
}
}
return errorCode;
}
ErrorCode CBagTextObject::attach() {
assert(isValidObject(this));
if (!getFileName().right(4).find(".TXT") || !getFileName().right(4).find(".txt")) {
// Prevent memory leak
delete _psText;
_psText = nullptr;
// Allocate a new string
_psText = new CBofString;
CBofFile fpTextFile(getFileName());
if (!fpTextFile.errorOccurred()) {
// Allocate the buffers
uint32 nFileLen = fpTextFile.getLength();
char *pTextBuff = (char *)bofCleanAlloc(nFileLen + 1);
// Read the text file into buffers
fpTextFile.read(pTextBuff, nFileLen);
fpTextFile.close();
*_psText += pTextBuff;
if (_psInitInfo != nullptr) {
CBagVar *pVar = g_VarManager->getVariable(*_psInitInfo);
if (pVar != nullptr) {
_bReAttach = true;
_psText->replaceStr("%s", pVar->getValue());
}
}
bofFree(pTextBuff);
} else {
reportError(ERR_FOPEN, "Failed to create a CBofFile for %s", getFileName().getBuffer());
}
if (isCaption()) {
recalcTextRect(true);
}
} else {
// The Text is in the Bagel script, rather than a .txt file
// Prevent memory leak
delete _psText;
_psText = nullptr;
// Allocate a new string
_psText = new CBofString;
*_psText = getFileName();
// Replace any underscores with spaces
_psText->replaceChar('_', ' ');
recalcTextRect(false);
}
// If this guy is linked to a residue printing object, make sure he knows
// we've been attached.
CBagRPObject *pRPObj = (CBagRPObject *)getRPObject();
if (pRPObj != nullptr) {
pRPObj->setTimeSet(false);
}
return CBagObject::attach();
}
ErrorCode CBagTextObject::detach() {
assert(isValidObject(this));
delete _psText;
_psText = nullptr;
return CBagObject::detach();
}
const CBofString &CBagTextObject::getText() {
if (_psText)
return *_psText;
return getFileName();
}
void CBagTextObject::setText(const CBofString &s) {
if (_psText) {
*_psText = s;
} else {
setFileName(s);
}
recalcTextRect(!getFileName().right(4).find(".TXT") || !getFileName().right(4).find(".txt"));
}
// Takes in info and then removes the relative information and returns the info
// without the relevant info.
//
ParseCodes CBagTextObject::setInfo(CBagIfstream &istr) {
bool nObjectUpdated = false;
while (!istr.eof()) {
istr.eatWhite();
char ch = (char)istr.peek();
switch (ch) {
//
// VAR var - var is a BAGEL CBagVar variable (replaces all %s in text)
//
case 'V': {
char szLocalStr[256];
szLocalStr[0] = 0;
CBofString sStr(szLocalStr, 256);
// Need to use this field, so no one else can
assert(_psInitInfo == nullptr);
getAlphaNumFromStream(istr, sStr);
if (!sStr.find("VAR")) {
istr.eatWhite();
getAlphaNumFromStream(istr, sStr);
setInitInfo(sStr);
nObjectUpdated = true;
} else {
putbackStringOnStream(istr, sStr);
}
break;
}
//
// SIZE n - n point size of the txt
//
case 'S': {
char szLocalStr[256];
szLocalStr[0] = 0;
CBofString sStr(szLocalStr, 256);
getAlphaNumFromStream(istr, sStr);
if (!sStr.find("SIZE")) {
istr.eatWhite();
int n;
getIntFromStream(istr, n);
_nPointSize = (byte)n;
nObjectUpdated = true;
// WORKAROUND: Reduce the font size of Cilia's full-screen log
// in ScummVM so that it fits on the screen
if (_nPointSize == 28 && istr.getSize() == 359105)
_nPointSize = 26;
} else {
putbackStringOnStream(istr, sStr);
}
break;
}
//
// FONT MONO or DEFAULT
//
case 'F': {
char szLocalStr[256];
szLocalStr[0] = 0;
CBofString sStr(szLocalStr, 256);
getAlphaNumFromStream(istr, sStr);
if (!sStr.find("FONT")) {
istr.eatWhite();
int n;
getIntFromStream(istr, n);
_nTextFont = MapFont(n);
nObjectUpdated = true;
} else {
putbackStringOnStream(istr, sStr);
}
// FIXME: Missing break?
}
// fallthrough
//
// AS [CAPTION] - how to run the link
//
case 'A': {
char szLocalStr[256];
szLocalStr[0] = 0;
CBofString sStr(szLocalStr, 256);
getAlphaNumFromStream(istr, sStr);
if (!sStr.find("AS")) {
istr.eatWhite();
getAlphaNumFromStream(istr, sStr);
if (!sStr.find("CAPTION")) {
_bCaption = true;
nObjectUpdated = true;
} else if (!sStr.find("TITLE")) {
_bTitle = true;
nObjectUpdated = true;
} else {
putbackStringOnStream(istr, sStr);
putbackStringOnStream(istr, "AS ");
}
} else {
putbackStringOnStream(istr, sStr);
}
break;
}
//
// COLOR n - n color index
//
case 'C': {
char szLocalStr[256];
szLocalStr[0] = 0;
CBofString sStr(szLocalStr, 256);
getAlphaNumFromStream(istr, sStr);
if (!sStr.find("COLOR")) {
int nColor;
istr.eatWhite();
getIntFromStream(istr, nColor);
setColor(nColor);
nObjectUpdated = true;
} else {
putbackStringOnStream(istr, sStr);
}
break;
}
//
// No match return from function
//
default: {
ParseCodes parseCode = CBagObject::setInfo(istr);
if (parseCode == PARSING_DONE) {
return PARSING_DONE;
}
if (parseCode == UPDATED_OBJECT) {
nObjectUpdated = true;
} else { // rc==UNKNOWN_TOKEN
if (nObjectUpdated)
return UPDATED_OBJECT;
return UNKNOWN_TOKEN;
}
break;
}
}
}
return PARSING_DONE;
}
void CBagTextObject::setColor(int nColor) {
switch (nColor) {
case 0:
_nFGColor = RGB(0, 0, 0);
break; // Black
case 1:
_nFGColor = RGB(226, 50, 51);
break; // Red
case 2:
_nFGColor = RGB(255, 255, 255);
break; // Yellow
case 3:
_nFGColor = RGB(255, 255, 255);
break; // Green
case 4:
_nFGColor = RGB(255, 255, 255);
break; // Green
case 5:
_nFGColor = RGB(0, 0, 255);
break; // Blue
case 6:
_nFGColor = RGB(255, 0, 255);
break;
case 7:
_nFGColor = CTEXT_WHITE;
break; // White
case 8:
_nFGColor = CTEXT_YELLOW;
break; // Yellow (chat highlight)
default:
break;
}
}
void CBagTextObject::setProperty(const CBofString &sProp, int nVal) {
if (!sProp.find("SIZE"))
setPointSize(nVal);
else if (!sProp.find("FONT"))
setFont(MapFont(nVal));
else if (!sProp.find("COLOR"))
setColor(nVal);
else
CBagObject::setProperty(sProp, nVal);
}
int CBagTextObject::getProperty(const CBofString &sProp) {
if (!sProp.find("SIZE"))
return getPointSize();
if (!sProp.find("FONT"))
return getFont();
if (!sProp.find("COLOR"))
return getColor();
return CBagObject::getProperty(sProp);
}
bool CBagTextObject::runObject() {
char szLocalBuff[256];
CBofString sStr(szLocalBuff, 256);
if (_bCaption && isImmediateRun()) {
// Re-attach this object to get any change in a variable
// (Must be using the VAR token).
if (_bReAttach) {
attach();
}
CBagel *pApp = CBagel::getBagApp();
if (pApp != nullptr) {
CBagMasterWin *pWin = pApp->getMasterWnd();
if (pWin != nullptr) {
CBagStorageDevWnd *pParent = pWin->getCurrentStorageDev();
CBofRect cRect(80, 10, 80 + 480 /*- 1 */, 10 + getRect().height() - 1 + 5);
CBofPoint cPoint(0, 0);
CBofPalette *pPal = pApp->getPalette();
CBofBitmap cBmp(cRect.width(), cRect.height(), pPal);
cBmp.fillRect(nullptr, pPal->getNearestIndex(RGB(92, 92, 92)));
CBagMenuDlg cDlg;
cDlg.createDlg(pParent, pPal, &cRect);
update(cDlg.getBackdrop(), cPoint, &cRect);
sStr = "BPDA_WLD";
CBagPDA *pPDA = (CBagPDA *)g_SDevManager->getStorageDevice(sStr);
// If we're in the zoom pda then put this box at the
// bottom of the zoom rect.
sStr = "BPDAZ_WLD";
SBZoomPda *pPDAZ = (SBZoomPda *)g_SDevManager->getStorageDevice(sStr);
if (pPDAZ && pPDAZ->getZoomed() == true) {
CBofRect zRect = pPDAZ->getViewRect();
assert(zRect.height() > 0 && zRect.height() < 480);
assert(zRect.width() > 0 && zRect.width() < 640);
cDlg.move(80, zRect.bottom - cRect.height(), true); // xxx
} else if ((pPDA != nullptr) && (pPDA->isActivated() || pPDA->isActivating())) {
cDlg.move(80, 10, true);
} else {
int x = 80;
int y = 360 + 10 - cRect.height();
cDlg.move(x, y, true);
}
g_pauseTimerFl = true;
cDlg.doModal();
g_pauseTimerFl = false;
}
}
}
return false;
}
int MapFont(int nFont) {
if (nFont == 0)
return FONT_MONO;
return FONT_DEFAULT;
}
void CBagTextObject::onLButtonUp(uint32 nFlags, CBofPoint *xPoint, void *pv) {
// If there's a residue printing object, then hand this guy off to
// him, otherwise, call back to Cbagobj.
CBagRPObject *pRPObj = (CBagRPObject *)getRPObject();
if (pRPObj) {
pRPObj->onLButtonUp(nFlags, xPoint, pv);
return;
}
CBagObject::onLButtonUp(nFlags, xPoint, pv);
}
void CBagTextObject::recalcTextRect(bool bTextFromFile) {
CBofRect ViewRect; // The rect of the area where objects are displayed
CBofSize cDisplaySize; // Size of rect needed to display font
CBofSize cSize; // Size of rect needed to display font
assert(_psText != nullptr);
// The window where the object are displayed
CBagPanWindow *pPanWin = (CBagPanWindow *)(CBagel::getBagApp()->getMasterWnd()->getCurrentGameWindow());
if (bTextFromFile) {
if (pPanWin->getDeviceType() == SDEV_GAMEWIN) {
ViewRect = pPanWin->getViewPort();
} else {
ViewRect = pPanWin->getClientRect();
}
}
if (ViewRect.isRectEmpty()) {
ViewRect.setRect(80, 10, 480 + 80 - 1, 360 + 10 - 1);
}
// Get the area spanned by the text (i.e. Get the pixel width and
// height of the text string).
CBofRect tmpRect = ViewRect;
if (!_bTitle) {
// Exactly match the width used in displayTextEx
tmpRect.left += 5;
tmpRect.right = (ViewRect.right == 640 ? PAN_AREA_WIDTH : ViewRect.right) - 5;
}
CBofRect textRect = calculateTextRect(tmpRect, _psText, _nPointSize, getFont());
CBofSize stTextSize(textRect.right, textRect.bottom);
if (bTextFromFile) {
// Add fudge factor to make sure that all the text will fit, and not
// get cut off. This may cause an extra blank line of text in some
// captions, but tough diddles, it's still better than truncating
// some text.
cSize.cx = stTextSize.cx;
cSize.cy = stTextSize.cy;
} else {
cSize.cx = stTextSize.cx + 9;
cSize.cy = stTextSize.cy + (_bTitle ? 0 : 7);
setSize(cSize);
}
if (bTextFromFile) {
cDisplaySize.cx = ViewRect.width();
cDisplaySize.cy = cSize.cy;
// If for some reason (CIC, CHAT) we got too large
// a viewrect, cut it back to the correct width
if (cDisplaySize.cx > PAN_AREA_WIDTH)
cDisplaySize.cx = PAN_AREA_WIDTH;
// Buffer the size a little for spacing etc.
cDisplaySize.cx -= 5;
// While the text is wider then the view area
while (cSize.cx > cDisplaySize.cx) {
// Increment Display Height to account for another line
cDisplaySize.cy += cSize.cy;
// Decrement the size of text by the width of one line
cSize.cx -= cDisplaySize.cx;
}
// Add a little space at the bottom
cDisplaySize.cy += 5;
setSize(cDisplaySize);
}
}
void CBagTextObject::setPSText(CBofString *p) {
assert(isValidObject(this));
delete _psText;
_psText = nullptr;
if (p != nullptr) {
_psText = new CBofString(*p);
}
}
} // namespace SpaceBar
} // namespace Bagel