Initial commit
This commit is contained in:
825
gui/EventRecorder.cpp
Normal file
825
gui/EventRecorder.cpp
Normal 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
259
gui/EventRecorder.h
Normal 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
87
gui/MetadataParser.cpp
Normal 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
149
gui/MetadataParser.h
Normal 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
2189
gui/ThemeEngine.cpp
Normal file
File diff suppressed because it is too large
Load Diff
837
gui/ThemeEngine.h
Normal file
837
gui/ThemeEngine.h
Normal 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
213
gui/ThemeEval.cpp
Normal 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
127
gui/ThemeEval.h
Normal 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
506
gui/ThemeLayout.cpp
Normal 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
353
gui/ThemeLayout.h
Normal 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
1177
gui/ThemeParser.cpp
Normal file
File diff suppressed because it is too large
Load Diff
292
gui/ThemeParser.h
Normal file
292
gui/ThemeParser.h
Normal 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
101
gui/Tooltip.cpp
Normal 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
79
gui/Tooltip.h
Normal 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
1480
gui/about.cpp
Normal file
File diff suppressed because it is too large
Load Diff
67
gui/about.h
Normal file
67
gui/about.h
Normal 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
|
||||
44
gui/animation/AccelerateInterpolator.h
Normal file
44
gui/animation/AccelerateInterpolator.h
Normal 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 */
|
||||
52
gui/animation/AlphaAnimation.h
Normal file
52
gui/animation/AlphaAnimation.h
Normal 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 */
|
||||
97
gui/animation/Animation.cpp
Normal file
97
gui/animation/Animation.cpp
Normal 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
75
gui/animation/Animation.h
Normal 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 */
|
||||
40
gui/animation/DeccelerateInterpolator.h
Normal file
40
gui/animation/DeccelerateInterpolator.h
Normal 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
108
gui/animation/Drawable.h
Normal 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 */
|
||||
43
gui/animation/Interpolator.h
Normal file
43
gui/animation/Interpolator.h
Normal 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 */
|
||||
71
gui/animation/ParallelAnimation.h
Normal file
71
gui/animation/ParallelAnimation.h
Normal 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 */
|
||||
51
gui/animation/RepeatAnimationWrapper.cpp
Normal file
51
gui/animation/RepeatAnimationWrapper.cpp
Normal 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
|
||||
60
gui/animation/RepeatAnimationWrapper.h
Normal file
60
gui/animation/RepeatAnimationWrapper.h
Normal 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 */
|
||||
68
gui/animation/ScaleAnimation.h
Normal file
68
gui/animation/ScaleAnimation.h
Normal 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 */
|
||||
71
gui/animation/SequenceAnimationComposite.cpp
Normal file
71
gui/animation/SequenceAnimationComposite.cpp
Normal 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
|
||||
50
gui/animation/SequenceAnimationComposite.h
Normal file
50
gui/animation/SequenceAnimationComposite.h
Normal 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 */
|
||||
70
gui/animation/WaitForConditionAnimation.h
Normal file
70
gui/animation/WaitForConditionAnimation.h
Normal 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
227
gui/browser.cpp
Normal 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
64
gui/browser.h
Normal 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
75
gui/chooser.cpp
Normal 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
54
gui/chooser.h
Normal 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
|
||||
774
gui/cloudconnectionwizard.cpp
Normal file
774
gui/cloudconnectionwizard.cpp
Normal 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
144
gui/cloudconnectionwizard.h
Normal 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
978
gui/console.cpp
Normal 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
221
gui/console.h
Normal 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
1574
gui/credits.h
Normal file
File diff suppressed because it is too large
Load Diff
877
gui/debugger.cpp
Normal file
877
gui/debugger.cpp
Normal 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, ¶m[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
294
gui/debugger.h
Normal 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
427
gui/dialog.cpp
Normal 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
134
gui/dialog.h
Normal 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
132
gui/dlcsdialog.cpp
Normal 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
56
gui/dlcsdialog.h
Normal 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
218
gui/downloaddialog.cpp
Normal 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
81
gui/downloaddialog.h
Normal 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
156
gui/downloaddlcsdialog.cpp
Normal 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
61
gui/downloaddlcsdialog.h
Normal 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
494
gui/downloadpacksdialog.cpp
Normal 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
90
gui/downloadpacksdialog.h
Normal 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
226
gui/dump-all-dialogs.cpp
Normal 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
38
gui/dump-all-dialogs.h
Normal 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
684
gui/editgamedialog.cpp
Normal 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
100
gui/editgamedialog.h
Normal 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
86
gui/editrecorddialog.cpp
Normal 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 ¬es) : 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
55
gui/editrecorddialog.h
Normal 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 ¬es);
|
||||
~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
41
gui/error.cpp
Normal 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
46
gui/error.h
Normal 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
163
gui/filebrowser-dialog.cpp
Normal 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
63
gui/filebrowser-dialog.h
Normal 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
360
gui/fluidsynth-dialog.cpp
Normal 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
104
gui/fluidsynth-dialog.h
Normal 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
993
gui/gui-manager.cpp
Normal 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
247
gui/gui-manager.h
Normal 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
159
gui/helpdialog.cpp
Normal 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 "
|
||||
" {w=70%,maxw=50em}\n"
|
||||
"\n"
|
||||
"3. Select **Quick mode**.\n"
|
||||
"\n "
|
||||
" {w=70%,maxw=50em}\n"
|
||||
"\n"
|
||||
"4. Select **Run server** and then select **Next** \n"
|
||||
"\n "
|
||||
" {w=70%,maxw=50em}\n"
|
||||
"\n"
|
||||
" {w=70%,maxw=50em}\n"
|
||||
"\n"
|
||||
"5. Open the link.\n"
|
||||
"\n "
|
||||
" {w=70%,maxw=50em}\n"
|
||||
"\n"
|
||||
"6. In the browser window that opens, select the cloud service to connect. \n"
|
||||
"\n "
|
||||
" {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 "
|
||||
" {w=70%,maxw=50em}\n"
|
||||
"9. Back on the main Cloud tab, select **Enable storage**.\n"
|
||||
"\n "
|
||||
" {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 "
|
||||
" {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
51
gui/helpdialog.h
Normal 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
462
gui/imagealbum-dialog.cpp
Normal 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
126
gui/imagealbum-dialog.h
Normal 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
738
gui/integrity-dialog.cpp
Normal 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
106
gui/integrity-dialog.h
Normal 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
1879
gui/launcher.cpp
Normal file
File diff suppressed because it is too large
Load Diff
293
gui/launcher.h
Normal file
293
gui/launcher.h
Normal 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
307
gui/massadd.cpp
Normal 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
99
gui/massadd.h
Normal 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
286
gui/message.cpp
Normal 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
136
gui/message.h
Normal 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
88
gui/module.mk
Normal 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
112
gui/object.cpp
Normal 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
114
gui/object.h
Normal 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
229
gui/onscreendialog.cpp
Normal 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
65
gui/onscreendialog.h
Normal 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
3875
gui/options.cpp
Normal file
File diff suppressed because it is too large
Load Diff
414
gui/options.h
Normal file
414
gui/options.h
Normal 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
1010
gui/predictivedialog.cpp
Normal file
File diff suppressed because it is too large
Load Diff
154
gui/predictivedialog.h
Normal file
154
gui/predictivedialog.h
Normal 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
373
gui/recorderdialog.cpp
Normal 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
86
gui/recorderdialog.h
Normal 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
229
gui/remotebrowser.cpp
Normal 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
83
gui/remotebrowser.h
Normal 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
1280
gui/saveload-dialog.cpp
Normal file
File diff suppressed because it is too large
Load Diff
265
gui/saveload-dialog.h
Normal file
265
gui/saveload-dialog.h
Normal 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
120
gui/saveload.cpp
Normal 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
74
gui/saveload.h
Normal 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
|
||||
201
gui/shaderbrowser-dialog.cpp
Normal file
201
gui/shaderbrowser-dialog.cpp
Normal 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
|
||||
69
gui/shaderbrowser-dialog.h
Normal file
69
gui/shaderbrowser-dialog.h
Normal 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
261
gui/textviewer.cpp
Normal 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
84
gui/textviewer.h
Normal 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
120
gui/themebrowser.cpp
Normal 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
58
gui/themebrowser.h
Normal 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
Reference in New Issue
Block a user