Files
scummvm-cursorfix/engines/sherlock/tattoo/widget_files.cpp
2026-02-02 04:50:13 +01:00

458 lines
15 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 "common/translation.h"
#include "gui/saveload.h"
#include "sherlock/tattoo/widget_files.h"
#include "sherlock/tattoo/tattoo.h"
#include "sherlock/tattoo/tattoo_fixed_text.h"
#include "sherlock/tattoo/tattoo_scene.h"
#include "sherlock/tattoo/tattoo_user_interface.h"
#include "backends/keymapper/keymapper.h"
namespace Sherlock {
namespace Tattoo {
#define FILES_LINES_COUNT 5
WidgetFiles::WidgetFiles(SherlockEngine *vm, const Common::String &target) :
SaveManager(vm, target), WidgetBase(vm), _vm(vm) {
_fileMode = SAVEMODE_NONE;
_selector = _oldSelector = -1;
}
void WidgetFiles::show(SaveMode mode) {
Events &events = *_vm->_events;
TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
Common::Point mousePos = events.mousePos();
if (_vm->_showOriginalSavesDialog) {
// Render and display the file dialog
_fileMode = mode;
ui._menuMode = FILES_MODE;
_selector = _oldSelector = -1;
_scroll = true;
createSavegameList();
// Set up the display area
_bounds = Common::Rect(SHERLOCK_SCREEN_WIDTH * 2 / 3, (_surface.fontHeight() + 1) *
(FILES_LINES_COUNT + 1) + 17);
_bounds.moveTo(mousePos.x - _bounds.width() / 2, mousePos.y - _bounds.height() / 2);
// Create the surface and render its contents
_surface.create(_bounds.width(), _bounds.height());
render(RENDER_ALL);
summonWindow();
ui._menuMode = FILES_MODE;
Common::Keymapper *keymapper = g_system->getEventManager()->getKeymapper();
keymapper->getKeymap("tattoo")->setEnabled(false);
keymapper->getKeymap("tattoo-scrolling")->setEnabled(true);
keymapper->getKeymap("tattoo-files")->setEnabled(true);
} else if (mode == SAVEMODE_LOAD) {
showScummVMRestoreDialog();
} else {
showScummVMSaveDialog();
}
}
void WidgetFiles::showScummVMSaveDialog() {
GUI::SaveLoadChooser *dialog = new GUI::SaveLoadChooser(_("Save game:"), _("Save"), true);
int slot = dialog->runModalWithCurrentTarget();
if (slot >= 0) {
Common::String desc = dialog->getResultString();
if (desc.empty()) {
// create our own description for the saved game, the user didn't enter it
desc = dialog->createDefaultSaveDescription(slot);
}
_vm->saveGameState(slot, desc);
}
close();
delete dialog;
}
void WidgetFiles::showScummVMRestoreDialog() {
GUI::SaveLoadChooser *dialog = new GUI::SaveLoadChooser(_("Restore game:"), _("Restore"), false);
int slot = dialog->runModalWithCurrentTarget();
close();
delete dialog;
if (slot >= 0) {
_vm->loadGameState(slot);
}
}
void WidgetFiles::render(FilesRenderMode mode) {
TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
ImageFile &images = *ui._interfaceImages;
byte color;
if (mode == RENDER_ALL) {
_surface.clear(TRANSPARENCY);
makeInfoArea();
switch (_fileMode) {
case SAVEMODE_LOAD:
_surface.writeString(FIXED(LoadGame),
Common::Point((_surface.width() - _surface.stringWidth(FIXED(LoadGame))) / 2, 5), INFO_TOP);
break;
case SAVEMODE_SAVE:
_surface.writeString(FIXED(SaveGame),
Common::Point((_surface.width() - _surface.stringWidth(FIXED(SaveGame))) / 2, 5), INFO_TOP);
break;
default:
break;
}
_surface.hLine(3, _surface.fontHeight() + 7, _surface.width() - 4, INFO_TOP);
_surface.hLine(3, _surface.fontHeight() + 8, _surface.width() - 4, INFO_MIDDLE);
_surface.hLine(3, _surface.fontHeight() + 9, _surface.width() - 4, INFO_BOTTOM);
_surface.SHtransBlitFrom(images[4], Common::Point(0, _surface.fontHeight() + 6));
_surface.SHtransBlitFrom(images[5], Common::Point(_surface.width() - images[5]._width, _surface.fontHeight() + 6));
int xp = _surface.width() - BUTTON_SIZE - 6;
_surface.vLine(xp, _surface.fontHeight() + 10, _bounds.height() - 4, INFO_TOP);
_surface.vLine(xp + 1, _surface.fontHeight() + 10, _bounds.height() - 4, INFO_MIDDLE);
_surface.vLine(xp + 2, _surface.fontHeight() + 10, _bounds.height() - 4, INFO_BOTTOM);
_surface.SHtransBlitFrom(images[6], Common::Point(xp - 1, _surface.fontHeight() + 8));
_surface.SHtransBlitFrom(images[7], Common::Point(xp - 1, _bounds.height() - 4));
}
int xp = _surface.stringWidth("00.") + _surface.widestChar() + 5;
int yp = _surface.fontHeight() + 14;
for (int idx = _savegameIndex; idx < (_savegameIndex + FILES_LINES_COUNT); ++idx) {
if (idx == _selector && mode != RENDER_ALL)
color = COMMAND_HIGHLIGHTED;
else
color = INFO_TOP;
if (mode == RENDER_NAMES_AND_SCROLLBAR)
_surface.fillRect(Common::Rect(4, yp, _surface.width() - BUTTON_SIZE - 9, yp + _surface.fontHeight()), TRANSPARENCY);
Common::String numStr = Common::String::format("%d.", idx + 1);
_surface.writeString(numStr, Common::Point(_surface.widestChar(), yp), color);
_surface.writeString(_savegames[idx], Common::Point(xp, yp), color);
yp += _surface.fontHeight() + 1;
}
// Draw the Scrollbar if necessary
if (mode != RENDER_NAMES)
drawScrollBar(_savegameIndex, FILES_LINES_COUNT, _savegames.size());
}
void WidgetFiles::handleEvents() {
Events &events = *_vm->_events;
TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui;
Common::Point mousePos = events.mousePos();
Common::CustomEventType action = ui._action;
// Handle scrollbar events
ScrollHighlight oldHighlight = ui._scrollHighlight;
handleScrollbarEvents(_savegameIndex, FILES_LINES_COUNT, _savegames.size());
int oldScrollIndex = _savegameIndex;
handleScrolling(_savegameIndex, FILES_LINES_COUNT, _savegames.size());
// See if the mouse is pointing at any filenames in the window
if (Common::Rect(_bounds.left, _bounds.top + _surface.fontHeight() + 14,
_bounds.right - BUTTON_SIZE - 5, _bounds.bottom - 5).contains(mousePos)) {
_selector = (mousePos.y - _bounds.top - _surface.fontHeight() - 14) / (_surface.fontHeight() + 1) +
_savegameIndex;
} else {
_selector = -1;
}
// Check for the slot change actions
if (action == kActionTattooFilesNextSlot || action == kActionTattooFilesNextPageSlot) {
// If the mouse is not over any of the filenames, move the mouse so that it points to the first one
if (_selector == -1) {
events.warpMouse(Common::Point(_bounds.right - BUTTON_SIZE - 20,
_bounds.top + _surface.fontHeight() * 2 + 8));
} else {
// See if we're doing Next Page Slot action
if (action == kActionTattooFilesNextPageSlot) {
// We're doing Shift Tab
if (_selector == _savegameIndex)
_selector = _savegameIndex + 4;
else
--_selector;
} else {
// We're doing Next Slot action
++_selector;
if (_selector >= _savegameIndex + 5)
_selector = _savegameIndex;
}
events.warpMouse(Common::Point(mousePos.x, _bounds.top + _surface.fontHeight() * 2
+ 8 + (_selector - _savegameIndex) * (_surface.fontHeight() + 1)));
}
}
// Only redraw the window if the scrollbar position has changed
if (ui._scrollHighlight != oldHighlight || oldScrollIndex != _savegameIndex || _selector != _oldSelector)
render(RENDER_NAMES_AND_SCROLLBAR);
_oldSelector = _selector;
if (events._firstPress && !_bounds.contains(mousePos))
_outsideMenu = true;
if (events._released || events._rightReleased || action == kActionTattooFilesSelect) {
ui._scrollHighlight = SH_NONE;
Common::Keymapper *keymapper = g_system->getEventManager()->getKeymapper();
if (_outsideMenu && !_bounds.contains(mousePos)) {
keymapper->getKeymap("tattoo-scrolling")->setEnabled(false);
keymapper->getKeymap("tattoo-files")->setEnabled(false);
keymapper->getKeymap("tattoo")->setEnabled(true);
close();
} else {
_outsideMenu = false;
if (_selector != -1) {
if (_fileMode == SAVEMODE_LOAD) {
// We're in Load Mode
_vm->loadGameState(_selector);
} else if (_fileMode == SAVEMODE_SAVE) {
keymapper->getKeymap("tattoo-scrolling")->setEnabled(false);
keymapper->getKeymap("tattoo-files")->setEnabled(false);
keymapper->getKeymap("tattoo-files-name")->setEnabled(true);
keymapper->getKeymap("tattoo-exit")->setEnabled(true);
// We're in Save Mode
if (getFilename())
_vm->saveGameState(_selector, _savegames[_selector]);
keymapper->getKeymap("tattoo-files-name")->setEnabled(false);
keymapper->getKeymap("tattoo-exit")->setEnabled(false);
keymapper->getKeymap("tattoo")->setEnabled(true);
close();
}
}
}
}
}
bool WidgetFiles::getFilename() {
Events &events = *_vm->_events;
TattooScene &scene = *(TattooScene *)_vm->_scene;
Screen &screen = *_vm->_screen;
Talk &talk = *_vm->_talk;
int index = 0;
int done = 0;
bool blinkFlag = false;
int blinkCountdown = 0;
int cursorColor = 192;
byte color, textColor;
bool insert = true;
assert(_selector != -1);
Common::Point pt(_surface.stringWidth("00.") + _surface.widestChar() + 5,
_surface.fontHeight() + 14 + (_selector - _savegameIndex) * (_surface.fontHeight() + 1));
Common::String numStr = Common::String::format("%d.", _selector + 1);
_surface.writeString(numStr, Common::Point(_surface.widestChar(), pt.y), COMMAND_HIGHLIGHTED);
Common::String filename = _savegames[_selector];
if (isSlotEmpty(_selector)) {
index = 0;
_surface.fillRect(Common::Rect(pt.x, pt.y, _bounds.width() - BUTTON_SIZE - 9, pt.y + _surface.fontHeight()), TRANSPARENCY);
filename = "";
} else {
index = filename.size();
_surface.writeString(filename, pt, COMMAND_HIGHLIGHTED);
pt.x = _surface.stringWidth("00.") + _surface.stringWidth(filename) + _surface.widestChar() + 5;
}
do {
scene.doBgAnim();
if (talk._talkToAbort)
return false;
char currentChar = (index == (int)filename.size()) ? ' ' : filename[index];
Common::String charString = Common::String::format("%c", currentChar);
int width = screen.charWidth(currentChar);
// Wait for keypress or action
while (!events.kbHit() && !events.actionHit()) {
events.pollEventsAndWait();
events.setButtonState();
scene.doBgAnim();
if (talk._talkToAbort)
return false;
if (--blinkCountdown <= 0) {
blinkCountdown = 3;
blinkFlag = !blinkFlag;
if (blinkFlag) {
textColor = 236;
color = cursorColor;
} else {
textColor = COMMAND_HIGHLIGHTED;
color = TRANSPARENCY;
}
_surface.fillRect(Common::Rect(pt.x, pt.y, pt.x + width, pt.y + _surface.fontHeight()), color);
if (currentChar != ' ')
_surface.writeString(charString, pt, textColor);
}
if (_vm->shouldQuit())
return false;
}
while (events.kbHit()) {
Common::KeyState keyState = events.getKey();
if (keyState.keycode == Common::KEYCODE_BACKSPACE && index > 0) {
pt.x -= _surface.charWidth(filename[index - 1]);
--index;
if (insert) {
filename.deleteChar(index);
} else {
filename.setChar(' ', index);
}
_surface.fillRect(Common::Rect(pt.x, pt.y, _surface.width() - BUTTON_SIZE - 9, pt.y + _surface.fontHeight()), TRANSPARENCY);
_surface.writeString(filename.c_str() + index, pt, COMMAND_HIGHLIGHTED);
} else if (keyState.keycode == Common::KEYCODE_DELETE && index < (int)filename.size()) {
filename.deleteChar(index);
_surface.fillRect(Common::Rect(pt.x, pt.y, _bounds.width() - BUTTON_SIZE - 9, pt.y + _surface.fontHeight()), TRANSPARENCY);
_surface.writeString(Common::String(filename.c_str() + index), pt, COMMAND_HIGHLIGHTED);
} else if (keyState.keycode == Common::KEYCODE_RETURN) {
done = 1;
}
if ((keyState.ascii >= ' ') && (keyState.ascii <= 'z') && (index < 50)) {
if (pt.x + _surface.charWidth(keyState.ascii) < _surface.w - BUTTON_SIZE - 20) {
if (insert)
filename.insertChar(keyState.ascii, index);
else
filename.setChar(keyState.ascii, index);
_surface.fillRect(Common::Rect(pt.x, pt.y, _bounds.width() - BUTTON_SIZE - 9,
pt.y + _surface.fontHeight()), TRANSPARENCY);
_surface.writeString(filename.c_str() + index, pt, COMMAND_HIGHLIGHTED);
pt.x += _surface.charWidth(keyState.ascii);
++index;
}
}
currentChar = (index == (int)filename.size()) ? ' ' : filename[index];
charString = Common::String::format("%c", currentChar);
width = screen.charWidth(currentChar);
}
while (events.actionHit()) {
Common::CustomEventType action = events.getAction();
if ((action == kActionTattooFilesNameLeft && index > 0)
|| (action == kActionTattooFilesNameRight && index < (int)filename.size() && pt.x < (_bounds.right - BUTTON_SIZE - 20))
|| (action == kActionTattooFilesNameStart && index > 0)
|| (action == kActionTattooFilesNameEnd)) {
_surface.fillRect(Common::Rect(pt.x, pt.y, pt.x + width, pt.y + _surface.fontHeight()), TRANSPARENCY);
if (currentChar)
_surface.writeString(charString, pt, COMMAND_HIGHLIGHTED);
switch (action) {
case kActionTattooFilesNameLeft:
pt.x -= _surface.charWidth(filename[index - 1]);
--index;
break;
case kActionTattooFilesNameRight:
pt.x += _surface.charWidth(filename[index]);
++index;
break;
case kActionTattooFilesNameStart:
pt.x = _surface.stringWidth("00.") + _surface.widestChar() + 5;
index = 0;
break;
case kActionTattooFilesNameEnd:
pt.x = _surface.stringWidth("00.") + _surface.stringWidth(filename) + _surface.widestChar() + 5;
index = filename.size();
while (filename[index - 1] == ' ' && index > 0) {
pt.x -= _surface.charWidth(filename[index - 1]);
--index;
}
break;
default:
break;
}
} else if (action == kActionTattooFilesNameToggleInsertMode) {
insert = !insert;
if (insert)
cursorColor = 192;
else
cursorColor = 200;
} else if (action == kActionTattooExit) {
_selector = -1;
render(RENDER_NAMES_AND_SCROLLBAR);
done = -1;
}
currentChar = (index == (int)filename.size()) ? ' ' : filename[index];
charString = Common::String::format("%c", currentChar);
width = screen.charWidth(currentChar);
}
} while (!done && !_vm->shouldQuit());
scene.doBgAnim();
if (talk._talkToAbort)
return false;
if (done == 1)
_savegames[_selector] = filename;
return done == 1;
}
Common::Rect WidgetFiles::getScrollBarBounds() const {
Common::Rect scrollRect(BUTTON_SIZE, _bounds.height() - _surface.fontHeight() - 13);
scrollRect.moveTo(_bounds.width() - BUTTON_SIZE - 3, _surface.fontHeight() + 10);
return scrollRect;
}
} // End of namespace Tattoo
} // End of namespace Sherlock