Initial commit

This commit is contained in:
2026-02-02 04:50:13 +01:00
commit 5b11698731
22592 changed files with 7677434 additions and 0 deletions

825
gui/EventRecorder.cpp Normal file
View File

@@ -0,0 +1,825 @@
/* 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 "gui/EventRecorder.h"
#ifdef ENABLE_EVENTRECORDER
namespace Common {
DECLARE_SINGLETON(GUI::EventRecorder);
}
#include "common/debug-channels.h"
#include "backends/timer/sdl/sdl-timer.h"
#include "backends/mixer/mixer.h"
#include "common/config-manager.h"
#include "common/md5.h"
#include "gui/gui-manager.h"
#include "gui/widget.h"
#include "gui/onscreendialog.h"
#include "common/random.h"
#include "common/savefile.h"
#include "common/textconsole.h"
#include "graphics/thumbnail.h"
#include "graphics/surface.h"
#include "graphics/scaler.h"
namespace GUI {
const int kMaxRecordsNames = 0x64;
const int kDefaultScreenshotPeriod = 60000;
EventRecorder::EventRecorder() {
_timerManager = nullptr;
_recordMode = kPassthrough;
_fakeMixerManager = nullptr;
_initialized = false;
_needRedraw = false;
_processingMillis = false;
_fastPlayback = false;
_lastTimeDate.tm_sec = 0;
_lastTimeDate.tm_min = 0;
_lastTimeDate.tm_hour = 0;
_lastTimeDate.tm_mday = 0;
_lastTimeDate.tm_mon = 0;
_lastTimeDate.tm_year = 0;
_lastTimeDate.tm_wday = 0;
_fakeTimer = 0;
_savedState = false;
_acquireCount = 0;
_needcontinueGame = false;
_temporarySlot = 0;
_realSaveManager = nullptr;
_realMixerManager = nullptr;
_controlPanel = nullptr;
_lastMillis = 0;
_lastScreenshotTime = 0;
_screenshotPeriod = 0;
_playbackFile = nullptr;
_recordFile = nullptr;
}
EventRecorder::~EventRecorder() {
delete _timerManager;
}
void EventRecorder::deinit() {
if (!_initialized) {
return;
}
setFileHeader();
_needRedraw = false;
_initialized = false;
_recordMode = kPassthrough;
delete _fakeMixerManager;
_fakeMixerManager = nullptr;
_controlPanel->close();
delete _controlPanel;
debugC(1, kDebugLevelEventRec, "playback:action=stopplayback");
Common::EventDispatcher *eventDispatcher = g_system->getEventManager()->getEventDispatcher();
eventDispatcher->unregisterSource(this);
eventDispatcher->ignoreSources(false);
_recordMode = kPassthrough;
if (_playbackFile) {
_playbackFile->close();
delete _playbackFile;
}
if (_recordFile) {
_recordFile->close();
delete _recordFile;
}
switchMixer();
switchTimerManagers();
DebugMan.disableDebugChannel("EventRec");
}
void EventRecorder::updateFakeTimer(uint32 millis) {
uint32 millisDelay = millis - _lastMillis;
_lastMillis = millis;
_fakeTimer += millisDelay;
_controlPanel->setReplayedTime(_fakeTimer);
}
void EventRecorder::processTimeAndDate(TimeDate &td, bool skipRecord) {
if (!_initialized) {
return;
}
if (skipRecord) {
td = _lastTimeDate;
return;
}
Common::RecorderEvent timeDateEvent;
if (_recordMode == kRecorderRecord) {
timeDateEvent.recordedtype = Common::kRecorderEventTypeTimeDate;
timeDateEvent.timeDate = td;
_lastTimeDate = td;
_recordFile->writeEvent(timeDateEvent);
}
if ((_recordMode == kRecorderPlayback) || (_recordMode == kRecorderUpdate)) {
if (_nextEvent.recordedtype != Common::kRecorderEventTypeTimeDate) {
// just re-use any previous date time value
td = _lastTimeDate;
if (_recordMode == kRecorderUpdate) {
// if we make it to here, we're expecting there to be an event in the file
// so add one to the updated recording
debug(3, "inserting additional timedate event");
timeDateEvent.recordedtype = Common::kRecorderEventTypeTimeDate;
timeDateEvent.timeDate = td;
_recordFile->writeEvent(timeDateEvent);
}
return;
}
_lastTimeDate = _nextEvent.timeDate;
td = _lastTimeDate;
debug(3, "timedate event");
if (_recordMode == kRecorderUpdate) {
// copy the event to the updated recording
timeDateEvent.recordedtype = Common::kRecorderEventTypeTimeDate;
timeDateEvent.timeDate = td;
_recordFile->writeEvent(timeDateEvent);
}
_nextEvent = _playbackFile->getNextEvent();
}
if (_recordMode == kRecorderPlaybackPause)
td = _lastTimeDate;
}
void EventRecorder::processMillis(uint32 &millis, bool skipRecord) {
if (!_initialized) {
return;
}
if (skipRecord || _processingMillis) {
millis = _fakeTimer;
return;
}
// to prevent calling this recursively
if (_recordMode == kRecorderPlaybackPause) {
millis = _fakeTimer;
}
Common::RecorderEvent timerEvent;
switch (_recordMode) {
case kRecorderRecord:
updateSubsystems();
updateFakeTimer(millis);
timerEvent.recordedtype = Common::kRecorderEventTypeTimer;
timerEvent.time = _fakeTimer;
_recordFile->writeEvent(timerEvent);
_timerManager->handler();
break;
case kRecorderUpdate: // fallthrough
case kRecorderPlayback:
if (_nextEvent.recordedtype != Common::kRecorderEventTypeTimer) {
// just re-use any previous millis value
// this might happen if you have EventSource instances registered, that
// are querying the millis by themselves, too. If the EventRecorder::poll
// is registered and thus dispatched after those EventSource instances, it
// might look like it ran out-of-sync.
millis = _fakeTimer;
if (_recordMode == kRecorderUpdate) {
// if we make it to here, we're expecting there to be an event in the file
// so add one to the updated recording
debug(3, "inserting additional timer event");
timerEvent.recordedtype = Common::kRecorderEventTypeTimer;
timerEvent.time = _fakeTimer;
_recordFile->writeEvent(timerEvent);
}
return;
}
_processingMillis = true;
_fakeTimer = _nextEvent.time;
millis = _fakeTimer;
if (_recordMode == kRecorderUpdate) {
// copy the event to the updated recording
timerEvent.recordedtype = Common::kRecorderEventTypeTimer;
timerEvent.time = _fakeTimer;
_recordFile->writeEvent(timerEvent);
}
updateSubsystems();
_nextEvent = _playbackFile->getNextEvent();
_timerManager->handler();
_controlPanel->setReplayedTime(_fakeTimer);
_processingMillis = false;
break;
case kRecorderPlaybackPause:
millis = _fakeTimer;
break;
default:
break;
}
}
bool EventRecorder::processDelayMillis() {
return !_fastPlayback;
}
bool EventRecorder::processAutosave() {
return _recordMode == kPassthrough;
}
void EventRecorder::processScreenUpdate() {
if (!_initialized) {
return;
}
Common::RecorderEvent screenUpdateEvent;
switch (_recordMode) {
case kRecorderRecord:
updateSubsystems();
screenUpdateEvent.recordedtype = Common::kRecorderEventTypeScreenUpdate;
screenUpdateEvent.time = _fakeTimer;
_recordFile->writeEvent(screenUpdateEvent);
takeScreenshot();
_timerManager->handler();
break;
case kRecorderUpdate: // fallthrough
case kRecorderPlayback:
// if the next event isn't a screen update, fast forward until we find one.
if (_nextEvent.recordedtype != Common::kRecorderEventTypeScreenUpdate) {
int numSkipped = 0;
while (true) {
_nextEvent = _playbackFile->getNextEvent();
numSkipped += 1;
if (_nextEvent.recordedtype == Common::kRecorderEventTypeScreenUpdate) {
warning("Skipped %d events to get to the next screen update at %d", numSkipped, _nextEvent.time);
break;
}
}
}
_processingMillis = true;
_fakeTimer = _nextEvent.time;
updateSubsystems();
_nextEvent = _playbackFile->getNextEvent();
if (_recordMode == kRecorderUpdate) {
// write event to the updated file and update screenshot if necessary
screenUpdateEvent.recordedtype = Common::kRecorderEventTypeScreenUpdate;
screenUpdateEvent.time = _fakeTimer;
_recordFile->writeEvent(screenUpdateEvent);
takeScreenshot();
}
_timerManager->handler();
_controlPanel->setReplayedTime(_fakeTimer);
_processingMillis = false;
break;
default:
break;
}
}
void EventRecorder::checkForKeyCode(const Common::Event &event) {
if ((event.type == Common::EVENT_KEYDOWN) && (event.kbd.flags & Common::KBD_CTRL) && (event.kbd.keycode == Common::KEYCODE_p) && (!event.kbdRepeat)) {
togglePause();
}
}
bool EventRecorder::pollEvent(Common::Event &ev) {
if (((_recordMode != kRecorderPlayback) &&
(_recordMode != kRecorderUpdate)) ||
!_initialized)
return false;
if (_nextEvent.recordedtype == Common::kRecorderEventTypeTimer
|| _nextEvent.recordedtype == Common::kRecorderEventTypeTimeDate
|| _nextEvent.recordedtype == Common::kRecorderEventTypeScreenUpdate
|| _nextEvent.type == Common::EVENT_INVALID) {
return false;
}
ev = _nextEvent;
_nextEvent = _playbackFile->getNextEvent();
switch (ev.type) {
case Common::EVENT_MOUSEMOVE:
case Common::EVENT_LBUTTONDOWN:
case Common::EVENT_LBUTTONUP:
case Common::EVENT_RBUTTONDOWN:
case Common::EVENT_RBUTTONUP:
case Common::EVENT_WHEELUP:
case Common::EVENT_WHEELDOWN:
g_system->warpMouse(ev.mouse.x, ev.mouse.y);
break;
default:
break;
}
return true;
}
void EventRecorder::switchFastMode() {
if (_recordMode == kRecorderPlaybackPause) {
_fastPlayback = !_fastPlayback;
}
}
void EventRecorder::togglePause() {
RecordMode oldState;
switch (_recordMode) {
case kRecorderPlayback:
case kRecorderRecord:
case kRecorderUpdate:
oldState = _recordMode;
_recordMode = kRecorderPlaybackPause;
_controlPanel->runModal();
_recordMode = oldState;
_initialized = true;
break;
case kRecorderPlaybackPause:
_controlPanel->close();
break;
default:
break;
}
}
void EventRecorder::RegisterEventSource() {
g_system->getEventManager()->getEventDispatcher()->registerObserver(this, Common::EventManager::kEventRecorderPriority, false);
}
uint32 EventRecorder::getRandomSeed(const Common::String &name) {
if (_recordMode == kRecorderPlayback) {
return _playbackFile->getHeader().randomSourceRecords[name];
}
uint32 result = Common::RandomSource::generateNewSeed();
if (_recordMode == kRecorderRecord) {
_recordFile->getHeader().randomSourceRecords[name] = result;
}
return result;
}
Common::String EventRecorder::generateRecordFileName(const Common::String &target) {
Common::String pattern(target + ".r??");
Common::StringArray files = g_system->getSavefileManager()->listSavefiles(pattern);
for (int i = 0; i < kMaxRecordsNames; ++i) {
Common::String recordName = Common::String::format("%s.r%02d", target.c_str(), i);
if (find(files.begin(), files.end(), recordName) != files.end()) {
continue;
}
return recordName;
}
return "";
}
void EventRecorder::setFastPlayback(bool fastPlayback) {
_fastPlayback = fastPlayback;
}
void EventRecorder::init(const Common::String &recordFileName, RecordMode mode) {
_fakeMixerManager = new NullMixerManager();
_fakeMixerManager->init();
_fakeMixerManager->suspendAudio();
_fakeTimer = 0;
_lastMillis = g_system->getMillis();
_lastScreenshotTime = 0;
_recordMode = mode;
_needcontinueGame = false;
_fastPlayback = false;
if (ConfMan.hasKey("disable_display")) {
DebugMan.enableDebugChannel("EventRec");
gDebugLevel = 1;
}
if ((_recordMode == kRecorderPlayback) || (_recordMode == kRecorderUpdate)) {
debugC(1, kDebugLevelEventRec, "playback:action=\"Load file\" filename=%s", recordFileName.c_str());
Common::EventDispatcher *eventDispatcher = g_system->getEventManager()->getEventDispatcher();
eventDispatcher->clearEvents();
eventDispatcher->ignoreSources(true);
eventDispatcher->registerSource(this, false);
}
_screenshotPeriod = ConfMan.getInt("screenshot_period");
if (_screenshotPeriod == 0) {
_screenshotPeriod = kDefaultScreenshotPeriod;
}
if (!openRecordFile(recordFileName)) {
deinit();
error("playback:action=error reason=\"Record file loading error\"");
return;
}
if (_recordMode != kPassthrough) {
_controlPanel = new GUI::OnScreenDialog(_recordMode == kRecorderRecord);
_controlPanel->reflowLayout();
}
if ((_recordMode == kRecorderPlayback) || (_recordMode == kRecorderUpdate)) {
applyPlaybackSettings();
_nextEvent = _playbackFile->getNextEvent();
}
if ((_recordMode == kRecorderRecord) || (_recordMode == kRecorderUpdate)) {
getConfig();
}
switchMixer();
switchTimerManagers();
_needRedraw = true;
_initialized = true;
}
/**
* Opens or creates file depend of recording mode.
*
* @param id of recording or playing back game
* @return true in case of success, false in case of error
*/
bool EventRecorder::openRecordFile(const Common::String &fileName) {
bool result;
switch (_recordMode) {
case kRecorderRecord:
_recordFile = new Common::PlaybackFile();
return _recordFile->openWrite(fileName);
case kRecorderPlayback:
_recordMode = kPassthrough;
_playbackFile = new Common::PlaybackFile();
result = _playbackFile->openRead(fileName);
_recordMode = kRecorderPlayback;
return result;
case kRecorderUpdate:
_recordMode = kPassthrough;
_playbackFile = new Common::PlaybackFile();
result = _playbackFile->openRead(fileName);
_recordMode = kRecorderUpdate;
_recordFile = new Common::PlaybackFile();
result &= _recordFile->openWrite(fileName + ".new");
return result;
default:
return false;
}
return true;
}
bool EventRecorder::checkGameHash(const ADGameDescription *gameDesc) {
if (_playbackFile->getHeader().hashRecords.size() == 0) {
warning("Engine doesn't contain description table, skipping hash check");
return true;
}
for (const ADGameFileDescription *fileDesc = gameDesc->filesDescriptions; fileDesc->fileName; fileDesc++) {
if (fileDesc->md5 == nullptr)
continue;
if (_playbackFile->getHeader().hashRecords.find(fileDesc->fileName) == _playbackFile->getHeader().hashRecords.end()) {
warning("MD5 hash for file %s not found in record file", fileDesc->fileName);
debugC(1, kDebugLevelEventRec, "playback:action=\"Check game hash\" filename=%s filehash=%s storedhash=\"\" result=different", fileDesc->fileName, fileDesc->md5);
return false;
}
if (_playbackFile->getHeader().hashRecords[fileDesc->fileName] != fileDesc->md5) {
warning("Incorrect version of game file %s. Stored MD5 is %s. MD5 of loaded game is %s", fileDesc->fileName, _playbackFile->getHeader().hashRecords[fileDesc->fileName].c_str(), fileDesc->md5);
debugC(1, kDebugLevelEventRec, "playback:action=\"Check game hash\" filename=%s filehash=%s storedhash=%s result=different", fileDesc->fileName, fileDesc->md5, _playbackFile->getHeader().hashRecords[fileDesc->fileName].c_str());
return false;
}
debugC(1, kDebugLevelEventRec, "playback:action=\"Check game hash\" filename=%s filehash=%s storedhash=%s result=equal", fileDesc->fileName, fileDesc->md5, _playbackFile->getHeader().hashRecords[fileDesc->fileName].c_str());
}
return true;
}
void EventRecorder::registerMixerManager(MixerManager *mixerManager) {
_realMixerManager = mixerManager;
}
void EventRecorder::switchMixer() {
if (_recordMode == kPassthrough) {
_realMixerManager->resumeAudio();
} else {
_realMixerManager->suspendAudio();
_fakeMixerManager->resumeAudio();
}
}
MixerManager *EventRecorder::getMixerManager() {
if (_recordMode == kPassthrough) {
return _realMixerManager;
} else {
return _fakeMixerManager;
}
}
void EventRecorder::getConfigFromDomain(const Common::ConfigManager::Domain *domain) {
for (Common::ConfigManager::Domain::const_iterator entry = domain->begin(); entry!= domain->end(); ++entry) {
_recordFile->getHeader().settingsRecords[entry->_key] = entry->_value;
}
}
void EventRecorder::getConfig() {
_recordFile->getHeader().settingsRecords["double_click_time"] = Common::String::format("%u", static_cast<unsigned int>(g_system->getDoubleClickTime()));
getConfigFromDomain(ConfMan.getDomain(ConfMan.kApplicationDomain));
getConfigFromDomain(ConfMan.getActiveDomain());
_recordFile->getHeader().settingsRecords["save_slot"] = ConfMan.get("save_slot");
}
void EventRecorder::applyPlaybackSettings() {
for (Common::StringMap::const_iterator i = _playbackFile->getHeader().settingsRecords.begin(); i != _playbackFile->getHeader().settingsRecords.end(); ++i) {
if (_recordMode == kRecorderUpdate) {
// copy the header values to the new recording
_recordFile->getHeader().settingsRecords[i->_key] = i->_value;
}
Common::String currentValue = ConfMan.get(i->_key);
if (currentValue != i->_value) {
ConfMan.set(i->_key, i->_value, ConfMan.kTransientDomain);
debugC(1, kDebugLevelEventRec, "playback:action=\"Apply settings\" key=%s storedvalue=%s currentvalue=%s result=different", i->_key.c_str(), i->_value.c_str(), currentValue.c_str());
} else {
debugC(1, kDebugLevelEventRec, "playback:action=\"Apply settings\" key=%s storedvalue=%s currentvalue=%s result=equal", i->_key.c_str(), i->_value.c_str(), currentValue.c_str());
}
}
removeDifferentEntriesInDomain(ConfMan.getDomain(ConfMan.kApplicationDomain));
removeDifferentEntriesInDomain(ConfMan.getActiveDomain());
}
void EventRecorder::removeDifferentEntriesInDomain(Common::ConfigManager::Domain *domain) {
for (Common::ConfigManager::Domain::const_iterator entry = domain->begin(); entry!= domain->end(); ++entry) {
if (_playbackFile->getHeader().settingsRecords.find(entry->_key) == _playbackFile->getHeader().settingsRecords.end()) {
debugC(1, kDebugLevelEventRec, "playback:action=\"Apply settings\" checksettings:key=%s storedvalue=%s currentvalue="" result=different", entry->_key.c_str(), entry->_value.c_str());
domain->erase(entry->_key);
}
}
}
DefaultTimerManager *EventRecorder::getTimerManager() {
return _timerManager;
}
void EventRecorder::registerTimerManager(DefaultTimerManager *timerManager) {
_timerManager = timerManager;
}
void EventRecorder::switchTimerManagers() {
delete _timerManager;
if (_recordMode == kPassthrough) {
_timerManager = new SdlTimerManager();
} else {
_timerManager = new DefaultTimerManager();
}
}
void EventRecorder::updateSubsystems() {
if (_recordMode == kPassthrough) {
return;
}
RecordMode oldRecordMode = _recordMode;
_recordMode = kPassthrough;
_fakeMixerManager->update();
_recordMode = oldRecordMode;
}
bool EventRecorder::notifyEvent(const Common::Event &ev) {
if ((!_initialized) && (_recordMode != kRecorderPlaybackPause)) {
return false;
}
checkForKeyCode(ev);
Common::Event evt = ev;
evt.mouse.x = evt.mouse.x * (g_system->getOverlayWidth() / g_system->getWidth());
evt.mouse.y = evt.mouse.y * (g_system->getOverlayHeight() / g_system->getHeight());
switch (_recordMode) {
case kRecorderUpdate: // passthrough
case kRecorderPlayback:
// pass through screen updates to avoid loss of sync!
if (evt.type == Common::EVENT_SCREEN_CHANGED)
g_gui.processEvent(evt, _controlPanel);
if (_recordMode == kRecorderUpdate) {
// write a copy of the event to the output buffer
Common::RecorderEvent e(ev);
e.recordedtype = Common::kRecorderEventTypeNormal;
e.time = _fakeTimer;
_recordFile->writeEvent(e);
}
return false;
case kRecorderRecord:
g_gui.processEvent(evt, _controlPanel);
if (((evt.type == Common::EVENT_LBUTTONDOWN) || (evt.type == Common::EVENT_LBUTTONUP) || (evt.type == Common::EVENT_MOUSEMOVE)) && _controlPanel->isMouseOver()) {
return true;
} else {
Common::RecorderEvent e(ev);
e.recordedtype = Common::kRecorderEventTypeNormal;
e.time = _fakeTimer;
_recordFile->writeEvent(e);
return false;
}
case kRecorderPlaybackPause: {
Common::Event dialogEvent;
if (_controlPanel->isEditDlgVisible()) {
dialogEvent = ev;
} else {
dialogEvent = evt;
}
g_gui.processEvent(dialogEvent, _controlPanel->getActiveDlg());
if (((dialogEvent.type == Common::EVENT_LBUTTONDOWN) || (dialogEvent.type == Common::EVENT_LBUTTONUP) || (dialogEvent.type == Common::EVENT_MOUSEMOVE)) && _controlPanel->isMouseOver()) {
return true;
}
return false;
}
default:
return false;
}
}
void EventRecorder::setGameMd5(const ADGameDescription *gameDesc) {
for (const ADGameFileDescription *fileDesc = gameDesc->filesDescriptions; fileDesc->fileName; fileDesc++) {
if (fileDesc->md5 != nullptr) {
_recordFile->getHeader().hashRecords[fileDesc->fileName] = fileDesc->md5;
}
}
}
void EventRecorder::processGameDescription(const ADGameDescription *desc) {
if ((_recordMode == kRecorderRecord) || (_recordMode == kRecorderUpdate)) {
setGameMd5(desc);
}
if (((_recordMode == kRecorderPlayback) || (_recordMode == kRecorderUpdate)) && !checkGameHash(desc)) {
deinit();
error("playback:action=error reason=\"\"");
}
}
void EventRecorder::deleteRecord(const Common::String& fileName) {
g_system->getSavefileManager()->removeSavefile(fileName);
}
void EventRecorder::takeScreenshot() {
if ((_fakeTimer - _lastScreenshotTime) > _screenshotPeriod) {
Graphics::Surface screen;
uint8 md5[16];
if (grabScreenAndComputeMD5(screen, md5)) {
_lastScreenshotTime = _fakeTimer;
_recordFile->saveScreenShot(screen, md5);
screen.free();
}
}
}
bool EventRecorder::grabScreenAndComputeMD5(Graphics::Surface &screen, uint8 md5[16]) {
if (!createScreenShot(screen)) {
warning("Can't save screenshot");
return false;
}
Common::MemoryReadStream bitmapStream((const byte*)screen.getPixels(), screen.w * screen.h * screen.format.bytesPerPixel);
computeStreamMD5(bitmapStream, md5);
return true;
}
Common::SeekableReadStream *EventRecorder::processSaveStream(const Common::String &fileName) {
Common::InSaveFile *saveFile;
switch (_recordMode) {
case kRecorderPlayback:
debugC(1, kDebugLevelEventRec, "playback:action=\"Process save file\" filename=%s len=%d", fileName.c_str(), _playbackFile->getHeader().saveFiles[fileName].size);
return new Common::MemoryReadStream(_playbackFile->getHeader().saveFiles[fileName].buffer, _playbackFile->getHeader().saveFiles[fileName].size);
case kRecorderRecord:
saveFile = _realSaveManager->openForLoading(fileName);
if (saveFile != nullptr) {
_recordFile->addSaveFile(fileName, saveFile);
saveFile->seek(0);
}
return saveFile;
default:
return nullptr;
}
}
Common::SaveFileManager *EventRecorder::getSaveManager(Common::SaveFileManager *realSaveManager) {
_realSaveManager = realSaveManager;
if (_recordMode != kPassthrough) {
return &_fakeSaveManager;
} else {
return realSaveManager;
}
}
void EventRecorder::preDrawOverlayGui() {
if ((_initialized) || (_needRedraw)) {
RecordMode oldMode = _recordMode;
_recordMode = kPassthrough;
g_system->showOverlay();
g_gui.checkScreenChange();
g_gui.theme()->clearAll();
g_gui.theme()->drawToBackbuffer();
_controlPanel->drawDialog(kDrawLayerBackground);
g_gui.theme()->drawToScreen();
g_gui.theme()->copyBackBufferToScreen();
_controlPanel->drawDialog(kDrawLayerForeground);
g_gui.theme()->updateScreen();
_recordMode = oldMode;
}
}
void EventRecorder::postDrawOverlayGui() {
if ((_initialized) || (_needRedraw)) {
RecordMode oldMode = _recordMode;
_recordMode = kPassthrough;
g_system->hideOverlay();
_recordMode = oldMode;
}
}
Common::StringArray EventRecorder::listSaveFiles(const Common::String &pattern) {
if ((_recordMode == kRecorderPlayback) || (_recordMode == kRecorderUpdate)) {
Common::StringArray result;
for (Common::HashMap<Common::String, Common::PlaybackFile::SaveFileBuffer>::iterator i = _playbackFile->getHeader().saveFiles.begin(); i != _playbackFile->getHeader().saveFiles.end(); ++i) {
if (i->_key.matchString(pattern, false, "/")) {
result.push_back(i->_key);
}
}
return result;
} else {
return _realSaveManager->listSavefiles(pattern);
}
}
void EventRecorder::setFileHeader() {
if ((_recordMode != kRecorderRecord) && (_recordMode != kRecorderUpdate)) {
return;
}
TimeDate t;
QualifiedGameDescriptor desc = EngineMan.findTarget(ConfMan.getActiveDomainName());
g_system->getTimeAndDate(t);
if (_author.empty()) {
setAuthor("Unknown Author");
}
if (_name.empty()) {
g_eventRec.setName(Common::String::format("%.2d.%.2d.%.4d ", t.tm_mday, t.tm_mon + 1, 1900 + t.tm_year) + desc.description);
}
_recordFile->getHeader().author = _author;
_recordFile->getHeader().notes = _desc;
_recordFile->getHeader().name = _name;
}
SDL_Surface *EventRecorder::getSurface(int width, int height) {
// Create a RGB565 surface of the requested dimensions.
return SDL_CreateRGBSurface(SDL_SWSURFACE, width, height, 16, 0xF800, 0x07E0, 0x001F, 0x0000);
}
bool EventRecorder::switchMode() {
const Plugin *plugin = PluginMan.findEnginePlugin(ConfMan.get("engineid"));
bool metaInfoSupport = plugin->get<MetaEngine>().hasFeature(MetaEngine::kSavesSupportMetaInfo);
bool featuresSupport = metaInfoSupport &&
g_engine->canSaveGameStateCurrently() &&
plugin->get<MetaEngine>().hasFeature(MetaEngine::kSupportsListSaves) &&
plugin->get<MetaEngine>().hasFeature(MetaEngine::kSupportsDeleteSave);
if (!featuresSupport) {
return false;
}
const Common::String target = ConfMan.getActiveDomainName();
SaveStateList saveList = plugin->get<MetaEngine>().listSaves(target.c_str());
int emptySlot = 1;
for (const auto &x : saveList) {
int saveSlot = x.getSaveSlot();
if (saveSlot == 0) {
continue;
}
if (emptySlot != saveSlot) {
break;
}
emptySlot++;
}
Common::String saveName;
if (emptySlot >= 0) {
saveName = Common::String::format("Save %d", emptySlot + 1);
Common::Error status = g_engine->saveGameState(emptySlot, saveName);
if (status.getCode() == Common::kNoError) {
Common::Event eventReturnToLauncher;
eventReturnToLauncher.type = Common::EVENT_RETURN_TO_LAUNCHER;
g_system->getEventManager()->pushEvent(eventReturnToLauncher);
}
}
ConfMan.set("record_mode", "", Common::ConfigManager::kTransientDomain);
ConfMan.setInt("save_slot", emptySlot, Common::ConfigManager::kTransientDomain);
_needcontinueGame = true;
return true;
}
bool EventRecorder::checkForContinueGame() {
bool result = _needcontinueGame;
_needcontinueGame = false;
return result;
}
void EventRecorder::deleteTemporarySave() {
if (_temporarySlot == -1) return;
const Plugin *plugin = PluginMan.findEnginePlugin(ConfMan.get("engineid"));
const Common::String target = ConfMan.getActiveDomainName();
plugin->get<MetaEngine>().removeSaveState(target.c_str(), _temporarySlot);
_temporarySlot = -1;
}
} // End of namespace GUI
#endif // ENABLE_EVENTRECORDER

259
gui/EventRecorder.h Normal file
View File

@@ -0,0 +1,259 @@
/* 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/>.
*
*/
#ifndef GUI_EVENTRECORDER_H
#define GUI_EVENTRECORDER_H
#include "common/system.h"
#include "common/events.h"
#include "common/savefile.h"
#include "common/singleton.h"
#include "engines/advancedDetector.h"
#ifdef ENABLE_EVENTRECORDER
#include "common/mutex.h"
#include "common/array.h"
#include "common/memstream.h"
#include "backends/mixer/mixer.h"
#include "common/hashmap.h"
#include "common/hash-str.h"
#include "backends/timer/sdl/sdl-timer.h"
#include "common/config-manager.h"
#include "common/recorderfile.h"
#include "backends/saves/recorder/recorder-saves.h"
#include "backends/mixer/null/null-mixer.h"
#include "backends/saves/default/default-saves.h"
#define g_eventRec (GUI::EventRecorder::instance())
namespace GUI {
class OnScreenDialog;
}
namespace GUI {
class RandomSource;
class SeekableReadStream;
class WriteStream;
/**
* Our generic event recorder.
*
* TODO: Add more documentation.
*/
class EventRecorder : private Common::EventSource, public Common::Singleton<EventRecorder>, private Common::EventObserver {
friend class Common::Singleton<SingletonBaseType>;
EventRecorder();
~EventRecorder() override;
public:
/** Specify operation mode of Event Recorder */
enum RecordMode {
kPassthrough = 0, /**< kPassthrough, do nothing */
kRecorderRecord = 1, /**< kRecorderRecord, do the recording */
kRecorderPlayback = 2, /**< kRecorderPlayback, playback existing recording */
kRecorderPlaybackPause = 3, /**< kRecorderPlaybackPause, internal state when user pauses the playback */
kRecorderUpdate = 4 /**< kRecorderUpdate, playback existing recording and update all hashes */
};
void init(const Common::String &recordFileName, RecordMode mode);
void deinit();
bool processDelayMillis();
void setFastPlayback(bool fastPlayback);
uint32 getRandomSeed(const Common::String &name);
void processTimeAndDate(TimeDate &td, bool skipRecord);
void processMillis(uint32 &millis, bool skipRecord);
void processScreenUpdate();
void processGameDescription(const ADGameDescription *desc);
bool processAutosave();
Common::SeekableReadStream *processSaveStream(const Common::String & fileName);
/** Hooks for intercepting into GUI processing, so required events could be shoot
* or filtered out */
void preDrawOverlayGui();
void postDrawOverlayGui();
/** Set recording author
*
* @see getAuthor
*/
void setAuthor(const Common::String &author) {
_author = author;
}
/** Set recording notes
*
* @see getNotes
*/
void setNotes(const Common::String &desc){
_desc = desc;
}
/** Set descriptive name of the recording
*
* @see getName
*/
void setName(const Common::String &name) {
_name = name;
}
/** Get recording author
*
* @see getAuthor
*/
const Common::String getAuthor() {
return _author;
}
/** Get recording notes
*
* @see setNotes
*/
const Common::String getNotes() {
return _desc;
}
/** Get recording name
*
* @see setName
*/
const Common::String getName() {
return _name;
}
void setRedraw(bool redraw) {
_needRedraw = redraw;
}
void registerMixerManager(MixerManager *mixerManager);
void registerTimerManager(DefaultTimerManager *timerManager);
MixerManager *getMixerManager();
DefaultTimerManager *getTimerManager();
void deleteRecord(const Common::String& fileName);
bool checkForContinueGame();
void acquireRecording() {
assert(_acquireCount >= 0);
if (_acquireCount == 0) {
_savedState = _initialized;
_initialized = false;
}
_acquireCount += 1;
}
void releaseRecording() {
assert(_acquireCount > 0);
_acquireCount -= 1;
if (_acquireCount == 0)
_initialized = _savedState;
}
RecordMode getRecordMode() const {
return _recordMode;
}
Common::StringArray listSaveFiles(const Common::String &pattern);
Common::String generateRecordFileName(const Common::String &target);
Common::SaveFileManager *getSaveManager(Common::SaveFileManager *realSaveManager);
SDL_Surface *getSurface(int width, int height);
void RegisterEventSource();
/** Retrieve game screenshot and compute its checksum for comparison */
bool grabScreenAndComputeMD5(Graphics::Surface &screen, uint8 md5[16]);
void updateSubsystems();
bool switchMode();
void switchFastMode();
private:
bool pollEvent(Common::Event &ev) override;
bool notifyEvent(const Common::Event &event) override;
bool _initialized;
volatile uint32 _fakeTimer;
TimeDate _lastTimeDate;
bool _savedState;
int _acquireCount;
bool _needcontinueGame;
int _temporarySlot;
Common::String _author;
Common::String _desc;
Common::String _name;
Common::SaveFileManager *_realSaveManager;
MixerManager *_realMixerManager;
DefaultTimerManager *_timerManager;
RecorderSaveFileManager _fakeSaveManager;
NullMixerManager *_fakeMixerManager;
GUI::OnScreenDialog *_controlPanel;
Common::RecorderEvent _nextEvent;
void setFileHeader();
void setGameMd5(const ADGameDescription *gameDesc);
void getConfig();
void getConfigFromDomain(const Common::ConfigManager::Domain *domain);
void removeDifferentEntriesInDomain(Common::ConfigManager::Domain *domain);
void applyPlaybackSettings();
void switchMixer();
void switchTimerManagers();
void togglePause();
void takeScreenshot();
bool openRecordFile(const Common::String &fileName);
bool checkGameHash(const ADGameDescription *desc);
void checkForKeyCode(const Common::Event &event);
/**
* @return false because we don't want to remap the given event again. This already happened on
* recording the event. We record the custom events already, not the raw backend events.
*/
bool allowMapping() const override { return false; }
volatile uint32 _lastMillis;
uint32 _lastScreenshotTime;
uint32 _screenshotPeriod;
Common::PlaybackFile *_playbackFile;
Common::PlaybackFile *_recordFile;
void saveScreenShot();
void checkRecordedMD5();
void deleteTemporarySave();
void updateFakeTimer(uint32 millis);
volatile RecordMode _recordMode;
Common::String _recordFileName;
bool _fastPlayback;
bool _needRedraw;
bool _processingMillis;
};
} // End of namespace GUI
#endif // ENABLE_EVENTRECORDER
#endif

87
gui/MetadataParser.cpp Normal file
View File

@@ -0,0 +1,87 @@
/* 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 "gui/MetadataParser.h"
#include "common/system.h"
#include "common/tokenizer.h"
namespace GUI {
MetadataParser::MetadataParser() : XMLParser() {
}
MetadataParser::~MetadataParser() {
}
void MetadataParser::cleanup() {
}
bool MetadataParser::parserCallback_games(ParserNode *node) {
return true;
}
bool MetadataParser::parserCallback_game(ParserNode *node) {
return true;
}
bool MetadataParser::parserCallback_engines(ParserNode *node) {
return true;
}
bool MetadataParser::parserCallback_engine(ParserNode *node) {
return true;
}
bool MetadataParser::parserCallback_series(ParserNode *node) {
return true;
}
bool MetadataParser::parserCallback_serie(ParserNode *node) {
return true;
}
bool MetadataParser::parserCallback_companies(ParserNode *node) {
return true;
}
bool MetadataParser::parserCallback_company(ParserNode *node) {
return true;
}
bool MetadataParser::closedKeyCallback(ParserNode *node) {
if (node->name == "game")
_gameInfo[Common::String::format("%s:%s",
node->values["engine_id"].c_str(), node->values["id"].c_str())] = MetadataGame(
node->values["id"], node->values["name"], node->values["engine_id"], node->values["company_id"],
node->values["moby_id"], node->values["datafiles"], node->values["series_id"], node->values["zoom_id"], node->values["year"]);
if (node->name == "engine")
_engineInfo[node->values["id"]] = MetadataEngine(node->values["id"], node->values["name"], node->values["alt_name"],
true);
if (node->name == "serie")
_seriesInfo[node->values["id"]] = MetadataSeries(node->values["id"], node->values["name"]);
if (node->name == "company")
_companyInfo[node->values["id"]] = MetadataCompany(node->values["id"], node->values["name"], node->values["alt_name"]);
return true;
}
} // End of namespace GUI

149
gui/MetadataParser.h Normal file
View File

@@ -0,0 +1,149 @@
/* 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/>.
*
*/
#ifndef GUI_METADATA_PARSER_H
#define GUI_METADATA_PARSER_H
#include "common/formats/xmlparser.h"
namespace GUI {
struct MetadataGame {
Common::String id;
Common::String name;
Common::String engine_id;
Common::String company_id;
Common::String moby_id;
Common::String zoom_id;
Common::String year;
Common::String datafiles;
Common::String series_id;
MetadataGame() {}
MetadataGame(const Common::String &i, const Common::String &n, const Common::String &eid, const Common::String &cid,
const Common::String &mid, const Common::String &df, const Common::String &sid, const Common::String &zid, const Common::String &yr)
: id(i), name(n), engine_id(eid), company_id(cid), year(yr), moby_id(mid), datafiles(df), zoom_id(zid), series_id(sid) {}
};
struct MetadataEngine {
Common::String id;
Common::String name;
Common::String alt_name;
bool enabled;
MetadataEngine() : enabled(false) {}
MetadataEngine(const Common::String &i, const Common::String &n, const Common::String &altn, bool e)
: id(i), name(n), alt_name(altn), enabled(e) {}
};
struct MetadataSeries {
Common::String id;
Common::String name;
MetadataSeries() {}
MetadataSeries(const Common::String &i, const Common::String &n) : id(i), name(n) {}
};
struct MetadataCompany {
Common::String id;
Common::String name;
Common::String alt_name;
MetadataCompany() {}
MetadataCompany(const Common::String &i, const Common::String &n, const Common::String &altn)
: id(i), name(n), alt_name(altn) {}
};
class MetadataParser : public Common::XMLParser {
public:
MetadataParser();
~MetadataParser() override;
Common::HashMap<Common::String, MetadataGame, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> _gameInfo;
Common::HashMap<Common::String, MetadataEngine, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> _engineInfo;
Common::HashMap<Common::String, MetadataSeries, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> _seriesInfo;
Common::HashMap<Common::String, MetadataCompany, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> _companyInfo;
protected:
CUSTOM_XML_PARSER(MetadataParser) {
XML_KEY(games)
XML_KEY(game)
XML_PROP(id, true)
XML_PROP(name, true)
XML_PROP(engine_id, true)
XML_PROP(company_id, true)
XML_PROP(moby_id, true)
XML_PROP(year, false)
XML_PROP(datafiles, true)
XML_PROP(wikipedia_page, true)
XML_PROP(series_id, true)
XML_PROP(steam_id, false)
XML_PROP(gog_id, false)
XML_PROP(zoom_id, false)
XML_PROP(additional_stores, false)
KEY_END() // game end
KEY_END() // games end
XML_KEY(engines)
XML_KEY(engine)
XML_PROP(id, true)
XML_PROP(name, true)
XML_PROP(alt_name, true)
XML_PROP(enabled, true)
KEY_END() // engine end
KEY_END() // engines end
XML_KEY(series)
XML_KEY(serie)
XML_PROP(id, true)
XML_PROP(name, true)
KEY_END() // serie end
KEY_END() // series end
XML_KEY(companies)
XML_KEY(company)
XML_PROP(id, true)
XML_PROP(name, true)
XML_PROP(alt_name, true)
KEY_END() // company end
KEY_END() // companies end
} PARSER_END()
/** Render info callbacks */
bool parserCallback_games(ParserNode *node);
bool parserCallback_game(ParserNode *node);
bool parserCallback_engines(ParserNode *node);
bool parserCallback_engine(ParserNode *node);
bool parserCallback_series(ParserNode *node);
bool parserCallback_serie(ParserNode *node);
bool parserCallback_companies(ParserNode *node);
bool parserCallback_company(ParserNode *node);
bool closedKeyCallback(ParserNode *node) override;
void cleanup() override;
};
} // End of namespace GUI
#endif

2189
gui/ThemeEngine.cpp Normal file

File diff suppressed because it is too large Load Diff

837
gui/ThemeEngine.h Normal file
View File

@@ -0,0 +1,837 @@
/* 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/>.
*
*/
#ifndef GUI_THEME_ENGINE_H
#define GUI_THEME_ENGINE_H
#include "common/scummsys.h"
#include "common/fs.h"
#include "common/hash-str.h"
#include "common/hashmap.h"
#include "common/language.h"
#include "common/list.h"
#include "common/str.h"
#include "common/rect.h"
#include "graphics/managed_surface.h"
#include "graphics/font.h"
#include "graphics/pixelformat.h"
#define SCUMMVM_THEME_VERSION_STR "SCUMMVM_STX0.9.20"
class OSystem;
namespace Graphics {
struct DrawStep;
class VectorRenderer;
}
namespace GUI {
struct WidgetDrawData;
struct TextDrawData;
class Dialog;
class GuiObject;
class ThemeEval;
class ThemeParser;
/**
* DrawData sets enumeration.
* Each DD set corresponds to the actual looks
* of a widget in a given state.
*/
enum DrawData {
kDDMainDialogBackground,
kDDSpecialColorBackground,
kDDPlainColorBackground,
kDDTooltipBackground,
kDDDefaultBackground,
kDDTextSelectionBackground,
kDDTextSelectionFocusBackground,
kDDThumbnailBackground,
kDDGridItemIdle,
kDDGridItemHover,
kDDWidgetBackgroundDefault,
kDDWidgetBackgroundSmall,
kDDWidgetBackgroundEditText,
kDDWidgetBackgroundSlider,
kDDButtonIdle,
kDDButtonHover,
kDDButtonDisabled,
kDDButtonPressed,
kDDDropDownButtonIdle,
kDDDropDownButtonHoverLeft,
kDDDropDownButtonHoverRight,
kDDDropDownButtonDisabled,
kDDDropDownButtonPressedLeft,
kDDDropDownButtonPressedRight,
kDDDropDownButtonIdleRTL,
kDDDropDownButtonHoverLeftRTL,
kDDDropDownButtonHoverRightRTL,
kDDDropDownButtonDisabledRTL,
kDDDropDownButtonPressedLeftRTL,
kDDDropDownButtonPressedRightRTL,
kDDSliderFull,
kDDSliderHover,
kDDSliderDisabled,
kDDCheckboxDefault,
kDDCheckboxDisabled,
kDDCheckboxSelected,
kDDCheckboxDisabledSelected,
kDDRadiobuttonDefault,
kDDRadiobuttonDisabled,
kDDRadiobuttonSelected,
kDDTabActive,
kDDTabInactive,
kDDTabBackground,
kDDScrollbarBase,
kDDScrollbarButtonIdle,
kDDScrollbarButtonHover,
kDDScrollbarHandleIdle,
kDDScrollbarHandleHover,
kDDPopUpIdle,
kDDPopUpHover,
kDDPopUpDisabled,
kDDPopUpIdleRTL,
kDDPopUpHoverRTL,
kDDPopUpDisabledRTL,
kDDCaret,
kDDSeparator,
kDrawDataMAX,
kDDNone = -1
};
/**
* Dialog layers.
* The currently active dialog has two layers, background and foreground.
* The background layer is drawn to the backbuffer. The foreground layer
* is drawn to the screen. This allows draw calls to restore the background
* layer before redrawing a widget.
*/
enum DrawLayer {
kDrawLayerBackground,
kDrawLayerForeground
};
// FIXME: TextData is really a bad name, not conveying what this enum is about.
enum TextData {
kTextDataNone = -1,
kTextDataDefault = 0,
kTextDataButton,
kTextDataNormalFont,
kTextDataTooltip,
kTextDataConsole,
kTextDataExtraLang,
kTextDataMAX
};
enum TextColor {
kTextColorNormal = 0,
kTextColorNormalInverted,
kTextColorNormalHover,
kTextColorNormalDisabled,
kTextColorAlternative,
kTextColorAlternativeInverted,
kTextColorAlternativeHover,
kTextColorAlternativeDisabled,
kTextColorOverride,
kTextColorOverrideInverted,
kTextColorOverrideHover,
kTextColorOverrideDisabled,
kTextColorButton,
kTextColorButtonHover,
kTextColorButtonDisabled,
kTextColorMAX
};
struct TextColorData {
int r, g, b;
};
class LangExtraFont {
public:
LangExtraFont(TextData textId, Common::Array<Common::Language> &lngs, const Common::String &filename, const Common::String &scalableFile, int ps) : _langs(lngs) {
storeFileNames(textId, filename, scalableFile, ps);
}
void storeFileNames(TextData textId, const Common::String &filename, const Common::String &scalableFile, int ps) {
assert(textId < kTextDataMAX);
_fontFilesStd[textId] = filename;
_fontFilesScalable[textId] = scalableFile;
_fontSize[textId] = ps;
}
bool operator==(Common::Language l) const {
return (Common::find(_langs.begin(), _langs.end(), l) != _langs.end());
}
Common::String file(TextData textId) const { return _fontFilesStd[textId]; }
Common::String sclFile(TextData textId) const { return _fontFilesScalable[textId]; }
int fntSize(TextData textId) const { return _fontSize[textId]; }
private:
Common::Array<Common::Language> _langs;
Common::String _fontFilesStd[kTextDataMAX];
Common::String _fontFilesScalable[kTextDataMAX];
int _fontSize[kTextDataMAX];
};
class ThemeEngine {
protected:
typedef Common::HashMap<Common::String, Graphics::ManagedSurface *> ImagesMap;
friend class GUI::Dialog;
friend class GUI::GuiObject;
public:
/// Vertical alignment of the text.
enum TextAlignVertical {
kTextAlignVInvalid,
kTextAlignVBottom,
kTextAlignVCenter,
kTextAlignVTop
};
/// Widget background type
enum WidgetBackground {
kWidgetBackgroundNo, ///< No background at all
kWidgetBackgroundPlain, ///< Simple background, this may not include borders
kWidgetBackgroundBorder, ///< Same as kWidgetBackgroundPlain just with a border
kWidgetBackgroundBorderSmall, ///< Same as kWidgetBackgroundPlain just with a small border
kWidgetBackgroundEditText, ///< Background used for edit text fields
kWidgetBackgroundSlider, ///< Background used for sliders
kThumbnailBackground, ///< Background used for thumbnails
kGridItemBackground, ///< Default Background used for grid items
kGridItemHighlight ///< Highlight Background used for grid items
};
/// Dialog background type
enum DialogBackground {
kDialogBackgroundMain,
kDialogBackgroundSpecial,
kDialogBackgroundPlain,
kDialogBackgroundTooltip,
kDialogBackgroundDefault,
kDialogBackgroundNone
};
/// State of the widget to be drawn
enum State {
kStateDisabled, ///< Indicates that the widget is disabled, that does NOT include that it is invisible
kStateEnabled, ///< Indicates that the widget is enabled
kStateHighlight, ///< Indicates that the widget is highlighted by the user
kStatePressed ///< Indicates that the widget is pressed, currently works for buttons
};
typedef State WidgetStateInfo;
/// Text inversion state of the text to be draw
enum TextInversionState {
kTextInversionNone, ///< Indicates that the text should not be drawn inverted
kTextInversion, ///< Indicates that the text should be drawn inverted, but not focused
kTextInversionFocus ///< Indicates that the text should be drawn inverted, and focused
};
enum ScrollbarState {
kScrollbarStateNo,
kScrollbarStateUp,
kScrollbarStateDown,
kScrollbarStateSlider,
kScrollbarStateSinglePage
};
/// Font style selector
enum FontStyle {
kFontStyleBold = 0, ///< A bold font. This is also the default font.
kFontStyleNormal = 1, ///< A normal font.
kFontStyleItalic = 2, ///< Italic styled font.
kFontStyleFixedNormal = 3, ///< Fixed size font.
kFontStyleFixedBold = 4, ///< Fixed size bold font.
kFontStyleFixedItalic = 5, ///< Fixed size italic font.
kFontStyleTooltip = 6, ///< Tiny console font
kFontStyleConsole = 7, ///< Debug console font
kFontStyleLangExtra = 8, ///< Language specific font for ingame dialogs (e. g. the SCUMM pause/restart dialogs)
kFontStyleMax
};
/// Font color selector
enum FontColor {
kFontColorFormatting = -1, ///< Use color from formatting
kFontColorNormal = 0, ///< The default color of the theme
kFontColorAlternate = 1, ///< Alternative font color
kFontColorOverride = 2, ///< Color of overwritten text
kFontColorMax
};
/// Function used to process areas other than the current dialog
enum ShadingStyle {
kShadingNone, ///< No special post processing
kShadingDim, ///< Dimming unused areas
kShadingLuminance ///< Converting colors to luminance for unused areas
};
/// AlphaBitmap scale mode selector
enum AutoScaleMode {
kAutoScaleNone = 0, ///< Use image dimensions
kAutoScaleStretch = 1, ///< Stretch image to full widget size
kAutoScaleFit = 2, ///< Scale image to widget size but keep aspect ratio
kAutoScaleNinePatch = 3 ///< 9-patch image
};
// Special image ids for images used in the GUI
static const char *const kImageLogo; ///< ScummVM logo used in the launcher
static const char *const kImageLogoSmall; ///< ScummVM logo used in the GMM
static const char *const kImageSearch; ///< Search tool image used in the launcher
static const char *const kImageGroup; ///< Select Group image used in the launcher
static const char *const kImageEraser; ///< Clear input image used in the launcher
static const char *const kImageDelButton; ///< Delete characters in the predictive dialog
static const char *const kImageList; ///< List image used in save/load chooser selection
static const char *const kImageGrid; ///< Grid image used in save/load chooser selection
static const char *const kImageStopButton; ///< Stop recording button in recorder onscreen dialog
static const char *const kImageEditButton; ///< Edit recording metadata in recorder onscreen dialog
static const char *const kImageSwitchModeButton; ///< Switch mode button in recorder onscreen dialog
static const char *const kImageFastReplayButton; ///< Fast playback mode button in recorder onscreen dialog
static const char *const kImageStopSmallButton; ///< Stop recording button in recorder onscreen dialog (for 320xY)
static const char *const kImageEditSmallButton; ///< Edit recording metadata in recorder onscreen dialog (for 320xY)
static const char *const kImageSwitchModeSmallButton; ///< Switch mode button in recorder onscreen dialog (for 320xY)
static const char *const kImageFastReplaySmallButton; ///< Fast playback mode button in recorder onscreen dialog (for 320xY)
/**
* Graphics mode enumeration.
* Each item represents a set of BPP and Renderer modes for a given
* surface.
*/
enum GraphicsMode {
kGfxDisabled = 0, ///< No GFX
kGfxStandard, ///< Standard (aliased) renderer.
kGfxAntialias ///< Optimized AA renderer.
};
/** Constant value to expand dirty rectangles, to make sure they are fully copied */
static const int kDirtyRectangleThreshold = 1;
struct Renderer {
const char *name;
const char *shortname;
const char *cfg;
GraphicsMode mode;
};
static const Renderer _rendererModes[];
static const uint _rendererModesSize;
static const GraphicsMode _defaultRendererMode;
static GraphicsMode findMode(const Common::String &cfg);
static const char *findModeConfigName(GraphicsMode mode);
/** Default constructor */
ThemeEngine(Common::String id, GraphicsMode mode);
/** Default destructor */
~ThemeEngine();
void setBaseResolution(int w, int h, float s);
bool init();
void clearAll();
void refresh();
void enable();
void showCursor();
void hideCursor();
void disable();
/**
* Query the set up pixel format.
*/
const Graphics::PixelFormat getPixelFormat() const { return _overlayFormat; }
/**
* Draw full screen shading with the supplied style
*
* This is used to dim the inactive dialogs so the active one stands out.
*/
void applyScreenShading(ShadingStyle shading);
/**
* Sets the active drawing surface to the back buffer.
*
* All drawing from this point on will be done on that surface.
* The back buffer surface needs to be copied to the screen surface
* in order to become visible.
*/
void drawToBackbuffer();
/**
* Sets the active drawing surface to the screen.
*
* All drawing from this point on will be done on that surface.
*/
void drawToScreen();
/**
* The updateScreen() method is called every frame.
* It copies dirty rectangles in the Screen surface to the overlay.
*/
void updateScreen();
/**
* Copy the entire backbuffer surface to the screen surface
*/
void copyBackBufferToScreen();
/** @name FONT MANAGEMENT METHODS */
//@{
TextData fontStyleToData(FontStyle font) const {
if (font == kFontStyleNormal)
return kTextDataNormalFont;
if (font == kFontStyleTooltip)
return kTextDataTooltip;
if (font == kFontStyleConsole)
return kTextDataConsole;
if (font == kFontStyleLangExtra)
return kTextDataExtraLang;
return kTextDataDefault;
}
const Graphics::Font *getFont(FontStyle font = kFontStyleBold) const;
int getFontHeight(FontStyle font = kFontStyleBold) const;
int getStringWidth(const Common::U32String &str, FontStyle font = kFontStyleBold) const;
int getCharWidth(uint32 c, FontStyle font = kFontStyleBold) const;
int getKerningOffset(uint32 left, uint32 right, FontStyle font = kFontStyleBold) const;
//@}
/**
* Set the clipping rect to be used by the widget drawing methods defined below.
*
* Widgets are not drawn outside of the clipping rect. Widgets that overlap the
* clipping rect are drawn partially.
*
* @param newRect The new clipping rect
* @return The previous clipping rect
*/
Common::Rect swapClipRect(const Common::Rect &newRect);
const Common::Rect getClipRect();
/**
* Set the clipping rect to allow rendering on the whole surface.
*/
void disableClipRect();
/** @name WIDGET DRAWING METHODS */
//@{
void drawWidgetBackground(const Common::Rect &r, WidgetBackground background);
void drawButton(const Common::Rect &r, const Common::U32String &str, WidgetStateInfo state = kStateEnabled,
uint16 hints = 0);
void drawDropDownButton(const Common::Rect &r, uint32 dropdownWidth, const Common::U32String &str,
WidgetStateInfo buttonState, bool inButton, bool inDropdown, bool rtl = false);
void drawManagedSurface(const Common::Point &p, const Graphics::ManagedSurface &surface, Graphics::AlphaType alphaType);
void drawSlider(const Common::Rect &r, int width, WidgetStateInfo state = kStateEnabled, bool rtl = false);
void drawCheckbox(const Common::Rect &r, int spacing, const Common::U32String &str, bool checked,
WidgetStateInfo state = kStateEnabled, bool override = false, bool rtl = false);
void drawRadiobutton(const Common::Rect &r, int spacing, const Common::U32String &str, bool checked,
WidgetStateInfo state = kStateEnabled, bool rtl = false);
void drawTab(const Common::Rect &r, int tabHeight, const Common::Array<int> &tabWidths,
const Common::Array<Common::U32String> &tabs, int active, bool rtl,
ThemeEngine::TextAlignVertical alignV);
void drawScrollbar(const Common::Rect &r, int sliderY, int sliderHeight, ScrollbarState scrollState);
void drawPopUpWidget(const Common::Rect &r, const Common::U32String &sel, int deltax,
WidgetStateInfo state = kStateEnabled, bool rtl = false);
void drawCaret(const Common::Rect &r, bool erase);
void drawLineSeparator(const Common::Rect &r);
void drawDialogBackground(const Common::Rect &r, DialogBackground type);
void drawText(const Common::Rect &r, const Common::U32String &str, WidgetStateInfo state = kStateEnabled,
Graphics::TextAlign align = Graphics::kTextAlignCenter,
TextInversionState inverted = kTextInversionNone, int deltax = 0, bool useEllipsis = true,
FontStyle font = kFontStyleBold, FontColor color = kFontColorNormal, bool restore = true,
const Common::Rect &drawableTextArea = Common::Rect(0, 0, 0, 0));
void drawChar(const Common::Rect &r, byte ch, const Graphics::Font *font, FontColor color = kFontColorNormal, TextInversionState inverted = ThemeEngine::kTextInversionNone);
void drawFoldIndicator(const Common::Rect &r, bool expanded);
//@}
/**
* Actual implementation of a dirty rect handling.
* Dirty rectangles are queued on a list, merged and optimized
* when possible and are later used for the actual drawing.
*
* @param r Area of the dirty rect.
*/
void addDirtyRect(Common::Rect r);
/**
* Returns the DrawData enumeration value that represents the given string
* in the DrawDataDefaults enumeration.
* It's slow, but called sparsely.
*
* @returns The drawdata enum value, or -1 if not found.
* @param name The representing name, as found on Theme Description XML files.
* @see kDrawDataDefaults[]
*/
DrawData parseDrawDataId(const Common::String &name) const;
TextData getTextData(DrawData ddId) const;
TextColor getTextColor(DrawData ddId) const;
TextColorData *getTextColorData(TextColor color) const;
/**
* Interface for ThemeParser class: Parsed DrawSteps are added via this function.
* There is no return type because DrawSteps can always be added, unless something
* goes horribly wrong.
* The specified step will be added to the Steps list of the given DrawData id.
*
* @param drawDataId The representing DrawData name, as found on Theme Description XML files.
* @param step The actual DrawStep struct to be added.
*/
void addDrawStep(const Common::String &drawDataId, const Graphics::DrawStep &step);
/**
* Interface for the ThemeParser class: Parsed DrawData sets are added via this function.
* The goal of the function is to initialize each DrawData set before their DrawSteps can
* be added, hence this must be called for each DD set before addDrawStep() can be called
* for that given set.
*
* @param data The representing DrawData name, as found on Theme Description XML files.
* @param cached Whether this DD set will be cached beforehand.
*/
bool addDrawData(const Common::String &data, bool cached);
/**
* Interface for the ThemeParser class: Loads a font to use on the GUI from the given
* filename.
*
* @param textId Identifier name for the font.
* @param language Wildcard for the language(s) to use.
* @param file Filename of the non-scalable font version.
* @param scalableFile Filename of the scalable version. (Optional)
* @param pointsize Point size for the scalable font. (Optional)
*/
bool addFont(TextData textId, const Common::String &language, const Common::String &file, const Common::String &scalableFile, const int pointsize);
/**
* Store language specific font names for ingame GUI dialogs which might require
* a different language than the current GUI setting
*
* @param textId, language, file, scalableFile, pointsize All exactly the same as with addFont()
*/
void storeFontNames(TextData textId, const Common::String &language, const Common::String &file, const Common::String &scalableFile, const int pointsize);
/**
* Load language specific font for ingame use
* @param style font style associated with the font file
* @param lang language associated with the font file
* @return
*/
bool loadExtraFont(FontStyle style, Common::Language lang);
/**
* Interface for the ThemeParser class: adds a text color value.
*
* @param colorId Identifier for the color type.
* @param r Red color component
* @param g Green color component
* @param b Blue color component
*/
bool addTextColor(TextColor colorId, int r, int g, int b);
/**
* Interface for the ThemeParser class: Loads a bitmap file to use on the GUI.
* The filename is also used as its identifier.
*
* @param filename Name of the bitmap file.
* @param filename Name of the scalable (SVG) file, could be empty
* @param width, height Default image dimensions
*/
bool addBitmap(const Common::String &filename, const Common::String &scalablefile, int widht, int height);
/**
* Adds a new TextStep from the ThemeParser. This will be deprecated/removed once the
* new Font API is in place. FIXME: Is that so ???
*/
bool addTextData(const Common::String &drawDataId, TextData textId, TextColor id, Graphics::TextAlign alignH, TextAlignVertical alignV);
protected:
/**
* Returns if the Theme is ready to draw stuff on screen.
* Must be called instead of just checking _initOk, because
* this checks if the renderer is initialized AND if the theme
* is loaded.
*/
bool ready() const {
return _initOk && _themeOk;
}
/** Load the them from the file with the specified name. */
void loadTheme(const Common::String &themeid);
/**
* Changes the active graphics mode of the GUI; may be used to either
* initialize the GUI or to change the mode while the GUI is already running.
*/
void setGraphicsMode(GraphicsMode mode);
public:
inline ThemeEval *getEvaluator() { return _themeEval; }
inline Graphics::VectorRenderer *renderer() { return _vectorRenderer; }
inline bool supportsImages() const { return true; }
inline bool ownCursor() const { return _useCursor; }
Graphics::ManagedSurface *getImageSurface(const Common::String &name) const {
return _bitmaps.contains(name) ? _bitmaps[name] : 0;
}
/**
* Interface for the Theme Parser: Creates a new cursor by loading the given
* bitmap and sets it as the active cursor.
*
* @param filename File name of the bitmap to load.
* @param hotspotX X Coordinate of the bitmap which does the cursor click.
* @param hotspotY Y Coordinate of the bitmap which does the cursor click.
*/
bool createCursor(const Common::String &filename, int hotspotX, int hotspotY);
/**
* Wrapper for restoring data from the Back Buffer to the screen.
* The actual processing is done in the VectorRenderer.
*
* @param r Area to restore.
*/
void restoreBackground(Common::Rect r);
const Common::String &getThemeName() const { return _themeName; }
const Common::String &getThemeId() const { return _themeId; }
int getGraphicsMode() const { return _graphicsMode; }
protected:
/**
* Loads the given theme into the ThemeEngine.
*
* @param themeId Theme identifier.
* @returns true if the theme was successfully loaded.
*/
bool loadThemeXML(const Common::String &themeId);
/**
* Loads the default theme file (the embedded XML file found
* in ThemeDefaultXML.cpp).
* Called only when no other themes are available.
*/
bool loadDefaultXML();
/**
* Unloads the currently loaded theme so another one can
* be loaded.
*/
void unloadTheme();
/**
* Unload the language specific font loaded via loadExtraFont()
*/
void unloadExtraFont();
const Graphics::Font *loadScalableFont(const Common::String &filename, const int pointsize, Common::String &name);
const Graphics::Font *loadFont(const Common::String &filename, Common::String &name);
Common::String genCacheFilename(const Common::String &filename) const;
const Graphics::Font *loadFont(const Common::String &filename, const Common::String &scalableFilename, const int pointsize, const bool makeLocalizedFont);
/**
* Dirty Screen handling function.
* Draws all the dirty rectangles in the list to the overlay.
*/
void updateDirtyScreen();
/**
* Draws a GUI element according to a DrawData descriptor.
*
* Only calls with a DrawData layer attribute matching the active layer
* are actually drawn to the active surface.
*
* These functions are called from all the Widget drawing methods.
*/
void drawDD(DrawData type, const Common::Rect &r, uint32 dynamic = 0, bool forceRestore = false);
void drawDDText(TextData type, TextColor color, const Common::Rect &r, const Common::U32String &text, bool restoreBg,
bool elipsis, Graphics::TextAlign alignH = Graphics::kTextAlignLeft,
TextAlignVertical alignV = kTextAlignVTop, int deltax = 0,
const Common::Rect &drawableTextArea = Common::Rect(0, 0, 0, 0));
/**
* DEBUG: Draws a white square and writes some text next to it.
*/
void debugWidgetPosition(const char *name, const Common::Rect &r);
public:
struct ThemeDescriptor {
Common::String name;
Common::String id;
Common::Path filename;
};
/**
* Lists all theme files useable.
*/
static void listUsableThemes(Common::List<ThemeDescriptor> &list);
private:
static bool themeConfigUsable(const Common::FSNode &node, Common::String &themeName);
static bool themeConfigUsable(const Common::ArchiveMember &member, Common::String &themeName);
static bool themeConfigParseHeader(Common::String header, Common::String &themeName);
static Common::Path getThemeFile(const Common::String &id);
static Common::String getThemeId(const Common::Path &filename);
static void listUsableThemes(const Common::FSNode &node, Common::List<ThemeDescriptor> &list, int depth = -1);
static void listUsableThemes(Common::Archive &archive, Common::List<ThemeDescriptor> &list);
protected:
OSystem *_system; /** Global system object. */
/** Vector Renderer object, does the actual drawing on screen */
Graphics::VectorRenderer *_vectorRenderer;
/** XML Parser, does the Theme parsing instead of the default parser */
GUI::ThemeParser *_parser;
/** Theme getEvaluator (changed from GUI::Eval to add functionality) */
GUI::ThemeEval *_themeEval;
/** Main screen surface. This is blitted straight into the overlay. */
Graphics::ManagedSurface _screen;
/** Backbuffer surface. Stores previous states of the screen to blit back */
Graphics::ManagedSurface _backBuffer;
/**
* Filter the submitted DrawData descriptors according to their layer attribute
*
* This is used to selectively draw the background or foreground layer
* of the dialogs.
*/
DrawLayer _layerToDraw;
/** Bytes per pixel of the Active Drawing Surface (i.e. the screen) */
int _bytesPerPixel;
/** Current graphics mode */
GraphicsMode _graphicsMode;
int16 _baseWidth, _baseHeight;
float _scaleFactor;
bool _needScaleRefresh = false;
/** Font info. */
const Graphics::Font *_font;
/**
* Array of all the DrawData elements than can be drawn to the screen.
* Must be full so the renderer can work.
*/
WidgetDrawData *_widgets[kDrawDataMAX];
/** Array of all the text fonts that can be drawn. */
TextDrawData *_texts[kTextDataMAX];
/** Array of all font colors available. */
TextColorData *_textColors[kTextColorMAX];
/** Extra font file names for languages like Japanese, Korean or Chinese
* for use in ingame dialogs (like the SCUMM pause/restart dialogs)
*/
Common::Array<LangExtraFont> _langExtraFonts;
ImagesMap _bitmaps;
Graphics::PixelFormat _overlayFormat;
Graphics::PixelFormat _cursorFormat;
/** List of all the dirty screens that must be blitted to the overlay. */
Common::List<Common::Rect> _dirtyScreen;
bool _initOk; ///< Class and renderer properly initialized
bool _themeOk; ///< Theme data successfully loaded.
bool _enabled; ///< Whether the Theme is currently shown on the overlay
Common::String _themeName; ///< Name of the currently loaded theme
Common::String _themeId;
Common::Path _themeFile;
Common::Archive *_themeArchive;
Common::SearchSet _themeFiles;
bool _useCursor;
int _cursorHotspotX, _cursorHotspotY;
uint32 _cursorTransparent;
byte *_cursor;
uint _cursorWidth, _cursorHeight;
enum {
MAX_CURS_COLORS = 255
};
byte _cursorPal[3 * MAX_CURS_COLORS];
byte _cursorPalSize;
Common::Rect _clip;
};
} // End of namespace GUI.
#endif

213
gui/ThemeEval.cpp Normal file
View File

@@ -0,0 +1,213 @@
/* 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 "gui/ThemeEval.h"
#include "graphics/scaler.h"
#include "common/system.h"
#include "common/tokenizer.h"
namespace GUI {
ThemeEval::~ThemeEval() {
reset();
}
void ThemeEval::buildBuiltinVars() {
_builtin["kThumbnailWidth"] = kThumbnailWidth;
_builtin["kThumbnailHeight"] = kThumbnailHeight1;
_builtin["kThumbnailHeight2"] = kThumbnailHeight2;
}
void ThemeEval::reset() {
_vars.clear();
_curDialog.clear();
_curLayout.clear();
for (LayoutsMap::iterator i = _layouts.begin(); i != _layouts.end(); ++i)
delete i->_value;
_layouts.clear();
}
bool ThemeEval::getWidgetData(const Common::String &widget, int16 &x, int16 &y, int16 &w, int16 &h) {
bool useRTL;
return getWidgetData(widget, x, y, w, h, useRTL);
}
bool ThemeEval::getWidgetData(const Common::String &widget, int16 &x, int16 &y, int16 &w, int16 &h, bool &useRTL) {
Common::StringTokenizer tokenizer(widget, ".");
if (widget.hasPrefix("Dialog."))
tokenizer.nextToken();
Common::String dialogName = "Dialog." + tokenizer.nextToken();
Common::String widgetName = tokenizer.nextToken();
if (!_layouts.contains(dialogName))
return false;
return _layouts[dialogName]->getWidgetData(widgetName, x, y, w, h, useRTL);
}
Graphics::TextAlign ThemeEval::getWidgetTextHAlign(const Common::String &widget) {
Common::StringTokenizer tokenizer(widget, ".");
if (widget.hasPrefix("Dialog."))
tokenizer.nextToken();
Common::String dialogName = "Dialog." + tokenizer.nextToken();
Common::String widgetName = tokenizer.nextToken();
if (!_layouts.contains(dialogName))
return Graphics::kTextAlignInvalid;
return _layouts[dialogName]->getWidgetTextHAlign(widgetName);
}
ThemeEval &ThemeEval::addWidget(const Common::String &name, const Common::String &type, int w, int h, Graphics::TextAlign align, bool useRTL) {
int typeW = -1;
int typeH = -1;
Graphics::TextAlign typeAlign = Graphics::kTextAlignInvalid;
if (!type.empty()) {
typeW = getVar("Globals." + type + ".Width", -1);
typeH = getVar("Globals." + type + ".Height", -1);
typeAlign = (Graphics::TextAlign)getVar("Globals." + type + ".Align", Graphics::kTextAlignInvalid);
}
ThemeLayoutWidget *widget;
if (type == "TabWidget")
widget = new ThemeLayoutTabWidget(_curLayout.top(), name,
typeW == -1 ? w : typeW,
typeH == -1 ? h : typeH,
typeAlign == Graphics::kTextAlignInvalid ? align : typeAlign,
getVar("Globals.TabWidget.Tab.Height", 0));
else if (type == "ScrollContainerWidget")
widget = new ThemeLayoutScrollContainerWidget(_curLayout.top(), name,
typeW == -1 ? w : typeW,
typeH == -1 ? h : typeH,
typeAlign == Graphics::kTextAlignInvalid ? align : typeAlign,
getVar("Globals.Scrollbar.Width", 0));
else
widget = new ThemeLayoutWidget(_curLayout.top(), name,
typeW == -1 ? w : typeW,
typeH == -1 ? h : typeH,
typeAlign == Graphics::kTextAlignInvalid ? align : typeAlign,
useRTL);
_curLayout.top()->addChild(widget);
return *this;
}
ThemeEval &ThemeEval::addDialog(const Common::String &name, const Common::String &overlays, int16 width, int16 height, int inset) {
Common::String var = "Dialog." + name;
ThemeLayout *layout = new ThemeLayoutMain(name, overlays, width, height, inset);
if (_layouts.contains(var))
delete _layouts[var];
_layouts[var] = layout;
layout->setPadding(
getVar("Globals.Padding.Left", 0),
getVar("Globals.Padding.Right", 0),
getVar("Globals.Padding.Top", 0),
getVar("Globals.Padding.Bottom", 0)
);
_curLayout.push(layout);
_curDialog = name;
return *this;
}
ThemeEval &ThemeEval::addLayout(ThemeLayout::LayoutType type, int spacing, ThemeLayout::ItemAlign itemAlign) {
ThemeLayout *layout = nullptr;
if (spacing == -1)
spacing = getVar("Globals.Layout.Spacing");
layout = new ThemeLayoutStacked(_curLayout.top(), type, spacing, itemAlign);
assert(layout);
layout->setPadding(
getVar("Globals.Padding.Left", 0),
getVar("Globals.Padding.Right", 0),
getVar("Globals.Padding.Top", 0),
getVar("Globals.Padding.Bottom", 0)
);
_curLayout.top()->addChild(layout);
_curLayout.push(layout);
return *this;
}
ThemeEval &ThemeEval::addSpace(int size) {
ThemeLayout *space = new ThemeLayoutSpacing(_curLayout.top(), size);
_curLayout.top()->addChild(space);
return *this;
}
#define SCALEVALUE(val) (val > 0 ? val * _scaleFactor : val)
ThemeEval &ThemeEval::addPadding(int16 l, int16 r, int16 t, int16 b) {
_curLayout.top()->setPadding(SCALEVALUE(l), SCALEVALUE(r), SCALEVALUE(t), SCALEVALUE(b));
return *this;
}
bool ThemeEval::hasDialog(const Common::String &name) {
Common::StringTokenizer tokenizer(name, ".");
if (name.hasPrefix("Dialog."))
tokenizer.nextToken();
Common::String dialogName = "Dialog." + tokenizer.nextToken();
return _layouts.contains(dialogName);
}
void ThemeEval::reflowDialogLayout(const Common::String &name, Widget *widgetChain) {
if (!_layouts.contains("Dialog." + name)) {
warning("No layout found for dialog '%s'", name.c_str());
return;
}
_layouts["Dialog." + name]->reflowLayout(widgetChain);
}
ThemeEval &ThemeEval::addImportedLayout(const Common::String &name) {
ThemeLayout *importedLayout = _layouts[name];
assert(importedLayout);
_curLayout.top()->importLayout(importedLayout);
return *this;
}
} // End of namespace GUI

127
gui/ThemeEval.h Normal file
View File

@@ -0,0 +1,127 @@
/* 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/>.
*
*/
#ifndef GUI_THEME_EVAL_H
#define GUI_THEME_EVAL_H
#include "common/scummsys.h"
#include "common/hashmap.h"
#include "common/hash-str.h"
#include "common/stack.h"
#include "common/str.h"
#include "common/textconsole.h"
#include "graphics/font.h"
#include "gui/ThemeLayout.h"
namespace GUI {
class ThemeEval {
typedef Common::HashMap<Common::String, int> VariablesMap;
typedef Common::HashMap<Common::String, ThemeLayout *> LayoutsMap;
public:
ThemeEval() : _scaleFactor(1.0f) {
buildBuiltinVars();
}
~ThemeEval();
void buildBuiltinVars();
int getVar(const Common::String &s) {
if (_vars.contains(s))
return _vars[s];
if (_builtin.contains(s))
return _builtin[s];
error("CRITICAL: Missing variable: '%s'", s.c_str());
return -13375; //EVAL_UNDEF_VAR
}
int getVar(const Common::String &s, int def) {
if (_vars.contains(s))
return _vars[s];
if (_builtin.contains(s))
return _builtin[s];
return def;
}
void setScaleFactor(float s) { _scaleFactor = s; }
void setVar(const Common::String &name, int val) { _vars[name] = val; }
bool hasVar(const Common::String &name) { return _vars.contains(name) || _builtin.contains(name); }
ThemeEval &addDialog(const Common::String &name, const Common::String &overlays, int16 maxWidth = -1, int16 maxHeight = -1, int inset = 0);
ThemeEval &addLayout(ThemeLayout::LayoutType type, int spacing = -1, ThemeLayout::ItemAlign itemAlign = ThemeLayout::kItemAlignStart);
ThemeEval &addWidget(const Common::String &name, const Common::String &type, int w = -1, int h = -1, Graphics::TextAlign align = Graphics::kTextAlignStart, bool useRTL = true);
ThemeEval &addImportedLayout(const Common::String &name);
ThemeEval &addSpace(int size = -1);
ThemeEval &addPadding(int16 l, int16 r, int16 t, int16 b);
ThemeEval &closeLayout() { _curLayout.pop(); return *this; }
ThemeEval &closeDialog() { _curLayout.pop(); _curDialog.clear(); return *this; }
bool hasDialog(const Common::String &name);
void reflowDialogLayout(const Common::String &name, Widget *widgetChain);
bool getWidgetData(const Common::String &widget, int16 &x, int16 &y, int16 &w, int16 &h);
bool getWidgetData(const Common::String &widget, int16 &x, int16 &y, int16 &w, int16 &h, bool &useRTL);
Graphics::TextAlign getWidgetTextHAlign(const Common::String &widget);
#ifdef LAYOUT_DEBUG_DIALOG
void debugDraw(Graphics::ManagedSurface *screen, const Graphics::Font *font) {
if (_layouts.contains(LAYOUT_DEBUG_DIALOG)) {
_layouts[LAYOUT_DEBUG_DIALOG]->debugDraw(screen, font);
} else {
Common::String list;
for (auto l = _layouts.begin(); l != _layouts.end(); ++l)
list += " " + l->_key;
warning("debugDraw: Unknown layout %s\nList:%s", LAYOUT_DEBUG_DIALOG, list.c_str());
}
}
#endif
void reset();
private:
VariablesMap _vars;
VariablesMap _builtin;
LayoutsMap _layouts;
Common::Stack<ThemeLayout *> _curLayout;
Common::String _curDialog;
float _scaleFactor;
};
} // End of namespace GUI
#endif

506
gui/ThemeLayout.cpp Normal file
View File

@@ -0,0 +1,506 @@
/* 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/util.h"
#include "common/system.h"
#include "gui/gui-manager.h"
#include "gui/widget.h"
#include "gui/ThemeEval.h"
#include "gui/ThemeLayout.h"
#include "graphics/font.h"
#ifdef LAYOUT_DEBUG_DIALOG
#include "graphics/surface.h"
#endif
namespace GUI {
void ThemeLayout::importLayout(ThemeLayout *layout) {
assert(layout->getLayoutType() == kLayoutMain);
if (layout->_children.size() == 0)
return;
layout = layout->_children[0];
if (getLayoutType() == layout->getLayoutType()) {
for (uint i = 0; i < layout->_children.size(); ++i)
_children.push_back(layout->_children[i]->makeClone(this));
} else {
ThemeLayout *clone = layout->makeClone(this);
// When importing a layout into a layout of the same type, the children
// of the imported layout are copied over, ignoring the padding of the
// imported layout. Here when importing a layout of a different type
// into a layout we explicitly ignore the padding so the appearance
// is the same in both cases.
clone->setPadding(0, 0, 0, 0);
_children.push_back(clone);
}
}
void ThemeLayout::resetLayout() {
_x = 0;
_y = 0;
_w = _defaultW;
_h = _defaultH;
for (uint i = 0; i < _children.size(); ++i)
_children[i]->resetLayout();
}
bool ThemeLayout::getWidgetData(const Common::String &name, int16 &x, int16 &y, int16 &w, int16 &h, bool &useRTL) {
if (name.empty()) {
assert(getLayoutType() == kLayoutMain);
x = _x; y = _y;
w = _w; h = _h;
useRTL = _useRTL;
return true;
}
for (uint i = 0; i < _children.size(); ++i) {
if (_children[i]->getWidgetData(name, x, y, w, h, useRTL))
return true;
}
return false;
}
Graphics::TextAlign ThemeLayout::getWidgetTextHAlign(const Common::String &name) {
if (name.empty()) {
assert(getLayoutType() == kLayoutMain);
return _textHAlign;
}
Graphics::TextAlign res;
for (uint i = 0; i < _children.size(); ++i) {
if ((res = _children[i]->getWidgetTextHAlign(name)) != Graphics::kTextAlignInvalid)
return res;
}
return Graphics::kTextAlignInvalid;
}
int16 ThemeLayoutStacked::getParentWidth() {
ThemeLayout *p = _parent;
int width = 0;
while (p && p->getLayoutType() != kLayoutMain) {
width += p->_padding.right + p->_padding.left;
if (p->getLayoutType() == kLayoutHorizontal) {
const int spacing = ((ThemeLayoutStacked *)p)->_spacing;
for (uint i = 0; i < p->_children.size(); ++i)
width += p->_children[i]->getWidth() + spacing;
}
// FIXME: Do we really want to assume that any layout type different
// from kLayoutHorizontal corresponds to width 0 ?
p = p->_parent;
}
assert(p && p->getLayoutType() == kLayoutMain);
return p->getWidth() - width;
}
int16 ThemeLayoutStacked::getParentHeight() {
ThemeLayout *p = _parent;
int height = 0;
while (p && p->getLayoutType() != kLayoutMain) {
height += p->_padding.bottom + p->_padding.top;
if (p->getLayoutType() == kLayoutVertical) {
const int spacing = ((ThemeLayoutStacked *)p)->_spacing;
for (uint i = 0; i < p->_children.size(); ++i)
height += p->_children[i]->getHeight() + spacing;
}
// FIXME: Do we really want to assume that any layout type different
// from kLayoutVertical corresponds to height 0 ?
p = p->_parent;
}
assert(p && p->getLayoutType() == kLayoutMain);
return p->getHeight() - height;
}
#ifdef LAYOUT_DEBUG_DIALOG
void ThemeLayout::debugDraw(Graphics::ManagedSurface *screen, const Graphics::Font *font) {
uint32 color = 0xFFFFFFFF;
font->drawString(screen, getName(), _x, _y, _w, color, Graphics::kTextAlignRight, 0, true);
screen->hLine(_x, _y, _x + _w, color);
screen->hLine(_x, _y + _h, _x + _w , color);
screen->vLine(_x, _y, _y + _h, color);
screen->vLine(_x + _w, _y, _y + _h, color);
for (uint i = 0; i < _children.size(); ++i)
_children[i]->debugDraw(screen, font);
}
#endif
bool ThemeLayoutWidget::getWidgetData(const Common::String &name, int16 &x, int16 &y, int16 &w, int16 &h, bool &useRTL) {
if (name == _name) {
x = _x; y = _y;
w = _w; h = _h;
useRTL = _useRTL;
return true;
}
return false;
}
Graphics::TextAlign ThemeLayoutWidget::getWidgetTextHAlign(const Common::String &name) {
if (name == _name) {
return _textHAlign;
}
return Graphics::kTextAlignInvalid;
}
void ThemeLayoutWidget::reflowLayout(Widget *widgetChain) {
Widget *guiWidget = getWidget(widgetChain);
if (!guiWidget) {
return;
}
int minWidth = -1;
int minHeight = -1;
guiWidget->getMinSize(minWidth, minHeight);
if (_w != -1 && minWidth != -1 && minWidth > _w) {
_w = minWidth;
}
if (_h != -1 && minHeight != -1 && minHeight > _h) {
_h = minHeight;
}
}
bool ThemeLayoutWidget::isBound(Widget *widgetChain) const {
Widget *guiWidget = getWidget(widgetChain);
return guiWidget != nullptr;
}
Widget *ThemeLayoutWidget::getWidget(Widget *widgetChain) const {
const ThemeLayout *topLevelLayout = this;
while (topLevelLayout->_parent) {
topLevelLayout = topLevelLayout->_parent;
}
assert(topLevelLayout && topLevelLayout->getLayoutType() == kLayoutMain);
const ThemeLayoutMain *dialogLayout = static_cast<const ThemeLayoutMain *>(topLevelLayout);
Common::String widgetName = Common::String::format("%s.%s", dialogLayout->getName(), _name.c_str());
return Widget::findWidgetInChain(widgetChain, widgetName.c_str());
}
enum SafeAreaType {
kSafeAreaNone,
kSafeAreaMove,
kSafeAreaExtend
};
void ThemeLayoutMain::reflowLayout(Widget *widgetChain) {
assert(_children.size() <= 1);
resetLayout();
int16 screenW;
int16 screenH;
Common::Rect safeArea = g_system->getSafeOverlayArea(&screenW, &screenH);
SafeAreaType safeAreaType;
// With RTL, we just flip everything on the X axis, so do the same with the safeArea
if (g_gui.useRTL()) {
int16 tmp = safeArea.left;
safeArea.left = screenW - safeArea.right;
safeArea.right = screenW - tmp;
}
int inset = _inset * g_gui.getScaleFactor();
if (_overlays == "screen") {
_x = MAX(inset, (int)safeArea.left);
_y = MAX(inset, (int)safeArea.top);
int16 r = MIN(screenW - inset, (int)safeArea.right);
int16 b = MIN(screenH - inset, (int)safeArea.bottom);
_w = r - _x;
_h = b - _y;
// Extend the dialog background only if it is supposed to stick on the borders
safeAreaType = inset == 0 ? kSafeAreaExtend : kSafeAreaMove;
} else if (_overlays == "screen_center") {
_x = -1;
_y = -1;
_w = _defaultW > 0 ? MIN(_defaultW - 2*inset, (int)safeArea.width()) : -1;
_h = _defaultH > 0 ? MIN(_defaultH - 2*inset, (int)safeArea.height()) : -1;
safeAreaType = kSafeAreaMove;
} else {
if (!g_gui.xmlEval()->getWidgetData(_overlays, _x, _y, _w, _h)) {
warning("Unable to retrieve overlayed dialog position %s", _overlays.c_str());
}
if (_w == -1 || _h == -1) {
warning("The overlayed dialog %s has not been sized, using a default size for %s", _overlays.c_str(), _name.c_str());
_x = MAX(safeArea.width() / 10 + inset, (int)safeArea.left);
_y = MAX(safeArea.height() / 10 + inset, (int)safeArea.top);
int16 r = MIN(screenW - safeArea.width() / 10 - inset, (int)safeArea.right);
int16 b = MIN(screenH - safeArea.height() / 10 - inset, (int)safeArea.bottom);
_w = r - _x;
_h = b - _y;
safeAreaType = kSafeAreaMove;
} else {
// We expect that the parent widget already takes the safe area into account
if (_x >= 0) _x += inset;
if (_y >= 0) _y += inset;
if (_w >= 0) _w -= 2 * inset;
if (_h >= 0) _h -= 2 * inset;
safeAreaType = kSafeAreaNone;
}
}
if (_children.size()) {
_children[0]->setWidth(_w);
_children[0]->setHeight(_h);
_children[0]->reflowLayout(widgetChain);
if (safeAreaType == kSafeAreaExtend) {
_x = 0;
_y = 0;
_w = screenW;
_h = screenH;
}
if (_w == -1)
_w = _children[0]->getWidth();
if (_h == -1)
_h = _children[0]->getHeight();
if (_y == -1)
_y = (screenH >> 1) - (_h >> 1);
if (_x == -1)
_x = (screenW >> 1) - (_w >> 1);
if (safeAreaType == kSafeAreaExtend) {
// Move the children to allow for background draw since the origin
_children[0]->offsetX(safeArea.left);
_children[0]->offsetY(safeArea.top);
} else if (safeAreaType == kSafeAreaMove) {
assert(safeArea.constrain(_x, _y, _w, _h));
}
} else if (safeAreaType == kSafeAreaExtend) {
_x = 0;
_y = 0;
_w = screenW;
_h = screenH;
}
}
void ThemeLayoutStacked::reflowLayoutVertical(Widget *widgetChain) {
int curY;
int resize[8];
int rescount = 0;
bool fixedWidth = _w != -1;
curY = _padding.top;
_h = _padding.top + _padding.bottom;
for (uint i = 0; i < _children.size(); ++i) {
if (!_children[i]->isBound(widgetChain)) continue;
_children[i]->reflowLayout(widgetChain);
if (_children[i]->getWidth() == -1) {
int16 width = (_w == -1 ? getParentWidth() : _w) - _padding.left - _padding.right;
_children[i]->setWidth(MAX<int16>(width, 0));
}
if (_children[i]->getHeight() == -1) {
assert(rescount < ARRAYSIZE(resize));
resize[rescount++] = i;
_children[i]->setHeight(0);
}
_children[i]->offsetY(curY);
// Advance the vertical offset by the height of the newest item, plus
// the item spacing value.
curY += _children[i]->getHeight() + _spacing;
// Update width and height of this stack layout
if (!fixedWidth) {
_w = MAX(_w, (int16)(_children[i]->getWidth() + _padding.left + _padding.right));
}
_h += _children[i]->getHeight() + _spacing;
}
// If there are any children at all, then we added the spacing value once
// too often. Correct that.
if (!_children.empty())
_h -= _spacing;
// If the width is not set at this point, then we have no bound widgets.
if (!fixedWidth && _w == -1) {
_w = 0;
}
for (uint i = 0; i < _children.size(); ++i) {
switch (_itemAlign) {
case kItemAlignStart:
default:
_children[i]->offsetX(_padding.left);
break;
case kItemAlignCenter:
// Center child if it this has been requested *and* the space permits it.
if (_children[i]->getWidth() < (_w - _padding.left - _padding.right)) {
_children[i]->offsetX((_w >> 1) - (_children[i]->getWidth() >> 1));
} else {
_children[i]->offsetX(_padding.left);
}
break;
case kItemAlignEnd:
_children[i]->offsetX(_w - _children[i]->getWidth() - _padding.right);
break;
case kItemAlignStretch:
_children[i]->offsetX(_padding.left);
_children[i]->setWidth(_w - _padding.left - _padding.right);
break;
}
}
// If there were any items with undetermined height, then compute and set
// their height now. We do so by determining how much space is left, and
// then distributing this equally over all items which need auto-resizing.
if (rescount) {
int newh = (getParentHeight() - _h - _padding.bottom) / rescount;
if (newh < 0) newh = 0; // In case there is no room left, avoid giving a negative height to widgets
for (int i = 0; i < rescount; ++i) {
// Set the height of the item.
_children[resize[i]]->setHeight(newh);
// Increase the height of this ThemeLayoutStacked accordingly, and
// then shift all subsequence children.
_h += newh;
for (uint j = resize[i] + 1; j < _children.size(); ++j)
_children[j]->offsetY(newh);
}
}
}
void ThemeLayoutStacked::reflowLayoutHorizontal(Widget *widgetChain) {
int curX;
int resize[8];
int rescount = 0;
bool fixedHeight = _h != -1;
curX = _padding.left;
_w = _padding.left + _padding.right;
for (uint i = 0; i < _children.size(); ++i) {
if (!_children[i]->isBound(widgetChain)) continue;
_children[i]->reflowLayout(widgetChain);
if (_children[i]->getHeight() == -1) {
int16 height = (_h == -1 ? getParentHeight() : _h) - _padding.top - _padding.bottom;
_children[i]->setHeight(MAX<int16>(height, 0));
}
if (_children[i]->getWidth() == -1) {
assert(rescount < ARRAYSIZE(resize));
resize[rescount++] = i;
_children[i]->setWidth(0);
}
_children[i]->offsetX(curX);
// Advance the horizontal offset by the width of the newest item, plus
// the item spacing value.
curX += (_children[i]->getWidth() + _spacing);
// Update width and height of this stack layout
_w += _children[i]->getWidth() + _spacing;
if (!fixedHeight) {
_h = MAX(_h, (int16)(_children[i]->getHeight() + _padding.top + _padding.bottom));
}
}
// If there are any children at all, then we added the spacing value once
// too often. Correct that.
if (!_children.empty())
_w -= _spacing;
// If the height is not set at this point, then we have no bound widgets.
if (!fixedHeight && _h == -1) {
_h = 0;
}
for (uint i = 0; i < _children.size(); ++i) {
switch (_itemAlign) {
case kItemAlignStart:
default:
_children[i]->offsetY(_padding.top);
break;
case kItemAlignCenter:
// Center child if it this has been requested *and* the space permits it.
if (_children[i]->getHeight() < (_h - _padding.top - _padding.bottom)) {
_children[i]->offsetY((_h >> 1) - (_children[i]->getHeight() >> 1));
} else {
_children[i]->offsetY(_padding.top);
}
break;
case kItemAlignEnd:
_children[i]->offsetY(_h - _children[i]->getHeight() - _padding.bottom);
break;
case kItemAlignStretch:
_children[i]->offsetY(_padding.top);
_children[i]->setHeight(_w - _padding.top - _padding.bottom);
break;
}
}
// If there were any items with undetermined width, then compute and set
// their width now. We do so by determining how much space is left, and
// then distributing this equally over all items which need auto-resizing.
if (rescount) {
int neww = (getParentWidth() - _w - _padding.right) / rescount;
if (neww < 0) neww = 0; // In case there is no room left, avoid giving a negative width to widgets
for (int i = 0; i < rescount; ++i) {
// Set the width of the item.
_children[resize[i]]->setWidth(neww);
// Increase the width of this ThemeLayoutStacked accordingly, and
// then shift all subsequence children.
_w += neww;
for (uint j = resize[i] + 1; j < _children.size(); ++j)
_children[j]->offsetX(neww);
}
}
}
} // End of namespace GUI

353
gui/ThemeLayout.h Normal file
View File

@@ -0,0 +1,353 @@
/* 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/>.
*
*/
#ifndef THEME_LAYOUT_H
#define THEME_LAYOUT_H
#include "common/array.h"
#include "common/rect.h"
#include "graphics/font.h"
//#define LAYOUT_DEBUG_DIALOG "Dialog.Launcher"
#ifdef LAYOUT_DEBUG_DIALOG
namespace Graphics {
struct Surface;
}
#endif
namespace GUI {
class Widget;
class ThemeLayout {
friend class ThemeLayoutMain;
friend class ThemeLayoutStacked;
friend class ThemeLayoutSpacing;
friend class ThemeLayoutWidget;
public:
enum LayoutType {
kLayoutMain,
kLayoutVertical,
kLayoutHorizontal,
kLayoutWidget,
kLayoutTabWidget,
kLayoutScrollContainerWidget,
kLayoutSpace
};
/// Cross-direction alignment of layout children.
enum ItemAlign {
kItemAlignStart, ///< Items are aligned to the left for vertical layouts or to the top for horizontal layouts
kItemAlignCenter, ///< Items are centered in the container
kItemAlignEnd, ///< Items are aligned to the right for vertical layouts or to the bottom for horizontal layouts
kItemAlignStretch ///< Items are resized to match the size of the layout in the cross-direction
};
ThemeLayout(ThemeLayout *p) :
_parent(p), _x(0), _y(0), _w(-1), _h(-1),
_defaultW(-1), _defaultH(-1),
_textHAlign(Graphics::kTextAlignInvalid), _useRTL(true) {}
virtual ~ThemeLayout() {
for (uint i = 0; i < _children.size(); ++i)
delete _children[i];
}
virtual void reflowLayout(Widget *widgetChain) = 0;
virtual void resetLayout();
void addChild(ThemeLayout *child) { _children.push_back(child); }
void setPadding(int16 left, int16 right, int16 top, int16 bottom) {
_padding.left = left;
_padding.right = right;
_padding.top = top;
_padding.bottom = bottom;
}
protected:
int16 getWidth() { return _w; }
int16 getHeight() { return _h; }
void offsetX(int newX) {
_x += newX;
for (uint i = 0; i < _children.size(); ++i)
_children[i]->offsetX(newX);
}
void offsetY(int newY) {
_y += newY;
for (uint i = 0; i < _children.size(); ++i)
_children[i]->offsetY(newY);
}
void setWidth(int16 width) { _w = width; }
void setHeight(int16 height) { _h = height; }
void setTextHAlign(Graphics::TextAlign align) { _textHAlign = align; }
/**
* Checks if the layout element is attached to a GUI widget
*
* Layout elements that are not bound do not take space.
*/
virtual bool isBound(Widget *widgetChain) const { return true; }
virtual LayoutType getLayoutType() const = 0;
virtual ThemeLayout *makeClone(ThemeLayout *newParent) = 0;
public:
virtual bool getWidgetData(const Common::String &name, int16 &x, int16 &y, int16 &w, int16 &h, bool &useRTL);
bool getUseRTL() { return _useRTL; }
virtual Graphics::TextAlign getWidgetTextHAlign(const Common::String &name);
void importLayout(ThemeLayout *layout);
Graphics::TextAlign getTextHAlign() { return _textHAlign; }
#ifdef LAYOUT_DEBUG_DIALOG
void debugDraw(Graphics::ManagedSurface *screen, const Graphics::Font *font);
#endif
virtual const char *getName() const { return "<override-me>"; }
protected:
ThemeLayout *_parent;
int16 _x, _y, _w, _h;
bool _useRTL;
Common::Rect _padding;
Common::Array<ThemeLayout *> _children;
int16 _defaultW, _defaultH;
Graphics::TextAlign _textHAlign;
};
class ThemeLayoutMain : public ThemeLayout {
public:
ThemeLayoutMain(const Common::String &name, const Common::String &overlays, int16 width, int16 height, int inset) :
ThemeLayout(nullptr),
_name(name),
_overlays(overlays),
_inset(inset) {
_w = _defaultW = width;
_h = _defaultH = height;
_x = _defaultX = -1;
_y = _defaultY = -1;
}
void reflowLayout(Widget *widgetChain) override;
void resetLayout() override {
ThemeLayout::resetLayout();
_x = _defaultX;
_y = _defaultY;
}
virtual const char *getName() const override { return _name.c_str(); }
protected:
LayoutType getLayoutType() const override { return kLayoutMain; }
ThemeLayout *makeClone(ThemeLayout *newParent) override { assert(!"Do not copy Main Layouts!"); return nullptr; }
int16 _defaultX;
int16 _defaultY;
Common::String _name;
Common::String _overlays;
int _inset;
};
class ThemeLayoutStacked : public ThemeLayout {
public:
ThemeLayoutStacked(ThemeLayout *p, LayoutType type, int spacing, ItemAlign itemAlign) :
ThemeLayout(p), _type(type), _itemAlign(itemAlign) {
assert((type == kLayoutVertical) || (type == kLayoutHorizontal));
_spacing = spacing;
}
void reflowLayout(Widget *widgetChain) override {
if (_type == kLayoutVertical)
reflowLayoutVertical(widgetChain);
else
reflowLayoutHorizontal(widgetChain);
}
void reflowLayoutHorizontal(Widget *widgetChain);
void reflowLayoutVertical(Widget *widgetChain);
#ifdef LAYOUT_DEBUG_DIALOG
const char *getName() const override {
return (_type == kLayoutVertical)
? "Vertical Layout" : "Horizontal Layout";
}
#endif
protected:
int16 getParentWidth();
int16 getParentHeight();
LayoutType getLayoutType() const override { return _type; }
ThemeLayout *makeClone(ThemeLayout *newParent) override {
ThemeLayoutStacked *n = new ThemeLayoutStacked(*this);
n->_parent = newParent;
for (uint i = 0; i < n->_children.size(); ++i)
n->_children[i] = n->_children[i]->makeClone(n);
return n;
}
const LayoutType _type;
ItemAlign _itemAlign;
int8 _spacing;
};
class ThemeLayoutWidget : public ThemeLayout {
public:
ThemeLayoutWidget(ThemeLayout *p, const Common::String &name, int16 w, int16 h, Graphics::TextAlign align, bool useRTL) : ThemeLayout(p), _name(name) {
_w = _defaultW = w;
_h = _defaultH = h;
_useRTL = useRTL;
setTextHAlign(align);
}
bool getWidgetData(const Common::String &name, int16 &x, int16 &y, int16 &w, int16 &h, bool &useRTL) override;
Graphics::TextAlign getWidgetTextHAlign(const Common::String &name) override;
void reflowLayout(Widget *widgetChain) override;
virtual const char *getName() const override { return _name.c_str(); }
protected:
LayoutType getLayoutType() const override { return kLayoutWidget; }
bool isBound(Widget *widgetChain) const override;
Widget *getWidget(Widget *widgetChain) const;
ThemeLayout *makeClone(ThemeLayout *newParent) override {
ThemeLayout *n = new ThemeLayoutWidget(*this);
n->_parent = newParent;
return n;
}
Common::String _name;
};
class ThemeLayoutTabWidget : public ThemeLayoutWidget {
int _tabHeight;
public:
ThemeLayoutTabWidget(ThemeLayout *p, const Common::String &name, int16 w, int16 h, Graphics::TextAlign align, int tabHeight):
ThemeLayoutWidget(p, name, w, h, align, p->getUseRTL()) {
_tabHeight = tabHeight;
}
void reflowLayout(Widget *widgetChain) override {
for (uint i = 0; i < _children.size(); ++i) {
_children[i]->reflowLayout(widgetChain);
}
}
bool getWidgetData(const Common::String &name, int16 &x, int16 &y, int16 &w, int16 &h, bool &useRTL) override {
if (ThemeLayoutWidget::getWidgetData(name, x, y, w, h, useRTL)) {
h -= _tabHeight;
return true;
}
return false;
}
protected:
LayoutType getLayoutType() const override { return kLayoutTabWidget; }
ThemeLayout *makeClone(ThemeLayout *newParent) override {
ThemeLayoutTabWidget *n = new ThemeLayoutTabWidget(*this);
n->_parent = newParent;
return n;
}
};
class ThemeLayoutScrollContainerWidget : public ThemeLayoutWidget {
int _scrollWidth;
public:
ThemeLayoutScrollContainerWidget(ThemeLayout *p, const Common::String &name, int16 w, int16 h, Graphics::TextAlign align, int scrollWidth):
ThemeLayoutWidget(p, name, w, h, align, p->getUseRTL()) {
_scrollWidth = scrollWidth;
}
void reflowLayout(Widget *widgetChain) override {
for (uint i = 0; i < _children.size(); ++i) {
_children[i]->reflowLayout(widgetChain);
}
}
bool getWidgetData(const Common::String &name, int16 &x, int16 &y, int16 &w, int16 &h, bool &useRTL) override {
if (ThemeLayoutWidget::getWidgetData(name, x, y, w, h, useRTL)) {
w -= _scrollWidth;
return true;
}
return false;
}
protected:
LayoutType getLayoutType() const override { return kLayoutScrollContainerWidget; }
ThemeLayout *makeClone(ThemeLayout *newParent) override {
ThemeLayoutScrollContainerWidget *n = new ThemeLayoutScrollContainerWidget(*this);
n->_parent = newParent;
return n;
}
};
class ThemeLayoutSpacing : public ThemeLayout {
public:
ThemeLayoutSpacing(ThemeLayout *p, int size) : ThemeLayout(p) {
if (p->getLayoutType() == kLayoutHorizontal) {
_w = _defaultW = size;
_h = _defaultH = 1;
} else if (p->getLayoutType() == kLayoutVertical) {
_w = _defaultW = 1;
_h = _defaultH = size;
}
}
bool getWidgetData(const Common::String &name, int16 &x, int16 &y, int16 &w, int16 &h, bool &useRTL) override { return false; }
void reflowLayout(Widget *widgetChain) override {}
#ifdef LAYOUT_DEBUG_DIALOG
const char *getName() const override { return "SPACE"; }
#endif
protected:
LayoutType getLayoutType() const override { return kLayoutSpace; }
ThemeLayout *makeClone(ThemeLayout *newParent) override {
ThemeLayout *n = new ThemeLayoutSpacing(*this);
n->_parent = newParent;
return n;
}
};
}
#endif

1177
gui/ThemeParser.cpp Normal file

File diff suppressed because it is too large Load Diff

292
gui/ThemeParser.h Normal file
View File

@@ -0,0 +1,292 @@
/* 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/>.
*
*/
#ifndef THEME_PARSER_H
#define THEME_PARSER_H
#include "common/scummsys.h"
#include "common/formats/xmlparser.h"
namespace GUI {
class ThemeEngine;
class ThemeParser : public Common::XMLParser {
public:
ThemeParser(ThemeEngine *parent);
~ThemeParser() override;
void setBaseResolution(int w, int h, float s) {
_baseWidth = w;
_baseHeight = h;
_scaleFactor = s;
}
bool getPaletteColor(const Common::String &name, int &r, int &g, int &b) {
if (!_palette.contains(name))
return false;
r = _palette[name].r;
g = _palette[name].g;
b = _palette[name].b;
return true;
}
protected:
ThemeEngine *_theme;
CUSTOM_XML_PARSER(ThemeParser) {
XML_KEY(render_info)
XML_PROP(resolution, false)
XML_KEY(palette)
XML_KEY(color)
XML_PROP(name, true)
XML_PROP(rgb, true)
KEY_END()
KEY_END()
XML_KEY(fonts)
XML_KEY(font)
XML_PROP(id, true)
XML_PROP(file, false)
XML_PROP(resolution, false)
XML_PROP(scalable_file, false)
XML_PROP(point_size, false)
XML_KEY(language)
XML_PROP(id, true)
XML_PROP(file, false)
XML_PROP(scalable_file, false)
XML_PROP(point_size, false)
KEY_END()
KEY_END()
XML_KEY(text_color)
XML_PROP(id, true);
XML_PROP(color, true);
KEY_END()
KEY_END()
XML_KEY(bitmaps)
XML_KEY(bitmap)
XML_PROP(filename, true)
XML_PROP(resolution, false)
XML_PROP(scalable_file, false)
XML_PROP(width, false)
XML_PROP(height, false)
KEY_END()
KEY_END()
XML_KEY(cursor)
XML_PROP(file, true)
XML_PROP(hotspot, true)
XML_PROP(resolution, false)
KEY_END()
XML_KEY(defaults)
XML_PROP(stroke, false)
XML_PROP(shadow, false)
XML_PROP(bevel, false)
XML_PROP(factor, false)
XML_PROP(fg_color, false)
XML_PROP(bg_color, false)
XML_PROP(gradient_start, false)
XML_PROP(gradient_end, false)
XML_PROP(bevel_color, false)
XML_PROP(gradient_factor, false)
XML_PROP(fill, false)
KEY_END()
XML_KEY(drawdata)
XML_PROP(id, true)
XML_PROP(cache, false)
XML_PROP(resolution, false)
XML_KEY(defaults)
XML_PROP(stroke, false)
XML_PROP(shadow, false)
XML_PROP(bevel, false)
XML_PROP(factor, false)
XML_PROP(fg_color, false)
XML_PROP(bg_color, false)
XML_PROP(gradient_start, false)
XML_PROP(gradient_end, false)
XML_PROP(bevel_color, false)
XML_PROP(gradient_factor, false)
XML_PROP(fill, false)
KEY_END()
XML_KEY(drawstep)
XML_PROP(func, true)
XML_PROP(stroke, false)
XML_PROP(shadow, false)
XML_PROP(bevel, false)
XML_PROP(factor, false)
XML_PROP(fg_color, false)
XML_PROP(bg_color, false)
XML_PROP(gradient_start, false)
XML_PROP(gradient_end, false)
XML_PROP(gradient_factor, false)
XML_PROP(bevel_color, false)
XML_PROP(fill, false)
XML_PROP(radius, false)
XML_PROP(width, false)
XML_PROP(height, false)
XML_PROP(xpos, false)
XML_PROP(ypos, false)
XML_PROP(padding, false)
XML_PROP(orientation, false)
XML_PROP(file, false)
XML_PROP(autoscale, false)
XML_PROP(clip, false)
KEY_END()
XML_KEY(text)
XML_PROP(font, true)
XML_PROP(text_color, true)
XML_PROP(vertical_align, true)
XML_PROP(horizontal_align, true)
KEY_END()
KEY_END()
KEY_END() // render_info end
XML_KEY(layout_info)
XML_PROP(resolution, false)
XML_KEY(globals)
XML_PROP(resolution, false)
XML_KEY(def)
XML_PROP(var, true)
XML_PROP(value, true)
XML_PROP(resolution, false)
XML_PROP(scalable, false)
KEY_END()
XML_KEY(widget)
XML_PROP(name, true)
XML_PROP(size, false)
XML_PROP(pos, false)
XML_PROP(padding, false)
XML_PROP(resolution, false)
XML_PROP(textalign, false)
XML_PROP(rtl, false)
KEY_END()
KEY_END()
XML_KEY(dialog)
XML_PROP(name, true)
XML_PROP(overlays, true)
XML_PROP(size, false)
XML_PROP(shading, false)
XML_PROP(resolution, false)
XML_PROP(inset, false)
XML_KEY(layout)
XML_PROP(type, true)
XML_PROP(align, false)
XML_PROP(padding, false)
XML_PROP(spacing, false)
XML_PROP(resolution, false)
XML_KEY(import)
XML_PROP(layout, true)
KEY_END()
XML_KEY(widget)
XML_PROP(name, true)
XML_PROP(width, false)
XML_PROP(height, false)
XML_PROP(type, false)
XML_PROP(textalign, false)
XML_PROP(rtl, false)
XML_PROP(resolution, false)
KEY_END()
XML_KEY(space)
XML_PROP(size, false)
XML_PROP(resolution, false)
KEY_END()
XML_KEY_RECURSIVE(layout)
KEY_END()
KEY_END()
KEY_END()
} PARSER_END()
/** Render info callbacks */
bool parserCallback_render_info(ParserNode *node);
bool parserCallback_defaults(ParserNode *node);
bool parserCallback_font(ParserNode *node);
bool parserCallback_text_color(ParserNode *node);
bool parserCallback_fonts(ParserNode *node);
bool parserCallback_language(ParserNode *node);
bool parserCallback_text(ParserNode *node);
bool parserCallback_palette(ParserNode *node);
bool parserCallback_color(ParserNode *node);
bool parserCallback_drawstep(ParserNode *node);
bool parserCallback_drawdata(ParserNode *node);
bool parserCallback_bitmaps(ParserNode *node) { return true; }
bool parserCallback_bitmap(ParserNode *node);
bool parserCallback_cursor(ParserNode *node);
/** Layout info callbacks */
bool parserCallback_layout_info(ParserNode *node);
bool parserCallback_globals(ParserNode *node) { return true; }
bool parserCallback_def(ParserNode *node);
bool parserCallback_widget(ParserNode *node);
bool parserCallback_dialog(ParserNode *node);
bool parserCallback_layout(ParserNode *node);
bool parserCallback_space(ParserNode *node);
bool parserCallback_import(ParserNode *node);
bool closedKeyCallback(ParserNode *node) override;
bool resolutionCheck(const Common::String &resolution);
void cleanup() override;
Graphics::DrawStep *newDrawStep();
Graphics::DrawStep *defaultDrawStep();
bool parseDrawStep(ParserNode *stepNode, Graphics::DrawStep *drawstep, bool functionSpecific);
bool parseCommonLayoutProps(ParserNode *node, const Common::String &var);
bool parseList(const char *key, int count, ...);
bool parseList(const Common::String &keyStr, int count, ...);
bool vparseList(const char *key, int count, va_list args);
Graphics::DrawStep *_defaultStepGlobal;
Graphics::DrawStep *_defaultStepLocal;
int16 _baseWidth, _baseHeight;
float _scaleFactor;
struct PaletteColor {
uint8 r, g, b;
};
Common::HashMap<Common::String, PaletteColor, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> _palette;
};
} // End of namespace GUI
#endif

101
gui/Tooltip.cpp Normal file
View File

@@ -0,0 +1,101 @@
/* 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/util.h"
#include "gui/widget.h"
#include "gui/dialog.h"
#include "gui/gui-manager.h"
#include "gui/Tooltip.h"
#include "gui/ThemeEval.h"
namespace GUI {
Tooltip::Tooltip() :
Dialog(-1, -1, -1, -1), _maxWidth(-1), _parent(nullptr), _xdelta(0), _ydelta(0), _xpadding(0), _ypadding(0) {
_backgroundType = GUI::ThemeEngine::kDialogBackgroundTooltip;
}
void Tooltip::setup(Dialog *parent, Widget *widget, int x, int y) {
assert(widget->hasTooltip());
_parent = parent;
setMouseUpdatedOnFocus(false);
_maxWidth = g_gui.xmlEval()->getVar("Globals.Tooltip.MaxWidth", 100);
_xdelta = g_gui.xmlEval()->getVar("Globals.Tooltip.XDelta", 0);
_ydelta = g_gui.xmlEval()->getVar("Globals.Tooltip.YDelta", 0);
_xpadding = g_gui.xmlEval()->getVar("Globals.Tooltip.XPadding", 2);
_ypadding = g_gui.xmlEval()->getVar("Globals.Tooltip.YPadding", 2);
const Graphics::Font *tooltipFont = g_gui.theme()->getFont(ThemeEngine::kFontStyleTooltip);
_wrappedLines.clear();
_w = tooltipFont->wordWrapText(widget->getTooltip(), _maxWidth - _xpadding * 2, _wrappedLines) + _xpadding * 2;
_h = (tooltipFont->getFontHeight() + 2) * _wrappedLines.size() + _ypadding * 2;
_x = MIN<int16>(parent->_x + x + _xdelta + _xpadding, g_system->getOverlayWidth() - _w - _xpadding * 2);
_y = MIN<int16>(parent->_y + y + _ydelta + _ypadding, g_system->getOverlayHeight() - _h - _ypadding * 2);
if (ConfMan.hasKey("tts_enabled", "scummvm") &&
ConfMan.getBool("tts_enabled", "scummvm")) {
Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
if (ttsMan == nullptr)
return;
ttsMan->say(widget->getTooltip(), Common::TextToSpeechManager::QUEUE_NO_REPEAT);
}
}
void Tooltip::drawDialog(DrawLayer layerToDraw) {
int num = 0;
int h = g_gui.theme()->getFontHeight(ThemeEngine::kFontStyleTooltip) + 2;
Dialog::drawDialog(layerToDraw);
int16 textX = _x + 1 + _xpadding;
if (g_gui.useRTL()) {
textX = g_system->getOverlayWidth() - _w - textX;
}
int16 textY = _y + 1 + _ypadding;
Graphics::TextAlign textAlignment = g_gui.useRTL() ? Graphics::kTextAlignRight : Graphics::kTextAlignLeft;
for (Common::U32StringArray::const_iterator i = _wrappedLines.begin(); i != _wrappedLines.end(); ++i, ++num) {
g_gui.theme()->drawText(
Common::Rect(textX, textY + num * h, textX + _w, textY + (num + 1) * h),
*i,
ThemeEngine::kStateEnabled,
textAlignment,
ThemeEngine::kTextInversionNone,
0,
false,
ThemeEngine::kFontStyleTooltip,
ThemeEngine::kFontColorNormal,
false
);
}
}
}

79
gui/Tooltip.h Normal file
View File

@@ -0,0 +1,79 @@
/* 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/>.
*
*/
#ifndef GUI_TOOLTIP_H
#define GUI_TOOLTIP_H
#include "common/keyboard.h"
#include "common/str-array.h"
#include "gui/dialog.h"
namespace GUI {
class Widget;
class Tooltip : public Dialog {
private:
Dialog *_parent;
public:
Tooltip();
void setup(Dialog *parent, Widget *widget, int x, int y);
void drawDialog(DrawLayer layerToDraw) override;
void receivedFocus(int x = -1, int y = -1) override {}
protected:
void handleMouseDown(int x, int y, int button, int clickCount) override {
close();
_parent->handleMouseDown(x + (getAbsX() - _parent->getAbsX()), y + (getAbsY() - _parent->getAbsY()), button, clickCount);
}
void handleMouseUp(int x, int y, int button, int clickCount) override {
close();
_parent->handleMouseUp(x + (getAbsX() - _parent->getAbsX()), y + (getAbsY() - _parent->getAbsY()), button, clickCount);
}
void handleMouseWheel(int x, int y, int direction) override {
close();
_parent->handleMouseWheel(x + (getAbsX() - _parent->getAbsX()), y + (getAbsX() - _parent->getAbsX()), direction);
}
void handleKeyDown(Common::KeyState state) override {
close();
_parent->handleKeyDown(state);
}
void handleKeyUp(Common::KeyState state) override {
close();
_parent->handleKeyUp(state);
}
void handleMouseMoved(int x, int y, int button) override {
close();
}
int _maxWidth;
int _xdelta, _ydelta;
int _xpadding, _ypadding;
Common::U32StringArray _wrappedLines;
};
} // End of namespace GUI
#endif // GUI_TOOLTIP_H

1480
gui/about.cpp Normal file

File diff suppressed because it is too large Load Diff

67
gui/about.h Normal file
View File

@@ -0,0 +1,67 @@
/* 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/>.
*
*/
#ifndef ABOUT_DIALOG_H
#define ABOUT_DIALOG_H
#include "gui/dialog.h"
#include "common/str.h"
#include "common/ustr.h"
#include "common/array.h"
#include "common/keyboard.h"
namespace GUI {
class EEHandler;
class AboutDialog : public Dialog {
protected:
int _scrollPos;
uint32 _scrollTime;
Common::U32StringArray _lines;
uint32 _lineHeight;
bool _willClose;
bool _autoScroll;
int _xOff, _yOff;
void addLine(const Common::U32String &str);
EEHandler *_eeHandler;
public:
AboutDialog(bool inGame = false);
void open() override;
void close() override;
void drawDialog(DrawLayer layerToDraw) override;
void handleTickle() override;
void handleMouseUp(int x, int y, int button, int clickCount) override;
void handleMouseWheel(int x, int y, int direction) override;
void handleKeyDown(Common::KeyState state) override;
void handleKeyUp(Common::KeyState state) override;
void reflowLayout() override;
};
} // End of namespace GUI
#endif

View File

@@ -0,0 +1,44 @@
/* 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/>.
*
*/
// Based on code by omergilad.
#ifndef GUI_ANIMATION_ACCELERATEINTERPOLATOR_H
#define GUI_ANIMATION_ACCELERATEINTERPOLATOR_H
#include "gui/animation/Interpolator.h"
namespace GUI {
class AccelerateInterpolator: public Interpolator {
public:
AccelerateInterpolator() {}
virtual ~AccelerateInterpolator() {}
virtual float interpolate(float linearValue) {
return pow(linearValue, 2);
}
};
} // End of namespace GUI
#endif /* GUI_ANIMATION_ACCELERATEINTERPOLATOR_H */

View File

@@ -0,0 +1,52 @@
/* 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/>.
*
*/
// Based on code by omergilad.
#ifndef GUI_ANIMATION_ALPHA_ANIMATION_H
#define GUI_ANIMATION_ALPHA_ANIMATION_H
#include "gui/animation/Animation.h"
namespace GUI {
class AlphaAnimation: public Animation {
public:
AlphaAnimation() {}
virtual ~AlphaAnimation() {}
float getEndAlpha() const { return _endAlpha; }
void setEndAlpha(float endAlpha) { _endAlpha = endAlpha; }
float getStartAlpha() const { return _startAlpha; }
void setStartAlpha(float startAlpha) { _startAlpha = startAlpha; }
protected:
virtual void updateInternal(Drawable* drawable, float interpolation) {
// Calculate alpha value based on properties and interpolation
drawable->setAlpha(_startAlpha * (1 - interpolation) + _endAlpha * interpolation);
}
float _startAlpha;
float _endAlpha;
};
} // End of namespace GUI
#endif /* GUI_ANIMATION_ALPHA_ANIMATION_H */

View File

@@ -0,0 +1,97 @@
/* 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/>.
*
*/
// Based on code by omergilad.
#include "gui/animation/Animation.h"
namespace GUI {
Animation::Animation()
: _startTime(0), _duration(0), _finished(false), _finishOnEnd(true) {
}
Animation::~Animation() {
}
void Animation::start(long currentTime) {
_finished = false;
_startTime = currentTime;
}
void Animation::setDuration(long duration) {
_duration = duration;
}
void Animation::update(Drawable *drawable, long currentTime) {
float interpolation;
if (currentTime < _startTime) {
// If the start time is in the future, nothing changes - the interpolated value is 0
interpolation = 0;
} else if (currentTime > _startTime + _duration) {
// If the animation is finished, the interpolated value is 1 and the animation is marked as finished
interpolation = 1;
finishAnimation();
} else {
// Calculate the interpolated value
interpolation = (currentTime - _startTime) / (float) (_duration);
}
// Activate the interpolator if present
if (_interpolator.get() != nullptr) {
interpolation = _interpolator->interpolate(interpolation);
}
updateInternal(drawable, interpolation);
}
void Animation::finishAnimation() {
if (_finishOnEnd) {
_finished = true;
}
}
void Animation::updateInternal(Drawable *drawable, float interpolation) {
// Default implementation
}
bool Animation::isFinished() const {
return _finished;
}
bool Animation::isFinishOnEnd() const {
return _finishOnEnd;
}
void Animation::setFinishOnEnd(bool finishOnEnd) {
_finishOnEnd = finishOnEnd;
}
InterpolatorPtr Animation::getInterpolator() const {
return _interpolator;
}
void Animation::setInterpolator(InterpolatorPtr interpolator) {
_interpolator = interpolator;
}
} // End of namespace GUI

75
gui/animation/Animation.h Normal file
View File

@@ -0,0 +1,75 @@
/* 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/>.
*
*/
// Based on code by omergilad.
#ifndef GUI_ANIMATION_ANIMATION_H
#define GUI_ANIMATION_ANIMATION_H
#include "gui/animation/Interpolator.h"
namespace GUI {
class Drawable;
class Animation {
public:
Animation();
virtual ~Animation() = 0;
virtual void update(Drawable *drawable, long currentTime);
/**
* Set start time in millis
*/
virtual void start(long currentTime);
/**
* Set duration in millis
*/
virtual void setDuration(long duration);
virtual bool isFinished() const;
bool isFinishOnEnd() const;
void setFinishOnEnd(bool finishOnEnd);
InterpolatorPtr getInterpolator() const;
void setInterpolator(InterpolatorPtr interpolator);
protected:
void finishAnimation();
virtual void updateInternal(Drawable *drawable, float interpolation);
long _startTime;
long _duration;
bool _finished;
bool _finishOnEnd;
InterpolatorPtr _interpolator;
};
typedef Common::SharedPtr<Animation> AnimationPtr;
} // End of namespace GUI
#endif /* GUI_ANIMATION_ANIMATION_H */

View File

@@ -0,0 +1,40 @@
/* 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/>.
*
*/
// Based on code by omergilad.
#ifndef GUI_ANIMATION_DECCELERATEINTERPOLATOR_H
#define GUI_ANIMATION_DECCELERATEINTERPOLATOR_H
#include "gui/animation/Interpolator.h"
namespace GUI {
class DeccelerateInterpolator: public Interpolator {
public:
DeccelerateInterpolator() {}
virtual ~DeccelerateInterpolator() {}
virtual float interpolate(float linearValue) { return sqrt(linearValue); }
};
} // End of namespace GUI
#endif /* GUI_ANIMATION_DECCELERATEINTERPOLATOR_H */

108
gui/animation/Drawable.h Normal file
View File

@@ -0,0 +1,108 @@
/* 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/>.
*
*/
// Based on code by omergilad.
#ifndef GUI_ANIMATION_DRAWABLE_H
#define GUI_ANIMATION_DRAWABLE_H
#include "common/ptr.h"
#include "graphics/managed_surface.h"
#include "gui/animation/Animation.h"
namespace GUI {
class Animation;
typedef Common::SharedPtr<Animation> AnimationPtr;
class Drawable {
public:
Drawable() :
_bitmap(NULL), _positionX(0), _positionY(0), _width(0), _height(0), _alpha(1),
_usingSnapshot(false), _shouldCenter(false) {
_displayRatio = 1.0;
}
virtual ~Drawable() {
if (_usingSnapshot)
delete _bitmap;
}
void updateAnimation(long currentTime) {
if (_animation.get() != NULL) {
_animation->update(this, currentTime);
}
}
bool isAnimationFinished() {
if (_animation.get() != NULL)
return _animation->isFinished();
return false;
}
float getAlpha() const { return _alpha; }
void setAlpha(float alpha) { _alpha = alpha; }
AnimationPtr getAnimation() const { return _animation; }
void setAnimation(AnimationPtr animation) { _animation = animation; }
Graphics::ManagedSurface *getBitmap() const { return _bitmap; }
void setBitmap(Graphics::ManagedSurface *bitmap) { _bitmap = bitmap; }
float getPositionX() const { return _positionX; }
void setPositionX(float positionX) { _positionX = positionX; }
float getPositionY() const { return _positionY; }
void setPositionY(float positionY) { _positionY = positionY; }
virtual float getWidth() const { return _width; }
void setWidth(float width) { _width = width; }
virtual float getHeight() const {
if (_height == 0 && _bitmap && _bitmap->w && _bitmap->h)
return getWidth() * _displayRatio * _bitmap->h / _bitmap->w;
return _height;
}
void setHeight(float height) { _height = height; }
void setDisplayRatio(float ratio) { _displayRatio = ratio; }
inline bool shouldCenter() const { return _shouldCenter; }
void setShouldCenter(bool shouldCenter) { _shouldCenter = shouldCenter; }
protected:
bool _usingSnapshot;
private:
Graphics::ManagedSurface *_bitmap;
float _positionX;
float _positionY;
float _width;
float _height;
float _alpha;
bool _shouldCenter;
AnimationPtr _animation;
float _displayRatio;
};
typedef Common::SharedPtr<Drawable> DrawablePtr;
} // End of namespace GUI
#endif /* GUI_ANIMATION_DRAWABLE_H */

View File

@@ -0,0 +1,43 @@
/* 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/>.
*
*/
// Based on code by omergilad.
#ifndef GUI_ANIMATION_INTERPOLATOR_H
#define GUI_ANIMATION_INTERPOLATOR_H
#include "common/ptr.h"
namespace GUI {
class Interpolator {
public:
Interpolator() {}
virtual ~Interpolator() {}
virtual float interpolate(float linearValue) = 0;
};
typedef Common::SharedPtr<Interpolator> InterpolatorPtr;
} // End of namespace GUI
#endif /* GUI_ANIMATION_INTERPOLATOR_H */

View File

@@ -0,0 +1,71 @@
/* 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/>.
*
*/
// Based on code by omergilad.
#ifndef GUI_ANIMATION_PARALLELANIMATION_H
#define GUI_ANIMATION_PARALLELANIMATION_H
#include "gui/animation/Animation.h"
#include "common/array.h"
namespace GUI {
class ParallelAnimation: public Animation {
public:
ParallelAnimation() {}
virtual ~ParallelAnimation() {}
virtual void addAnimation(AnimationPtr animation) {
_animations.push_back(animation);
}
virtual void update(Drawable *drawable, long currentTime) {
for (AnimationPtr anim : _animations) {
anim->update(drawable, currentTime);
if (anim->isFinished()) {
finishAnimation();
}
}
}
virtual void start(long currentTime) {
Animation::start(currentTime);
for (AnimationPtr anim : _animations)
anim->start(currentTime);
}
virtual void setDuration(long duration) {
Animation::setDuration(duration);
for (AnimationPtr anim : _animations)
anim->setDuration(duration);
}
private:
Common::Array<AnimationPtr> _animations;
};
} // End of namespace GUI
#endif /* GUI_ANIMATION_PARALLELANIMATION_H */

View File

@@ -0,0 +1,51 @@
/* 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/>.
*
*/
// Based on code by omergilad.
#include "gui/animation/RepeatAnimationWrapper.h"
namespace GUI {
void RepeatAnimationWrapper::update(Drawable* drawable, long currentTime) {
// Update wrapped animation
_animation->update(drawable, currentTime);
// If the animation is finished, increase the repeat count and restart it if needed
if (_animation->isFinished()) {
++_repeatCount;
if (_timesToRepeat > 0 && _repeatCount >= _timesToRepeat) {
finishAnimation();
} else {
_animation->start(currentTime);
}
}
}
void RepeatAnimationWrapper::start(long currentTime) {
Animation::start(currentTime);
_repeatCount = 0;
// Start wrapped animation
_animation->start(currentTime);
}
} // End of namespace GUI

View File

@@ -0,0 +1,60 @@
/* 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/>.
*
*/
// Based on code by omergilad.
#ifndef GUI_ANIMATION_REPEATANIMATIONWRAPPER_H
#define GUI_ANIMATION_REPEATANIMATIONWRAPPER_H
#include "gui/animation/Animation.h"
namespace GUI {
class RepeatAnimationWrapper: public Animation {
public:
/**
* Animation - animation to repeat
*
* timesToRepeat - 0 means infinite
*/
RepeatAnimationWrapper(AnimationPtr animation, uint16 timesToRepeat) :
_animation(animation), _timesToRepeat(timesToRepeat) {}
virtual ~RepeatAnimationWrapper() {}
virtual void update(Drawable* drawable, long currentTime);
/**
* Set start time in millis
*/
virtual void start(long currentTime);
private:
uint16 _timesToRepeat;
uint16 _repeatCount;
AnimationPtr _animation;
};
} // End of namespace GUI
#endif /* GUI_ANIMATION_REPEATANIMATIONWRAPPER_H */

View File

@@ -0,0 +1,68 @@
/* 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/>.
*
*/
// Based on code by omergilad.
#ifndef GUI_ANIMATION_SCALEANIMATION_H
#define GUI_ANIMATION_SCALEANIMATION_H
#include "gui/animation/Animation.h"
namespace GUI {
class ScaleAnimation: public Animation {
public:
ScaleAnimation() : _endWidth(0), _endWidthFactor(0) {}
virtual ~ScaleAnimation() {}
float getEndWidth() const { return _endWidth; }
void setEndWidth(float endWidth) { _endWidth = endWidth; }
float getEndWidthFactor() const { return _endWidthFactor; }
void setEndWidthFactor(float endWidthFactor) { _endWidthFactor = endWidthFactor; }
float getStartWidth() const { return _startWidth; }
void setStartWidth(float startWidth) { _startWidth = startWidth; }
void updateInternal(Drawable *drawable, float interpolation) {
// If start width was set as 0 -> use the current width as the start dimension
if (_startWidth == 0)
_startWidth = drawable->getWidth();
// If end width was set as 0 - multiply the start width by the given factor
if (_endWidth == 0)
_endWidth = _startWidth * _endWidthFactor;
// Calculate width based on interpolation
float width = _startWidth * (1 - interpolation) + _endWidth * interpolation;
drawable->setWidth(width);
}
private:
virtual void updateInternal(Drawable *drawable, float interpolation);
float _startWidth;
float _endWidth;
float _endWidthFactor;
};
} // End of namespace GUI
#endif /* GUI_ANIMATION_SCALEANIMATION_H */

View File

@@ -0,0 +1,71 @@
/* 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/>.
*
*/
// Based on code by omergilad.
#include "gui/animation/SequenceAnimationComposite.h"
namespace GUI {
void SequenceAnimationComposite::start(long currentTime) {
Animation::start(currentTime);
// The first animation in the sequence should a start time equal to this sequence
if (_sequence.size() >= 1)
_sequence[0]->start(currentTime);
// Set the index to 0
_index = 0;
}
void SequenceAnimationComposite::addAnimation(AnimationPtr animation) {
_sequence.push_back(animation);
}
void SequenceAnimationComposite::update(Drawable *drawable, long currentTime) {
uint16 sequenceSize = _sequence.size();
// Check index bounds
if (_index >= sequenceSize)
return;
// Get the current animation in the sequence
AnimationPtr anim = _sequence[_index];
// Update the drawable
anim->update(drawable, currentTime);
// Check if the current animation is finished
if (anim->isFinished()) {
// Increase the index - move to the next animation
++_index;
if (_index >= sequenceSize) {
// Finished the sequence
finishAnimation();
} else {
// Set the start time for the next animation
_sequence[_index]->start(currentTime);
}
}
}
} // End of namespace GUI

View File

@@ -0,0 +1,50 @@
/* 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/>.
*
*/
// Based on code by omergilad.
#ifndef GUI_ANIMATION_SEQUENCEANIMATION_H
#define GUI_ANIMATION_SEQUENCEANIMATION_H
#include "gui/animation/Animation.h"
#include "common/array.h"
namespace GUI {
class SequenceAnimationComposite: public Animation {
public:
SequenceAnimationComposite() {}
virtual ~SequenceAnimationComposite() {}
virtual void addAnimation(AnimationPtr animation);
virtual void update(Drawable* drawable, long currentTime);
virtual void start(long currentTime);
private:
uint16 _index;
Common::Array<AnimationPtr> _sequence;
};
} // End of namespace GUI
#endif /* GUI_ANIMATION_SEQUENCEANIMATION_H */

View File

@@ -0,0 +1,70 @@
/* 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/>.
*
*/
// Based on code by omergilad.
#ifndef GUI_ANIMATION_WAITFORCONDITIONANIMATION_H
#define GUI_ANIMATION_WAITFORCONDITIONANIMATION_H
#include "gui/animation/Animation.h"
namespace GUI {
class Condition {
public:
virtual ~Condition() {}
virtual bool evaluate() = 0;
};
typedef Common::SharedPtr<Condition> ConditionPtr;
/**
* Used for delaying the animation sequence until a certain condition has been met
*/
class WaitForConditionAnimation: public Animation {
public:
WaitForConditionAnimation() {}
virtual ~WaitForConditionAnimation() {}
virtual void update(Drawable *drawable, long currentTime) {
// Check the condition - if it has been met, finish.
if (_condition.get() != NULL && _condition->evaluate()) {
finishAnimation();
}
}
ConditionPtr getCondition() const {
return _condition;
}
void setCondition(ConditionPtr condition) {
_condition = condition;
}
private:
ConditionPtr _condition;
};
} // End of namespace GUI
#endif /* GUI_ANIMATION_WAITFORCONDITIONANIMATION_H */

227
gui/browser.cpp Normal file
View File

@@ -0,0 +1,227 @@
/* 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 "gui/browser.h"
#include "gui/gui-manager.h"
#include "gui/widgets/edittext.h"
#include "gui/widgets/list.h"
#include "common/config-manager.h"
#include "common/system.h"
#include "common/algorithm.h"
#if defined(USE_SYSDIALOGS)
#include "common/dialogs.h"
#endif
#include "common/translation.h"
namespace GUI {
enum {
kChooseCmd = 'Chos',
kGoUpCmd = 'GoUp',
kHiddenCmd = 'Hidd',
kPathEditedCmd = 'Path'
};
/* We want to use this as a general directory selector at some point... possible uses
* - to select the data dir for a game
* - to select the place where save games are stored
* - others???
*/
BrowserDialog::BrowserDialog(const Common::U32String &title, bool dirBrowser)
: Dialog("Browser") {
_title = title;
_isDirBrowser = dirBrowser;
_fileList = nullptr;
_currentPath = nullptr;
_showHidden = false;
// Headline - TODO: should be customizable during creation time
new StaticTextWidget(this, "Browser.Headline", title);
// Current path - TODO: handle long paths ?
_currentPath = new EditTextWidget(this, "Browser.Path", Common::U32String(), Common::U32String(), 0, kPathEditedCmd);
// Add file list
_fileList = new ListWidget(this, "Browser.List");
_fileList->setNumberingMode(kListNumberingOff);
_fileList->setEditable(false);
_backgroundType = GUI::ThemeEngine::kDialogBackgroundPlain;
// Checkbox for the "show hidden files" state.
_showHiddenWidget = new CheckboxWidget(this, "Browser.Hidden", _("Show hidden files"), _("Show files marked with the hidden attribute"), kHiddenCmd);
// Buttons
if (!g_gui.useLowResGUI())
new ButtonWidget(this, "Browser.Up", _("Go up"), _("Go to previous directory level"), kGoUpCmd);
else
new ButtonWidget(this, "Browser.Up", _c("Go up", "lowres"), _("Go to previous directory level"), kGoUpCmd);
new ButtonWidget(this, "Browser.Cancel", _("Cancel"), Common::U32String(), kCloseCmd);
new ButtonWidget(this, "Browser.Choose", _("Choose"), Common::U32String(), kChooseCmd);
}
int BrowserDialog::runModal() {
#if defined(USE_SYSDIALOGS)
// Try to use the backend browser
Common::DialogManager *dialogManager = g_system->getDialogManager();
if (dialogManager) {
if (ConfMan.getBool("gui_browser_native", Common::ConfigManager::kApplicationDomain)) {
Common::DialogManager::DialogResult result = dialogManager->showFileBrowser(_title, _choice, _isDirBrowser);
if (result != Common::DialogManager::kDialogError) {
return result;
}
}
}
#endif
// If all else fails, use the GUI browser
return Dialog::runModal();
}
void BrowserDialog::open() {
// Call super implementation
Dialog::open();
if (ConfMan.hasKey("browser_lastpath"))
_node = Common::FSNode(ConfMan.getPath("browser_lastpath"));
if (!_node.isDirectory())
_node = Common::FSNode(".");
_showHidden = ConfMan.getBool("gui_browser_show_hidden", Common::ConfigManager::kApplicationDomain);
_showHiddenWidget->setState(_showHidden);
// At this point the file list has already been refreshed by the kHiddenCmd handler
}
void BrowserDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {
switch (cmd) {
//Search for typed-in directory
case kPathEditedCmd:
_node = Common::FSNode(Common::Path(Common::convertFromU32String(_currentPath->getEditString()), Common::Path::kNativeSeparator));
updateListing();
break;
//Search by text input
case kChooseCmd:
if (_isDirBrowser) {
// If nothing is selected in the list widget, choose the current dir.
// Else, choose the dir that is selected.
int selection = _fileList->getSelected();
if (selection >= 0)
_choice = _nodeContent[selection];
else
_choice = _node;
setResult(1);
close();
} else {
int selection = _fileList->getSelected();
if (selection < 0)
break;
if (_nodeContent[selection].isDirectory()) {
_node = _nodeContent[selection];
updateListing();
} else {
_choice = _nodeContent[selection];
setResult(1);
close();
}
}
break;
case kGoUpCmd:
_node = _node.getParent();
updateListing();
break;
case kListItemActivatedCmd:
case kListItemDoubleClickedCmd:
if (_nodeContent[data].isDirectory()) {
_node = _nodeContent[data];
updateListing();
} else if (!_isDirBrowser) {
_choice = _nodeContent[data];
setResult(1);
close();
}
break;
case kListSelectionChangedCmd:
// We do not allow selecting directories in directory
// browser mode, thus we will invalidate the selection
// when the user selects a directory over here.
if (data != (uint32)-1 && _isDirBrowser && !_nodeContent[data].isDirectory())
_fileList->setSelected(-1);
break;
case kHiddenCmd:
// Update whether the user wants hidden files to be shown
_showHidden = _showHiddenWidget->getState();
// We save the state in the application domain to avoid cluttering and
// to prevent odd behavior.
ConfMan.setBool("gui_browser_show_hidden", _showHidden, Common::ConfigManager::kApplicationDomain);
// Update the file listing
updateListing();
break;
default:
Dialog::handleCommand(sender, cmd, data);
}
}
void BrowserDialog::updateListing() {
// Update the path display
_currentPath->setEditString(_node.getPath().toString(Common::Path::kNativeSeparator));
// We memorize the last visited path.
// Don't memorize a path that is not a directory
if (_node.isDirectory()) {
ConfMan.setPath("browser_lastpath", _node.getPath());
}
// Read in the data from the file system
if (!_node.getChildren(_nodeContent, Common::FSNode::kListAll, _showHidden))
_nodeContent.clear();
else
Common::sort(_nodeContent.begin(), _nodeContent.end());
// Populate the ListWidget
Common::U32StringArray list;
Common::U32String color = ListWidget::getThemeColor(ThemeEngine::kFontColorNormal);
for (auto &node : _nodeContent) {
if (_isDirBrowser) {
if (node.isDirectory())
color = ListWidget::getThemeColor(ThemeEngine::kFontColorNormal);
else
color = ListWidget::getThemeColor(ThemeEngine::kFontColorAlternate);
}
if (node.isDirectory())
list.push_back(color + ListWidget::escapeString(Common::U32String(node.getName()) + "/"));
else
list.push_back(color + ListWidget::escapeString(Common::U32String(node.getName())));
}
_fileList->setList(list);
_fileList->scrollTo(0);
// Finally, redraw
g_gui.scheduleTopDialogRedraw();
}
} // End of namespace GUI

64
gui/browser.h Normal file
View File

@@ -0,0 +1,64 @@
/* 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/>.
*
*/
#ifndef BROWSER_DIALOG_H
#define BROWSER_DIALOG_H
#include "gui/dialog.h"
#include "common/fs.h"
#include "common/str.h"
namespace GUI {
class ListWidget;
class EditTextWidget;
class CheckboxWidget;
class CommandSender;
class BrowserDialog : public Dialog {
public:
BrowserDialog(const Common::U32String &title, bool dirBrowser);
int runModal() override;
void open() override;
void handleCommand(CommandSender *sender, uint32 cmd, uint32 data) override;
const Common::FSNode &getResult() { return _choice; }
protected:
ListWidget *_fileList;
EditTextWidget *_currentPath;
Common::FSNode _node;
Common::FSList _nodeContent;
bool _showHidden;
CheckboxWidget *_showHiddenWidget;
Common::FSNode _choice;
Common::U32String _title;
bool _isDirBrowser;
void updateListing();
};
} // End of namespace GUI
#endif

75
gui/chooser.cpp Normal file
View File

@@ -0,0 +1,75 @@
/* 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/chooser.h"
#include "gui/widget.h"
#include "gui/widgets/list.h"
namespace GUI {
enum {
kChooseCmd = 'Chos'
};
ChooserDialog::ChooserDialog(const Common::U32String &title, Common::String dialogId)
: Dialog(dialogId) {
// Headline
new StaticTextWidget(this, dialogId + ".Headline", title);
// Add choice list
_list = new ListWidget(this, dialogId + ".List");
_list->setNumberingMode(kListNumberingOff);
_list->setEditable(false);
// Buttons
new ButtonWidget(this, dialogId + ".Cancel", _("Cancel"), Common::U32String(), kCloseCmd);
_chooseButton = new ButtonWidget(this, dialogId + ".Choose", _("Choose"), Common::U32String(), kChooseCmd);
_chooseButton->setEnabled(false);
}
void ChooserDialog::setList(const Common::U32StringArray &list) {
_list->setList(list);
}
void ChooserDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {
int item = _list->getSelected();
switch (cmd) {
case kChooseCmd:
case kListItemActivatedCmd:
case kListItemDoubleClickedCmd:
_list->endEditMode();
setResult(item);
close();
break;
case kListSelectionChangedCmd:
_chooseButton->setEnabled(item >= 0);
break;
case kCloseCmd:
setResult(-1);
// Fall through
default:
Dialog::handleCommand(sender, cmd, data);
}
}
} // End of namespace GUI

54
gui/chooser.h Normal file
View File

@@ -0,0 +1,54 @@
/* 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/>.
*
*/
#ifndef CHOOSER_DIALOG_H
#define CHOOSER_DIALOG_H
#include "common/array.h"
#include "common/str.h"
#include "gui/dialog.h"
namespace GUI {
class ButtonWidget;
class CommandSender;
class ListWidget;
/*
* A dialog that allows the user to choose between a selection of items
*/
class ChooserDialog : public Dialog {
protected:
ListWidget *_list;
ButtonWidget *_chooseButton;
public:
ChooserDialog(const Common::U32String &title, Common::String dialogId = "Browser");
void setList(const Common::U32StringArray &list);
void handleCommand(CommandSender *sender, uint32 cmd, uint32 data) override;
};
} // End of namespace GUI
#endif

View File

@@ -0,0 +1,774 @@
/* 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 "gui/cloudconnectionwizard.h"
#include "backends/cloud/cloudmanager.h"
#ifdef USE_SDL_NET
#include "backends/networking/sdl_net/localwebserver.h"
#endif // USE_SDL_NET
#ifdef EMSCRIPTEN
#include "backends/platform/sdl/emscripten/emscripten.h"
#endif
#include "common/formats/json.h"
#include "common/file.h"
#include "common/memstream.h"
#include "common/tokenizer.h"
#include "common/translation.h"
#include "gui/browser.h"
#include "gui/message.h"
#include "gui/gui-manager.h"
#include "gui/widget.h"
#include "gui/widgets/edittext.h"
#include "gui/widgets/scrollcontainer.h"
namespace GUI {
enum {
kCloudConnectionWizardQuickModeButtonCmd = 'WQMb',
kCloudConnectionWizardManualModeButtonCmd = 'WMMb',
kCloudConnectionWizardBackButtonCmd = 'WSBb',
kCloudConnectionWizardNextButtonCmd = 'WSNb',
kCloudConnectionWizardRunServerButtonCmd = 'WRSb',
kCloudConnectionWizardOpenUrlStorageCmd = 'WOUb',
kCloudConnectionWizardReflowCmd = 'WRFb',
kCloudConnectionWizardPasteCodeCmd = 'WPCb',
kCloudConnectionWizardLoadCodeCmd = 'WLCd',
};
CloudConnectionWizard::CloudConnectionWizard() :
Dialog("GlobalOptions_Cloud_ConnectionWizard"),
_currentStep(Step::NONE), _switchToSuccess(false), _switchToFailure(false),
_connecting(false) {
_backgroundType = GUI::ThemeEngine::kDialogBackgroundPlain;
_headlineLabel = new StaticTextWidget(this, "GlobalOptions_Cloud_ConnectionWizard.Headline", Common::U32String());
_closeButton = new ButtonWidget(this, "GlobalOptions_Cloud_ConnectionWizard.CancelButton", _("Cancel"), Common::U32String(), kCloseCmd);
_prevStepButton = nullptr;
_nextStepButton = nullptr;
_label0 = nullptr;
_label1 = nullptr;
_label2 = nullptr;
_label3 = nullptr;
_button0 = nullptr;
_button1 = nullptr;
_container = nullptr;
_quickModeButton = nullptr;
_quickModeLabel = nullptr;
_manualModeButton = nullptr;
_codeBox = nullptr;
#ifdef USE_SDL_NET
showStep(Step::MODE_SELECT);
#else
showStep(Step::MANUAL_MODE_STEP_1);
#endif
_callback = new Common::Callback<CloudConnectionWizard, const Networking::ErrorResponse &>(this, &CloudConnectionWizard::storageConnectionCallback);
}
CloudConnectionWizard::~CloudConnectionWizard() {
#ifdef USE_SDL_NET
LocalServer.setStorageConnectionCallback(nullptr);
#endif // USE_SDL_NET
delete _callback;
}
#ifdef EMSCRIPTEN
// allow browser to pass in JSON tokens
void CloudConnectionWizard::emscriptenCloudConnectionCallback(const Common::String *message ){
showStep(Step::MANUAL_MODE_STEP_2);
if (!message->empty()) {
_codeBox->setEditString(*message);
}
manualModeConnect();
}
#endif
void CloudConnectionWizard::showStep(Step newStep) {
if (newStep == _currentStep)
return;
switch (_currentStep) {
case Step::NONE:
break;
case Step::MODE_SELECT:
hideStepModeSelect();
break;
case Step::QUICK_MODE_STEP_1:
hideStepQuickMode1();
break;
case Step::QUICK_MODE_STEP_2:
hideStepQuickMode2();
break;
case Step::QUICK_MODE_SUCCESS:
hideStepQuickModeSuccess();
break;
case Step::MANUAL_MODE_STEP_1:
hideStepManualMode1();
break;
case Step::MANUAL_MODE_STEP_2:
hideStepManualMode2();
break;
case Step::MANUAL_MODE_FAILURE:
hideStepManualModeFailure();
break;
case Step::MANUAL_MODE_SUCCESS:
hideStepManualModeSuccess();
break;
}
_currentStep = newStep;
switch (_currentStep) {
case Step::NONE:
break;
case Step::MODE_SELECT:
showStepModeSelect();
break;
case Step::QUICK_MODE_STEP_1:
showStepQuickMode1();
break;
case Step::QUICK_MODE_STEP_2:
showStepQuickMode2();
break;
case Step::QUICK_MODE_SUCCESS:
showStepQuickModeSuccess();
break;
case Step::MANUAL_MODE_STEP_1:
showStepManualMode1();
break;
case Step::MANUAL_MODE_STEP_2:
showStepManualMode2();
break;
case Step::MANUAL_MODE_FAILURE:
showStepManualModeFailure();
break;
case Step::MANUAL_MODE_SUCCESS:
showStepManualModeSuccess();
break;
}
reflowLayout();
g_gui.scheduleTopDialogRedraw();
}
// mode select
void CloudConnectionWizard::showStepModeSelect() {
_headlineLabel->setLabel(_("Cloud Connection Wizard"));
showContainer("ConnectionWizard_ModeSelect");
_quickModeButton = new ButtonWidget(_container, "ConnectionWizard_ModeSelect.QuickModeButton", _("Quick mode"), Common::U32String(), kCloudConnectionWizardQuickModeButtonCmd);
_manualModeButton = new ButtonWidget(_container, "ConnectionWizard_ModeSelect.ManualModeButton", _("Manual mode"), Common::U32String(), kCloudConnectionWizardManualModeButtonCmd);
#ifdef USE_SDL_NET
_quickModeLabel = new StaticTextWidget(_container, "ConnectionWizard_ModeSelect.QuickModeHint", _("Will ask you to run the Local Webserver"));
#else
_quickModeLabel = new StaticTextWidget(_container, "ConnectionWizard_ModeSelect.QuickModeHint", _("Requires the Local Webserver feature"), Common::U32String(), ThemeEngine::kFontStyleNormal);
_quickModeLabel->setEnabled(false);
_quickModeButton->setEnabled(false);
#endif // USE_SDL_NET
}
void CloudConnectionWizard::hideStepModeSelect() {
hideContainer();
removeWidgetChecked(_quickModeButton);
removeWidgetChecked(_manualModeButton);
removeWidgetChecked(_quickModeLabel);
}
// quick mode
void CloudConnectionWizard::showStepQuickMode1() {
_headlineLabel->setLabel(_("Quick Mode: Step 1"));
showContainer("ConnectionWizard_QuickModeStep1");
showBackButton();
showNextButton();
Common::U32StringTokenizer tok(_("In this mode, the Local Webserver must be running,\n"
"so your browser can forward data to ScummVM"), "\n");
Common::U32StringArray labels = tok.split();
if (labels.size() < 2)
labels.push_back(Common::U32String());
_label0 = new StaticTextWidget(_container, "ConnectionWizard_QuickModeStep1.Line1", labels[0]);
_label1 = new StaticTextWidget(_container, "ConnectionWizard_QuickModeStep1.Line2", labels[1]);
_button0 = new ButtonWidget(_container, "ConnectionWizard_QuickModeStep1.RunServerButton", Common::U32String(), Common::U32String(), kCloudConnectionWizardRunServerButtonCmd);
_label2 = new StaticTextWidget(_container, "ConnectionWizard_QuickModeStep1.ServerInfoLabel", Common::U32String());
refreshStepQuickMode1();
}
void CloudConnectionWizard::refreshStepQuickMode1(bool displayAsStopped) {
bool serverIsRunning = false;
#ifdef USE_SDL_NET
serverIsRunning = LocalServer.isRunning();
#endif // USE_SDL_NET
if (displayAsStopped)
serverIsRunning = false;
if (_nextStepButton)
_nextStepButton->setEnabled(serverIsRunning);
if (_button0) {
_button0->setLabel(serverIsRunning ? _("Stop server") : _("Run server"));
_button0->setTooltip(serverIsRunning ? _("Stop local webserver") : _("Run local webserver"));
}
if (_label2) {
Common::U32String address;
#ifdef USE_SDL_NET
address = LocalServer.getAddress();
#endif // USE_SDL_NET
_label2->setLabel(serverIsRunning ? address : _("Not running"));
}
}
void CloudConnectionWizard::hideStepQuickMode1() {
hideContainer();
hideBackButton();
hideNextButton();
removeWidgetChecked(_label0);
removeWidgetChecked(_label1);
removeWidgetChecked(_button0);
removeWidgetChecked(_label2);
}
void CloudConnectionWizard::showStepQuickMode2() {
_headlineLabel->setLabel(_("Quick Mode: Step 2"));
showContainer("ConnectionWizard_QuickModeStep2");
showBackButton();
_label0 = new StaticTextWidget(_container, "ConnectionWizard_QuickModeStep2.Line1", _("Now, open this link in your browser:"));
_button0 = new ButtonWidget(_container, "ConnectionWizard_QuickModeStep2.OpenLinkButton", Common::U32String("https://cloud.scummvm.org/"), _("Open URL"), kCloudConnectionWizardOpenUrlStorageCmd);
Common::U32StringTokenizer tok(_("It will automatically pass the data to ScummVM,\n"
"and warn you should there be any errors."), "\n");
Common::U32StringArray labels = tok.split();
if (labels.size() < 2)
labels.push_back(Common::U32String());
_label1 = new StaticTextWidget(_container, "ConnectionWizard_QuickModeStep2.Line2", labels[0]);
_label2 = new StaticTextWidget(_container, "ConnectionWizard_QuickModeStep2.Line3", labels[1]);
_label3 = new StaticTextWidget(_container, "ConnectionWizard_QuickModeStep2.Line4", Common::U32String(), Common::U32String(), ThemeEngine::kFontStyleNormal);
#ifdef USE_SDL_NET
_label3->setLabel(_("Local Webserver address: ") + Common::U32String(LocalServer.getAddress()));
_label3->setEnabled(false);
#endif // USE_SDL_NET
}
void CloudConnectionWizard::hideStepQuickMode2() {
hideContainer();
hideBackButton();
removeWidgetChecked(_label0);
removeWidgetChecked(_button0);
removeWidgetChecked(_label1);
removeWidgetChecked(_label2);
removeWidgetChecked(_label3);
}
void CloudConnectionWizard::showStepQuickModeSuccess() {
_headlineLabel->setLabel(_("Quick Mode: Success"));
showContainer("ConnectionWizard_Success");
_closeButton->setVisible(false);
_label0 = new StaticTextWidget(_container, "ConnectionWizard_Success.Line1", _("Your cloud storage has been connected!"));
_button0 = new ButtonWidget(this, "GlobalOptions_Cloud_ConnectionWizard.FinishButton", _("Finish"), Common::U32String(), kCloseCmd);
}
void CloudConnectionWizard::hideStepQuickModeSuccess() {
hideContainer();
_closeButton->setVisible(true);
removeWidgetChecked(_label0);
removeWidgetChecked(_button0);
}
// manual mode
void CloudConnectionWizard::showStepManualMode1() {
#ifdef EMSCRIPTEN
OSystem_Emscripten *emscripten_g_system = dynamic_cast<OSystem_Emscripten*>(g_system);
emscripten_g_system->setCloudConnectionCallback(new Common::Callback<CloudConnectionWizard, const Common::String *>(this, &CloudConnectionWizard::emscriptenCloudConnectionCallback));
#endif
_headlineLabel->setLabel(_("Manual Mode: Step 1"));
showContainer("ConnectionWizard_ManualModeStep1");
#ifdef USE_SDL_NET
showBackButton();
#endif
showNextButton();
_label0 = new StaticTextWidget(_container, "ConnectionWizard_ManualModeStep1.Line1", _("Open this link in your browser:"));
_button0 = new ButtonWidget(_container, "ConnectionWizard_ManualModeStep1.OpenLinkButton", Common::U32String("https://cloud.scummvm.org/"), _("Open URL"), kCloudConnectionWizardOpenUrlStorageCmd);
Common::U32StringTokenizer tok(_("When it fails to pass the JSON code to ScummVM,\n"
"find it on the Troubleshooting section of the page,\n"
"and go to the next step here."), "\n");
Common::U32StringArray labels = tok.split();
if (labels.size() < 2)
labels.push_back(Common::U32String());
if (labels.size() < 3)
labels.push_back(Common::U32String());
_label1 = new StaticTextWidget(_container, "ConnectionWizard_ManualModeStep1.Line2", labels[0]);
_label2 = new StaticTextWidget(_container, "ConnectionWizard_ManualModeStep1.Line3", labels[1]);
_label3 = new StaticTextWidget(_container, "ConnectionWizard_ManualModeStep1.Line4", labels[2]);
}
void CloudConnectionWizard::hideStepManualMode1() {
hideContainer();
hideBackButton();
hideNextButton();
removeWidgetChecked(_label0);
removeWidgetChecked(_button0);
removeWidgetChecked(_label1);
removeWidgetChecked(_label2);
removeWidgetChecked(_label3);
}
void CloudConnectionWizard::showStepManualMode2() {
_headlineLabel->setLabel(_("Manual Mode: Step 2"));
showContainer("ConnectionWizard_ManualModeStep2");
showBackButton();
showNextButton();
_label0 = new StaticTextWidget(_container, "ConnectionWizard_ManualModeStep2.Line1", _("Copy the JSON code from the browser here and press Next:"));
_codeBox = new EditTextWidget(_container, "ConnectionWizard_ManualModeStep2.CodeBox", Common::U32String(), Common::U32String(), 0, 0, ThemeEngine::kFontStyleConsole);
#ifndef EMSCRIPTEN
_button0 = new ButtonWidget(_container, "ConnectionWizard_ManualModeStep2.PasteButton", _("Paste"), _("Paste code from clipboard"), kCloudConnectionWizardPasteCodeCmd);
_button1 = new ButtonWidget(_container, "ConnectionWizard_ManualModeStep2.LoadButton", _("Load"), _("Load code from file"), kCloudConnectionWizardLoadCodeCmd);
#endif
_label1 = new StaticTextWidget(_container, "ConnectionWizard_ManualModeStep2.Line2", Common::U32String());
}
void CloudConnectionWizard::hideStepManualMode2() {
#ifdef EMSCRIPTEN
OSystem_Emscripten *emscripten_g_system = dynamic_cast<OSystem_Emscripten*>(g_system);
emscripten_g_system->setCloudConnectionCallback(nullptr);
#endif
hideContainer();
_closeButton->setEnabled(true);
hideBackButton();
hideNextButton();
removeWidgetChecked(_label0);
removeWidgetChecked(_codeBox);
removeWidgetChecked(_button0);
removeWidgetChecked(_button1);
removeWidgetChecked(_label1);
}
void CloudConnectionWizard::showStepManualModeFailure() {
_headlineLabel->setLabel(_("Manual Mode: Something went wrong"));
showContainer("ConnectionWizard_Failure");
showBackButton();
_label0 = new StaticTextWidget(_container, "ConnectionWizard_Failure.Line1", _("Cloud storage was not connected."));
_label1 = new StaticTextWidget(_container, "ConnectionWizard_Failure.Line2", _("Make sure the JSON code was copied correctly and retry."));
_label2 = new StaticTextWidget(_container, "ConnectionWizard_Failure.Line3", _("If that doesn't work, try again from the beginning."));
_label3 = new StaticTextWidget(_container, "ConnectionWizard_Failure.Line4", _("Error message: ") + _errorMessage, Common::U32String(), ThemeEngine::kFontStyleNormal);
_label3->setEnabled(false);
}
void CloudConnectionWizard::hideStepManualModeFailure() {
hideContainer();
hideBackButton();
removeWidgetChecked(_label0);
removeWidgetChecked(_label1);
removeWidgetChecked(_label2);
removeWidgetChecked(_label3);
}
void CloudConnectionWizard::showStepManualModeSuccess() {
_headlineLabel->setLabel(_("Manual Mode: Success"));
showContainer("ConnectionWizard_Success");
_closeButton->setVisible(false);
_label0 = new StaticTextWidget(_container, "ConnectionWizard_Success.Line1", _("Your cloud storage has been connected!"));
_button0 = new ButtonWidget(this, "GlobalOptions_Cloud_ConnectionWizard.FinishButton", _("Finish"), Common::U32String(), kCloseCmd);
}
void CloudConnectionWizard::hideStepManualModeSuccess() {
hideContainer();
_closeButton->setVisible(true);
removeWidgetChecked(_label0);
removeWidgetChecked(_button0);
}
// utils
void CloudConnectionWizard::showContainer(const Common::String &dialogName) {
_container = new ScrollContainerWidget(this, "GlobalOptions_Cloud_ConnectionWizard.Container", dialogName, kCloudConnectionWizardReflowCmd);
_container->setTarget(this);
_container->setBackgroundType(ThemeEngine::kWidgetBackgroundNo);
}
void CloudConnectionWizard::hideContainer() {
removeWidgetChecked(_container);
}
void CloudConnectionWizard::showBackButton() {
_prevStepButton = new ButtonWidget(this, "GlobalOptions_Cloud_ConnectionWizard.PrevButton", _("Back"), Common::U32String(), kCloudConnectionWizardBackButtonCmd);
}
void CloudConnectionWizard::hideBackButton() {
removeWidgetChecked(_prevStepButton);
}
void CloudConnectionWizard::showNextButton() {
_nextStepButton = new ButtonWidget(this, "GlobalOptions_Cloud_ConnectionWizard.NextButton", _("Next"), Common::U32String(), kCloudConnectionWizardNextButtonCmd);
}
void CloudConnectionWizard::hideNextButton() {
removeWidgetChecked(_nextStepButton);
}
void CloudConnectionWizard::removeWidgetChecked(ScrollContainerWidget *&widget) {
if (widget) {
removeWidget(widget);
widget = nullptr;
}
}
void CloudConnectionWizard::removeWidgetChecked(ButtonWidget *&widget) {
if (widget) {
removeWidget(widget);
widget = nullptr;
}
}
void CloudConnectionWizard::removeWidgetChecked(StaticTextWidget *&widget) {
if (widget) {
removeWidget(widget);
widget = nullptr;
}
}
void CloudConnectionWizard::removeWidgetChecked(EditTextWidget *&widget) {
if (widget) {
removeWidget(widget);
widget = nullptr;
}
}
// logic
void CloudConnectionWizard::storageConnectionCallback(const Networking::ErrorResponse &response) {
if (response.failed || response.interrupted) {
return;
}
_switchToSuccess = true;
}
void CloudConnectionWizard::manualModeConnect() {
if (_connecting)
return;
if (_label1)
_label1->setLabel(Common::U32String());
// get the code entered
Common::String code;
if (_codeBox)
code = _codeBox->getEditString().encode();
if (code.size() == 0)
return;
// warn about other Storage working
if (CloudMan.isWorking()) {
bool cancel = true;
MessageDialog alert(_("Another Storage is working right now. Do you want to interrupt it?"), _("Yes"), _("No"));
if (alert.runModal() == GUI::kMessageOK) {
if (CloudMan.isDownloading())
CloudMan.cancelDownload();
if (CloudMan.isSyncing())
CloudMan.cancelSync();
// I believe it still would return `true` here, but just in case
if (CloudMan.isWorking()) {
MessageDialog alert2(_("Wait until current Storage finishes and try again."));
alert2.runModal();
} else {
cancel = false;
}
}
if (cancel) {
return;
}
}
// parse JSON and display message if failed
Common::JSONValue *json = Common::JSON::parse(code);
// pass JSON to the manager
_connecting = true;
Networking::ErrorCallback callback = new Common::Callback<CloudConnectionWizard, const Networking::ErrorResponse &>(this, &CloudConnectionWizard::manualModeStorageConnectionCallback);
Networking::JsonResponse jsonResponse(nullptr, json);
if (!CloudMan.connectStorage(jsonResponse, callback)) { // no "storage" in JSON (or invalid one)
_connecting = false;
delete json;
delete callback;
if (_label1)
// I18N: JSON is name of the format, this message is displayed if user entered something incorrect to the text field
_label1->setLabel(_("JSON code contents are malformed."));
return;
}
// disable UI
if (_codeBox)
_codeBox->setEnabled(false);
if (_button0)
_button0->setEnabled(false);
if (_closeButton)
_closeButton->setEnabled(false);
if (_prevStepButton)
_prevStepButton->setEnabled(false);
if (_nextStepButton)
_nextStepButton->setEnabled(false);
}
void CloudConnectionWizard::manualModeStorageConnectionCallback(const Networking::ErrorResponse &response) {
if (response.failed || response.interrupted) {
if (response.failed) {
const char *knownErrorMessages[] = {
_s("OK"),
_s("Incorrect JSON.") // see "cloud/basestorage.cpp"
};
(void)knownErrorMessages;
_errorMessage = _(response.response.c_str());
} else {
// I18N: error message displayed on 'Manual Mode: Failure' step of 'Cloud Connection Wizard', describing that storage connection process was interrupted
_errorMessage = _("Interrupted.");
}
_switchToFailure = true;
return;
}
_switchToSuccess = true;
}
// public
int CloudConnectionWizard::runStorageModal(uint32 selectedStorageIndex) {
_selectedStorageIndex = selectedStorageIndex;
return Dialog::runModal();
}
void CloudConnectionWizard::open() {
Dialog::open();
#ifdef USE_SDL_NET
LocalServer.setStorageConnectionCallback(_callback);
#endif // USE_SDL_NET
}
void CloudConnectionWizard::close() {
#ifdef USE_SDL_NET
LocalServer.setStorageConnectionCallback(nullptr);
#endif // USE_SDL_NET
Dialog::close();
}
void CloudConnectionWizard::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {
switch (cmd) {
case kCloudConnectionWizardQuickModeButtonCmd:
showStep(Step::QUICK_MODE_STEP_1);
break;
case kCloudConnectionWizardManualModeButtonCmd:
showStep(Step::MANUAL_MODE_STEP_1);
break;
case kCloudConnectionWizardRunServerButtonCmd:
#ifdef USE_SDL_NET
if (LocalServer.isRunning()) {
LocalServer.stopOnIdle();
refreshStepQuickMode1(true);
} else {
LocalServer.start();
refreshStepQuickMode1();
}
#endif // USE_SDL_NET
break;
case kCloudConnectionWizardOpenUrlStorageCmd: {
Common::String url = "https://cloud.scummvm.org/";
switch (_selectedStorageIndex) {
case Cloud::kStorageDropboxId:
url += "dropbox/271?refresh_token=true";
break;
case Cloud::kStorageOneDriveId:
url += "onedrive/271";
break;
case Cloud::kStorageGoogleDriveId:
url += "gdrive/271";
break;
case Cloud::kStorageBoxId:
url += "box/271";
break;
default:
break;
}
if (!g_system->openUrl(url)) {
MessageDialog alert(_("Failed to open URL!\nPlease navigate to this page manually."));
alert.runModal();
}
break;
}
case kCloudConnectionWizardPasteCodeCmd:
if (g_system->hasTextInClipboard()) {
Common::U32String message = g_system->getTextFromClipboard();
if (!message.empty()) {
_codeBox->setEditString(message);
}
}
break;
case kCloudConnectionWizardLoadCodeCmd: {
// I18N: JSON is a file format name
BrowserDialog browser(_("Select JSON file copied from scummvm.org site"), false);
if (browser.runModal() > 0) {
// User made his choice...
Common::File codeFile;
if (!codeFile.open(browser.getResult())) {
// I18N: JSON is a file format name
MessageDialog alert(_("Failed to load JSON file"));
alert.runModal();
break;
}
Common::String json = codeFile.readString();
_codeBox->setEditString(json);
}
break;
}
case kCloudConnectionWizardNextButtonCmd:
switch (_currentStep) {
case Step::QUICK_MODE_STEP_1:
showStep(Step::QUICK_MODE_STEP_2);
break;
case Step::MANUAL_MODE_STEP_1:
showStep(Step::MANUAL_MODE_STEP_2);
break;
case Step::MANUAL_MODE_STEP_2:
manualModeConnect();
break;
default:
break;
}
break;
case kCloudConnectionWizardBackButtonCmd:
switch (_currentStep) {
case Step::QUICK_MODE_STEP_1:
case Step::MANUAL_MODE_STEP_1:
showStep(Step::MODE_SELECT);
break;
case Step::QUICK_MODE_STEP_2:
showStep(Step::QUICK_MODE_STEP_1);
break;
case Step::MANUAL_MODE_STEP_2:
showStep(Step::MANUAL_MODE_STEP_1);
break;
case Step::MANUAL_MODE_FAILURE:
showStep(Step::MANUAL_MODE_STEP_2);
break;
default:
break;
}
break;
default:
Dialog::handleCommand(sender, cmd, data);
}
}
void CloudConnectionWizard::handleTickle() {
if (_connecting && _currentStep == Step::MANUAL_MODE_STEP_2) {
bool switched = false;
if (_switchToFailure) {
showStep(Step::MANUAL_MODE_FAILURE);
switched = true;
} else if (_switchToSuccess) {
showStep(Step::MANUAL_MODE_SUCCESS);
switched = true;
}
if (switched) {
_switchToFailure = false;
_switchToSuccess = false;
_connecting = false;
}
}
if (_switchToSuccess) {
showStep(Step::QUICK_MODE_SUCCESS);
_switchToSuccess = false;
}
Dialog::handleTickle();
}
} // End of namespace GUI

144
gui/cloudconnectionwizard.h Normal file
View File

@@ -0,0 +1,144 @@
/* 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/>.
*
*/
#ifndef GUI_CLOUDCONNECTIONWIZARD_H
#define GUI_CLOUDCONNECTIONWIZARD_H
#include "backends/networking/http/request.h"
#include "common/str.h"
#include "common/ustr.h"
#include "gui/dialog.h"
namespace GUI {
class ScrollContainerWidget;
class StaticTextWidget;
class ButtonWidget;
class EditTextWidget;
class CloudConnectionWizard : public Dialog {
enum class Step {
NONE,
MODE_SELECT,
QUICK_MODE_STEP_1,
QUICK_MODE_STEP_2,
QUICK_MODE_SUCCESS,
MANUAL_MODE_STEP_1,
MANUAL_MODE_STEP_2,
MANUAL_MODE_FAILURE,
MANUAL_MODE_SUCCESS
};
// wizard flow
Step _currentStep;
bool _switchToSuccess;
bool _switchToFailure;
// state
Networking::ErrorCallback _callback;
bool _connecting;
Common::U32String _errorMessage;
uint32 _selectedStorageIndex;
// common and generic widgets
StaticTextWidget *_headlineLabel;
ButtonWidget *_closeButton;
ButtonWidget *_prevStepButton;
ButtonWidget *_nextStepButton;
StaticTextWidget *_label0;
StaticTextWidget *_label1;
StaticTextWidget *_label2;
StaticTextWidget *_label3;
ButtonWidget *_button0;
ButtonWidget *_button1;
// specific widgets
ScrollContainerWidget *_container;
ButtonWidget *_quickModeButton;
StaticTextWidget *_quickModeLabel;
ButtonWidget *_manualModeButton;
EditTextWidget *_codeBox;
// wizard flow
void showStep(Step newStep);
void showStepModeSelect();
void hideStepModeSelect();
void showStepQuickMode1();
void refreshStepQuickMode1(bool displayAsStopped = false);
void hideStepQuickMode1();
void showStepQuickMode2();
void hideStepQuickMode2();
void showStepQuickModeSuccess();
void hideStepQuickModeSuccess();
void showStepManualMode1();
void hideStepManualMode1();
void showStepManualMode2();
void hideStepManualMode2();
void showStepManualModeFailure();
void hideStepManualModeFailure();
void showStepManualModeSuccess();
void hideStepManualModeSuccess();
// widgets utils
void showContainer(const Common::String &dialogName);
void hideContainer();
void showBackButton();
void hideBackButton();
void showNextButton();
void hideNextButton();
void removeWidgetChecked(ScrollContainerWidget *&widget);
void removeWidgetChecked(ButtonWidget *&widget);
void removeWidgetChecked(StaticTextWidget *&widget);
void removeWidgetChecked(EditTextWidget *&widget);
// logic
void storageConnectionCallback(const Networking::ErrorResponse &response);
void manualModeConnect();
void manualModeStorageConnectionCallback(const Networking::ErrorResponse &response);
#ifdef EMSCRIPTEN
void emscriptenCloudConnectionCallback(const Common::String *message);
#endif
public:
CloudConnectionWizard();
~CloudConnectionWizard() override;
int runStorageModal(uint32 selectedStorageIndex);
void open() override;
void close() override;
void handleCommand(CommandSender *sender, uint32 cmd, uint32 data) override;
void handleTickle() override;
};
} // End of namespace GUI
#endif

978
gui/console.cpp Normal file
View File

@@ -0,0 +1,978 @@
/* 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 "gui/console.h"
#include "common/savefile.h"
#include "gui/widgets/scrollbar.h"
#include "gui/ThemeEval.h"
#include "gui/gui-manager.h"
#include "base/version.h"
#include "common/system.h"
#include "common/util.h"
#include "graphics/fontman.h"
namespace GUI {
#define kConsoleCharWidth (_font->getCharWidth('M'))
#define kConsoleLineHeight (_font->getFontHeight())
#define HISTORY_FILENAME "scummvm-history.txt"
enum {
kConsoleSlideDownDuration = 200 // Time in milliseconds
};
#define PROMPT ") "
/* TODO:
* - it is very inefficient to redraw the full thingy when just one char is added/removed.
* Instead, we could just copy the GFX of the blank console (i.e. after the transparent
* background is drawn, before any text is drawn). Then using that, it becomes trivial
* to erase a single character, do scrolling etc.
* - a *lot* of others things, this code is in no way complete and heavily under progress
*/
ConsoleDialog::ConsoleDialog(float widthPercent, float heightPercent)
: Dialog(0, 0, 1, 1),
_widthPercent(widthPercent), _heightPercent(heightPercent) {
// Reset the line buffer
memset(_buffer, ' ', kBufferSize);
// Dummy
_scrollBar = new ScrollBarWidget(this, 0, 0, 5, 10);
_scrollBar->setTarget(this);
init();
_currentPos = 0;
_scrollLine = _linesPerPage - 1;
_firstLineInBuffer = 0;
_caretVisible = false;
_caretTime = 0;
_selectionTime = 0;
_slideMode = kNoSlideMode;
_slideTime = 0;
_promptStartPos = _promptEndPos = -1;
// Init callback
_callbackProc = nullptr;
_callbackRefCon = nullptr;
// Init History
_historyIndex = 0;
_historyLine = 0;
_historySize = 0;
// Display greetings & prompt
print(gScummVMFullVersion);
print("\nConsole is ready\n");
}
ConsoleDialog::~ConsoleDialog() {
saveHistory();
}
void ConsoleDialog::init() {
const Common::Rect safeArea = g_system->getSafeOverlayArea();
_font = &g_gui.getFont(ThemeEngine::kFontStyleConsole);
_leftPadding = g_gui.xmlEval()->getVar("Globals.Console.Padding.Left", 0);
_rightPadding = g_gui.xmlEval()->getVar("Globals.Console.Padding.Right", 0);
_topPadding = MAX(g_gui.xmlEval()->getVar("Globals.Console.Padding.Top", 0), (int)safeArea.top);
_bottomPadding = g_gui.xmlEval()->getVar("Globals.Console.Padding.Bottom", 0);
// Calculate the real width/height (rounded to char/line multiples)
_w = (uint16)(_widthPercent * safeArea.width());
_h = (uint16)((_heightPercent * safeArea.height() - 2) / kConsoleLineHeight);
_w = _w - _w / 20;
_h = _h * kConsoleLineHeight + 2 + safeArea.top;
_x = MAX(_w / 40, (int)safeArea.left);
// Set scrollbar dimensions
int scrollBarWidth = g_gui.xmlEval()->getVar("Globals.Scrollbar.Width", 0);
_scrollBar->resize(_w - scrollBarWidth - 1, 0, scrollBarWidth, _h, false);
_pageWidth = (_w - scrollBarWidth - 2 - _leftPadding - _topPadding - scrollBarWidth) / kConsoleCharWidth;
_linesPerPage = (_h - 2 - _topPadding - _bottomPadding) / kConsoleLineHeight;
_linesInBuffer = kBufferSize / kCharsPerLine;
_isDragging = false;
_selBegin = -1;
_selEnd = -1;
_scrollDirection = 0;
resetPrompt();
}
void ConsoleDialog::setPrompt(Common::String prompt) {
_prompt = prompt;
}
void ConsoleDialog::resetPrompt() {
_prompt = PROMPT;
}
void ConsoleDialog::clearBuffer() {
// Reset the line buffer.
memset(_buffer, ' ', kBufferSize);
// Along with a few key vars.
_currentPos = 0;
_scrollLine = _linesPerPage - 1;
_firstLineInBuffer = 0;
updateScrollBuffer();
}
void ConsoleDialog::slideUpAndClose() {
if (_slideMode == kNoSlideMode) {
_slideTime = g_system->getMillis();
_slideMode = kUpSlideMode;
}
}
void ConsoleDialog::open() {
// TODO: find a new way to do this
// Initiate sliding the console down. We do a very simple trick to achieve
// this effect: we simply move the console dialog just above (outside) the
// visible screen area, then shift it down in handleTickle() over a
// certain period of time.
const Common::Rect safeArea = g_system->getSafeOverlayArea();
// Calculate the real width/height (rounded to char/line multiples)
uint16 w = (uint16)(_widthPercent * safeArea.width());
uint16 h = (uint16)((_heightPercent * safeArea.height() - 2) / kConsoleLineHeight);
h = h * kConsoleLineHeight + 2 + safeArea.top;
w = w - w / 20;
if (_w != w || _h != h)
init();
_y = -_h;
_slideTime = g_system->getMillis();
_slideMode = kDownSlideMode;
Dialog::open();
if ((_promptStartPos == -1) || (_currentPos > _promptEndPos)) {
// we print a prompt, if this is the first time we are called or if the
// engine wrote onto us since the last call
print(_prompt.c_str());
_promptStartPos = _promptEndPos = _currentPos;
}
if (_historySize == 0) {
loadHistory();
}
}
void ConsoleDialog::close() {
Dialog::close();
}
void ConsoleDialog::drawDialog(DrawLayer layerToDraw) {
Dialog::drawDialog(layerToDraw);
for (int line = 0; line < _linesPerPage; line++)
drawLine(line);
}
void ConsoleDialog::drawLine(int line) {
int x = _x + 1 + _leftPadding;
int start = _scrollLine - _linesPerPage + 1;
int y = _y + 2 + _topPadding;
int limit = MIN(_pageWidth, (int)kCharsPerLine);
y += line * kConsoleLineHeight;
for (int column = 0; column < limit; column++) {
#if 0
int l = (start + line) % _linesInBuffer;
byte c = buffer(l * kCharsPerLine + column);
#else
int idx = (start + line) * kCharsPerLine + column;
byte c = buffer(idx);
#endif
if (idx >= MIN(_selBegin, _selEnd) && idx < MAX(_selBegin, _selEnd))
g_gui.theme()->drawChar(Common::Rect(x, y, x + kConsoleCharWidth, y + kConsoleLineHeight), c, _font, ThemeEngine::kFontColorNormal, ThemeEngine::kTextInversionFocus);
else
g_gui.theme()->drawChar(Common::Rect(x, y, x + kConsoleCharWidth, y + kConsoleLineHeight), c, _font);
x += kConsoleCharWidth;
}
}
void ConsoleDialog::reflowLayout() {
init();
_scrollLine = _promptEndPos / kCharsPerLine;
if (_scrollLine < _linesPerPage - 1)
_scrollLine = _linesPerPage - 1;
updateScrollBuffer();
Dialog::reflowLayout();
g_gui.scheduleTopDialogRedraw();
}
void ConsoleDialog::handleTickle() {
uint32 time = g_system->getMillis();
if (_caretTime < time) {
_caretTime = time + kCaretBlinkTime;
drawCaret(_caretVisible);
}
if (_selectionTime < time) {
_selectionTime += kDraggingTime;
if (_isDragging && _scrollDirection != 0) {
handleMouseWheel(0, 0, -_scrollDirection);
_selEnd -= kCharsPerLine * _scrollDirection;
if (clampSelection(_selEnd))
// Scrolled as far as possible. Don't re-execute this block
// unnecessarily.
_scrollDirection = 0;
}
}
// Perform the "slide animation".
if (_slideMode != kNoSlideMode) {
const float tmp = (float)(g_system->getMillis() - _slideTime) / kConsoleSlideDownDuration;
if (_slideMode == kUpSlideMode) {
_y = (int)(_h * (0.0 - tmp));
} else {
_y = (int)(_h * (tmp - 1.0));
}
if (_slideMode == kDownSlideMode && _y > 0) {
// End the slide
_slideMode = kNoSlideMode;
_y = 0;
g_gui.scheduleTopDialogRedraw();
} else if (_slideMode == kUpSlideMode && _y <= -_h) {
// End the slide
//_slideMode = kNoSlideMode;
close();
} else
g_gui.scheduleFullRedraw();
}
_scrollBar->handleTickle();
}
void ConsoleDialog::handleMouseWheel(int x, int y, int direction) {
_scrollBar->handleMouseWheel(x, y, direction);
}
Common::String ConsoleDialog::getUserInput() {
assert(_promptEndPos >= _promptStartPos);
int len = _promptEndPos - _promptStartPos;
// Copy the user input to str
Common::String str;
for (int i = 0; i < len; i++)
str.insertChar(buffer(_promptStartPos + i), i);
return str;
}
void ConsoleDialog::handleKeyDown(Common::KeyState state) {
if (_slideMode != kNoSlideMode)
return;
switch (state.keycode) {
case Common::KEYCODE_RETURN:
case Common::KEYCODE_KP_ENTER: {
if (_caretVisible)
drawCaret(true);
nextLine();
bool keepRunning = true;
Common::String userInput = getUserInput();
if (!userInput.empty()) {
// Add the input to the history
addToHistory(userInput);
// Pass it to the input callback, if any
if (_callbackProc)
keepRunning = (*_callbackProc)(this, userInput.c_str(), _callbackRefCon);
}
print(_prompt.c_str());
_promptStartPos = _promptEndPos = _currentPos;
g_gui.scheduleTopDialogRedraw();
if (!keepRunning)
slideUpAndClose();
break;
}
case Common::KEYCODE_ESCAPE:
slideUpAndClose();
break;
case Common::KEYCODE_BACKSPACE:
if (_caretVisible)
drawCaret(true);
if (_currentPos > _promptStartPos) {
_currentPos--;
killChar();
}
scrollToCurrent();
drawLine(pos2line(_currentPos));
break;
case Common::KEYCODE_TAB: {
if (_completionCallbackProc) {
int len = _currentPos - _promptStartPos;
assert(len >= 0);
char *str = new char[len + 1];
// Copy the user input to str
for (int i = 0; i < len; i++)
str[i] = buffer(_promptStartPos + i);
str[len] = '\0';
Common::String completion;
if ((*_completionCallbackProc)(this, str, completion, _callbackRefCon)) {
if (_caretVisible)
drawCaret(true);
insertIntoPrompt(completion.c_str());
scrollToCurrent();
drawLine(pos2line(_currentPos));
}
delete[] str;
}
break;
}
// Keypad & special keys
// - if num lock is set, we always go to the default case
// - if num lock is not set, we either fall down to the special key case
// or ignore the key press in case of 0 (INSERT) or 5
case Common::KEYCODE_KP0:
case Common::KEYCODE_KP5:
if (state.flags & Common::KBD_NUM)
defaultKeyDownHandler(state);
break;
case Common::KEYCODE_KP_PERIOD:
if (state.flags & Common::KBD_NUM) {
defaultKeyDownHandler(state);
break;
}
// fall through
case Common::KEYCODE_DELETE:
if (_currentPos < _promptEndPos) {
killChar();
drawLine(pos2line(_currentPos));
}
break;
case Common::KEYCODE_KP1:
if (state.flags & Common::KBD_NUM) {
defaultKeyDownHandler(state);
break;
}
// fall through
case Common::KEYCODE_END:
if (state.hasFlags(Common::KBD_SHIFT)) {
_scrollLine = _promptEndPos / kCharsPerLine;
if (_scrollLine < _linesPerPage - 1)
_scrollLine = _linesPerPage - 1;
updateScrollBuffer();
} else {
_currentPos = _promptEndPos;
}
g_gui.scheduleTopDialogRedraw();
break;
case Common::KEYCODE_KP2:
if (state.flags & Common::KBD_NUM) {
defaultKeyDownHandler(state);
break;
}
// fall through
case Common::KEYCODE_DOWN:
historyScroll(-1);
break;
case Common::KEYCODE_KP3:
if (state.flags & Common::KBD_NUM) {
defaultKeyDownHandler(state);
break;
}
// fall through
case Common::KEYCODE_PAGEDOWN:
if (state.hasFlags(Common::KBD_SHIFT)) {
_scrollLine += _linesPerPage - 1;
if (_scrollLine > _promptEndPos / kCharsPerLine) {
_scrollLine = _promptEndPos / kCharsPerLine;
if (_scrollLine < _firstLineInBuffer + _linesPerPage - 1)
_scrollLine = _firstLineInBuffer + _linesPerPage - 1;
}
updateScrollBuffer();
g_gui.scheduleTopDialogRedraw();
}
break;
case Common::KEYCODE_KP4:
if (state.flags & Common::KBD_NUM) {
defaultKeyDownHandler(state);
break;
}
// fall through
case Common::KEYCODE_LEFT:
if (_currentPos > _promptStartPos)
_currentPos--;
drawLine(pos2line(_currentPos));
break;
case Common::KEYCODE_KP6:
if (state.flags & Common::KBD_NUM) {
defaultKeyDownHandler(state);
break;
}
// fall through
case Common::KEYCODE_RIGHT:
if (_currentPos < _promptEndPos)
_currentPos++;
drawLine(pos2line(_currentPos));
break;
case Common::KEYCODE_KP7:
if (state.flags & Common::KBD_NUM) {
defaultKeyDownHandler(state);
break;
}
// fall through
case Common::KEYCODE_HOME:
if (state.hasFlags(Common::KBD_SHIFT)) {
_scrollLine = _firstLineInBuffer + _linesPerPage - 1;
updateScrollBuffer();
} else {
_currentPos = _promptStartPos;
}
g_gui.scheduleTopDialogRedraw();
break;
case Common::KEYCODE_KP8:
if (state.flags & Common::KBD_NUM) {
defaultKeyDownHandler(state);
break;
}
// fall through
case Common::KEYCODE_UP:
historyScroll(+1);
break;
case Common::KEYCODE_KP9:
if (state.flags & Common::KBD_NUM) {
defaultKeyDownHandler(state);
break;
}
// fall through
case Common::KEYCODE_PAGEUP:
if (state.hasFlags(Common::KBD_SHIFT)) {
_scrollLine -= _linesPerPage - 1;
if (_scrollLine < _firstLineInBuffer + _linesPerPage - 1)
_scrollLine = _firstLineInBuffer + _linesPerPage - 1;
updateScrollBuffer();
g_gui.scheduleTopDialogRedraw();
}
break;
default:
defaultKeyDownHandler(state);
}
}
void ConsoleDialog::defaultKeyDownHandler(Common::KeyState &state) {
if (state.hasFlags(Common::KBD_CTRL)) {
specialKeys(state.keycode);
} else if ((state.ascii >= 32 && state.ascii <= 127) || (state.ascii >= 160 && state.ascii <= 255)) {
_selBegin = -1;
_selEnd = -1;
drawDialog(kDrawLayerForeground);
for (int i = _promptEndPos - 1; i >= _currentPos; i--)
buffer(i + 1) = buffer(i);
_promptEndPos++;
printChar((byte)state.ascii);
scrollToCurrent();
}
}
void ConsoleDialog::insertIntoPrompt(const char* str) {
unsigned int l = strlen(str);
for (int i = _promptEndPos - 1; i >= _currentPos; i--)
buffer(i + l) = buffer(i);
for (unsigned int j = 0; j < l; ++j) {
_promptEndPos++;
printCharIntern(str[j]);
}
}
void ConsoleDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {
switch (cmd) {
case kSetPositionCmd:
{
int newPos = (int)data + _linesPerPage - 1 + _firstLineInBuffer;
if (newPos != _scrollLine) {
_scrollLine = newPos;
g_gui.scheduleTopDialogRedraw();
}
}
break;
default:
break;
}
}
void ConsoleDialog::handleOtherEvent(const Common::Event &evt) {
if (evt.type == Common::EVENT_CUSTOM_ENGINE_ACTION_START) {
switch (evt.customType) {
case kActionCopy: {
if (_selBegin == -1 || _selEnd == -1) {
Common::String userInput = getUserInput();
if (!userInput.empty())
g_system->setTextInClipboard(userInput);
} else {
Common::String str;
Common::String whitespaces; // for dealing with trailing whitespaces
for (int i = MIN(_selBegin, _selEnd); i < MAX(_selBegin, _selEnd); i++) {
if (i % kCharsPerLine != kCharsPerLine - 1) {
if (buffer(i) == ' ') {
whitespaces += buffer(i); // to deal with trailing whitespaces
} else {
str += whitespaces;
str += buffer(i);
whitespaces.clear();
}
} else {
whitespaces.clear();
str += "\n";
}
}
g_system->setTextInClipboard(str);
}
} break;
case kActionPaste:
if (g_system->hasTextInClipboard()) {
Common::U32String text = g_system->getTextFromClipboard();
insertIntoPrompt(text.encode().c_str());
scrollToCurrent();
drawLine(pos2line(_currentPos));
_selBegin = -1;
_selEnd = -1;
drawDialog(kDrawLayerForeground);
}
break;
default:
break;
}
}
}
void ConsoleDialog::specialKeys(Common::KeyCode keycode) {
switch (keycode) {
case Common::KEYCODE_a:
_currentPos = _promptStartPos;
g_gui.scheduleTopDialogRedraw();
break;
case Common::KEYCODE_d:
if (_currentPos < _promptEndPos) {
killChar();
g_gui.scheduleTopDialogRedraw();
}
break;
case Common::KEYCODE_e:
_currentPos = _promptEndPos;
g_gui.scheduleTopDialogRedraw();
break;
case Common::KEYCODE_k:
killLine();
g_gui.scheduleTopDialogRedraw();
break;
case Common::KEYCODE_w:
killLastWord();
g_gui.scheduleTopDialogRedraw();
break;
default:
break;
}
}
void ConsoleDialog::killChar() {
for (int i = _currentPos; i < _promptEndPos; i++)
buffer(i) = buffer(i + 1);
if (_promptEndPos > _promptStartPos) {
buffer(_promptEndPos) = ' ';
_promptEndPos--;
}
}
void ConsoleDialog::killLine() {
for (int i = _currentPos; i < _promptEndPos; i++)
buffer(i) = ' ';
_promptEndPos = _currentPos;
}
void ConsoleDialog::killLastWord() {
int cnt = 0;
bool space = true;
while (_currentPos > _promptStartPos) {
if (buffer(_currentPos - 1) == ' ') {
if (!space)
break;
} else
space = false;
_currentPos--;
cnt++;
}
for (int i = _currentPos; i < _promptEndPos; i++)
buffer(i) = buffer(i + cnt);
if (_promptEndPos > _promptStartPos) {
buffer(_promptEndPos) = ' ';
_promptEndPos -= cnt;
}
}
void ConsoleDialog::loadHistory() {
Common::SaveFileManager *saveFileMan = g_system->getSavefileManager();
Common::InSaveFile *loadFile = saveFileMan->openRawFile(HISTORY_FILENAME);
if (!loadFile) {
return;
}
for (int i = 0; i < kHistorySize; ++i) {
const Common::String &line = loadFile->readLine();
if (line.empty()) {
break;
}
addToHistory(line);
}
delete loadFile;
debug("Read %i history entries", _historySize);
}
void ConsoleDialog::saveHistory() {
if (_historySize == 0) {
return;
}
Common::SaveFileManager *saveFileMan = g_system->getSavefileManager();
Common::WriteStream *saveFile = saveFileMan->openForSaving(HISTORY_FILENAME, false);
if (!saveFile) {
warning("Failed to open " HISTORY_FILENAME " for writing");
return;
}
// Saving the history entries in the proper order;
// The most recent entry should be the last to be saved.
// NOTE When the _history table is full, we need to start saving
// from one slot after (in a circular manner) the _historyIndex slot.
// In this case the _historyIndex slot contains the temporary stored user input,
// which we do not want to persist.
// This means that when full, (kHistorySize - 1) entries will be saved.
// When the table is not full, storing always begins from index 0.
int idx = (kHistorySize == _historySize) ? ((_historyIndex + 1) % kHistorySize) : 0;
int entriesWritten = 0;
while (idx != _historyIndex) {
if (!_history[idx].empty()) {
saveFile->writeString(_history[idx]);
saveFile->writeByte('\n');
++entriesWritten;
}
idx = (idx + 1) % kHistorySize;
}
saveFile->finalize();
delete saveFile;
debug("Wrote %i history entries", entriesWritten);
}
void ConsoleDialog::addToHistory(const Common::String &str) {
_history[_historyIndex] = str;
_historyIndex = (_historyIndex + 1) % kHistorySize;
_historyLine = 0;
if (_historySize < kHistorySize)
_historySize++;
}
void ConsoleDialog::historyScroll(int direction) {
if (_historySize == 0)
return;
if (_historyLine == 0 && direction > 0)
// Save current line in history
_history[_historyIndex] = getUserInput();
// Advance to the next line in the history
// NOTE Due to temporarily storing the user input line in the
// _history table, without executing an addToHistory() call,
// that user input line is stored into the slot _historyIndex
// where the next committed command will replace it.
// However, since this slot is still a slot from the _history table,
// when the table is full (kHistorySize entries) and while scrolling
// the history upwards, the user can reach this slot (top most)
// and get their user input again instead of a historic entry.
// We prevent this by stopping upwards navigation one slot earlier,
// when the table is full.
int line = _historyLine + direction;
if ((direction < 0 && line < 0)
|| (direction > 0 && (line > _historySize
|| (_historySize == kHistorySize && line == _historySize))) )
return;
_historyLine = line;
// Hide caret if visible
if (_caretVisible)
drawCaret(true);
// Remove the current user text
_currentPos = _promptStartPos;
killLine();
// ... and ensure the prompt is visible
scrollToCurrent();
// Print the text from the history
int idx;
if (_historyLine > 0)
idx = (_historyIndex - _historyLine + _historySize) % _historySize;
else
idx = _historyIndex;
int length = _history[idx].size();
for (int i = 0; i < length; i++)
printCharIntern(_history[idx][i]);
_promptEndPos = _currentPos;
// Ensure once more the caret is visible (in case of very long history entries)
scrollToCurrent();
g_gui.scheduleTopDialogRedraw();
}
void ConsoleDialog::nextLine() {
int line = _currentPos / kCharsPerLine;
if (line == _scrollLine)
_scrollLine++;
_currentPos = (line + 1) * kCharsPerLine;
updateScrollBuffer();
}
// Call this (at least) when the current line changes or when
// a new line is added
void ConsoleDialog::updateScrollBuffer() {
int lastchar = MAX(_promptEndPos, _currentPos);
int line = lastchar / kCharsPerLine;
int numlines = (line < _linesInBuffer) ? line + 1 : _linesInBuffer;
int firstline = line - numlines + 1;
if (firstline > _firstLineInBuffer) {
// clear old line from buffer
for (int i = lastchar; i < (line+1) * kCharsPerLine; ++i)
buffer(i) = ' ';
_firstLineInBuffer = firstline;
}
_scrollBar->_numEntries = numlines;
_scrollBar->_currentPos = _scrollBar->_numEntries - (line - _scrollLine + _linesPerPage);
_scrollBar->_entriesPerPage = _linesPerPage;
_scrollBar->recalc();
}
int ConsoleDialog::printFormat(int dummy, const char *format, ...) {
va_list argptr;
va_start(argptr, format);
int count = this->vprintFormat(dummy, format, argptr);
va_end (argptr);
return count;
}
int ConsoleDialog::vprintFormat(int dummy, const char *format, va_list argptr) {
Common::String buf = Common::String::vformat(format, argptr);
const int size = buf.size();
print(buf.c_str());
debugN("%s", buf.c_str());
return size;
}
void ConsoleDialog::printChar(int c) {
if (_caretVisible)
drawCaret(true);
printCharIntern(c);
drawLine(pos2line(_currentPos));
}
void ConsoleDialog::printCharIntern(int c) {
if (c == '\n')
nextLine();
else {
buffer(_currentPos) = (char)c;
_currentPos++;
if (_currentPos % kCharsPerLine == _pageWidth) {
nextLine();
updateScrollBuffer();
}
}
}
void ConsoleDialog::print(const char *str) {
if (_caretVisible)
drawCaret(true);
while (*str)
printCharIntern(*str++);
g_gui.scheduleTopDialogRedraw();
}
void ConsoleDialog::drawCaret(bool erase) {
// TODO: use code from EditableWidget::drawCaret here
int line = _currentPos / kCharsPerLine;
int displayLine = line - _scrollLine + _linesPerPage - 1;
// Only draw caret if visible
if (!isVisible() || displayLine < 0 || displayLine >= _linesPerPage) {
_caretVisible = false;
return;
}
int x = _x + 1 + _leftPadding + (_currentPos % kCharsPerLine) * kConsoleCharWidth;
int y = _y + 2 + _topPadding + displayLine * kConsoleLineHeight;
_caretVisible = !erase;
g_gui.theme()->drawCaret(Common::Rect(x, y, x + 1, y + kConsoleLineHeight), erase);
}
void ConsoleDialog::scrollToCurrent() {
int line = _promptEndPos / kCharsPerLine;
if (line + _linesPerPage <= _scrollLine) {
// TODO - this should only occur for loong edit lines, though
} else if (line > _scrollLine) {
_scrollLine = line;
updateScrollBuffer();
g_gui.scheduleTopDialogRedraw();
}
}
void ConsoleDialog::handleMouseDown(int x, int y, int button, int clickCount) {
Widget *w;
w = findWidget(x, y);
if (w) {
if (!(w->getFlags() & WIDGET_IGNORE_DRAG))
_dragWidget = w;
if (w != _focusedWidget && w->wantsFocus()) {
setFocusWidget(w);
}
w->handleMouseDown(x - (w->getAbsX() - _x), y - (w->getAbsY() - _y), button, clickCount);
} else if (_selBegin == -1 || _selEnd == -1) {
x = MIN(MAX(x, _leftPadding), kCharsPerLine * kConsoleCharWidth + _leftPadding);
y = MIN(MAX(y, _topPadding), (decltype(y))_h - kConsoleLineHeight);
int lineNumber = (y - _topPadding) / kConsoleLineHeight;
int ind = (x - _leftPadding) / kConsoleCharWidth;
_selBegin = (_scrollLine - _linesPerPage + 1 + lineNumber) * kCharsPerLine + ind;
clampSelection(_selBegin);
_selEnd = _selBegin;
_isDragging = true;
} else {
_selBegin = -1;
_selEnd = -1;
drawDialog(kDrawLayerForeground);
}
}
bool ConsoleDialog::clampSelection(int &sel) {
int oldSel = sel;
int lowerBound = 0;
int upperBound;
upperBound = MAX(_promptEndPos, _currentPos);
upperBound = MAX(upperBound, _linesPerPage * kCharsPerLine); // at least the whole first page
upperBound += kCharsPerLine - (upperBound % kCharsPerLine); // to end of line
sel = MAX(lowerBound, MIN(upperBound, sel));
return sel != oldSel;
}
void ConsoleDialog::handleMouseMoved(int x, int y, int button) {
if (!_isDragging)
Dialog::handleMouseMoved(x, y, button);
else {
int selEndPreviousMove = _selEnd;
x = MIN(MAX(x, _leftPadding), kCharsPerLine * kConsoleCharWidth + _leftPadding);
y = MIN(MAX(y, _topPadding), (decltype(y))_h - kConsoleLineHeight);
int lineNumber = (y - _topPadding) / kConsoleLineHeight;
lineNumber = MIN(lineNumber, _linesPerPage - 1);
int col = (x - _leftPadding) / kConsoleCharWidth;
_selEnd = (_scrollLine - _linesPerPage + 1 + lineNumber) * kCharsPerLine + col;
clampSelection(_selEnd);
if (_selEnd == selEndPreviousMove)
return;
if (lineNumber > _linesPerPage - 2)
_scrollDirection = -1;
else if (lineNumber < 1)
_scrollDirection = 1;
else
_scrollDirection = 0;
for (int i = MIN(_selEnd / kCharsPerLine, selEndPreviousMove / kCharsPerLine); i <= MAX(_selEnd / kCharsPerLine, selEndPreviousMove / kCharsPerLine); i++)
drawLine(i - _scrollBar->_currentPos / _scrollBar->_singleStep);
}
}
void ConsoleDialog::handleMouseUp(int x, int y, int button, int clickCount) {
Dialog::handleMouseUp(x, y, button, clickCount);
_isDragging = false;
if (_selBegin == _selEnd) {
_selBegin = -1;
_selEnd = -1;
}
_scrollDirection = 0;
}
} // End of namespace GUI

221
gui/console.h Normal file
View File

@@ -0,0 +1,221 @@
/* 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/>.
*
*/
#ifndef CONSOLE_DIALOG_H
#define CONSOLE_DIALOG_H
#include "gui/dialog.h"
#include "common/str.h"
namespace GUI {
class ScrollBarWidget;
/*
FIXME #1: The console dialog code has some fundamental problems.
First of, note the conflict between the (constant) value kCharsPerLine, and the
(variable) value _pageWidth. Look a bit at the code to get familiar with them,
then return...
Now, why don't we just drop kCharsPerLine? Because of the problem of resizing!
When the user changes the scaler, the console will get resized. If the dialog
becomes smaller because of this, we may have to rewrap text. If the resolution
is then increased again, we'd end up with garbled content.
One can now either ignore this problem (and modify our code accordingly to
implement this simple rewrapping -- we currently don't do that at all!).
Or, one can go and implement a more complete console, by replacing the
_buffer by a real line buffer -- an array of char* pointers.
This will allow one to implement resizing perfectly, but has the drawback
of making things like scrolling, drawing etc. more complicated.
Either way, the current situation is bad, and we should resolve it one way
or the other (and if you can think of a third, feel free to suggest it).
FIXME #2: Another problem is that apparently _pageWidth isn't computed quite
correctly. The current line ends well before reaching the right side of the
console dialog. That's irritating and should be fixed.
FIXME #3: The scroll bar is not shown initially, but the area it would
occupy is not used for anything else. As a result, the gap described above
becomes even wider and thus even more irritating.
*/
class ConsoleDialog : public Dialog {
public:
typedef bool (*InputCallbackProc)(ConsoleDialog *console, const char *input, void *refCon);
typedef bool (*CompletionCallbackProc)(ConsoleDialog* console, const char *input, Common::String &completion, void *refCon);
protected:
enum {
kCharsPerLine = 128,
kBufferSize = kCharsPerLine * 1024,
kHistorySize = 20,
kDraggingTime = 10
};
const Graphics::Font *_font;
char _buffer[kBufferSize];
int _linesInBuffer;
int _pageWidth;
int _linesPerPage;
int _currentPos;
int _scrollLine;
int _firstLineInBuffer;
int _promptStartPos;
int _promptEndPos;
bool _caretVisible;
uint32 _caretTime;
uint32 _selectionTime;
enum SlideMode {
kNoSlideMode,
kUpSlideMode,
kDownSlideMode
};
SlideMode _slideMode;
uint32 _slideTime;
ScrollBarWidget *_scrollBar;
// The _callbackProc is called whenver a data line is entered
//
InputCallbackProc _callbackProc;
void *_callbackRefCon;
// _completionCallbackProc is called when tab is pressed
CompletionCallbackProc _completionCallbackProc;
void *_completionCallbackRefCon;
Common::String _history[kHistorySize];
int _historySize;
int _historyIndex;
int _historyLine;
float _widthPercent, _heightPercent;
int _leftPadding;
int _rightPadding;
int _topPadding;
int _bottomPadding;
void slideUpAndClose();
Common::String _prompt;
bool _isDragging;
int _selBegin;
int _selEnd;
int _scrollDirection;
public:
ConsoleDialog(float widthPercent, float heightPercent);
virtual ~ConsoleDialog();
void open() override;
void close() override;
void drawDialog(DrawLayer layerToDraw) override;
void handleTickle() override;
void reflowLayout() override;
void handleMouseWheel(int x, int y, int direction) override;
void handleKeyDown(Common::KeyState state) override;
void handleCommand(CommandSender *sender, uint32 cmd, uint32 data) override;
void handleOtherEvent(const Common::Event &evt) override;
void handleMouseDown(int x, int y, int button, int clickCount) override;
void handleMouseMoved(int x, int y, int button) override;
void handleMouseUp(int x, int y, int button, int clickCount) override;
int printFormat(int dummy, MSVC_PRINTF const char *format, ...) GCC_PRINTF(3, 4);
int vprintFormat(int dummy, const char *format, va_list argptr);
void printChar(int c);
void setInputCallback(InputCallbackProc proc, void *refCon) {
_callbackProc = proc;
_callbackRefCon = refCon;
}
void setCompletionCallback(CompletionCallbackProc proc, void *refCon) {
_completionCallbackProc = proc;
_completionCallbackRefCon = refCon;
}
int getCharsPerLine() {
return _pageWidth;
}
void setPrompt(Common::String prompt);
void resetPrompt();
void clearBuffer();
protected:
inline char &buffer(int idx) {
return _buffer[idx % kBufferSize];
}
void init();
int pos2line(int pos) { return (pos - (_scrollLine - _linesPerPage + 1) * kCharsPerLine) / kCharsPerLine; }
void drawLine(int line);
void drawCaret(bool erase);
void printCharIntern(int c);
void insertIntoPrompt(const char *str);
void print(const char *str);
void updateScrollBuffer();
void scrollToCurrent();
Common::String getUserInput();
void defaultKeyDownHandler(Common::KeyState &state);
// Line editing
void specialKeys(Common::KeyCode keycode);
void nextLine();
void killChar();
void killLine();
void killLastWord();
// History
void loadHistory();
void saveHistory();
void addToHistory(const Common::String &str);
void historyScroll(int direction);
/**
* Returns whether sel was modified
*/
bool clampSelection(int &sel);
};
} // End of namespace GUI
#endif

1574
gui/credits.h Normal file

File diff suppressed because it is too large Load Diff

877
gui/debugger.cpp Normal file
View File

@@ -0,0 +1,877 @@
/* 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/>.
*
*/
// NB: This is really only necessary if USE_READLINE is defined
#define FORBIDDEN_SYMBOL_ALLOW_ALL
#include "common/file.h"
#include "common/debug.h"
#include "common/debug-channels.h"
#include "common/system.h"
#ifndef DISABLE_MD5
#include "common/md5.h"
#include "common/archive.h"
#include "common/macresman.h"
#include "common/stream.h"
#endif
#include "engines/engine.h"
#include "gui/debugger.h"
#ifndef USE_TEXT_CONSOLE_FOR_DEBUGGER
#include "gui/console.h"
#elif defined(USE_READLINE)
#include <readline/readline.h>
#include <readline/history.h>
#include "common/events.h"
#endif
namespace GUI {
Debugger::Debugger() {
_frameCountdown = 0;
_isActive = false;
_firstTime = true;
_defaultCommandProcessor = nullptr;
#ifndef USE_TEXT_CONSOLE_FOR_DEBUGGER
_debuggerDialog = new GUI::ConsoleDialog(1.0f, 0.67f);
_debuggerDialog->setInputCallback(debuggerInputCallback, this);
_debuggerDialog->setCompletionCallback(debuggerCompletionCallback, this);
#endif
// Register variables
registerVarImpl("debug_countdown", &_frameCountdown, DVAR_INT, 0);
// Register commands
//registerCmd("continue", WRAP_METHOD(Debugger, cmdExit));
registerCmd("exit", WRAP_METHOD(Debugger, cmdExit));
registerCmd("quit", WRAP_METHOD(Debugger, cmdExit));
registerCmd("help", WRAP_METHOD(Debugger, cmdHelp));
registerCmd("openlog", WRAP_METHOD(Debugger, cmdOpenLog));
#ifndef DISABLE_MD5
registerCmd("md5", WRAP_METHOD(Debugger, cmdMd5));
registerCmd("md5mac", WRAP_METHOD(Debugger, cmdMd5Mac));
#endif
registerCmd("clear", WRAP_METHOD(Debugger, cmdClearLog));
registerCmd("cls", WRAP_METHOD(Debugger, cmdClearLog)); // alias
registerCmd("exec", WRAP_METHOD(Debugger, cmdExecFile));
registerCmd("debuglevel", WRAP_METHOD(Debugger, cmdDebugLevel));
registerCmd("debugflag_list", WRAP_METHOD(Debugger, cmdDebugFlagsList));
registerCmd("debugflag_enable", WRAP_METHOD(Debugger, cmdDebugFlagEnable));
registerCmd("debugflag_disable", WRAP_METHOD(Debugger, cmdDebugFlagDisable));
}
Debugger::~Debugger() {
#ifndef USE_TEXT_CONSOLE_FOR_DEBUGGER
delete _debuggerDialog;
#endif
}
void Debugger::clearVars() {
_vars.resize(1); // Keep "debug_countdown"
}
void Debugger::setPrompt(Common::String prompt) {
#ifndef USE_TEXT_CONSOLE_FOR_DEBUGGER
_debuggerDialog->setPrompt(prompt);
#endif
}
void Debugger::resetPrompt() {
#ifndef USE_TEXT_CONSOLE_FOR_DEBUGGER
_debuggerDialog->resetPrompt();
#endif
}
// Initialisation Functions
int Debugger::getCharsPerLine() {
#ifndef USE_TEXT_CONSOLE_FOR_DEBUGGER
const int charsPerLine = _debuggerDialog->getCharsPerLine();
#elif defined(USE_READLINE)
int charsPerLine, rows;
rl_get_screen_size(&rows, &charsPerLine);
#else
// Can we do better?
const int charsPerLine = 80;
#endif
return charsPerLine;
}
int Debugger::debugPrintf(const char *format, ...) {
va_list argptr;
va_start(argptr, format);
int count;
#ifndef USE_TEXT_CONSOLE_FOR_DEBUGGER
count = _debuggerDialog->vprintFormat(1, format, argptr);
#else
count = ::vprintf(format, argptr);
::fflush(stdout);
#endif
va_end (argptr);
return count;
}
void Debugger::debugPrintColumns(const Common::StringArray &list) {
uint maxLength = 0;
uint i, j;
for (i = 0; i < list.size(); i++) {
if (list[i].size() > maxLength)
maxLength = list[i].size();
}
uint charsPerLine = getCharsPerLine();
uint columnWidth = maxLength + 2;
uint columns = charsPerLine / columnWidth;
uint lines = list.size() / columns;
if (list.size() % columns)
lines++;
// This won't always use all available columns, but even if it did the
// number of lines should be the same so that's good enough.
for (i = 0; i < lines; i++) {
for (j = 0; j < columns; j++) {
uint pos = i + j * lines;
if (pos < list.size()) {
debugPrintf("%*s", -1 * columnWidth, list[pos].c_str());
}
}
debugPrintf("\n");
}
}
void Debugger::preEnter() {
_debugPauseToken = g_engine->pauseEngine();
}
void Debugger::postEnter() {
_debugPauseToken.clear();
}
void Debugger::attach(const char *entry) {
g_system->setFeatureState(OSystem::kFeatureVirtualKeyboard, true);
// Set error string (if any)
_errStr = entry ? entry : "";
// Reset frame countdown (i.e. attach immediately)
_frameCountdown = 1;
}
void Debugger::detach() {
g_system->setFeatureState(OSystem::kFeatureVirtualKeyboard, false);
}
// Temporary execution handler
void Debugger::onFrame() {
// Count down until 0 is reached
if (_frameCountdown > 0) {
--_frameCountdown;
if (_frameCountdown == 0) {
_isActive = true;
preEnter();
enter();
postEnter();
_isActive = false;
}
}
}
#if defined(USE_TEXT_CONSOLE_FOR_DEBUGGER) && defined(USE_READLINE)
namespace {
Debugger *g_readline_debugger;
char *readline_completionFunction(const char *text, int state) {
return g_readline_debugger->readlineComplete(text, state);
}
void readline_eventFunction() {
Common::EventManager *eventMan = g_system->getEventManager();
Common::Event event;
while (eventMan->pollEvent(event)) {
// drop all events
}
}
#ifdef USE_READLINE_INT_COMPLETION
typedef int RLCompFunc_t(const char *, int);
#else
typedef char *RLCompFunc_t(const char *, int);
#endif
} // end of anonymous namespace
#endif
// Main Debugger Loop
void Debugger::enter() {
// TODO: Having three I/O methods #ifdef-ed in this file is not the
// cleanest approach to this...
#ifndef USE_TEXT_CONSOLE_FOR_DEBUGGER
if (_firstTime) {
debugPrintf("Debugger started, type 'exit' to return to the game.\n");
debugPrintf("Type 'help' to see a little list of commands and variables.\n");
_firstTime = false;
}
if (_errStr.size()) {
debugPrintf("ERROR: %s\n\n", _errStr.c_str());
_errStr.clear();
}
_debuggerDialog->runModal();
#else
printf("Debugger entered, please switch to this console for input.\n");
#ifdef USE_READLINE
// TODO: add support for saving/loading history?
g_readline_debugger = this;
rl_completion_entry_function = (RLCompFunc_t *)&readline_completionFunction;
rl_event_hook = (rl_hook_func_t *)&readline_eventFunction;
char *line_read = 0;
do {
free(line_read);
line_read = readline("debug> ");
if (line_read && line_read[0])
add_history(line_read);
} while (line_read && parseCommand(line_read));
free(line_read);
line_read = 0;
#else
int i;
char buf[256];
do {
printf("debug> ");
::fflush(stdout);
if (!fgets(buf, sizeof(buf), stdin))
return;
i = strlen(buf);
while (i > 0 && buf[i - 1] == '\n')
buf[--i] = 0;
if (i == 0)
continue;
} while (parseCommand(buf));
#endif
#endif
}
bool Debugger::handleCommand(int argc, const char **argv, bool &result) {
assert(argc > 0);
if (_cmds.contains(argv[0])) {
assert(_cmds[argv[0]]);
result = (*_cmds[argv[0]])(argc, argv);
return true;
}
return false;
}
// Command execution loop
bool Debugger::parseCommand(const char *inputOrig) {
int num_params = 0;
const char *param[256];
if (_defaultCommandProcessor)
return (*_defaultCommandProcessor)(inputOrig);
// Parse out any params
Common::String input(inputOrig);
splitCommand(input, num_params, &param[0]);
if (num_params == 0) {
return true;
}
// Handle commands first
bool result;
if (handleCommand(num_params, param, result)) {
return result;
}
// It's not a command, so things get a little tricky for variables. Do fuzzy matching to ignore things like subscripts.
for (uint i = 0; i < _vars.size(); i++) {
if (!strncmp(_vars[i].name.c_str(), param[0], _vars[i].name.size())) {
if (num_params > 1) {
// Alright, we need to check the TYPE of the variable to deref and stuff... the array stuff is a bit ugly :)
switch (_vars[i].type) {
// Integer
case DVAR_BYTE:
*(byte *)_vars[i].variable = atoi(param[1]);
debugPrintf("byte%s = %d\n", param[0], *(byte *)_vars[i].variable);
break;
case DVAR_INT:
*(int32 *)_vars[i].variable = atoi(param[1]);
debugPrintf("(int)%s = %d\n", param[0], *(int32 *)_vars[i].variable);
break;
case DVAR_FLOAT:
*(float *)_vars[i].variable = (float)atof(param[1]);
debugPrintf("(float)%s = %f\n", param[0], *(float *)_vars[i].variable);
break;
case DVAR_BOOL:
if (Common::parseBool(param[1], *(bool *)_vars[i].variable))
debugPrintf("(bool)%s = %s\n", param[0], *(bool *)_vars[i].variable ? "true" : "false");
else
debugPrintf("Invalid value for boolean variable. Valid values are \"true\", \"false\", \"1\", \"0\", \"yes\", \"no\"\n");
break;
// Integer Array
case DVAR_INTARRAY: {
const char *chr = strchr(param[0], '[');
if (!chr) {
debugPrintf("You must access this array as %s[element]\n", param[0]);
} else {
int element = atoi(chr+1);
int32 *var = *(int32 **)_vars[i].variable;
if (element >= _vars[i].arraySize) {
debugPrintf("%s is out of range (array is %d elements big)\n", param[0], _vars[i].arraySize);
} else {
var[element] = atoi(param[1]);
debugPrintf("(int)%s = %d\n", param[0], var[element]);
}
}
}
break;
default:
debugPrintf("Failed to set variable %s to %s - unknown type\n", _vars[i].name.c_str(), param[1]);
break;
}
} else {
// And again, type-dependent prints/defrefs. The array one is still ugly.
switch (_vars[i].type) {
// Integer
case DVAR_BYTE:
debugPrintf("(byte)%s = %d\n", param[0], *(const byte *)_vars[i].variable);
break;
case DVAR_INT:
debugPrintf("(int)%s = %d\n", param[0], *(const int32 *)_vars[i].variable);
break;
case DVAR_FLOAT:
debugPrintf("(float)%s = %f\n", param[0], *(const float *)_vars[i].variable);
break;
case DVAR_BOOL:
debugPrintf("(bool)%s = %s\n", param[0], *(const bool *)_vars[i].variable ? "true" : "false");
break;
// Integer array
case DVAR_INTARRAY: {
const char *chr = strchr(param[0], '[');
if (!chr) {
debugPrintf("You must access this array as %s[element]\n", param[0]);
} else {
int element = atoi(chr+1);
const int32 *var = *(const int32 **)_vars[i].variable;
if (element >= _vars[i].arraySize) {
debugPrintf("%s is out of range (array is %d elements big)\n", param[0], _vars[i].arraySize);
} else {
debugPrintf("(int)%s = %d\n", param[0], var[element]);
}
}
}
break;
// String
case DVAR_STRING:
debugPrintf("(string)%s = %s\n", param[0], ((Common::String *)_vars[i].variable)->c_str());
break;
default:
debugPrintf("%s = (unknown type)\n", param[0]);
break;
}
}
return true;
}
}
debugPrintf("Unknown command or variable\n");
return true;
}
void Debugger::splitCommand(Common::String &input, int &argc, const char **argv) {
byte c;
enum states { DULL, IN_WORD, IN_STRING } state = DULL;
const char *paramStart = nullptr;
argc = 0;
for (Common::String::iterator p = input.begin(); *p; ++p) {
c = (byte)*p;
switch (state) {
default:
// fallthrough intended
case DULL:
// not in a word, not in a double quoted string
if (isspace(c))
break;
// not a space -- if it's a double quote we go to IN_STRING, else to IN_WORD
if (c == '"') {
state = IN_STRING;
paramStart = p + 1; // word starts at *next* char, not this one
} else {
state = IN_WORD;
paramStart = p; // word starts here
}
break;
case IN_STRING:
// we're in a double quoted string, so keep going until we hit a close "
if (c == '"') {
// Add entire quoted string to parameter list
*p = '\0';
argv[argc++] = paramStart;
state = DULL; // back to "not in word, not in string" state
}
break;
case IN_WORD:
// we're in a word, so keep going until we get to a space
if (isspace(c)) {
*p = '\0';
argv[argc++] = paramStart;
state = DULL; // back to "not in word, not in string" state
}
break;
}
}
if (state != DULL)
// Add in final parameter
argv[argc++] = paramStart;
}
// returns true if something has been completed
// completion has to be delete[]-ed then
bool Debugger::tabComplete(const char *input, Common::String &completion) const {
// very basic tab completion
// for now it just supports command completions
// adding completions of command parameters would be nice (but hard) :-)
// maybe also give a list of possible command completions?
// (but this will require changes to console)
if (strchr(input, ' '))
return false; // already finished the first word
const uint inputlen = strlen(input);
completion.clear();
CommandsMap::const_iterator i, e = _cmds.end();
for (i = _cmds.begin(); i != e; ++i) {
if (i->_key.hasPrefixIgnoreCase(input)) {
uint commandlen = i->_key.size();
if (commandlen == inputlen) { // perfect match, so no tab completion possible
return false;
}
if (commandlen > inputlen) { // possible match
// no previous match
if (completion.empty()) {
completion = i->_key.c_str() + inputlen;
} else {
// take common prefix of previous match and this command
for (uint j = 0; j < completion.size(); j++) {
if (inputlen + j >= i->_key.size() ||
completion[j] != i->_key[inputlen + j]) {
completion = Common::String(completion.begin(), completion.begin() + j);
// If there is no unambiguous completion, abort
if (completion.empty())
return false;
break;
}
}
}
}
}
}
if (completion.empty())
return false;
return true;
}
#if defined(USE_TEXT_CONSOLE_FOR_DEBUGGER) && defined(USE_READLINE)
char *Debugger::readlineComplete(const char *input, int state) {
static CommandsMap::const_iterator iter;
// We assume that _cmds isn't changed between calls to readlineComplete,
// unless state is 0.
if (state == 0) {
iter = _cmds.begin();
} else {
++iter;
}
for (; iter != _cmds.end(); ++iter) {
if (iter->_key.hasPrefix(input)) {
char *ret = (char *)malloc(iter->_key.size() + 1);
Common::strcpy_s(ret, iter->_key.size() + 1, iter->_key.c_str());
return ret;
}
}
return 0;
}
#endif
// Variable registration function
void Debugger::registerVarImpl(const Common::String &varname, void *pointer, VarType type, int arraySize) {
// TODO: Filter out duplicates
// TODO: Sort this list? Then we can do binary search later on when doing lookups.
assert(pointer);
Var tmp;
tmp.name = varname;
tmp.type = type;
tmp.variable = pointer;
tmp.arraySize = arraySize;
_vars.push_back(tmp);
}
// Command registration function
void Debugger::registerCmd(const Common::String &cmdname, Debuglet *debuglet) {
assert(debuglet && debuglet->isValid());
_cmds[cmdname] = Common::SharedPtr<Debuglet>(debuglet);
}
// Detach ("exit") the debugger
bool Debugger::cmdExit(int argc, const char **argv) {
detach();
return false;
}
// Print a list of all registered commands (and variables, if any),
// nicely word-wrapped.
bool Debugger::cmdHelp(int argc, const char **argv) {
const int charsPerLine = getCharsPerLine();
int width, size;
uint i;
debugPrintf("Commands are:\n");
// Obtain a list of sorted command names
Common::Array<Common::String> cmds;
CommandsMap::const_iterator iter, e = _cmds.end();
for (iter = _cmds.begin(); iter != e; ++iter) {
cmds.push_back(iter->_key);
}
sort(cmds.begin(), cmds.end());
// Print them all
width = 0;
for (i = 0; i < cmds.size(); i++) {
size = cmds[i].size() + 1;
if ((width + size) >= charsPerLine) {
debugPrintf("\n");
width = size;
} else
width += size;
debugPrintf("%s ", cmds[i].c_str());
}
debugPrintf("\n");
if (!_vars.empty()) {
debugPrintf("\n");
debugPrintf("Variables are:\n");
width = 0;
for (i = 0; i < _vars.size(); i++) {
size = _vars[i].name.size() + 1;
if ((width + size) >= charsPerLine) {
debugPrintf("\n");
width = size;
} else
width += size;
debugPrintf("%s ", _vars[i].name.c_str());
}
debugPrintf("\n");
}
return true;
}
bool Debugger::cmdOpenLog(int argc, const char **argv) {
if (g_system->hasFeature(OSystem::kFeatureDisplayLogFile))
g_system->displayLogFile();
else
debugPrintf("Opening the log file not supported on this system\n");
return true;
}
#ifndef DISABLE_MD5
struct ArchiveMemberLess {
bool operator()(const Common::ArchiveMemberPtr &x, const Common::ArchiveMemberPtr &y) const {
return (*x).getName().compareToIgnoreCase((*y).getName()) < 0;
}
};
bool Debugger::cmdMd5(int argc, const char **argv) {
if (argc < 2) {
debugPrintf("md5 [-n length] <filename | pattern>\n");
} else {
int32 length = 0;
uint paramOffset = 0;
bool tail = false;
// If the user supplied an -n parameter, set the bytes to read
if (!strcmp(argv[1], "-n")) {
// Make sure that we have at least two more parameters
if (argc < 4) {
debugPrintf("md5 [-n length] <filename | pattern>\n");
return true;
}
length = atoi(argv[2]);
if (length < 0) {
length = -length;
tail = true;
}
paramOffset = 2;
}
// Assume that spaces are part of a single filename.
Common::String filename = argv[1 + paramOffset];
for (int i = 2 + paramOffset; i < argc; i++) {
filename = filename + " " + argv[i];
}
Common::ArchiveMemberList list;
SearchMan.listMatchingMembers(list, Common::Path(filename, Common::Path::kNativeSeparator));
if (list.empty()) {
debugPrintf("File '%s' not found\n", filename.c_str());
} else {
sort(list.begin(), list.end(), ArchiveMemberLess());
for (auto &archive : list) {
Common::SeekableReadStream *stream = archive->createReadStream();
if (tail && stream->size() > length)
stream->seek(-length, SEEK_END);
Common::String md5 = Common::computeStreamMD5AsString(*stream, length);
if (length != 0 && length < stream->size())
md5 += Common::String::format(" (%s %d bytes)", tail ? "last" : "first", length);
debugPrintf("%s: %s, %llu bytes\n", archive->getName().c_str(), md5.c_str(), (unsigned long long)stream->size());
delete stream;
}
}
}
return true;
}
bool Debugger::cmdMd5Mac(int argc, const char **argv) {
if (argc < 2) {
debugPrintf("md5mac [-n length] <base filename>\n");
} else {
int32 length = 0;
uint paramOffset = 0;
bool tail = false;
// If the user supplied an -n parameter, set the bytes to read
if (!strcmp(argv[1], "-n")) {
// Make sure that we have at least two more parameters
if (argc < 4) {
debugPrintf("md5mac [-n length] <base filename>\n");
return true;
}
length = atoi(argv[2]);
if (length < 0) {
length = -length;
tail = true;
}
paramOffset = 2;
}
// Assume that spaces are part of a single filename.
Common::String filename = argv[1 + paramOffset];
for (int i = 2 + paramOffset; i < argc; i++) {
filename = filename + " " + argv[i];
}
Common::MacResManager macResMan;
// FIXME: There currently isn't any way to tell the Mac resource
// manager to open a specific file. Instead, it takes a "base name"
// and constructs a file name out of that. While usually a desirable
// thing, it's not ideal here.
if (!macResMan.open(Common::Path(filename, Common::Path::kNativeSeparator))) {
debugPrintf("Resource file '%s' not found\n", filename.c_str());
} else {
Common::ScopedPtr<Common::SeekableReadStream> dataFork(Common::MacResManager::openFileOrDataFork(Common::Path(filename, Common::Path::kNativeSeparator)));
if (!macResMan.hasResFork() && !dataFork) {
debugPrintf("'%s' has neither data not resource fork\n", macResMan.getBaseFileName().toString().c_str());
} else {
// The resource fork is probably the most relevant one.
if (macResMan.hasResFork()) {
Common::String md5 = macResMan.computeResForkMD5AsString(length, tail);
if (length != 0 && length < (int32)macResMan.getResForkDataSize())
md5 += Common::String::format(" (%s %d bytes)", tail ? "last" : "first", length);
debugPrintf("%s (resource): %s, %llu bytes\n", macResMan.getBaseFileName().toString().c_str(), md5.c_str(), (unsigned long long)macResMan.getResForkDataSize());
}
if (dataFork) {
if (tail && dataFork->size() > length)
dataFork->seek(-length, SEEK_END);
Common::String md5 = Common::computeStreamMD5AsString(*dataFork, length);
if (length != 0 && length < dataFork->size())
md5 += Common::String::format(" (%s %d bytes)", tail ? "last" : "first", length);
debugPrintf("%s (data): %s, %llu bytes\n", macResMan.getBaseFileName().toString().c_str(), md5.c_str(), (unsigned long long)dataFork->size());
}
}
macResMan.close();
}
}
return true;
}
#endif
bool Debugger::cmdDebugLevel(int argc, const char **argv) {
if (argc == 1) { // print level
debugPrintf("Debugging is currently %s (set at level %d)\n", (gDebugLevel >= 0) ? "enabled" : "disabled", gDebugLevel);
debugPrintf("Usage: %s <n> where n is 0 to 10 or -1 to disable debugging\n", argv[0]);
} else { // set level
gDebugLevel = atoi(argv[1]);
if (gDebugLevel >= 0 && gDebugLevel < 11) {
debugPrintf("Debug level set to level %d\n", gDebugLevel);
} else if (gDebugLevel < 0) {
debugPrintf("Debugging is now disabled\n");
} else {
debugPrintf("Invalid debug level value\n");
debugPrintf("Usage: %s <n> where n is 0 to 10 or -1 to disable debugging\n", argv[0]);
}
}
return true;
}
bool Debugger::cmdDebugFlagsList(int argc, const char **argv) {
const Common::DebugManager::DebugChannelList &debugLevels = DebugMan.getDebugChannels();
debugPrintf("Engine debug levels:\n");
debugPrintf("--------------------\n");
if (debugLevels.empty()) {
debugPrintf("No engine debug levels\n");
return true;
}
for (const auto &debugLevel : debugLevels) {
bool enabled = DebugMan.isDebugChannelEnabled(debugLevel.channel);
debugPrintf("%c%s - %s (%s)\n", enabled ? '+' : ' ',
debugLevel.name.c_str(), debugLevel.description.c_str(),
enabled ? "enabled" : "disabled");
}
debugPrintf("\n");
return true;
}
bool Debugger::cmdDebugFlagEnable(int argc, const char **argv) {
if (argc < 2) {
debugPrintf("debugflag_enable [<flag> | all]\n");
} else {
if (!scumm_stricmp(argv[1], "all")) {
debugPrintf("Enabled all debug flags\n");
DebugMan.enableAllDebugChannels();
} else if (DebugMan.enableDebugChannel(argv[1])) {
debugPrintf("Enabled debug flag '%s'\n", argv[1]);
} else {
debugPrintf("Failed to enable debug flag '%s'\n", argv[1]);
}
}
return true;
}
bool Debugger::cmdClearLog(int argc, const char **argv) {
#ifndef USE_TEXT_CONSOLE_FOR_DEBUGGER
_debuggerDialog->clearBuffer();
#endif
return true;
}
bool Debugger::cmdExecFile(int argc, const char **argv) {
if (argc <= 1) {
debugPrintf("Expected to get the file with debug commands\n");
return false;
}
const Common::String filename(argv[1]);
Common::File file;
if (!file.open(Common::Path(filename, Common::Path::kNativeSeparator))) {
debugPrintf("Can't open file %s\n", filename.c_str());
return false;
}
for (;;) {
const Common::String &line = file.readLine();
if (line.empty()) {
break;
}
parseCommand(line.c_str());
}
return true;
}
bool Debugger::cmdDebugFlagDisable(int argc, const char **argv) {
if (argc < 2) {
debugPrintf("debugflag_disable [<flag> | all]\n");
} else {
if (!scumm_stricmp(argv[1], "all")) {
debugPrintf("Disabled all debug flags\n");
DebugMan.disableAllDebugChannels();
} else if (DebugMan.disableDebugChannel(argv[1])) {
debugPrintf("Disabled debug flag '%s'\n", argv[1]);
} else {
debugPrintf("Failed to disable debug flag '%s'\n", argv[1]);
}
}
return true;
}
// Console handler
#ifndef USE_TEXT_CONSOLE_FOR_DEBUGGER
bool Debugger::debuggerInputCallback(GUI::ConsoleDialog *console, const char *input, void *refCon) {
Debugger *debugger = (Debugger *)refCon;
return debugger->parseCommand(input);
}
bool Debugger::debuggerCompletionCallback(GUI::ConsoleDialog *console, const char *input, Common::String &completion, void *refCon) {
Debugger *debugger = (Debugger *)refCon;
return debugger->tabComplete(input, completion);
}
#endif
} // End of namespace GUI

294
gui/debugger.h Normal file
View File

@@ -0,0 +1,294 @@
/* 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/>.
*
*/
#ifndef GUI_DEBUGGER_H
#define GUI_DEBUGGER_H
#include "common/func.h"
#include "common/ptr.h"
#include "common/hashmap.h"
#include "common/hash-str.h"
#include "common/array.h"
#include "common/str.h"
#include "common/str-array.h"
#include "engines/engine.h"
namespace GUI {
#ifndef USE_TEXT_CONSOLE_FOR_DEBUGGER
class ConsoleDialog;
#endif
class Debugger {
public:
Debugger();
virtual ~Debugger();
int getCharsPerLine();
int debugPrintf(MSVC_PRINTF const char *format, ...) GCC_PRINTF(2, 3);
void debugPrintColumns(const Common::StringArray &list);
/**
* The onFrame() method should be invoked by the engine at regular
* intervals (usually once per main loop iteration) whenever the
* debugger is attached.
* This will open up the console and accept user input if certain
* preconditions are met, such as the frame countdown having
* reached zero.
*
* Subclasses can override this to e.g. check for breakpoints being
* triggered.
*/
virtual void onFrame();
/**
* 'Attach' the debugger. This ensures that the next time onFrame()
* is invoked, the debugger will activate and accept user input.
*/
virtual void attach(const char *entry = nullptr);
/**
* Return true if the debugger is currently active (i.e. executing
* a command or waiting for use input).
*/
bool isActive() const { return _isActive; }
protected:
typedef Common::Functor1<const char *, bool> defaultCommand;
typedef Common::Functor2<int, const char **, bool> Debuglet;
/**
* Convenience macro that makes it easier to register a method
* of a debugger subclass as a command.
* Usage example:
* registerCmd("COMMAND", WRAP_METHOD(MyDebugger, myCmd));
* would register the method MyDebugger::myCmd(int, const char **)
* under the command name "COMMAND".
*/
#define WRAP_METHOD(cls, method) \
new Common::Functor2Mem<int, const char **, bool, cls>(this, &cls::method)
/**
* Convenience macro that makes it easier to register a defaultCommandProcessor
* Usage example:
* registerDefaultCmd(WRAP_DEFAULTCOMMAND(MyDebugger, myCmd));
*/
#define WRAP_DEFAULTCOMMAND(cls, command) \
new Common::Functor1Mem<const char *, bool, cls>(this, &cls::command)
enum VarType {
DVAR_BYTE,
DVAR_INT,
DVAR_FLOAT,
DVAR_BOOL,
DVAR_INTARRAY,
DVAR_STRING
};
struct Var {
Common::String name;
void *variable;
VarType type;
int arraySize;
};
private:
/**
* Register a variable with the debugger. This allows the user to read and modify
* this variable.
* @param varname the identifier with which the user may access the variable
* @param variable pointer to the actual storage of the variable
* @param type the type of the variable (byte, int, bool, ...)
* @param arraySize for type DVAR_INTARRAY this specifies the size of the array
*/
void registerVarImpl(const Common::String &varname, void *variable, VarType type, int arraySize);
protected:
void registerVar(const Common::String &varname, byte *variable) {
registerVarImpl(varname, variable, DVAR_BYTE, 0);
}
void registerVar(const Common::String &varname, int *variable) {
registerVarImpl(varname, variable, DVAR_INT, 0);
}
void registerVar(const Common::String &varname, float *variable) {
registerVarImpl(varname, variable, DVAR_FLOAT, 0);
}
void registerVar(const Common::String &varname, bool *variable) {
registerVarImpl(varname, variable, DVAR_BOOL, 0);
}
void registerVar(const Common::String &varname, int32 **variable, int arraySize) {
registerVarImpl(varname, variable, DVAR_INTARRAY, arraySize);
}
void registerVar(const Common::String &varname, Common::String *variable) {
registerVarImpl(varname, variable, DVAR_STRING, 0);
}
void registerCmd(const Common::String &cmdname, Debuglet *debuglet);
/**
* Register a default command processor with the debugger. This
* allows an engine to receive all user input in the debugger.
*
* A defaultCommandProcessor has the following signature:
* bool func(const char **inputOrig)
*
* To deactivate call with a nullptr.
*/
void registerDefaultCmd(defaultCommand *defaultCommandProcessor) {
_defaultCommandProcessor = defaultCommandProcessor; }
/**
* Remove all vars except default "debug_countdown"
*/
void clearVars();
void setPrompt(Common::String prompt);
void resetPrompt();
private:
/**
* The frame countdown specifies a number of frames that must pass
* until the console will show up. This value is decremented by one
* each time onFrame() is called, until it reaches 0, at which point
* onFrame() will open the console and handle input into it.
*
* The user can modify this value using the debug_countdown command.
*
* Note: The console must be in *attached* state, otherwise, it
* won't show up (and the countdown won't count down either).
*/
uint _frameCountdown;
Common::Array<Var> _vars;
typedef Common::HashMap<Common::String, Common::SharedPtr<Debuglet>, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> CommandsMap;
CommandsMap _cmds;
/**
* True if the debugger is currently active (i.e. executing
* a command or waiting for use input).
*/
bool _isActive;
Common::String _errStr;
/**
* Initially true, set to false when Debugger::enter is called
* the first time. We use this flag to show a greeting message
* to the user once, when he opens the debugger for the first
* time.
*/
bool _firstTime;
/**
* A nullptr till set by via registerDefaultCommand.
*/
defaultCommand *_defaultCommandProcessor;
protected:
PauseToken _debugPauseToken;
#ifndef USE_TEXT_CONSOLE_FOR_DEBUGGER
GUI::ConsoleDialog *_debuggerDialog;
#endif
protected:
/**
* Hook for subclasses which is called just before enter() is run.
* A typical usage example is pausing music and sound effects.
*
* The default implementation invokes Engine::pauseEngine(true).
*/
virtual void preEnter();
/**
* Hook for subclasses which is called just after enter() was run.
* A typical usage example is resuming music and sound effects.
*
* The default implementation invokes Engine::pauseEngine(false).
*/
virtual void postEnter();
/**
* Process the given command line.
* Returns true if and only if argv[0] is a known command and was
* handled, false otherwise.
*/
virtual bool handleCommand(int argc, const char **argv, bool &keepRunning);
/**
* Subclasses should invoke the detach() method in their cmdFOO methods
* if that command will resume execution of the program (as opposed to
* executing, say, a "single step through code" command).
*
* This currently only hides the virtual keyboard, if any.
*/
void detach();
private:
void enter();
/**
* Splits up the input into individual parameters
* @remarks Adapted from code provided by torek on StackOverflow
*/
void splitCommand(Common::String &input, int &argc, const char **argv);
bool parseCommand(const char *input);
bool tabComplete(const char *input, Common::String &completion) const;
protected:
bool cmdExit(int argc, const char **argv);
bool cmdHelp(int argc, const char **argv);
bool cmdOpenLog(int argc, const char **argv);
#ifndef DISABLE_MD5
bool cmdMd5(int argc, const char **argv);
bool cmdMd5Mac(int argc, const char **argv);
#endif
bool cmdDebugLevel(int argc, const char **argv);
bool cmdDebugFlagsList(int argc, const char **argv);
bool cmdDebugFlagEnable(int argc, const char **argv);
bool cmdDebugFlagDisable(int argc, const char **argv);
bool cmdClearLog(int argc, const char **argv);
bool cmdExecFile(int argc, const char **argv);
#ifndef USE_TEXT_CONSOLE_FOR_DEBUGGER
private:
static bool debuggerInputCallback(GUI::ConsoleDialog *console, const char *input, void *refCon);
static bool debuggerCompletionCallback(GUI::ConsoleDialog *console, const char *input, Common::String &completion, void *refCon);
#elif defined(USE_READLINE)
public:
char *readlineComplete(const char *input, int state);
#endif
};
} // End of namespace GUI
#endif

427
gui/dialog.cpp Normal file
View File

@@ -0,0 +1,427 @@
/* 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/rect.h"
#include "gui/gui-manager.h"
#include "gui/dialog.h"
#include "gui/ThemeEval.h"
#include "gui/widget.h"
namespace GUI {
/*
* TODO list
* - add some sense of the window being "active" (i.e. in front) or not. If it
* was inactive and just became active, reset certain vars (like who is focused).
* Maybe we should just add lostFocus and receivedFocus methods to Dialog, just
* like we have for class Widget?
* ...
*/
Dialog::Dialog(int x, int y, int w, int h, bool scale)
: GuiObject(x, y, w, h, scale),
_mouseWidget(nullptr), _focusedWidget(nullptr), _dragWidget(nullptr), _tickleWidget(nullptr), _visible(false),
_backgroundType(GUI::ThemeEngine::kDialogBackgroundDefault), _handlingMouseWheel(false) {
// Some dialogs like LauncherDialog use internally a fixed size, even though
// their widgets rely on the layout to be initialized correctly by the theme.
// Thus we need to catch screen changes here too. If we do not do that, it
// will for example crash after returning to the launcher when the user
// started a 640x480 game with a non 1x scaler.
g_gui.checkScreenChange();
_mouseUpdatedOnFocus = true;
_result = -1;
}
Dialog::Dialog(const Common::String &name)
: GuiObject(name),
_mouseWidget(nullptr), _focusedWidget(nullptr), _dragWidget(nullptr), _tickleWidget(nullptr), _visible(false),
_backgroundType(GUI::ThemeEngine::kDialogBackgroundDefault), _handlingMouseWheel(false) {
// It may happen that we have 3x scaler in launcher (960xY) and then 640x480
// game will be forced to 1x. At this stage GUI will not be aware of
// resolution change, so widgets will be off screen. This forces it to
// recompute
//
// Fixes bug #2892: "HE: When 3x graphics are chosen, F5 crashes game"
// and bug #2903: "SCUMM: F5 crashes game (640x480)"
g_gui.checkScreenChange();
_mouseUpdatedOnFocus = true;
_result = -1;
}
int Dialog::runModal() {
// Open up
open();
// Start processing events
g_gui.runLoop();
// Return the result code
return _result;
}
void Dialog::open() {
_result = 0;
_visible = true;
g_gui.openDialog(this);
setDefaultFocusedWidget();
}
void Dialog::close() {
_visible = false;
if (_mouseWidget) {
_mouseWidget->handleMouseLeft(0);
_mouseWidget = nullptr;
}
releaseFocus();
g_gui.closeTopDialog();
}
void Dialog::reflowLayout() {
// The screen has changed. That means the screen visual may also have
// changed, so any cached image may be invalid. The subsequent redraw
// should be treated as the very first draw.
if (!_name.empty()) {
g_gui.xmlEval()->reflowDialogLayout(_name, _firstWidget);
}
GuiObject::reflowLayout();
Widget *w = _firstWidget;
while (w) {
w->reflowLayout();
w = w->_next;
}
}
void Dialog::lostFocus() {
_dragWidget = nullptr;
if (_tickleWidget) {
_tickleWidget->lostFocus();
}
}
void Dialog::setFocusWidget(Widget *widget) {
// The focus will change. Tell the old focused widget (if any)
// that it lost the focus.
releaseFocus();
// Tell the new focused widget (if any) that it just gained the focus.
if (widget)
widget->receivedFocus();
_focusedWidget = widget;
}
void Dialog::setDefaultFocusedWidget() {
Widget *w = _firstWidget;
// Search for the first objects that wantsFocus() (if any) and give it the focus
while (w && !w->wantsFocus()) {
w = w->_next;
}
setFocusWidget(w);
}
void Dialog::releaseFocus() {
if (_focusedWidget) {
_focusedWidget->lostFocus();
_focusedWidget = nullptr;
}
}
void Dialog::markWidgetsAsDirty() {
Widget *w = _firstWidget;
while (w) {
w->markAsDirty();
w = w->_next;
}
}
void Dialog::drawDialog(DrawLayer layerToDraw) {
if (!isVisible())
return;
g_gui.theme()->disableClipRect();
g_gui.theme()->_layerToDraw = layerToDraw;
int16 x = _x;
if (g_gui.useRTL()) {
x = g_system->getOverlayWidth() - _x - _w;
}
g_gui.theme()->drawDialogBackground(Common::Rect(x, _y, x + _w, _y + _h), _backgroundType);
markWidgetsAsDirty();
#ifdef LAYOUT_DEBUG_DIALOG
return;
#endif
drawWidgets();
}
void Dialog::drawWidgets() {
if (!isVisible())
return;
// Draw all children
Widget *w = _firstWidget;
while (w) {
//if (w->_debugVisible)
w->draw();
w = w->_next;
}
}
void Dialog::handleMouseDown(int x, int y, int button, int clickCount) {
Widget *w;
w = findWidget(x, y);
if (w && !(w->getFlags() & WIDGET_IGNORE_DRAG))
_dragWidget = w;
// If the click occurred inside a widget which is not the currently
// focused one, change the focus to that widget.
if (w && w != _focusedWidget && w->wantsFocus()) {
setFocusWidget(w);
}
if (w)
w->handleMouseDown(x - (w->getAbsX() - _x), y - (w->getAbsY() - _y), button, clickCount);
}
void Dialog::handleMouseUp(int x, int y, int button, int clickCount) {
Widget *w;
if (_focusedWidget) {
//w = _focusedWidget;
// Lose focus on mouseup unless the widget requested to retain the focus
if (! (_focusedWidget->getFlags() & WIDGET_RETAIN_FOCUS )) {
releaseFocus();
}
}
if (_dragWidget)
w = _dragWidget;
else
w = findWidget(x, y);
if (w)
w->handleMouseUp(x - (w->getAbsX() - _x), y - (w->getAbsY() - _y), button, clickCount);
if (_dragWidget) {
_dragWidget = nullptr;
// Fake a mouse move to refresh now hovered widget
handleMouseMoved(x, y, button);
}
}
void Dialog::handleMouseWheel(int x, int y, int direction) {
// Guard against recursive call.
// This can happen as we call handleMouseWheel() on the widget under the mouse,
// and the default implementation of Widget::handleMouseWheel() is to call it
// for the widget boss, which can be this dialog.
if (_handlingMouseWheel)
return;
_handlingMouseWheel = true;
// This may look a bit backwards, but I think it makes more sense for
// the mouse wheel to primarily affect the widget the mouse is at than
// the widget that happens to be focused.
Widget *w = findWidget(x, y);
if (!w)
w = _focusedWidget;
if (w)
w->handleMouseWheel(x - (w->getAbsX() - _x), y - (w->getAbsY() - _y), direction);
_handlingMouseWheel = false;
}
void Dialog::handleKeyDown(Common::KeyState state) {
if (_focusedWidget) {
if (_focusedWidget->handleKeyDown(state))
return;
}
// Hotkey handling
// Convert keypad Enter to Return key
if (state.keycode == Common::KEYCODE_KP_ENTER) {
state.ascii = Common::ASCII_RETURN;
}
if (state.ascii != 0) {
Widget *w = _firstWidget;
state.ascii = toupper(state.ascii);
while (w) {
if (w->_type == kButtonWidget && state.ascii == toupper(((ButtonWidget *)w)->_hotkey)) {
// The hotkey for widget w was pressed. We fake a mouse click into the
// button by invoking the appropriate methods.
w->handleMouseDown(0, 0, 1, 1);
w->handleMouseUp(0, 0, 1, 1);
return;
}
w = w->_next;
}
}
// ESC closes all dialogs by default
if (state.keycode == Common::KEYCODE_ESCAPE) {
setResult(-1);
close();
}
if (state.keycode == Common::KEYCODE_TAB) {
// TODO: Maybe add Tab behaviours for all widgets too.
// searches through widgets on screen for tab widget
Widget *w = _firstWidget;
while (w) {
if (w->_type == kTabWidget)
if (w->handleKeyDown(state))
return;
w = w->_next;
}
}
}
void Dialog::handleKeyUp(Common::KeyState state) {
// Focused widget receives keyup events
if (_focusedWidget)
_focusedWidget->handleKeyUp(state);
}
void Dialog::handleMouseMoved(int x, int y, int button) {
Widget *w;
if (_focusedWidget && !_dragWidget) {
w = _focusedWidget;
int wx = w->getAbsX() - _x;
int wy = w->getAbsY() - _y;
// We still send mouseEntered/Left messages to the focused item
// (but to no other items).
bool mouseInFocusedWidget = (x >= wx && x < wx + w->_w && y >= wy && y < wy + w->_h);
if (mouseInFocusedWidget && _mouseWidget != w) {
if (_mouseWidget)
_mouseWidget->handleMouseLeft(button);
_mouseWidget = w;
w->handleMouseEntered(button);
} else if (!mouseInFocusedWidget && _mouseWidget == w) {
_mouseWidget = nullptr;
w->handleMouseLeft(button);
}
if (w->getFlags() & WIDGET_TRACK_MOUSE)
w->handleMouseMoved(x - wx, y - wy, button);
}
// We process mouseEntered/Left events if we don't have any
// currently active dragged widget or if the currently dragged widget
// does not want to be informed about the mouse mouse events.
if (!_dragWidget || !(_dragWidget->getFlags() & WIDGET_TRACK_MOUSE))
w = findWidget(x, y);
else
w = _dragWidget;
if (_mouseWidget != w) {
if (_mouseWidget)
_mouseWidget->handleMouseLeft(button);
// If we have a widget in drag mode we prevent mouseEntered
// events from being sent to other widgets.
if (_dragWidget && w != _dragWidget)
w = nullptr;
if (w)
w->handleMouseEntered(button);
_mouseWidget = w;
}
// We only sent mouse move events when the widget requests to be informed about them.
if (w && (w->getFlags() & WIDGET_TRACK_MOUSE))
w->handleMouseMoved(x - (w->getAbsX() - _x), y - (w->getAbsY() - _y), button);
}
void Dialog::handleTickle() {
// Focused widget receives tickle notifications
if (_focusedWidget && _focusedWidget->getFlags() & WIDGET_WANT_TICKLE)
_focusedWidget->handleTickle();
if (_tickleWidget && _tickleWidget->getFlags() & WIDGET_WANT_TICKLE)
_tickleWidget->handleTickle();
}
void Dialog::handleOtherEvent(const Common::Event &evt) {
if (_focusedWidget)
_focusedWidget->handleOtherEvent(evt);
}
void Dialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {
switch (cmd) {
case kCloseCmd:
close();
break;
case kCloseWithResultCmd:
setResult(data);
close();
break;
default:
break;
}
}
/*
* Determine the widget at location (x,y) if any. Assumes the coordinates are
* in the local coordinate system, i.e. relative to the top left of the dialog.
*/
Widget *Dialog::findWidget(int x, int y) {
return Widget::findWidgetInChain(_firstWidget, x, y);
}
Widget *Dialog::findWidget(const char *name) {
return Widget::findWidgetInChain(_firstWidget, name);
}
Widget *Dialog::findWidget(uint32 type) {
return Widget::findWidgetInChain(_firstWidget, type);
}
void Dialog::removeWidget(Widget *del) {
if (del == _mouseWidget || del->containsWidget(_mouseWidget))
_mouseWidget = nullptr;
if (del == _focusedWidget || del->containsWidget(_focusedWidget))
_focusedWidget = nullptr;
if (del == _dragWidget || del->containsWidget(_dragWidget))
_dragWidget = nullptr;
GuiObject::removeWidget(del);
}
} // End of namespace GUI

134
gui/dialog.h Normal file
View File

@@ -0,0 +1,134 @@
/* 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/>.
*
*/
#ifndef GUI_DIALOG_H
#define GUI_DIALOG_H
#include "common/scummsys.h"
#include "common/str.h"
#include "common/keyboard.h"
#include "gui/object.h"
#include "gui/ThemeEngine.h"
namespace Common {
struct Event;
}
namespace GUI {
class EventRecorder;
class Widget;
// Some "common" commands sent to handleCommand()
enum {
kCloseWithResultCmd = 'clsr',
kCloseCmd = 'clos',
kOKCmd = 'ok '
};
class Dialog : public GuiObject {
friend class GuiManager;
friend class EventRecorder;
friend class Tooltip;
protected:
Widget *_mouseWidget;
Widget *_focusedWidget;
Widget *_dragWidget;
Widget *_tickleWidget;
bool _visible;
// _mouseUpdatedOnFocus instructs gui-manager whether
// its lastMousePosition (time and x,y coordinates)
// should be updated, when this Dialog acquires focus.
// Default value is true.
bool _mouseUpdatedOnFocus;
ThemeEngine::DialogBackground _backgroundType;
private:
int _result;
bool _handlingMouseWheel;
public:
Dialog(int x, int y, int w, int h, bool scale = false);
Dialog(const Common::String &name);
virtual int runModal();
bool isVisible() const override { return _visible; }
bool isMouseUpdatedOnFocus() const { return _mouseUpdatedOnFocus; }
void releaseFocus() override;
void setFocusWidget(Widget *widget);
Widget *getFocusWidget() { return _focusedWidget; }
bool isDragging() const { return _dragWidget != nullptr; }
void setTickleWidget(Widget *widget) { _tickleWidget = widget; }
void unSetTickleWidget() { _tickleWidget = nullptr; }
Widget *getTickleWidget() { return _tickleWidget; }
void reflowLayout() override;
virtual void lostFocus();
virtual void receivedFocus(int x = -1, int y = -1) { if (x >= 0 && y >= 0) handleMouseMoved(x, y, 0); }
virtual void open();
virtual void close();
Widget *findWidget(uint32 type);
protected:
/** Recursively mark all the widgets in this dialog as dirty so they are redrawn */
void markWidgetsAsDirty();
/** Draw the dialog in its entirety (background and widgets) */
virtual void drawDialog(DrawLayer layerToDraw);
/** Draw only the dialog's widgets */
void drawWidgets();
virtual void handleTickle(); // Called periodically (in every guiloop() )
virtual void handleMouseDown(int x, int y, int button, int clickCount);
virtual void handleMouseUp(int x, int y, int button, int clickCount);
virtual void handleMouseWheel(int x, int y, int direction) override;
virtual void handleKeyDown(Common::KeyState state);
virtual void handleKeyUp(Common::KeyState state);
virtual void handleMouseMoved(int x, int y, int button);
virtual void handleMouseLeft(int button) {}
virtual void handleOtherEvent(const Common::Event &evt);
void handleCommand(CommandSender *sender, uint32 cmd, uint32 data) override;
Widget *findWidget(int x, int y); // Find the widget at pos x,y if any
Widget *findWidget(const char *name);
void removeWidget(Widget *widget) override;
void setMouseUpdatedOnFocus(bool mouseUpdatedOnFocus) { _mouseUpdatedOnFocus = mouseUpdatedOnFocus; }
void setDefaultFocusedWidget();
void setResult(int result) { _result = result; }
int getResult() const { return _result; }
};
} // End of namespace GUI
#endif

132
gui/dlcsdialog.cpp Normal file
View File

@@ -0,0 +1,132 @@
/* 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/dlcsdialog.h"
#include "gui/message.h"
#include "gui/widget.h"
#include "gui/widgets/list.h"
#include "gui/gui-manager.h"
#include "gui/launcher.h"
#include "gui/downloaddlcsdialog.h"
#include "backends/dlc/dlcmanager.h"
#include "backends/dlc/dlcdesc.h"
namespace GUI {
DLCsDialog::DLCsDialog() : Dialog("DownloadGames") {
// Set target (Command Receiver) for Command Sender
DLCMan.setTarget(this);
new StaticTextWidget(this, "DownloadGames.Headline", _("Download Freeware Games and Demos"));
// Add list with downloadable game titles
_gamesList = new ListWidget(this, "DownloadGames.List");
_gamesList->setNumberingMode(kListNumberingOff);
_gamesList->setEditable(false);
if (!DLCMan._fetchDLCs) {
DLCMan.getAllDLCs();
DLCMan._fetchDLCs = true;
Common::U32StringArray fetchingText = {
_("Fetching DLCs..."),
};
_gamesList->setList(fetchingText);
} else {
refreshDLCList();
}
new ButtonWidget(this, "DownloadGames.Back", _("Back"), Common::U32String(), kCloseCmd);
new ButtonWidget(this, "DownloadGames.AllDownloads", _("All Downloads"), Common::U32String(), kAllDownloadsCmd);
_downloadButton = new ButtonWidget(this, "DownloadGames.Download", _("Download"), Common::U32String(), kDownloadSelectedCmd);
}
DLCsDialog::~DLCsDialog() {
DLCMan.setTarget(nullptr);
}
void DLCsDialog::refreshDLCList() {
// Populate the ListWidget
Common::U32StringArray games;
for (uint32 i = 0; i < DLCMan._dlcs.size(); ++i) {
if (DLCMan._dlcs[i]->state == DLC::DLCDesc::kInProgress) {
games.push_back("[Downloading] " + DLCMan._dlcs[i]->name);
} else {
games.push_back(DLCMan._dlcs[i]->name);
}
}
// Gray out already downloaded packages
for (Common::ConfigManager::DomainMap::iterator domain = ConfMan.beginGameDomains(); domain != ConfMan.endGameDomains(); ++domain) {
if (domain->_value.contains("download")) {
Common::String id = domain->_value.getVal("download");
uint idx = DLCMan.getDLCIdxFromId(id);
if (idx != -1u && idx < games.size()) {
games[idx] = "\001C{alternate}" + games[idx];
}
}
}
_gamesList->setList(games);
g_gui.scheduleTopDialogRedraw();
}
void DLCsDialog::handleTickle() {
// enable download button only when a list item is selected
if (_gamesList->getSelected() == -1) {
if (_downloadButton->isEnabled()) {
_downloadButton->setEnabled(false);
g_gui.scheduleTopDialogRedraw();
}
} else {
if (!_downloadButton->isEnabled()) {
_downloadButton->setEnabled(true);
g_gui.scheduleTopDialogRedraw();
}
}
Dialog::handleTickle();
}
void DLCsDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {
switch (cmd) {
case kAllDownloadsCmd: {
DownloadDLCsDialog dialog;
dialog.runModal();
}
break;
case kDownloadSelectedCmd: {
DLCMan.addDownload(_gamesList->getSelected());
refreshDLCList();
}
break;
case kRefreshDLCList: {
refreshDLCList();
}
break;
default:
Dialog::handleCommand(sender, cmd, data);
}
}
} // End of namespace GUI

56
gui/dlcsdialog.h Normal file
View File

@@ -0,0 +1,56 @@
/* 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/>.
*
*/
#ifndef GUI_DLCSDIALOG_H
#define GUI_DLCSDIALOG_H
#include "gui/dialog.h"
namespace GUI {
enum {
kDownloadSelectedCmd = 'DWNS',
kRefreshDLCList = 'RDLC',
kAllDownloadsCmd = 'ALLD'
};
class CommandSender;
class ListWidget;
class ButtonWidget;
class DLCsDialog : public Dialog {
public:
DLCsDialog();
~DLCsDialog() override;
void handleTickle() override;
void handleCommand(CommandSender *sender, uint32 cmd, uint32 data) override;
void refreshDLCList();
private:
ListWidget *_gamesList;
ButtonWidget *_downloadButton;
};
} // End of namespace GUI
#endif

218
gui/downloaddialog.cpp Normal file
View File

@@ -0,0 +1,218 @@
/* 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 "gui/downloaddialog.h"
#include "backends/cloud/cloudmanager.h"
#include "common/config-manager.h"
#include "common/translation.h"
#include "common/util.h"
#include "engines/metaengine.h"
#include "gui/browser.h"
#include "gui/chooser.h"
#include "gui/editgamedialog.h"
#include "gui/gui-manager.h"
#include "gui/launcher.h"
#include "gui/message.h"
#include "gui/remotebrowser.h"
#include "gui/widgets/edittext.h"
#include "gui/widgets/list.h"
namespace GUI {
enum {
kDownloadDialogButtonCmd = 'Dldb'
};
DownloadDialog::DownloadDialog(uint32 storageId, LauncherDialog *launcher) :
Dialog("GlobalOptions_Cloud_DownloadDialog"), _launcher(launcher), _close(false) {
_backgroundType = GUI::ThemeEngine::kDialogBackgroundPlain;
_browser = new BrowserDialog(_("Select directory where to download game data"), true);
_remoteBrowser = new RemoteBrowserDialog(_("Select directory with game data"));
_remoteDirectoryLabel = new StaticTextWidget(this, "GlobalOptions_Cloud_DownloadDialog.RemoteDirectory", _("From: "));
_localDirectoryLabel = new StaticTextWidget(this, "GlobalOptions_Cloud_DownloadDialog.LocalDirectory", _("To: "));
uint32 progress = (uint32)(100 * CloudMan.getDownloadingProgress());
_progressBar = new SliderWidget(this, "GlobalOptions_Cloud_DownloadDialog.ProgressBar");
_progressBar->setMinValue(0);
_progressBar->setMaxValue(100);
_progressBar->setValue(progress);
_progressBar->setEnabled(false);
_percentLabel = new StaticTextWidget(this, "GlobalOptions_Cloud_DownloadDialog.PercentText", Common::String::format("%u %%", progress));
_downloadSizeLabel = new StaticTextWidget(this, "GlobalOptions_Cloud_DownloadDialog.DownloadSize", Common::U32String());
_downloadSpeedLabel = new StaticTextWidget(this, "GlobalOptions_Cloud_DownloadDialog.DownloadSpeed", Common::U32String());
if (!g_gui.useLowResGUI())
_cancelButton = new ButtonWidget(this, "GlobalOptions_Cloud_DownloadDialog.MainButton", _("Cancel download"), Common::U32String(), kDownloadDialogButtonCmd);
else
_cancelButton = new ButtonWidget(this, "GlobalOptions_Cloud_DownloadDialog.MainButton", _c("Cancel download", "lowres"), Common::U32String(), kDownloadDialogButtonCmd);
_closeButton = new ButtonWidget(this, "GlobalOptions_Cloud_DownloadDialog.CloseButton", _("Hide"), Common::U32String(), kCloseCmd);
refreshWidgets();
CloudMan.setDownloadTarget(this);
}
DownloadDialog::~DownloadDialog() {
CloudMan.setDownloadTarget(nullptr);
}
void DownloadDialog::open() {
Dialog::open();
CloudMan.setDownloadTarget(this);
if (!CloudMan.isDownloading())
if (!selectDirectories())
close();
reflowLayout();
g_gui.scheduleTopDialogRedraw();
}
void DownloadDialog::close() {
CloudMan.setDownloadTarget(nullptr);
Dialog::close();
}
void DownloadDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {
switch (cmd) {
case kDownloadDialogButtonCmd:
{
CloudMan.setDownloadTarget(nullptr);
CloudMan.cancelDownload();
close();
break;
}
case kDownloadProgressCmd:
if (!_close) {
refreshWidgets();
g_gui.scheduleTopDialogRedraw();
}
break;
case kDownloadEndedCmd:
_close = true;
break;
default:
Dialog::handleCommand(sender, cmd, data);
}
}
bool DownloadDialog::selectDirectories() {
if (g_system->isConnectionLimited()) {
MessageDialog alert(_("It looks like your connection is limited. "
"Do you really want to download files with it?"), _("Yes"), _("No"));
if (alert.runModal() != GUI::kMessageOK)
return false;
}
// First, the user should select the remote directory to download
if (_remoteBrowser->runModal() <= 0)
return false;
Cloud::StorageFile remoteDirectory = _remoteBrowser->getResult();
// Now, the user should select the local directory to download into
if (_browser->runModal() <= 0)
return false;
Common::FSNode dir(_browser->getResult());
Common::FSList files;
if (!dir.getChildren(files, Common::FSNode::kListAll)) {
MessageDialog alert(_("ScummVM couldn't open the specified directory!"));
alert.runModal();
return false;
}
// Check that there is no file with the remote directory's name in the local one
for (auto &file : files) {
if (file.getName().equalsIgnoreCase(remoteDirectory.name())) {
// If there is, ask user whether it's OK
if (!file.isDirectory()) {
GUI::MessageDialog alert(_("Cannot create a directory to download - the specified directory has a file with the same name."));
alert.runModal();
return false;
}
GUI::MessageDialog alert(
Common::U32String::format(_("The \"%s\" already exists in the specified directory.\nDo you really want to download files into that directory?"), remoteDirectory.name().c_str()),
_("Yes"),
_("No")
);
if (alert.runModal() != GUI::kMessageOK)
return false;
break;
}
}
// Make a local path
Common::Path localPath = dir.getPath();
localPath = localPath.appendComponent(remoteDirectory.name());
CloudMan.startDownload(remoteDirectory.path(), localPath);
CloudMan.setDownloadTarget(this);
_localDirectory = localPath;
return true;
}
void DownloadDialog::handleTickle() {
if (_close) {
if (_launcher)
_launcher->doGameDetection(_localDirectory);
close();
_close = false;
return;
}
int32 progress = (int32)(100 * CloudMan.getDownloadingProgress());
if (_progressBar->getValue() != progress) {
refreshWidgets();
g_gui.scheduleTopDialogRedraw();
}
Dialog::handleTickle();
}
void DownloadDialog::reflowLayout() {
Dialog::reflowLayout();
refreshWidgets();
}
Common::U32String DownloadDialog::getSizeLabelText() {
const char *downloadedUnits, *totalUnits;
Common::String downloaded = Common::getHumanReadableBytes(CloudMan.getDownloadBytesNumber(), downloadedUnits);
Common::String total = Common::getHumanReadableBytes(CloudMan.getDownloadTotalBytesNumber(), totalUnits);
return Common::U32String::format(_("Downloaded %s %S / %s %S"), downloaded.c_str(), _(downloadedUnits).c_str(), total.c_str(), _(totalUnits).c_str());
}
Common::U32String DownloadDialog::getSpeedLabelText() {
const char *speedUnits;
Common::String speed = Common::getHumanReadableBytes(CloudMan.getDownloadSpeed(), speedUnits);
return Common::U32String::format(_("Download speed: %s %S/s"), speed.c_str(), _(speedUnits).c_str());
}
void DownloadDialog::refreshWidgets() {
_localDirectory = CloudMan.getDownloadLocalDirectory();
_remoteDirectoryLabel->setLabel(_("From: ") + Common::U32String(CloudMan.getDownloadRemoteDirectory()));
_localDirectoryLabel->setLabel(_("To: ") + Common::U32String(_localDirectory.toString(Common::Path::kNativeSeparator)));
uint32 progress = (uint32)(100 * CloudMan.getDownloadingProgress());
_percentLabel->setLabel(Common::String::format("%u %%", progress));
_downloadSizeLabel->setLabel(getSizeLabelText());
_downloadSpeedLabel->setLabel(getSpeedLabelText());
_progressBar->setValue(progress);
}
} // End of namespace GUI

81
gui/downloaddialog.h Normal file
View File

@@ -0,0 +1,81 @@
/* 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/>.
*
*/
#ifndef GUI_DOWNLOADDIALOG_H
#define GUI_DOWNLOADDIALOG_H
#include "gui/dialog.h"
#include "common/str.h"
#include "common/ustr.h"
namespace GUI {
class LauncherDialog;
class CommandSender;
class EditTextWidget;
class StaticTextWidget;
class ButtonWidget;
class SliderWidget;
class BrowserDialog;
class RemoteBrowserDialog;
enum DownloadProgress {
kDownloadProgressCmd = 'DLPR',
kDownloadEndedCmd = 'DLEN'
};
class DownloadDialog : public Dialog {
LauncherDialog *_launcher;
BrowserDialog *_browser;
RemoteBrowserDialog *_remoteBrowser;
StaticTextWidget *_remoteDirectoryLabel;
StaticTextWidget *_localDirectoryLabel;
StaticTextWidget *_percentLabel;
StaticTextWidget *_downloadSizeLabel;
StaticTextWidget *_downloadSpeedLabel;
SliderWidget *_progressBar;
ButtonWidget *_cancelButton;
ButtonWidget *_closeButton;
Common::Path _localDirectory;
bool _close;
Common::U32String getSizeLabelText();
Common::U32String getSpeedLabelText();
void refreshWidgets();
bool selectDirectories();
public:
DownloadDialog(uint32 storageId, LauncherDialog *launcher);
~DownloadDialog() override;
void open() override;
void close() override;
void handleCommand(CommandSender *sender, uint32 cmd, uint32 data) override;
void handleTickle() override;
void reflowLayout() override;
};
} // End of namespace GUI
#endif

156
gui/downloaddlcsdialog.cpp Normal file
View File

@@ -0,0 +1,156 @@
/* 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/downloaddlcsdialog.h"
#include "gui/widget.h"
#include "gui/gui-manager.h"
#include "gui/widgets/list.h"
#include "backends/dlc/dlcmanager.h"
#include "backends/dlc/dlcdesc.h"
enum {
kCancelSelectedCmd = 'CANS',
};
namespace GUI {
DownloadDLCsDialog::DownloadDLCsDialog()
: Dialog("DLCDownloads") {
new StaticTextWidget(this, "DLCDownloads.Headline", _("Currently Downloading Games"));
_currentDownloadLabel = new StaticTextWidget(this, "DLCDownloads.CurrentDownload", Common::U32String());
_errorLabel = new StaticTextWidget(this, "DLCDownloads.ErrorText", Common::U32String(DLCMan._errorText));
_progressBar = new SliderWidget(this, "DLCDownloads.ProgressBar");
_progressBar->setMinValue(0);
_progressBar->setMaxValue(100);
_progressBar->setValue(0);
_progressBar->setEnabled(false);
_percentLabel = new StaticTextWidget(this, "DLCDownloads.PercentText", Common::String());
_downloadedSizeLabel = new StaticTextWidget(this, "DLCDownloads.DownloadedSize", Common::U32String());
new StaticTextWidget(this, "DLCDownloads.Pending", _("Pending Downloads"));
_pendingDownloadsList = new ListWidget(this, "DLCDownloads.List");
_pendingDownloadsList->setNumberingMode(kListNumberingOff);
_pendingDownloadsList->setEditable(false);
new ButtonWidget(this, "DLCDownloads.Back", _("Back"), Common::U32String(), kCloseCmd);
_cancelButton = new ButtonWidget(this, "DLCDownloads.Cancel", _("Cancel"), Common::U32String(), kCancelSelectedCmd);
refreshWidgets();
}
Common::U32String DownloadDLCsDialog::getSizeLabelText() {
const char *downloadedUnits, *totalUnits;
Common::String downloaded = Common::getHumanReadableBytes(DLCMan._currentDownloadedSize, downloadedUnits);
Common::String total = Common::getHumanReadableBytes(DLCMan._queuedDownloadTasks.front()->size, totalUnits);
return Common::U32String::format(_("Downloaded %s %S / %s %S"), downloaded.c_str(), _(downloadedUnits).c_str(), total.c_str(), _(totalUnits).c_str());
}
uint32 DownloadDLCsDialog::getDownloadingProgress() {
if (DLCMan._queuedDownloadTasks.empty()) {
// no DLC is currently downloading
return 0;
}
uint32 progress = (uint32)(100 * ((double)DLCMan._currentDownloadedSize / (double)DLCMan._queuedDownloadTasks.front()->size));
return progress;
}
void DownloadDLCsDialog::refreshWidgets() {
Common::U32StringArray pendingList;
if (DLCMan._queuedDownloadTasks.empty()) {
// no DLC is currently downloading
_currentDownloadLabel->setLabel(Common::U32String("No downloads in progress"));
_downloadedSizeLabel->setLabel(Common::U32String());
_pendingDownloadsList->setList(pendingList);
} else {
_currentDownloadLabel->setLabel(DLCMan._queuedDownloadTasks.front()->name);
_downloadedSizeLabel->setLabel(getSizeLabelText());
for (const auto &it : DLCMan._dlcsInProgress) {
if (it->state == DLC::DLCDesc::kInProgress) {
pendingList.push_back(it->name);
} else {
pendingList.push_back("[Cancelled] " + it->name);
}
}
_pendingDownloadsList->setList(pendingList);
if (_progressBar->getValue() >= 100) {
// if a game is downloaded i.e. the first item is removed from _dlcsInProgress
_selectedIdx--;
}
_pendingDownloadsList->setSelected(_selectedIdx);
}
uint32 progress = getDownloadingProgress();
_percentLabel->setLabel(Common::String::format("%u %%", progress));
_progressBar->setValue(progress);
g_gui.scheduleTopDialogRedraw();
}
void DownloadDLCsDialog::handleTickle() {
int32 progress = getDownloadingProgress();
if (_progressBar->getValue() != progress) {
_selectedIdx = _pendingDownloadsList->getSelected();
refreshWidgets();
}
// enable cancel button only when a list item is selected
if (_pendingDownloadsList->getSelected() == -1) {
if (_cancelButton->isEnabled()) {
_cancelButton->setEnabled(false);
g_gui.scheduleTopDialogRedraw();
}
} else {
if (!_cancelButton->isEnabled()) {
_cancelButton->setEnabled(true);
g_gui.scheduleTopDialogRedraw();
}
}
if (_errorLabel->getLabel() != DLCMan._errorText) {
_errorLabel->setLabel(DLCMan._errorText);
g_gui.scheduleTopDialogRedraw();
}
Dialog::handleTickle();
}
void DownloadDLCsDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {
switch (cmd) {
case kCancelSelectedCmd: {
uint32 idx = DLCMan._dlcsInProgress[_pendingDownloadsList->getSelected()]->idx;
DLCMan.cancelDownload(idx);
}
break;
default:
Dialog::handleCommand(sender, cmd, data);
}
}
} // End of namespace GUI

61
gui/downloaddlcsdialog.h Normal file
View File

@@ -0,0 +1,61 @@
/* 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/>.
*
*/
#ifndef GUI_DOWNLOADDLCSDIALOG_H
#define GUI_DOWNLOADDLCSDIALOG_H
#include "gui/dialog.h"
namespace GUI {
class CommandSender;
class StaticTextWidget;
class SliderWidget;
class ListWidget;
class ButtonWidget;
class DownloadDLCsDialog : public Dialog {
public:
DownloadDLCsDialog();
void handleCommand(CommandSender *sender, uint32 cmd, uint32 data) override;
void handleTickle() override;
uint32 getDownloadingProgress();
private:
StaticTextWidget *_currentDownloadLabel;
StaticTextWidget *_errorLabel;
StaticTextWidget *_downloadedSizeLabel;
StaticTextWidget *_percentLabel;
SliderWidget *_progressBar;
ListWidget *_pendingDownloadsList;
ButtonWidget *_cancelButton;
int _selectedIdx = -1;
Common::U32String getSizeLabelText();
void refreshWidgets();
};
} // End of namespace GUI
#endif

494
gui/downloadpacksdialog.cpp Normal file
View File

@@ -0,0 +1,494 @@
/* 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 "backends/networking/http/request.h"
#include "gui/downloadpacksdialog.h"
#include "gui/downloaddialog.h"
#include "backends/networking/http/session.h"
#include "common/config-manager.h"
#include "common/translation.h"
#include "common/util.h"
#include "engines/metaengine.h"
#include "gui/browser.h"
#include "gui/chooser.h"
#include "gui/editgamedialog.h"
#include "gui/gui-manager.h"
#include "gui/launcher.h"
#include "gui/message.h"
#include "gui/remotebrowser.h"
#include "gui/widgets/edittext.h"
#include "gui/widgets/list.h"
namespace GUI {
enum {
kDownloadProceedCmd = 'Dlpr',
kListDownloadFinishedCmd = 'DlLE',
kCleanupCmd = 'DlCL',
kClearCacheCmd = 'DlCC'
};
struct DialogState {
DownloadPacksDialog *dialog;
Networking::Session session;
Common::HashMap<Common::String, uint32> fileHash;
IconProcessState state;
uint32 downloadedSize;
uint32 totalSize;
uint32 totalFiles;
uint32 startTime;
const char *listfname;
uint32 lastUpdate;
DialogState(const char *fname) {
listfname = fname;
state = kDownloadStateNone;
downloadedSize = totalSize = totalFiles = startTime = lastUpdate = 0;
dialog = nullptr;
}
void downloadList();
void proceedDownload();
void downloadListCallback(const Networking::DataResponse &response);
void downloadFileCallback(const Networking::DataResponse &response);
void errorCallback(const Networking::ErrorResponse &error);
private:
bool takeOneFile();
} static *g_state;
void DialogState::downloadList() {
Networking::SessionRequest *rq = session.get(Common::String::format("https://downloads.scummvm.org/frs/icons/%s", listfname), Common::Path(),
new Common::Callback<DialogState, const Networking::DataResponse &>(this, &DialogState::downloadListCallback),
new Common::Callback<DialogState, const Networking::ErrorResponse &>(this, &DialogState::errorCallback),
true);
rq->start();
}
void DialogState::proceedDownload() {
startTime = lastUpdate = g_system->getMillis();
downloadedSize = 0;
g_system->taskStarted(OSystem::kDataPackDownload);
takeOneFile();
}
bool DialogState::takeOneFile() {
auto f = fileHash.begin();
if (f == fileHash.end())
return false;
Common::String fname = f->_key;
fileHash.erase(fname);
Common::String url = Common::String::format("https://downloads.scummvm.org/frs/icons/%s", fname.c_str());
Common::Path localFile = ConfMan.getPath("iconspath").join(fname).normalize();
Networking::SessionRequest *rq = session.get(url, localFile,
new Common::Callback<DialogState, const Networking::DataResponse &>(this, &DialogState::downloadFileCallback),
new Common::Callback<DialogState, const Networking::ErrorResponse &>(this, &DialogState::errorCallback));
rq->start();
return true;
}
void DialogState::downloadListCallback(const Networking::DataResponse &r) {
Networking::SessionFileResponse *response = static_cast<Networking::SessionFileResponse *>(r.value);
Common::MemoryReadStream stream(response->buffer, response->len);
int nline = 0;
while (!stream.eos()) {
Common::String s = stream.readString('\n');
nline++;
if (s.empty())
continue;
size_t pos = s.findFirstOf(',');
if (pos == Common::String::npos) {
warning("DownloadPacksDialog: wrong string format at line %d: <%s>", nline, s.c_str());
continue;
}
fileHash.setVal(s.substr(0, pos), atol(s.substr(pos + 1).c_str()));
}
state = kDownloadStateListDownloaded;
if (dialog)
dialog->sendCommand(kListDownloadFinishedCmd, 0);
}
void DialogState::downloadFileCallback(const Networking::DataResponse &r) {
Networking::SessionFileResponse *response = static_cast<Networking::SessionFileResponse *>(r.value);
downloadedSize += response->len;
if (response->eos) {
if (!takeOneFile()) {
state = kDownloadComplete;
g_system->taskFinished(OSystem::kDataPackDownload);
if (dialog)
dialog->sendCommand(kDownloadEndedCmd, 0);
return;
}
}
if (dialog && g_system->getMillis() > lastUpdate + 500) {
lastUpdate = g_system->getMillis();
dialog->sendCommand(kDownloadProgressCmd, 0);
}
}
void DialogState::errorCallback(const Networking::ErrorResponse &error) {
Common::U32String message = Common::U32String::format(_("ERROR %d: %s"), error.httpResponseCode, error.response.c_str());
g_system->taskFinished(OSystem::kDataPackDownload);
if (dialog)
dialog->setError(message);
}
static uint32 getDownloadingProgress() {
if (!g_state || g_state->totalSize == 0)
return 0;
uint32 progress = (uint32)(100 * ((double)g_state->downloadedSize / (double)g_state->totalSize));
return progress;
}
static uint32 getDownloadSpeed() {
uint32 speed = 1000 * ((double)g_state->downloadedSize / (g_system->getMillis() - g_state->startTime));
return speed;
}
DownloadPacksDialog::DownloadPacksDialog(Common::U32String packname, const char *listfname, const char *packsglob) :
Dialog("GlobalOptions_DownloadPacksDialog"), CommandSender(this), _close(false), _packname(packname),
_packsglob(packsglob) {
_backgroundType = GUI::ThemeEngine::kDialogBackgroundPlain;
// I18N: String like "Downloading icon packs list..."
_statusText = new StaticTextWidget(this, "GlobalOptions_DownloadPacksDialog.StatusText", Common::U32String::format(_("Downloading %S list..."), _packname.c_str()));
_errorText = new StaticTextWidget(this, "GlobalOptions_DownloadPacksDialog.ErrorText", Common::U32String(""));
uint32 progress = getDownloadingProgress();
_progressBar = new SliderWidget(this, "GlobalOptions_DownloadPacksDialog.ProgressBar");
_progressBar->setMinValue(0);
_progressBar->setMaxValue(100);
_progressBar->setValue(progress);
_progressBar->setEnabled(false);
_percentLabel = new StaticTextWidget(this, "GlobalOptions_DownloadPacksDialog.PercentText", Common::String::format("%u %%", progress));
_downloadSizeLabel = new StaticTextWidget(this, "GlobalOptions_DownloadPacksDialog.DownloadSize", Common::U32String());
_downloadSpeedLabel = new StaticTextWidget(this, "GlobalOptions_DownloadPacksDialog.DownloadSpeed", Common::U32String());
_cancelButton = new ButtonWidget(this, "GlobalOptions_DownloadPacksDialog.MainButton", _("Cancel download"), Common::U32String(), kCleanupCmd);
_closeButton = new ButtonWidget(this, "GlobalOptions_DownloadPacksDialog.CloseButton", _("Hide"), Common::U32String(), kCloseCmd);
_clearCacheButton = new ButtonWidget(this, "GlobalOptions_DownloadPacksDialog.ResetButton", _("Clear Cache"), Common::U32String(), kClearCacheCmd);
if (!g_state) {
g_state = new DialogState(listfname);
g_state->dialog = this;
setState(kDownloadStateList);
refreshWidgets();
g_state->downloadList();
} else {
g_state->dialog = this;
setState(g_state->state);
refreshWidgets();
}
}
DownloadPacksDialog::~DownloadPacksDialog() {
}
void DownloadPacksDialog::open() {
Dialog::open();
reflowLayout();
g_gui.scheduleTopDialogRedraw();
}
void DownloadPacksDialog::close() {
if (g_state)
g_state->dialog = nullptr;
Dialog::close();
}
void DownloadPacksDialog::setState(IconProcessState state) {
g_state->state = state;
_errorText->setLabel(Common::U32String());
switch (state) {
case kDownloadStateNone:
case kDownloadStateList:
_statusText->setLabel(Common::U32String::format(_("Downloading %S list..."), _packname.c_str()));
_cancelButton->setLabel(_("Cancel download"));
_cancelButton->setCmd(kCleanupCmd);
_closeButton->setVisible(true);
g_state->totalSize = 0;
g_state->fileHash.clear();
break;
case kDownloadStateListDownloaded:
_statusText->setLabel(Common::U32String::format(_("Downloading %S list... %d entries"), _packname.c_str(), g_state->fileHash.size()));
_cancelButton->setLabel(_("Cancel download"));
_cancelButton->setCmd(kCleanupCmd);
_closeButton->setVisible(true);
break;
case kDownloadStateListCalculated: {
const char *sizeUnits;
Common::String size = Common::getHumanReadableBytes(g_state->totalSize, sizeUnits);
_statusText->setLabel(Common::U32String::format(_("Detected %d new packs, %s %S"), g_state->fileHash.size(), size.c_str(), _(sizeUnits).c_str()));
_cancelButton->setLabel(_("Download"));
_cancelButton->setCmd(kDownloadProceedCmd);
_closeButton->setVisible(true);
_closeButton->setLabel(_("Cancel"));
_closeButton->setCmd(kCleanupCmd);
_closeButton->setEnabled(true);
break;
}
case kDownloadStateDownloading:
_cancelButton->setLabel(_("Cancel download"));
_cancelButton->setCmd(kCleanupCmd);
_closeButton->setVisible(true);
_closeButton->setLabel(_("Hide"));
_closeButton->setCmd(kCloseCmd);
_closeButton->setEnabled(true);
_clearCacheButton->setEnabled(false);
break;
case kDownloadComplete: {
const char *sizeUnits;
Common::String size = Common::getHumanReadableBytes(g_state->totalSize, sizeUnits);
_statusText->setLabel(Common::U32String::format(_("Download complete, downloaded %d packs, %s %S"), g_state->totalFiles, size.c_str(), _(sizeUnits).c_str()));
_cancelButton->setVisible(false);
_cancelButton->setLabel(_("Cancel download"));
_cancelButton->setCmd(kCleanupCmd);
_closeButton->setVisible(true);
_closeButton->setLabel(_("Close"));
_closeButton->setCmd(kCleanupCmd);
_closeButton->setEnabled(true);
_clearCacheButton->setEnabled(true);
break;
}
}
}
void DownloadPacksDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {
switch (cmd) {
case kCleanupCmd:
{
g_state->session.abortRequest();
delete g_state;
g_state = nullptr;
close();
break;
}
case kDownloadProgressCmd:
if (!_close) {
refreshWidgets();
g_gui.scheduleTopDialogRedraw();
}
break;
case kDownloadEndedCmd:
setState(kDownloadComplete);
break;
case kListDownloadFinishedCmd:
setState(kDownloadStateListDownloaded);
calculateList();
break;
case kDownloadProceedCmd:
setState(kDownloadStateDownloading);
g_state->proceedDownload();
break;
case kClearCacheCmd:
_clearCacheButton->setEnabled(false);
clearCache();
break;
default:
Dialog::handleCommand(sender, cmd, data);
}
}
void DownloadPacksDialog::handleTickle() {
if (_close) {
close();
_close = false;
return;
}
int32 progress = getDownloadingProgress();
if (_progressBar->getValue() != progress) {
refreshWidgets();
g_gui.scheduleTopDialogRedraw();
}
Dialog::handleTickle();
}
void DownloadPacksDialog::reflowLayout() {
Dialog::reflowLayout();
refreshWidgets();
}
Common::U32String DownloadPacksDialog::getSizeLabelText() {
const char *downloadedUnits, *totalUnits;
Common::String downloaded = Common::getHumanReadableBytes(g_state->downloadedSize, downloadedUnits);
Common::String total = Common::getHumanReadableBytes(g_state->totalSize, totalUnits);
return Common::U32String::format(_("Downloaded %s %S / %s %S"), downloaded.c_str(), _(downloadedUnits).c_str(), total.c_str(), _(totalUnits).c_str());
}
Common::U32String DownloadPacksDialog::getSpeedLabelText() {
const char *speedUnits;
Common::String speed = Common::getHumanReadableBytes(getDownloadSpeed(), speedUnits);
return Common::U32String::format(_("Download speed: %s %S/s"), speed.c_str(), _(speedUnits).c_str());
}
void DownloadPacksDialog::refreshWidgets() {
uint32 progress = getDownloadingProgress();
_percentLabel->setLabel(Common::String::format("%u %%", progress));
_downloadSizeLabel->setLabel(getSizeLabelText());
_downloadSpeedLabel->setLabel(getSpeedLabelText());
_progressBar->setValue(progress);
}
void DownloadPacksDialog::setError(Common::U32String &msg) {
_errorText->setLabel(msg);
_cancelButton->setLabel(_("Close"));
_cancelButton->setCmd(kCleanupCmd);
}
void DownloadPacksDialog::calculateList() {
Common::Path iconsPath = ConfMan.getPath("iconspath");
if (iconsPath.empty()) {
Common::U32String str(_("ERROR: No icons path set"));
setError(str);
return;
}
// Scan all files in iconspath and remove present and incomplete ones from the
// donwloaded files list
Common::FSDirectory iconDir(iconsPath);
Common::ArchiveMemberList iconFiles;
iconDir.listMatchingMembers(iconFiles, _packsglob);
for (auto ic = iconFiles.begin(); ic != iconFiles.end(); ++ic) {
Common::String fname = (*ic)->getName();
Common::SeekableReadStream *str = (*ic)->createReadStream();
uint32 size = str->size();
delete str;
if (g_state->fileHash.contains(fname) && size == g_state->fileHash[fname])
g_state->fileHash.erase(fname);
}
// Now calculate the size of the missing files
g_state->totalSize = 0;
for (auto f = g_state->fileHash.begin(); f != g_state->fileHash.end(); ++f) {
g_state->totalSize += f->_value;
}
g_state->totalFiles = g_state->fileHash.size();
if (g_state->totalSize == 0) {
// I18N: String like "No new icon packs available"
Common::U32String error(Common::U32String::format(_("No new %S available"), _packname.c_str()));
_closeButton->setEnabled(false);
setError(error);
return;
}
setState(kDownloadStateListCalculated);
}
void DownloadPacksDialog::clearCache() {
Common::Path iconsPath = ConfMan.getPath("iconspath");
if (iconsPath.empty()) {
Common::U32String str(_("ERROR: No icons path set"));
setError(str);
return;
}
Common::FSDirectory iconDir(iconsPath);
Common::ArchiveMemberList iconFiles;
iconDir.listMatchingMembers(iconFiles, _packsglob);
int totalSize = 0;
for (auto ic = iconFiles.begin(); ic != iconFiles.end(); ++ic) {
Common::String fname = (*ic)->getName();
Common::SeekableReadStream *str = (*ic)->createReadStream();
uint32 size = str->size();
delete str;
totalSize += size;
}
const char *sizeUnits;
Common::String size = Common::getHumanReadableBytes(totalSize, sizeUnits);
GUI::MessageDialog dialog(Common::U32String::format(_("You are about to remove %s %S of data, deleting all previously downloaded %S. Do you want to proceed?"), size.c_str(), _(sizeUnits).c_str(), _packname.c_str()), _("Proceed"), _("Cancel"));
if (dialog.runModal() == ::GUI::kMessageOK) {
// Cancel all downloads
g_state->session.abortRequest();
// Build list of previously downloaded icon files
for (auto ic = iconFiles.begin(); ic != iconFiles.end(); ++ic) {
Common::String fname = (*ic)->getName();
Common::FSNode fs(iconsPath.join(fname));
Common::WriteStream *str = fs.createWriteStream();
// Overwrite previously downloaded pack files with dummy data
str->writeByte(0);
str->finalize();
delete str;
}
g_state->fileHash.clear();
// Fetch current packs list file and re-trigger downloads
setState(kDownloadStateList);
_cancelButton->setVisible(true);
refreshWidgets();
g_state->downloadList();
} else {
_clearCacheButton->setEnabled(true);
}
}
} // End of namespace GUI

90
gui/downloadpacksdialog.h Normal file
View File

@@ -0,0 +1,90 @@
/* 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/>.
*
*/
#ifndef GUI_DOWNLOADPACKSDIALOG_H
#define GUI_DOWNLOADPACKSDIALOG_H
#include "gui/dialog.h"
#include "common/str.h"
#include "common/ustr.h"
namespace Networking {
class Session;
}
namespace GUI {
class CommandSender;
class StaticTextWidget;
class ButtonWidget;
class SliderWidget;
enum IconProcessState {
kDownloadStateNone,
kDownloadStateList,
kDownloadStateListDownloaded,
kDownloadStateListCalculated,
kDownloadStateDownloading,
kDownloadComplete
};
class DownloadPacksDialog : public Dialog, public CommandSender {
StaticTextWidget *_statusText;
StaticTextWidget *_errorText;
StaticTextWidget *_percentLabel;
StaticTextWidget *_downloadSizeLabel;
StaticTextWidget *_downloadSpeedLabel;
SliderWidget *_progressBar;
ButtonWidget *_cancelButton;
ButtonWidget *_closeButton;
ButtonWidget *_clearCacheButton;
Common::U32String _packname;
const char *_packsglob;
Common::String _localDirectory;
bool _close;
Common::U32String getSizeLabelText();
Common::U32String getSpeedLabelText();
void refreshWidgets();
public:
DownloadPacksDialog(Common::U32String packname, const char *listfname, const char *packsglob);
~DownloadPacksDialog() override;
void open() override;
void close() override;
void handleCommand(CommandSender *sender, uint32 cmd, uint32 data) override;
void handleTickle() override;
void reflowLayout() override;
void setError(Common::U32String &msg);
private:
void calculateList();
void clearCache();
void setState(IconProcessState state);
};
} // End of namespace GUI
#endif

226
gui/dump-all-dialogs.cpp Normal file
View File

@@ -0,0 +1,226 @@
/* 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 "gui/dump-all-dialogs.h"
#include "common/file.h"
#include "common/fs.h"
#include "common/language.h"
#include "common/translation.h"
#include "gui/ThemeEngine.h"
#include "gui/about.h"
#include "gui/gui-manager.h"
#include "gui/launcher.h"
#include "gui/message.h"
#include "gui/browser.h"
#include "gui/downloaddialog.h"
#include "gui/remotebrowser.h"
#include "gui/chooser.h"
#include "gui/cloudconnectionwizard.h"
#include "gui/dialog.h"
#include "gui/downloaddialog.h"
#include "gui/downloadpacksdialog.h"
#include "gui/fluidsynth-dialog.h"
#include "gui/themebrowser.h"
#include "gui/massadd.h"
#include "gui/options.h"
#include "gui/widgets/tab.h"
#include "gui/launcher.h"
#include "image/png.h"
namespace GUI {
void saveGUISnapshot(Graphics::Surface surf, const Common::String &filename) {
Common::DumpFile outFile;
Common::String outName = Common::String::format("snapshots/%s", filename.c_str());
if (outFile.open(Common::Path(outName, '/'))) {
Image::writePNG(outFile, surf);
outFile.finalize();
outFile.close();
warning("Dumped %s", filename.c_str());
}
}
void handleSimpleDialog(GUI::Dialog &dialog, const Common::String &filename,Graphics::Surface surf) {
dialog.open(); // For rendering
dialog.reflowLayout(); // For updating surface
g_gui.redrawFull();
g_system->grabOverlay(surf);
saveGUISnapshot(surf, filename);
dialog.close();
}
void loopThroughTabs(GUI::Dialog &dialog, const Common::String &lang, Graphics::Surface surf, const Common::String name) {
dialog.open();
GUI::Widget *widget = nullptr;
widget = dialog.findWidget((uint32)kTabWidget);
if (widget) {
TabWidget *tabWidget = (TabWidget *)widget;
for (int tabNo = 0; tabNo < tabWidget->getTabCount(); tabNo++) {
Common::String suffix = Common::String::format("-%d-%dx%d-%s.png", tabNo + 1, g_system->getOverlayWidth(), g_system->getOverlayHeight(), lang.c_str());
tabWidget->setActiveTab(tabNo);
handleSimpleDialog(dialog, name + suffix, surf);
}
}
dialog.close();
}
void dumpDialogs(const Common::String &message, const Common::String &lang) {
#ifdef USE_TRANSLATION
// Update GUI language
TransMan.setLanguage(lang);
#endif
Graphics::Surface surf;
surf.create(g_system->getOverlayWidth(), g_system->getOverlayHeight(), g_system->getOverlayFormat());
Common::String suffix = Common::String::format("-%dx%d-%s.png", g_system->getOverlayWidth(), g_system->getOverlayHeight(), lang.c_str());
// Skipping Tooltips as not required
// MessageDialog
GUI::MessageDialog messageDialog(message);
handleSimpleDialog(messageDialog, "messageDialog" + suffix, surf);
// AboutDialog
GUI::AboutDialog aboutDialog;
handleSimpleDialog(aboutDialog, "aboutDialog" + suffix, surf);
#ifdef USE_CLOUD
// CloudConnectingWizard
GUI::CloudConnectionWizard cloudConnectingWizard;
handleSimpleDialog(cloudConnectingWizard, "cloudConnectingWizard" + suffix, surf);
// RemoteBrowserDialog
GUI::RemoteBrowserDialog remoteBrowserDialog(_("Select directory with game data"));
handleSimpleDialog(remoteBrowserDialog, "remoteBrowserDialog" + suffix, surf);
#endif
#ifdef USE_HTTP
// DownloadIconPacksDialog
GUI::DownloadPacksDialog downloadIconPacksDialog(_("icon packs"), "LIST", "gui-icons*.dat");
handleSimpleDialog(downloadIconPacksDialog, "downloadIconPacksDialog" + suffix, surf);
// DownloadShaderPacksDialog
GUI::DownloadPacksDialog downloadShaderPacksDialog(_("shader packs"), "LIST-SHADERS", "shaders*.dat");
handleSimpleDialog(downloadShaderPacksDialog, "downloadShaderPacksDialog" + suffix, surf);
#endif
#ifdef USE_FLUIDSYNTH
// FluidSynthSettingsDialog
GUI::FluidSynthSettingsDialog fluidSynthSettingsDialog;
handleSimpleDialog(fluidSynthSettingsDialog, "fluidSynthSettings-" + suffix, surf);
#endif
// ThemeBrowserDialog
GUI::ThemeBrowser themeBrowser;
handleSimpleDialog(themeBrowser, "themeBrowser-" + suffix, surf);
// BrowserDialog
GUI::BrowserDialog browserDialog(_("Select directory with game data"), true);
handleSimpleDialog(browserDialog, "browserDialog-" + suffix, surf);
// ChooserDialog
GUI::ChooserDialog chooserDialog(_("Pick the game:"));
handleSimpleDialog(chooserDialog, "chooserDialog-" + suffix, surf);
// MassAddDialog
GUI::MassAddDialog massAddDialog(Common::FSNode("."));
handleSimpleDialog(massAddDialog, "massAddDialog-" + suffix, surf);
// GlobalOptionsDialog
LauncherSimple launcherDialog("Launcher");
GUI::GlobalOptionsDialog globalOptionsDialog(&launcherDialog);
loopThroughTabs(globalOptionsDialog, lang, surf, "GlobalOptionDialog");
// LauncherDialog
#if 0
GUI::LauncherChooser chooser;
chooser.selectLauncher();
chooser.open();
g_system->grabOverlay(surf);
saveGUISnapshot(surf, "launcher-" + filename);
chooser.close();
#endif
}
void dumpAllDialogs(const Common::String &message) {
#ifdef USE_TRANSLATION
auto originalLang = TransMan.getCurrentLanguage();
#endif
int original_window_width = ConfMan.getInt("last_window_width", Common::ConfigManager::kApplicationDomain);
int original_window_height = ConfMan.getInt("last_window_height", Common::ConfigManager::kApplicationDomain);
Common::List<Common::String> list = Common::getLanguageList();
const int res[] = {320, 200,
320, 240,
640, 400,
640, 480,
800, 600,
0};
// HACK: Pass info to backend to force window resize
ConfMan.setBool("force_resize", true, Common::ConfigManager::kApplicationDomain);
Common::FSNode dumpDir("snapshots");
if (!dumpDir.isDirectory())
dumpDir.createDirectory();
// Iterate through all resolutions available
for (const int *r = res; *r; r += 2) {
int w = r[0];
int h = r[1];
// Update resolution
ConfMan.setInt("last_window_width" , w, Common::ConfigManager::kApplicationDomain);
ConfMan.setInt("last_window_height", h, Common::ConfigManager::kApplicationDomain);
g_system->beginGFXTransaction();
g_system->initSize(w, h);
g_system->endGFXTransaction();
// Iterate through all langauges
for (Common::String &lang : list) {
dumpDialogs(message, lang);
}
}
#ifdef USE_TRANSLATION
TransMan.setLanguage(originalLang);
#endif
ConfMan.setInt("last_window_width", original_window_width, Common::ConfigManager::kApplicationDomain);
ConfMan.setInt("last_window_height", original_window_height, Common::ConfigManager::kApplicationDomain);
g_system->beginGFXTransaction();
g_system->initSize(original_window_width, original_window_height);
g_system->endGFXTransaction();
// Clean up the temporary flag.
// Since we are still within the same method where we added,
// there is no need to flush config to the disk
ConfMan.removeKey("force_resize", Common::ConfigManager::kApplicationDomain);
}
} // End of namespace GUI

38
gui/dump-all-dialogs.h Normal file
View File

@@ -0,0 +1,38 @@
/* 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/>.
*
*/
#ifndef GUI_DUMP_ALL_DIALOGS_H
#define GUI_DUMP_ALL_DIALOGS_H
#include "common/str.h"
#include "graphics/surface.h"
#include "gui/dialog.h"
namespace GUI {
void saveGUISnapshot(Graphics::Surface surf, const Common::String &filename);
void dumpDialogs(const Common::String &message, const Common::String &lang);
void dumpAllDialogs(const Common::String &message = "test");
void loopThroughTabs(GUI::Dialog &dialog, const Common::String &lang, Graphics::Surface surf, const Common::String name);
} // End of namespace GUI
#endif // GUI_DUMP_ALL_DIALOGS_H

684
gui/editgamedialog.cpp Normal file
View File

@@ -0,0 +1,684 @@
/* 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 "gui/editgamedialog.h"
#include "backends/keymapper/keymapper.h"
#include "common/config-manager.h"
#include "common/gui_options.h"
#include "common/translation.h"
#include "common/system.h"
#include "gui/browser.h"
#include "gui/gui-manager.h"
#include "gui/message.h"
#ifdef ENABLE_EVENTRECORDER
#include "gui/onscreendialog.h"
#include "gui/recorderdialog.h"
#include "gui/EventRecorder.h"
#endif
#include "gui/integrity-dialog.h"
#include "gui/widgets/edittext.h"
#include "gui/widgets/tab.h"
#include "gui/widgets/popup.h"
#include "gui/widgets/scrollcontainer.h"
#ifdef USE_CLOUD
#include "backends/cloud/cloudmanager.h"
#endif
using Common::ConfigManager;
namespace GUI {
enum {
kStartCmd = 'STRT',
kAboutCmd = 'ABOU',
kOptionsCmd = 'OPTN',
kAddGameCmd = 'ADDG',
kEditGameCmd = 'EDTG',
kRemoveGameCmd = 'REMG',
kLoadGameCmd = 'LOAD',
kQuitCmd = 'QUIT',
kSearchCmd = 'SRCH',
kListSearchCmd = 'LSSR',
kSearchClearCmd = 'SRCL',
kCmdGlobalGraphicsOverride = 'OGFX',
kCmdGlobalBackendOverride = 'OBAK',
kCmdGlobalAudioOverride = 'OSFX',
kCmdGlobalMIDIOverride = 'OMID',
kCmdGlobalMT32Override = 'OM32',
kCmdGlobalVolumeOverride = 'OVOL',
kCmdChooseSoundFontCmd = 'chsf',
kCmdExtraBrowser = 'PEXT',
kCmdExtraPathClear = 'PEXC',
kCmdGameBrowser = 'PGME',
kCmdSaveBrowser = 'PSAV',
kCmdSavePathClear = 'PSAC',
kCmdCheckIntegrity = 'PCHI',
kGraphicsTabContainerReflowCmd = 'gtcr'
};
class DomainEditTextWidget : public EditTextWidget {
public:
DomainEditTextWidget(GuiObject *boss, const Common::String &name, const Common::U32String &text, const Common::U32String &tooltip)
: EditTextWidget(boss, name, text, tooltip) {}
protected:
bool isCharAllowed(Common::u32char_type_t c) const override {
return Common::isAlnum(c) || c == '-' || c == '_';
}
};
EditGameDialog::EditGameDialog(const Common::String &domain)
: OptionsDialog(domain, "GameOptions") {
EngineMan.upgradeTargetIfNecessary(domain);
_engineOptions = nullptr;
// Retrieve the plugin, since we need to access the engine's MetaEngine
// implementation.
const Plugin *enginePlugin = nullptr;
QualifiedGameDescriptor qgd = EngineMan.findTarget(domain);
#if defined(UNCACHED_PLUGINS) && defined(DYNAMIC_MODULES) && !defined(DETECTION_STATIC)
// Unload all MetaEnginesDetection if we're using uncached plugins to save extra memory.
PluginMan.unloadDetectionPlugin();
#endif
enginePlugin = PluginMan.findEnginePlugin(qgd.engineId);
if (!enginePlugin) {
warning("Engine Plugin for target \"%s\" not found! Game specific settings might be missing.", domain.c_str());
}
// GAME: Path to game data (r/o), extra data (r/o), and save data (r/w)
Common::Path gamePath(ConfMan.getPath("path", _domain));
Common::Path extraPath(ConfMan.getPath("extrapath", _domain));
if (!ConfMan.hasKey("extrapath", _domain)) {
extraPath.clear();
}
Common::Path savePath(ConfMan.getPath("savepath", _domain));
if (!ConfMan.hasKey("savepath", _domain)) {
savePath.clear();
}
// GAME: Determine the description string
Common::String description(ConfMan.get("description", domain));
if (description.empty() && !qgd.description.empty()) {
description = qgd.description;
}
// GUI: Add tab widget
TabWidget *tab = new TabWidget(this, "GameOptions.TabWidget");
//
// 1) The game tab
//
tab->addTab(_("Game"), "GameOptions_Game");
_gameContainer = new ScrollContainerWidget(tab, "GameOptions_Game.Container", "GameOptions_Game_Container");
_gameContainer->setBackgroundType(ThemeEngine::kWidgetBackgroundNo);
_gameContainer->setTarget(this);
addGameControls(_gameContainer, "GameOptions_Game_Container.", description);
//
// 2) The engine's game settings (shown only if the engine implements one or there are custom engine options)
//
if (enginePlugin) {
enginePlugin->get<MetaEngine>().registerDefaultSettings(_domain);
_engineOptions = enginePlugin->get<MetaEngine>().buildEngineOptionsWidget(_gameContainer, "GameOptions_Game_Container.Container", _domain);
if (_engineOptions) {
_engineOptions->setParentDialog(this);
}
}
//
// 3) The graphics tab
//
_graphicsTabId = tab->addTab(g_gui.useLowResGUI() ? _("GFX") : _("Graphics"), "GameOptions_Graphics");
ScrollContainerWidget *graphicsContainer = new ScrollContainerWidget(tab, "GameOptions_Graphics.Container", "GameOptions_Graphics_Container", kGraphicsTabContainerReflowCmd);
graphicsContainer->setBackgroundType(ThemeEngine::kWidgetBackgroundNo);
graphicsContainer->setTarget(this);
if (!g_gui.useLowResGUI())
_globalGraphicsOverride = new CheckboxWidget(graphicsContainer, "GameOptions_Graphics_Container.EnableTabCheckbox", _("Override global graphic settings"), Common::U32String(), kCmdGlobalGraphicsOverride);
else
_globalGraphicsOverride = new CheckboxWidget(graphicsContainer, "GameOptions_Graphics_Container.EnableTabCheckbox", _c("Override global graphic settings", "lowres"), Common::U32String(), kCmdGlobalGraphicsOverride);
addGraphicControls(graphicsContainer, "GameOptions_Graphics_Container.");
//
// The Keymap tab
//
Common::KeymapArray keymaps;
if (enginePlugin) {
keymaps = enginePlugin->get<MetaEngine>().initKeymaps(domain.c_str());
}
if (!keymaps.empty()) {
tab->addTab(_("Keymaps"), "GameOptions_KeyMapper");
ScrollContainerWidget *keymapContainer = new ScrollContainerWidget(tab, "GameOptions_KeyMapper.Container", "GameOptions_KeyMapper_Container");
keymapContainer->setBackgroundType(ThemeEngine::kWidgetBackgroundNo);
keymapContainer->setTarget(this);
addKeyMapperControls(keymapContainer, "GameOptions_KeyMapper_Container.", keymaps, domain);
}
//
// The backend tab (shown only if the backend implements one)
//
int backendTabId = tab->addTab(_("Backend"), "GameOptions_Backend");
ScrollContainerWidget *backendContainer = new ScrollContainerWidget(tab, "GameOptions_Backend.Container", "GameOptions_Backend_Container");
backendContainer->setBackgroundType(ThemeEngine::kWidgetBackgroundNo);
backendContainer->setTarget(this);
if (!g_gui.useLowResGUI())
_globalBackendOverride = new CheckboxWidget(backendContainer, "GameOptions_Backend_Container.EnableTabCheckbox", _("Override global backend settings"), Common::U32String(), kCmdGlobalBackendOverride);
else
_globalBackendOverride = new CheckboxWidget(backendContainer, "GameOptions_Backend_Container.EnableTabCheckbox", _c("Override global backend settings", "lowres"), Common::U32String(), kCmdGlobalBackendOverride);
g_system->registerDefaultSettings(_domain);
_backendOptions = g_system->buildBackendOptionsWidget(backendContainer, "GameOptions_Backend_Container.Container", _domain);
if (_backendOptions) {
_backendOptions->setParentDialog(this);
} else {
tab->removeTab(backendTabId);
}
//
// 4) The audio tab
//
tab->addTab(_("Audio"), "GameOptions_Audio");
if (!g_gui.useLowResGUI())
_globalAudioOverride = new CheckboxWidget(tab, "GameOptions_Audio.EnableTabCheckbox", _("Override global audio settings"), Common::U32String(), kCmdGlobalAudioOverride);
else
_globalAudioOverride = new CheckboxWidget(tab, "GameOptions_Audio.EnableTabCheckbox", _c("Override global audio settings", "lowres"), Common::U32String(), kCmdGlobalAudioOverride);
addAudioControls(tab, "GameOptions_Audio.");
addSubtitleControls(tab, "GameOptions_Audio.");
//
// 5) The volume tab
//
if (!g_gui.useLowResGUI())
tab->addTab(_("Volume"), "GameOptions_Volume");
else
tab->addTab(_c("Volume", "lowres"), "GameOptions_Volume");
if (!g_gui.useLowResGUI())
_globalVolumeOverride = new CheckboxWidget(tab, "GameOptions_Volume.EnableTabCheckbox", _("Override global volume settings"), Common::U32String(), kCmdGlobalVolumeOverride);
else
_globalVolumeOverride = new CheckboxWidget(tab, "GameOptions_Volume.EnableTabCheckbox", _c("Override global volume settings", "lowres"), Common::U32String(), kCmdGlobalVolumeOverride);
addVolumeControls(tab, "GameOptions_Volume.");
bool showMidi = !_guioptions.contains(GUIO_NOMIDI) && !_guioptions.contains(GUIO_NOMUSIC);
//
// 6) The MIDI tab
//
_globalMIDIOverride = nullptr;
if (showMidi) {
tab->addTab(_("MIDI"), "GameOptions_MIDI");
if (!g_gui.useLowResGUI())
_globalMIDIOverride = new CheckboxWidget(tab, "GameOptions_MIDI.EnableTabCheckbox", _("Override global MIDI settings"), Common::U32String(), kCmdGlobalMIDIOverride);
else
_globalMIDIOverride = new CheckboxWidget(tab, "GameOptions_MIDI.EnableTabCheckbox", _c("Override global MIDI settings", "lowres"), Common::U32String(), kCmdGlobalMIDIOverride);
addMIDIControls(tab, "GameOptions_MIDI.");
}
//
// 7) The MT-32 tab
//
_globalMT32Override = nullptr;
if (showMidi) {
tab->addTab(_("MT-32"), "GameOptions_MT32");
if (!g_gui.useLowResGUI())
_globalMT32Override = new CheckboxWidget(tab, "GameOptions_MT32.EnableTabCheckbox", _("Override global MT-32 settings"), Common::U32String(), kCmdGlobalMT32Override);
else
_globalMT32Override = new CheckboxWidget(tab, "GameOptions_MT32.EnableTabCheckbox", _c("Override global MT-32 settings", "lowres"), Common::U32String(), kCmdGlobalMT32Override);
addMT32Controls(tab, "GameOptions_MT32.");
}
//
// 8) The Paths tab
//
if (!g_gui.useLowResGUI())
tab->addTab(_("Paths"), "GameOptions_Paths");
else
tab->addTab(_c("Paths", "lowres"), "GameOptions_Paths");
// These buttons have to be extra wide, or the text will be truncated
// in the small version of the GUI.
#ifdef USE_HTTP
// GUI: Check integrity button
if (ConfMan.hasKey("enable_integrity_checking", Common::ConfigManager::kApplicationDomain))
new ButtonWidget(tab, "GameOptions_Paths.Checkintegrity", _("Check Integrity"), _("Perform integrity check for all game files"), kCmdCheckIntegrity);
#endif
// GUI: Button + Label for the game path
if (!g_gui.useLowResGUI())
new ButtonWidget(tab, "GameOptions_Paths.Gamepath", _("Game Path:"), Common::U32String(), kCmdGameBrowser);
else
new ButtonWidget(tab, "GameOptions_Paths.Gamepath", _c("Game Path:", "lowres"), Common::U32String(), kCmdGameBrowser);
_gamePathWidget = new PathWidget(tab, "GameOptions_Paths.GamepathText", gamePath);
// GUI: Button + Label for the additional path
if (!g_gui.useLowResGUI())
new ButtonWidget(tab, "GameOptions_Paths.Extrapath", _("Extra Path:"), _("Specifies path to additional data used by the game"), kCmdExtraBrowser);
else
new ButtonWidget(tab, "GameOptions_Paths.Extrapath", _c("Extra Path:", "lowres"), _("Specifies path to additional data used by the game"), kCmdExtraBrowser);
_extraPathWidget = new PathWidget(tab, "GameOptions_Paths.ExtrapathText", extraPath, _c("None", "path"), _("Specifies path to additional data used by the game"));
_extraPathClearButton = addClearButton(tab, "GameOptions_Paths.ExtraPathClearButton", kCmdExtraPathClear);
// GUI: Button + Label for the save path
if (!g_gui.useLowResGUI())
new ButtonWidget(tab, "GameOptions_Paths.Savepath", _("Save Path:"), _("Specifies where your saved games are put"), kCmdSaveBrowser);
else
new ButtonWidget(tab, "GameOptions_Paths.Savepath", _c("Save Path:", "lowres"), _("Specifies where your saved games are put"), kCmdSaveBrowser);
_savePathWidget = new PathWidget(tab, "GameOptions_Paths.SavepathText", savePath, _("Default"), _("Specifies where your saved games are put"));
_savePathClearButton = addClearButton(tab, "GameOptions_Paths.SavePathClearButton", kCmdSavePathClear);
//
// 9) The Achievements & The Statistics tabs
//
if (enginePlugin) {
const MetaEngine &metaEngine = enginePlugin->get<MetaEngine>();
AchMan.setActiveDomain(metaEngine.getAchievementsInfo(domain));
if (AchMan.getAchievementCount()) {
tab->addTab(_("Achievements"), "GameOptions_Achievements");
addAchievementsControls(tab, "GameOptions_Achievements.");
}
if (AchMan.getStatCount()) {
tab->addTab(_("Statistics"), "GameOptions_Achievements");
addStatisticsControls(tab, "GameOptions_Achievements.");
}
}
// Activate the first tab
tab->setActiveTab(0);
_tabWidget = tab;
// Add OK & Cancel buttons
new ButtonWidget(this, "GameOptions.Cancel", _("Cancel"), Common::U32String(), kCloseCmd);
new ButtonWidget(this, "GameOptions.Ok", _("OK"), Common::U32String(), kOKCmd);
}
void EditGameDialog::addGameControls(GuiObject *boss, const Common::String &prefix, const Common::String &description) {
// GUI: Label & edit widget for the game ID
if (!g_gui.useLowResGUI())
new StaticTextWidget(boss, prefix + "Id", _("ID:"), _("Short game identifier used for referring to saved games and running the game from the command line"));
else
new StaticTextWidget(boss, prefix + "Id", _c("ID:", "lowres"), _("Short game identifier used for referring to saved games and running the game from the command line"));
_domainWidget = new DomainEditTextWidget(boss, prefix + "Domain", _domain, _("Short game identifier used for referring to saved games and running the game from the command line"));
// GUI: Label & edit widget for the description
if (!g_gui.useLowResGUI())
new StaticTextWidget(boss, prefix + "Name", _("Name:"), _("Full title of the game"));
else
new StaticTextWidget(boss, prefix + "Name", _c("Name:", "lowres"), _("Full title of the game"));
_descriptionWidget = new EditTextWidget(boss, prefix + "Desc", description, _("Full title of the game"));
// Language popup
_langPopUpDesc = nullptr;
_langPopUp = nullptr;
if (!_guioptions.contains(GUIO_NOLANG)) {
_langPopUpDesc = new StaticTextWidget(boss, prefix + "LangPopupDesc", _("Language:"), _("Language of the game. This will not turn your Spanish game version into English"));
_langPopUp = new PopUpWidget(boss, prefix + "LangPopup", _("Language of the game. This will not turn your Spanish game version into English"));
_langPopUp->appendEntry(_("<default>"), (uint32)Common::UNK_LANG);
_langPopUp->appendEntry("", (uint32)Common::UNK_LANG);
const Common::LanguageDescription *l = Common::g_languages;
for (; l->code; ++l) {
if (checkGameGUIOptionLanguage(l->id, _guioptionsString))
_langPopUp->appendEntry(l->description, l->id);
}
}
// Platform popup
if (!g_gui.useLowResGUI())
_platformPopUpDesc = new StaticTextWidget(boss, prefix + "PlatformPopupDesc", _("Platform:"), _("Platform the game was originally designed for"));
else
_platformPopUpDesc = new StaticTextWidget(boss, prefix + "PlatformPopupDesc", _c("Platform:", "lowres"), _("Platform the game was originally designed for"));
_platformPopUp = new PopUpWidget(boss, prefix + "PlatformPopup", _("Platform the game was originally designed for"));
_platformPopUp->appendEntry(_("<default>"));
_platformPopUp->appendEntry("");
const Common::PlatformDescription *p = Common::g_platforms;
for (; p->code; ++p) {
if (checkGameGUIOptionPlatform(p->id, _guioptionsString))
_platformPopUp->appendEntry(p->description, p->id);
}
}
void EditGameDialog::setupGraphicsTab() {
OptionsDialog::setupGraphicsTab();
_globalGraphicsOverride->setVisible(true);
}
void EditGameDialog::open() {
OptionsDialog::open();
bool e;
// En-/disable dialog items depending on whether overrides are active or not.
e = ConfMan.hasKey("gfx_mode", _domain) ||
ConfMan.hasKey("render_mode", _domain) ||
ConfMan.hasKey("rotation_mode", _domain) ||
ConfMan.hasKey("stretch_mode", _domain) ||
ConfMan.hasKey("scaler", _domain) ||
ConfMan.hasKey("scale_factor", _domain) ||
ConfMan.hasKey("shader", _domain) ||
ConfMan.hasKey("aspect_ratio", _domain) ||
ConfMan.hasKey("fullscreen", _domain) ||
ConfMan.hasKey("vsync", _domain) ||
ConfMan.hasKey("filtering", _domain) ||
ConfMan.hasKey("renderer", _domain) ||
ConfMan.hasKey("antialiasing", _domain);
_globalGraphicsOverride->setState(e);
if (_backendOptions) {
e = _backendOptions->hasKeys();
_globalBackendOverride->setState(e);
}
e = ConfMan.hasKey("music_driver", _domain) ||
ConfMan.hasKey("output_rate", _domain) ||
ConfMan.hasKey("opl_driver", _domain) ||
ConfMan.hasKey("subtitles", _domain) ||
ConfMan.hasKey("talkspeed", _domain);
_globalAudioOverride->setState(e);
e = ConfMan.hasKey("music_volume", _domain) ||
ConfMan.hasKey("sfx_volume", _domain) ||
ConfMan.hasKey("speech_volume", _domain);
_globalVolumeOverride->setState(e);
if (!_guioptions.contains(GUIO_NOMIDI) && !_guioptions.contains(GUIO_NOMUSIC)) {
e = ConfMan.hasKey("soundfont", _domain) ||
ConfMan.hasKey("multi_midi", _domain) ||
ConfMan.hasKey("midi_gain", _domain);
_globalMIDIOverride->setState(e);
e = ConfMan.hasKey("native_mt32", _domain) ||
ConfMan.hasKey("enable_gs", _domain);
_globalMT32Override->setState(e);
}
// TODO: game path
if (_langPopUp != nullptr) {
const Common::Language lang = Common::parseLanguage(ConfMan.get("language", _domain));
if (ConfMan.hasKey("language", _domain)) {
_langPopUp->setSelectedTag(lang);
} else {
_langPopUp->setSelectedTag((uint32)Common::UNK_LANG);
}
if (_langPopUp->numEntries() <= 3) { // If only one language is available
_langPopUpDesc->setEnabled(false);
_langPopUp->setEnabled(false);
}
}
if (_engineOptions) {
_engineOptions->load();
}
const Common::Platform platform = Common::parsePlatform(ConfMan.get("platform", _domain));
if (ConfMan.hasKey("platform", _domain)) {
_platformPopUp->setSelectedTag(platform);
} else {
_platformPopUp->setSelectedTag((uint32)Common::kPlatformUnknown);
}
// First entry is <default>
// Second entry is ""
// So from third entry onwards are actual platforms - same logic as for language
if (_platformPopUp->numEntries() <= 3) {
_platformPopUpDesc->setEnabled(false);
_platformPopUp->setEnabled(false);
}
}
void EditGameDialog::close() {
OptionsDialog::close();
// Cleanup engine widgets before unloading its plugin
if (_engineOptions) {
// Remove the widget from the container before deleting the widget
_gameContainer->removeWidget(_engineOptions);
delete _engineOptions;
}
PluginMan.loadDetectionPlugin(); // only for uncached manager
}
void EditGameDialog::apply() {
ConfMan.set("description", _descriptionWidget->getEditString(), _domain);
if (_langPopUp != nullptr) {
Common::Language lang = (Common::Language)_langPopUp->getSelectedTag();
if (lang < 0)
ConfMan.removeKey("language", _domain);
else
ConfMan.set("language", Common::getLanguageCode(lang), _domain);
}
Common::Path gamePath(_gamePathWidget->getLabel());
if (!gamePath.empty())
ConfMan.setPath("path", gamePath, _domain);
Common::Path extraPath(_extraPathWidget->getLabel());
if (!extraPath.empty())
ConfMan.setPath("extrapath", extraPath, _domain);
else
ConfMan.removeKey("extrapath", _domain);
Common::Path savePath(_savePathWidget->getLabel());
if (!savePath.empty())
ConfMan.setPath("savepath", savePath, _domain);
else
ConfMan.removeKey("savepath", _domain);
Common::Platform platform = (Common::Platform)_platformPopUp->getSelectedTag();
if (platform < 0)
ConfMan.removeKey("platform", _domain);
else
ConfMan.set("platform", Common::getPlatformCode(platform), _domain);
if (_engineOptions) {
_engineOptions->save();
}
OptionsDialog::apply();
}
void EditGameDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {
switch (cmd) {
case kCmdGlobalGraphicsOverride:
setGraphicSettingsState(data != 0);
g_gui.scheduleTopDialogRedraw();
break;
case kCmdGlobalBackendOverride:
_backendOptions->setEnabled(data != 0);
g_gui.scheduleTopDialogRedraw();
break;
case kCmdGlobalAudioOverride:
setAudioSettingsState(data != 0);
setSubtitleSettingsState(data != 0);
if (_globalVolumeOverride == nullptr)
setVolumeSettingsState(data != 0);
g_gui.scheduleTopDialogRedraw();
break;
case kCmdGlobalMIDIOverride:
setMIDISettingsState(data != 0);
g_gui.scheduleTopDialogRedraw();
break;
case kCmdGlobalMT32Override:
setMT32SettingsState(data != 0);
g_gui.scheduleTopDialogRedraw();
break;
case kCmdGlobalVolumeOverride:
setVolumeSettingsState(data != 0);
g_gui.scheduleTopDialogRedraw();
break;
case kCmdChooseSoundFontCmd:
{
BrowserDialog browser(_("Select SoundFont"), false);
if (browser.runModal() > 0) {
// User made this choice...
Common::FSNode file(browser.getResult());
_soundFont->setLabel(file.getPath());
if (!file.getPath().empty())
_soundFontClearButton->setEnabled(true);
else
_soundFontClearButton->setEnabled(false);
g_gui.scheduleTopDialogRedraw();
}
break;
}
// Change path for the game
case kCmdGameBrowser:
{
BrowserDialog browser(_("Select directory with game data"), true);
if (browser.runModal() > 0) {
// User made his choice...
Common::FSNode dir(browser.getResult());
// TODO: Verify the game can be found in the new directory... Best
// done with optional specific gameid to pluginmgr detectgames?
// FSList files = dir.listDir(FSNode::kListFilesOnly);
_gamePathWidget->setLabel(dir.getPath());
g_gui.scheduleTopDialogRedraw();
}
g_gui.scheduleTopDialogRedraw();
break;
}
// Change path for extra game data (eg, using sword cutscenes when playing via CD)
case kCmdExtraBrowser:
{
BrowserDialog browser(_("Select additional game directory"), true);
if (browser.runModal() > 0) {
// User made his choice...
Common::FSNode dir(browser.getResult());
_extraPathWidget->setLabel(dir.getPath());
g_gui.scheduleTopDialogRedraw();
}
g_gui.scheduleTopDialogRedraw();
break;
}
// Change path for stored save game (perm and temp) data
case kCmdSaveBrowser:
{
BrowserDialog browser(_("Select directory for saved games"), true);
if (browser.runModal() > 0) {
// User made his choice...
Common::FSNode dir(browser.getResult());
if (dir.isWritable()) {
_savePathWidget->setLabel(dir.getPath());
} else {
MessageDialog error(_("The chosen directory cannot be written to. Please select another one."));
error.runModal();
return;
}
#if defined(USE_CLOUD)
MessageDialog warningMessage(_("Saved games sync feature doesn't work with non-default directories. If you want your saved games to sync, use default directory."));
warningMessage.runModal();
#endif
g_gui.scheduleTopDialogRedraw();
}
g_gui.scheduleTopDialogRedraw();
break;
}
case kCmdExtraPathClear:
_extraPathWidget->setLabel(Common::Path());
break;
case kCmdSavePathClear:
_savePathWidget->setLabel(Common::Path());
break;
#ifdef USE_HTTP
case kCmdCheckIntegrity: {
IntegrityDialog wizard("http://gamesdb.sev.zone/validate", _domain);
wizard.runModal();
break;
}
#endif
case kOKCmd:
{
// Write back changes made to config object
Common::String newDomain(Common::convertFromU32String(_domainWidget->getEditString()));
if (newDomain != _domain) {
if (newDomain.empty()
|| newDomain.hasPrefix("_")
|| newDomain == ConfigManager::kApplicationDomain
|| ConfMan.hasGameDomain(newDomain)) {
MessageDialog alert(_("This game ID is already taken. Please choose another one."));
alert.runModal();
return;
}
ConfMan.renameGameDomain(_domain, newDomain);
_domain = newDomain;
if (_engineOptions) {
_engineOptions->setDomain(newDomain);
}
if (_backendOptions) {
_backendOptions->setDomain(newDomain);
}
}
}
// fall through
default:
OptionsDialog::handleCommand(sender, cmd, data);
}
}
} // End of namespace GUI

100
gui/editgamedialog.h Normal file
View File

@@ -0,0 +1,100 @@
/* 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/>.
*
*/
#ifndef GUI_EDITGAMEDIALOG_H
#define GUI_EDITGAMEDIALOG_H
#include "engines/game.h"
#include "gui/dialog.h"
#include "gui/options.h"
#include "gui/widget.h"
namespace GUI {
class BrowserDialog;
class CommandSender;
class DomainEditTextWidget;
class ListWidget;
class ButtonWidget;
class PicButtonWidget;
class GraphicsWidget;
class StaticTextWidget;
class EditTextWidget;
class SaveLoadChooser;
/*
* A dialog that allows the user to edit a config game entry.
* TODO: add widgets for some/all of the following
* - Maybe scaler/graphics mode. But there are two problems:
* 1) Different backends can have different scalers with different names,
* so we first have to add a way to query those... no Ender, I don't
* think a bitmasked property() value is nice for this, because we would
* have to add to the bitmask values whenever a backends adds a new scaler).
* 2) At the time the launcher is running, the GFX backend is already setup.
* So when a game is run via the launcher, the custom scaler setting for it won't be
* used. So we'd also have to add an API to change the scaler during runtime
* (the SDL backend can already do that based on user input, but there is no API
* to achieve it)
* If the APIs for 1&2 are in place, we can think about adding this to the Edit&Option dialogs
*/
class EditGameDialog : public OptionsDialog {
public:
EditGameDialog(const Common::String &domain);
void open() override;
void apply() override;
void close() override;
void handleCommand(CommandSender *sender, uint32 cmd, uint32 data) override;
protected:
void setupGraphicsTab() override;
void addGameControls(GuiObject *boss, const Common::String &prefix, const Common::String &description);
EditTextWidget *_descriptionWidget;
DomainEditTextWidget *_domainWidget;
PathWidget *_gamePathWidget;
PathWidget *_extraPathWidget;
PathWidget *_savePathWidget;
ButtonWidget *_extraPathClearButton;
ButtonWidget *_savePathClearButton;
StaticTextWidget *_langPopUpDesc;
PopUpWidget *_langPopUp;
StaticTextWidget *_platformPopUpDesc;
PopUpWidget *_platformPopUp;
CheckboxWidget *_globalGraphicsOverride;
CheckboxWidget *_globalBackendOverride;
CheckboxWidget *_globalAudioOverride;
CheckboxWidget *_globalMIDIOverride;
CheckboxWidget *_globalMT32Override;
CheckboxWidget *_globalVolumeOverride;
ScrollContainerWidget *_gameContainer;
OptionsContainerWidget *_engineOptions;
};
} // End of namespace GUI
#endif

86
gui/editrecorddialog.cpp Normal file
View File

@@ -0,0 +1,86 @@
/* 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 "gui/editrecorddialog.h"
#include "gui/widgets/edittext.h"
#include "common/translation.h"
namespace GUI {
const Common::U32String EditRecordDialog::getAuthor() {
return _authorEdit->getEditString();
}
void EditRecordDialog::setAuthor(const Common::U32String &author) {
_authorEdit->setEditString(author);
}
const Common::String EditRecordDialog::getNotes() {
return _notesEdit->getEditString();
}
void EditRecordDialog::setNotes(const Common::String &desc) {
_notesEdit->setEditString(desc);
}
const Common::String EditRecordDialog::getName() {
return _nameEdit->getEditString();
}
void EditRecordDialog::setName(const Common::String &name) {
_nameEdit->setEditString(name);
}
EditRecordDialog::~EditRecordDialog() {
}
EditRecordDialog::EditRecordDialog(const Common::U32String &author, const Common::String &name, const Common::String &notes) : Dialog("EditRecordDialog") {
new StaticTextWidget(this, "EditRecordDialog.AuthorLabel", _("Author:"));
new StaticTextWidget(this, "EditRecordDialog.NameLabel", _("Name:"));
new StaticTextWidget(this, "EditRecordDialog.NotesLabel", _("Notes:"));
_authorEdit = new EditTextWidget(this, "EditRecordDialog.AuthorEdit", Common::U32String());
_notesEdit = new EditTextWidget(this, "EditRecordDialog.NotesEdit", Common::U32String());
_nameEdit = new EditTextWidget(this, "EditRecordDialog.NameEdit", Common::U32String());
_authorEdit->setEditString(author);
_notesEdit->setEditString(notes);
_nameEdit->setEditString(name);
new GUI::ButtonWidget(this, "EditRecordDialog.Cancel", _("Cancel"), Common::U32String(), kCloseCmd);
new GUI::ButtonWidget(this, "EditRecordDialog.OK", _("OK"), Common::U32String(), kOKCmd);
}
void EditRecordDialog::handleCommand(GUI::CommandSender *sender, uint32 cmd, uint32 data) {
switch(cmd) {
case kCloseCmd:
setResult(kCloseCmd);
close();
break;
case kOKCmd:
setResult(kOKCmd);
close();
break;
default:
Dialog::handleCommand(sender, cmd, data);
break;
}
}
}

55
gui/editrecorddialog.h Normal file
View File

@@ -0,0 +1,55 @@
/* 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/>.
*
*/
#ifndef GUI_EDITRECORDDIALOG_H
#define GUI_EDITRECORDDIALOG_H
#include "gui/dialog.h"
namespace GUI {
class EditTextWidget;
class StaticTextWidget;
class EditRecordDialog : public Dialog {
private:
EditTextWidget *_notesEdit;
EditTextWidget *_nameEdit;
EditTextWidget *_authorEdit;
EditRecordDialog() : Dialog("EditRecordDialog") {};
public:
EditRecordDialog(const Common::U32String &author, const Common::String &name, const Common::String &notes);
~EditRecordDialog() override;
const Common::U32String getAuthor();
const Common::String getNotes();
const Common::String getName();
void setAuthor(const Common::U32String &author);
void setNotes(const Common::String &desc);
void setName(const Common::String &name);
void handleCommand(GUI::CommandSender *sender, uint32 cmd, uint32 data) override;
};
}// End of namespace GUI
#endif

41
gui/error.cpp Normal file
View File

@@ -0,0 +1,41 @@
/* 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/error.h"
#include "gui/message.h"
#include "gui/error.h"
namespace GUI {
void displayErrorDialog(const Common::U32String &text) {
GUI::MessageDialog alert(text);
alert.runModal();
}
void displayErrorDialog(const Common::Error &error, const Common::U32String &extraText) {
Common::U32String errorText(extraText);
errorText += Common::U32String(" ");
errorText += error.getTranslatedDesc();
GUI::MessageDialog alert(errorText);
alert.runModal();
}
} // End of namespace GUI

46
gui/error.h Normal file
View File

@@ -0,0 +1,46 @@
/* 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/>.
*
*/
#ifndef GUI_ERROR_H
#define GUI_ERROR_H
#include "common/error.h"
namespace GUI {
/**
* Displays an error dialog for some error code.
*
* @param error error code
* @param extraText extra text to be displayed in addition to default string description(optional)
*/
void displayErrorDialog(const Common::Error &error, const Common::U32String &extraText = Common::U32String());
/**
* Displays an error dialog for a given message.
*
* @param text message to be displayed
*/
void displayErrorDialog(const Common::U32String &text);
} // End of namespace GUI
#endif //GUI_ERROR_H

163
gui/filebrowser-dialog.cpp Normal file
View File

@@ -0,0 +1,163 @@
/* 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 "gui/filebrowser-dialog.h"
#include "common/system.h"
#include "common/algorithm.h"
#include "common/savefile.h"
#include "common/str-array.h"
#include "common/translation.h"
#include "gui/widgets/list.h"
#include "gui/gui-manager.h"
#include "gui/message.h"
namespace GUI {
enum {
kChooseCmd = 'Chos'
};
FileBrowserDialog::FileBrowserDialog(const char *title, const char *fileExtension, int mode, const char *fileMask, const char *initialFilename)
: Dialog("FileBrowser"), _mode(mode), _fileExt(fileExtension) {
if (fileMask == NULL) {
_fileMask = "*.";
_fileMask += fileExtension;
} else {
_fileMask = fileMask;
}
_fileList = nullptr;
new StaticTextWidget(this, "FileBrowser.Headline", title ? Common::convertToU32String(title) :
mode == kFBModeLoad ? _("Choose file for loading") : _("Enter filename for saving"));
_fileName = new EditTextWidget(this, "FileBrowser.Filename", Common::U32String(initialFilename));
if (mode == kFBModeLoad)
_fileName->setEnabled(false);
// Add file list
_fileList = new ListWidget(this, "FileBrowser.List");
_fileList->setNumberingMode(kListNumberingOff);
_fileList->setEditable(false);
_backgroundType = GUI::ThemeEngine::kDialogBackgroundPlain;
// Buttons
new ButtonWidget(this, "FileBrowser.Cancel", _("Cancel"), Common::U32String(), kCloseCmd);
new ButtonWidget(this, "FileBrowser.Choose", _("Choose"), Common::U32String(), kChooseCmd);
}
void FileBrowserDialog::open() {
// Call super implementation
Dialog::open();
updateListing();
}
void FileBrowserDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {
switch (cmd) {
case kChooseCmd:
if (_fileName->getEditString().empty())
break;
normalieFileName();
if (!isProceedSave())
break;
setResult(1);
close();
break;
case kListSelectionChangedCmd:
_fileName->setEditString(_fileList->getList().operator[](_fileList->getSelected()));
break;
case kListItemActivatedCmd:
case kListItemDoubleClickedCmd:
normalieFileName();
if (!isProceedSave())
break;
setResult(1);
close();
break;
default:
Dialog::handleCommand(sender, cmd, data);
}
}
void FileBrowserDialog::normalieFileName() {
Common::String filename = Common::convertFromU32String(_fileName->getEditString());
if (filename.matchString(_fileMask, true))
return;
_fileName->setEditString(filename + "." + _fileExt);
}
bool FileBrowserDialog::isProceedSave() {
bool matched = false;
if (_mode == kFBModeLoad)
return true;
for (Common::U32StringArray::const_iterator file = _fileList->getList().begin(); file != _fileList->getList().end(); ++file) {
if (*file == _fileName->getEditString()) {
matched = true;
break;
}
}
if (matched) {
GUI::MessageDialog alert(_("Do you really want to overwrite the file?"), _("Yes"), _("No"));
if (alert.runModal() != GUI::kMessageOK)
return false;
}
return true;
}
void FileBrowserDialog::updateListing() {
Common::SaveFileManager *saveFileMan = g_system->getSavefileManager();
Common::U32StringArray list;
Common::StringArray filenames = saveFileMan->listSavefiles(_fileMask);
Common::sort(filenames.begin(), filenames.end());
for (const auto &file : filenames) {
list.push_back(Common::U32String(file));
}
_fileList->setList(list);
_fileList->scrollTo(0);
// Finally, redraw
g_gui.scheduleTopDialogRedraw();
}
} // End of namespace GUI

63
gui/filebrowser-dialog.h Normal file
View File

@@ -0,0 +1,63 @@
/* 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/>.
*
*/
#ifndef FILEBROWSER_DIALOG_H
#define FILEBROWSER_DIALOG_H
#include "gui/dialog.h"
#include "gui/widgets/edittext.h"
namespace GUI {
class ListWidget;
class EditTextWidget;
class CommandSender;
enum {
kFBModeLoad = 0,
kFBModeSave
};
class FileBrowserDialog : public Dialog {
public:
FileBrowserDialog(const char *title, const char *fileExtension, int mode, const char *fileMask = NULL, const char *initialFilename = NULL);
void open() override;
void handleCommand(CommandSender *sender, uint32 cmd, uint32 data) override;
Common::String getResult() { return Dialog::getResult() ? _fileName->getEditString().encode() : Common::String(); }
protected:
EditTextWidget *_fileName;
ListWidget *_fileList;
Common::String _fileMask;
Common::String _fileExt;
int _mode;
void updateListing();
void normalieFileName();
bool isProceedSave();
};
} // End of namespace GUI
#endif

360
gui/fluidsynth-dialog.cpp Normal file
View File

@@ -0,0 +1,360 @@
/* 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 "gui/fluidsynth-dialog.h"
#include "gui/gui-manager.h"
#include "gui/message.h"
#include "gui/widgets/tab.h"
#include "gui/widgets/popup.h"
#include "common/config-manager.h"
#include "common/translation.h"
#include "common/debug.h"
namespace GUI {
enum {
kActivateChorusCmd = 'acho',
kChorusVoiceCountChangedCmd = 'cvcc',
kChorusLevelChangedCmd = 'clec',
kChorusSpeedChangedCmd = 'cspc',
kChorusDepthChangedCmd = 'cdec',
kActivateReverbCmd = 'arev',
kReverbRoomSizeChangedCmd = 'rrsc',
kReverbDampingChangedCmd = 'rdac',
kReverbWidthChangedCmd = 'rwic',
kReverbLevelChangedCmd = 'rlec',
kResetSettingsCmd = 'rese'
};
enum {
kWaveFormTypeSine = 0,
kWaveFormTypeTriangle = 1
};
enum {
kInterpolationNone = 0,
kInterpolationLinear = 1,
kInterpolation4thOrder = 2,
kInterpolation7thOrder = 3
};
FluidSynthSettingsDialog::FluidSynthSettingsDialog()
: Dialog("FluidSynthSettings") {
_domain = Common::ConfigManager::kApplicationDomain;
_tabWidget = new TabWidget(this, "FluidSynthSettings.TabWidget");
_tabWidget->addTab(_("Reverb"), "FluidSynthSettings_Reverb");
_reverbActivate = new CheckboxWidget(_tabWidget, "FluidSynthSettings_Reverb.EnableTabCheckbox", _("Active"), Common::U32String(), kActivateReverbCmd);
_reverbRoomSizeDesc = new StaticTextWidget(_tabWidget, "FluidSynthSettings_Reverb.RoomSizeText", _("Room:"));
_reverbRoomSizeSlider = new SliderWidget(_tabWidget, "FluidSynthSettings_Reverb.RoomSizeSlider", Common::U32String(), kReverbRoomSizeChangedCmd);
// 0.00 - 1.00, Default: 0.20
_reverbRoomSizeSlider->setMinValue(0);
_reverbRoomSizeSlider->setMaxValue(100);
_reverbRoomSizeLabel = new StaticTextWidget(_tabWidget, "FluidSynthSettings_Reverb.RoomSizeLabel", Common::U32String("20"));
_reverbDampingDesc = new StaticTextWidget(_tabWidget, "FluidSynthSettings_Reverb.DampingText", _("Damp:"));
_reverbDampingSlider = new SliderWidget(_tabWidget, "FluidSynthSettings_Reverb.DampingSlider", Common::U32String(), kReverbDampingChangedCmd);
// 0.00 - 1.00, Default: 0.00
_reverbDampingSlider->setMinValue(0);
_reverbDampingSlider->setMaxValue(100);
_reverbDampingLabel = new StaticTextWidget(_tabWidget, "FluidSynthSettings_Reverb.DampingLabel", Common::U32String("0"));
_reverbWidthDesc = new StaticTextWidget(_tabWidget, "FluidSynthSettings_Reverb.WidthText", _("Width:"));
_reverbWidthSlider = new SliderWidget(_tabWidget, "FluidSynthSettings_Reverb.WidthSlider", Common::U32String(), kReverbWidthChangedCmd);
// 0.0 - 100.0, Default: 0.5
_reverbWidthSlider->setMinValue(0);
_reverbWidthSlider->setMaxValue(1000);
_reverbWidthLabel = new StaticTextWidget(_tabWidget, "FluidSynthSettings_Reverb.WidthLabel", Common::U32String("5"));
_reverbLevelDesc = new StaticTextWidget(_tabWidget, "FluidSynthSettings_Reverb.LevelText", _("Level:"));
_reverbLevelSlider = new SliderWidget(_tabWidget, "FluidSynthSettings_Reverb.LevelSlider", Common::U32String(), kReverbLevelChangedCmd);
// 0.00 - 1.00, Default: 0.90
_reverbLevelSlider->setMinValue(0);
_reverbLevelSlider->setMaxValue(100);
_reverbLevelLabel = new StaticTextWidget(_tabWidget, "FluidSynthSettings_Reverb.LevelLabel", Common::U32String("90"));
_tabWidget->addTab(_("Chorus"), "FluidSynthSettings_Chorus");
_chorusActivate = new CheckboxWidget(_tabWidget, "FluidSynthSettings_Chorus.EnableTabCheckbox", _("Active"), Common::U32String(), kActivateChorusCmd);
// I18N: N stands for number here
_chorusVoiceCountDesc = new StaticTextWidget(_tabWidget, "FluidSynthSettings_Chorus.VoiceCountText", _("N:"));
_chorusVoiceCountSlider = new SliderWidget(_tabWidget, "FluidSynthSettings_Chorus.VoiceCountSlider", Common::U32String(), kChorusVoiceCountChangedCmd);
// 0-99, Default: 3
_chorusVoiceCountSlider->setMinValue(0);
_chorusVoiceCountSlider->setMaxValue(99);
_chorusVoiceCountLabel = new StaticTextWidget(_tabWidget, "FluidSynthSettings_Chorus.VoiceCountLabel", Common::U32String("3"));
_chorusLevelDesc = new StaticTextWidget(_tabWidget, "FluidSynthSettings_Chorus.LevelText", _("Level:"));
_chorusLevelSlider = new SliderWidget(_tabWidget, "FluidSynthSettings_Chorus.LevelSlider", Common::U32String(), kChorusLevelChangedCmd);
// 0.00 - 10.00, Default: 2.00
_chorusLevelSlider->setMinValue(0);
_chorusLevelSlider->setMaxValue(1000);
_chorusLevelLabel = new StaticTextWidget(_tabWidget, "FluidSynthSettings_Chorus.LevelLabel", Common::U32String("200"));
_chorusSpeedDesc = new StaticTextWidget(_tabWidget, "FluidSynthSettings_Chorus.SpeedText", _("Speed:"));
_chorusSpeedSlider = new SliderWidget(_tabWidget, "FluidSynthSettings_Chorus.SpeedSlider", Common::U32String(), kChorusSpeedChangedCmd);
// 0.10 - 5.00, Default: 0.30
_chorusSpeedSlider->setMinValue(10);
_chorusSpeedSlider->setMaxValue(500);
_chorusSpeedLabel = new StaticTextWidget(_tabWidget, "FluidSynthSettings_Chorus.SpeedLabel", Common::U32String("30"));
_chorusDepthDesc = new StaticTextWidget(_tabWidget, "FluidSynthSettings_Chorus.DepthText", _("Depth:"));
_chorusDepthSlider = new SliderWidget(_tabWidget, "FluidSynthSettings_Chorus.DepthSlider", Common::U32String(), kChorusDepthChangedCmd);
// 0.0 - 256.0, Default: 8.0
_chorusDepthSlider->setMinValue(0);
_chorusDepthSlider->setMaxValue(2560);
_chorusDepthLabel = new StaticTextWidget(_tabWidget, "FluidSynthSettings_Chorus.DepthLabel", Common::U32String("80"));
_chorusWaveFormTypePopUpDesc = new StaticTextWidget(_tabWidget, "FluidSynthSettings_Chorus.WaveFormTypeText", _("Type:"));
_chorusWaveFormTypePopUp = new PopUpWidget(_tabWidget, "FluidSynthSettings_Chorus.WaveFormType");
_chorusWaveFormTypePopUp->appendEntry(_("Sine"), kWaveFormTypeSine);
_chorusWaveFormTypePopUp->appendEntry(_("Triangle"), kWaveFormTypeTriangle);
_tabWidget->addTab(_("Misc"), "FluidSynthSettings_Misc");
_miscInterpolationPopUpDesc = new StaticTextWidget(_tabWidget, "FluidSynthSettings_Misc.InterpolationText", _("Interpolation:"));
_miscInterpolationPopUp = new PopUpWidget(_tabWidget, "FluidSynthSettings_Misc.Interpolation");
_miscInterpolationPopUp->appendEntry(_("None (fastest)"), kInterpolationNone);
_miscInterpolationPopUp->appendEntry(_("Linear"), kInterpolationLinear);
_miscInterpolationPopUp->appendEntry(_("Fourth-order"), kInterpolation4thOrder);
_miscInterpolationPopUp->appendEntry(_("Seventh-order"), kInterpolation7thOrder);
_tabWidget->setActiveTab(0);
new ButtonWidget(this, "FluidSynthSettings.ResetSettings", _("Reset"), _("Reset all FluidSynth settings to their default values."), kResetSettingsCmd);
new ButtonWidget(this, "FluidSynthSettings.Cancel", _("Cancel"), Common::U32String(), kCloseCmd);
new ButtonWidget(this, "FluidSynthSettings.Ok", _("OK"), Common::U32String(), kOKCmd);
}
FluidSynthSettingsDialog::~FluidSynthSettingsDialog() {
}
void FluidSynthSettingsDialog::open() {
Dialog::open();
// Reset result value
setResult(0);
readSettings();
}
void FluidSynthSettingsDialog::close() {
if (getResult()) {
writeSettings();
}
Dialog::close();
}
void FluidSynthSettingsDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {
switch (cmd) {
case kActivateChorusCmd:
setChorusSettingsState(data);
break;
case kChorusVoiceCountChangedCmd:
_chorusVoiceCountLabel->setLabel(Common::String::format("%d", _chorusVoiceCountSlider->getValue()));
break;
case kChorusLevelChangedCmd:
_chorusLevelLabel->setLabel(Common::String::format("%d", _chorusLevelSlider->getValue()));
break;
case kChorusSpeedChangedCmd:
_chorusSpeedLabel->setLabel(Common::String::format("%d", _chorusSpeedSlider->getValue()));
break;
case kChorusDepthChangedCmd:
_chorusDepthLabel->setLabel(Common::String::format("%d", _chorusDepthSlider->getValue()));
break;
case kActivateReverbCmd:
setReverbSettingsState(data);
break;
case kReverbRoomSizeChangedCmd:
_reverbRoomSizeLabel->setLabel(Common::String::format("%d", _reverbRoomSizeSlider->getValue()));
break;
case kReverbDampingChangedCmd:
_reverbDampingLabel->setLabel(Common::String::format("%d", _reverbDampingSlider->getValue()));
break;
case kReverbWidthChangedCmd:
_reverbWidthLabel->setLabel(Common::String::format("%d", _reverbWidthSlider->getValue()));
break;
case kReverbLevelChangedCmd:
_reverbLevelLabel->setLabel(Common::String::format("%d", _reverbLevelSlider->getValue()));
break;
case kResetSettingsCmd: {
MessageDialog alert(_("Do you really want to reset all FluidSynth settings to their default values?"), _("Yes"), _("No"));
if (alert.runModal() == GUI::kMessageOK) {
resetSettings();
readSettings();
g_gui.scheduleTopDialogRedraw();
}
break;
}
case kOKCmd:
setResult(1);
close();
break;
default:
Dialog::handleCommand(sender, cmd, data);
break;
}
}
void FluidSynthSettingsDialog::setChorusSettingsState(bool enabled) {
_chorusVoiceCountDesc->setEnabled(enabled);
_chorusVoiceCountSlider->setEnabled(enabled);
_chorusVoiceCountLabel->setEnabled(enabled);
_chorusLevelDesc->setEnabled(enabled);
_chorusLevelSlider->setEnabled(enabled);
_chorusLevelLabel->setEnabled(enabled);
_chorusSpeedDesc->setEnabled(enabled);
_chorusSpeedSlider->setEnabled(enabled);
_chorusSpeedLabel->setEnabled(enabled);
_chorusDepthDesc->setEnabled(enabled);
_chorusDepthSlider->setEnabled(enabled);
_chorusDepthLabel->setEnabled(enabled);
_chorusWaveFormTypePopUpDesc->setEnabled(enabled);
_chorusWaveFormTypePopUp->setEnabled(enabled);
}
void FluidSynthSettingsDialog::setReverbSettingsState(bool enabled) {
_reverbRoomSizeDesc->setEnabled(enabled);
_reverbRoomSizeSlider->setEnabled(enabled);
_reverbRoomSizeLabel->setEnabled(enabled);
_reverbDampingDesc->setEnabled(enabled);
_reverbDampingSlider->setEnabled(enabled);
_reverbDampingLabel->setEnabled(enabled);
_reverbWidthDesc->setEnabled(enabled);
_reverbWidthSlider->setEnabled(enabled);
_reverbWidthLabel->setEnabled(enabled);
_reverbLevelDesc->setEnabled(enabled);
_reverbLevelSlider->setEnabled(enabled);
_reverbLevelLabel->setEnabled(enabled);
}
void FluidSynthSettingsDialog::readSettings() {
_chorusVoiceCountSlider->setValue(ConfMan.getInt("fluidsynth_chorus_nr", _domain));
_chorusVoiceCountLabel->setLabel(Common::String::format("%d", _chorusVoiceCountSlider->getValue()));
_chorusLevelSlider->setValue(ConfMan.getInt("fluidsynth_chorus_level", _domain));
_chorusLevelLabel->setLabel(Common::String::format("%d", _chorusLevelSlider->getValue()));
_chorusSpeedSlider->setValue(ConfMan.getInt("fluidsynth_chorus_speed", _domain));
_chorusSpeedLabel->setLabel(Common::String::format("%d", _chorusSpeedSlider->getValue()));
_chorusDepthSlider->setValue(ConfMan.getInt("fluidsynth_chorus_depth", _domain));
_chorusDepthLabel->setLabel(Common::String::format("%d", _chorusDepthSlider->getValue()));
Common::String waveForm = ConfMan.get("fluidsynth_chorus_waveform", _domain);
if (waveForm == "sine") {
_chorusWaveFormTypePopUp->setSelectedTag(kWaveFormTypeSine);
} else if (waveForm == "triangle") {
_chorusWaveFormTypePopUp->setSelectedTag(kWaveFormTypeTriangle);
}
_reverbRoomSizeSlider->setValue(ConfMan.getInt("fluidsynth_reverb_roomsize", _domain));
_reverbRoomSizeLabel->setLabel(Common::String::format("%d", _reverbRoomSizeSlider->getValue()));
_reverbDampingSlider->setValue(ConfMan.getInt("fluidsynth_reverb_damping", _domain));
_reverbDampingLabel->setLabel(Common::String::format("%d", _reverbDampingSlider->getValue()));
_reverbWidthSlider->setValue(ConfMan.getInt("fluidsynth_reverb_width", _domain));
_reverbWidthLabel->setLabel(Common::String::format("%d", _reverbWidthSlider->getValue()));
_reverbLevelSlider->setValue(ConfMan.getInt("fluidsynth_reverb_level", _domain));
_reverbLevelLabel->setLabel(Common::String::format("%d", _reverbLevelSlider->getValue()));
Common::String interpolation = ConfMan.get("fluidsynth_misc_interpolation", _domain);
if (interpolation == "none") {
_miscInterpolationPopUp->setSelectedTag(kInterpolationNone);
} else if (interpolation == "linear") {
_miscInterpolationPopUp->setSelectedTag(kInterpolationLinear);
} else if (interpolation == "4th") {
_miscInterpolationPopUp->setSelectedTag(kInterpolation4thOrder);
} else if (interpolation == "7th") {
_miscInterpolationPopUp->setSelectedTag(kInterpolation7thOrder);
}
// This may trigger redrawing, so don't do it until all sliders have
// their proper values. Otherwise, the dialog may crash because of
// invalid slider values.
_chorusActivate->setState(ConfMan.getBool("fluidsynth_chorus_activate", _domain));
_reverbActivate->setState(ConfMan.getBool("fluidsynth_reverb_activate", _domain));
}
void FluidSynthSettingsDialog::writeSettings() {
ConfMan.setBool("fluidsynth_chorus_activate", _chorusActivate->getState());
ConfMan.setInt("fluidsynth_chorus_nr", _chorusVoiceCountSlider->getValue(), _domain);
ConfMan.setInt("fluidsynth_chorus_level", _chorusLevelSlider->getValue(), _domain);
ConfMan.setInt("fluidsynth_chorus_speed", _chorusSpeedSlider->getValue(), _domain);
ConfMan.setInt("fluidsynth_chorus_depth", _chorusDepthSlider->getValue(), _domain);
uint32 waveForm = _chorusWaveFormTypePopUp->getSelectedTag();
if (waveForm == kWaveFormTypeSine) {
ConfMan.set("fluidsynth_chorus_waveform", "sine", _domain);
} else if (waveForm == kWaveFormTypeTriangle) {
ConfMan.set("fluidsynth_chorus_waveform", "triangle", _domain);
} else {
ConfMan.removeKey("fluidsynth_chorus_waveform", _domain);
}
ConfMan.setBool("fluidsynth_reverb_activate", _reverbActivate->getState());
ConfMan.setInt("fluidsynth_reverb_roomsize", _reverbRoomSizeSlider->getValue(), _domain);
ConfMan.setInt("fluidsynth_reverb_damping", _reverbDampingSlider->getValue(), _domain);
ConfMan.setInt("fluidsynth_reverb_width", _reverbWidthSlider->getValue(), _domain);
ConfMan.setInt("fluidsynth_reverb_level", _reverbLevelSlider->getValue(), _domain);
uint32 interpolation = _miscInterpolationPopUp->getSelectedTag();
if (interpolation == kInterpolationNone) {
ConfMan.set("fluidsynth_misc_interpolation", "none", _domain);
} else if (interpolation == kInterpolationLinear) {
ConfMan.set("fluidsynth_misc_interpolation", "linear", _domain);
} else if (interpolation == kInterpolation4thOrder) {
ConfMan.set("fluidsynth_misc_interpolation", "4th", _domain);
} else if (interpolation == kInterpolation7thOrder) {
ConfMan.set("fluidsynth_misc_interpolation", "7th", _domain);
} else {
ConfMan.removeKey("fluidsynth_misc_interpolation", _domain);
}
// The main options dialog is responsible for writing the config file.
// That's why we don't actually flush the settings to the file here.
}
void FluidSynthSettingsDialog::resetSettings() {
ConfMan.removeKey("fluidsynth_chorus_activate", _domain);
ConfMan.removeKey("fluidsynth_chorus_nr", _domain);
ConfMan.removeKey("fluidsynth_chorus_level", _domain);
ConfMan.removeKey("fluidsynth_chorus_speed", _domain);
ConfMan.removeKey("fluidsynth_chorus_depth", _domain);
ConfMan.removeKey("fluidsynth_chorus_waveform", _domain);
ConfMan.removeKey("fluidsynth_reverb_activate", _domain);
ConfMan.removeKey("fluidsynth_reverb_roomsize", _domain);
ConfMan.removeKey("fluidsynth_reverb_damping", _domain);
ConfMan.removeKey("fluidsynth_reverb_width", _domain);
ConfMan.removeKey("fluidsynth_reverb_level", _domain);
ConfMan.removeKey("fluidsynth_misc_interpolation", _domain);
}
} // End of namespace GUI

104
gui/fluidsynth-dialog.h Normal file
View File

@@ -0,0 +1,104 @@
/* 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/>.
*
*/
#ifndef FLUIDSYNTH_DIALOG_H
#define FLUIDSYNTH_DIALOG_H
#include "common/str.h"
#include "gui/dialog.h"
namespace GUI {
class TabWidget;
class CheckboxWidget;
class SliderWidget;
class StaticTextWidget;
class PopUpWidget;
class FluidSynthSettingsDialog : public Dialog {
public:
FluidSynthSettingsDialog();
~FluidSynthSettingsDialog() override;
void open() override;
void close() override;
void handleCommand(CommandSender *sender, uint32 cmd, uint32 data) override;
protected:
void setChorusSettingsState(bool enabled);
void setReverbSettingsState(bool enabled);
void readSettings();
void writeSettings();
void resetSettings();
private:
Common::String _domain;
TabWidget *_tabWidget;
CheckboxWidget *_chorusActivate;
StaticTextWidget *_chorusVoiceCountDesc;
SliderWidget *_chorusVoiceCountSlider;
StaticTextWidget *_chorusVoiceCountLabel;
StaticTextWidget *_chorusLevelDesc;
SliderWidget *_chorusLevelSlider;
StaticTextWidget *_chorusLevelLabel;
StaticTextWidget *_chorusSpeedDesc;
SliderWidget *_chorusSpeedSlider;
StaticTextWidget *_chorusSpeedLabel;
StaticTextWidget *_chorusDepthDesc;
SliderWidget *_chorusDepthSlider;
StaticTextWidget *_chorusDepthLabel;
StaticTextWidget *_chorusWaveFormTypePopUpDesc;
PopUpWidget *_chorusWaveFormTypePopUp;
CheckboxWidget *_reverbActivate;
StaticTextWidget *_reverbRoomSizeDesc;
SliderWidget *_reverbRoomSizeSlider;
StaticTextWidget *_reverbRoomSizeLabel;
StaticTextWidget *_reverbDampingDesc;
SliderWidget *_reverbDampingSlider;
StaticTextWidget *_reverbDampingLabel;
StaticTextWidget *_reverbWidthDesc;
SliderWidget *_reverbWidthSlider;
StaticTextWidget *_reverbWidthLabel;
StaticTextWidget *_reverbLevelDesc;
SliderWidget *_reverbLevelSlider;
StaticTextWidget *_reverbLevelLabel;
StaticTextWidget *_miscInterpolationPopUpDesc;
PopUpWidget *_miscInterpolationPopUp;
};
} // End of namespace GUI
#endif

993
gui/gui-manager.cpp Normal file
View File

@@ -0,0 +1,993 @@
/* 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/events.h"
#include "common/translation.h"
#include "common/zip-set.h"
#include "gui/EventRecorder.h"
#include "backends/keymapper/action.h"
#include "backends/keymapper/keymap.h"
#include "backends/keymapper/keymapper.h"
#include "backends/keymapper/standard-actions.h"
#include "gui/gui-manager.h"
#include "gui/dialog.h"
#include "gui/ThemeEngine.h"
#include "gui/ThemeEval.h"
#include "gui/Tooltip.h"
#include "gui/widget.h"
#include "graphics/cursorman.h"
#include "graphics/macgui/macwindowmanager.h"
namespace Common {
DECLARE_SINGLETON(GUI::GuiManager);
}
namespace GUI {
enum {
kDoubleClickDelay = 500, // milliseconds
kCursorAnimateDelay = 250,
kTooltipDelay = 1250,
kTooltipSameWidgetDelay = 7000
};
// Constructor
GuiManager::GuiManager() : CommandSender(nullptr), _redrawStatus(kRedrawDisabled), _stateIsSaved(false),
_cursorAnimateCounter(0), _cursorAnimateTimer(0) {
_theme = nullptr;
_useStdCursor = false;
_system = g_system;
_lastScreenChangeID = _system->getScreenChangeID();
computeScaleFactor();
_launched = false;
_useRTL = false;
_iconsSetChanged = false;
_displayTopDialogOnly = false;
// Clear the cursor
memset(_cursor, 0xFF, sizeof(_cursor));
#ifdef USE_TRANSLATION
// Enable translation
TransMan.setLanguage(ConfMan.get("gui_language").c_str());
setLanguageRTL();
#endif // USE_TRANSLATION
initTextToSpeech();
initIconsSet();
_iconsSetChanged = false;
ConfMan.registerDefault("gui_theme", "scummremastered");
Common::String themefile(ConfMan.get("gui_theme"));
ConfMan.registerDefault("gui_renderer", ThemeEngine::findModeConfigName(ThemeEngine::_defaultRendererMode));
ThemeEngine::GraphicsMode gfxMode = (ThemeEngine::GraphicsMode)ThemeEngine::findMode(ConfMan.get("gui_renderer"));
// Try to load the theme
if (!loadNewTheme(themefile, gfxMode)) {
// Loading the theme failed, try to load the built-in theme
if (!loadNewTheme("builtin", gfxMode)) {
// Loading the built-in theme failed as well. Bail out
error("Failed to load any GUI theme, aborting");
}
}
}
GuiManager::~GuiManager() {
delete _theme;
delete _wm;
}
void GuiManager::initIconsSet() {
Common::StackLock lock(_iconsMutex);
_iconsSet.clear();
#ifdef EMSCRIPTEN
Common::Path iconsPath = ConfMan.getPath("iconspath");
_iconsSet = Common::SearchSet();
_iconsSet.addDirectory("gui-icons/", iconsPath, 0, 3, false);
_iconsSetChanged = true;
#else
_iconsSetChanged = Common::generateZipSet(_iconsSet, "gui-icons.dat", "gui-icons*.dat");
#endif
}
void GuiManager::computeScaleFactor() {
const Common::Rect safeArea = g_system->getSafeOverlayArea();
const uint16 w = safeArea.width();
const uint16 h = safeArea.height();
_scaleFactor = g_system->getHiDPIScreenFactor();
if (ConfMan.hasKey("gui_scale"))
_scaleFactor *= ConfMan.getInt("gui_scale") / 100.f;
_baseHeight = (int16)((float)h / _scaleFactor);
_baseWidth = (int16)((float)w / _scaleFactor);
// Never go below 320x200. Our GUI layout is not designed to go below that.
// On the DS, this causes issues at 256x192 due to the use of non-scalable
// BDF fonts.
#ifndef __DS__
if (_baseHeight < 200) {
_baseHeight = 200;
_scaleFactor = (float)h / (float)_baseHeight;
_baseWidth = (int16)((float)w / _scaleFactor);
}
if (_baseWidth < 320) {
_baseWidth = 320;
_scaleFactor = (float)w / (float)_baseWidth;
_baseHeight = (int16)((float)h / _scaleFactor);
}
#endif
if (_theme)
_theme->setBaseResolution(_baseWidth, _baseHeight, _scaleFactor);
debug(1, "Setting %d x %d -> %d x %d -- %g", w, h, _baseWidth, _baseHeight, _scaleFactor);
}
Common::Keymap *GuiManager::getKeymap() const {
using namespace Common;
Keymap *guiMap = new Keymap(Keymap::kKeymapTypeGui, kGuiKeymapName, _("GUI"));
Action *act;
act = new Action(Common::kStandardActionInteract, _("Interact"));
act->addDefaultInputMapping("JOY_A");
act->setLeftClickEvent();
guiMap->addAction(act);
act = new Action("CLOS", _("Close"));
act->addDefaultInputMapping("ESCAPE");
act->addDefaultInputMapping("JOY_Y");
act->setKeyEvent(KeyState(KEYCODE_ESCAPE, ASCII_ESCAPE, 0));
guiMap->addAction(act);
act = new Action(kStandardActionMoveUp, _("Up"));
act->setKeyEvent(KEYCODE_UP);
act->addDefaultInputMapping("JOY_UP");
act->allowKbdRepeats();
guiMap->addAction(act);
act = new Action(kStandardActionMoveDown, _("Down"));
act->setKeyEvent(KEYCODE_DOWN);
act->addDefaultInputMapping("JOY_DOWN");
act->allowKbdRepeats();
guiMap->addAction(act);
act = new Action(kStandardActionMoveLeft, _("Left"));
act->setKeyEvent(KEYCODE_LEFT);
act->addDefaultInputMapping("JOY_LEFT");
act->allowKbdRepeats();
guiMap->addAction(act);
act = new Action(kStandardActionMoveRight, _("Right"));
act->setKeyEvent(KEYCODE_RIGHT);
act->addDefaultInputMapping("JOY_RIGHT");
act->allowKbdRepeats();
guiMap->addAction(act);
act = new Action("BACKSPACE", _("Backspace"));
act->setKeyEvent(KEYCODE_BACKSPACE);
act->addDefaultInputMapping("BACKSPACE");
act->allowKbdRepeats();
guiMap->addAction(act);
act = new Action("DEL", _("Delete Character"));
act->setKeyEvent(KEYCODE_DELETE);
act->addDefaultInputMapping("DELETE");
act->allowKbdRepeats();
guiMap->addAction(act);
act = new Action("END", _("Go to end of line"));
#ifdef MACOSX
act->setCustomEngineActionEvent(kActionEnd);
act->addDefaultInputMapping("C+e");
#else
act->setKeyEvent(KEYCODE_END);
act->addDefaultInputMapping("END");
#endif
guiMap->addAction(act);
act = new Action("SHIFT_END", _("Select to end of line"));
#ifdef MACOSX
act->setCustomEngineActionEvent(kActionShiftEnd);
act->addDefaultInputMapping("C+S+e");
#else
act->setKeyEvent(KeyState(KEYCODE_END, (uint16)KEYCODE_END, KBD_SHIFT));
act->addDefaultInputMapping("S+END");
#endif
guiMap->addAction(act);
act = new Action("SHIFT_HOME", _("Select to start of line"));
#ifdef MACOSX
act->setCustomEngineActionEvent(kActionShiftHome);
act->addDefaultInputMapping("C+S+a");
#else
act->setKeyEvent(KeyState(KEYCODE_HOME, (uint16)KEYCODE_HOME, KBD_SHIFT));
act->addDefaultInputMapping("S+HOME");
#endif
guiMap->addAction(act);
act = new Action("HOME", _("Go to start of line"));
#ifdef MACOSX
act->setCustomEngineActionEvent(kActionHome);
act->addDefaultInputMapping("C+a");
#else
act->setKeyEvent(KEYCODE_HOME);
act->addDefaultInputMapping("HOME");
#endif
guiMap->addAction(act);
#ifdef MACOSX
act = new Action(kStandardActionCut, _("Cut"));
act->setCustomEngineActionEvent(kActionCut);
act->addDefaultInputMapping("M+x");
guiMap->addAction(act);
act = new Action(kStandardActionPaste, _("Paste"));
act->setCustomEngineActionEvent(kActionPaste);
act->addDefaultInputMapping("M+v");
guiMap->addAction(act);
act = new Action(kStandardActionCopy, _("Copy"));
act->setCustomEngineActionEvent(kActionCopy);
act->addDefaultInputMapping("M+c");
guiMap->addAction(act);
#else
act = new Action(kStandardActionCut, _("Cut"));
act->setCustomEngineActionEvent(kActionCut);
act->addDefaultInputMapping("C+x");
guiMap->addAction(act);
act = new Action(kStandardActionPaste, _("Paste"));
act->setCustomEngineActionEvent(kActionPaste);
act->addDefaultInputMapping("C+v");
guiMap->addAction(act);
act = new Action(kStandardActionCopy, _("Copy"));
act->setCustomEngineActionEvent(kActionCopy);
act->addDefaultInputMapping("C+c");
guiMap->addAction(act);
#endif
act = new Action(kStandardActionEE, _("???"));
act->setKeyEvent(KEYCODE_v);
guiMap->addAction(act);
return guiMap;
}
void GuiManager::initKeymap() {
using namespace Common;
Keymapper *mapper = _system->getEventManager()->getKeymapper();
// Do not try to recreate same keymap over again
if (mapper->getKeymap(kGuiKeymapName) != 0)
return;
Keymap *guiMap = getKeymap();
mapper->addGlobalKeymap(guiMap);
}
void GuiManager::enableKeymap(bool enabled) {
Common::Keymapper *keymapper = _system->getEventManager()->getKeymapper();
keymapper->setEnabledKeymapType(enabled ? Common::Keymap::kKeymapTypeGui : Common::Keymap::kKeymapTypeGame);
}
bool GuiManager::loadNewTheme(Common::String id, ThemeEngine::GraphicsMode gfx, bool forced) {
// If we are asked to reload the currently active theme, just do nothing
// FIXME: Actually, why? It might be desirable at times to force a theme reload...
if (!forced)
if (_theme && id == _theme->getThemeId() && gfx == _theme->getGraphicsMode())
return true;
ThemeEngine *newTheme = nullptr;
if (gfx == ThemeEngine::kGfxDisabled)
gfx = ThemeEngine::_defaultRendererMode;
// Try to load the new theme
newTheme = new ThemeEngine(id, gfx);
assert(newTheme);
newTheme->setBaseResolution(_baseWidth, _baseHeight, _scaleFactor);
if (!newTheme->init()) {
delete newTheme;
return false;
}
//
// Disable and delete the old theme
//
if (_theme)
_theme->disable();
delete _theme;
if (_useStdCursor) {
CursorMan.popCursorPalette();
CursorMan.popCursor();
}
//
// Enable the new theme
//
_theme = newTheme;
_useStdCursor = !_theme->ownCursor();
// If _stateIsSaved is set, we know that a Theme is already initialized,
// thus we initialize the new theme properly
if (_stateIsSaved) {
_theme->enable();
if (_useStdCursor)
setupCursor();
}
// refresh all dialogs
for (DialogStack::size_type i = 0; i < _dialogStack.size(); ++i)
_dialogStack[i]->reflowLayout();
// We need to redraw immediately. Otherwise
// some other event may cause a widget to be
// redrawn before redraw() has been called.
redrawFull();
return true;
}
void GuiManager::redrawFull() {
_redrawStatus = kRedrawFull;
redraw();
_system->updateScreen();
}
void GuiManager::displayTopDialogOnly(bool mode) {
if (mode == _displayTopDialogOnly)
return;
_displayTopDialogOnly = mode;
redrawFull();
}
void GuiManager::redrawInternalTopDialogOnly() {
// This is the simple case where only one dialog (the top one) is drawn on screen
switch (_redrawStatus) {
case kRedrawCloseDialog:
case kRedrawFull:
case kRedrawOpenDialog:
// Clear everything
_theme->clearAll();
// fall through
case kRedrawTopDialog:
// Draw top dialog background on backbuffer
_theme->drawToBackbuffer();
_dialogStack.top()->drawDialog(kDrawLayerBackground);
// Copy just drawn background to screen and draw foreground
_theme->drawToScreen();
_theme->copyBackBufferToScreen();
_dialogStack.top()->drawDialog(kDrawLayerForeground);
break;
default:
// Redraw only the widgets that are marked as dirty on screen
_theme->drawToScreen();
_dialogStack.top()->drawWidgets();
break;
}
}
void GuiManager::redrawInternal() {
ThemeEngine::ShadingStyle shading;
shading = (ThemeEngine::ShadingStyle)xmlEval()->getVar("Dialog." + _dialogStack.top()->_name + ".Shading", 0);
switch (_redrawStatus) {
case kRedrawCloseDialog:
case kRedrawFull:
// Clear everything
_theme->clearAll();
// Draw background and foreground of the whole dialog stack except top one on the backbuffer
_theme->drawToBackbuffer();
for (DialogStack::size_type i = 0; i < _dialogStack.size() - 1; i++) {
_dialogStack[i]->drawDialog(kDrawLayerBackground);
_dialogStack[i]->drawDialog(kDrawLayerForeground);
}
// fall through
case kRedrawOpenDialog:
case kRedrawTopDialog:
// This case is an optimization to avoid redrawing the whole dialog
// stack when opening a new dialog or redrawing the current one.
_theme->drawToBackbuffer();
if (_redrawStatus == kRedrawOpenDialog && _dialogStack.size() > 1) {
// When opening a new dialog, merge the foreground of the last top dialog
// inside the backbuffer
// New top dialog foreground will be drawn on screen
Dialog *previousDialog = _dialogStack[_dialogStack.size() - 2];
previousDialog->drawDialog(kDrawLayerForeground);
}
// Do not shade when only redrawing the top dialog: shading has already been applied
// Do not shade more than once when opening many dialogs on top of each other.
// Shading being already applied previously, screen darkens
if ((_redrawStatus != kRedrawTopDialog) &&
((_redrawStatus != kRedrawOpenDialog) || (_dialogStack.size() <= 2))) {
_theme->applyScreenShading(shading);
}
// Finally, draw the top dialog background
_dialogStack.top()->drawDialog(kDrawLayerBackground);
// copy everything to screen and render the top dialog foreground
_theme->drawToScreen();
_theme->copyBackBufferToScreen();
_dialogStack.top()->drawDialog(kDrawLayerForeground);
break;
default:
// Redraw only the widgets that are marked as dirty on screen
_theme->drawToScreen();
_dialogStack.top()->drawWidgets();
break;
}
}
void GuiManager::redraw() {
if (_dialogStack.empty())
return;
if (_displayTopDialogOnly) {
redrawInternalTopDialogOnly();
} else {
redrawInternal();
}
_theme->updateScreen();
_redrawStatus = kRedrawDisabled;
}
Dialog *GuiManager::getTopDialog() const {
if (_dialogStack.empty())
return nullptr;
return _dialogStack.top();
}
void GuiManager::addToTrash(GuiObject* object, Dialog *parent) {
debug(7, "Adding Gui Object %p to trash", (void *)object);
GuiObjectTrashItem t;
t.object = object;
t.parent = nullptr;
// If a dialog was provided, check it is in the dialog stack
if (parent != nullptr) {
for (uint i = 0 ; i < _dialogStack.size() ; ++i) {
if (_dialogStack[i] == parent) {
t.parent = parent;
break;
}
}
}
for (auto it = _guiObjectTrash.begin(); it != _guiObjectTrash.end(); ++it) {
if (it->object == object) {
debug(6, "The object %p was already scheduled for deletion, skipping", (void *)(*it).object);
return;
}
}
_guiObjectTrash.push_back(t);
}
void GuiManager::runLoop() {
Dialog * const activeDialog = getTopDialog();
bool didSaveState = false;
if (activeDialog == nullptr)
return;
#ifdef ENABLE_EVENTRECORDER
// Suspend recording while GUI is shown
g_eventRec.acquireRecording();
#endif
if (!_stateIsSaved) {
saveState();
_theme->enable();
didSaveState = true;
_useStdCursor = !_theme->ownCursor();
if (_useStdCursor)
setupCursor();
// _theme->refresh();
_redrawStatus = kRedrawFull;
redraw();
}
Common::EventManager *eventMan = _system->getEventManager();
const uint32 targetFrameDuration = 1000 / 60;
while (!_dialogStack.empty() && activeDialog == getTopDialog() && !eventMan->shouldQuit() && (!g_engine || !eventMan->shouldReturnToLauncher())) {
uint32 frameStartTime = _system->getMillis(true);
// Don't "tickle" the dialog until the theme has had a chance
// to re-allocate buffers in case of a scaler change.
activeDialog->handleTickle();
if (_useStdCursor)
animateCursor();
Common::Event event;
while (eventMan->pollEvent(event)) {
// We will need to check whether the screen changed while polling
// for an event here. While we do send EVENT_SCREEN_CHANGED
// whenever this happens we still cannot be sure that we get such
// an event immediately. For example, we might have a mouse move
// event queued before a screen changed event. In some rare cases
// this would make the GUI redraw (with the code a few lines
// below) when it is not yet updated for new overlay dimensions.
// As a result ScummVM would crash because it tries to copy data
// outside the actual overlay screen.
if (event.type != Common::EVENT_SCREEN_CHANGED) {
checkScreenChange();
}
// The top dialog can change during the event loop. In that case, flush all the
// dialog-related events since they were probably generated while the old dialog
// was still visible, and therefore not intended for the new one.
//
// This hopefully fixes strange behavior/crashes with pop-up widgets. (Most easily
// triggered in 3x mode or when running ScummVM under Valgrind.)
if (activeDialog != getTopDialog() && event.type != Common::EVENT_SCREEN_CHANGED) {
processEvent(event, getTopDialog());
continue;
}
processEvent(event, activeDialog);
}
// If iconsSet was modified, notify dialogs so that they can be updated if needed
_iconsMutex.lock();
bool iconsChanged = _iconsSetChanged;
_iconsSetChanged = false;
_iconsMutex.unlock();
if (iconsChanged) {
for (DialogStack::size_type i = 0; i < _dialogStack.size(); ++i) {
setTarget(_dialogStack[i]);
sendCommand(kIconsSetLoadedCmd, 0);
}
}
// Delete GuiObject that have been added to the trash for a delayed deletion
emptyTrash(activeDialog);
// Handle tooltip for the widget under the mouse cursor.
// 1. Only try to show a tooltip if the mouse cursor was actually moved
// and sufficient time (kTooltipDelay) passed since mouse cursor rested in-place.
// Note, Dialog objects acquiring or losing focus lead to a _lastMousePosition update,
// which may lead to a change of its time and x,y coordinate values.
// See: GuiManager::giveFocusToDialog()
// We avoid updating _lastMousePosition when giving focus to the Tooltip object
// by having the Tooltip objects set a false value for their (inherited) member
// var _mouseUpdatedOnFocus (in Tooltip::setup()).
// However, when the tooltip loses focus, _lastMousePosition will be updated.
// If the mouse had stayed in the same position in the meantime,
// then at the time of the tooltip losing focus
// the _lastMousePosition.time will be new, but the x,y cordinates
// will be the same as the stored ones in _lastTooltipShown.
// 2. If the mouse was moved but ended on the same (tooltip enabled) widget,
// then delay showing the tooltip based on the value of kTooltipSameWidgetDelay.
uint32 systemMillisNowForTooltipCheck = _system->getMillis(true);
if ((_lastTooltipShown.x != _lastMousePosition.x || _lastTooltipShown.y != _lastMousePosition.y)
&& systemMillisNowForTooltipCheck - _lastMousePosition.time > (uint32)kTooltipDelay
&& !activeDialog->isDragging()) {
Widget *wdg = activeDialog->findWidget(_lastMousePosition.x, _lastMousePosition.y);
if (wdg && (wdg->hasTooltip() || (wdg->getFlags() & WIDGET_DYN_TOOLTIP)) && !(wdg->getFlags() & WIDGET_PRESSED)
&& (_lastTooltipShown.wdg != wdg || systemMillisNowForTooltipCheck - _lastTooltipShown.time > (uint32)kTooltipSameWidgetDelay)) {
_lastTooltipShown.time = systemMillisNowForTooltipCheck;
_lastTooltipShown.wdg = wdg;
_lastTooltipShown.x = _lastMousePosition.x;
_lastTooltipShown.y = _lastMousePosition.y;
if (wdg->getType() != kEditTextWidget || activeDialog->getFocusWidget() != wdg) {
if (wdg->getFlags() & WIDGET_DYN_TOOLTIP)
wdg->handleTooltipUpdate(_lastMousePosition.x + activeDialog->_x - wdg->getAbsX(), _lastMousePosition.y + activeDialog->_y - wdg->getAbsY());
if (wdg->hasTooltip()) {
Tooltip *tooltip = new Tooltip();
tooltip->setup(activeDialog, wdg, _lastMousePosition.x, _lastMousePosition.y);
tooltip->runModal();
delete tooltip;
}
}
}
}
redraw();
// Delay until the allocated frame time is elapsed to match the target frame rate.
// In case we have vsync enabled, we should rely on vsync to do take care about frame times.
// With vsync enabled, we currently have to force a frame time of 1ms since otherwise
// CPU usage will skyrocket on one thread as soon as no updateScreen(); calls happening.
if (g_system->getFeatureState(OSystem::kFeatureVSync)) {
_system->delayMillis(1);
} else {
uint32 actualFrameDuration = _system->getMillis(true) - frameStartTime;
if (actualFrameDuration < targetFrameDuration) {
_system->delayMillis(targetFrameDuration - actualFrameDuration);
}
}
_system->updateScreen();
}
// WORKAROUND: When quitting we might not properly close the dialogs on
// the dialog stack, thus we do this here to avoid any problems.
// This is most noticeable in bug #5954 "LAUNCHER: Can't quit from unsupported game dialog".
// It seems that Dialog::runModal never removes the dialog from the dialog
// stack, thus if the dialog does not call Dialog::close to close itself
// it will never be removed. Since we can have multiple run loops being
// called we cannot rely on catching EVENT_QUIT in the event loop above,
// since it would only catch it for the top run loop.
if ((eventMan->shouldQuit() || (g_engine && eventMan->shouldReturnToLauncher())) && activeDialog == getTopDialog()) {
activeDialog->close();
emptyTrash(activeDialog);
}
if (didSaveState) {
_theme->disable();
restoreState();
_useStdCursor = false;
}
#ifdef ENABLE_EVENTRECORDER
// Resume recording once GUI is shown
g_eventRec.releaseRecording();
#endif
}
void GuiManager::exitLoop() {
while (!_dialogStack.empty())
getTopDialog()->close();
}
#pragma mark -
void GuiManager::saveState() {
initKeymap();
enableKeymap(true);
// Backup old cursor
_lastClick.x = _lastClick.y = 0;
_lastClick.time = 0;
_lastClick.count = 0;
_stateIsSaved = true;
}
void GuiManager::restoreState() {
enableKeymap(false);
if (_useStdCursor) {
CursorMan.popCursor();
CursorMan.popCursorPalette();
}
_system->updateScreen();
_stateIsSaved = false;
}
void GuiManager::openDialog(Dialog *dialog) {
giveFocusToDialog(dialog);
if (!_dialogStack.empty())
getTopDialog()->lostFocus();
_dialogStack.push(dialog);
// We were already ready to redraw a new dialog
// Redraw fully to ensure a proper draw of the whole stack
if (_redrawStatus == kRedrawOpenDialog)
_redrawStatus = kRedrawFull;
if (_redrawStatus != kRedrawFull)
_redrawStatus = kRedrawOpenDialog;
// We reflow the dialog just before opening it. If the screen changed
// since the last time we looked, also refresh the loaded theme,
// and reflow all other open dialogs, too.
if (!checkScreenChange())
dialog->reflowLayout();
}
void GuiManager::closeTopDialog() {
// Don't do anything if no dialog is open
if (_dialogStack.empty())
return;
// Remove the dialog from the stack
_dialogStack.pop()->lostFocus();
if (!_dialogStack.empty()) {
Dialog *dialog = getTopDialog();
giveFocusToDialog(dialog);
}
if (_redrawStatus != kRedrawFull)
_redrawStatus = kRedrawCloseDialog;
redraw();
}
void GuiManager::setupCursor() {
const byte palette[] = {
255, 255, 255,
255, 255, 255,
171, 171, 171,
87, 87, 87
};
CursorMan.pushCursorPalette(palette, 0, 4);
CursorMan.pushCursor(nullptr, 0, 0, 0, 0, 0);
CursorMan.showMouse(true);
}
// Draw the mouse cursor (animated). This is pretty much the same as in old
// SCUMM games, but the code no longer resembles what we have in cursor.cpp
// very much. We could plug in a different cursor here if we like to.
void GuiManager::animateCursor() {
uint32 time = _system->getMillis(true);
if (time - _cursorAnimateTimer > (uint32)kCursorAnimateDelay) {
for (int i = 0; i < 15; i++) {
if ((i < 6) || (i > 8)) {
_cursor[16 * 7 + i] = _cursorAnimateCounter;
_cursor[16 * i + 7] = _cursorAnimateCounter;
}
}
CursorMan.replaceCursor(_cursor, 16, 16, 7, 7, 255);
_cursorAnimateTimer = time;
_cursorAnimateCounter = (_cursorAnimateCounter + 1) % 4;
}
}
bool GuiManager::checkScreenChange() {
int tmpScreenChangeID = _system->getScreenChangeID();
if (_lastScreenChangeID != tmpScreenChangeID) {
screenChange();
return true;
}
return false;
}
void GuiManager::screenChange() {
#ifdef ENABLE_EVENTRECORDER
// Suspend recording while GUI is redrawn.
// We need this in addition to the lock in runLoop, as EVENT_SCREEN_CHANGED can
// be fired by in-game GUI components (such as the event recorder itself)
g_eventRec.acquireRecording();
#endif
_lastScreenChangeID = _system->getScreenChangeID();
computeScaleFactor();
// reinit the whole theme
_theme->refresh();
// refresh all dialogs
for (DialogStack::size_type i = 0; i < _dialogStack.size(); ++i) {
_dialogStack[i]->reflowLayout();
}
// We need to redraw immediately. Otherwise
// some other event may cause a widget to be
// redrawn before redraw() has been called.
redrawFull();
#ifdef ENABLE_EVENTRECORDER
// Resume recording once GUI has redrawn
g_eventRec.releaseRecording();
#endif
}
void GuiManager::processEvent(const Common::Event &event, Dialog *const activeDialog) {
if (activeDialog == nullptr)
return;
int button;
uint32 time;
int16 mouseX = event.mouse.x;
if (g_gui.useRTL()) {
mouseX = g_system->getOverlayWidth() - mouseX;
}
Common::Point mouse(mouseX - activeDialog->_x, event.mouse.y - activeDialog->_y);
switch (event.type) {
case Common::EVENT_KEYDOWN:
activeDialog->handleKeyDown(event.kbd);
break;
case Common::EVENT_KEYUP:
activeDialog->handleKeyUp(event.kbd);
break;
case Common::EVENT_MOUSEMOVE:
_globalMousePosition.x = mouseX;
_globalMousePosition.y = event.mouse.y;
activeDialog->handleMouseMoved(mouse.x, mouse.y, 0);
if (mouse.x != _lastMousePosition.x || mouse.y != _lastMousePosition.y) {
setLastMousePos(mouse.x, mouse.y);
}
break;
// We don't distinguish between mousebuttons (for now at least)
case Common::EVENT_LBUTTONDOWN:
case Common::EVENT_RBUTTONDOWN:
button = (event.type == Common::EVENT_LBUTTONDOWN ? 1 : 2);
time = _system->getMillis(true);
if (_lastClick.count && (time - _lastClick.time < (uint32)kDoubleClickDelay)
&& ABS(_lastClick.x - event.mouse.x) < 3
&& ABS(_lastClick.y - event.mouse.y) < 3) {
_lastClick.count++;
} else {
_lastClick.x = event.mouse.x;
_lastClick.y = event.mouse.y;
_lastClick.count = 1;
}
_lastClick.time = time;
activeDialog->handleMouseDown(mouse.x, mouse.y, button, _lastClick.count);
break;
case Common::EVENT_LBUTTONUP:
case Common::EVENT_RBUTTONUP:
button = (event.type == Common::EVENT_LBUTTONUP ? 1 : 2);
activeDialog->handleMouseUp(mouse.x, mouse.y, button, _lastClick.count);
break;
case Common::EVENT_WHEELUP:
activeDialog->handleMouseWheel(mouse.x, mouse.y, -1);
break;
case Common::EVENT_WHEELDOWN:
activeDialog->handleMouseWheel(mouse.x, mouse.y, 1);
break;
case Common::EVENT_SCREEN_CHANGED:
screenChange();
break;
default:
activeDialog->handleOtherEvent(event);
break;
}
}
void GuiManager::scheduleTopDialogRedraw() {
// Open/Close dialog redraws have higher priority, otherwise they may not be processed at all
// Full redraw also has higher priority
if (_redrawStatus != kRedrawOpenDialog &&
_redrawStatus != kRedrawCloseDialog &&
_redrawStatus != kRedrawFull)
_redrawStatus = kRedrawTopDialog;
}
void GuiManager::scheduleFullRedraw() {
_redrawStatus = kRedrawFull;
}
void GuiManager::giveFocusToDialog(Dialog *dialog) {
int16 dialogX = _globalMousePosition.x - dialog->_x;
int16 dialogY = _globalMousePosition.y - dialog->_y;
dialog->receivedFocus(dialogX, dialogY);
if (dialog->isMouseUpdatedOnFocus()) {
setLastMousePos(dialogX, dialogY);
}
}
void GuiManager::setLastMousePos(int16 x, int16 y) {
_lastMousePosition.x = x;
_lastMousePosition.y = y;
_lastMousePosition.time = _system->getMillis(true);
}
void GuiManager::setLanguageRTL() {
if (ConfMan.hasKey("guiRTL")) { // Put guiRTL = yes to your scummvm.ini to force RTL GUI
_useRTL = ConfMan.getBool("guiRTL");
return;
}
#ifdef USE_TRANSLATION
Common::String language = TransMan.getCurrentLanguage();
if (language.equals("he") || language.equals("ar")) { // GUI TODO: modify when we'll support other RTL languages, such as Arabic and Farsi
_useRTL = true;
return;
}
#endif // USE_TRANSLATION
_useRTL = false;
}
void GuiManager::initTextToSpeech() {
Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
if (ttsMan == nullptr)
return;
ttsMan->enable(ConfMan.hasKey("tts_enabled", "scummvm") ? ConfMan.getBool("tts_enabled", "scummvm") : false);
#ifdef USE_TRANSLATION
Common::String currentLanguage = TransMan.getCurrentLanguage();
ttsMan->setLanguage(currentLanguage);
#endif
int volume = (ConfMan.getInt("speech_volume", "scummvm") * 100) / 256;
if (ConfMan.hasKey("mute", "scummvm") && ConfMan.getBool("mute", "scummvm"))
volume = 0;
ttsMan->setVolume(volume);
unsigned voice;
if(ConfMan.hasKey("tts_voice")) {
voice = ConfMan.getInt("tts_voice", "scummvm");
if (voice >= ttsMan->getVoicesArray().size())
voice = ttsMan->getDefaultVoice();
} else
voice = ttsMan->getDefaultVoice();
ttsMan->setVoice(voice);
}
Graphics::MacWindowManager *GuiManager::getWM() {
if (_wm)
return _wm;
if (ConfMan.hasKey("extrapath")) {
Common::FSNode dir(ConfMan.getPath("extrapath"));
SearchMan.addDirectory(dir);
}
uint32 wmMode = Graphics::kWMModeNoDesktop | Graphics::kWMMode32bpp | Graphics::kWMModeNoCursorOverride;
_wm = new Graphics::MacWindowManager(wmMode);
return _wm;
}
void GuiManager::emptyTrash(Dialog *const activeDialog) {
Common::List<GuiObjectTrashItem>::iterator it = _guiObjectTrash.begin();
while (it != _guiObjectTrash.end()) {
if ((*it).parent == nullptr || (*it).parent == activeDialog) {
debug(7, "Delayed deletion of Gui Object %p", (void *)(*it).object);
delete (*it).object;
it = _guiObjectTrash.erase(it);
} else
++it;
}
}
} // End of namespace GUI

247
gui/gui-manager.h Normal file
View File

@@ -0,0 +1,247 @@
/* 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/>.
*
*/
#ifndef GUIMANAGER_H
#define GUIMANAGER_H
#include "common/scummsys.h"
#include "common/singleton.h"
#include "common/stack.h"
#include "common/str.h"
#include "common/list.h"
#include "common/mutex.h"
#include "gui/ThemeEngine.h"
#include "gui/widget.h"
class OSystem;
namespace Graphics {
class Font;
class MacWindowManager;
}
namespace Common {
struct Event;
class Keymap;
}
namespace GUI {
enum {
kActionEnd,
kActionShiftEnd,
kActionHome,
kActionShiftHome,
kActionCopy,
kActionCut,
kActionPaste,
};
enum {
kIconsSetLoadedCmd = 'icns'
};
class Dialog;
class ThemeEval;
class GuiObject;
#define g_gui (GUI::GuiManager::instance())
// Height of a single text line
#define kLineHeight (g_gui.getFontHeight() + 2)
// Simple dialog stack class
// Anybody nesting dialogs deeper than 4 is mad anyway
typedef Common::FixedStack<Dialog *> DialogStack;
/**
* GUI manager singleton.
*/
class GuiManager : public Common::Singleton<GuiManager>, public CommandSender {
friend class Dialog;
friend class Common::Singleton<SingletonBaseType>;
GuiManager();
~GuiManager() override;
public:
// Main entry for the GUI: this will start an event loop that keeps running
// until no dialogs are active anymore.
void runLoop();
// If the GUI loop is running close all the dialogs causing the loop to finish.
// Typically you may want to use it after setting the ConfMan active domain to
// a game domain to cause the game to start.
void exitLoop();
void processEvent(const Common::Event &event, Dialog *const activeDialog);
Common::Keymap *getKeymap() const;
void scheduleTopDialogRedraw();
void scheduleFullRedraw();
bool isActive() const { return ! _dialogStack.empty(); }
bool loadNewTheme(Common::String id, ThemeEngine::GraphicsMode gfx = ThemeEngine::kGfxDisabled, bool force = false);
ThemeEngine *theme() { return _theme; }
ThemeEval *xmlEval() { return _theme->getEvaluator(); }
void lockIconsSet() { _iconsMutex.lock(); }
void unlockIconsSet() { _iconsMutex.unlock(); }
Common::SearchSet &getIconsSet() { return _iconsSet; }
int16 getGUIWidth() const { return _baseWidth; }
int16 getGUIHeight() const { return _baseHeight; }
float getScaleFactor() const { return _scaleFactor; }
void computeScaleFactor();
bool useLowResGUI() const { return _baseWidth <= 320; }
bool useRTL() const { return _useRTL; }
void setLanguageRTL();
const Graphics::Font &getFont(ThemeEngine::FontStyle style = ThemeEngine::kFontStyleBold) const { return *(_theme->getFont(style)); }
int getFontHeight(ThemeEngine::FontStyle style = ThemeEngine::kFontStyleBold) const { return _theme->getFontHeight(style); }
int getStringWidth(const Common::String &str, ThemeEngine::FontStyle style = ThemeEngine::kFontStyleBold) const { return _theme->getStringWidth(str, style); }
int getStringWidth(const Common::U32String &str, ThemeEngine::FontStyle style = ThemeEngine::kFontStyleBold) const { return _theme->getStringWidth(str, style); }
int getCharWidth(uint32 c, ThemeEngine::FontStyle style = ThemeEngine::kFontStyleBold) const { return _theme->getCharWidth(c, style); }
int getKerningOffset(uint32 left, uint32 right, ThemeEngine::FontStyle font = ThemeEngine::kFontStyleBold) const { return _theme->getKerningOffset(left, right, font); }
/**
* Tell the GuiManager to check whether the screen resolution has changed.
* If that is the case, the GuiManager will reload/refresh the active theme.
*
* @return true if the a screen change indeed occurred, false otherwise
*/
bool checkScreenChange();
/**
* Tell the GuiManager to delete the given GuiObject later. If a parent
* dialog is provided and is present in the DialogStack, the object will
* only be deleted when that dialog is the top level dialog.
*/
void addToTrash(GuiObject*, Dialog* parent = nullptr);
void initTextToSpeech();
bool _launched;
void redrawFull();
void initIconsSet();
void displayTopDialogOnly(bool mode);
Graphics::MacWindowManager *getWM();
protected:
enum RedrawStatus {
kRedrawDisabled = 0,
kRedrawOpenDialog,
kRedrawCloseDialog,
kRedrawTopDialog,
kRedrawFull
};
OSystem *_system;
ThemeEngine *_theme;
// bool _needRedraw;
RedrawStatus _redrawStatus;
int _lastScreenChangeID;
int16 _baseWidth, _baseHeight;
float _scaleFactor;
DialogStack _dialogStack;
bool _stateIsSaved;
bool _useStdCursor;
bool _useRTL;
bool _displayTopDialogOnly;
Common::Mutex _iconsMutex;
Common::SearchSet _iconsSet;
bool _iconsSetChanged;
Graphics::MacWindowManager *_wm = nullptr;
// position and time of last mouse click (used to detect double clicks)
struct MousePos {
MousePos() : x(-1), y(-1), count(0) { time = 0; }
int16 x, y; // Position of mouse when the click occurred
uint32 time; // Time
int count; // How often was it already pressed?
} _lastClick, _lastMousePosition, _globalMousePosition;
struct TooltipData {
TooltipData() : x(-1), y(-1) { time = 0; wdg = nullptr; }
uint32 time; // Time
Widget *wdg; // Widget that had its tooltip shown
int16 x, y; // Position of mouse before tooltip was focused
} _lastTooltipShown;
// mouse cursor state
uint32 _cursorAnimateCounter;
uint32 _cursorAnimateTimer;
byte _cursor[2048];
// delayed deletion of GuiObject
struct GuiObjectTrashItem {
GuiObject* object;
Dialog* parent;
};
Common::List<GuiObjectTrashItem> _guiObjectTrash;
void initKeymap();
void enableKeymap(bool enabled);
void saveState();
void restoreState();
void openDialog(Dialog *dialog);
void closeTopDialog();
void redraw();
void redrawInternalTopDialogOnly();
void redrawInternal();
void setupCursor();
void animateCursor();
Dialog *getTopDialog() const;
void screenChange();
void giveFocusToDialog(Dialog *dialog);
void setLastMousePos(int16 x, int16 y);
void emptyTrash(Dialog *const activeDialog);
};
} // End of namespace GUI
#endif

159
gui/helpdialog.cpp Normal file
View File

@@ -0,0 +1,159 @@
/* 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/helpdialog.h"
#include "gui/gui-manager.h"
#include "gui/ThemeEval.h"
#include "gui/widget.h"
#include "gui/widgets/richtext.h"
#include "gui/widgets/tab.h"
namespace GUI {
HelpDialog::HelpDialog()
: Dialog("HelpDialog") {
_tab = new TabWidget(this, "HelpDialog.TabWidget");
static const char * const helpTabs[] = {
_s("General"),
"",
_s(
"## ScummVM at a Glance\n"
"\n"
"ScummVM is a modern reimplementation of various game engines. Once you transfer the original game data to your device, it endeavors to use it to faithfully recreate the original gaming experience. \n"
"\n"
"ScummVM isn't your typical emulator of DOS, Windows, or some console. Rather than a one-size-fits-all approach, it takes a meticulous route, implementing the precise game logic for each specific title or engine it supports. ScummVM will not work with game engines it does not support.\n"
"\n"
"ScummVM is developed by a team of volunteers and is free software. We lack an extensive testing team, possess only a limited range of devices, and cannot always address every request. We also do not run advertisements or sell you anything. Please be mindful of this when you submit a complaint or a bug report.\n"
"\n"
"## Where to get the games\n"
"\n"
"Visit [our Wiki](https://wiki.scummvm.org/index.php?title=Where_to_get_the_games) for a detailed list of supported games and where to purchase them.\n"
"\n"
"Alternatively, you can download a variety of [freeware games](https://scummvm.org/games) and [demos](https://www.scummvm.org/demos/) directly from our website.\n"
"\n"
"The ScummVM team does not endorse any specific game supplier. "
"However, the project receives a commission from every purchase made on "
"[ZOOM-Platform](https://www.zoom-platform.com/?affiliate=c049516c-9c4c-42d6-8649-92ed870e8b53) "
"through affiliate referral links.\n"
"\n"
"Additionally, games not available on ZOOM-Platform can be found on other suppliers such as GOG.com and Steam.\n"
"\n"
"For other (out-of-print) games, consider checking platforms like Amazon, eBay, Game Trading Zone, or other auction "
"sites. Be cautious of faulty games and illegal game copies.\n"
),
#ifdef USE_CLOUD
_s("Cloud"),
"helpdialog.zip",
_s(
"## Connecting a cloud service - Quick mode\n"
"\n"
"1. From the Launcher, select **Global Options** and then select the **Cloud** tab.\n"
"\n"
"2. Select your preferred cloud storage service from the **Active storage** dropdown, then select **Connect**.\n"
"\n "
" ![Select cloud service](choose_storage.png \"Select cloud service\"){w=70%,maxw=50em}\n"
"\n"
"3. Select **Quick mode**.\n"
"\n "
" ![Quick mode](quick_mode.png \"Quick mode\"){w=70%,maxw=50em}\n"
"\n"
"4. Select **Run server** and then select **Next** \n"
"\n "
" ![Run server](run_server.png \"Run server\"){w=70%,maxw=50em}\n"
"\n"
" ![Next step](server_next.png \"Next step\"){w=70%,maxw=50em}\n"
"\n"
"5. Open the link.\n"
"\n "
" ![Open the link](open_link.png \"Open the link\"){w=70%,maxw=50em}\n"
"\n"
"6. In the browser window that opens, select the cloud service to connect. \n"
"\n "
" ![Choose the cloud service](cloud_browser.png \"Choose the cloud service\"){w=70%,maxw=50em}\n"
"\n"
"7. Sign in to the chosen cloud service. Once completed, return to ScummVM.\n"
"\n"
"8. On the success screen, select **Finish** to exit. \n"
"\n "
" ![Success](cloud_success.png \"Success\"){w=70%,maxw=50em}\n"
"9. Back on the main Cloud tab, select **Enable storage**.\n"
"\n "
" ![Enable storage](enable_storage.png \"Enable storage\"){w=70%,maxw=50em}\n"
"\n"
"10. You're ready to go! Use the cloud functionality to sync saved games or game files between your devices.\n"
"\n "
" ![Cloud functionality](cloud_functions.png \"Cloud functionality\"){w=70%,maxw=50em}\n"
"\n"
" For more information, including how to use the manual connection wizard, see our [Cloud documentation](https://docs.scummvm.org/en/latest/use_scummvm/connect_cloud.html) "
),
#endif
0,
};
addTabs(helpTabs);
// Now add backend-specific tabs if any
const char * const *backendTabs = g_system->buildHelpDialogData();
if (backendTabs)
addTabs(backendTabs);
_tab->setActiveTab(0);
new ButtonWidget(this, "HelpDialog.Close", Common::U32String("Close"), Common::U32String(), kCloseCmd);
}
void HelpDialog::addTabs(const char * const *tabData) {
while (*tabData) {
Common::U32String tabName(_(*tabData++));
const char *imagePack = nullptr;
if (*tabData && **tabData)
imagePack = *tabData;
tabData++;
Common::U32String tabText(_(*tabData++));
_tab->addTab(tabName, "HelpContentDialog");
RichTextWidget *rt = new RichTextWidget(_tab, "HelpContentDialog.RichTextWidget", tabText);
if (imagePack)
rt->setImageArchive(imagePack);
}
}
void HelpDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {
switch (cmd) {
case kCloseCmd:
close();
}
}
} // End of namespace GUI

51
gui/helpdialog.h Normal file
View File

@@ -0,0 +1,51 @@
/* 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/>.
*
*/
#ifndef GUI_HELP_DIALOG_H
#define GUI_HELP_DIALOG_H
#include "gui/dialog.h"
#include "common/str.h"
#include "common/str-array.h"
namespace GUI {
class CommandSender;
class TabWidget;
/**
* Multitab help dialog
*/
class HelpDialog : public Dialog {
public:
HelpDialog();
void handleCommand(CommandSender *sender, uint32 cmd, uint32 data) override;
private:
void addTabs(const char * const *tabs);
TabWidget *_tab;
};
} // End of namespace GUI
#endif

462
gui/imagealbum-dialog.cpp Normal file
View File

@@ -0,0 +1,462 @@
/* 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 "gui/imagealbum-dialog.h"
#include "graphics/palette.h"
#include "gui/dialog.h"
#include "gui/filebrowser-dialog.h"
#include "gui/gui-manager.h"
#include "gui/ThemeEval.h"
#include "gui/widget.h"
#include "gui/widgets/scrollcontainer.h"
#include "image/bmp.h"
#include "image/png.h"
#include "common/dialogs.h"
#include "common/savefile.h"
#include "common/stream.h"
#include "common/translation.h"
namespace GUI {
ImageAlbumImageSupplier::~ImageAlbumImageSupplier() {
}
class ImageAlbumDialog : public Dialog {
public:
ImageAlbumDialog(const Common::U32String &title, ImageAlbumImageSupplier *imageSupplier, uint initialSlot);
~ImageAlbumDialog();
void open() override;
void close() override;
void handleCommand(CommandSender *sender, uint32 cmd, uint32 data) override;
private:
ImageAlbumDialog() = delete;
ImageAlbumDialog(const ImageAlbumDialog &) = delete;
void changeToSlot(uint slot);
void saveImageInSlot(uint slot);
ImageAlbumImageSupplier *_imageSupplier;
uint _currentSlot;
uint _numSlots;
ButtonWidget *_prevButton;
ButtonWidget *_nextButton;
ButtonWidget *_saveButton;
StaticTextWidget *_imageNumberLabel;
ContainerWidget *_imageContainer;
GraphicsWidget *_imageGraphic;
bool _canAlwaysSaveImage;
enum {
kPrevCmd = 'PREV',
kNextCmd = 'NEXT',
kSaveCmd = 'SAVE',
};
};
ImageAlbumDialog::ImageAlbumDialog(const Common::U32String &title, ImageAlbumImageSupplier *imageSupplier, uint initialSlot)
: Dialog("ImageAlbum"), _imageSupplier(imageSupplier), _currentSlot(initialSlot), _numSlots(0), _imageGraphic(nullptr), _canAlwaysSaveImage(false) {
_backgroundType = ThemeEngine::kDialogBackgroundSpecial;
_numSlots = imageSupplier->getNumSlots();
assert(_numSlots > 0);
new StaticTextWidget(this, "ImageAlbum.Title", title);
if (initialSlot >= _numSlots)
initialSlot = _numSlots - 1;
// Buttons
if (_numSlots > 1) {
_prevButton = new ButtonWidget(this, "ImageAlbum.Prev", _("Prev"), Common::U32String(), kPrevCmd);
_nextButton = new ButtonWidget(this, "ImageAlbum.Next", _("Next"), Common::U32String(), kNextCmd);
_imageNumberLabel = new StaticTextWidget(this, "ImageAlbum.ImageNumber", Common::U32String());
} else {
_prevButton = nullptr;
_nextButton = nullptr;
_imageNumberLabel = nullptr;
}
_saveButton = nullptr;
bool canSaveAnyFormat = false;
for (uint fmtID = Common::FormatInfo::kFirstImageFormat; fmtID <= Common::FormatInfo::kLastImageFormat; fmtID++) {
Common::FormatInfo::FormatID format = static_cast<Common::FormatInfo::FormatID>(fmtID);
if (Common::FormatInfo::getFormatSupportLevel(format) > Common::FormatInfo::kFormatSupportLevelNone) {
canSaveAnyFormat = true;
if (Common::FormatInfo::getImageSaveFunction(format) != nullptr) {
_canAlwaysSaveImage = true;
break;
}
}
}
if (canSaveAnyFormat) {
_saveButton = new ButtonWidget(this, "ImageAlbum.Save", _("Save Image..."), Common::U32String(), kSaveCmd);
_saveButton->setEnabled(!_canAlwaysSaveImage);
}
new ButtonWidget(this, "ImageAlbum.Close", _("Close"), Common::U32String(), kCloseCmd);
_imageContainer = new ContainerWidget(this, "ImageAlbum.ImageContainer");
_imageGraphic = nullptr;
}
ImageAlbumDialog::~ImageAlbumDialog() {
}
void ImageAlbumDialog::open() {
Dialog::open();
changeToSlot(_currentSlot);
}
void ImageAlbumDialog::changeToSlot(uint slot) {
bool canSaveImage = _canAlwaysSaveImage;
if (_imageGraphic) {
_imageContainer->removeWidget(_imageGraphic);
delete _imageGraphic;
_imageGraphic = nullptr;
}
Common::Rect graphicRect = Common::Rect(0, 0, _imageContainer->getWidth(), _imageContainer->getHeight());
int inset = g_gui.xmlEval()->getVar("Globals.ImageAlbum.ImageInset", 0);
graphicRect.grow(-inset);
if (graphicRect.isValidRect()) {
uint32 graphicRectWidth = graphicRect.width();
uint32 graphicRectHeight = graphicRect.height();
const Graphics::Surface *surf = nullptr;
bool hasPalette = false;
Graphics::Palette palette(256);
ImageAlbumImageMetadata metadata;
if (_imageSupplier->loadImageSlot(slot, surf, hasPalette, palette, metadata)) {
if (!canSaveImage) {
// If we can't always save the image (meaning we don't have an image write-out function) then see if we can
// at least save this file in its native format.
Common::FormatInfo::FormatID format = Common::FormatInfo::kFormatUnknown;
if (_imageSupplier->getFileFormatForImageSlot(slot, format)) {
if (Common::FormatInfo::getFormatSupportLevel(format) > Common::FormatInfo::kFormatSupportLevelNone)
canSaveImage = true;
}
}
uint32 imageWidth = surf->w;
uint32 imageHeight = surf->h;
uint32 scaledWidth = graphicRectWidth;
uint32 scaledHeight = graphicRectHeight;
bool needs90Rotate = (metadata._viewTransformation == kImageAlbumViewTransformationRotate90CCW || metadata._viewTransformation == kImageAlbumViewTransformationRotate90CW);
uint32 imageRotatedWidth = imageWidth;
uint32 imageRotatedHeight = imageHeight;
if (needs90Rotate) {
imageRotatedWidth = imageHeight;
imageRotatedHeight = imageWidth;
}
// if (imageRotatedWidth / imageRotatedHeight > graphicRectWidth / graphicRectHeight)
if (imageRotatedWidth * graphicRectHeight >= graphicRectWidth * imageRotatedHeight) {
// Image aspect ratio is wider than the graphic space, or same
scaledWidth = graphicRectWidth;
scaledHeight = imageRotatedHeight * graphicRectWidth / imageRotatedWidth;
} else {
// Image aspect ratio is taller than the graphic space
scaledWidth = imageRotatedWidth * graphicRectHeight / imageRotatedHeight;
scaledHeight = graphicRectHeight;
}
if (scaledWidth < 1)
scaledWidth = 1;
if (scaledHeight < 1)
scaledHeight = 1;
Graphics::ManagedSurface rescaledGraphic;
rescaledGraphic.create(scaledWidth, scaledHeight, surf->format);
if (hasPalette)
rescaledGraphic.setPalette(palette.data(), 0, 256);
if (needs90Rotate) {
bool isClockwise = metadata._viewTransformation == kImageAlbumViewTransformationRotate90CW;
for (uint32 destX = 0; destX < scaledWidth; destX++) {
uint32 srcY = destX * imageHeight / scaledWidth;
if (isClockwise)
srcY = imageHeight - 1 - srcY;
for (uint32 destY = 0; destY < scaledHeight; destY++) {
uint32 srcX = destY * imageWidth / scaledHeight;
if (!isClockwise)
srcX = imageWidth - 1 - srcX;
rescaledGraphic.setPixel(destX, destY, surf->getPixel(srcX, srcY));
}
}
} else if (metadata._viewTransformation == kImageAlbumViewTransformationRotate180) {
for (uint32 destX = 0; destX < scaledWidth; destX++) {
uint32 srcX = (imageWidth - 1 - (destX * imageWidth / scaledWidth));
for (uint32 destY = 0; destY < scaledHeight; destY++) {
uint32 srcY = (imageHeight - 1 - (destY * imageHeight / scaledHeight));
rescaledGraphic.setPixel(destX, destY, surf->getPixel(srcX, srcY));
}
}
} else {
rescaledGraphic.blitFrom(*surf, Common::Rect(0, 0, imageWidth, imageHeight), Common::Rect(0, 0, scaledWidth, scaledHeight));
}
_imageSupplier->releaseImageSlot(slot);
int32 xCoord = (static_cast<int32>(_imageContainer->getWidth()) - static_cast<int32>(scaledWidth)) / 2u;
int32 yCoord = (static_cast<int32>(_imageContainer->getHeight()) - static_cast<int32>(scaledHeight)) / 2u;
_imageGraphic = new GraphicsWidget(_imageContainer, xCoord, yCoord, xCoord + static_cast<int32>(scaledWidth), yCoord + static_cast<int32>(scaledHeight));
_imageGraphic->setGfx(&rescaledGraphic, false);
if (_numSlots > 1) {
_imageNumberLabel->setLabel(Common::U32String::format(_("%u of %u"), static_cast<uint>(slot + 1u), _numSlots));
_prevButton->setEnabled(slot > 0);
_nextButton->setEnabled(slot < _numSlots - 1u);
}
_currentSlot = slot;
} else {
warning("Image album failed to retrieve slot %u", slot);
}
}
if (_saveButton)
_saveButton->setEnabled(canSaveImage);
}
void ImageAlbumDialog::saveImageInSlot(uint slot) {
Common::U32String defaultFileName = _imageSupplier->getDefaultFileNameForSlot(slot);
Common::FormatInfo::FormatID nativeFormat = Common::FormatInfo::kFormatUnknown;
Common::U32String fileExt;
Common::U32String fileDesc;
bool hasExtension = 0;
uint extensionPos = 0;
for (uint i = 0; i < defaultFileName.size(); i++) {
if (defaultFileName[i] == '.') {
hasExtension = true;
extensionPos = i;
}
}
Common::SaveFileManager *saveFileManager = g_system->getSavefileManager();
Common::FormatInfo::FormatSupportLevel bestFormatSupportLevel = Common::FormatInfo::kFormatSupportLevelNone;
Common::FormatInfo::FormatID bestFormat = Common::FormatInfo::kFormatUnknown;
bool bestFormatIsLossy = true;
// Find the best format we can write the image as
for (uint fmtID = Common::FormatInfo::kFirstImageFormat; fmtID <= Common::FormatInfo::kLastImageFormat; fmtID++) {
Common::FormatInfo::FormatID candidateFormat = static_cast<Common::FormatInfo::FormatID>(fmtID);
if (!Common::FormatInfo::getImageSaveFunction(candidateFormat))
continue;
Common::FormatInfo::FormatSupportLevel supportLevel = Common::FormatInfo::getFormatSupportLevel(candidateFormat);
bool formatIsLossy = false;
Common::FormatInfo::ImageFormatCharacteristics characteristics;
if (Common::FormatInfo::getImageFormatCharacteristics(candidateFormat, characteristics))
formatIsLossy = (characteristics._lossiness == Common::FormatInfo::kLossinessLossy);
bool isBetter = false;
// If the best format we have chosen is lossy, and this is a lossless format that is at least supported, it is better
// If this format is the same lossiness, but is better-supported, it is better
if (bestFormatIsLossy && !formatIsLossy && supportLevel >= Common::FormatInfo::kFormatSupportLevelSupported)
isBetter = true;
else if (bestFormatIsLossy == formatIsLossy && supportLevel > bestFormatSupportLevel)
isBetter = true;
else if (bestFormat == Common::FormatInfo::kFormatUnknown && supportLevel >= Common::FormatInfo::kFormatSupportLevelNone)
isBetter = true;
if (isBetter) {
bestFormatSupportLevel = supportLevel;
bestFormat = candidateFormat;
bestFormatIsLossy = formatIsLossy;
}
}
assert(bestFormat != Common::FormatInfo::kFormatUnknown);
if (_imageSupplier->getFileFormatForImageSlot(slot, nativeFormat)) {
if (nativeFormat != bestFormat) {
Common::FormatInfo::FormatSupportLevel nativeSupportLevel = Common::FormatInfo::getFormatSupportLevel(nativeFormat);
if (nativeSupportLevel > Common::FormatInfo::kFormatSupportLevelNone) {
bool nativeFormatIsLossy = false;
Common::FormatInfo::ImageFormatCharacteristics characteristics;
if (Common::FormatInfo::getImageFormatCharacteristics(nativeFormat, characteristics))
nativeFormatIsLossy = (characteristics._lossiness == Common::FormatInfo::kLossinessLossy);
// If the native format is lossy and is at least supported, prefer using it directly, otherwise only use it if it has a higher support level
if ((nativeFormatIsLossy && nativeSupportLevel >= Common::FormatInfo::kFormatSupportLevelSupported) || nativeSupportLevel >= bestFormatSupportLevel) {
bestFormat = nativeFormat;
bestFormatSupportLevel = nativeSupportLevel;
}
}
}
}
// This shouldn't be possible, the Save button should not be visible unless there is either a saveable format,
// or the file's native format is saveable, and in either of those circumstances, a format should have been selected by this point.
assert(bestFormatSupportLevel > Common::FormatInfo::kFormatSupportLevelNone);
bool needsConversion = false;
if (nativeFormat == bestFormat) {
// Save in the preferred format
if (hasExtension)
fileExt = defaultFileName.substr(extensionPos + 1);
} else {
// Save in the preferred format
needsConversion = true;
fileExt = Common::U32String(Common::FormatInfo::getFormatExtension(bestFormat, true));
if (hasExtension)
defaultFileName = defaultFileName.substr(0, extensionPos) + Common::U32String(".") + fileExt;
}
fileDesc = Common::FormatInfo::getFormatSaveDescription(bestFormat);
Common::U32String title = _("Save Image");
if (needsConversion) {
const Graphics::Surface *surf = nullptr;
bool hasPalette = false;
Graphics::Palette palette(256);
ImageAlbumImageMetadata metadata;
if (_imageSupplier->loadImageSlot(slot, surf, hasPalette, palette, metadata)) {
Common::ScopedPtr<Common::SeekableWriteStream> writeStream;
GUI::FileBrowserDialog browser(title.encode(Common::kUtf8).c_str(), fileExt.encode(Common::kUtf8).c_str(), GUI::kFBModeSave, nullptr, defaultFileName.encode(Common::kUtf8).c_str());
if (browser.runModal() > 0) {
Common::String path = browser.getResult();
writeStream.reset(saveFileManager->openForSaving(path, false));
if (writeStream) {
assert(writeStream);
Common::FormatInfo::ImageSaveCallback_t saveCallback = Common::FormatInfo::getImageSaveFunction(bestFormat);
assert(saveCallback);
Common::FormatInfo::ImageSaveProperties saveProps;
saveCallback(*writeStream, *surf, hasPalette ? palette.data() : nullptr, saveProps);
} else {
warning("Failed to open image output stream");
}
}
_imageSupplier->releaseImageSlot(slot);
}
} else {
Common::ScopedPtr<Common::SeekableReadStream> readStream;
readStream.reset(_imageSupplier->createReadStreamForSlot(slot));
if (!readStream) {
warning("Failed to open input stream for slot %u", slot);
return;
}
Common::ScopedPtr<Common::SeekableWriteStream> writeStream;
GUI::FileBrowserDialog browser(title.encode(Common::kUtf8).c_str(), fileExt.encode(Common::kUtf8).c_str(), GUI::kFBModeSave, nullptr, defaultFileName.encode(Common::kUtf8).c_str());
if (browser.runModal() > 0) {
Common::String path = browser.getResult();
writeStream.reset(saveFileManager->openForSaving(path, false));
if (writeStream) {
assert(writeStream);
byte copyBuffer[2048];
uint32 bytesRead = readStream->read(copyBuffer, sizeof(copyBuffer));
while (bytesRead) {
writeStream->write(copyBuffer, bytesRead);
bytesRead = readStream->read(copyBuffer, sizeof(copyBuffer));
}
} else {
warning("Failed to open image output stream");
}
}
}
}
void ImageAlbumDialog::close() {
Dialog::close();
}
void ImageAlbumDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {
switch (cmd) {
case kPrevCmd:
if (_currentSlot > 0)
changeToSlot(_currentSlot - 1);
break;
case kNextCmd:
if (_currentSlot < _numSlots - 1)
changeToSlot(_currentSlot + 1);
break;
case kSaveCmd:
saveImageInSlot(_currentSlot);
break;
default:
Dialog::handleCommand(sender, cmd, data);
}
}
GUI::Dialog *createImageAlbumDialog(const Common::U32String &title, ImageAlbumImageSupplier *imageSupplier, uint initialSlot) {
return new ImageAlbumDialog(title, imageSupplier, initialSlot);
}
} // End of namespace GUI

126
gui/imagealbum-dialog.h Normal file
View File

@@ -0,0 +1,126 @@
/* 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/>.
*
*/
#ifndef IMAGEALBUM_DIALOG_H
#define IMAGEALBUM_DIALOG_H
#include "common/formats/formatinfo.h"
#include "common/str.h"
#include "common/rational.h"
namespace Common {
class SeekableReadStream;
} // End of namespace Common
namespace Graphics {
class Palette;
struct Surface;
} // End of namespace Graphics
namespace GUI {
class Dialog;
enum ImageAlbumImageOrientation {
kImageAlbumImageOrientationUnspecified,
kImageAlbumImageOrientationLandscape,
kImageAlbumImageOrientationPortrait,
};
enum ImageAlbumViewTransformation {
kImageAlbumViewTransformationNone,
kImageAlbumViewTransformationRotate90CCW,
kImageAlbumViewTransformationRotate90CW,
kImageAlbumViewTransformationRotate180,
};
struct ImageAlbumImageMetadata {
ImageAlbumImageMetadata() : _orientation(kImageAlbumImageOrientationUnspecified), _viewTransformation(kImageAlbumViewTransformationNone), _hdpi(72, 1), _vdpi(72, 1) {}
ImageAlbumViewTransformation _viewTransformation; ///< Transformation required to present the image at its normal intended viewing orientation
ImageAlbumImageOrientation _orientation; ///< Orientation of the image after view transformation
Common::Rational _hdpi; ///< Horizontal DPI of the image
Common::Rational _vdpi; ///< Vertical DPI of the image
};
/**
* @brief Interface that supplies images to the image album dialog.
*/
class ImageAlbumImageSupplier {
public:
virtual ~ImageAlbumImageSupplier();
/**
* @brief Loads and returns the image for a specified slot
* @param slot The image slot to load
* @param outSurface An outputted pointer to a surface containing the image data
* @param outHasPalette An outputted boolean containing true if the image has a palette and false if not
* @param outPalette Outputted palette colors if the image has a palette
* @param outMetadata Outputted metadata for the image
* @return True if the image loaded successfully, false if it failed
*/
virtual bool loadImageSlot(uint slot, const Graphics::Surface *&outSurface, bool &outHasPalette, Graphics::Palette &outPalette, ImageAlbumImageMetadata &outMetadata) = 0;
/**
* @brief Releases any resources for an image loaded with loadImageSlot
* @param slot The image slot to release
*/
virtual void releaseImageSlot(uint slot) = 0;
/**
* Returns the file format of the image in the specified image slot, if it's capable of being loaded as raw file data.
*
* @param slot The image slot to load
* @param outFormat A reference to a file format ID to set to the file format
* @return true if the slot is loadable as raw data and has a MIME type available, false if not
*/
virtual bool getFileFormatForImageSlot(uint slot, Common::FormatInfo::FormatID &outFormat) const = 0;
/**
* @brief Opens a slot as a read stream containing raw file data.
* @param slot The image slot to load
* @return The created read stream, if it could be created, or nullptr if it failed
*/
virtual Common::SeekableReadStream *createReadStreamForSlot(uint slot) = 0;
/**
* @brief Returns the number of image slots
* @return The number of slots
*/
virtual uint getNumSlots() const = 0;
/**
* @brief Returns the default filename, including extension, for the specified slot
* @return The filename of the slot without an extension
*/
virtual Common::U32String getDefaultFileNameForSlot(uint slot) const = 0;
};
Dialog *createImageAlbumDialog(const Common::U32String &title, ImageAlbumImageSupplier *imageSupplier, uint initialSlot);
} // End of namespace GUI
#endif

738
gui/integrity-dialog.cpp Normal file
View File

@@ -0,0 +1,738 @@
/* 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 "gui/integrity-dialog.h"
#include "common/array.h"
#include "common/config-manager.h"
#include "common/debug.h"
#include "common/file.h"
#include "common/macresman.h"
#include "common/md5.h"
#include "common/tokenizer.h"
#include "common/translation.h"
#include "gui/chooser.h"
#include "gui/gui-manager.h"
#include "gui/launcher.h"
#include "gui/message.h"
#include "gui/widget.h"
#define TESTING 0
namespace GUI {
enum {
kResponseCmd = 'IDRC',
kCopyEmailCmd = 'IDCE',
kCleanupCmd = 'IDCl',
kDownloadProgressCmd = 'DLPg',
};
struct ResultFormat {
bool error;
Common::U32StringArray messageText;
Common::String emailLink;
Common::String errorText;
ResultFormat() {
error = 0;
messageText = Common::U32StringArray();
emailLink = "";
errorText = "";
}
} static *g_result;
struct ChecksumDialogState {
IntegrityDialog *dialog;
ProcessState state;
int totalSize;
int calculatedSize;
uint32 lastUpdate;
Common::String endpoint;
Common::Path gamePath;
Common::HashMap<Common::Path, bool, Common::Path::IgnoreCase_Hash, Common::Path::IgnoreCase_EqualTo> ignoredSubdirsMap;
Common::String gameid;
Common::String engineid;
Common::String extra;
Common::String platform;
Common::String language;
ChecksumDialogState() {
state = kChecksumStateNone;
totalSize = calculatedSize = 0;
lastUpdate = 0;
dialog = nullptr;
}
} static *g_checksum_state;
uint32 getCalculationProgress() {
if (!g_checksum_state || g_checksum_state->totalSize == 0)
return 0;
uint32 progress = (uint32)(100 * ((double)g_checksum_state->calculatedSize / (double)g_checksum_state->totalSize));
return progress;
}
IntegrityDialog::IntegrityDialog(Common::String endpoint, Common::String domain) : Dialog("GameOptions_IntegrityDialog"), CommandSender(this), _close(false), _lastEventPoll(0) {
_backgroundType = GUI::ThemeEngine::kDialogBackgroundPlain;
Common::U32String warningMessage = _(
"Verifying file integrity may take a long time to complete. Please wait...\n");
_warningText = new StaticTextWidget(this, "GameOptions_IntegrityDialog.WarningText", warningMessage);
_resultsText = new ListWidget(this, "GameOptions_IntegrityDialog.ResultsText");
_resultsText->setNumberingMode(kListNumberingOff);
_resultsText->setList({Common::U32String()});
_resultsText->setVisible(false);
_statusText = new StaticTextWidget(this, "GameOptions_IntegrityDialog.StatusText", Common::U32String::format(_("Calculating file checksums...")));
_errorText = new StaticTextWidget(this, "GameOptions_IntegrityDialog.ErrorText", Common::U32String(""));
uint32 progress = getCalculationProgress();
_progressBar = new SliderWidget(this, "GameOptions_IntegrityDialog.ProgressBar");
_progressBar->setMinValue(0);
_progressBar->setMaxValue(100);
_progressBar->setValue(progress);
_progressBar->setEnabled(false);
_percentLabel = new StaticTextWidget(this, "GameOptions_IntegrityDialog.PercentText", Common::String::format("%u %%", progress));
_calcSizeLabel = new StaticTextWidget(this, "GameOptions_IntegrityDialog.DownloadSize", Common::U32String());
_cancelButton = new ButtonWidget(this, "GameOptions_IntegrityDialog.MainButton", _("Cancel"), Common::U32String(), kCleanupCmd);
_copyEmailButton = new ButtonWidget(this, "GameOptions_IntegrityDialog.CopyButton", _("Launch Email Client"), Common::U32String(), kCopyEmailCmd);
_copyEmailButton->setEnabled(false);
if (!g_checksum_state) {
g_checksum_state = new ChecksumDialogState();
g_checksum_state->dialog = this;
Common::Array<Common::String> gameAddOns;
Common::ConfigManager::DomainMap::iterator iter = ConfMan.beginGameDomains();
for (; iter != ConfMan.endGameDomains(); ++iter) {
Common::String name(iter->_key);
Common::ConfigManager::Domain &dom = iter->_value;
Common::String parent;
if (dom.tryGetVal("parent", parent) && parent == domain)
gameAddOns.push_back(name);
}
if (!gameAddOns.empty()) {
// Ask the user to choose between the base game or one of its add-ons
Common::U32StringArray list;
list.push_back(ConfMan.get("description", domain));
for (Common::String &gameAddOn : gameAddOns) {
list.push_back(ConfMan.get("description", gameAddOn));
}
ChooserDialog dialog(_("This game includes add-ons, pick the part you want to be checked:"));
dialog.setList(list);
int idx = dialog.runModal();
if (idx < 0) {
// User cancelled the dialog
_close = true;
return;
}
if (idx >= 1 && idx < (int)gameAddOns.size() + 1) {
// User selected an add-on, change the selected domain
domain = gameAddOns[idx - 1];
} else {
// User selected the base game, ignore the add-ons subdirectories
for (Common::String &gameAddOn : gameAddOns) {
Common::Path addOnPath = ConfMan.getPath("path", gameAddOn);
g_checksum_state->ignoredSubdirsMap[addOnPath] = true;
}
}
}
setState(kChecksumStateCalculating);
refreshWidgets();
g_checksum_state->endpoint = endpoint;
g_checksum_state->gamePath = Common::Path(ConfMan.getPath("path", domain));
g_checksum_state->gameid = ConfMan.get("gameid", domain);
g_checksum_state->engineid = ConfMan.get("engineid", domain);
g_checksum_state->extra = ConfMan.get("extra", domain);
g_checksum_state->platform = ConfMan.get("platform", domain);
g_checksum_state->language = ConfMan.get("language", domain);
calculateTotalSize(g_checksum_state->gamePath, g_checksum_state->ignoredSubdirsMap);
} else {
g_checksum_state->dialog = this;
setState(g_checksum_state->state);
refreshWidgets();
}
}
IntegrityDialog::~IntegrityDialog() {
}
bool IntegrityDialog::progressUpdate(int bytesProcessed) {
if (g_checksum_state->dialog->_close)
return false;
g_checksum_state->calculatedSize += bytesProcessed;
if (g_system->getMillis() > g_checksum_state->lastUpdate + 500) {
g_checksum_state->lastUpdate = g_system->getMillis();
g_checksum_state->dialog->sendCommand(kDownloadProgressCmd, 0);
}
Common::Event event;
if (g_system->getEventManager()->pollEvent(event)) {
if (g_system->getMillis() > g_checksum_state->dialog->_lastEventPoll + 16) {
g_checksum_state->dialog->_lastEventPoll = g_system->getMillis();
g_gui.processEvent(event, g_checksum_state->dialog);
g_system->updateScreen();
}
}
return true;
}
static bool progressUpdateCallback(void *param, int bytesProcessed) {
IntegrityDialog *dialog = (IntegrityDialog *)param;
return dialog->progressUpdate(bytesProcessed);
}
void IntegrityDialog::open() {
Dialog::open();
reflowLayout();
g_gui.scheduleTopDialogRedraw();
}
void IntegrityDialog::close() {
if (g_checksum_state) {
g_checksum_state->dialog = nullptr;
delete g_checksum_state;
g_checksum_state = nullptr;
}
if (g_result) {
delete g_result;
g_result = nullptr;
}
Dialog::close();
}
void IntegrityDialog::setState(ProcessState state) {
g_checksum_state->state = state;
switch (state) {
case kChecksumStateNone:
case kChecksumStateCalculating:
_statusText->setLabel(Common::U32String::format(_("Calculating file checksums...")));
_cancelButton->setLabel(_("Cancel"));
_cancelButton->setCmd(kCleanupCmd);
break;
case kChecksumComplete:
_statusText->setLabel(Common::U32String::format(_("Calculation complete")));
_cancelButton->setVisible(true);
_cancelButton->setLabel(_("OK"));
_cancelButton->setCmd(kCleanupCmd);
// Hide all elements
_warningText->setVisible(false);
_statusText->setVisible(false);
_errorText->setVisible(false);
_percentLabel->setVisible(false);
_calcSizeLabel->setVisible(false);
_progressBar->setVisible(false);
break;
case kResponseReceived:
if (g_result->messageText.size() != 0) {
_resultsText->setList(g_result->messageText);
} else
_resultsText->setList(Common::U32StringArray({g_result->errorText}));
if (g_result->error != 0) {
_copyEmailButton->setEnabled(true);
_copyEmailButton->setCmd(kCopyEmailCmd);
}
_resultsText->setVisible(true);
break;
}
}
void IntegrityDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {
switch (cmd) {
case kResponseReceived:
setState(kResponseReceived);
break;
case kCleanupCmd: {
_close = true;
break;
}
case kDownloadProgressCmd:
if (!_close) {
refreshWidgets();
g_gui.redrawFull();
}
break;
case kCopyEmailCmd: {
g_system->openUrl(g_result->emailLink);
break;
}
default:
Dialog::handleCommand(sender, cmd, data);
}
}
void IntegrityDialog::handleTickle() {
if (_close) {
close();
_close = false;
return;
}
if (g_checksum_state->state == kChecksumStateCalculating)
sendJSON();
int32 progress = getCalculationProgress();
if (_progressBar->getValue() != progress) {
refreshWidgets();
g_gui.scheduleTopDialogRedraw();
}
Dialog::handleTickle();
}
void IntegrityDialog::reflowLayout() {
Dialog::reflowLayout();
refreshWidgets();
}
Common::U32String IntegrityDialog::getSizeLabelText() {
const char *calculatedUnits, *totalUnits;
Common::String calculated = Common::getHumanReadableBytes(g_checksum_state->calculatedSize, calculatedUnits);
Common::String total = Common::getHumanReadableBytes(g_checksum_state->totalSize, totalUnits);
return Common::U32String::format(_("Calculated %s %S / %s %S"), calculated.c_str(), _(calculatedUnits).c_str(), total.c_str(), _(totalUnits).c_str());
}
void IntegrityDialog::refreshWidgets() {
uint32 progress = getCalculationProgress();
_percentLabel->setLabel(Common::String::format("%u %%", progress));
_calcSizeLabel->setLabel(getSizeLabelText());
_progressBar->setValue(progress);
}
void IntegrityDialog::setError(Common::U32String &msg) {
_errorText->setLabel(msg);
_cancelButton->setLabel(_("Close"));
_cancelButton->setCmd(kCleanupCmd);
}
void IntegrityDialog::calculateTotalSize(Common::Path gamePath, const Common::HashMap<Common::Path, bool, Common::Path::IgnoreCase_Hash, Common::Path::IgnoreCase_EqualTo> &ignoredSubdirsMap) {
const Common::FSNode dir(gamePath);
if (!dir.exists() || !dir.isDirectory())
return;
Common::FSList fileList;
if (!dir.getChildren(fileList, Common::FSNode::kListAll))
return;
if (fileList.empty())
return;
// Process the files and subdirectories in the current directory recursively
for (const auto &entry : fileList) {
if (entry.isDirectory()) {
if (!ignoredSubdirsMap.contains(entry.getPath()))
calculateTotalSize(entry.getPath(), ignoredSubdirsMap);
} else {
Common::File file;
if (!file.open(entry))
continue;
g_checksum_state->totalSize += file.size();
}
}
}
Common::Array<Common::StringArray> IntegrityDialog::generateChecksums(Common::Path currentPath, Common::Array<Common::StringArray> &fileChecksums, Common::Path gamePath) {
const Common::FSNode dir(currentPath);
if (!dir.exists() || !dir.isDirectory())
return {};
Common::FSList fileList;
if (!dir.getChildren(fileList, Common::FSNode::kListAll))
return {};
if (fileList.empty())
return {};
// First, we go through the list and check any Mac files
Common::HashMap<Common::Path, bool, Common::Path::IgnoreCase_Hash, Common::Path::IgnoreCase_EqualTo> macFiles;
Common::HashMap<Common::Path, bool, Common::Path::IgnoreCase_Hash, Common::Path::IgnoreCase_EqualTo> toRemove;
Common::List<Common::Path> tmpFileList;
for (const auto &entry : fileList) {
if (entry.isDirectory())
continue;
Common::Path filename(entry.getPath().relativeTo(gamePath));
const Common::Path originalFileName = filename;
filename.removeExtension(".bin");
filename.removeExtension(".rsrc");
auto macFile = Common::MacResManager();
if (macFile.open(filename) && macFile.isMacFile()) {
macFiles[originalFileName] = true;
switch (macFile.getMode()) {
case Common::MacResManager::kResForkRaw:
toRemove[filename.append(".rsrc")] = true;
toRemove[filename.append(".data")] = true;
toRemove[filename.append(".finf")] = true;
break;
case Common::MacResManager::kResForkMacBinary:
toRemove[filename.append(".bin")] = true;
break;
case Common::MacResManager::kResForkAppleDouble:
toRemove[Common::MacResManager::constructAppleDoubleName(filename)] = true;
toRemove[filename.getParent().append("__MACOSX")] = true;
break;
default:
error("Unsupported MacResManager mode: %d", macFile.getMode());
}
tmpFileList.push_back(filename);
} else {
if (!toRemove.contains(originalFileName))
tmpFileList.push_back(originalFileName);
}
}
// Process the files and subdirectories in the current directory recursively
for (const auto &entry : fileList) {
Common::Path filename(entry.getPath().relativeTo(gamePath));
if (macFiles.contains(filename)) {
filename.removeExtension(".bin");
filename.removeExtension(".rsrc");
}
if (toRemove.contains(filename))
continue;
if (entry.isDirectory()) {
if (!g_checksum_state->ignoredSubdirsMap.contains(entry.getPath()))
generateChecksums(entry.getPath(), fileChecksums, gamePath);
continue;
}
auto macFile = Common::MacResManager();
if (macFile.open(filename) && macFile.isMacFile()) {
auto dataForkStream = macFile.openFileOrDataFork(filename);
Common::Array<Common::String> fileChecksum = {filename.toString()};
// Data fork
// Various checksizes
for (auto size : {0, 5000, 1024 * 1024}) {
Common::String sz = size ? Common::String::format("-%d", size) : "";
fileChecksum.push_back(Common::String::format("md5-d%s", sz.c_str()));
fileChecksum.push_back(Common::computeStreamMD5AsString(*dataForkStream, size, progressUpdateCallback, this));
dataForkStream->seek(0);
}
// Tail checksums with checksize 5000
dataForkStream->seek(-5000, SEEK_END);
fileChecksum.push_back("md5-dt-5000");
fileChecksum.push_back(Common::computeStreamMD5AsString(*dataForkStream, 0, progressUpdateCallback, this).c_str());
// Resource fork
if (macFile.hasResFork()) {
// Various checksizes
for (auto size : {0, 5000, 1024 * 1024}) {
Common::String sz = size ? Common::String::format("-%d", size) : "";
fileChecksum.push_back(Common::String::format("md5-r%s", sz.c_str()));
fileChecksum.push_back(macFile.computeResForkMD5AsString(size, false, progressUpdateCallback, this));
}
// Tail checksums with checksize 5000
fileChecksum.push_back("md5-rt-5000");
fileChecksum.push_back(macFile.computeResForkMD5AsString(5000, true, progressUpdateCallback, this).c_str());
}
fileChecksum.push_back("size");
fileChecksum.push_back(Common::String::format("%llu", (unsigned long long)macFile.getDataForkSize()));
fileChecksum.push_back("size-r");
fileChecksum.push_back(Common::String::format("%llu", (unsigned long long)macFile.getResForkSize()));
fileChecksum.push_back("size-rd");
fileChecksum.push_back(Common::String::format("%llu", (unsigned long long)macFile.getResForkDataSize()));
fileChecksums.push_back(fileChecksum);
g_checksum_state->calculatedSize += dataForkStream->size();
macFile.close();
continue;
}
Common::File file;
if (!file.open(filename)) {
warning("Failed to open file: %s", filename.toString().c_str());
continue;
}
Common::Array<Common::String> fileChecksum = {filename.toString()};
// Various checksizes
for (auto size : {0, 5000, 1024 * 1024}) {
Common::String sz = size ? Common::String::format("-%d", size) : "";
fileChecksum.push_back(Common::String::format("md5%s", sz.c_str()));
fileChecksum.push_back(Common::computeStreamMD5AsString(file, size, progressUpdateCallback, this).c_str());
file.seek(0);
}
// Tail checksums with checksize 5000
file.seek(-5000, SEEK_END);
fileChecksum.push_back("md5-t-5000");
fileChecksum.push_back(Common::computeStreamMD5AsString(file, 0, progressUpdateCallback, this).c_str());
fileChecksum.push_back("size");
fileChecksum.push_back(Common::String::format("%llu", (unsigned long long)file.size()));
file.close();
fileChecksums.push_back(fileChecksum);
}
if (currentPath == gamePath) // Enter "checksum complete" state only once the whole root directory has been processed
setState(kChecksumComplete);
return fileChecksums;
}
Common::JSONValue *IntegrityDialog::generateJSONRequest(Common::Path gamePath, Common::String gameid, Common::String engineid, Common::String extra, Common::String platform, Common::String language) {
Common::Array<Common::StringArray> fileChecksums = {};
// Add game path to SearchMan
SearchMan.addDirectory(gamePath.toString(), gamePath, 0, 20);
fileChecksums = generateChecksums(gamePath, fileChecksums, gamePath);
Common::JSONObject requestObject;
requestObject.setVal("gameid", new Common::JSONValue(gameid));
requestObject.setVal("engineid", new Common::JSONValue(engineid));
requestObject.setVal("extra", new Common::JSONValue(extra));
requestObject.setVal("platform", new Common::JSONValue(platform));
requestObject.setVal("language", new Common::JSONValue(language));
Common::JSONArray filesObject;
for (Common::StringArray fileChecksum : fileChecksums) {
Common::JSONObject file;
Common::Path relativePath = Common::Path(fileChecksum[0]).relativeTo(gamePath);
file.setVal("name", new Common::JSONValue(relativePath.toConfig()));
Common::JSONArray checksums;
Common::StringArray checkcodes;
uint i;
for (i = 1; i < fileChecksum.size(); i += 2) {
Common::JSONObject checksum;
checksum.setVal("type", new Common::JSONValue(fileChecksum[i]));
checksum.setVal("checksum", new Common::JSONValue(fileChecksum[i + 1]));
if (fileChecksum[i].hasPrefix("size"))
break;
checksums.push_back(new Common::JSONValue(checksum));
}
file.setVal("checksums", new Common::JSONValue(checksums));
for (; i < fileChecksum.size(); i += 2)
file.setVal(fileChecksum[i], new Common::JSONValue(fileChecksum[i + 1]));
filesObject.push_back(new Common::JSONValue(file));
}
requestObject.setVal("files", new Common::JSONValue(filesObject));
SearchMan.remove(gamePath.toString());
Common::JSONValue *request = new Common::JSONValue(requestObject);
return request;
}
void IntegrityDialog::checksumResponseCallback(const Common::JSONValue *r) {
if (!g_result || !g_checksum_state) {
return;
}
debug(3, "JSON Response: %s", r->stringify().c_str());
IntegrityDialog::parseJSON(r);
if (g_checksum_state->dialog)
g_checksum_state->dialog->sendCommand(kResponseReceived, 0);
}
void IntegrityDialog::errorCallback(const Networking::ErrorResponse &error) {
warning("ERROR %ld: %s", error.httpResponseCode, error.response.c_str());
g_result->errorText = Common::String::format("ERROR %ld: %s", error.httpResponseCode, error.response.c_str());
if (g_checksum_state->dialog)
g_checksum_state->dialog->sendCommand(kResponseCmd, 0);
}
void IntegrityDialog::sendJSON() {
g_result = new ResultFormat();
#if !TESTING
auto conn = new Networking::PostRequest(g_checksum_state->endpoint,
new Common::Callback<IntegrityDialog, const Common::JSONValue *>(this, &IntegrityDialog::checksumResponseCallback),
new Common::Callback<IntegrityDialog, const Networking::ErrorResponse &>(this, &IntegrityDialog::errorCallback));
Common::JSONValue *json = generateJSONRequest(
g_checksum_state->gamePath, g_checksum_state->gameid, g_checksum_state->engineid, g_checksum_state->extra, g_checksum_state->platform, g_checksum_state->language);
conn->setJSONData(json);
conn->setContentType("application/json");
conn->start();
#else
Common::JSONValue *json = generateJSONRequest(
g_checksum_state->gamePath, g_checksum_state->gameid, g_checksum_state->engineid, g_checksum_state->extra, g_checksum_state->platform, g_checksum_state->language);
warning("%s", json->stringify(true).c_str());
#endif
delete json;
}
void IntegrityDialog::parseJSON(const Common::JSONValue *response) {
if (!g_result || !g_checksum_state) {
return;
}
Common::JSONObject responseObject = response->asObject();
int responseError = responseObject.getVal("error")->asIntegerNumber();
Common::U32StringArray messageText;
if (responseError == -1) { // Unknown variant
g_result->error = true;
Common::String fileset = responseObject.getVal("fileset")->asString();
Common::String emailSubj = "Unknown game variant fileset ";
Common::String emailBody = "Fileset %s is a new or unknown fileset, the game details are:\n"
" gameid: %s\n"
" platform: %s\n"
" language: %s\n"
" \n"
"Below please describe the details of your release:\n";
Common::String emailText =
Common::String::format(emailBody.c_str(),
fileset.c_str(), g_checksum_state->gameid.c_str(), g_checksum_state->platform.c_str(),
g_checksum_state->language.c_str());
Common::String emailLink = Common::String::format("mailto:integrity@scummvm.org?subject=%s&body=%s",
Common::percentEncodeString(Common::String("Subject: ") + emailSubj + fileset).c_str(),
Common::percentEncodeString(emailText).c_str());
Common::U32String message = _(
"### Results\n"
"Your set of game files seems to be unknown to us.\n"
"\n"
"If you are sure that this is a valid unknown variant, "
"please send the following e-mail to integrity@scummvm.org");
Common::U32StringTokenizer mtok(message, "\n");
for (auto &line : mtok.split())
messageText.push_back(line);
messageText.push_back(Common::U32String(""));
messageText.push_back(Common::U32String("To: integrity@scummvm.org"));
messageText.push_back(Common::U32String::format("Subject: %s%s", emailSubj.c_str(), fileset.c_str()));
messageText.push_back(Common::U32String(""));
Common::StringTokenizer tok(emailText, "\n");
for (auto &line : tok.split())
messageText.push_back(line);
g_result->messageText = messageText;
g_result->emailLink = emailLink;
return;
} else if (responseError == 2) { // Fileset is empty
messageText.push_back(_("The game doesn't seem to have any files. Are you sure the path is correct?"));
g_result->messageText = messageText;
return;
} else if (responseError == 3) { // Game does not have any metadata
messageText.push_back(_("The game doesn't seem to have any metadata associated with it, so it is unable to be matched. Please fill in the correct metadata for the game."));
g_result->messageText = messageText;
return;
}
Common::Array<int> results = Common::Array<int>(5, 0);
for (Common::JSONValue *fileJSON : responseObject.getVal("files")->asArray()) {
Common::String name = fileJSON->asObject().getVal("name")->asString();
Common::String status = fileJSON->asObject().getVal("status")->asString();
if (status == "ok") {
results[OK]++;
continue;
} else if (status == "missing")
results[MISSING]++;
else if (status == "checksum_mismatch")
results[CHECKSUM_MISMATCH]++;
else if (status == "size_mismatch")
results[SIZE_MISMATCH]++;
else if (status == "unknown_file")
results[UNKNOWN]++;
messageText.push_back(Common::String::format("%s %s\n", name.c_str(), status.c_str()));
}
if (messageText.size() == 0)
messageText.push_back(_("Files all OK"));
else {
g_result->error = true;
Common::String resultSummary = "\n\nTotal: ";
resultSummary += Common::U32String::format("%d OK, %d missing, %d mismatch, %d unknown files",
results[OK], results[MISSING], results[SIZE_MISMATCH] + results[CHECKSUM_MISMATCH], results[UNKNOWN]);
messageText.push_back(resultSummary);
}
g_result->messageText = messageText;
}
} // End of namespace GUI

106
gui/integrity-dialog.h Normal file
View File

@@ -0,0 +1,106 @@
/* 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/>.
*
*/
#ifndef GUI_INTEGRITY_DIALOG_H
#define GUI_INTEGRITY_DIALOG_H
#include "backends/networking/http/postrequest.h"
#include "common/array.h"
#include "common/formats/json.h"
#include "common/str.h"
#include "gui/dialog.h"
#include "gui/widget.h"
#include "gui/widgets/list.h"
namespace GUI {
enum {
OK = 0,
MISSING = 1,
CHECKSUM_MISMATCH = 2,
SIZE_MISMATCH = 3,
UNKNOWN = 4
};
enum ProcessState {
kChecksumStateNone,
kChecksumStateCalculating,
kChecksumComplete,
kResponseReceived
};
class IntegrityDialog : public Dialog, public CommandSender {
StaticTextWidget *_warningText;
ListWidget *_resultsText;
StaticTextWidget *_statusText;
StaticTextWidget *_errorText;
StaticTextWidget *_percentLabel;
StaticTextWidget *_calcSizeLabel;
SliderWidget *_progressBar;
ButtonWidget *_cancelButton;
ButtonWidget *_copyEmailButton;
bool _close;
uint32 _lastEventPoll;
Common::U32String getSizeLabelText();
void refreshWidgets();
public:
IntegrityDialog(Common::String endpoint, Common::String gameConfig);
~IntegrityDialog();
/**
* Updates the progress bar every 500ms
* Includes polling to avoid freezing when processing files
*/
bool progressUpdate(int bytesProcessed);
void sendJSON();
void checksumResponseCallback(const Common::JSONValue *r);
void errorCallback(const Networking::ErrorResponse &error);
void calculateTotalSize(Common::Path gamePath, const Common::HashMap<Common::Path, bool, Common::Path::IgnoreCase_Hash, Common::Path::IgnoreCase_EqualTo> &ignoredSubdirsMap);
Common::Array<Common::StringArray> generateChecksums(Common::Path currentPath, Common::Array<Common::StringArray> &fileChecksums, Common::Path gamePath);
Common::JSONValue *generateJSONRequest(Common::Path gamePath, Common::String gameid, Common::String engineid, Common::String extra, Common::String platform, Common::String language);
void parseJSON(const Common::JSONValue *response);
void open() override;
void close() override;
void handleCommand(CommandSender *sender, uint32 cmd, uint32 data) override;
void handleTickle() override;
void reflowLayout() override;
void setError(Common::U32String &msg);
private:
void setState(ProcessState state);
};
} // End of namespace GUI
#endif // GUI_INTEGRITY_DIALOG_H

1879
gui/launcher.cpp Normal file

File diff suppressed because it is too large Load Diff

293
gui/launcher.h Normal file
View File

@@ -0,0 +1,293 @@
/* 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/>.
*
*/
#ifndef GUI_LAUNCHER_DIALOG_H
#define GUI_LAUNCHER_DIALOG_H
// Disable the grid for platforms that disable fancy themes
#ifdef DISABLE_FANCY_THEMES
#define DISABLE_LAUNCHERDISPLAY_GRID
#endif
#define kSwitchLauncherDialog -2
#include "common/hashmap.h"
#include "gui/dialog.h"
#include "gui/widgets/popup.h"
#include "gui/MetadataParser.h"
#include "engines/game.h"
namespace GUI {
enum LauncherDisplayType {
kLauncherDisplayList = 1,
kLauncherDisplayGrid = 2
};
enum GroupingMethod {
kGroupByNone,
kGroupByFirstLetter,
kGroupByEngine,
kGroupBySeries,
kGroupByCompany,
kGroupByLanguage,
kGroupByPlatform,
kGroupByYear,
};
struct GroupingMode {
/**
* The name of the mode. This is for example what is stored in the config file.
*/
const char *name;
/**
* A human-readable description for the mode.
*/
const char *description;
/**
* A short human-readable description for the mode.
*/
const char *lowresDescription;
/**
* ID of he mode.
*/
GroupingMethod id;
};
class BrowserDialog;
class CommandSender;
class GroupedListWidget;
class ContainerWidget;
class EntryContainerWidget;
class GridWidget;
class ButtonWidget;
class PicButtonWidget;
class GraphicsWidget;
class StaticTextWidget;
class EditTextWidget;
class SaveLoadChooser;
class PopUpWidget;
struct LauncherEntry {
Common::String key;
Common::String engineid;
Common::String gameid;
Common::String description;
Common::String title;
const Common::ConfigManager::Domain *domain;
LauncherEntry(const Common::String &k, const Common::String &e, const Common::String &g,
const Common::String &d, const Common::String &t, const Common::ConfigManager::Domain *v) :
key(k), engineid(e), gameid(g), description(d), title(t), domain(v) {
}
};
class LauncherDialog : public Dialog {
public:
LauncherDialog(const Common::String &dialogName);
~LauncherDialog() override;
void rebuild();
void handleCommand(CommandSender *sender, uint32 cmd, uint32 data) override;
virtual LauncherDisplayType getType() const = 0;
int run();
void handleKeyDown(Common::KeyState state) override;
void handleKeyUp(Common::KeyState state) override;
void handleOtherEvent(const Common::Event &evt) override;
bool doGameDetection(const Common::Path &path);
Common::String getGameConfig(int item, Common::String key);
protected:
EditTextWidget *_searchWidget;
#ifndef DISABLE_FANCY_THEMES
GraphicsWidget *_logo;
GraphicsWidget *_searchPic;
GraphicsWidget *_groupPic;
#endif
StaticTextWidget *_searchDesc;
ButtonWidget *_searchClearButton;
ButtonWidget *_addButton;
Widget *_removeButton;
Widget *_startButton;
ButtonWidget *_loadButton;
Widget *_editButton;
Common::StringArray _domains;
BrowserDialog *_browser;
SaveLoadChooser *_loadDialog;
PopUpWidget *_grpChooserPopup;
StaticTextWidget *_grpChooserDesc;
GroupingMethod _groupBy;
Common::String _title;
Common::String _search;
MetadataParser _metadataParser;
Common::StringArray _domainTitles; // Store game titles for each domain
#ifndef DISABLE_LAUNCHERDISPLAY_GRID
ButtonWidget *_listButton;
ButtonWidget *_gridButton;
/**
* Create two buttons to choose between grid display and list display
* in the launcher.
*/
void addLayoutChooserButtons();
ButtonWidget *createSwitchButton(const Common::String &name, const Common::U32String &desc, const Common::U32String &tooltip, const char *image, uint32 cmd = 0);
#endif // !DISABLE_LAUNCHERDISPLAY_GRID
void reflowLayout() override;
/**
* Fill the list widget with all currently configured targets, and trigger
* a redraw.
*/
virtual void updateListing(int selPos = -1) = 0;
virtual int getItemPos(int item) = 0;
virtual void updateButtons() = 0;
virtual void build();
void clean();
void open() override;
void close() override;
/**
* Handle "Add game..." button.
*/
virtual void addGame();
void massAddGame();
/**
* Handle "Remove game..." button.
*/
void removeGame(int item);
/**
* Remove multiple games and their addons.
*/
void removeGamesWithAddons(const Common::StringArray &domainsToRemove);
/**
* Shared helper for removing games after confirmation.
* Called by subclasses after building their own confirmation message.
*/
void removeGames(const Common::Array<bool> &selectedItems, bool isGrid);
/**
* Handle game removal confirmation with selection validation.
* Checks if at least one item is selected, then shows the removal
* confirmation dialog with a list of games to be removed.
*/
void confirmRemoveGames(const Common::Array<bool> &selectedItems);
/**
* Update selection after game removal.
* Each subclass handles its own UI-specific selection logic.
*/
virtual void updateSelectionAfterRemoval() = 0;
/**
* Check if any items are selected in the given array.
*/
bool hasAnySelection(const Common::Array<bool> &selectedItems) const;
// Get the selected items from the current view (list or grid).
virtual const Common::Array<bool>& getSelectedItems() const = 0;
/**
* Handle "Edit game..." button.
*/
void editGame(int item);
/**
* Handle "Record..." button.
*/
void recordGame(int item);
/**
* Handle "Load..." button.
*/
void loadGame(int item);
Common::Array<LauncherEntry> generateEntries(const Common::ConfigManager::DomainMap &domains, bool skipAddOns);
/**
* Select the target with the given name in the launcher game list.
* Also scrolls the list so that the newly selected item is visible.
*
* @target name of target to select
*/
virtual void selectTarget(const Common::String &target) = 0;
virtual int getSelected() = 0;
private:
Common::HashMap<Common::String, Common::StringMap, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> _engines;
bool checkModifier(int modifier);
};
class LauncherChooser {
protected:
LauncherDialog *_impl;
public:
LauncherChooser();
~LauncherChooser();
int runModal();
void selectLauncher();
};
class LauncherSimple : public LauncherDialog {
public:
LauncherSimple(const Common::String &title);
~LauncherSimple() override;
void handleCommand(CommandSender *sender, uint32 cmd, uint32 data) override;
void handleKeyDown(Common::KeyState state) override;
LauncherDisplayType getType() const override { return kLauncherDisplayList; }
protected:
void updateSelectionAfterRemoval() override;
const Common::Array<bool>& getSelectedItems() const override;
void updateListing(int selPos = -1) override;
int getItemPos(int item) override;
void groupEntries(const Common::Array<LauncherEntry> &metadata);
void updateButtons() override;
void selectTarget(const Common::String &target) override;
int getSelected() override;
void build() override;
private:
GroupedListWidget *_list;
};
} // End of namespace GUI
#endif

307
gui/massadd.cpp Normal file
View File

@@ -0,0 +1,307 @@
/* 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 "engines/metaengine.h"
#include "common/algorithm.h"
#include "common/config-manager.h"
#include "common/debug.h"
#include "common/system.h"
#include "common/taskbar.h"
#include "common/translation.h"
#include "engines/advancedDetector.h"
#include "gui/massadd.h"
#ifndef DISABLE_MASS_ADD
namespace GUI {
/*
TODO:
- Themify this dialog
- Add a ListWidget showing all the games we are going to add, and update it live
- Add a 'busy' mouse cursor (animated?) which indicates to the user that
something is in progress, and show this cursor while we scan
*/
enum {
// Upper bound (im milliseconds) we want to spend in handleTickle.
// Setting this low makes the GUI more responsive but also slows
// down the scanning.
kMaxScanTime = 50
};
enum {
kOkCmd = 'OK ',
kCancelCmd = 'CNCL'
};
MassAddDialog::MassAddDialog(const Common::FSNode &startDir)
: Dialog("MassAdd"),
_dirsScanned(0),
_oldGamesCount(0),
_dirTotal(0),
_okButton(nullptr),
_dirProgressText(nullptr),
_gameProgressText(nullptr) {
Common::U32StringArray l;
// The dir we start our scan at
_scanStack.push(startDir);
// Removed for now... Why would you put a title on mass add dialog called "Mass Add Dialog"?
// new StaticTextWidget(this, "massadddialog_caption", "Mass Add Dialog");
_dirProgressText = new StaticTextWidget(this, "MassAdd.DirProgressText",
_("... progress ..."));
_gameProgressText = new StaticTextWidget(this, "MassAdd.GameProgressText",
_("... progress ..."));
_dirProgressText->setAlign(Graphics::kTextAlignCenter);
_gameProgressText->setAlign(Graphics::kTextAlignCenter);
_list = new MassAddListWidget(this, "MassAdd.GameList");
_list->setEditable(false);
_list->setNumberingMode(kListNumberingOff);
_list->setList(l);
_okButton = new ButtonWidget(this, "MassAdd.Ok", _("OK"), Common::U32String(), kOkCmd, Common::ASCII_RETURN);
_okButton->setEnabled(false);
new ButtonWidget(this, "MassAdd.Cancel", _("Cancel"), Common::U32String(), kCancelCmd, Common::ASCII_ESCAPE);
// Build a map from all configured game paths to the targets using them
const Common::ConfigManager::DomainMap &domains = ConfMan.getGameDomains();
Common::ConfigManager::DomainMap::const_iterator iter;
for (iter = domains.begin(); iter != domains.end(); ++iter) {
Common::Path path(Common::Path::fromConfig(iter->_value.getVal("path")));
// Remove trailing slash, so that "/foo" and "/foo/" match.
// This works around a bug in the POSIX FS code (and others?)
// where paths are not normalized (so FSNodes referring to identical
// FS objects may return different values in path()).
path.removeTrailingSeparators();
if (!path.empty()) {
_pathToTargets[path].push_back(iter->_key);
}
}
}
struct GameTargetLess {
bool operator()(const DetectedGame &x, const DetectedGame &y) const {
return x.preferredTarget.compareToIgnoreCase(y.preferredTarget) < 0;
}
};
struct GameDescLess {
bool operator()(const DetectedGame &x, const DetectedGame &y) const {
return x.description.compareToIgnoreCase(y.description) < 0;
}
};
void MassAddDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {
#if defined(USE_TASKBAR)
// Remove progress bar and count from taskbar
g_system->getTaskbarManager()->setProgressState(Common::TaskbarManager::kTaskbarNoProgress);
g_system->getTaskbarManager()->setCount(0);
#endif
// FIXME: It's a really bad thing that we use two arbitrary constants
if (cmd == kOkCmd) {
// Sort the detected games. This is not strictly necessary, but nice for
// people who want to edit their config file by hand after a mass add.
Common::sort(_games.begin(), _games.end(), GameTargetLess());
// Add all the detected games to the config
for (auto &game : _games) {
// Make sure the game is selected
if (game.isSelected) {
debug(1, " Added gameid '%s', desc '%s'",
game.gameId.c_str(),
game.description.c_str());
game.gameId = EngineMan.createTargetForGame(game);
}
}
// Write everything to disk
ConfMan.flushToDisk();
// And scroll to first detected game
if (!_games.empty()) {
Common::sort(_games.begin(), _games.end(), GameDescLess());
ConfMan.set("temp_selection", _games.front().gameId);
}
close();
} else if (cmd == kCancelCmd) {
// User cancelled, so we don't do anything and just leave.
_games.clear();
close();
} else if (cmd == kListSelectionChangedCmd) {
// Select / unselect game from list
int curretScrollPos = _list->getCurrentScrollPos();
_games[_list->getSelected()].isSelected = !_games[_list->getSelected()].isSelected;
updateGameList();
_list->scrollTo(curretScrollPos);
} else {
Dialog::handleCommand(sender, cmd, data);
}
}
void MassAddDialog::updateGameList() {
// Update list to correctly display selected / unselected games
Common::U32StringArray l;
_list->setList(l);
_list->clearSelectedList();
for (const auto &game : _games) {
Common::U32String displayString = game.isSelected ? Common::String("[x] ") + game.description : Common::String("[\u2000] ") + game.description;
_list->append(displayString);
_list->appendToSelectedList(game.isSelected);
}
}
void MassAddDialog::handleTickle() {
if (_scanStack.empty())
return; // We have finished scanning
uint32 t = g_system->getMillis();
// Perform a breadth-first scan of the filesystem.
while (!_scanStack.empty() && (g_system->getMillis() - t) < kMaxScanTime) {
Common::FSNode dir = _scanStack.pop();
Common::FSList files;
if (!dir.getChildren(files, Common::FSNode::kListAll)) {
continue;
}
// Run the detector on the dir
DetectionResults detectionResults = EngineMan.detectGames(files, (ADGF_WARNING | ADGF_UNSUPPORTED | ADGF_ADDON), true);
if (detectionResults.foundUnknownGames()) {
Common::U32String report = detectionResults.generateUnknownGameReport(false, 80);
g_system->logMessage(LogMessageType::kInfo, report.encode().c_str());
}
// Just add all detected games / game variants. If we get more than one,
// that either means the directory contains multiple games, or the detector
// could not fully determine which game variant it was seeing. In either
// case, let the user choose which entries he wants to keep.
//
// However, we only add games which are not already in the config file.
DetectedGames candidates = detectionResults.listRecognizedGames();
for (const auto &cand : candidates) {
const DetectedGame &result = cand;
Common::Path path = dir.getPath();
path.removeTrailingSeparators();
// Check for existing config entries for this path/engineid/gameid/lang/platform combination
if (_pathToTargets.contains(path)) {
Common::String resultPlatformCode = Common::getPlatformCode(result.platform);
Common::String resultLanguageCode = Common::getLanguageCode(result.language);
bool duplicate = false;
const Common::StringArray &targets = _pathToTargets[path];
for (const auto &target : targets) {
// If the engineid, gameid, platform and language match -> skip it
Common::ConfigManager::Domain *dom = ConfMan.getDomain(target);
assert(dom);
if ((!dom->contains("engineid") || (*dom)["engineid"] == result.engineId) &&
(*dom)["gameid"] == result.gameId &&
dom->getValOrDefault("platform") == resultPlatformCode &&
parseLanguage(dom->getValOrDefault("language")) == parseLanguage(resultLanguageCode)) {
duplicate = true;
break;
}
}
if (duplicate) {
_oldGamesCount++;
continue; // Skip duplicates
}
}
_games.push_back(result);
_list->append(result.description);
}
for (DetectedGame &game : _games) {
game.isSelected = true;
}
updateGameList();
// Recurse into all subdirs
for (const auto &file : files) {
if (file.isDirectory()) {
_scanStack.push(file);
_dirTotal++;
}
}
_dirsScanned++;
#if defined(USE_TASKBAR)
g_system->getTaskbarManager()->setProgressValue(_dirsScanned, _dirTotal);
g_system->getTaskbarManager()->setCount(_games.size());
#endif
}
// Update the dialog
Common::U32String buf;
if (_scanStack.empty()) {
// Enable the OK button
_okButton->setEnabled(true);
buf = _("Scan complete!");
_dirProgressText->setLabel(buf);
buf = Common::U32String::format(_("Discovered %d new games, ignored %d previously added games."), _games.size(), _oldGamesCount);
_gameProgressText->setLabel(buf);
} else {
buf = Common::U32String::format(_("Scanned %d directories ..."), _dirsScanned);
_dirProgressText->setLabel(buf);
buf = Common::U32String::format(_("Discovered %d new games, ignored %d previously added games ..."), _games.size(), _oldGamesCount);
_gameProgressText->setLabel(buf);
}
if (_games.size() > 0) {
_list->scrollToEnd();
}
drawDialog(kDrawLayerForeground);
}
} // End of namespace GUI
#endif // DISABLE_MASS_ADD

99
gui/massadd.h Normal file
View File

@@ -0,0 +1,99 @@
/* 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/>.
*
*/
#ifndef MASSADD_DIALOG_H
#define MASSADD_DIALOG_H
#include "gui/dialog.h"
#include "gui/widgets/list.h"
#include "common/fs.h"
#include "common/hashmap.h"
#include "common/stack.h"
#include "common/str.h"
namespace GUI {
class StaticTextWidget;
class MassAddListWidget;
class MassAddDialog : public Dialog {
public:
MassAddDialog(const Common::FSNode &startDir);
//void open();
void handleCommand(CommandSender *sender, uint32 cmd, uint32 data) override;
void handleTickle() override;
Common::String getFirstAddedTarget() const {
if (!_games.empty())
return _games.front().gameId;
return Common::String();
}
private:
Common::Stack<Common::FSNode> _scanStack;
DetectedGames _games;
void updateGameList();
/**
* Map each path occurring in the config file to the target(s) using that path.
* Used to detect whether a potential new target is already present in the
* config manager.
*/
Common::HashMap<Common::Path, Common::StringArray,
Common::Path::IgnoreCase_Hash, Common::Path::IgnoreCase_EqualTo> _pathToTargets;
int _dirsScanned;
int _oldGamesCount;
int _dirTotal;
Widget *_okButton;
StaticTextWidget *_dirProgressText;
StaticTextWidget *_gameProgressText;
MassAddListWidget *_list;
};
class MassAddListWidget : public ListWidget {
public:
MassAddListWidget(Dialog *boss, const Common::String &name)
: ListWidget(boss, name) { }
void appendToSelectedList(bool selected) { _listSelected.push_back(selected); }
void clearSelectedList() { _listSelected.clear(); }
protected:
ThemeEngine::WidgetStateInfo getItemState(int item) const override {
// Display selected/unselected games in mass detection as enabled/disabled items.
if (item < (signed int)_listSelected.size() && _listSelected[item]) {
return ThemeEngine::kStateEnabled;
} else {
return ThemeEngine::kStateDisabled;
}
}
Common::Array<bool> _listSelected;
};
} // End of namespace GUI
#endif

286
gui/message.cpp Normal file
View File

@@ -0,0 +1,286 @@
/* 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/str.h"
#include "common/system.h"
#include "common/translation.h"
#include "gui/message.h"
#include "gui/gui-manager.h"
#include "gui/ThemeEval.h"
#include "gui/widget.h"
namespace GUI {
enum {
kDefaultCmd = 'DFLT',
kAltCmd = 'ALTC'
};
// TODO: The default button should be visibly distinct from the alternate button
void MessageDialog::init(const Common::U32String &message,
const Common::U32String &defaultButton,
const Common::U32StringArray &altButtons,
Graphics::TextAlign alignment,
const char *url,
const Common::U32String &extraMessage) {
// message widgets will be created in reflowLayout as needed
_message = message;
_alignment = alignment;
_url = url;
_extraMessage = nullptr;
// Only use bogus sizes, we do the calculation in reflowLayout
if (!defaultButton.empty()) {
// Confirm dialog
_buttons.push_back(new ButtonWidget(this, 0, 0, 0, 0, defaultButton, Common::U32String(), kDefaultCmd, Common::ASCII_RETURN));
}
int buttonHotKey = altButtons.size() == 1 ? Common::ASCII_ESCAPE : 0;
for (size_t i = 0, total = altButtons.size(); i < total; ++i) {
_buttons.push_back(new ButtonWidget(this, 0, 0, 0, 0, altButtons[i], Common::U32String(), kAltCmd + i, buttonHotKey));
buttonHotKey = 0;
}
if (!extraMessage.empty()) {
_extraMessage = new StaticTextWidget(this, 0, 0, 0, 0, extraMessage, Graphics::kTextAlignLeft);
}
}
void MessageDialog::reflowLayout() {
const int horizontalMargin = 10;
const int buttonSpacing = 10;
int16 screenW, screenH;
const Common::Rect safeArea = g_system->getSafeOverlayArea(&screenW, &screenH);
int buttonWidth = g_gui.xmlEval()->getVar("Globals.Button.Width", 0);
int buttonHeight = g_gui.xmlEval()->getVar("Globals.Button.Height", 0);
// First, determine the size the dialog needs. For this we have to break
// down the string into lines, and taking the maximum of their widths.
// Using this, and accounting for the space the button(s) need, we can set
// the real size of the dialog
Common::Array<Common::U32String> lines;
size_t lineCount;
int maxlineWidth = g_gui.getFont().wordWrapText(_message, safeArea.width() - 2 * horizontalMargin - 20, lines);
const size_t buttonCount = _buttons.size();
const int buttonsTotalWidth = buttonCount * buttonWidth + (buttonCount - 1) * buttonSpacing;
// Calculate the desired dialog size
_w = MAX(maxlineWidth, buttonsTotalWidth) + 2 * horizontalMargin;
lineCount = lines.size();
_h = 16;
if (buttonCount)
_h += buttonHeight + 8;
if (_extraMessage)
_h += kLineHeight;
// Limit the number of lines so that the dialog still fits on the screen.
lineCount = MIN(lineCount, (size_t)((safeArea.height() - 20 - _h) / kLineHeight));
_h += lineCount * kLineHeight;
// Center the dialog
_x = (screenW - _w) / 2;
_y = (screenH - _h) / 2;
safeArea.constrain(_x, _y, _w, _h);
int curY = 10;
// Each line is represented by one static text item.
// Update existing lines
size_t toUpdateLines = MIN<size_t>(lineCount, _lines.size());
for (size_t i = 0; i < toUpdateLines; i++) {
_lines[i]->setPos(horizontalMargin, curY);
_lines[i]->setSize(maxlineWidth, kLineHeight);
_lines[i]->setLabel(lines[i]);
curY += kLineHeight;
}
// Create missing lines
for (size_t i = toUpdateLines; i < lineCount; i++) {
_lines.push_back(new StaticTextWidget(this, horizontalMargin, curY, maxlineWidth, kLineHeight, lines[i], _alignment));
curY += kLineHeight;
}
// Cleanup old useless lines
for (size_t i = lineCount, total = _lines.size(); i < total; i++) {
this->removeWidget(_lines[i]);
delete _lines[i];
}
_lines.resize(lineCount);
if (buttonCount) {
curY += 8;
int buttonPos = (_w - buttonsTotalWidth) / 2;
for (size_t i = 0; i < buttonCount; ++i) {
_buttons[i]->setPos(buttonPos, curY);
_buttons[i]->setSize(buttonWidth, buttonHeight);
buttonPos += buttonWidth + buttonSpacing;
}
curY += buttonHeight;
}
curY += 6;
if (_extraMessage) {
_extraMessage->setPos(10, curY);
_extraMessage->setSize(maxlineWidth, kLineHeight);
}
}
MessageDialog::MessageDialog(const Common::U32String &message)
: MessageDialog(message, _("OK")) {
}
MessageDialog::MessageDialog(const Common::String &message)
: MessageDialog(Common::U32String(message), _("OK")) {
}
MessageDialog::MessageDialog(const Common::U32String &message,
const Common::U32String &defaultButton,
const Common::U32String &altButton,
Graphics::TextAlign alignment,
const char *url,
const Common::U32String &extraMessage)
: Dialog(30, 20, 260, 124) {
init(message, defaultButton,
altButton.empty() ? Common::U32StringArray() : Common::U32StringArray(1, altButton),
alignment, url, extraMessage);
}
MessageDialog::MessageDialog(const Common::String &message,
const Common::String &defaultButton,
const Common::String &altButton,
Graphics::TextAlign alignment,
const char *url)
: Dialog(30, 20, 260, 124) {
init(Common::U32String(message), Common::U32String(defaultButton),
altButton.empty() ? Common::U32StringArray() : Common::U32StringArray(1, Common::U32String(altButton)),
alignment, url, Common::U32String());
}
MessageDialog::MessageDialog(const Common::U32String &message,
const Common::U32String &defaultButton,
const Common::U32StringArray &altButtons,
Graphics::TextAlign alignment)
: Dialog(30, 20, 260, 124) {
init(message, defaultButton, altButtons, alignment, nullptr, Common::U32String());
}
void MessageDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {
if (cmd == kDefaultCmd) {
setResult(kMessageOK);
close();
return;
}
if (cmd >= kAltCmd) {
if (_url) {
if (g_system->hasFeature(OSystem::kFeatureOpenUrl))
g_system->openUrl(_url);
setResult(kMessageOK);
} else {
setResult(kMessageAlt + cmd - kAltCmd);
}
close();
} else {
Dialog::handleCommand(sender, cmd, data);
}
}
TimedMessageDialog::TimedMessageDialog(const Common::U32String &message, uint32 duration)
: MessageDialog(message, Common::U32String(), Common::U32String()) {
_timer = g_system->getMillis() + duration;
}
void TimedMessageDialog::handleTickle() {
MessageDialog::handleTickle();
if (g_system->getMillis() > _timer)
close();
}
CountdownMessageDialog::CountdownMessageDialog(const Common::U32String &message,
uint32 duration)
: CountdownMessageDialog(message, duration, _("OK")) {
}
CountdownMessageDialog::CountdownMessageDialog(const Common::U32String &message,
uint32 duration,
const Common::U32String &defaultButton,
const Common::U32String &altButton,
Graphics::TextAlign alignment,
const Common::U32String &countdownMessage)
: MessageDialog(message, defaultButton, altButton, alignment, nullptr, countdownMessage) {
_startTime = g_system->getMillis();
_timer = _startTime + duration;
_countdownMessage = countdownMessage;
updateCountdown();
}
void CountdownMessageDialog::handleTickle() {
updateCountdown();
MessageDialog::handleTickle();
if (g_system->getMillis() > _timer) {
setResult(kMessageAlt);
close();
}
}
void CountdownMessageDialog::updateCountdown() {
uint32 secs = (_timer - g_system->getMillis()) / 1000;
Common::U32String msg = Common::U32String::format(_countdownMessage, secs);
if (msg != _extraMessage->getLabel()) {
_extraMessage->setLabel(msg);
}
}
MessageDialogWithURL::MessageDialogWithURL(const Common::U32String &message, const char *url)
: MessageDialogWithURL(message, url, _("OK")) {
}
MessageDialogWithURL::MessageDialogWithURL(const Common::String &message, const char *url)
: MessageDialogWithURL(Common::U32String(message), url, _("OK")) {
}
MessageDialogWithURL::MessageDialogWithURL(const Common::U32String &message, const char *url, const Common::U32String &defaultButton, Graphics::TextAlign alignment)
: MessageDialog(message, defaultButton, _("Open URL"), alignment, url) {
}
MessageDialogWithURL::MessageDialogWithURL(const Common::String &message, const char *url, const char *defaultButton, Graphics::TextAlign alignment)
: MessageDialog(Common::U32String(message), Common::U32String(defaultButton), _("Open URL"), alignment, url) {
}
} // End of namespace GUI

136
gui/message.h Normal file
View File

@@ -0,0 +1,136 @@
/* 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/>.
*
*/
#ifndef MESSAGE_DIALOG_H
#define MESSAGE_DIALOG_H
#include "gui/dialog.h"
#include "common/str.h"
#include "common/str-array.h"
namespace GUI {
class CommandSender;
class StaticTextWidget;
class ButtonWidget;
enum {
kMessageOK = 0,
kMessageAlt = 1
};
/**
* Simple message dialog ("alert box"): presents a text message in a dialog with up to two buttons.
*/
class MessageDialog : public Dialog {
public:
MessageDialog(const Common::U32String &message);
MessageDialog(const Common::String &message);
MessageDialog(const Common::U32String &message,
const Common::U32String &defaultButton,
const Common::U32String &altButton = Common::U32String(),
Graphics::TextAlign alignment = Graphics::kTextAlignCenter,
const char *url = nullptr,
const Common::U32String &extraMessage = Common::U32String());
MessageDialog(const Common::String &message,
const Common::String &defaultButton,
const Common::String &altButton = Common::String(),
Graphics::TextAlign alignment = Graphics::kTextAlignCenter,
const char *url = nullptr);
MessageDialog(const Common::U32String &message,
const Common::U32String &defaultButton,
const Common::U32StringArray &altButtons,
Graphics::TextAlign alignment = Graphics::kTextAlignCenter);
void handleCommand(CommandSender *sender, uint32 cmd, uint32 data) override;
void reflowLayout() override;
private:
const char *_url;
void init(const Common::U32String &message,
const Common::U32String &defaultButton,
const Common::U32StringArray &altButtons,
Graphics::TextAlign alignment,
const char *url,
const Common::U32String &extraMessage);
protected:
Common::U32String _message;
Graphics::TextAlign _alignment;
Common::Array<StaticTextWidget *> _lines;
Common::Array<ButtonWidget *> _buttons;
StaticTextWidget *_extraMessage;
};
/**
* Timed message dialog: displays a message to the user for brief time period.
*/
class TimedMessageDialog : public MessageDialog {
public:
TimedMessageDialog(const Common::U32String &message, uint32 duration);
void handleTickle() override;
protected:
uint32 _timer;
};
/**
* Timed message dialog: displays a message with a countdown.
*/
class CountdownMessageDialog : public MessageDialog {
public:
CountdownMessageDialog(const Common::U32String &message,
uint32 duration);
CountdownMessageDialog(const Common::U32String &message,
uint32 duration,
const Common::U32String &defaultButton,
const Common::U32String &altButton = Common::U32String(),
Graphics::TextAlign alignment = Graphics::kTextAlignCenter,
const Common::U32String &countdownMessage = Common::U32String(""));
void handleTickle() override;
protected:
void updateCountdown();
uint32 _timer;
uint32 _startTime;
Common::U32String _countdownMessage;
};
/**
* Message dialog with button to open a specified URL
*/
class MessageDialogWithURL : public MessageDialog {
public:
MessageDialogWithURL(const Common::U32String &message, const char *url);
MessageDialogWithURL(const Common::String &message, const char *url);
MessageDialogWithURL(const Common::U32String &message, const char *url, const Common::U32String &defaultButton, Graphics::TextAlign alignment = Graphics::kTextAlignCenter);
MessageDialogWithURL(const Common::String &message, const char *url, const char *defaultButton, Graphics::TextAlign alignment = Graphics::kTextAlignCenter);
};
} // End of namespace GUI
#endif

88
gui/module.mk Normal file
View File

@@ -0,0 +1,88 @@
MODULE := gui
MODULE_OBJS := \
about.o \
browser.o \
chooser.o \
console.o \
debugger.o \
dialog.o \
dump-all-dialogs.o \
editgamedialog.o \
error.o \
EventRecorder.o \
filebrowser-dialog.o \
gui-manager.o \
helpdialog.o \
imagealbum-dialog.o \
launcher.o \
massadd.o \
message.o \
MetadataParser.o \
object.o \
options.o \
predictivedialog.o \
saveload.o \
saveload-dialog.o \
shaderbrowser-dialog.o \
textviewer.o \
themebrowser.o \
ThemeEngine.o \
ThemeEval.o \
ThemeLayout.o \
ThemeParser.o \
Tooltip.o \
unknown-game-dialog.o \
widget.o \
animation/Animation.o \
animation/RepeatAnimationWrapper.o \
animation/SequenceAnimationComposite.o \
widgets/editable.o \
widgets/edittext.o \
widgets/grid.o \
widgets/groupedlist.o \
widgets/list.o \
widgets/popup.o \
widgets/richtext.o \
widgets/scrollbar.o \
widgets/scrollcontainer.o \
widgets/tab.o
ifdef USE_CLOUD
MODULE_OBJS += \
cloudconnectionwizard.o \
downloaddialog.o \
remotebrowser.o
endif
ifdef USE_HTTP
MODULE_OBJS += \
downloadpacksdialog.o \
integrity-dialog.o
endif
ifdef USE_DLC
MODULE_OBJS += \
dlcsdialog.o \
downloaddlcsdialog.o
endif
ifdef ENABLE_EVENTRECORDER
MODULE_OBJS += \
editrecorddialog.o \
onscreendialog.o \
recorderdialog.o
endif
ifdef USE_FLUIDSYNTH
MODULE_OBJS += \
fluidsynth-dialog.o
endif
ifdef USE_UPDATES
MODULE_OBJS += \
updates-dialog.o
endif
# Include common rules
include $(srcdir)/rules.mk

112
gui/object.cpp Normal file
View File

@@ -0,0 +1,112 @@
/* 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/textconsole.h"
#include "gui/object.h"
#include "gui/widget.h"
#include "gui/gui-manager.h"
#include "gui/ThemeEval.h"
namespace GUI {
#define SCALEVALUE(val) (val > 0 ? val * g_gui.getScaleFactor() : val)
GuiObject::GuiObject(int x, int y, int w, int h, bool scale) : _useRTL(true), _firstWidget(nullptr) {
if (scale) {
_x = SCALEVALUE(x);
_y = SCALEVALUE(y);
_w = SCALEVALUE(w);
_h = SCALEVALUE(h);
} else {
_x = x;
_y = y;
_w = w;
_h = h;
}
}
GuiObject::GuiObject(const Common::String &name)
: _x(-1000), _y(-1000), _w(0), _h(0), _useRTL(true), _name(name), _firstWidget(nullptr) {
}
GuiObject::~GuiObject() {
delete _firstWidget;
_firstWidget = nullptr;
}
void GuiObject::resize(int x, int y, int w, int h, bool scale) {
if (scale) {
_x = SCALEVALUE(x);
_y = SCALEVALUE(y);
_w = SCALEVALUE(w);
_h = SCALEVALUE(h);
} else {
_x = x; _y = y;
_w = w; _h = h;
}
}
Widget *GuiObject::addChild(Widget *newChild) {
Widget *oldFirstWidget = _firstWidget;
_firstWidget = newChild;
return oldFirstWidget;
}
void GuiObject::reflowLayout() {
if (!_name.empty()) {
int16 w, h;
bool useRTL = true;
if (!g_gui.xmlEval()->getWidgetData(_name, _x, _y, w, h, useRTL) || w == -1 || h == -1) {
error("Unable to load widget position for '%s'. Please check your theme files for theme '%s'", _name.c_str(), g_gui.theme()->getThemeId().c_str());
}
_w = w;
_h = h;
_useRTL = useRTL;
}
}
void GuiObject::removeWidget(Widget *del) {
if (del == _firstWidget) {
Widget *del_next = del->next();
del->setNext(nullptr);
_firstWidget = del_next;
return;
}
Widget *w = _firstWidget;
while (w) {
if (w->next() == del) {
Widget *del_next = del->next();
del->setNext(nullptr);
w->setNext(del_next);
return;
}
w = w->next();
}
}
Common::Rect GuiObject::getClipRect() const {
return Common::Rect(getAbsX(), getAbsY(), getAbsX() + getWidth(), getAbsY() + getHeight());
}
} // End of namespace GUI

114
gui/object.h Normal file
View File

@@ -0,0 +1,114 @@
/* 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/>.
*
*/
#ifndef GUI_OBJECT_H
#define GUI_OBJECT_H
#include "common/scummsys.h"
#include "common/str.h"
#include "common/rect.h"
namespace GUI {
class CommandSender;
class CommandReceiver {
friend class CommandSender;
protected:
virtual ~CommandReceiver() {}
virtual void handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {}
};
class CommandSender {
// TODO - allow for multiple targets, i.e. store targets in a list
// and add methods addTarget/removeTarget.
protected:
CommandReceiver *_target;
public:
CommandSender(CommandReceiver *target) : _target(target) {}
virtual ~CommandSender() {}
void setTarget(CommandReceiver *target) { _target = target; }
CommandReceiver *getTarget() const { return _target; }
virtual void sendCommand(uint32 cmd, uint32 data) {
if (_target && cmd)
_target->handleCommand(this, cmd, data);
}
};
class Widget;
class GuiObject : public CommandReceiver {
friend class Widget;
protected:
Common::Rect _textDrawableArea;
int16 _x, _y;
uint16 _w, _h;
bool _useRTL;
const Common::String _name;
Widget *_firstWidget;
public:
GuiObject(int x, int y, int w, int h, bool scale = false);
GuiObject(const Common::String &name);
~GuiObject() override;
virtual void setTextDrawableArea(const Common::Rect &r) { _textDrawableArea = r; }
virtual void resize(int x, int y, int w, int h, bool scale = true);
virtual Widget *addChild(Widget *newChild);
virtual int16 getRelX() const { return _x; }
virtual int16 getRelY() const { return _y; }
virtual int16 getAbsX() const { return _x; }
virtual int16 getAbsY() const { return _y; }
virtual int16 getChildX() const { return getAbsX(); }
virtual int16 getChildY() const { return getAbsY(); }
virtual uint16 getWidth() const { return _w; }
virtual uint16 getHeight() const { return _h; }
virtual bool isVisible() const = 0;
virtual void reflowLayout();
virtual void removeWidget(Widget *widget);
virtual bool isPointIn(int x, int y) {
return (x >= _x && x < (_x + _w) && (y >= _y) && (y < _y + _h));
}
/**
* Returns the clipping rect to be used when drawing the children widgets of this object
*/
virtual Common::Rect getClipRect() const;
virtual void handleMouseWheel(int x, int y, int direction) {};
virtual void handleTooltipUpdate(int x, int y) {};
protected:
virtual void releaseFocus() = 0;
};
} // End of namespace GUI
#endif

229
gui/onscreendialog.cpp Normal file
View File

@@ -0,0 +1,229 @@
/* 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/system.h"
#include "gui/gui-manager.h"
#include "gui/EventRecorder.h"
#include "common/events.h"
#include "common/rect.h"
#include "common/translation.h"
#include "graphics/cursorman.h"
#include "gui/editrecorddialog.h"
#include "gui/ThemeEval.h"
#include "gui/onscreendialog.h"
namespace GUI {
bool OnScreenDialog::isVisible() const {
return true;
}
enum {
kStopCmd = 'STOP',
kEditCmd = 'EDIT',
kSwitchModeCmd = 'MODE',
kFastModeCmd = 'FAST'
};
void OnScreenDialog::reflowLayout() {
Dialog::reflowLayout();
_x = _y = 0;
}
void OnScreenDialog::releaseFocus() {
}
OnScreenDialog::OnScreenDialog(bool isRecord) : Dialog("OnScreenDialog") {
#ifndef DISABLE_FANCY_THEMES
if (g_gui.xmlEval()->getVar("Globals.OnScreenDialog.ShowPics") == 1 && g_gui.theme()->supportsImages()) {
GUI::PicButtonWidget *button;
button = new PicButtonWidget(this, "OnScreenDialog.StopButton", Common::U32String(), kStopCmd, 0);
if (!g_gui.useLowResGUI())
button->setGfxFromTheme(ThemeEngine::kImageStopButton);
else
button->setGfxFromTheme(ThemeEngine::kImageStopSmallButton);
if (isRecord) {
button = new PicButtonWidget(this, "OnScreenDialog.EditButton", Common::U32String(), kEditCmd, 0);
if (!g_gui.useLowResGUI())
button->setGfxFromTheme(ThemeEngine::kImageEditButton);
else
button->setGfxFromTheme(ThemeEngine::kImageEditSmallButton);
} else {
button = new PicButtonWidget(this, "OnScreenDialog.SwitchModeButton", Common::U32String(), kSwitchModeCmd, 0);
if (!g_gui.useLowResGUI())
button->setGfxFromTheme(ThemeEngine::kImageSwitchModeButton);
else
button->setGfxFromTheme(ThemeEngine::kImageSwitchModeSmallButton);
button = new PicButtonWidget(this, "OnScreenDialog.FastReplayButton", Common::U32String(), kFastModeCmd, 0);
if (!g_gui.useLowResGUI())
button->setGfxFromTheme(ThemeEngine::kImageFastReplayButton);
else
button->setGfxFromTheme(ThemeEngine::kImageFastReplaySmallButton);
}
} else
#endif
{
if (!g_gui.useLowResGUI())
new ButtonWidget(this, "OnScreenDialog.StopButton", Common::U32String("[ ]"), _("Stop"), kStopCmd);
else
new ButtonWidget(this, "OnScreenDialog.StopButton", Common::U32String("[]"), _("Stop"), kStopCmd);
if (isRecord) {
new ButtonWidget(this, "OnScreenDialog.EditButton", Common::U32String("E"), _("Edit record description"), kEditCmd);
} else {
new ButtonWidget(this, "OnScreenDialog.SwitchModeButton", Common::U32String("G"), _("Switch to Game"), kSwitchModeCmd);
new ButtonWidget(this, "OnScreenDialog.FastReplayButton", Common::U32String(">>"), _("Fast replay"), kFastModeCmd);
}
}
_text = new GUI::StaticTextWidget(this, "OnScreenDialog.TimeLabel", Common::U32String("00:00:00"));
_enableDrag = false;
_mouseOver = false;
_editDlgShown = false;
_lastTime = 0;
_dlg = nullptr;
}
void OnScreenDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {
Common::Event eventReturnToLauncher;
switch (cmd) {
case kStopCmd:
eventReturnToLauncher.type = Common::EVENT_RETURN_TO_LAUNCHER;
g_system->getEventManager()->pushEvent(eventReturnToLauncher);
close();
break;
case kEditCmd:
_dlg = new EditRecordDialog(g_eventRec.getAuthor(), g_eventRec.getName(), g_eventRec.getNotes());
CursorMan.lock(false);
g_eventRec.setRedraw(false);
g_system->showOverlay();
_editDlgShown = true;
_dlg->runModal();
_editDlgShown = false;
g_system->hideOverlay();
g_eventRec.setRedraw(true);
CursorMan.lock(true);
g_eventRec.setAuthor(((EditRecordDialog *)_dlg)->getAuthor());
g_eventRec.setName(((EditRecordDialog *)_dlg)->getName());
g_eventRec.setNotes(((EditRecordDialog *)_dlg)->getNotes());
delete _dlg;
break;
case kSwitchModeCmd:
if (g_eventRec.switchMode()) {
close();
}
break;
case kFastModeCmd:
g_eventRec.switchFastMode();
break;
default:
break;
}
}
void OnScreenDialog::setReplayedTime(uint32 newTime) {
if (newTime - _lastTime > 1000) {
uint32 seconds = newTime / 1000;
_text->setLabel(Common::String::format("%.2d:%.2d:%.2d", seconds / 3600 % 24, seconds / 60 % 60, seconds % 60));
_lastTime = newTime;
}
}
OnScreenDialog::~OnScreenDialog() {
}
void OnScreenDialog::handleMouseMoved(int x, int y, int button) {
if (_enableDrag) {
_x = _x + x - _dragPoint.x;
_y = _y + y - _dragPoint.y;
}
Dialog::handleMouseMoved(x, y, button);
if (isMouseOver(x, y)) {
if (_mouseOver == false) {
g_gui.theme()->showCursor();
CursorMan.lock(true);
}
_mouseOver = true;
} else {
if (_mouseOver == true) {
CursorMan.lock(false);
g_gui.theme()->hideCursor();
}
_mouseOver = false;
}
}
void OnScreenDialog::handleMouseDown(int x, int y, int button, int clickCount) {
if (isMouseOver(x, y)) {
_dragPoint.x = x;
_dragPoint.y = y;
_enableDrag = true;
}
Dialog::handleMouseDown(x, y, button, clickCount);
}
void OnScreenDialog::handleMouseUp(int x, int y, int button, int clickCount) {
if (isMouseOver(x, y)) {
}
_enableDrag = false;
Dialog::handleMouseUp(x, y, button, clickCount);
}
bool OnScreenDialog::isMouseOver(int x, int y) {
return (x >= 0 && x < _w && y >= 0 && y < _h);
}
bool OnScreenDialog::isMouseOver() {
return _mouseOver;
}
void OnScreenDialog::close() {
CursorMan.lock(false);
Dialog::close();
}
Dialog *OnScreenDialog::getActiveDlg() {
if (_editDlgShown) {
return _dlg;
} else {
return this;
}
}
bool OnScreenDialog::isEditDlgVisible() {
return _editDlgShown;
}
}

65
gui/onscreendialog.h Normal file
View File

@@ -0,0 +1,65 @@
/* 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/>.
*
*/
#ifndef GUI_ONSCREENDIALOG_H
#define GUI_ONSCREENDIALOG_H
#include "gui/dialog.h"
#include "gui/widget.h"
namespace GUI {
class OnScreenDialog : public Dialog {
private:
uint32 _lastTime;
bool _enableDrag;
bool _mouseOver;
bool _editDlgShown;
Common::Point _dragPoint;
GUI::StaticTextWidget *_text;
Dialog *_dlg;
bool isMouseOver(int x, int y);
public:
OnScreenDialog(bool recordingMode);
~OnScreenDialog() override;
void close() override;
bool isVisible() const override;
void reflowLayout() override;
void setReplayedTime(uint32 newTime);
void handleMouseMoved(int x, int y, int button) override;
void handleMouseDown(int x, int y, int button, int clickCount) override;
void handleMouseUp(int x, int y, int button, int clickCount) override;
void handleCommand(CommandSender *sender, uint32 cmd, uint32 data) override;
bool isMouseOver();
bool isEditDlgVisible();
Dialog *getActiveDlg();
protected:
void releaseFocus() override;
};
} // End of namespace GUI
#endif

3875
gui/options.cpp Normal file

File diff suppressed because it is too large Load Diff

414
gui/options.h Normal file
View File

@@ -0,0 +1,414 @@
/* 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/>.
*
*/
#ifndef OPTIONS_DIALOG_H
#define OPTIONS_DIALOG_H
#include "engines/metaengine.h"
#include "gui/dialog.h"
#include "common/str.h"
#include "audio/mididrv.h"
#ifdef USE_FLUIDSYNTH
#include "gui/fluidsynth-dialog.h"
#endif
#ifdef USE_CLOUD
#include "backends/cloud/storage.h"
#endif
namespace Common {
class RemapWidget;
}
namespace GUI {
class LauncherDialog;
class CheckboxWidget;
class EditTextWidget;
class PopUpWidget;
class SliderWidget;
class StaticTextWidget;
class TabWidget;
class ButtonWidget;
class CommandSender;
class GuiObject;
class RadiobuttonGroup;
class RadiobuttonWidget;
class PathWidget;
class ScrollContainerWidget;
class OptionsContainerWidget;
class OptionsDialog : public Dialog {
public:
OptionsDialog(const Common::String &domain, int x, int y, int w, int h);
OptionsDialog(const Common::String &domain, const Common::String &name);
~OptionsDialog() override;
void init();
void open() override;
virtual void apply();
void close() override;
void handleCommand(CommandSender *sender, uint32 cmd, uint32 data) override;
void handleTickle() override;
void handleOtherEvent(const Common::Event &event) override;
const Common::String& getDomain() const { return _domain; }
void reflowLayout() override;
protected:
/** Config domain this dialog is used to edit. */
Common::String _domain;
ButtonWidget *_soundFontButton;
PathWidget *_soundFont;
ButtonWidget *_soundFontClearButton;
virtual void build();
virtual void clean();
void rebuild();
bool testGraphicsSettings();
void addControlControls(GuiObject *boss, const Common::String &prefix);
void addKeyMapperControls(GuiObject *boss, const Common::String &prefix, const Common::Array<Common::Keymap *> &keymaps, const Common::String &domain);
void addAchievementsControls(GuiObject *boss, const Common::String &prefix);
void addStatisticsControls(GuiObject *boss, const Common::String &prefix);
void addGraphicControls(GuiObject *boss, const Common::String &prefix);
void addAudioControls(GuiObject *boss, const Common::String &prefix);
void addMIDIControls(GuiObject *boss, const Common::String &prefix);
void addMT32Controls(GuiObject *boss, const Common::String &prefix);
void addVolumeControls(GuiObject *boss, const Common::String &prefix);
// The default value is the launcher's non-scaled talkspeed value. When SCUMM uses the widget,
// it uses its own scale
void addSubtitleControls(GuiObject *boss, const Common::String &prefix, int maxSliderVal = 255);
void setGraphicSettingsState(bool enabled);
void setAudioSettingsState(bool enabled);
void setMIDISettingsState(bool enabled);
void setMT32SettingsState(bool enabled);
void setVolumeSettingsState(bool enabled);
void setSubtitleSettingsState(bool enabled);
void enableShaderControls(bool enabled);
virtual void setupGraphicsTab();
void updateScaleFactors(uint32 tag);
bool loadMusicDeviceSetting(PopUpWidget *popup, Common::String setting, MusicType preferredType = MT_AUTO);
void saveMusicDeviceSetting(PopUpWidget *popup, Common::String setting);
TabWidget *_tabWidget;
int _graphicsTabId;
int _midiTabId;
ScrollContainerWidget *_pathsContainer;
PathWidget *_shader;
ButtonWidget *_shaderClearButton;
ButtonWidget *_updateShadersButton = nullptr;
private:
//
// Control controls
//
bool _enableControlSettings;
CheckboxWidget *_touchpadCheckbox;
StaticTextWidget *_kbdMouseSpeedDesc;
SliderWidget *_kbdMouseSpeedSlider;
StaticTextWidget *_kbdMouseSpeedLabel;
StaticTextWidget *_joystickDeadzoneDesc;
SliderWidget *_joystickDeadzoneSlider;
StaticTextWidget *_joystickDeadzoneLabel;
//
// KeyMapper controls
//
Common::RemapWidget *_keymapperWidget;
//
// Graphics controls
//
bool _enableGraphicSettings;
StaticTextWidget *_gfxPopUpDesc;
PopUpWidget *_gfxPopUp;
StaticTextWidget *_stretchPopUpDesc;
PopUpWidget *_stretchPopUp;
StaticTextWidget *_scalerPopUpDesc;
PopUpWidget *_scalerPopUp, *_scaleFactorPopUp;
ButtonWidget *_shaderButton;
CheckboxWidget *_fullscreenCheckbox;
CheckboxWidget *_filteringCheckbox;
CheckboxWidget *_aspectCheckbox;
CheckboxWidget *_vsyncCheckbox;
StaticTextWidget *_rendererTypePopUpDesc;
PopUpWidget *_rendererTypePopUp;
StaticTextWidget *_antiAliasPopUpDesc;
PopUpWidget *_antiAliasPopUp;
StaticTextWidget *_renderModePopUpDesc;
PopUpWidget *_renderModePopUp;
StaticTextWidget *_rotationModePopUpDesc;
PopUpWidget *_rotationModePopUp;
//
// Audio controls
//
bool _enableAudioSettings;
StaticTextWidget *_midiPopUpDesc;
PopUpWidget *_midiPopUp;
StaticTextWidget *_oplPopUpDesc;
PopUpWidget *_oplPopUp;
StaticTextWidget *_mt32DevicePopUpDesc;
PopUpWidget *_mt32DevicePopUp;
StaticTextWidget *_gmDevicePopUpDesc;
PopUpWidget *_gmDevicePopUp;
//
// MIDI controls
//
bool _enableMIDISettings;
CheckboxWidget *_multiMidiCheckbox;
StaticTextWidget *_midiGainDesc;
SliderWidget *_midiGainSlider;
StaticTextWidget *_midiGainLabel;
//
// MT-32 controls
//
bool _enableMT32Settings;
CheckboxWidget *_mt32Checkbox;
CheckboxWidget *_enableGSCheckbox;
//
// Subtitle controls
//
int getSubtitleMode(bool subtitles, bool speech_mute);
bool _enableSubtitleSettings;
bool _enableSubtitleToggle;
StaticTextWidget *_subToggleDesc;
RadiobuttonGroup *_subToggleGroup;
RadiobuttonWidget *_subToggleSubOnly;
RadiobuttonWidget *_subToggleSpeechOnly;
RadiobuttonWidget *_subToggleSubBoth;
static const char *_subModeDesc[];
static const char *_lowresSubModeDesc[];
StaticTextWidget *_subSpeedDesc;
SliderWidget *_subSpeedSlider;
StaticTextWidget *_subSpeedLabel;
//
// Volume controls
//
void updateMusicVolume(const int newValue) const;
void updateSfxVolume(const int newValue) const;
void updateSpeechVolume(const int newValue) const;
bool _enableVolumeSettings;
StaticTextWidget *_musicVolumeDesc;
SliderWidget *_musicVolumeSlider;
StaticTextWidget *_musicVolumeLabel;
StaticTextWidget *_sfxVolumeDesc;
SliderWidget *_sfxVolumeSlider;
StaticTextWidget *_sfxVolumeLabel;
StaticTextWidget *_speechVolumeDesc;
SliderWidget *_speechVolumeSlider;
StaticTextWidget *_speechVolumeLabel;
CheckboxWidget *_muteCheckbox;
protected:
//
// Game GUI options
//
Common::String _guioptions;
Common::String _guioptionsString;
//
// Backend controls
//
OptionsContainerWidget *_backendOptions;
};
class GlobalOptionsDialog : public OptionsDialog, public CommandSender {
public:
GlobalOptionsDialog(LauncherDialog *launcher);
~GlobalOptionsDialog() override;
void apply() override;
void close() override;
void handleCommand(CommandSender *sender, uint32 cmd, uint32 data) override;
void handleTickle() override;
void reflowLayout() override;
protected:
void build() override;
void clean() override;
Common::String _newTheme;
LauncherDialog *_launcher;
#ifdef USE_FLUIDSYNTH
FluidSynthSettingsDialog *_fluidSynthSettingsDialog;
#endif
void addMIDIControls(GuiObject *boss, const Common::String &prefix);
PathWidget *_savePath;
ButtonWidget *_savePathClearButton;
PathWidget *_themePath;
ButtonWidget *_themePathClearButton;
PathWidget *_iconPath;
ButtonWidget *_iconPathClearButton;
#ifdef USE_DLC
PathWidget *_dlcPath;
ButtonWidget *_dlcPathClearButton;
#endif
PathWidget *_extraPath;
ButtonWidget *_extraPathClearButton;
#ifdef DYNAMIC_MODULES
PathWidget *_pluginsPath;
ButtonWidget *_pluginsPathClearButton;
#endif
StaticTextWidget *_browserPath;
ButtonWidget *_browserPathClearButton;
StaticTextWidget *_logPath;
void addPathsControls(GuiObject *boss, const Common::String &prefix, bool lowres);
//
// GUI controls
//
StaticTextWidget *_curTheme;
StaticTextWidget *_guiBasePopUpDesc;
PopUpWidget *_guiBasePopUp;
StaticTextWidget *_rendererPopUpDesc;
PopUpWidget *_rendererPopUp;
StaticTextWidget *_guiLanguagePopUpDesc;
PopUpWidget *_guiLanguagePopUp;
CheckboxWidget *_guiLanguageUseGameLanguageCheckbox;
CheckboxWidget *_useSystemDialogsCheckbox;
CheckboxWidget *_guiReturnToLauncherAtExit;
CheckboxWidget *_guiConfirmExit;
CheckboxWidget *_guiDisableBDFScaling;
void addGUIControls(GuiObject *boss, const Common::String &prefix, bool lowres);
//
// Misc controls
//
StaticTextWidget *_autosavePeriodPopUpDesc;
PopUpWidget *_autosavePeriodPopUp;
StaticTextWidget *_randomSeedDesc;
EditTextWidget *_randomSeed;
ButtonWidget *_randomSeedClearButton;
PopUpWidget *_debugLevelPopUp;
#ifdef USE_UPDATES
StaticTextWidget *_updatesPopUpDesc;
PopUpWidget *_updatesPopUp;
#endif
bool updateAutosavePeriod(int newValue);
void addMiscControls(GuiObject *boss, const Common::String &prefix, bool lowres);
#ifdef USE_CLOUD
//
// Cloud controls
//
uint32 _selectedStorageIndex;
StaticTextWidget *_storagePopUpDesc;
PopUpWidget *_storagePopUp;
StaticTextWidget *_storageDisabledHint;
ButtonWidget *_storageEnableButton;
StaticTextWidget *_storageUsernameDesc;
StaticTextWidget *_storageUsername;
StaticTextWidget *_storageUsedSpaceDesc;
StaticTextWidget *_storageUsedSpace;
StaticTextWidget *_storageSyncHint;
StaticTextWidget *_storageLastSyncDesc;
StaticTextWidget *_storageLastSync;
ButtonWidget *_storageSyncSavesButton;
StaticTextWidget *_storageDownloadHint;
ButtonWidget *_storageDownloadButton;
StaticTextWidget *_storageDisconnectHint;
ButtonWidget *_storageDisconnectButton;
bool _connectingStorage;
StaticTextWidget *_storageWizardNotConnectedHint;
ButtonWidget *_storageWizardConnectButton;
bool _redrawCloudTab;
void addCloudControls(GuiObject *boss, const Common::String &prefix, bool lowres);
void setupCloudTab();
void shiftWidget(Widget *widget, const char *widgetName, int32 xOffset, int32 yOffset);
void storageSavesSyncedCallback(const Cloud::Storage::BoolResponse &response);
void storageErrorCallback(const Networking::ErrorResponse &response);
#endif // USE_CLOUD
#ifdef USE_SDL_NET
//
// LAN controls
//
ButtonWidget *_runServerButton;
StaticTextWidget *_serverInfoLabel;
ButtonWidget *_rootPathButton;
PathWidget *_rootPath;
ButtonWidget *_rootPathClearButton;
StaticTextWidget *_serverPortDesc;
EditTextWidget *_serverPort;
ButtonWidget *_serverPortClearButton;
StaticTextWidget *_featureDescriptionLine1;
StaticTextWidget *_featureDescriptionLine2;
bool _serverWasRunning;
void addNetworkControls(GuiObject *boss, const Common::String &prefix, bool lowres);
void reflowNetworkTabLayout();
#endif // USE_SDL_NET
//
// Accessibility controls
//
#ifdef USE_TTS
bool _enableTTS;
CheckboxWidget *_ttsCheckbox;
PopUpWidget *_ttsVoiceSelectionPopUp;
void addAccessibilityControls(GuiObject *boss, const Common::String &prefix);
#endif
#ifdef USE_DISCORD
bool _enableDiscordRpc;
CheckboxWidget *_discordRpcCheckbox;
#endif
};
} // End of namespace GUI
#endif

1010
gui/predictivedialog.cpp Normal file

File diff suppressed because it is too large Load Diff

154
gui/predictivedialog.h Normal file
View File

@@ -0,0 +1,154 @@
/* 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/>.
*
*/
#ifndef GUI_PREDICTIVEDIALOG_H
#define GUI_PREDICTIVEDIALOG_H
#include "gui/dialog.h"
#include "common/str.h"
#include "common/stream.h"
namespace GUI {
class EditTextWidget;
class ButtonWidget;
class PicButtonWidget;
class PredictiveDialog : public GUI::Dialog {
public:
PredictiveDialog();
~PredictiveDialog() override;
void reflowLayout() override;
void handleCommand(GUI::CommandSender *sender, uint32 cmd, uint32 data) override;
void handleKeyUp(Common::KeyState state) override;
void handleKeyDown(Common::KeyState state) override;
const char *getResult() const { return _predictiveResult; }
private:
enum ButtonId {
kButton1Act = 0,
kButton2Act = 1,
kButton3Act = 2,
kButton4Act = 3,
kButton5Act = 4,
kButton6Act = 5,
kButton7Act = 6,
kButton8Act = 7,
kButton9Act = 8,
kNextAct = 9,
kAddAct = 10,
kDelAct = 11,
kCancelAct = 12,
kOkAct = 13,
kModeAct = 14,
kButton0Act = 15,
kNoAct = -1
};
enum {
kButtonCount = kButton0Act + 1
};
enum {
kRepeatDelay = 500
};
enum {
kMaxLineLen = 80,
kMaxWordLen = 24,
kMaxWord = 50
};
struct Dict {
Dict() : dictLine(nullptr), dictText(nullptr), dictActLine(nullptr),
dictLineCount(0), dictTextSize(0) {}
~Dict() { free(dictText); }
char **dictLine;
char *dictText;
char *dictActLine; // using only for united dict...
int32 dictLineCount;
int32 dictTextSize;
Common::String nameDict;
Common::String defaultFilename;
};
uint8 countWordsInString(const char *const str);
void bringWordtoTop(char *str, int wordnum);
void loadDictionary(Common::SeekableReadStream *in, Dict &dict);
void loadAllDictionary(Dict &dict);
void addWordToDict();
void addWord(Dict &dict, const Common::String &word, const Common::String &code);
bool searchWord(const char *const where, const Common::String &whatCode);
int binarySearch(const char *const *const dictLine, const Common::String &code, const int dictLineCount);
bool matchWord();
void processButton(ButtonId active);
void pressEditText();
void saveUserDictToFile();
void mergeDicts();
void updateHighLightedButton(ButtonId active);
private:
Dict _unitedDict;
Dict _predictiveDict;
Dict _userDict;
int _mode;
ButtonId _lastButton;
bool _userDictHasChanged;
int _wordNumber;
uint8 _numMatchingWords;
char _predictiveResult[40];
Common::String _currentCode;
Common::String _currentWord;
Common::String _prefix;
uint32 _curTime, _lastTime;
ButtonId _lastPressedButton;
ButtonId _curPressedButton;
char _temp[kMaxWordLen + 1];
int _repeatcount[kMaxWordLen];
char *_memoryList[kMaxWord];
int _numMemory;
Common::String _search;
bool _navigationWithKeys;
bool _needRefresh;
bool _isPressed;
private:
EditTextWidget *_editText;
ButtonWidget *_button[kButtonCount];
};
} // namespace GUI
#endif

373
gui/recorderdialog.cpp Normal file
View File

@@ -0,0 +1,373 @@
/* 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/algorithm.h"
#include "common/bufferedstream.h"
#include "common/savefile.h"
#include "common/system.h"
#include "graphics/palette.h"
#include "graphics/scaler.h"
#include "graphics/thumbnail.h"
#include "common/translation.h"
#include "gui/widgets/list.h"
#include "gui/editrecorddialog.h"
#include "gui/EventRecorder.h"
#include "gui/message.h"
#include "common/system.h"
#include "gui/ThemeEval.h"
#include "gui/gui-manager.h"
#include "gui/recorderdialog.h"
#define MAX_RECORDS_NAMES 0xFF
namespace GUI {
#define SCALEVALUE(val) ((val) * g_gui.getScaleFactor())
enum {
kRecordCmd = 'RCRD',
kPlaybackCmd = 'PBCK',
kDeleteCmd = 'DEL ',
kNextScreenshotCmd = 'NEXT',
kPrevScreenshotCmd = 'PREV',
kEditRecordCmd = 'EDIT'
};
RecorderDialog::RecorderDialog() : Dialog("RecorderDialog"), _list(nullptr), _currentScreenshot(0) {
_firstScreenshotUpdate = false;
_screenShotsCount = 0;
_currentScreenshotText = nullptr;
_authorText = nullptr;
_notesText = nullptr;
_container = nullptr;
_gfxWidget = nullptr;
_nextScreenshotBtn = nullptr;
_prevScreenshotBtn = nullptr;
_backgroundType = ThemeEngine::kDialogBackgroundSpecial;
new StaticTextWidget(this, "RecorderDialog.Title", _("Recorder or Playback Gameplay"));
_list = new GUI::ListWidget(this, "RecorderDialog.List");
_list->setNumberingMode(GUI::kListNumberingOff);
_deleteButton = new GUI::ButtonWidget(this, "RecorderDialog.Delete", _("Delete"), Common::U32String(), kDeleteCmd);
new GUI::ButtonWidget(this, "RecorderDialog.Cancel", _("Cancel"), Common::U32String(), kCloseCmd);
new GUI::ButtonWidget(this, "RecorderDialog.Record", _("Record"), Common::U32String(), kRecordCmd);
_playbackButton = new GUI::ButtonWidget(this, "RecorderDialog.Playback", _("Playback"), Common::U32String(), kPlaybackCmd);
_editButton = new GUI::ButtonWidget(this, "RecorderDialog.Edit", _("Edit"), Common::U32String(), kEditRecordCmd);
_editButton->setEnabled(false);
_deleteButton->setEnabled(false);
_playbackButton->setEnabled(false);
_gfxWidget = new GUI::GraphicsWidget(this, 0, 0, 10, 10);
addThumbnailContainerButtonsAndText();
}
void RecorderDialog::reflowLayout() {
addThumbnailContainerButtonsAndText();
Dialog::reflowLayout();
if (g_gui.xmlEval()->getVar("Globals.RecorderDialog.ExtInfo.Visible") == 1) {
int16 x, y;
int16 w, h;
if (!g_gui.xmlEval()->getWidgetData("RecorderDialog.Thumbnail", x, y, w, h)) {
error("Error when loading position data for Recorder Thumbnails");
}
int thumbW = SCALEVALUE(kThumbnailWidth);
int thumbH = SCALEVALUE(kThumbnailHeight2);
int thumbX = x + (w >> 1) - (thumbW >> 1);
int thumbY = y + kLineHeight;
_gfxWidget->resize(thumbX, thumbY, thumbW, thumbH, false);
_gfxWidget->setVisible(true);
_container->resize(x, y, w, h, false);
_container->setVisible(true);
_notesText->setVisible(true);
_authorText->setVisible(true);
_nextScreenshotBtn->setVisible(true);
_prevScreenshotBtn->setVisible(true);
updateSelection(false);
} else {
if (_container) _container->setVisible(false);
_gfxWidget->setVisible(false);
if (_notesText) _notesText->setVisible(false);
if (_authorText) _authorText->setVisible(false);
if (_nextScreenshotBtn) _nextScreenshotBtn->setVisible(false);
if (_prevScreenshotBtn) _prevScreenshotBtn->setVisible(false);
}
}
void RecorderDialog::addThumbnailContainerButtonsAndText() {
// When switching layouts, create / remove the thumbnail container as needed
if (g_gui.xmlEval()->getVar("Globals.RecorderDialog.ExtInfo.Visible") == 1) {
if (!_container)
_container = new ContainerWidget(this, "RecorderDialog.Thumbnail");
if (!_nextScreenshotBtn)
_nextScreenshotBtn = new GUI::ButtonWidget(this,"RecorderDialog.NextScreenShotButton", Common::U32String("<"), Common::U32String(), kPrevScreenshotCmd);
if (!_prevScreenshotBtn)
_prevScreenshotBtn = new GUI::ButtonWidget(this, "RecorderDialog.PreviousScreenShotButton", Common::U32String(">"), Common::U32String(), kNextScreenshotCmd);
if (!_currentScreenshotText)
_currentScreenshotText = new StaticTextWidget(this, "RecorderDialog.currentScreenshot", Common::U32String("0/0"));
if (!_authorText)
_authorText = new StaticTextWidget(this, "RecorderDialog.Author", _("Author: "));
if (!_notesText)
_notesText = new StaticTextWidget(this, "RecorderDialog.Notes", _("Notes: "));
} else if (g_gui.xmlEval()->getVar("Globals.RecorderDialog.ExtInfo.Visible") == 0) {
if (_container) {
removeWidget(_container);
delete _container;
_container = nullptr;
}
if (_nextScreenshotBtn) {
removeWidget(_nextScreenshotBtn);
delete _nextScreenshotBtn;
_nextScreenshotBtn = nullptr;
}
if (_prevScreenshotBtn) {
removeWidget(_prevScreenshotBtn);
delete _prevScreenshotBtn;
_prevScreenshotBtn = nullptr;
}
if (_currentScreenshotText) {
removeWidget(_currentScreenshotText);
delete _currentScreenshotText;
_currentScreenshotText = nullptr;
}
if (_authorText) {
removeWidget(_authorText);
delete _authorText;
_authorText = nullptr;
}
if (_notesText) {
removeWidget(_notesText);
delete _notesText;
_notesText = nullptr;
}
}
}
void RecorderDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {
switch(cmd) {
case kEditRecordCmd: {
if (_list->getSelected() >= 0) {
EditRecordDialog editDlg(_fileHeaders[_list->getSelected()].author, _fileHeaders[_list->getSelected()].name, _fileHeaders[_list->getSelected()].notes);
if (editDlg.runModal() != kOKCmd) {
return;
}
_playbackFile.openRead(_fileHeaders[_list->getSelected()].fileName);
_playbackFile.getHeader().author = editDlg.getAuthor();
_playbackFile.getHeader().name = editDlg.getName();
_playbackFile.getHeader().notes = editDlg.getNotes();
_playbackFile.updateHeader();
_fileHeaders[_list->getSelected()] = _playbackFile.getHeader();
int oldselection = _list->getSelected();
updateList();
_list->setSelected(oldselection);
updateSelection(true);
_playbackFile.close();
}
}
break;
case kNextScreenshotCmd:
++_currentScreenshot;
updateScreenshot();
break;
case kPrevScreenshotCmd:
--_currentScreenshot;
updateScreenshot();
break;
case kDeleteCmd:
if (_list->getSelected() >= 0) {
MessageDialog alert(_("Do you really want to delete this record?"),
_("Delete"), _("Cancel"));
if (alert.runModal() == GUI::kMessageOK) {
_playbackFile.close();
g_eventRec.deleteRecord(_fileHeaders[_list->getSelected()].fileName);
_list->setSelected(-1);
updateList();
}
}
break;
case GUI::kListSelectionChangedCmd:
updateSelection(true);
break;
case kRecordCmd: {
TimeDate t;
QualifiedGameDescriptor desc = EngineMan.findTarget(_target);
g_system->getTimeAndDate(t);
EditRecordDialog editDlg(_("Unknown Author"), Common::String::format("%.2d.%.2d.%.4d ", t.tm_mday, t.tm_mon + 1, 1900 + t.tm_year) + desc.description, "");
if (editDlg.runModal() != kOKCmd) {
return;
}
_author = editDlg.getAuthor();
_name = editDlg.getName();
_notes = editDlg.getNotes();
_filename = g_eventRec.generateRecordFileName(_target);
setResult(kRecordDialogRecord);
close();
}
break;
case kPlaybackCmd:
if (_list->getSelected() >= 0) {
_filename = _fileHeaders[_list->getSelected()].fileName;
setResult(kRecordDialogPlayback);
close();
}
break;
case kCloseCmd:
setResult(kRecordDialogClose);
// Fall through
default:
Dialog::handleCommand(sender, cmd, data);
}
}
void RecorderDialog::updateList() {
Common::SaveFileManager *saveFileMan = g_system->getSavefileManager();
Common::String pattern(_target + ".r??");
Common::StringArray files = saveFileMan->listSavefiles(pattern);
Common::PlaybackFile file;
Common::U32StringArray namesList;
Common::sort(files.begin(), files.end());
_fileHeaders.clear();
for (auto &curFile : files) {
if (file.openRead(curFile)) {
namesList.push_back(file.getHeader().name);
_fileHeaders.push_back(file.getHeader());
}
file.close();
}
_list->setList(namesList);
_list->markAsDirty();
}
int RecorderDialog::runModal(Common::String &target) {
_target = target;
if (_gfxWidget)
_gfxWidget->setGfx((Graphics::ManagedSurface *)nullptr);
reflowLayout();
updateList();
return Dialog::runModal();
}
RecorderDialog::~RecorderDialog() {
}
void RecorderDialog::updateSelection(bool redraw) {
if (_list->getSelected() >= 0) {
_editButton->setEnabled(true);
_deleteButton->setEnabled(true);
_playbackButton->setEnabled(true);
}
if (g_gui.xmlEval()->getVar("Globals.RecorderDialog.ExtInfo.Visible") != 1)
return;
_gfxWidget->setGfx(-1, -1, 0, 0, 0);
_screenShotsCount = 0;
_currentScreenshot = 0;
if (_list->getSelected() >= 0) {
_authorText->setLabel(_("Author: ") + Common::U32String(_fileHeaders[_list->getSelected()].author));
_notesText->setLabel(_("Notes: ") + Common::U32String(_fileHeaders[_list->getSelected()].notes));
_firstScreenshotUpdate = true;
updateScreenshot();
updateScreenShotsText();
} else {
_authorText->setLabel(_("Author: "));
_notesText->setLabel(_("Notes: "));
_screenShotsCount = -1;
_currentScreenshot = 0;
_gfxWidget->setGfx(-1, -1, 0, 0, 0);
_gfxWidget->markAsDirty();
updateScreenShotsText();
}
if (redraw) {
_gfxWidget->markAsDirty();
_authorText->markAsDirty();
_notesText->markAsDirty();
updateScreenShotsText();
g_gui.scheduleTopDialogRedraw();
}
}
void RecorderDialog::updateScreenshot() {
if (_list->getSelected() == -1) {
return;
}
if (_firstScreenshotUpdate) {
_playbackFile.openRead(_fileHeaders[_list->getSelected()].fileName);
_screenShotsCount = _playbackFile.getScreensCount();
_currentScreenshot = _screenShotsCount > 0 ? 1 : 0;
_firstScreenshotUpdate = false;
}
if (_screenShotsCount == 0) {
return;
}
if (_currentScreenshot < 1) {
_currentScreenshot = _screenShotsCount;
}
if (_currentScreenshot > _screenShotsCount) {
_currentScreenshot = 1;
}
Graphics::Surface *srcsf = _playbackFile.getScreenShot(_currentScreenshot);
Common::SharedPtr<Graphics::Surface> srcsfSptr = Common::SharedPtr<Graphics::Surface>(srcsf, Graphics::SurfaceDeleter());
if (srcsfSptr) {
Graphics::Surface *destsf = Graphics::scale(*srcsfSptr, _gfxWidget->getWidth(), _gfxWidget->getHeight());
Common::SharedPtr<Graphics::Surface> destsfSptr = Common::SharedPtr<Graphics::Surface>(destsf, Graphics::SurfaceDeleter());
if (destsfSptr && _gfxWidget->isVisible())
_gfxWidget->setGfx(destsf, false);
} else {
_gfxWidget->setGfx(-1, -1, 0, 0, 0);
}
updateScreenShotsText();
_gfxWidget->markAsDirty();
}
void RecorderDialog::updateScreenShotsText() {
if (_screenShotsCount == -1) {
_currentScreenshotText->setLabel(Common::String::format("%d / ?", _currentScreenshot));
} else {
_currentScreenshotText->setLabel(Common::String::format("%d / %d", _currentScreenshot, _screenShotsCount));
}
_currentScreenshotText->markAsDirty();
}
} // End of namespace GUI

86
gui/recorderdialog.h Normal file
View File

@@ -0,0 +1,86 @@
/* 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/>.
*
*/
#ifndef GUI_RECORDER_DIALOG_H
#define GUI_RECORDER_DIALOG_H
#include "common/stream.h"
#include "common/recorderfile.h"
#include "gui/dialog.h"
namespace GUI {
class ListWidget;
class GraphicsWidget;
class ButtonWidget;
class CommandSender;
class ContainerWidget;
class StaticTextWidget;
class RecorderDialog : public GUI::Dialog {
using GUI::Dialog::runModal;
private:
bool _firstScreenshotUpdate;
Common::PlaybackFile _playbackFile;
Common::String _target;
Common::String _filename;
int _currentScreenshot;
int _screenShotsCount;
Common::Array<Common::PlaybackFile::PlaybackFileHeader> _fileHeaders;
GUI::ListWidget *_list;
GUI::ContainerWidget *_container;
GUI::GraphicsWidget *_gfxWidget;
GUI::StaticTextWidget *_currentScreenshotText;
GUI::StaticTextWidget *_authorText;
GUI::StaticTextWidget *_notesText;
GUI::ButtonWidget *_editButton;
GUI::ButtonWidget *_deleteButton;
GUI::ButtonWidget *_playbackButton;
GUI::ButtonWidget *_nextScreenshotBtn;
GUI::ButtonWidget *_prevScreenshotBtn;
void updateList();
void updateScreenShotsText();
void updateSelection(bool redraw);
void updateScreenshot();
void addThumbnailContainerButtonsAndText();
public:
Common::U32String _author;
Common::String _name;
Common::String _notes;
enum DialogResult {
kRecordDialogClose,
kRecordDialogRecord,
kRecordDialogPlayback
};
RecorderDialog();
~RecorderDialog() override;
void handleCommand(GUI::CommandSender *sender, uint32 cmd, uint32 data) override;
void reflowLayout() override;
int runModal(Common::String &target);
const Common::String getFileName() {return _filename;}
};
} // End of namespace GUI
#endif

229
gui/remotebrowser.cpp Normal file
View File

@@ -0,0 +1,229 @@
/* 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 "gui/remotebrowser.h"
#include "gui/gui-manager.h"
#include "gui/widgets/list.h"
#include "common/config-manager.h"
#include "common/system.h"
#include "common/algorithm.h"
#include "common/translation.h"
#include "backends/networking/http/request.h"
#include "backends/cloud/storage.h"
#include "backends/cloud/cloudmanager.h"
#include "gui/message.h"
namespace GUI {
enum {
kChooseCmd = 'Chos',
kGoUpCmd = 'GoUp'
};
RemoteBrowserDialog::RemoteBrowserDialog(const Common::U32String &title):
Dialog("Browser"), _navigationLocked(false), _updateList(false), _showError(false),
_workingRequest(nullptr), _ignoreCallback(false) {
_backgroundType = GUI::ThemeEngine::kDialogBackgroundPlain;
new StaticTextWidget(this, "Browser.Headline", title);
_currentPath = new StaticTextWidget(this, "Browser.Path", Common::U32String("DUMMY"));
_fileList = new ListWidget(this, "Browser.List");
_fileList->setNumberingMode(kListNumberingOff);
_fileList->setEditable(false);
if (!g_gui.useLowResGUI())
new ButtonWidget(this, "Browser.Up", _("Go up"), _("Go to previous directory level"), kGoUpCmd);
else
new ButtonWidget(this, "Browser.Up", _c("Go up", "lowres"), _("Go to previous directory level"), kGoUpCmd);
new ButtonWidget(this, "Browser.Cancel", _("Cancel"), Common::U32String(), kCloseCmd);
new ButtonWidget(this, "Browser.Choose", _("Choose"), Common::U32String(), kChooseCmd);
}
RemoteBrowserDialog::~RemoteBrowserDialog() {
if (_workingRequest) {
_ignoreCallback = true;
_workingRequest->finish();
}
}
void RemoteBrowserDialog::open() {
Dialog::open();
listDirectory(Cloud::StorageFile());
}
void RemoteBrowserDialog::close() {
Dialog::close();
if (_workingRequest) {
_ignoreCallback = true;
_workingRequest->finish();
_ignoreCallback = false;
}
}
void RemoteBrowserDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {
switch (cmd) {
case kChooseCmd: {
// If nothing is selected in the list widget, choose the current dir.
// Else, choose the dir that is selected.
int selection = _fileList->getSelected();
if (selection >= 0)
_choice = _nodeContent[selection];
else
_choice = _node;
setResult(1);
close();
break;
}
case kGoUpCmd:
goUp();
break;
case kListItemActivatedCmd:
case kListItemDoubleClickedCmd:
if (_nodeContent[data].isDirectory()) {
_rememberedNodeContents[_node.path()] = _nodeContent;
listDirectory(_nodeContent[data]);
}
break;
case kListSelectionChangedCmd:
// We do not allow selecting directories,
// thus we will invalidate the selection
// when the user selects a directory over here.
if (data != (uint32)-1 && !_nodeContent[data].isDirectory())
_fileList->setSelected(-1);
break;
default:
Dialog::handleCommand(sender, cmd, data);
}
}
void RemoteBrowserDialog::handleTickle() {
if (_updateList) {
updateListing();
_updateList = false;
}
if (_showError) {
_showError = false;
MessageDialog alert(_("ScummVM could not access the directory!"));
alert.runModal();
}
Dialog::handleTickle();
}
void RemoteBrowserDialog::updateListing() {
// Update the path display
Common::String path = _node.path();
if (path.empty())
path = "/"; //root
if (_navigationLocked)
path = "Loading... " + path;
_currentPath->setLabel(path);
if (!_navigationLocked) {
// Populate the ListWidget
Common::U32StringArray list;
for (auto &node : _nodeContent) {
if (node.isDirectory()) {
list.push_back(ListWidget::getThemeColor(ThemeEngine::kFontColorNormal) + Common::U32String(node.name() + "/"));
} else {
list.push_back(ListWidget::getThemeColor(ThemeEngine::kFontColorAlternate) + Common::U32String(node.name()));
}
}
_fileList->setList(list);
_fileList->scrollTo(0);
}
_fileList->setEnabled(!_navigationLocked);
// Finally, redraw
g_gui.scheduleTopDialogRedraw();
}
void RemoteBrowserDialog::goUp() {
if (_rememberedNodeContents.contains(_node.path()))
_rememberedNodeContents.erase(_node.path());
Common::String path = _node.path();
if (path.size() && (path.lastChar() == '/' || path.lastChar() == '\\'))
path.deleteLastChar();
if (path.empty()) {
_rememberedNodeContents.erase("");
} else {
for (int i = path.size() - 1; i >= 0; --i)
if (i == 0 || path[i] == '/' || path[i] == '\\') {
path.erase(i);
break;
}
}
listDirectory(Cloud::StorageFile(path, 0, 0, true));
}
void RemoteBrowserDialog::listDirectory(const Cloud::StorageFile &node) {
if (_navigationLocked || _workingRequest)
return;
if (_rememberedNodeContents.contains(node.path())) {
_nodeContent = _rememberedNodeContents[node.path()];
} else {
_navigationLocked = true;
_workingRequest = CloudMan.listDirectory(
node.path(),
new Common::Callback<RemoteBrowserDialog, const Cloud::Storage::ListDirectoryResponse &>(this, &RemoteBrowserDialog::directoryListedCallback),
new Common::Callback<RemoteBrowserDialog, const Networking::ErrorResponse &>(this, &RemoteBrowserDialog::directoryListedErrorCallback),
false
);
}
_backupNode = _node;
_node = node;
updateListing();
}
void RemoteBrowserDialog::directoryListedCallback(const Cloud::Storage::ListDirectoryResponse &response) {
_workingRequest = nullptr;
if (_ignoreCallback)
return;
_navigationLocked = false;
_nodeContent = response.value;
Common::sort(_nodeContent.begin(), _nodeContent.end(), FileListOrder());
_updateList = true;
}
void RemoteBrowserDialog::directoryListedErrorCallback(const Networking::ErrorResponse &error) {
_workingRequest = nullptr;
if (_ignoreCallback)
return;
_navigationLocked = false;
_node = _backupNode;
_updateList = true;
_showError = true;
}
} // End of namespace GUI

83
gui/remotebrowser.h Normal file
View File

@@ -0,0 +1,83 @@
/* 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/>.
*
*/
#ifndef GUI_REMOTEBROWSER_DIALOG_H
#define GUI_REMOTEBROWSER_DIALOG_H
#include "gui/dialog.h"
#include "common/fs.h"
#include "backends/cloud/storagefile.h"
#include "backends/networking/http/request.h"
#include "backends/cloud/storage.h"
namespace GUI {
class ListWidget;
class StaticTextWidget;
class CheckboxWidget;
class CommandSender;
class RemoteBrowserDialog : public Dialog {
public:
RemoteBrowserDialog(const Common::U32String &title);
~RemoteBrowserDialog() override;
void open() override;
void close() override;
void handleCommand(CommandSender *sender, uint32 cmd, uint32 data) override;
void handleTickle() override;
const Cloud::StorageFile &getResult() { return _choice; }
protected:
ListWidget *_fileList;
StaticTextWidget *_currentPath;
Cloud::StorageFile _node, _backupNode;
Common::Array<Cloud::StorageFile> _nodeContent;
Common::HashMap<Common::String, Common::Array<Cloud::StorageFile> > _rememberedNodeContents;
Cloud::StorageFile _choice;
bool _navigationLocked;
bool _updateList;
bool _showError;
Networking::Request *_workingRequest;
bool _ignoreCallback;
void updateListing();
void goUp();
void listDirectory(const Cloud::StorageFile &node);
void directoryListedCallback(const Cloud::Storage::ListDirectoryResponse &response);
void directoryListedErrorCallback(const Networking::ErrorResponse &error);
struct FileListOrder : public Common::BinaryFunction<Cloud::StorageFile, Cloud::StorageFile, bool> {
bool operator()(const Cloud::StorageFile &x, const Cloud::StorageFile &y) const {
if (x.isDirectory() != y.isDirectory()) {
return x.isDirectory(); //x < y (directory < not directory) or x > y (not directory > directory)
}
return x.name() < y.name();
}
};
};
} // End of namespace GUI
#endif

1280
gui/saveload-dialog.cpp Normal file

File diff suppressed because it is too large Load Diff

265
gui/saveload-dialog.h Normal file
View File

@@ -0,0 +1,265 @@
/* 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/>.
*
*/
#ifndef GUI_SAVELOAD_DIALOG_H
#define GUI_SAVELOAD_DIALOG_H
#include "gui/dialog.h"
#include "gui/widgets/list.h"
#include "engines/metaengine.h"
namespace GUI {
#ifdef USE_CLOUD
class SaveLoadChooserDialog;
class SaveLoadCloudSyncProgressDialog : public Dialog { //protected?
StaticTextWidget *_label, *_percentLabel;
SliderWidget *_progressBar;
SaveLoadChooserDialog *_parent;
bool _close;
int _pollFrame;
public:
SaveLoadCloudSyncProgressDialog(bool canRunInBackground, SaveLoadChooserDialog *parent);
~SaveLoadCloudSyncProgressDialog() override;
void handleCommand(CommandSender *sender, uint32 cmd, uint32 data) override;
void handleTickle() override;
private:
void pollCloudMan();
};
#endif
#define kSwitchSaveLoadDialog -2
// TODO: We might want to disable the grid based save/load chooser for more
// platforms, than those which define DISABLE_FANCY_THEMES. But those are
// probably not able to handle the grid chooser anyway, so disabling it
// for them is a good start.
#ifdef DISABLE_FANCY_THEMES
#define DISABLE_SAVELOADCHOOSER_GRID
#endif // DISABLE_FANCY_THEMES
#ifndef DISABLE_SAVELOADCHOOSER_GRID
enum SaveLoadChooserType {
kSaveLoadDialogList = 0,
kSaveLoadDialogGrid = 1
};
SaveLoadChooserType getRequestedSaveLoadDialog(const MetaEngine *metaEngine);
#endif // !DISABLE_SAVELOADCHOOSER_GRID
class SaveLoadChooserDialog : protected Dialog {
public:
SaveLoadChooserDialog(const Common::String &dialogName, const bool saveMode);
SaveLoadChooserDialog(int x, int y, int w, int h, const bool saveMode);
~SaveLoadChooserDialog() override;
void open() override;
void close() override;
void reflowLayout() override;
void handleCommand(CommandSender *sender, uint32 cmd, uint32 data) override;
#ifdef USE_CLOUD
virtual void runSaveSync(bool hasSavepathOverride);
#endif
void handleTickle() override;
#ifndef DISABLE_SAVELOADCHOOSER_GRID
virtual SaveLoadChooserType getType() const = 0;
#endif // !DISABLE_SAVELOADCHOOSER_GRID
int run(const Common::String &target, const MetaEngine *metaEngine);
virtual const Common::U32String getResultString() const = 0;
protected:
virtual int runIntern() = 0;
/** Common function to refresh the list on the screen. */
virtual void updateSaveList(bool external);
/**
* Common function to get saves list from MetaEngine.
*
* It also checks whether there are some locked saves
* because of saves sync and adds such saves as locked
* slots. User sees these slots, but is unable to save
* or load from these.
*/
virtual void listSaves();
void activate(int slot, const Common::U32String &description);
const bool _saveMode;
const MetaEngine *_metaEngine;
bool _delSupport;
bool _metaInfoSupport;
bool _thumbnailSupport;
bool _saveDateSupport;
bool _playTimeSupport;
Common::String _target;
bool _dialogWasShown;
SaveStateList _saveList;
Common::U32String _resultString;
#ifndef DISABLE_SAVELOADCHOOSER_GRID
ButtonWidget *_listButton;
ButtonWidget *_gridButton;
void addChooserButtons();
ButtonWidget *createSwitchButton(const Common::String &name, const Common::U32String &desc, const Common::U32String &tooltip, const char *image, uint32 cmd = 0);
#endif // !DISABLE_SAVELOADCHOOSER_GRID
#ifdef USE_CLOUD
int _pollFrame;
bool _didUpdateAfterSync;
/** If CloudMan is syncing, this will refresh the list of saves. */
void pollCloudMan();
friend class SaveLoadCloudSyncProgressDialog;
#endif
};
class SaveLoadChooserSimple : public SaveLoadChooserDialog {
public:
SaveLoadChooserSimple(const Common::U32String &title, const Common::U32String &buttonLabel, bool saveMode);
void handleCommand(CommandSender *sender, uint32 cmd, uint32 data) override;
const Common::U32String getResultString() const override;
void reflowLayout() override;
#ifndef DISABLE_SAVELOADCHOOSER_GRID
SaveLoadChooserType getType() const override { return kSaveLoadDialogList; }
#endif // !DISABLE_SAVELOADCHOOSER_GRID
void open() override;
void close() override;
protected:
void updateSaveList(bool external) override;
private:
int runIntern() override;
ListWidget *_list;
ButtonWidget *_chooseButton;
ButtonWidget *_deleteButton;
GraphicsWidget *_gfxWidget;
ContainerWidget *_container;
StaticTextWidget *_date;
StaticTextWidget *_time;
StaticTextWidget *_playtime;
StaticTextWidget *_pageTitle;
void addThumbnailContainer();
void updateSelection(bool redraw);
};
#ifndef DISABLE_SAVELOADCHOOSER_GRID
class EditTextWidget;
class SavenameDialog : public Dialog {
public:
SavenameDialog();
void setDescription(const Common::U32String &desc);
const Common::U32String &getDescription();
void setTargetSlot(int slot) { _targetSlot = slot; }
void open() override;
protected:
void handleCommand(CommandSender *sender, uint32 cmd, uint32 data) override;
private:
int _targetSlot;
StaticTextWidget *_title;
EditTextWidget *_description;
};
class SaveLoadChooserGrid : public SaveLoadChooserDialog {
public:
SaveLoadChooserGrid(const Common::U32String &title, bool saveMode);
~SaveLoadChooserGrid() override;
const Common::U32String getResultString() const override;
void open() override;
void reflowLayout() override;
SaveLoadChooserType getType() const override { return kSaveLoadDialogGrid; }
void close() override;
protected:
void handleCommand(CommandSender *sender, uint32 cmd, uint32 data) override;
void handleMouseWheel(int x, int y, int direction) override;
void updateSaveList(bool external) override;
private:
int runIntern() override;
uint _columns, _lines;
uint _entriesPerPage;
uint _curPage;
ButtonWidget *_nextButton;
ButtonWidget *_prevButton;
StaticTextWidget *_pageTitle;
StaticTextWidget *_pageDisplay;
ContainerWidget *_newSaveContainer;
int _nextFreeSaveSlot;
SavenameDialog _savenameDialog;
bool selectDescription();
struct SlotButton {
SlotButton() : container(nullptr), button(nullptr), description(nullptr) {}
SlotButton(ContainerWidget *c, PicButtonWidget *b, StaticTextWidget *d) : container(c), button(b), description(d) {}
ContainerWidget *container;
PicButtonWidget *button;
StaticTextWidget *description;
void setVisible(bool state) {
container->setVisible(state);
}
};
typedef Common::Array<SlotButton> ButtonArray;
ButtonArray _buttons;
void destroyButtons();
void hideButtons();
void updateSaves();
};
#endif // !DISABLE_SAVELOADCHOOSER_GRID
} // End of namespace GUI
#endif

120
gui/saveload.cpp Normal file
View File

@@ -0,0 +1,120 @@
/* 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/config-manager.h"
#include "common/system.h"
#include "gui/saveload.h"
#include "gui/saveload-dialog.h"
#include "engines/engine.h"
#include "engines/metaengine.h"
namespace GUI {
SaveLoadChooser::SaveLoadChooser(const Common::U32String &title, const Common::U32String &buttonLabel, bool saveMode)
: _impl(nullptr), _title(title), _buttonLabel(buttonLabel), _saveMode(saveMode) {
}
SaveLoadChooser::~SaveLoadChooser() {
delete _impl;
_impl = nullptr;
}
void SaveLoadChooser::selectChooser(const MetaEngine *engine) {
#ifndef DISABLE_SAVELOADCHOOSER_GRID
const SaveLoadChooserType requestedType = getRequestedSaveLoadDialog(engine);
if (!_impl || _impl->getType() != requestedType) {
delete _impl;
_impl = nullptr;
switch (requestedType) {
case kSaveLoadDialogGrid:
_impl = new SaveLoadChooserGrid(_title, _saveMode);
break;
default:
// fallthrough intended
case kSaveLoadDialogList:
#endif // !DISABLE_SAVELOADCHOOSER_GRID
_impl = new SaveLoadChooserSimple(_title, _buttonLabel, _saveMode);
#ifndef DISABLE_SAVELOADCHOOSER_GRID
break;
}
}
#endif // !DISABLE_SAVELOADCHOOSER_GRID
}
Common::String SaveLoadChooser::createDefaultSaveDescription(const int slot) const {
#if defined(USE_SAVEGAME_TIMESTAMP)
TimeDate curTime;
g_system->getTimeAndDate(curTime);
curTime.tm_year += 1900; // fixup year
curTime.tm_mon++; // fixup month
return Common::String::format("%04d-%02d-%02d / %02d:%02d:%02d", curTime.tm_year, curTime.tm_mon, curTime.tm_mday, curTime.tm_hour, curTime.tm_min, curTime.tm_sec);
#else
return Common::String::format("Save %d", slot + 1);
#endif
}
int SaveLoadChooser::runModalWithCurrentTarget() {
if (!g_engine)
error("No engine is currently active");
return runModalWithMetaEngineAndTarget(g_engine->getMetaEngine(), ConfMan.getActiveDomainName());
}
int SaveLoadChooser::runModalWithMetaEngineAndTarget(const MetaEngine *engine, const Common::String &target) {
selectChooser(engine);
if (!_impl)
return -1;
#ifdef USE_CLOUD
_impl->runSaveSync(ConfMan.hasKey("savepath", target));
#endif
// Set up the game domain as newly active domain, so
// target specific savepath will be checked
Common::String oldDomain = ConfMan.getActiveDomainName();
ConfMan.setActiveDomain(target);
int ret;
do {
ret = _impl->run(target, engine);
#ifndef DISABLE_SAVELOADCHOOSER_GRID
if (ret == kSwitchSaveLoadDialog) {
selectChooser(engine);
}
#endif // !DISABLE_SAVELOADCHOOSER_GRID
} while (ret < -1);
// Revert to the old active domain
ConfMan.setActiveDomain(oldDomain);
return ret;
}
const Common::U32String SaveLoadChooser::getResultString() const {
assert(_impl);
return _impl->getResultString();
}
} // End of namespace GUI

74
gui/saveload.h Normal file
View File

@@ -0,0 +1,74 @@
/* 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/>.
*
*/
#ifndef GUI_SAVELOAD_H
#define GUI_SAVELOAD_H
#include "common/str.h"
#include "engines/metaengine.h"
namespace GUI {
class SaveLoadChooserDialog;
class SaveLoadChooser {
protected:
SaveLoadChooserDialog *_impl;
const Common::U32String _title;
const Common::U32String _buttonLabel;
const bool _saveMode;
void selectChooser(const MetaEngine *engine);
public:
SaveLoadChooser(const Common::U32String &title, const Common::U32String &buttonLabel, bool saveMode);
~SaveLoadChooser();
/**
* Runs the save/load chooser with the currently active config manager
* domain as target.
*
* @return The selcted save slot. -1 in case none is selected.
*/
int runModalWithCurrentTarget();
int runModalWithMetaEngineAndTarget(const MetaEngine *engine, const Common::String &target);
const Common::U32String getResultString() const;
/**
* Creates a default save description for the specified slot. Depending
* on the ScummVM configuration this might be a simple "Slot #" description
* or the current date and time.
*
* TODO: This might not be the best place to put this, since engines not
* using this class might want to mimic the same behavior. Check whether
* moving this to a better place makes sense and find what this place would
* be.
*
* @param slot The slot number (must be >= 0).
* @return The slot description.
*/
Common::String createDefaultSaveDescription(const int slot) const;
};
} // End of namespace GUI
#endif

View File

@@ -0,0 +1,201 @@
/* 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 "gui/shaderbrowser-dialog.h"
#include "common/system.h"
#include "common/algorithm.h"
#include "common/str-array.h"
#include "common/zip-set.h"
#include "common/translation.h"
#include "gui/ThemeEval.h"
#include "gui/widgets/list.h"
#include "gui/browser.h"
#include "gui/gui-manager.h"
#include "gui/message.h"
namespace GUI {
enum {
kChooseCmd = 'Chos',
kChooseFileCmd = 'File',
kSearchCmd = 'SRCH',
kListSearchCmd = 'LSSR',
kSearchClearCmd = 'SRCL',
};
const char *const kFileMask = "*.glslp";
const char *const kFileExt = "glslp";
ShaderBrowserDialog::ShaderBrowserDialog(const Common::Path &initialSelection) : Dialog("ShaderBrowser") {
new StaticTextWidget(this, "ShaderBrowser.Headline", _("Choose shader from the list below (or pick a file instead)"));
_fileName = new PathWidget(this, "ShaderBrowser.Filename", initialSelection);
// Search box
_searchDesc = nullptr;
#ifndef DISABLE_FANCY_THEMES
_searchPic = nullptr;
if (g_gui.xmlEval()->getVar("Globals.ShowSearchPic") == 1 && g_gui.theme()->supportsImages()) {
_searchPic = new GraphicsWidget(this, "ShaderBrowser.SearchPic", _("Search in game list"));
_searchPic->setGfxFromTheme(ThemeEngine::kImageSearch);
} else
#endif
_searchDesc = new StaticTextWidget(this, "ShaderBrowser.SearchDesc", _("Search:"));
_searchWidget = new EditTextWidget(this, "ShaderBrowser.Search", _search, Common::U32String(), kSearchCmd);
_searchClearButton = addClearButton(this, "ShaderBrowser.SearchClearButton", kSearchClearCmd);
new ButtonWidget(this, "ShaderBrowser.BrowseFile", _("Pick file instead..."), _("Pick shader from file system"), kChooseFileCmd);
// Add file list
_fileList = new ListWidget(this, "ShaderBrowser.List");
_fileList->setNumberingMode(kListNumberingOff);
_fileList->setEditable(false);
_backgroundType = GUI::ThemeEngine::kDialogBackgroundPlain;
// Buttons
new ButtonWidget(this, "ShaderBrowser.Cancel", _("Cancel"), Common::U32String(), kCloseCmd);
new ButtonWidget(this, "ShaderBrowser.Choose", _("Choose"), Common::U32String(), kChooseCmd);
}
void ShaderBrowserDialog::open() {
// Call super implementation
Dialog::open();
Common::generateZipSet(_shaderSet, "shaders.dat", "shaders*.dat");
updateListing();
}
void ShaderBrowserDialog::reflowLayout() {
#ifndef DISABLE_FANCY_THEMES
if (g_gui.xmlEval()->getVar("Globals.ShowSearchPic") == 1 && g_gui.theme()->supportsImages()) {
if (!_searchPic)
_searchPic = new GraphicsWidget(this, "ShaderBrowser.SearchPic", _("Search in game list"));
_searchPic->setGfxFromTheme(ThemeEngine::kImageSearch);
if (_searchDesc) {
removeWidget(_searchDesc);
g_gui.addToTrash(_searchDesc, this);
_searchDesc = nullptr;
}
} else {
if (!_searchDesc)
_searchDesc = new StaticTextWidget(this, "ShaderBrowser.SearchDesc", _("Search:"));
if (_searchPic) {
removeWidget(_searchPic);
g_gui.addToTrash(_searchPic, this);
_searchPic = nullptr;
}
}
#endif
removeWidget(_searchClearButton);
g_gui.addToTrash(_searchClearButton, this);
_searchClearButton = addClearButton(this, "ShaderBrowser.SearchClearButton", kSearchClearCmd);
Dialog::reflowLayout();
}
void ShaderBrowserDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {
switch (cmd) {
case kChooseCmd:
if (_fileName->getLabel().empty())
break;
normalizeFileName();
setResult(1);
close();
break;
case kChooseFileCmd: {
BrowserDialog browser(_("Select shader"), false);
if (browser.runModal() > 0) {
// User made his choice...
Common::FSNode file(browser.getResult());
_fileName->setLabel(file.getPath());
setResult(1);
close();
}
break;
}
case kSearchCmd:
// Update the active search filter.
_fileList->setFilter(_searchWidget->getEditString());
break;
case kSearchClearCmd:
// Reset the active search filter, thus showing all games again
_searchWidget->setEditString(Common::U32String());
_fileList->setFilter(Common::U32String());
break;
case kListSelectionChangedCmd:
_fileName->setLabel(Common::Path(_fileList->getList().operator[](_fileList->getSelected()).encode()));
break;
case kListItemActivatedCmd:
case kListItemDoubleClickedCmd:
normalizeFileName();
setResult(1);
close();
break;
default:
Dialog::handleCommand(sender, cmd, data);
}
}
void ShaderBrowserDialog::normalizeFileName() {
Common::Path filename = _fileName->getLabel();
Common::String basename = filename.baseName();
if (basename.matchString(kFileMask, true))
return;
filename.appendInPlace(".").appendInPlace(kFileExt);
_fileName->setLabel(filename);
}
void ShaderBrowserDialog::updateListing() {
Common::U32StringArray list;
Common::ArchiveMemberList files;
_shaderSet.listMatchingMembers(files, kFileMask, true);
Common::sort(files.begin(), files.end(), Common::ArchiveMemberListComparator());
for (auto &file : files) {
list.push_back(file->getPathInArchive().toString().decode());
}
_fileList->setList(list);
_fileList->scrollTo(0);
// Finally, redraw
g_gui.scheduleTopDialogRedraw();
}
} // End of namespace GUI

View File

@@ -0,0 +1,69 @@
/* 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/>.
*
*/
#ifndef SHADERBROWSER_DIALOG_H
#define SHADERBROWSER_DIALOG_H
#include "gui/dialog.h"
#include "gui/widgets/edittext.h"
namespace GUI {
class ListWidget;
class EditTextWidget;
class CommandSender;
enum {
kFBModeLoad = 0,
kFBModeSave
};
class ShaderBrowserDialog : public Dialog {
public:
ShaderBrowserDialog(const Common::Path &initialSelection);
void open() override;
void reflowLayout() override;
void handleCommand(CommandSender *sender, uint32 cmd, uint32 data) override;
Common::Path getResult() { return Dialog::getResult() ? _fileName->getLabel() : Common::Path(); }
protected:
PathWidget *_fileName;
ListWidget *_fileList;
Common::SearchSet _shaderSet;
Common::String _search;
EditTextWidget *_searchWidget;
#ifndef DISABLE_FANCY_THEMES
GraphicsWidget *_searchPic;
#endif
StaticTextWidget *_searchDesc;
ButtonWidget *_searchClearButton;
void updateListing();
void normalizeFileName();
};
} // End of namespace GUI
#endif

261
gui/textviewer.cpp Normal file
View File

@@ -0,0 +1,261 @@
/* 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/file.h"
#include "common/tokenizer.h"
#include "common/translation.h"
#include "graphics/font.h"
#include "graphics/fontman.h"
#include "gui/gui-manager.h"
#include "gui/ThemeEval.h"
#include "gui/textviewer.h"
#include "gui/widgets/scrollbar.h"
#ifdef EMSCRIPTEN
#include "backends/platform/sdl/emscripten/emscripten.h"
#endif
namespace GUI {
#define kDialogWidthPercent 0.8f
#define kDialogHeightPercent 0.8f
#define kPadX 0.05
#define kPadY 0.05
TextViewerDialog::TextViewerDialog(const Common::Path &fname)
: Dialog(0, 0, 1, 1), _fname(fname) {
_font = &g_gui.getFont(ThemeEngine::kFontStyleConsole);
_charWidth = _font->getMaxCharWidth();
_lineHeight = _font->getFontHeight() + 2;
// Add scrollbar
_scrollBar = new ScrollBarWidget(this, 0, 0, 1, 1);
_scrollBar->setTarget(this);
// I18N: Close dialog button
_closeButton = new ButtonWidget(this, 0, 0, 1, 1, _("Close"), Common::U32String(), kCloseCmd);
#ifdef EMSCRIPTEN
_downloadButton = new ButtonWidget(this, 0, 0, 1, 1, _("Download"), Common::U32String(), kDownloadFileCmd);
#endif
_currentPos = 0;
_scrollLine = _linesPerPage - 1;
reflowLayout();
loadFile(fname);
}
TextViewerDialog::~TextViewerDialog() {
destroy();
}
bool TextViewerDialog::loadFile(const Common::Path &fname) {
Common::FSNode file(fname);
if (!file.exists()) {
warning("TextViewerDialog::loadFile(): Cannot open file %s", fname.toString(Common::Path::kNativeSeparator).c_str());
return false;
}
Common::SeekableReadStream *stream = file.createReadStream();
if (!stream) {
warning("TextViewerDialog::loadFile(): Cannot load file %s", fname.toString(Common::Path::kNativeSeparator).c_str());
return false;
}
warning("TextViewerDialog::loadFile(): File size is: %ld", long(stream->size()));
_linesArray.clear();
while (!stream->eos()) {
Common::String line = stream->readString('\n');
line.wordWrap(_lineWidth);
Common::StringTokenizer lines(line, "\n");
while (!lines.empty())
_linesArray.push_back(lines.nextToken());
}
delete stream;
return true;
}
void TextViewerDialog::destroy() {
_linesArray.clear();
}
void TextViewerDialog::reflowLayout() {
int16 screenW, screenH;
const Common::Rect safeArea = g_system->getSafeOverlayArea(&screenW, &screenH);
// Calculate the real width/height (rounded to char/line multiples)
_w = (uint16)(kDialogWidthPercent * safeArea.width());
_h = (uint16)((kDialogHeightPercent * safeArea.height() - 2) / _lineHeight);
_h = _h * _lineHeight + 2;
_x = (screenW - _w) / 2;
_y = (screenH - _h) / 2;
safeArea.constrain(_x, _y, _w, _h);
_padX = _w * kPadX;
_padY = _h * kPadY;
int16 bW = g_gui.xmlEval()->getVar("Globals.Button.Width", 0);
int16 bH = g_gui.xmlEval()->getVar("Globals.Button.Height", 0);
int16 padR = g_gui.xmlEval()->getVar("Globals.Padding.Right", 5);
int16 padB = g_gui.xmlEval()->getVar("Globals.Padding.Bottom", 5);
int16 scrollbarWidth = g_gui.xmlEval()->getVar("Globals.Scrollbar.Width", 0);
int16 buttonOffset = bH + padB;
_closeButton->setPos(_w - bW - padR, _h - buttonOffset);
_closeButton->setSize(bW, bH);
#ifdef EMSCRIPTEN
_downloadButton->setPos(_w - 2 * bW - 2 * padR, _h - buttonOffset);
_downloadButton->setSize(bW, bH);
#endif
// Calculate depending values
_lineWidth = (_w - scrollbarWidth - _padX * 2) / _charWidth;
_linesPerPage = (_h - _padY * 2 - buttonOffset) / _lineHeight;
_scrollBar->setPos(_w - scrollbarWidth - 1, 0);
_scrollBar->setSize(scrollbarWidth, _h - buttonOffset);
}
void TextViewerDialog::open() {
Dialog::open();
}
void TextViewerDialog::drawDialog(DrawLayer layerToDraw) {
Dialog::drawDialog(layerToDraw);
setTextDrawableArea(Common::Rect(_x, _y, _x + _w, _y + _h));
// Draw a border
//g_gui.hLine(_x, _y + _h - 1, _x + _w - 1, g_gui._color);
// Draw text
int y = _y + _padY;
for (int line = 0; (line < _linesPerPage) && ((_currentPos + line) < (int)_linesArray.size()); line++) {
int x = _x + _padX;
const char *text = _linesArray[line + _currentPos].c_str();
int w = MIN<int>(_lineWidth, _linesArray[line + _currentPos].size());
for (int column = 0; column < w; column++) {
byte c = text[column];
g_gui.theme()->drawChar(Common::Rect(x, y, x + _charWidth, y + _lineHeight), c, _font);
x += _charWidth;
}
y += _lineHeight;
}
// Draw the scrollbar
_scrollBar->_numEntries = _linesArray.size();
_scrollBar->_currentPos = _currentPos;
_scrollBar->_entriesPerPage = _linesPerPage;
_scrollBar->recalc();
_scrollBar->draw();
}
void TextViewerDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {
switch (cmd) {
case kSetPositionCmd:
_currentPos = _scrollBar->_currentPos;
if (_currentPos < 0) {
_currentPos = 0;
}
if ((_currentPos + _linesPerPage) >= (int)_linesArray.size()) {
_currentPos = MAX<int32>(_linesArray.size() - _linesPerPage, 0);
}
drawDialog(kDrawLayerForeground);
break;
case kCloseCmd:
close();
break;
#ifdef EMSCRIPTEN
case kDownloadFileCmd:
dynamic_cast<OSystem_Emscripten *>(g_system)->exportFile(_fname);
break;
#endif
default:
return;
}
}
void TextViewerDialog::handleMouseWheel(int x, int y, int direction) {
_scrollBar->handleMouseWheel(x, y, direction);
}
void TextViewerDialog::handleKeyDown(Common::KeyState state) {
switch (state.keycode) {
case Common::KEYCODE_ESCAPE:
close();
break;
case Common::KEYCODE_UP:
_currentPos--;
break;
case Common::KEYCODE_DOWN:
_currentPos++;
break;
case Common::KEYCODE_HOME:
_currentPos = 0;
break;
case Common::KEYCODE_END:
_currentPos = _linesArray.size() - _linesPerPage;
break;
case Common::KEYCODE_PAGEUP:
_currentPos -= _linesPerPage;
break;
case Common::KEYCODE_PAGEDOWN:
_currentPos += _linesPerPage;
break;
default:
return;
}
if (_currentPos < 0) {
_currentPos = 0;
}
if ((_currentPos + _linesPerPage) >= (int)_linesArray.size()) {
_currentPos = MAX<int32>(_linesArray.size() - _linesPerPage, 0);
}
drawDialog(kDrawLayerForeground);
}
} // End of namespace GUI

84
gui/textviewer.h Normal file
View File

@@ -0,0 +1,84 @@
/* 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/>.
*
*/
#ifndef TEXTVIEWER_DIALOG_H
#define TEXTVIEWER_DIALOG_H
#include "common/array.h"
#include "common/str.h"
#include "gui/dialog.h"
namespace Graphics {
class Font;
}
namespace GUI {
class ButtonWidget;
class ScrollBarWidget;
#ifdef EMSCRIPTEN
enum {
kDownloadFileCmd = 'dlfc',
};
#endif
class TextViewerDialog : public Dialog {
private:
int _lineWidth;
int _linesPerPage;
int _currentPos;
int _scrollLine;
int _charWidth;
int _lineHeight;
int _padX, _padY;
Common::StringArray _linesArray;
ScrollBarWidget *_scrollBar;
ButtonWidget *_closeButton;
#ifdef EMSCRIPTEN
ButtonWidget *_downloadButton;
#endif
Common::Path _fname;
const Graphics::Font *_font = nullptr;
bool loadFile(const Common::Path &fname);
void reflowLayout();
public:
TextViewerDialog(const Common::Path &fname);
~TextViewerDialog();
void destroy();
void open();
void drawDialog(DrawLayer layerToDraw);
void handleMouseWheel(int x, int y, int direction);
void handleKeyDown(Common::KeyState state);
void handleCommand(CommandSender *sender, uint32 cmd, uint32 data);
};
} // End of namespace GUI
#endif

120
gui/themebrowser.cpp Normal file
View File

@@ -0,0 +1,120 @@
/* 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 "gui/themebrowser.h"
#include "gui/widgets/list.h"
#include "gui/widget.h"
#include "gui/gui-manager.h"
#include "common/translation.h"
namespace GUI {
enum {
kChooseCmd = 'Chos'
};
// TODO: this is a rip off of GUI::Browser right now
// it will get some more output like theme name,
// theme style, theme preview(?) in the future
// but for now this simple browser works,
// also it will get its own theme config values
// and not use 'browser_' anymore
ThemeBrowser::ThemeBrowser() : Dialog("Browser") {
_fileList = nullptr;
new StaticTextWidget(this, "Browser.Headline", _("Select a Theme"));
// Add file list
_fileList = new ListWidget(this, "Browser.List");
_fileList->setNumberingMode(kListNumberingOff);
_fileList->setEditable(false);
_backgroundType = GUI::ThemeEngine::kDialogBackgroundPlain;
// Buttons
new ButtonWidget(this, "Browser.Cancel", _("Cancel"), Common::U32String(), kCloseCmd);
new ButtonWidget(this, "Browser.Choose", _("Choose"), Common::U32String(), kChooseCmd);
}
void ThemeBrowser::open() {
// Alway refresh file list
updateListing();
// Call super implementation
Dialog::open();
}
void ThemeBrowser::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {
switch (cmd) {
case kChooseCmd:
case kListItemActivatedCmd:
case kListItemDoubleClickedCmd: {
int selection = _fileList->getSelected();
if (selection < 0)
break;
// TODO:
// Currently ThemeEngine::listUseableThemes uses a
// list. Thus we can not use operator[] here but
// need to iterate through the list. We might want
// to think of changing it, but it should not be
// of high importance anyway.
ThemeDescList::const_iterator sel = _themes.begin();
for (int i = 0; i < selection; ++i)
++sel;
_select = sel->id;
_selectName = sel->name;
setResult(1);
close();
break;
}
default:
Dialog::handleCommand(sender, cmd, data);
}
}
void ThemeBrowser::updateListing() {
_themes.clear();
ThemeEngine::listUsableThemes(_themes);
const Common::String currentThemeId = g_gui.theme()->getThemeId();
int currentThemeIndex = 0, index = 0;
Common::U32StringArray list;
for (ThemeDescList::const_iterator i = _themes.begin(); i != _themes.end(); ++i, ++index) {
list.push_back(i->name);
if (i->id == currentThemeId)
currentThemeIndex = index;
}
_fileList->setList(list);
_fileList->scrollTo(0);
_fileList->setSelected(currentThemeIndex);
// Finally, redraw
g_gui.scheduleTopDialogRedraw();
}
} // End of namespace GUI

58
gui/themebrowser.h Normal file
View File

@@ -0,0 +1,58 @@
/* 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/>.
*
*/
#ifndef GUI_THEMEBROWSER_H
#define GUI_THEMEBROWSER_H
#include "gui/dialog.h"
#include "gui/ThemeEngine.h"
#include "common/str.h"
#include "common/list.h"
namespace GUI {
class CommandSender;
class ListWidget;
class ThemeBrowser : public Dialog {
public:
ThemeBrowser();
void open() override;
void handleCommand(CommandSender *sender, uint32 cmd, uint32 data) override;
const Common::String &getSelected() const { return _select; }
const Common::String &getSelectedName() const { return _selectName; }
private:
ListWidget *_fileList;
Common::String _select;
Common::String _selectName;
typedef Common::List<ThemeEngine::ThemeDescriptor> ThemeDescList;
ThemeDescList _themes;
void updateListing();
};
} // End of namespace GUI
#endif

Some files were not shown because too many files have changed in this diff Show More