scummvm/engines/saga2/document.cpp
2022-10-29 23:46:00 +02:00

904 lines
22 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
* aint32 with this program; if not, write to the Free Software
*
*
* Based on the original sources
* Faery Tale II -- The Halls of the Dead
* (c) 1993-1996 The Wyrmkeep Entertainment Co.
*/
#include "common/keyboard.h"
#include "saga2/saga2.h"
#include "saga2/document.h"
#include "saga2/script.h"
#include "saga2/intrface.h"
#include "saga2/grequest.h"
#include "saga2/mouseimg.h"
#include "saga2/version.h"
#include "saga2/fontlib.h"
namespace Saga2 {
/* ===================================================================== *
About box
* ===================================================================== */
#define PROGRAM_ABOUT " v" VERSION_OPSYS " " VERSION_STAMP
// externs
extern BackWindow *mainWindow;
// declarations
APPFUNC(cmdDocumentQuit);
/* ===================================================================== *
scroll metrics
* ===================================================================== */
// buttons
//Rect16 closeScrollBtnRect( 184, 206, 44, 42 );
// options dialog window decorations
static StaticWindow scrollDecorations[] = {
{{202, 54, 232, 100}, nullptr, 0},
{{212, 154, 212, 100}, nullptr, 1},
{{202, 254, 236, 117}, nullptr, 2}
};
static uint8 scrollTextColors[] = { 65, 65, 65, 65, 65, 65, 65, 66, 66, 67, 67, 67, 67, 66, 66, 66 };
CDocumentAppearance scrollAppearance = {
{202, 54, 236, 317},
1,
kPageOrientVertical,
scrollTextColors,
{ {50, 64, 131, 169}, {0, 0, 0, 0} },
{184, 206, 44, 42},
scrollDecorations,
ARRAYSIZE(scrollDecorations),
MKTAG('S', 'C', 'R', 'L'),
MKTAG('S', 'R', 'L', 0)
};
/* ===================================================================== *
Book metrics
* ===================================================================== */
// options dialog window decorations
static StaticWindow bookDecorations[] = {
{{123, 76, 394, 88}, nullptr, 0},
{{123, 164, 394, 80}, nullptr, 1},
{{123, 244, 394, 77}, nullptr, 2}
};
static uint8 bookTextColors[] = { 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65 };
CDocumentAppearance bookAppearance = {
{123, 76, 394, 252},
2,
kPageOrientHorizontal,
bookTextColors,
{ {40, 26, 135, 205}, {218, 26, 135, 205} },
{231, 217, 34, 27},
bookDecorations,
ARRAYSIZE(bookDecorations),
MKTAG('B', 'O', 'O', 'K'),
MKTAG('B', 'K', 'D', 0)
};
/* ===================================================================== *
Parchment metrics
* ===================================================================== */
// options dialog window decorations
static StaticWindow parchDecorations[] = {
{{202, 54, 208, 256}, nullptr, 0}
};
CDocumentAppearance parchAppearance = {
{202, 54, 208, 256},
1,
kPageOrientVertical,
bookTextColors,
{ {27, 18, 149, 212}, {0, 0, 0, 0} },
{164, 229, 20, 20},
parchDecorations,
ARRAYSIZE(parchDecorations),
MKTAG('P', 'A', 'R', 'C'),
MKTAG('P', 'C', 'H', 0)
};
// deliminator defines
char deliminator = '@';
char dPageBreak[3] = { "pb" };
char dImage[3] = { "im" };
/* ===================================================================== *
Document base class
* ===================================================================== */
CDocument::CDocument(CDocumentAppearance &dApp,
char *buffer, // buffer to edit
gFont *font, // font of the text
uint16 ident, // control ID
AppFunc *cmd) // application command func
: _app(dApp), ModalWindow(dApp.windowPos, ident, cmd) {
// resource handle
hResContext *decRes;
// init the resource context handle
decRes = resFile->newContext(_app.groupID, "docimage context");
// init con pointer to NULL
_illustrationCon = nullptr;
// set the maxium string length
_maxSize = kMaxPages * kMaxLines * kMaxChars;
// get the org text size
_textSize = clamp(0, strlen(buffer), _maxSize);
// set the original text pointer
origText = new char[_textSize + 1];
// and fill it
Common::strlcpy(origText, buffer, _textSize + 1);
// make a working buffer
text = new char[_textSize + 1];
// and fill it
Common::strlcpy(text, origText, _textSize + 1);
_textFont = font;
_textHeight = (_textFont ? _textFont->height : 0);
_lineWidth = dApp.pageRect[0].width;
_pageHeight = dApp.pageRect[0].height;
_currentPage = 0;
_totalLines = 0;
_totalPages = 0;
_pageBreakSet = true;
// null out the image pointer array
for (int16 i = 0; i < kMaxPages; i++) {
_images[i] = nullptr;
}
makePages();
// attach the graphics for the book
setDecorations(_app.decoList,
_app.numDecos,
decRes, _app.decoID);
// remove the resource handle
if (decRes) resFile->disposeContext(decRes);
decRes = nullptr;
}
CDocument::~CDocument() {
int16 i;
for (i = 0; i < kMaxPages; i++) {
if (_images[i]) {
free(_images[i]);
}
}
// get rid of the working text buffer
if (text) {
delete[] text;
text = nullptr;
}
if (origText) {
delete[] origText;
origText = nullptr;
}
// get rid of the resource context
if (_illustrationCon)
resFile->disposeContext(_illustrationCon);
}
void CDocument::deactivate() {
_selected = 0;
gPanel::deactivate();
}
bool CDocument::activate(gEventType why) {
if (why == kEventMouseDown) { // momentarily depress
_selected = 1;
notify(why, 0); // notify App of successful hit
return true;
}
return false;
}
bool CDocument::keyStroke(gPanelMessage &msg) {
gEvent ev;
switch (msg._key) {
case Common::ASCII_ESCAPE:
cmdDocumentEsc(ev);
return true;
case Common::KEYCODE_LEFT:
cmdDocumentLt(ev);
return true;
case Common::KEYCODE_RIGHT:
cmdDocumentRt(ev);
return true;
case Common::KEYCODE_UP:
cmdDocumentUp(ev);
return true;
case Common::KEYCODE_DOWN:
cmdDocumentDn(ev);
return true;
default:
return false;
}
}
gPanel *CDocument::keyTest(int16 key) {
switch (key) {
case Common::ASCII_ESCAPE:
case Common::KEYCODE_LEFT:
case Common::KEYCODE_RIGHT:
case Common::KEYCODE_UP:
case Common::KEYCODE_DOWN:
return this;
default:
return nullptr;
}
}
// Cursor images for turning book pages
void CDocument::pointerMove(gPanelMessage &msg) {
Point16 pos = msg._pickPos;
if (msg._inPanel && Rect16(0, 0, _extent.width, _extent.height).ptInside(pos)) {
if (_app.orientation == kPageOrientVertical) {
// find out which end of the book we're on
if (pos.y < _extent.height / 2) setMouseImage(kMousePgUpImage, -7, -7);
else setMouseImage(kMousePgDownImage, -7, -7);
} else {
// find out which side of the book we're on
if (pos.x < _extent.width / 2) setMouseImage(kMousePgLeftImage, -7, -7);
else setMouseImage(kMousePgRightImage, -7, -7);
}
} else if (msg._pointerLeave) {
setMouseImage(kMouseArrowImage, 0, 0);
}
notify(kEventMouseMove, 0);
}
void CDocument::pointerDrag(gPanelMessage &) {
if (_selected) {
notify(kEventMouseDrag, 0);
}
}
bool CDocument::pointerHit(gPanelMessage &msg) {
Point16 pos = msg._pickPos;
if (msg._inPanel && Rect16(0, 0, _extent.width, _extent.height).ptInside(pos)) {
gEvent ev;
if (_app.orientation == kPageOrientVertical) {
// find out which end of the book we're on
if (pos.y < _extent.height / 2) cmdDocumentUp(ev); //gotoPage( _currentPage - _app.numPages );
else cmdDocumentDn(ev); //gotoPage( _currentPage + _app.numPages );
} else {
// find out which side of the book we're on
if (pos.x < _extent.width / 2) cmdDocumentLt(ev); //gotoPage( _currentPage - _app.numPages );
else cmdDocumentRt(ev); //gotoPage( _currentPage + _app.numPages );
}
} else {
// mouse hit outside book area, close book
gWindow *win;
requestInfo *ri;
win = getWindow(); // get the window pointer
ri = win ? (requestInfo *)win->_userData : nullptr;
if (ri) {
ri->running = 0;
ri->result = _id;
setMouseImage(kMouseArrowImage, 0, 0);
}
}
activate(kEventMouseDown);
return true;
}
void CDocument::gotoPage(int8 page) {
page = clamp(0, page, kMaxPages);
while (page % _app.numPages) page++;
if (page != _currentPage && page < _pages) {
_currentPage = page;
renderText();
}
}
void CDocument::pointerRelease(gPanelMessage &) {
if (_selected) notify(kEventMouseUp, 0); // notify App of successful hit
deactivate();
}
bool CDocument::checkForPageBreak(char *string, uint16 index, int32 &offset) {
// get the current index into the string
char *strIndex = string + index;
// page break detected
if (strIndex[1] == dPageBreak[0] &&
strIndex[2] == dPageBreak[1]) {
// eat the page breaks chars
// tie off the end
strIndex[0] = 0;
size_t txtlen = strlen(&strIndex[3]);
// string them together
memmove(&strIndex[0], &strIndex[3], txtlen);
// take the offset to the end of this line
offset = index;
// and set the new page flag
return true;
}
return false;
}
bool CDocument::checkForImage(char *string,
uint16 index,
uint16 pageIndex,
int32 &offset) {
// get the current index into the string
char *strIndex = string + index;
uint16 offPageIndex = pageIndex;
// if there was not just a page break
if (!_pageBreakSet) {
// then the images are going to end up on the next page
offPageIndex++;
}
// image detected marker
if (strIndex[1] == dImage[0] &&
strIndex[2] == dImage[1]) {
int16 numEat = 0; // number of characters to eat
char *argv = &strIndex[2 + 1]; // array to first element
// delete context
if (_illustrationCon) resFile->disposeContext(_illustrationCon);
// resource handle
_illustrationCon = resFile->newContext(MKTAG(argv[0], argv[1], argv[2], argv[3]),
"book internal resources");
// set image for next page
if (offPageIndex < kMaxPages) {
// if the last entry is defined as a number
if (argv[7] == ':') {
// convert the text into a number
char numSt[2] = { argv[8], 0 };
uint8 num = atoi(numSt);
if (!_images[offPageIndex]) {
// get the image
_images[offPageIndex] = LoadResource(_illustrationCon,
MKTAG(argv[4], argv[5], argv[6], num),
"book internal image");
}
// number of chars to eat
numEat = 9;
} else {
_images[offPageIndex] = LoadResource(_illustrationCon,
MKTAG(argv[4], argv[5], argv[6], argv[7]),
"book internal image");
numEat = 8;
}
// get the size of the image
_imageSizes[offPageIndex] = ((ImageHeader *)_images[offPageIndex])->size;
// tie off the end
strIndex[0] = 0;
// and string them together
// strIndex is inside text buffer
Common::strcat_s(&strIndex[0], _textSize + 1 - (&strIndex[0] - text), &strIndex[2 + 1 + numEat]);
// set new line length
offset = index;
// set the line offset
_lineOffset[offPageIndex] =
_imageSizes[offPageIndex].y / (_textHeight + 1) +
kTextPictureOffset;
} else {
warning("CDocument: Document overflow");
}
// set the new page flag
return true;
}
return false;
}
void CDocument::makePages() {
// copy the original text back to the working buffer
Common::strlcpy(text, origText, _textSize + 1);
char *str = text;
int32 offset = 0;
uint16 lineIndex = 0;
uint16 pageIndex = 0;
uint16 linesPerPage = _pageHeight / (_textHeight + 1);
uint16 dummy;
uint16 i;
bool newPage = false;
while (offset >= 0 && pageIndex < kMaxPages) {
while (offset >= 0 &&
lineIndex < linesPerPage &&
!newPage) {
offset = GTextWrap(_textFont, str, dummy, _lineWidth, 0);
// check for page breaks and images
for (i = 0; i <= offset; i++) {
// we hit a diliminator
if (str[i] == deliminator) {
// page break check
if (checkForPageBreak(str, i, offset)) {
// if a break did not just occur
if (!_pageBreakSet) {
newPage = true;
_pageBreakSet = true;
} else {
// eat the newPage and
// reset the flag for a just set break
_pageBreakSet = false;
}
}
// image check
if (checkForImage(str, i, pageIndex, offset)) {
// if a break did not just occur
if (!_pageBreakSet) {
newPage = true;
_pageBreakSet = true;
} else {
// eat the newPage and
// reset the flag for a just set break
_pageBreakSet = false;
}
lineIndex = _lineOffset[pageIndex];
}
}
// we got token that was not a page break so reset the flag
_pageBreakSet = false;
}
// set the length of this line
if (offset >= 0) {
// number of characters on this line
_lineLen[pageIndex][lineIndex] = offset;
} else {
// remaining number of characters in string
_lineLen[pageIndex][lineIndex] = strlen(str);
}
// increment the str pointer and line index
str += offset;
lineIndex++;
}
_numLines[pageIndex] = lineIndex;
pageIndex++;
newPage = false;
lineIndex = 0;
}
_pages = pageIndex;
}
// This function will draw the text onto the book.
void CDocument::renderText() {
gPort tPort;
gPort &port = _window._windowPort;
uint16 pageIndex;
uint16 lineIndex;
uint16 linesPerPage = _pageHeight / (_textHeight + 1);
char *str = text;
assert(_textFont);
Rect16 bltRect(0, 0, _extent.width, _extent.height);
if (NewTempPort(tPort, bltRect.width, bltRect.height)) {
// clear out the text buffer
int16 i, k;
uint8 *buffer = (uint8 *)tPort._map->_data;
for (i = 0; i < tPort._map->_size.x; i++) {
for (k = 0; k < tPort._map->_size.y; k++) {
*buffer++ = 0;
}
}
// draw a new copy of the background to the temp port
drawClipped(tPort,
Point16(_extent.x, _extent.y),
Rect16(0, 0, _extent.width, _extent.height));
tPort.setFont(_textFont); // setup the string pointer
for (pageIndex = 0; pageIndex < _currentPage; pageIndex++) {
if (_images[pageIndex]) {
lineIndex = _lineOffset[pageIndex];
assert(lineIndex < linesPerPage);
} else {
lineIndex = 0;
}
for (; lineIndex < _numLines[pageIndex]; lineIndex++) {
int16 temp = _lineLen[pageIndex][lineIndex];
assert(pageIndex < kMaxPages);
assert(temp < 35);
str += _lineLen[pageIndex][lineIndex];
}
}
// draw the text onto the pages of the book
for (pageIndex = _currentPage;
pageIndex - _currentPage < _app.numPages && pageIndex < _pages;
pageIndex++) {
StaticRect *pageRect = &_app.pageRect[pageIndex % _app.numPages];
// if there is an image on this page
if (_images[pageIndex]) {
Point16 pos;
pos.x = pageRect->x + (pageRect->width - _imageSizes[pageIndex].x) / 2;
pos.y = pageRect->y;
drawCompressedImage(tPort, pos, _images[pageIndex]);
lineIndex = _lineOffset[pageIndex];
} else {
lineIndex = 0;
}
for (; lineIndex < _numLines[pageIndex]; lineIndex++) {
assert(pageIndex <= kMaxPages);
tPort.moveTo(pageRect->x, pageRect->y + (_textHeight * lineIndex) + 1);
tPort.setColor(_app.textColors[lineIndex]);
tPort.drawText(str, _lineLen[pageIndex][lineIndex]);
// grab the next text offset
int16 temp = _lineLen[pageIndex][lineIndex];
assert(temp < 35);
str += _lineLen[pageIndex][lineIndex];
}
}
port.setMode(kDrawModeMatte);
g_vm->_pointer->hide();
port.bltPixels(*tPort._map, 0, 0,
bltRect.x, bltRect.y,
bltRect.width, bltRect.height);
g_vm->_pointer->show();
DisposeTempPort(tPort); // dispose of temporary pixelmap
}
}
void CDocument::drawClipped(
gPort &port,
const Point16 &offset,
const Rect16 &clipRect) {
g_vm->_pointer->hide();
ModalWindow::drawClipped(port, offset, clipRect);
g_vm->_pointer->show();
}
void CDocument::draw() { // redraw the window
// draw the book image
drawClipped(g_vm->_mainPort, Point16(0, 0), _extent);
// draw the text onto the book
renderText();
}
/////////
// Notes
/*
// page breaks like:
@pb
// images declared like:
@imDIALBTN:8
@imCONTHED:0
*/
/* ===================================================================== *
Text buffer
* ===================================================================== */
const int _textSize = 4096;
char bookText[_textSize] = { "" };
void appendBookText(char *string) {
if (string) {
Common::strlcat(bookText, string, _textSize - 1);
bookText[_textSize - 1] = 0;
}
}
/* ===================================================================== *
scroll window
* ===================================================================== */
void buildText(uint16 textScript) {
if (textScript > 0) {
// clear out the scroll text
Common::strlcpy(bookText, "", sizeof(bookText));
if (textScript == resImports->reserved[0]) {
Common::strlcpy(bookText, PROGRAM_ABOUT, sizeof(bookText));
}
// generate the text for the book
scriptCallFrame scf;
scf.invokedObject = Nothing;
scf.enactor = Nothing;
scf.directObject = Nothing;
scf.indirectObject = Nothing;
scf.value = 0;
// Run the script
runScript(textScript, scf);
} else {
Common::sprintf_s(bookText, "Invalid textScript: %d", textScript);
}
}
int16 openScroll(uint16 textScript) {
buildText(textScript);
// requester info struct
requestInfo rInfo;
rInfo.result = -1;
rInfo.running = true;
// point to book
CDocument *win = nullptr;
// close button
GfxCompButton *closeScroll;
void **closeBtnImage;
uint16 buttonResID = 0;
hResContext *decRes;
// init the resource context handle
decRes = resFile->newContext(MKTAG('S', 'C', 'R', 'L'), "book resources");
// get the graphics associated with the buttons
closeBtnImage = loadButtonRes(decRes, buttonResID, kNumBtnImages);
// create the window
win = new CDocument(scrollAppearance, bookText, &Script10Font, 0, nullptr);
// make the quit button
closeScroll = new GfxCompButton(*win, scrollAppearance.closeRect, closeBtnImage, kNumBtnImages, 0, cmdDocumentQuit);
closeScroll->_accelKey = 0x1B;
// attach the structure to the book, open the book
win->_userData = &rInfo;
win->open();
// do stuff
EventLoop(rInfo.running, true);
// remove the window all attatched controls
delete win;
// unload all image arrays
unloadImageRes(closeBtnImage, kNumBtnImages);
// remove the resource handle
if (decRes)
resFile->disposeContext(decRes);
// return the result code
return rInfo.result;
}
/* ===================================================================== *
Book window
* ===================================================================== */
int16 openBook(uint16 textScript) {
buildText(textScript);
// requester info struct
requestInfo rInfo;
rInfo.result = -1;
rInfo.running = true;
// point to book
CDocument *win = nullptr;
GfxCompButton *closeBook;
hResContext *decRes;
decRes = resFile->newContext(MKTAG('S', 'C', 'R', 'L'), "book resources");
// create the window
win = new CDocument(bookAppearance, bookText, &Script10Font, 0, nullptr);
// make the quit button
closeBook = new GfxCompButton(*win, bookAppearance.closeRect, cmdDocumentQuit);
closeBook->_accelKey = 0x1B;
// attach the structure to the book, open the book
win->_userData = &rInfo;
win->open();
// do stuff
EventLoop(rInfo.running, true);
// remove the window all attatched controls
delete win;
if (decRes)
resFile->disposeContext(decRes);
// return the result code
return rInfo.result;
}
/* ===================================================================== *
Parchment window
* ===================================================================== */
int16 openParchment(uint16 textScript) {
buildText(textScript);
// requester info struct
requestInfo rInfo;
rInfo.result = -1;
rInfo.running = true;
// point to book
CDocument *win = nullptr;
GfxCompButton *closeParchment;
hResContext *decRes;
decRes = resFile->newContext(MKTAG('S', 'C', 'R', 'L'), "book resources");
// create the window
win = new CDocument(parchAppearance, bookText, &Script10Font, 0, nullptr);
// make the quit button
closeParchment = new GfxCompButton(*win, parchAppearance.closeRect, cmdDocumentQuit);
closeParchment->_accelKey = 0x1B;
// attach the structure to the book, open the book
win->_userData = &rInfo;
win->open();
// do stuff
EventLoop(rInfo.running, true);
// remove the window all attatched controls
delete win;
if (decRes)
resFile->disposeContext(decRes);
// return the result code
return rInfo.result;
}
APPFUNC(cmdDocumentQuit) {
gWindow *win;
requestInfo *ri;
if (ev.panel && ev.eventType == kEventNewValue && ev.value) {
win = ev.panel->getWindow(); // get the window pointer
ri = win ? (requestInfo *)win->_userData : nullptr;
if (ri) {
ri->running = 0;
ri->result = ev.panel->_id;
}
}
}
APPFUNCV(CDocument::cmdDocumentEsc) {
requestInfo *ri = (requestInfo *)_userData;
if (ri) {
ri->running = 0;
ri->result = 0;
}
}
APPFUNCV(CDocument::cmdDocumentLt) {
gotoPage(_currentPage - _app.numPages); //draw();
}
APPFUNCV(CDocument::cmdDocumentRt) {
gotoPage(_currentPage + _app.numPages); //draw();
}
APPFUNCV(CDocument::cmdDocumentUp) {
gotoPage(_currentPage - _app.numPages); //draw();
}
APPFUNCV(CDocument::cmdDocumentDn) {
gotoPage(_currentPage + _app.numPages); //draw();
}
} // end of namespace Saga2