diff options
Diffstat (limited to 'engines/sci/graphics/menu.cpp')
-rw-r--r-- | engines/sci/graphics/menu.cpp | 648 |
1 files changed, 648 insertions, 0 deletions
diff --git a/engines/sci/graphics/menu.cpp b/engines/sci/graphics/menu.cpp new file mode 100644 index 0000000000..719b52fec1 --- /dev/null +++ b/engines/sci/graphics/menu.cpp @@ -0,0 +1,648 @@ +/* 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "common/util.h" +#include "common/stack.h" +#include "graphics/primitives.h" + +#include "sci/sci.h" +#include "sci/event.h" +#include "sci/engine/state.h" +#include "sci/graphics/helpers.h" +#include "sci/graphics/gfx.h" +#include "sci/graphics/cursor.h" +#include "sci/graphics/font.h" +#include "sci/graphics/text.h" +#include "sci/graphics/screen.h" +#include "sci/graphics/menu.h" + +namespace Sci { + +SciGuiMenu::SciGuiMenu(SciEvent *event, SegManager *segMan, Gfx *gfx, Text *text, Screen *screen, Cursor *cursor) + : _event(event), _segMan(segMan), _gfx(gfx), _text(text), _screen(screen), _cursor(cursor) { + + _listCount = 0; + // We actually set active item in here and remember last selection of the user + // sierra sci always defaulted to first item every time menu was called via ESC, we dont follow that logic + _curMenuId = 1; + _curItemId = 1; + + _menuSaveHandle = NULL_REG; + _barSaveHandle = NULL_REG; + _oldPort = NULL; +} + +SciGuiMenu::~SciGuiMenu() { + // TODO: deallocate _list and _itemList +} + +void SciGuiMenu::add(Common::String title, Common::String content, reg_t contentVmPtr) { + GuiMenuEntry *menuEntry; + uint16 itemCount = 0; + GuiMenuItemEntry *itemEntry; + int contentSize = content.size(); + int separatorCount; + int curPos, beginPos, endPos, tempPos; + int tagPos, rightAlignedPos, functionPos, altPos, controlPos; + + // Sierra SCI starts with id 1, so we do so as well + _listCount++; + menuEntry = new GuiMenuEntry(_listCount); + menuEntry->text = title; + _list.push_back(menuEntry); + + curPos = 0; + do { + itemCount++; + itemEntry = new GuiMenuItemEntry(_listCount, itemCount); + + beginPos = curPos; + + // Now go through the content till we find end-marker and collect data about it + // ':' is an end-marker for each item + tagPos = 0; rightAlignedPos = 0; + controlPos = 0; altPos = 0; functionPos = 0; + while ((curPos < contentSize) && (content[curPos] != ':')) { + switch (content[curPos]) { + case '=': // Set tag + if (tagPos) + error("multiple tag markers within one menu-item"); + tagPos = curPos; + break; + case '`': // Right-aligned + if (rightAlignedPos) + error("multiple right-aligned markers within one menu-item"); + rightAlignedPos = curPos; + break; + case '^': // Ctrl-prefix + if (controlPos) + error("multiple control markers within one menu-item"); + controlPos = curPos; + break; + case '@': // Alt-prefix + if (altPos) + error("multiple alt markers within one menu-item"); + altPos = curPos; + break; + case '#': // Function-prefix + if (functionPos) + error("multiple function markers within one menu-item"); + functionPos = curPos; + break; + } + curPos++; + } + endPos = curPos; + + // Control/Alt/Function key mapping... + if (controlPos) { + content.setChar(SCI_MENU_REPLACE_ONCONTROL, controlPos); + itemEntry->keyModifier = SCI_KEYMOD_CTRL; + tempPos = controlPos + 1; + if (tempPos >= contentSize) + error("control marker at end of item"); + itemEntry->keyPress = tolower(content[tempPos]); + content.setChar(toupper(content[tempPos]), tempPos); + } + if (altPos) { + content.setChar(SCI_MENU_REPLACE_ONALT, altPos); + itemEntry->keyModifier = SCI_KEYMOD_ALT; + tempPos = altPos + 1; + if (tempPos >= contentSize) + error("alt marker at end of item"); + itemEntry->keyPress = tolower(content[tempPos]); + content.setChar(toupper(content[tempPos]), tempPos); + } + if (functionPos) { + content.setChar(SCI_MENU_REPLACE_ONFUNCTION, functionPos); + tempPos = functionPos + 1; + if (tempPos >= contentSize) + error("function marker at end of item"); + itemEntry->keyPress = content[tempPos]; + switch (content[functionPos + 1]) { + case '1': itemEntry->keyPress = SCI_KEY_F1; break; + case '2': itemEntry->keyPress = SCI_KEY_F2; break; + case '3': itemEntry->keyPress = SCI_KEY_F3; break; + case '4': itemEntry->keyPress = SCI_KEY_F4; break; + case '5': itemEntry->keyPress = SCI_KEY_F5; break; + case '6': itemEntry->keyPress = SCI_KEY_F6; break; + case '7': itemEntry->keyPress = SCI_KEY_F7; break; + case '8': itemEntry->keyPress = SCI_KEY_F8; break; + case '9': itemEntry->keyPress = SCI_KEY_F9; break; + case '0': itemEntry->keyPress = SCI_KEY_F10; break; + default: + error("illegal function key specified"); + } + } + + // Now get all strings + tempPos = endPos; + if (rightAlignedPos) { + tempPos = rightAlignedPos; + } else if (tagPos) { + tempPos = tagPos; + } + curPos = beginPos; + separatorCount = 0; + while (curPos < tempPos) { + switch (content[curPos]) { + case '!': + case '-': + case ' ': + separatorCount++; + } + curPos++; + } + if (separatorCount == tempPos - beginPos) { + itemEntry->separatorLine = true; + } else { + EngineState *s = ((SciEngine *)g_engine)->getEngineState(); // HACK: needed for strSplit() + itemEntry->text = s->strSplit(Common::String(content.c_str() + beginPos, tempPos - beginPos).c_str()); + } + itemEntry->textVmPtr = contentVmPtr; + itemEntry->textVmPtr.offset += beginPos; + + if (rightAlignedPos) { + rightAlignedPos++; + tempPos = endPos; + // Shouldnt be needed at all, cause right aligned occurs after tag (hopefully) + // If no game is found that causes problems, remove this line (29.11.2009) + //if (tagPos && tagPos >= rightAlignedPos) + // tempPos = tagPos; + itemEntry->textRightAligned = Common::String(content.c_str() + rightAlignedPos, tempPos - rightAlignedPos); + } + + if (tagPos) { + tempPos = functionPos + 1; + if (tempPos >= contentSize) + error("tag marker at end of item"); + itemEntry->tag = atoi(content.c_str() + tempPos); + } + + curPos = endPos + 1; + + _itemList.push_back(itemEntry); + } while (curPos < contentSize); +} + +GuiMenuItemEntry *SciGuiMenu::findItem(uint16 menuId, uint16 itemId) { + GuiMenuItemList::iterator listIterator; + GuiMenuItemList::iterator listEnd = _itemList.end(); + GuiMenuItemEntry *listEntry; + + listIterator = _itemList.begin(); + while (listIterator != listEnd) { + listEntry = *listIterator; + if ((listEntry->menuId == menuId) && (listEntry->id == itemId)) + return listEntry; + + listIterator++; + } + return NULL; +} + +void SciGuiMenu::setAttribute(uint16 menuId, uint16 itemId, uint16 attributeId, reg_t value) { + EngineState *s = ((SciEngine *)g_engine)->getEngineState(); // HACK: needed for strSplit() + GuiMenuItemEntry *itemEntry = findItem(menuId, itemId); + if (!itemEntry) + error("Tried to setAttribute() on non-existant menu-item %d:%d", menuId, itemId); + switch (attributeId) { + case SCI_MENU_ATTRIBUTE_ENABLED: + itemEntry->enabled = value.isNull() ? false : true; + break; + case SCI_MENU_ATTRIBUTE_SAID: + itemEntry->saidVmPtr = value; + break; + case SCI_MENU_ATTRIBUTE_TEXT: + itemEntry->text = s->strSplit(_segMan->getString(value).c_str()); + itemEntry->textVmPtr = value; + // We assume here that no script ever creates a separatorLine dynamically + break; + case SCI_MENU_ATTRIBUTE_KEYPRESS: + itemEntry->keyPress = tolower(value.offset); + itemEntry->keyModifier = 0; + // TODO: Find out how modifier is handled + printf("setAttr keypress %X %X\n", value.segment, value.offset); + break; + case SCI_MENU_ATTRIBUTE_TAG: + itemEntry->tag = value.offset; + break; + default: + // Happens when loading a game in LSL3 - attribute 1A + warning("setAttribute() called with unsupported attributeId %X", attributeId); + } +} + +reg_t SciGuiMenu::getAttribute(uint16 menuId, uint16 itemId, uint16 attributeId) { + GuiMenuItemEntry *itemEntry = findItem(menuId, itemId); + if (!itemEntry) + error("Tried to getAttribute() on non-existant menu-item %d:%d", menuId, itemId); + switch (attributeId) { + case SCI_MENU_ATTRIBUTE_ENABLED: + if (itemEntry->enabled) + return make_reg(0, 1); + break; + case SCI_MENU_ATTRIBUTE_SAID: + return itemEntry->saidVmPtr; + case SCI_MENU_ATTRIBUTE_TEXT: + return itemEntry->textVmPtr; + case SCI_MENU_ATTRIBUTE_KEYPRESS: + // TODO: Find out how modifier is handled + return make_reg(0, itemEntry->keyPress); + case SCI_MENU_ATTRIBUTE_TAG: + return make_reg(0, itemEntry->tag); + default: + error("getAttribute() called with unsupported attributeId %X", attributeId); + } + return NULL_REG; +} + +void SciGuiMenu::drawBar() { + GuiMenuEntry *listEntry; + GuiMenuList::iterator listIterator; + GuiMenuList::iterator listEnd = _list.end(); + + // Hardcoded black on white + _gfx->FillRect(_gfx->_menuBarRect, 1, _screen->_colorWhite); + _gfx->PenColor(0); + _gfx->MoveTo(8, 1); + + listIterator = _list.begin(); + while (listIterator != listEnd) { + listEntry = *listIterator; + _text->Draw_String(listEntry->text.c_str()); + + listIterator++; + } +} + +// This helper calculates all text widths for all menus/items +void SciGuiMenu::calculateTextWidth() { + GuiMenuList::iterator menuIterator; + GuiMenuList::iterator menuEnd = _list.end(); + GuiMenuEntry *menuEntry; + GuiMenuItemList::iterator itemIterator; + GuiMenuItemList::iterator itemEnd = _itemList.end(); + GuiMenuItemEntry *itemEntry; + int16 dummyHeight; + + menuIterator = _list.begin(); + while (menuIterator != menuEnd) { + menuEntry = *menuIterator; + _text->StringWidth(menuEntry->text.c_str(), 0, menuEntry->textWidth, dummyHeight); + + menuIterator++; + } + + itemIterator = _itemList.begin(); + while (itemIterator != itemEnd) { + itemEntry = *itemIterator; + _text->StringWidth(itemEntry->text.c_str(), 0, itemEntry->textWidth, dummyHeight); + _text->StringWidth(itemEntry->textRightAligned.c_str(), 0, itemEntry->textRightAlignedWidth, dummyHeight); + + itemIterator++; + } +} + +reg_t SciGuiMenu::select(reg_t eventObject) { + int16 eventType = GET_SEL32V(_segMan, eventObject, type); + int16 keyPress, keyModifier; + Common::Point mousePosition; + GuiMenuItemList::iterator itemIterator = _itemList.begin(); + GuiMenuItemList::iterator itemEnd = _itemList.end(); + GuiMenuItemEntry *itemEntry = NULL; + bool forceClaimed = false; + EngineState *s; + byte saidSpec[64]; + + switch (eventType) { + case SCI_EVENT_KEYBOARD: + keyPress = GET_SEL32V(_segMan, eventObject, message); + keyModifier = GET_SEL32V(_segMan, eventObject, modifiers); + switch (keyPress) { + case 0: + break; + case SCI_KEY_ESC: + itemEntry = interactiveWithKeyboard(); + forceClaimed = true; + break; + default: + while (itemIterator != itemEnd) { + itemEntry = *itemIterator; + if ((itemEntry->keyPress == keyPress) && (itemEntry->keyModifier == keyModifier)) + break; + itemIterator++; + } + if (itemIterator == itemEnd) + itemEntry = NULL; + } + break; + + case SCI_EVENT_SAID: + // HACK: should be removed as soon as said() is cleaned up + s = ((SciEngine *)g_engine)->getEngineState(); + while (itemIterator != itemEnd) { + itemEntry = *itemIterator; + + if (!itemEntry->saidVmPtr.isNull()) { + // TODO: get a pointer to saidVmPtr or make said() work on VmPtrs + _segMan->memcpy(saidSpec, itemEntry->saidVmPtr, 64); + if (said(s, saidSpec, 0) != SAID_NO_MATCH) + break; + } + itemIterator++; + } + if (itemIterator == itemEnd) + itemEntry = NULL; + break; + + case SCI_EVENT_MOUSE_PRESS: + mousePosition = _cursor->getPosition(); + if (mousePosition.y < 10) { + itemEntry = interactiveWithMouse(); + forceClaimed = true; + } + break; + } + + if (!_menuSaveHandle.isNull()) { + _gfx->BitsRestore(_menuSaveHandle); + _gfx->BitsShow(_menuRect); + _menuSaveHandle = NULL_REG; + // TODO: Change to ReAnimate() + } + if (!_barSaveHandle.isNull()) { + _gfx->BitsRestore(_barSaveHandle); + _gfx->BitsShow(_gfx->_menuBarRect); + _barSaveHandle = NULL_REG; + } + if (_oldPort) + _gfx->SetPort(_oldPort); + + if ((itemEntry) || (forceClaimed)) + PUT_SEL32(_segMan, eventObject, claimed, make_reg(0, 1)); + if (itemEntry) + return make_reg(0, (itemEntry->menuId << 8) | (itemEntry->id)); + return NULL_REG; +} + +GuiMenuItemEntry *SciGuiMenu::interactiveGetItem(uint16 menuId, uint16 itemId, bool menuChanged) { + GuiMenuItemList::iterator itemIterator = _itemList.begin(); + GuiMenuItemList::iterator itemEnd = _itemList.end(); + GuiMenuItemEntry *itemEntry; + GuiMenuItemEntry *firstItemEntry = NULL; + GuiMenuItemEntry *lastItemEntry = NULL; + + // Fixup menuId if needed + if (menuId > _listCount) + menuId = 1; + if (menuId == 0) + menuId = _listCount; + while (itemIterator != itemEnd) { + itemEntry = *itemIterator; + if (itemEntry->menuId == menuId) { + if (itemEntry->id == itemId) + return itemEntry; + if (!firstItemEntry) + firstItemEntry = itemEntry; + if ((!lastItemEntry) || (itemEntry->id > lastItemEntry->id)) + lastItemEntry = itemEntry; + } + itemIterator++; + } + if ((itemId == 0) || (menuChanged)) + return lastItemEntry; + return firstItemEntry; +} + +void SciGuiMenu::drawMenu(uint16 oldMenuId, uint16 newMenuId) { + GuiMenuEntry *listEntry; + GuiMenuList::iterator listIterator; + GuiMenuList::iterator listEnd = _list.end(); + GuiMenuItemEntry *listItemEntry; + GuiMenuItemList::iterator listItemIterator; + GuiMenuItemList::iterator listItemEnd = _itemList.end(); + Common::Rect menuTextRect; + uint16 listNr = 0; + int16 maxTextWidth = 0, maxTextRightAlignedWidth = 0; + int16 topPos; + Common::Point pixelPos; + + // Remove menu, if one is displayed + if (!_menuSaveHandle.isNull()) { + _gfx->BitsRestore(_menuSaveHandle); + _gfx->BitsShow(_menuRect); + // TODO: Change to ReAnimate() + } + + // First calculate rect of menu and also invert old and new menu text + _menuRect.top = _gfx->_menuBarRect.bottom; + menuTextRect.top = _gfx->_menuBarRect.top; + menuTextRect.bottom = _gfx->_menuBarRect.bottom; + menuTextRect.left = menuTextRect.right = 7; + listIterator = _list.begin(); + while (listIterator != listEnd) { + listEntry = *listIterator; + listNr++; + menuTextRect.left = menuTextRect.right; + menuTextRect.right += listEntry->textWidth; + if (listNr == newMenuId) + _menuRect.left = menuTextRect.left; + if ((listNr == newMenuId) || (listNr == oldMenuId)) { + menuTextRect.translate(1, 0); + _gfx->InvertRect(menuTextRect); + menuTextRect.translate(-1, 0); + } + + listIterator++; + } + if (oldMenuId != 0) + _gfx->BitsShow(_gfx->_menuBarRect); + + _menuRect.bottom = _menuRect.top + 2; + listItemIterator = _itemList.begin(); + while (listItemIterator != listItemEnd) { + listItemEntry = *listItemIterator; + if (listItemEntry->menuId == newMenuId) { + _menuRect.bottom += _gfx->_curPort->fontHeight; + maxTextWidth = MAX<int16>(maxTextWidth, listItemEntry->textWidth); + maxTextRightAlignedWidth = MAX<int16>(maxTextRightAlignedWidth, listItemEntry->textRightAlignedWidth); + } + listItemIterator++; + } + _menuRect.right = _menuRect.left + 16 + 4 + 2; + _menuRect.right += maxTextWidth + maxTextRightAlignedWidth; + if (!maxTextRightAlignedWidth) + _menuRect.right -= 5; + + // Save background + _menuSaveHandle = _gfx->BitsSave(_menuRect, SCI_SCREEN_MASK_VISUAL); + + // Do the drawing + _gfx->FillRect(_menuRect, SCI_SCREEN_MASK_VISUAL, 0); + _menuRect.left++; _menuRect.right--; _menuRect.bottom--; + _gfx->FillRect(_menuRect, SCI_SCREEN_MASK_VISUAL, _screen->_colorWhite); + + _menuRect.left += 8; + topPos = _menuRect.top + 1; + listItemIterator = _itemList.begin(); + while (listItemIterator != listItemEnd) { + listItemEntry = *listItemIterator; + if (listItemEntry->menuId == newMenuId) { + if (!listItemEntry->separatorLine) { + _gfx->TextGreyedOutput(listItemEntry->enabled ? false : true); + _gfx->MoveTo(_menuRect.left, topPos); + _text->Draw_String(listItemEntry->text.c_str()); + _gfx->MoveTo(_menuRect.right - listItemEntry->textRightAlignedWidth - 5, topPos); + _text->Draw_String(listItemEntry->textRightAligned.c_str()); + } else { + // We dont 100% follow sierra here, we draw the line from left to right. Looks better + // BTW. SCI1.1 seems to put 2 pixels and then skip one, we don't do this at all (lsl6) + pixelPos.y = topPos + (_gfx->_curPort->fontHeight >> 1) - 1; + pixelPos.x = _menuRect.left - 7; + while (pixelPos.x < (_menuRect.right - 1)) { + _screen->putPixel(pixelPos.x, pixelPos.y, SCI_SCREEN_MASK_VISUAL, 0, 0, 0); + pixelPos.x += 2; + } + } + topPos += _gfx->_curPort->fontHeight; + } + listItemIterator++; + } + _gfx->TextGreyedOutput(false); + + + _menuRect.left -= 8; + _menuRect.left--; _menuRect.right++; _menuRect.bottom++; + _gfx->BitsShow(_menuRect); +} + +void SciGuiMenu::invertMenuSelection(uint16 itemId) { + Common::Rect itemRect = _menuRect; + + itemRect.top += (itemId - 1) * _gfx->_curPort->fontHeight; + itemRect.bottom = itemRect.top + _gfx->_curPort->fontHeight + 1; + itemRect.left++; itemRect.right--; + + _gfx->InvertRect(itemRect); + _gfx->BitsShow(itemRect); +} + +GuiMenuItemEntry *SciGuiMenu::interactiveWithKeyboard() { + sciEvent curEvent; + uint16 newMenuId = _curMenuId; + uint16 newItemId = _curItemId; + GuiMenuItemEntry *curItemEntry = findItem(_curMenuId, _curItemId); + GuiMenuItemEntry *newItemEntry = curItemEntry; + + // We don't 100% follow sierra here: we select last item instead of selecting first item of first menu everytime + + calculateTextWidth(); + _oldPort = _gfx->SetPort(_gfx->_menuPort); + _barSaveHandle = _gfx->BitsSave(_gfx->_menuBarRect, SCI_SCREEN_MASK_VISUAL); + + _gfx->PenColor(0); + _gfx->BackColor(_screen->_colorWhite); + + drawBar(); + drawMenu(0, curItemEntry->menuId); + invertMenuSelection(curItemEntry->id); + _gfx->BitsShow(_gfx->_menuBarRect); + _gfx->BitsShow(_menuRect); + + while (true) { + curEvent = _event->get(SCI_EVENT_ANY); + + switch (curEvent.type) { + case SCI_EVENT_KEYBOARD: + // We don't 100% follow sierra here: - sierra didn't wrap around when changing item id + // - sierra allowed item id to be 0, which didnt make any sense + do { + switch (curEvent.data) { + case SCI_KEY_ESC: + _curMenuId = curItemEntry->menuId; _curItemId = curItemEntry->id; + return NULL; + case SCI_KEY_ENTER: + if (curItemEntry->enabled) { + _curMenuId = curItemEntry->menuId; _curItemId = curItemEntry->id; + return curItemEntry; + } + break; + case SCI_KEY_LEFT: + newMenuId--; newItemId = 1; + break; + case SCI_KEY_RIGHT: + newMenuId++; newItemId = 1; + break; + case SCI_KEY_UP: + newItemId--; + break; + case SCI_KEY_DOWN: + newItemId++; + break; + } + if ((newMenuId != curItemEntry->menuId) || (newItemId != curItemEntry->id)) { + // Selection changed, fix up new selection if required + newItemEntry = interactiveGetItem(newMenuId, newItemId, newMenuId != curItemEntry->menuId); + newMenuId = newItemEntry->menuId; newItemId = newItemEntry->id; + + // if we do this step again because of a separator line -> don't repeat left/right, but go down + switch (curEvent.data) { + case SCI_KEY_LEFT: + case SCI_KEY_RIGHT: + curEvent.data = SCI_KEY_DOWN; + } + } + } while (newItemEntry->separatorLine); + if ((newMenuId != curItemEntry->menuId) || (newItemId != curItemEntry->id)) { + // paint old and new + if (newMenuId != curItemEntry->menuId) { + // Menu changed, remove cur menu and paint new menu + drawMenu(curItemEntry->menuId, newMenuId); + } else { + invertMenuSelection(curItemEntry->id); + } + invertMenuSelection(newItemId); + + curItemEntry = newItemEntry; + } + break; + + case SCI_EVENT_NONE: + kernel_sleep(_event, 2500 / 1000); + break; + } + } +} + +GuiMenuItemEntry *SciGuiMenu::interactiveWithMouse() { + calculateTextWidth(); + + // TODO + + return NULL; +} + +} // End of namespace Sci |