/* 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/savefile.h" #include "sherlock/tattoo/tattoo_journal.h" #include "sherlock/tattoo/tattoo_fixed_text.h" #include "sherlock/tattoo/tattoo_scene.h" #include "sherlock/tattoo/tattoo_user_interface.h" #include "sherlock/tattoo/tattoo.h" namespace Sherlock { namespace Tattoo { #define JOURNAL_BAR_WIDTH 450 TattooJournal::TattooJournal(SherlockEngine *vm) : Journal(vm) { _journalImages = nullptr; _selector = _oldSelector = JH_NONE; _wait = false; _exitJournal = false; _scrollingTimer = 0; _savedIndex = _savedSub = _savedPage = 0; loadLocations(); } void TattooJournal::show() { Events &events = *_vm->_events; Resources &res = *_vm->_res; Screen &screen = *_vm->_screen; TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; byte palette[PALETTE_SIZE]; Common::Point oldScroll = screen._currentScroll; screen._currentScroll = Common::Point(0, 0); // Load journal images _journalImages = new ImageFile("journal.vgs"); // Load palette Common::SeekableReadStream *stream = res.load("journal.pal"); stream->read(palette, PALETTE_SIZE); ui.setupBGArea(palette); screen.translatePalette(palette); delete stream; // Set screen to black, and set background screen._backBuffer1.SHblitFrom((*_journalImages)[0], Common::Point(0, 0)); screen.clear(); screen.setPalette(palette); if (_journal.empty()) { _up = _down = false; } else { drawJournal(0, 0); } drawControls(0); screen.slamRect(Common::Rect(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT)); _exitJournal = false; _scrollingTimer = 0; do { events.pollEventsAndWait(); events.setButtonState(); _wait = true; handleKeyboardEvents(); highlightJournalControls(true); handleButtons(); if (_wait) events.wait(2); } while (!_vm->shouldQuit() && !_exitJournal); // Clear events events.clearEvents(); // Free the images delete _journalImages; _journalImages = nullptr; // Reset back to whatever scroll was active for the screen screen._currentScroll = oldScroll; } void TattooJournal::handleKeyboardEvents() { Events &events = *_vm->_events; Screen &screen = *_vm->_screen; Common::Point mousePos = events.mousePos(); if (!events.kbHit()) return; Common::KeyState keyState = events.getKey(); if (keyState.keycode == Common::KEYCODE_TAB && (keyState.flags & Common::KBD_SHIFT)) { // Shift tab Common::Rect r(JOURNAL_BAR_WIDTH, BUTTON_SIZE + screen.fontHeight() + 13); r.moveTo((SHERLOCK_SCREEN_WIDTH - r.width()) / 2, SHERLOCK_SCREEN_HEIGHT - r.height()); // See if mouse is over any of the journal controls _selector = JH_NONE; if (Common::Rect(r.left + 3, r.top + 3, r.right - 3, r.top + screen.fontHeight() + 4).contains(mousePos)) _selector = (mousePos.x - r.left) / (r.width() / 3); // If the mouse is not over an option, move the mouse to that it points to the first option if (_selector == JH_NONE) { events.warpMouse(Common::Point(r.left + r.width() / 3 - 10, r.top + screen.fontHeight() + 2)); } else { if (_selector == JH_CLOSE) _selector = JH_SAVE; else --_selector; events.warpMouse(Common::Point(r.left + (r.width() / 3) * (_selector + 1) - 10, mousePos.y)); } } else if (keyState.keycode == Common::KEYCODE_PAGEUP) { // See if they have Shift held down to go forward 10 pages if (keyState.flags & Common::KBD_SHIFT) { if (_page > 1) { // Scroll Up 10 pages if possible if (_page < 11) drawJournal(1, (_page - 1) * LINES_PER_PAGE); else drawJournal(1, 10 * LINES_PER_PAGE); drawScrollBar(); screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); _wait = false; } } else { if (_page > 1) { // Scroll Up 1 page drawJournal(1, LINES_PER_PAGE); drawScrollBar(); drawJournal(0, 0); screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); _wait = false; } } } else if (keyState.keycode == Common::KEYCODE_PAGEDOWN) { if (keyState.flags & Common::KBD_SHIFT) { if (_down) { // Scroll down 10 Pages if (_page + 10 > _maxPage) drawJournal(2, (_maxPage - _page) * LINES_PER_PAGE); else drawJournal(2, 10 * LINES_PER_PAGE); drawScrollBar(); screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); _wait = false; } } else { if (_down) { // Scroll down 1 page drawJournal(2, LINES_PER_PAGE); drawScrollBar(); drawJournal(0, 0); screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); _wait = false; } } } else if (keyState.keycode == Common::KEYCODE_HOME) { // Scroll to start of journal if (_page > 1) { // Go to the beginning of the journal _index = _sub = _up = _down = 0; _page = 1; drawFrame(); drawJournal(0, 0); drawScrollBar(); screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); _wait = false; } } else if (keyState.keycode == Common::KEYCODE_END) { // Scroll to end of journal if (_down) { // Go to the end of the journal drawJournal(2, 100000); drawScrollBar(); screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); _wait = false; } } else if (keyState.keycode == Common::KEYCODE_RETURN) { events._pressed = false; events._released = true; events._oldButtons = 0; } else if (keyState.keycode == Common::KEYCODE_ESCAPE) { _exitJournal = true; } else if (keyState.keycode == Common::KEYCODE_TAB) { Common::Rect r(JOURNAL_BAR_WIDTH, BUTTON_SIZE + screen.fontHeight() + 13); r.moveTo((SHERLOCK_SCREEN_WIDTH - r.width()) / 2, SHERLOCK_SCENE_HEIGHT - r.height()); // See if the mouse is over any of the journal controls _selector = JH_NONE; if (Common::Rect(r.left + 3, r.top + 3, r.right - 3, r.top + screen.fontHeight() + 4).contains(mousePos)) _selector = (mousePos.x - r.left) / (r.width() / 3); // If the mouse is not over any of the options, move the mouse so that it points to the first option if (_selector == JH_NONE) { events.warpMouse(Common::Point(r.left + r.width() / 3 - 10, r.top + screen.fontHeight() + 2)); } else { if (_selector == JH_SAVE) _selector = JH_NONE; else ++_selector; events.warpMouse(Common::Point(r.left + (r.width() / 3) * (_selector + 1) - 10, mousePos.y)); } } } void TattooJournal::handleButtons() { Events &events = *_vm->_events; Screen &screen = *_vm->_screen; uint32 frameCounter = events.getFrameCounter(); Common::Point mousePos = events.mousePos(); // If they're dragging the scrollbar thumb, keep it selected whilst the button is being held if ((events._pressed || events._released) && _selector == JH_THUMBNAIL) { // Scrolling area including left/right buttons at the edges Common::Rect r(JOURNAL_BAR_WIDTH, BUTTON_SIZE + screen.fontHeight() + 13); r.moveTo((SHERLOCK_SCREEN_WIDTH - r.width()) / 2, SHERLOCK_SCREEN_HEIGHT - r.height()); // Thumbnail sliding area of the scrolling area Common::Rect scrollRect(r.left + (BUTTON_SIZE + 3), r.top, r.right - (BUTTON_SIZE + 3), r.bottom); const int numPages = (_maxPage + LINES_PER_PAGE - 1) / LINES_PER_PAGE; const int barWidth = CLIP(scrollRect.width() / numPages, BUTTON_SIZE, (int)scrollRect.width()); if (numPages == 1) return; const int scrollOffset = (mousePos.x + (barWidth / 2)) - scrollRect.left; const int page = CLIP(scrollOffset * (numPages - 1) / (scrollRect.width() - barWidth) + 1, 1, numPages); if (page != _page) { if (page < _page) drawJournal(1, (_page - page) * LINES_PER_PAGE); else drawJournal(2, (page - _page) * LINES_PER_PAGE); drawScrollBar(); screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); _wait = false; } } else if (_selector != JH_NONE && events._pressed) { if (frameCounter >= _scrollingTimer) { // Set next scrolling time _scrollingTimer = frameCounter + 6; // Handle different scrolling actions switch (_selector) { case JH_SCROLL_LEFT: // Scroll left (1 page back) if (_page > 1) { // Scroll Up drawJournal(1, LINES_PER_PAGE); drawScrollBar(); screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); _wait = false; } break; case JH_PAGE_LEFT: // Page left (10 pages back) if (_page > 1) { // Scroll Up 10 Pages if possible if (_page < 11) drawJournal(1, (_page - 1) * LINES_PER_PAGE); else drawJournal(1, 10 * LINES_PER_PAGE); drawScrollBar(); drawJournal(0, 0); screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); _wait = false; } break; case JH_PAGE_RIGHT: // Page right (10 pages ahead) if (_down) { // Scroll Down 10 Pages if (_page + 10 > _maxPage) drawJournal(2, (_maxPage - _page) * LINES_PER_PAGE); else drawJournal(2, 10 * LINES_PER_PAGE); drawScrollBar(); screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); _wait = false; } break; case JH_SCROLL_RIGHT: // Scroll right (1 Page Ahead) if (_down) { // Scroll Down drawJournal(2, LINES_PER_PAGE); drawScrollBar(); screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); _wait = false; } break; default: break; } } } if (events._released || events._rightReleased) { _scrollingTimer = 0; switch (_selector) { case JH_CLOSE: _exitJournal = true; break; case JH_SEARCH: { // Search Journal disableControls(); bool notFound = false; do { int dir; if ((dir = getFindName(notFound)) != 0) { _savedIndex = _index; _savedSub = _sub; _savedPage = _page; bool drawResult = drawJournal(dir + 2, 1000 * LINES_PER_PAGE); if (!drawResult) { _index = _savedIndex; _sub = _savedSub; _page = _savedPage; drawFrame(); drawJournal(0, 0); notFound = true; } highlightJournalControls(false); screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); if (drawResult) break; } else { break; } } while (!_vm->shouldQuit()); break; } case JH_SAVE: // Save journal to file disableControls(); saveJournal(); drawFrame(); drawJournal(0, 0); screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); break; default: break; } } } void TattooJournal::loadLocations() { Resources &res = *_vm->_res; _directory.clear(); _locations.clear(); Common::SeekableReadStream *dir = res.load("talk.lib"); dir->skip(4); // Skip header // Get the numer of entries _directory.resize(dir->readUint16LE()); dir->seek((_directory.size() + 1) * 8, SEEK_CUR); // Read in each entry char buffer[17]; for (uint idx = 0; idx < _directory.size(); ++idx) { dir->read(buffer, 17); buffer[16] = '\0'; _directory[idx] = Common::String(buffer); } delete dir; // Load in the locations stored in journal.txt Common::SeekableReadStream *loc = res.load("journal.txt"); // Initialize locations _locations.resize(100); for (int idx = 0; idx < 100; ++idx) _locations[idx] = "No Description"; while (loc->pos() < loc->size()) { // In Rose Tattoo, each location line starts with the location // number, followed by a dot, some spaces and its description // in quotes Common::String line = loc->readLine(); Common::String locNumStr; int locNum = 0; int i = 0; Common::String locDesc; // Get the location while (Common::isDigit(line[i])) { locNumStr += line[i]; i++; } locNum = atoi(locNumStr.c_str()) - 1; // Skip the dot, spaces and initial quotation mark while (line[i] == ' ' || line[i] == '.' || line[i] == '\"') i++; do { locDesc += line[i]; i++; } while (line[i] != '\"'); _locations[locNum] = locDesc; } delete loc; } void TattooJournal::drawFrame() { Screen &screen = *_vm->_screen; screen._backBuffer1.SHblitFrom((*_journalImages)[0], Common::Point(0, 0)); drawControls(0); } void TattooJournal::drawControls(int mode) { TattooEngine &vm = *(TattooEngine *)_vm; Screen &screen = *_vm->_screen; TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; ImageFile &images = *ui._interfaceImages; Common::Rect r(JOURNAL_BAR_WIDTH, !mode ? (BUTTON_SIZE + screen.fontHeight() + 13) : (screen.fontHeight() + 4) * 2 + 9); r.moveTo((SHERLOCK_SCREEN_WIDTH - r.width()) / 2, !mode ? (SHERLOCK_SCREEN_HEIGHT - r.height()) : (SHERLOCK_SCREEN_HEIGHT - r.height()) / 2); Common::Rect inner = r; inner.grow(-3); if (vm._transparentMenus) ui.makeBGArea(inner); else screen._backBuffer1.fillRect(inner, MENU_BACKGROUND); // Draw the four corners of the info box screen._backBuffer1.SHtransBlitFrom(images[0], Common::Point(r.left, r.top)); screen._backBuffer1.SHtransBlitFrom(images[1], Common::Point(r.right - images[1]._width, r.top)); screen._backBuffer1.SHtransBlitFrom(images[1], Common::Point(r.left, r.bottom - images[1]._height)); screen._backBuffer1.SHtransBlitFrom(images[1], Common::Point(r.right - images[1]._width, r.bottom - images[1]._height)); // Draw the top of the info box screen._backBuffer1.hLine(r.left + images[0]._width, r.top, r.right - images[0]._height, INFO_TOP); screen._backBuffer1.hLine(r.left + images[0]._width, r.top + 1, r.right - images[0]._height, INFO_MIDDLE); screen._backBuffer1.hLine(r.left + images[0]._width, r.top + 2, r.right - images[0]._height, INFO_BOTTOM); // Draw the bottom of the info box screen._backBuffer1.hLine(r.left + images[0]._width, r.bottom - 3, r.right - images[0]._height, INFO_TOP); screen._backBuffer1.hLine(r.left + images[0]._width, r.bottom - 2, r.right - images[0]._height, INFO_MIDDLE); screen._backBuffer1.hLine(r.left + images[0]._width, r.bottom - 1, r.right - images[0]._height, INFO_BOTTOM); // Draw the left side of the info box screen._backBuffer1.vLine(r.left, r.top + images[0]._height, r.bottom - images[2]._height, INFO_TOP); screen._backBuffer1.vLine(r.left + 1, r.top + images[0]._height, r.bottom - images[2]._height, INFO_MIDDLE); screen._backBuffer1.vLine(r.left + 2, r.top + images[0]._height, r.bottom - images[2]._height, INFO_BOTTOM); // Draw the right side of the info box screen._backBuffer1.vLine(r.right - 3, r.top + images[0]._height, r.bottom - images[2]._height, INFO_TOP); screen._backBuffer1.vLine(r.right - 2, r.top + images[0]._height, r.bottom - images[2]._height, INFO_MIDDLE); screen._backBuffer1.vLine(r.right - 1, r.top + images[0]._height, r.bottom - images[2]._height, INFO_BOTTOM); // Draw the sides of the separator bar above the scroll bar int yp = r.top + screen.fontHeight() + 7; screen._backBuffer1.SHtransBlitFrom(images[4], Common::Point(r.left, yp - 1)); screen._backBuffer1.SHtransBlitFrom(images[5], Common::Point(r.right - images[5]._width, yp - 1)); // Draw the bar above the scroll bar screen._backBuffer1.hLine(r.left + images[4]._width, yp, r.right - images[5]._width, INFO_TOP); screen._backBuffer1.hLine(r.left + images[4]._width, yp + 1, r.right - images[5]._width, INFO_MIDDLE); screen._backBuffer1.hLine(r.left + images[4]._width, yp + 2, r.right - images[5]._width, INFO_BOTTOM); if (mode != 2) { // Draw the Bars separating the Journal Commands int xp = r.left + r.width() / 3; for (int idx = 0; idx < 2; ++idx) { screen._backBuffer1.SHtransBlitFrom(images[6], Common::Point(xp - 2, r.top + 1)); screen._backBuffer1.SHtransBlitFrom(images[7], Common::Point(xp - 2, yp - 1)); screen._backBuffer1.vLine(xp - 1, r.top + 4, yp - 2, INFO_TOP); screen._backBuffer1.vLine(xp, r.top + 4, yp - 2, INFO_MIDDLE); screen._backBuffer1.vLine(xp + 1, r.top + 4, yp - 2, INFO_BOTTOM); xp += r.width() / 3; } } int savedSelector = _oldSelector; _oldSelector = 100; switch (mode) { case 0: highlightJournalControls(false); break; case 1: highlightSearchControls(false); break; default: break; } _oldSelector = savedSelector; } void TattooJournal::highlightJournalControls(bool slamIt) { Events &events = *_vm->_events; Screen &screen = *_vm->_screen; Common::Point mousePos = events.mousePos(); // Scrolling area including left/right buttons at the edges Common::Rect r(JOURNAL_BAR_WIDTH, BUTTON_SIZE + screen.fontHeight() + 13); r.moveTo((SHERLOCK_SCREEN_WIDTH - r.width()) / 2, SHERLOCK_SCREEN_HEIGHT - r.height()); // Thumbnail sliding area of the scrolling area Common::Rect scrollRect(r.left + (BUTTON_SIZE + 3), r.top, r.right - (BUTTON_SIZE + 3), r.bottom); if ((events._pressed || events._released) && _selector == JH_THUMBNAIL) { if (events._released) _selector = JH_NONE; } else { // Calculate the Scroll Position Bar const int numPages = (_maxPage + LINES_PER_PAGE - 1) / LINES_PER_PAGE; const int barWidth = CLIP(scrollRect.width() / numPages, BUTTON_SIZE, (int)scrollRect.width()); int barX = (numPages <= 1) ? scrollRect.left : (scrollRect.width() - barWidth) * FIXED_INT_MULTIPLIER / (numPages - 1) * (_page - 1) / FIXED_INT_MULTIPLIER + scrollRect.left; // See if the mouse is over any of the Journal Controls Common::Rect bounds(r.left, r.top, r.right - 3, r.top + screen.fontHeight() + 7); _selector = JH_NONE; if (bounds.contains(mousePos)) _selector = (mousePos.x - r.left) / (r.width() / 3); else if (events._pressed && mousePos.y >= (r.top + screen.fontHeight() + 10) && mousePos.y < (r.top + screen.fontHeight() + 10 + BUTTON_SIZE)) { if (mousePos.x >= r.left && mousePos.x < (r.left + BUTTON_SIZE)) // Press on the Scroll Left button _selector = JH_SCROLL_LEFT; else if (mousePos.x >= (r.left + BUTTON_SIZE + 3) && mousePos.x < barX) // Press on area to the left of the thumb, for scrolling back 10 pages _selector = JH_PAGE_LEFT; else if (mousePos.x >= (barX + barWidth) && mousePos.x < (r.right - BUTTON_SIZE - 3)) // Press on area to the right of the thumb, for scrolling forward 10 pages _selector = JH_PAGE_RIGHT; else if (mousePos.x >= (r.right - BUTTON_SIZE) && mousePos.x < r.right) // Press of the Scroll Right button _selector = JH_SCROLL_RIGHT; else if (mousePos.x >= barX && mousePos.x < (barX + barWidth)) // Mouse on thumbnail _selector = JH_THUMBNAIL; } } // See if the Search was selected, but is not available if (_journal.empty() && (_selector == JH_SEARCH || _selector == JH_SAVE)) _selector = JH_NONE; if (_selector == JH_PAGE_LEFT && _oldSelector == JH_PAGE_RIGHT) _selector = JH_PAGE_RIGHT; else if (_selector == JH_PAGE_RIGHT && _oldSelector == JH_PAGE_LEFT) _selector = JH_PAGE_LEFT; // See if they're pointing at a different control if (_selector != _oldSelector) { // Print the Journal commands int xp = r.left + r.width() / 6; byte color = (_selector == JH_CLOSE) ? COMMAND_HIGHLIGHTED : INFO_TOP; screen.gPrint(Common::Point(xp - screen.stringWidth(FIXED(CloseJournal)) / 2, r.top + 5), color, "%s", FIXED(CloseJournal)); xp += r.width() / 3; if (!_journal.empty()) color = (_selector == JH_SEARCH) ? COMMAND_HIGHLIGHTED : INFO_TOP; else color = INFO_BOTTOM; screen.gPrint(Common::Point(xp - screen.stringWidth(FIXED(SearchJournal)) / 2, r.top + 5), color, "%s", FIXED(SearchJournal)); xp += r.width() / 3; if (!_journal.empty()) color = (_selector == JH_SAVE) ? COMMAND_HIGHLIGHTED : INFO_TOP; else color = INFO_BOTTOM; screen.gPrint(Common::Point(xp - screen.stringWidth(FIXED(SaveJournal)) / 2, r.top + 5), color, "%s", FIXED(SaveJournal)); // Draw the horizontal scrollbar drawScrollBar(); if (slamIt) screen.slamRect(r); _oldSelector = _selector; } } void TattooJournal::highlightSearchControls(bool slamIt) { Events &events = *_vm->_events; Screen &screen = *_vm->_screen; Common::Point mousePos = events.mousePos(); Common::Rect r(JOURNAL_BAR_WIDTH, (screen.fontHeight() + 4) * 2 + 9); r.moveTo((SHERLOCK_SCREEN_WIDTH - r.width()) / 2, (SHERLOCK_SCREEN_HEIGHT - r.height()) / 2); const char *SEARCH_COMMANDS[3] = { FIXED(AbortSearch), FIXED(SearchBackwards), FIXED(SearchForwards) }; // See if the mouse is over any of the Journal Controls _selector = JH_NONE; if (Common::Rect(r.left + 3, r.top + 3, r.right - 3, r.top + 7 + screen.fontHeight()).contains(mousePos)) _selector = (mousePos.x - r.left) / (r.width() / 3); // See if they're pointing at a different control if (_selector != _oldSelector) { // Print the search commands int xp = r.left + r.width() / 6; for (int idx = 0; idx < 3; ++idx) { byte color = (_selector == idx) ? COMMAND_HIGHLIGHTED : INFO_TOP; screen.gPrint(Common::Point(xp - screen.stringWidth(SEARCH_COMMANDS[idx]) / 2, r.top + 5), color, "%s", SEARCH_COMMANDS[idx]); xp += r.width() / 3; } if (slamIt) screen.slamRect(r); _oldSelector = _selector; } } void TattooJournal::drawScrollBar() { Screen &screen = *_vm->_screen; TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; bool raised; byte color; Common::Rect r(JOURNAL_BAR_WIDTH, BUTTON_SIZE + screen.fontHeight() + 13); r.moveTo((SHERLOCK_SCREEN_WIDTH - r.width()) / 2, SHERLOCK_SCREEN_HEIGHT - r.height()); // Calculate the Scroll Position Bar int numPages = (_maxPage + LINES_PER_PAGE - 1) / LINES_PER_PAGE; int barWidth = (r.width() - BUTTON_SIZE * 2 - 6) / numPages; barWidth = CLIP(barWidth, BUTTON_SIZE, r.width() - BUTTON_SIZE * 2 - 6); int barX; if (numPages <= 1) { barX = r.left + 3 + BUTTON_SIZE; } else { barX = (r.width() - BUTTON_SIZE * 2 - 6 - barWidth) * FIXED_INT_MULTIPLIER / (numPages - 1) * (_page - 1) / FIXED_INT_MULTIPLIER + r.left + 3 + BUTTON_SIZE; if (barX + BUTTON_SIZE > r.left + r.width() - BUTTON_SIZE - 3) barX = r.right - BUTTON_SIZE * 2 - 3; } // Draw the scroll bar here // Draw the Scroll Left button raised = _selector != JH_SCROLL_LEFT; screen._backBuffer1.fillRect(Common::Rect(r.left, r.top + screen.fontHeight() + 12, r.left + BUTTON_SIZE, r.top + screen.fontHeight() + BUTTON_SIZE + 9), INFO_MIDDLE); ui.drawDialogRect(screen._backBuffer1, Common::Rect(r.left + 3, r.top + screen.fontHeight() + 10, r.left + 3 + BUTTON_SIZE, r.top + screen.fontHeight() + 10 + BUTTON_SIZE), raised); color = (_page > 1) ? INFO_BOTTOM + 2 : INFO_BOTTOM; screen._backBuffer1.vLine(r.left + 1 + BUTTON_SIZE / 2, r.top + screen.fontHeight() + 10 + BUTTON_SIZE / 2, r.top + screen.fontHeight() + 10 + BUTTON_SIZE / 2, color); screen._backBuffer1.vLine(r.left + 2 + BUTTON_SIZE / 2, r.top + screen.fontHeight() + 9 + BUTTON_SIZE / 2, r.top + screen.fontHeight() + 11 + BUTTON_SIZE / 2, color); screen._backBuffer1.vLine(r.left + 3 + BUTTON_SIZE / 2, r.top + screen.fontHeight() + 8 + BUTTON_SIZE / 2, r.top + screen.fontHeight() + 12 + BUTTON_SIZE / 2, color); screen._backBuffer1.vLine(r.left + 4 + BUTTON_SIZE / 2, r.top + screen.fontHeight() + 7 + BUTTON_SIZE / 2, r.top + screen.fontHeight() + 13 + BUTTON_SIZE / 2, color); // Draw the Scroll Right button raised = _selector != JH_SCROLL_RIGHT; screen._backBuffer1.fillRect(Common::Rect(r.right - BUTTON_SIZE - 1, r.top + screen.fontHeight() + 12, r.right - 5, r.top + screen.fontHeight() + BUTTON_SIZE + 9), INFO_MIDDLE); ui.drawDialogRect(screen._backBuffer1, Common::Rect(r.right - BUTTON_SIZE - 3, r.top + screen.fontHeight() + 10, r.right - 3, r.top + screen.fontHeight() + BUTTON_SIZE + 9), raised); color = _down ? INFO_BOTTOM + 2 : INFO_BOTTOM; screen._backBuffer1.vLine(r.right - 1 - BUTTON_SIZE + BUTTON_SIZE / 2, r.top + screen.fontHeight() + 10 + BUTTON_SIZE / 2, r.top + screen.fontHeight() + 10 + BUTTON_SIZE / 2, color); screen._backBuffer1.vLine(r.right - 2 - BUTTON_SIZE + BUTTON_SIZE / 2, r.top + screen.fontHeight() + 9 + BUTTON_SIZE / 2, r.top + screen.fontHeight() + 11 + BUTTON_SIZE / 2, color); screen._backBuffer1.vLine(r.right - 3 - BUTTON_SIZE + BUTTON_SIZE / 2, r.top + screen.fontHeight() + 8 + BUTTON_SIZE / 2, r.top + screen.fontHeight() + 12 + BUTTON_SIZE / 2, color); screen._backBuffer1.vLine(r.right - 4 - BUTTON_SIZE + BUTTON_SIZE / 2, r.top + screen.fontHeight() + 7 + BUTTON_SIZE / 2, r.top + screen.fontHeight() + 13 + BUTTON_SIZE / 2, color); // Draw the scroll bar screen._backBuffer1.fillRect(Common::Rect(barX + 2, r.top + screen.fontHeight() + 12, barX + barWidth - 3, r.top + screen.fontHeight() + BUTTON_SIZE + 9), INFO_MIDDLE); ui.drawDialogRect(screen._backBuffer1, Common::Rect(barX, r.top + screen.fontHeight() + 10, barX + barWidth, r.top + screen.fontHeight() + 10 + BUTTON_SIZE), true); } void TattooJournal::disableControls() { Screen &screen = *_vm->_screen; Common::Rect r(JOURNAL_BAR_WIDTH, BUTTON_SIZE + screen.fontHeight() + 13); r.moveTo((SHERLOCK_SCREEN_WIDTH - r.width()) / 2, SHERLOCK_SCREEN_HEIGHT - r.height()); const char *JOURNAL_COMMANDS[3] = { FIXED(CloseJournal), FIXED(SearchJournal), FIXED(SaveJournal) }; // Print the Journal commands int xp = r.left + r.width() / 6; for (int idx = 0; idx < 3; ++idx) { screen.gPrint(Common::Point(xp - screen.stringWidth(JOURNAL_COMMANDS[idx]) / 2, r.top + 5), INFO_BOTTOM, "%s", JOURNAL_COMMANDS[idx]); xp += r.width() / 3; } screen.slamRect(r); } int TattooJournal::getFindName(bool printError) { Events &events = *_vm->_events; Screen &screen = *_vm->_screen; Talk &talk = *_vm->_talk; int result = 0; int done = 0; Common::String name; int cursorX, cursorY; bool blinkFlag = false; int blinkCountdown = 1; enum SearchButtons { SB_CANCEL = 0, SB_BACKWARDS = 1, SB_FORWARDS = 2 }; Common::Rect r(JOURNAL_BAR_WIDTH, (screen.fontHeight() + 4) * 2 + 9); r.moveTo((SHERLOCK_SCREEN_WIDTH - r.width()) / 2, (SHERLOCK_SCREEN_HEIGHT - r.height()) / 2); // Set the cursors Y position cursorY = r.top + screen.fontHeight() + 12; drawControls(1); disableControls(); // Backup the area under the text entry Surface bgSurface(r.width() - 6, screen.fontHeight()); bgSurface.SHblitFrom(screen._backBuffer1, Common::Point(0, 0), Common::Rect(r.left + 3, cursorY, r.right - 3, cursorY + screen.fontHeight())); if (printError) { screen.gPrint(Common::Point(r.left + (r.width() - screen.stringWidth(FIXED(TextNotFound))) / 2, cursorY), INFO_TOP, "%s", FIXED(TextNotFound)); } else { // If there was a name already entered, copy it to name and display it if (!_find.empty()) { screen.gPrint(Common::Point(r.left + screen.widestChar() + 3, cursorY), COMMAND_HIGHLIGHTED, "%s", _find.c_str()); name = _find; } } screen.slamRect(r); if (printError) { // Pause to allow error to be shown int timer = 0; do { events.pollEvents(); events.setButtonState(); ++timer; events.wait(2); } while (!_vm->shouldQuit() && !events.kbHit() && !events._released && !events._rightReleased && timer < 40); events.clearEvents(); // Restore the text background screen._backBuffer1.SHblitFrom(bgSurface, Common::Point(r.left, cursorY)); // If there was a name already entered, copy it to name and display it if (!_find.empty()) { screen.gPrint(Common::Point(r.left + screen.widestChar() + 3, cursorY), COMMAND_HIGHLIGHTED, "%s", _find.c_str()); name = _find; } screen.slamArea(r.left + 3, cursorY, r.width() - 6, screen.fontHeight()); } // Set the cursors X position cursorX = r.left + screen.widestChar() + 3 + screen.stringWidth(name); do { events._released = events._rightReleased = false; while (!events.kbHit() && !events._released && !events._rightReleased) { if (talk._talkToAbort) return 0; // See if a key or a mouse button is pressed events.pollEventsAndWait(); events.setButtonState(); // Handle blinking cursor if (--blinkCountdown == 0) { blinkCountdown = 3; blinkFlag = !blinkFlag; if (blinkFlag) { // Draw cursor screen._backBuffer1.fillRect(Common::Rect(cursorX, cursorY, cursorX + 7, cursorY + 8), COMMAND_HIGHLIGHTED); screen.slamArea(cursorX, cursorY, 8, 9); } else { // Erase cursor by restoring background and writing current text screen._backBuffer1.SHblitFrom(bgSurface, Common::Point(r.left + 3, cursorY)); screen.gPrint(Common::Point(r.left + screen.widestChar() + 3, cursorY), COMMAND_HIGHLIGHTED, "%s", name.c_str()); screen.slamArea(r.left + 3, cursorY, r.width() - 3, screen.fontHeight()); } } highlightSearchControls(true); events.wait(2); if (_vm->shouldQuit()) return 0; } if (events.kbHit()) { Common::KeyState keyState = events.getKey(); Common::Point mousePos = events.mousePos(); if (keyState.keycode == Common::KEYCODE_BACKSPACE && !name.empty()) { cursorX -= screen.charWidth(name.lastChar()); name.deleteLastChar(); } if (keyState.keycode == Common::KEYCODE_RETURN) done = 1; else if (keyState.keycode == Common::KEYCODE_ESCAPE) done = -1; if (keyState.keycode == Common::KEYCODE_TAB) { r = Common::Rect(JOURNAL_BAR_WIDTH, BUTTON_SIZE + screen.fontHeight() + 13); r.moveTo((SHERLOCK_SCREEN_WIDTH - r.width()) / 2, (SHERLOCK_SCREEN_HEIGHT - r.height()) / 2); // See if the mouse is over any of the journal controls _selector = JH_NONE; if (Common::Rect(r.left + 3, r.top + 3, r.right - 3, r.top + screen.fontHeight() + 4).contains(mousePos)) _selector = (mousePos.x - r.left) / (r.width() / 3); // If the mouse is not over any of the options, move the mouse so that it points to the first option if (_selector == JH_NONE) { events.warpMouse(Common::Point(r.left + r.width() / 3, r.top + screen.fontHeight() + 2)); } else { if (keyState.keycode & Common::KBD_SHIFT) { if (_selector == JH_CLOSE) _selector = JH_SAVE; else --_selector; } else { if (_selector == JH_SAVE) _selector = JH_CLOSE; else ++_selector; } events.warpMouse(Common::Point(r.left + (r.width() / 3) * (_selector + 1) - 10, mousePos.y)); } } if (keyState.ascii >= ' ' && keyState.ascii != '@' && name.size() < 50) { if ((cursorX + screen.charWidth(keyState.ascii)) < (r.right - screen.widestChar() * 3)) { char c = toupper(keyState.ascii); cursorX += screen.charWidth(c); name += c; } } // Redraw the text screen._backBuffer1.SHblitFrom(bgSurface, Common::Point(r.left + 3, cursorY)); screen.gPrint(Common::Point(r.left + screen.widestChar() + 3, cursorY), COMMAND_HIGHLIGHTED, "%s", name.c_str()); screen.slamArea(r.left + 3, cursorY, r.right - 3, screen.fontHeight()); } if (events._released || events._rightReleased) { switch (_selector) { case (int)SB_CANCEL: done = -1; break; case (int)SB_BACKWARDS: done = 2; break; case (int)SB_FORWARDS: done = 1; break; default: break; } } } while (!done); if (done != -1) { // Forwards or backwards search, so save the entered name _find = name; result = done; } else { result = 0; } drawFrame(); drawJournal(0, 0); screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); return result; } void TattooJournal::record(int converseNum, int statementNum, bool replyOnly) { TattooEngine &vm = *(TattooEngine *)_vm; // Only record activity in the Journal if the player is Holmes (i.e. we're paast the prologoue) if (_vm->readFlags(FLAG_PLAYER_IS_HOLMES) && !vm._runningProlog) Journal::record(converseNum, statementNum, replyOnly); } void TattooJournal::saveJournal() { Talk &talk = *_vm->_talk; Common::OutSaveFile *file = g_system->getSavefileManager()->openForSaving("journal.txt", false); int tempIndex = _index; _index = 0; talk._converseNum = -1; file->writeString(" "); file->writeString(FIXED(WatsonsJournal)); file->writeString("\n\n"); // Loop through saving each page of the journal do { // Print a single talk file Common::String text; int line = 0; // Copy all of the talk files entries into one big string do { if (_lines[line].hasPrefix("@")) { text += Common::String(_lines[line].c_str() + 1); if ((line + 1) < (int)_lines.size() && _lines[line + 1].hasPrefix("@")) text += "\n"; else text += " "; } else { text += _lines[line]; text += " "; // Check for embedded location names embedded in comment fields, // which show up as a blank line with the next line starting // with a '@'. We have to add a line break here because the '@' handler // previously assumes that they're always following a blank line if ((_lines[line].empty() || _lines[line] == " ") && (line + 1) < (int)_lines.size() && _lines[line + 1].hasPrefix("@")) text += "\n"; } ++line; } while (line < (int)_lines.size()); // Now write out the text in 80 column lines do { if (text.size() > 80) { const char *msgP = text.c_str() + 80; if (Common::String(text.c_str(), msgP).contains("\n")) { // The 80 characters contain a carriage return, // so we can print out that line const char *cr = strchr(text.c_str(), '\n'); file->writeString(Common::String(text.c_str(), cr)); text = Common::String(cr + 1); } else { // Move backwards to find a word break while (*msgP != ' ') --msgP; // Write out the figured out line file->writeString(Common::String(text.c_str(), msgP)); // Remove the line that was written out while (*msgP == ' ') ++msgP; text = Common::String(msgP); } } else { // The remainder of the string is under 80 characters. // Check to see if has any line ends if (text.contains("\n")) { // Write out the line up to the carraige return const char *cr = strchr(text.c_str(), '\n'); file->writeString(Common::String(text.c_str(), cr)); text = Common::String(cr + 1); } else { // Write out the final line file->writeString(text); text = ""; } } file->writeString("\n"); } while (!text.empty()); // Move to next talk file do { ++_index; if (_index < (int)_journal.size()) loadJournalFile(false); } while (_index < (int)_journal.size() && _lines.empty()); // Don't immediately exit if there are no loaded lines for // the next page, since it's probably a stealth file and // can simply be skipped file->writeString("\n"); } while (_index < (int)_journal.size()); file->finalize(); delete file; // Free up any talk file in memory talk.freeTalkVars(); // Show the message for the journal having been saved showSavedDialog(); // Reset the previous settings of the journal _index = tempIndex; } void TattooJournal::showSavedDialog() { TattooEngine &vm = *(TattooEngine *)_vm; Events &events = *vm._events; Screen &screen = *vm._screen; TattooUserInterface &ui = *(TattooUserInterface *)vm._ui; ImageFile &images = *ui._interfaceImages; disableControls(); Common::String msg = FIXED(JournalSaved); Common::Rect inner(0, 0, screen.stringWidth(msg), screen.fontHeight()); inner.moveTo((SHERLOCK_SCREEN_WIDTH - inner.width()) / 2, (SHERLOCK_SCREEN_HEIGHT / 2) - (screen.fontHeight() / 2)); Common::Rect r = inner; r.grow(10); if (vm._transparentMenus) ui.makeBGArea(r); else screen._backBuffer1.fillRect(r, MENU_BACKGROUND); // Draw the four corners of the info box screen._backBuffer1.transBlitFrom(images[0], Common::Point(r.left, r.top)); screen._backBuffer1.transBlitFrom(images[1], Common::Point(r.right - images[1]._width, r.top)); screen._backBuffer1.transBlitFrom(images[1], Common::Point(r.left, r.bottom - images[1]._height)); screen._backBuffer1.transBlitFrom(images[1], Common::Point(r.right - images[1]._width, r.bottom - images[1]._height)); // Draw the top of the info box screen._backBuffer1.hLine(r.left + images[0]._width, r.top, r.right - images[0]._height, INFO_TOP); screen._backBuffer1.hLine(r.left + images[0]._width, r.top + 1, r.right - images[0]._height, INFO_MIDDLE); screen._backBuffer1.hLine(r.left + images[0]._width, r.top + 2, r.right - images[0]._height, INFO_BOTTOM); // Draw the bottom of the info box screen._backBuffer1.hLine(r.left + images[0]._width, r.bottom - 3, r.right - images[0]._height, INFO_TOP); screen._backBuffer1.hLine(r.left + images[0]._width, r.bottom - 2, r.right - images[0]._height, INFO_MIDDLE); screen._backBuffer1.hLine(r.left + images[0]._width, r.bottom - 1, r.right - images[0]._height, INFO_BOTTOM); // Draw the left side of the info box screen._backBuffer1.vLine(r.left, r.top + images[0]._height, r.bottom - images[2]._height, INFO_TOP); screen._backBuffer1.vLine(r.left + 1, r.top + images[0]._height, r.bottom - images[2]._height, INFO_MIDDLE); screen._backBuffer1.vLine(r.left + 2, r.top + images[0]._height, r.bottom - images[2]._height, INFO_BOTTOM); // Draw the right side of the info box screen._backBuffer1.vLine(r.right - 3, r.top + images[0]._height, r.bottom - images[2]._height, INFO_TOP); screen._backBuffer1.vLine(r.right - 2, r.top + images[0]._height, r.bottom - images[2]._height, INFO_MIDDLE); screen._backBuffer1.vLine(r.right - 1, r.top + images[0]._height, r.bottom - images[2]._height, INFO_BOTTOM); // Draw the text screen._backBuffer1.writeString(msg, Common::Point(inner.left, inner.top), INFO_TOP); screen.slamRect(r); // Five second pause events.delay(5000, true); } } // End of namespace Tattoo } // End of namespace Sherlock