/* 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. * */ #include "common/util.h" #include "common/stack.h" #include "common/system.h" #include "graphics/primitives.h" #include "sci/sci.h" #include "sci/event.h" #include "sci/engine/kernel.h" #include "sci/engine/state.h" #include "sci/engine/selector.h" #include "sci/graphics/compare.h" #include "sci/graphics/ports.h" #include "sci/graphics/paint16.h" #include "sci/graphics/font.h" #include "sci/graphics/screen.h" #include "sci/graphics/text16.h" #include "sci/graphics/controls16.h" namespace Sci { GfxControls16::GfxControls16(SegManager *segMan, GfxPorts *ports, GfxPaint16 *paint16, GfxText16 *text16, GfxScreen *screen) : _segMan(segMan), _ports(ports), _paint16(paint16), _text16(text16), _screen(screen) { _texteditBlinkTime = 0; _texteditCursorVisible = false; } GfxControls16::~GfxControls16() { } const char controlListUpArrow[2] = { 0x18, 0 }; const char controlListDownArrow[2] = { 0x19, 0 }; void GfxControls16::drawListControl(Common::Rect rect, reg_t obj, int16 maxChars, int16 count, const Common::String *entries, GuiResourceId fontId, int16 upperPos, int16 cursorPos, bool isAlias) { Common::Rect workerRect = rect; GuiResourceId oldFontId = _text16->GetFontId(); int16 oldPenColor = _ports->_curPort->penClr; uint16 fontSize = 0; int16 i; int16 lastYpos; // draw basic window _paint16->eraseRect(workerRect); workerRect.grow(1); _paint16->frameRect(workerRect); // draw UP/DOWN arrows // we draw UP arrow one pixel lower than sierra did, because it looks nicer. Also the DOWN arrow has one pixel // line inbetween as well // They "fixed" this in SQ4 by having the arrow character start one pixel line later, we don't adjust there if (g_sci->getGameId() != GID_SQ4) workerRect.top++; _text16->Box(controlListUpArrow, false, workerRect, SCI_TEXT16_ALIGNMENT_CENTER, 0); workerRect.top = workerRect.bottom - 10; _text16->Box(controlListDownArrow, false, workerRect, SCI_TEXT16_ALIGNMENT_CENTER, 0); // Draw inner lines workerRect.top = rect.top + 9; workerRect.bottom -= 10; _paint16->frameRect(workerRect); workerRect.grow(-1); _text16->SetFont(fontId); fontSize = _ports->_curPort->fontHeight; _ports->penColor(_ports->_curPort->penClr); _ports->backColor(_ports->_curPort->backClr); workerRect.bottom = workerRect.top + fontSize; lastYpos = rect.bottom - fontSize; // Write actual text for (i = upperPos; i < count; i++) { _paint16->eraseRect(workerRect); const Common::String &listEntry = entries[i]; if (listEntry[0]) { _ports->moveTo(workerRect.left, workerRect.top); _text16->Draw(listEntry.c_str(), 0, MIN(maxChars, listEntry.size()), oldFontId, oldPenColor); if ((!isAlias) && (i == cursorPos)) { _paint16->invertRect(workerRect); } } workerRect.translate(0, fontSize); if (workerRect.bottom > lastYpos) break; } _text16->SetFont(oldFontId); } void GfxControls16::texteditCursorDraw(Common::Rect rect, const char *text, uint16 curPos) { int16 textWidth, i; if (!_texteditCursorVisible) { textWidth = 0; for (i = 0; i < curPos; i++) { textWidth += _text16->_font->getCharWidth((unsigned char)text[i]); } _texteditCursorRect.left = rect.left + textWidth; _texteditCursorRect.top = rect.top; _texteditCursorRect.bottom = _texteditCursorRect.top + _text16->_font->getHeight(); _texteditCursorRect.right = _texteditCursorRect.left + (text[curPos] == 0 ? 1 : _text16->_font->getCharWidth((unsigned char)text[curPos])); _paint16->invertRect(_texteditCursorRect); _paint16->bitsShow(_texteditCursorRect); _texteditCursorVisible = true; texteditSetBlinkTime(); } } void GfxControls16::texteditCursorErase() { if (_texteditCursorVisible) { _paint16->invertRect(_texteditCursorRect); _paint16->bitsShow(_texteditCursorRect); _texteditCursorVisible = false; } texteditSetBlinkTime(); } void GfxControls16::texteditSetBlinkTime() { _texteditBlinkTime = g_system->getMillis() + (30 * 1000 / 60); } void GfxControls16::kernelTexteditChange(reg_t controlObject, reg_t eventObject) { uint16 cursorPos = readSelectorValue(_segMan, controlObject, SELECTOR(cursor)); uint16 maxChars = readSelectorValue(_segMan, controlObject, SELECTOR(max)); reg_t textReference = readSelector(_segMan, controlObject, SELECTOR(text)); Common::String text; uint16 textSize, eventType, eventKey = 0, modifiers = 0; bool textChanged = false; bool textAddChar = false; Common::Rect rect; if (textReference.isNull()) error("kEditControl called on object that doesn't have a text reference"); text = _segMan->getString(textReference); uint16 oldCursorPos = cursorPos; if (!eventObject.isNull()) { textSize = text.size(); eventType = readSelectorValue(_segMan, eventObject, SELECTOR(type)); switch (eventType) { case kSciEventMousePress: // TODO: Implement mouse support for cursor change break; case kSciEventKeyDown: eventKey = readSelectorValue(_segMan, eventObject, SELECTOR(message)); modifiers = readSelectorValue(_segMan, eventObject, SELECTOR(modifiers)); switch (eventKey) { case kSciKeyBackspace: if (cursorPos > 0) { cursorPos--; text.deleteChar(cursorPos); textChanged = true; } break; case kSciKeyDelete: if (cursorPos < textSize) { text.deleteChar(cursorPos); textChanged = true; } break; case kSciKeyHome: cursorPos = 0; textChanged = true; break; case kSciKeyEnd: cursorPos = textSize; textChanged = true; break; case kSciKeyLeft: if (cursorPos > 0) { cursorPos--; textChanged = true; } break; case kSciKeyRight: if (cursorPos + 1 <= textSize) { cursorPos++; textChanged = true; } break; case kSciKeyEtx: if (modifiers & kSciKeyModCtrl) { // Control-C erases the whole line cursorPos = 0; text.clear(); textChanged = true; } break; default: if ((modifiers & kSciKeyModCtrl) && eventKey == 99) { // Control-C in earlier SCI games (SCI0 - SCI1 middle) // Control-C erases the whole line cursorPos = 0; text.clear(); textChanged = true; } else if (eventKey > 31 && eventKey < 256 && textSize < maxChars) { // insert pressed character textAddChar = true; textChanged = true; } break; } break; } } if (g_sci->getVocabulary() && !textChanged && oldCursorPos != cursorPos) { assert(!textAddChar); textChanged = g_sci->getVocabulary()->checkAltInput(text, cursorPos); } if (textChanged) { GuiResourceId oldFontId = _text16->GetFontId(); GuiResourceId fontId = readSelectorValue(_segMan, controlObject, SELECTOR(font)); rect = g_sci->_gfxCompare->getNSRect(controlObject); _text16->SetFont(fontId); if (textAddChar) { const char *textPtr = text.c_str(); // We check if we are really able to add the new char uint16 textWidth = 0; while (*textPtr) textWidth += _text16->_font->getCharWidth((byte)*textPtr++); textWidth += _text16->_font->getCharWidth(eventKey); // Does it fit? if (textWidth >= rect.width()) { _text16->SetFont(oldFontId); return; } text.insertChar(eventKey, cursorPos++); // Note: the following checkAltInput call might make the text // too wide to fit, but SSCI fails to check that too. } if (g_sci->getVocabulary()) g_sci->getVocabulary()->checkAltInput(text, cursorPos); texteditCursorErase(); _paint16->eraseRect(rect); _text16->Box(text.c_str(), false, rect, SCI_TEXT16_ALIGNMENT_LEFT, -1); _paint16->bitsShow(rect); texteditCursorDraw(rect, text.c_str(), cursorPos); _text16->SetFont(oldFontId); // Write back string _segMan->strcpy(textReference, text.c_str()); } else { if (g_system->getMillis() >= _texteditBlinkTime) { _paint16->invertRect(_texteditCursorRect); _paint16->bitsShow(_texteditCursorRect); _texteditCursorVisible = !_texteditCursorVisible; texteditSetBlinkTime(); } } writeSelectorValue(_segMan, controlObject, SELECTOR(cursor), cursorPos); } int GfxControls16::getPicNotValid() { if (getSciVersion() >= SCI_VERSION_1_1) return _screen->_picNotValidSci11; return _screen->_picNotValid; } void GfxControls16::kernelDrawButton(Common::Rect rect, reg_t obj, const char *text, uint16 languageSplitter, int16 fontId, int16 style, bool hilite) { int16 sci0EarlyPen = 0, sci0EarlyBack = 0; if (!hilite) { if (getSciVersion() == SCI_VERSION_0_EARLY) { // SCI0early actually used hardcoded green/black buttons instead of using the port colors sci0EarlyPen = _ports->_curPort->penClr; sci0EarlyBack = _ports->_curPort->backClr; _ports->penColor(0); _ports->backColor(2); } rect.grow(1); _paint16->eraseRect(rect); _paint16->frameRect(rect); rect.grow(-2); _ports->textGreyedOutput(!(style & SCI_CONTROLS_STYLE_ENABLED)); _text16->Box(text, languageSplitter, false, rect, SCI_TEXT16_ALIGNMENT_CENTER, fontId); _ports->textGreyedOutput(false); rect.grow(1); if (style & SCI_CONTROLS_STYLE_SELECTED) _paint16->frameRect(rect); if (!getPicNotValid()) { rect.grow(1); _paint16->bitsShow(rect); } if (getSciVersion() == SCI_VERSION_0_EARLY) { _ports->penColor(sci0EarlyPen); _ports->backColor(sci0EarlyBack); } } else { // SCI0early used xor to invert button rectangles resulting in pink/white buttons if (getSciVersion() == SCI_VERSION_0_EARLY) _paint16->invertRectViaXOR(rect); else _paint16->invertRect(rect); _paint16->bitsShow(rect); } } void GfxControls16::kernelDrawText(Common::Rect rect, reg_t obj, const char *text, uint16 languageSplitter, int16 fontId, TextAlignment alignment, int16 style, bool hilite) { if (!hilite) { rect.grow(1); _paint16->eraseRect(rect); rect.grow(-1); _text16->Box(text, languageSplitter, false, rect, alignment, fontId); if (style & SCI_CONTROLS_STYLE_SELECTED) { _paint16->frameRect(rect); } if (!getPicNotValid()) _paint16->bitsShow(rect); } else { _paint16->invertRect(rect); _paint16->bitsShow(rect); } } void GfxControls16::kernelDrawTextEdit(Common::Rect rect, reg_t obj, const char *text, uint16 languageSplitter, int16 fontId, int16 mode, int16 style, int16 cursorPos, int16 maxChars, bool hilite) { Common::Rect textRect = rect; uint16 oldFontId = _text16->GetFontId(); rect.grow(1); _texteditCursorVisible = false; texteditCursorErase(); _paint16->eraseRect(rect); _text16->Box(text, languageSplitter, false, textRect, SCI_TEXT16_ALIGNMENT_LEFT, fontId); _paint16->frameRect(rect); if (style & SCI_CONTROLS_STYLE_SELECTED) { _text16->SetFont(fontId); rect.grow(-1); texteditCursorDraw(rect, text, cursorPos); _text16->SetFont(oldFontId); rect.grow(1); } if (!getPicNotValid()) _paint16->bitsShow(rect); } void GfxControls16::kernelDrawIcon(Common::Rect rect, reg_t obj, GuiResourceId viewId, int16 loopNo, int16 celNo, int16 priority, int16 style, bool hilite) { if (!hilite) { _paint16->drawCelAndShow(viewId, loopNo, celNo, rect.left, rect.top, priority, 0); if (style & 0x20) { _paint16->frameRect(rect); } if (!getPicNotValid()) _paint16->bitsShow(rect); } else { _paint16->invertRect(rect); _paint16->bitsShow(rect); } } void GfxControls16::kernelDrawList(Common::Rect rect, reg_t obj, int16 maxChars, int16 count, const Common::String *entries, GuiResourceId fontId, int16 style, int16 upperPos, int16 cursorPos, bool isAlias, bool hilite) { if (!hilite) { drawListControl(rect, obj, maxChars, count, entries, fontId, upperPos, cursorPos, isAlias); rect.grow(1); if (isAlias && (style & SCI_CONTROLS_STYLE_SELECTED)) { _paint16->frameRect(rect); } if (!getPicNotValid()) _paint16->bitsShow(rect); } } } // End of namespace Sci