RECORDER: Implement Events Recorder
|
@ -20,6 +20,8 @@
|
|||
*
|
||||
*/
|
||||
|
||||
#include "gui/EventRecorder.h"
|
||||
|
||||
#include "common/util.h"
|
||||
#include "common/system.h"
|
||||
#include "common/textconsole.h"
|
||||
|
@ -427,6 +429,7 @@ void MixerImpl::pauseHandle(SoundHandle handle, bool paused) {
|
|||
|
||||
bool MixerImpl::isSoundIDActive(int id) {
|
||||
Common::StackLock lock(_mutex);
|
||||
g_eventRec.updateSubsystems();
|
||||
for (int i = 0; i != NUM_CHANNELS; i++)
|
||||
if (_channels[i] && _channels[i]->getId() == id)
|
||||
return true;
|
||||
|
@ -443,6 +446,7 @@ int MixerImpl::getSoundID(SoundHandle handle) {
|
|||
|
||||
bool MixerImpl::isSoundHandleActive(SoundHandle handle) {
|
||||
Common::StackLock lock(_mutex);
|
||||
g_eventRec.updateSubsystems();
|
||||
const int index = handle._val % NUM_CHANNELS;
|
||||
return _channels[index] && _channels[index]->getHandle()._val == handle._val;
|
||||
}
|
||||
|
@ -556,12 +560,12 @@ void Channel::pause(bool paused) {
|
|||
_pauseLevel++;
|
||||
|
||||
if (_pauseLevel == 1)
|
||||
_pauseStartTime = g_system->getMillis();
|
||||
_pauseStartTime = g_system->getMillis(true);
|
||||
} else if (_pauseLevel > 0) {
|
||||
_pauseLevel--;
|
||||
|
||||
if (!_pauseLevel) {
|
||||
_pauseTime = (g_system->getMillis() - _pauseStartTime);
|
||||
_pauseTime = (g_system->getMillis(true) - _pauseStartTime);
|
||||
_pauseStartTime = 0;
|
||||
}
|
||||
}
|
||||
|
@ -579,7 +583,7 @@ Timestamp Channel::getElapsedTime() {
|
|||
if (isPaused())
|
||||
delta = _pauseStartTime - _mixerTimeStamp;
|
||||
else
|
||||
delta = g_system->getMillis() - _mixerTimeStamp - _pauseTime;
|
||||
delta = g_system->getMillis(true) - _mixerTimeStamp - _pauseTime;
|
||||
|
||||
// Convert the number of samples into a time duration.
|
||||
|
||||
|
@ -599,13 +603,12 @@ int Channel::mix(int16 *data, uint len) {
|
|||
assert(_stream);
|
||||
|
||||
int res = 0;
|
||||
|
||||
if (_stream->endOfData()) {
|
||||
// TODO: call drain method
|
||||
} else {
|
||||
assert(_converter);
|
||||
_samplesConsumed = _samplesDecoded;
|
||||
_mixerTimeStamp = g_system->getMillis();
|
||||
_mixerTimeStamp = g_system->getMillis(true);
|
||||
_pauseTime = 0;
|
||||
res = _converter->flow(*_stream, data, len, _volL, _volR);
|
||||
_samplesDecoded += res;
|
||||
|
|
|
@ -84,7 +84,8 @@ void DefaultEventManager::init() {
|
|||
}
|
||||
|
||||
bool DefaultEventManager::pollEvent(Common::Event &event) {
|
||||
uint32 time = g_system->getMillis();
|
||||
// Skip recording of these events
|
||||
uint32 time = g_system->getMillis(true);
|
||||
bool result = false;
|
||||
|
||||
_dispatcher.dispatch();
|
||||
|
|
|
@ -106,7 +106,9 @@ void SdlEventSource::processMouseEvent(Common::Event &event, int x, int y) {
|
|||
}
|
||||
|
||||
void SdlEventSource::handleKbdMouse() {
|
||||
uint32 curTime = g_system->getMillis();
|
||||
// Skip recording of these events
|
||||
uint32 curTime = g_system->getMillis(true);
|
||||
|
||||
if (curTime >= _km.last_time + _km.delay_time) {
|
||||
_km.last_time = curTime;
|
||||
if (_km.x_down_count == 1) {
|
||||
|
|
|
@ -40,6 +40,7 @@
|
|||
#include "graphics/scaler.h"
|
||||
#include "graphics/scaler/aspect.h"
|
||||
#include "graphics/surface.h"
|
||||
#include "gui/EventRecorder.h"
|
||||
|
||||
static const OSystem::GraphicsMode s_supportedGraphicsModes[] = {
|
||||
{"1x", _s("Normal (no scaling)"), GFX_NORMAL},
|
||||
|
@ -135,6 +136,7 @@ SurfaceSdlGraphicsManager::SurfaceSdlGraphicsManager(SdlEventSource *sdlEventSou
|
|||
_paletteDirtyStart(0), _paletteDirtyEnd(0),
|
||||
_screenIsLocked(false),
|
||||
_graphicsMutex(0),
|
||||
_displayDisabled(false),
|
||||
#ifdef USE_SDL_DEBUG_FOCUSRECT
|
||||
_enableFocusRectDebugCode(false), _enableFocusRect(false), _focusRect(),
|
||||
#endif
|
||||
|
@ -765,9 +767,20 @@ bool SurfaceSdlGraphicsManager::loadGFXMode() {
|
|||
fixupResolutionForAspectRatio(_videoMode.desiredAspectRatio, _videoMode.hardwareWidth, _videoMode.hardwareHeight);
|
||||
}
|
||||
|
||||
_hwscreen = SDL_SetVideoMode(_videoMode.hardwareWidth, _videoMode.hardwareHeight, 16,
|
||||
_videoMode.fullscreen ? (SDL_FULLSCREEN|SDL_SWSURFACE) : SDL_SWSURFACE
|
||||
);
|
||||
|
||||
#ifdef ENABLE_KEYMAPPER
|
||||
_displayDisabled = ConfMan.getBool("disable_display");
|
||||
|
||||
if (_displayDisabled) {
|
||||
_hwscreen = g_eventRec.getSurface(_videoMode.hardwareWidth, _videoMode.hardwareHeight);
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
_hwscreen = SDL_SetVideoMode(_videoMode.hardwareWidth, _videoMode.hardwareHeight, 16,
|
||||
_videoMode.fullscreen ? (SDL_FULLSCREEN|SDL_SWSURFACE) : SDL_SWSURFACE
|
||||
);
|
||||
}
|
||||
|
||||
#ifdef USE_RGB_COLOR
|
||||
detectSupportedFormats();
|
||||
#endif
|
||||
|
@ -865,7 +878,12 @@ void SurfaceSdlGraphicsManager::unloadGFXMode() {
|
|||
}
|
||||
|
||||
if (_hwscreen) {
|
||||
SDL_FreeSurface(_hwscreen);
|
||||
if (_displayDisabled) {
|
||||
delete _hwscreen;
|
||||
} else {
|
||||
SDL_FreeSurface(_hwscreen);
|
||||
}
|
||||
|
||||
_hwscreen = NULL;
|
||||
}
|
||||
|
||||
|
@ -1188,7 +1206,9 @@ void SurfaceSdlGraphicsManager::internUpdateScreen() {
|
|||
#endif
|
||||
|
||||
// Finally, blit all our changes to the screen
|
||||
SDL_UpdateRects(_hwscreen, _numDirtyRects, _dirtyRectList);
|
||||
if (!_displayDisabled) {
|
||||
SDL_UpdateRects(_hwscreen, _numDirtyRects, _dirtyRectList);
|
||||
}
|
||||
}
|
||||
|
||||
_numDirtyRects = 0;
|
||||
|
|
|
@ -232,6 +232,9 @@ protected:
|
|||
int _scalerType;
|
||||
int _transactionMode;
|
||||
|
||||
// Indicates whether it is needed to free _hwsurface in destructor
|
||||
bool _displayDisabled;
|
||||
|
||||
bool _screenIsLocked;
|
||||
Graphics::Surface _framebuffer;
|
||||
|
||||
|
|
75
backends/mixer/nullmixer/nullsdl-mixer.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 2
|
||||
* of the License, or (at your option) any later version.
|
||||
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "backends/mixer/nullmixer/nullsdl-mixer.h"
|
||||
#include "common/savefile.h"
|
||||
|
||||
NullSdlMixerManager::NullSdlMixerManager() : SdlMixerManager() {
|
||||
_outputRate = 22050;
|
||||
_callsCounter = 0;
|
||||
_callbackPeriod = 10;
|
||||
_samples = 8192;
|
||||
while (_samples * 16 > _outputRate * 2)
|
||||
_samples >>= 1;
|
||||
_samplesBuf = new uint8[_samples * 4];
|
||||
}
|
||||
|
||||
NullSdlMixerManager::~NullSdlMixerManager() {
|
||||
delete _samplesBuf;
|
||||
}
|
||||
|
||||
void NullSdlMixerManager::init() {
|
||||
_mixer = new Audio::MixerImpl(g_system, _outputRate);
|
||||
assert(_mixer);
|
||||
_mixer->setReady(true);
|
||||
}
|
||||
|
||||
void NullSdlMixerManager::suspendAudio() {
|
||||
_audioSuspended = true;
|
||||
}
|
||||
|
||||
int NullSdlMixerManager::resumeAudio() {
|
||||
if (!_audioSuspended) {
|
||||
return -2;
|
||||
}
|
||||
_audioSuspended = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void NullSdlMixerManager::startAudio() {
|
||||
}
|
||||
|
||||
void NullSdlMixerManager::callbackHandler(byte *samples, int len) {
|
||||
assert(_mixer);
|
||||
_mixer->mixCallback(samples, len);
|
||||
}
|
||||
|
||||
void NullSdlMixerManager::update() {
|
||||
if (_audioSuspended) {
|
||||
return;
|
||||
}
|
||||
_callsCounter++;
|
||||
if ((_callsCounter % _callbackPeriod) == 0) {
|
||||
callbackHandler(_samplesBuf, _samples);
|
||||
}
|
||||
}
|
62
backends/mixer/nullmixer/nullsdl-mixer.h
Normal file
|
@ -0,0 +1,62 @@
|
|||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef BACKENDS_MIXER_NULLSDL_H
|
||||
#define BACKENDS_MIXER_NULLSDL_H
|
||||
|
||||
#include "backends/mixer/sdl/sdl-mixer.h"
|
||||
#include "common/str.h"
|
||||
|
||||
/** Audio mixer which in fact does not output audio.
|
||||
*
|
||||
* It is used by events recorder since the recorder is intentionally
|
||||
* turning sound off to avoid stuttering.
|
||||
*
|
||||
* It returns correct output and shoots callbacks, so all OSystem
|
||||
* users could work without modifications.
|
||||
*/
|
||||
|
||||
class NullSdlMixerManager : public SdlMixerManager {
|
||||
public:
|
||||
NullSdlMixerManager();
|
||||
virtual ~NullSdlMixerManager();
|
||||
|
||||
virtual void init();
|
||||
void update();
|
||||
|
||||
virtual void suspendAudio();
|
||||
virtual int resumeAudio();
|
||||
|
||||
protected:
|
||||
|
||||
virtual void startAudio();
|
||||
virtual void callbackHandler(byte *samples, int len);
|
||||
|
||||
private:
|
||||
uint32 _outputRate;
|
||||
uint32 _callsCounter;
|
||||
uint8 _callbackPeriod;
|
||||
uint32 _samples;
|
||||
uint8 *_samplesBuf;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -26,6 +26,7 @@
|
|||
|
||||
#include "backends/graphics/graphics.h"
|
||||
#include "backends/mutex/mutex.h"
|
||||
#include "gui/EventRecorder.h"
|
||||
|
||||
#include "audio/mixer.h"
|
||||
#include "graphics/pixelformat.h"
|
||||
|
@ -141,7 +142,9 @@ void ModularBackend::fillScreen(uint32 col) {
|
|||
}
|
||||
|
||||
void ModularBackend::updateScreen() {
|
||||
g_eventRec.preDrawOverlayGui();
|
||||
_graphicsManager->updateScreen();
|
||||
g_eventRec.postDrawOverlayGui();
|
||||
}
|
||||
|
||||
void ModularBackend::setShakePos(int shakeOffset) {
|
||||
|
|
|
@ -214,5 +214,11 @@ MODULE_OBJS += \
|
|||
plugins/wii/wii-provider.o
|
||||
endif
|
||||
|
||||
ifdef ENABLE_EVENTRECORDER
|
||||
MODULE_OBJS += \
|
||||
mixer/nullmixer/nullsdl-mixer.o \
|
||||
saves/recorder/recorder-saves.o
|
||||
endif
|
||||
|
||||
# Include common rules
|
||||
include $(srcdir)/rules.mk
|
||||
|
|
|
@ -33,15 +33,15 @@ OSystem::MutexRef SdlMutexManager::createMutex() {
|
|||
}
|
||||
|
||||
void SdlMutexManager::lockMutex(OSystem::MutexRef mutex) {
|
||||
SDL_mutexP((SDL_mutex *) mutex);
|
||||
SDL_mutexP((SDL_mutex *)mutex);
|
||||
}
|
||||
|
||||
void SdlMutexManager::unlockMutex(OSystem::MutexRef mutex) {
|
||||
SDL_mutexV((SDL_mutex *) mutex);
|
||||
SDL_mutexV((SDL_mutex *)mutex);
|
||||
}
|
||||
|
||||
void SdlMutexManager::deleteMutex(OSystem::MutexRef mutex) {
|
||||
SDL_DestroyMutex((SDL_mutex *) mutex);
|
||||
SDL_DestroyMutex((SDL_mutex *)mutex);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -450,7 +450,7 @@ bool OSystem_Android::getFeatureState(Feature f) {
|
|||
}
|
||||
}
|
||||
|
||||
uint32 OSystem_Android::getMillis() {
|
||||
uint32 OSystem_Android::getMillis(bool skipRecord) {
|
||||
timeval curTime;
|
||||
|
||||
gettimeofday(&curTime, 0);
|
||||
|
|
|
@ -274,7 +274,7 @@ public:
|
|||
virtual void setCursorPalette(const byte *colors, uint start, uint num);
|
||||
|
||||
virtual bool pollEvent(Common::Event &event);
|
||||
virtual uint32 getMillis();
|
||||
virtual uint32 getMillis(bool skipRecord = false);
|
||||
virtual void delayMillis(uint msecs);
|
||||
|
||||
virtual MutexRef createMutex(void);
|
||||
|
|
|
@ -373,7 +373,7 @@ bool BadaSystem::pollEvent(Common::Event &event) {
|
|||
return _appForm->pollEvent(event);
|
||||
}
|
||||
|
||||
uint32 BadaSystem::getMillis() {
|
||||
uint32 BadaSystem::getMillis(bool skipRecord) {
|
||||
long long result, ticks = 0;
|
||||
SystemTime::GetTicks(ticks);
|
||||
result = ticks - _epoch;
|
||||
|
|
|
@ -83,7 +83,7 @@ private:
|
|||
|
||||
void updateScreen();
|
||||
bool pollEvent(Common::Event &event);
|
||||
uint32 getMillis();
|
||||
uint32 getMillis(bool skipRecord = false);
|
||||
void delayMillis(uint msecs);
|
||||
void getTimeAndDate(TimeDate &t) const;
|
||||
void fatalError();
|
||||
|
|
|
@ -151,7 +151,7 @@ public:
|
|||
void setShakePos(int shake_pos);
|
||||
|
||||
// Get the number of milliseconds since the program was started.
|
||||
uint32 getMillis();
|
||||
uint32 getMillis(bool skipRecord = false);
|
||||
|
||||
// Delay for a specified amount of milliseconds
|
||||
void delayMillis(uint msecs);
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
#include "dc.h"
|
||||
|
||||
|
||||
uint32 OSystem_Dreamcast::getMillis()
|
||||
uint32 OSystem_Dreamcast::getMillis(bool skipRecord)
|
||||
{
|
||||
static uint32 msecs=0;
|
||||
static unsigned int t0=0;
|
||||
|
|
|
@ -2280,7 +2280,7 @@ void VBlankHandler(void) {
|
|||
//REG_IF = IRQ_VBLANK;
|
||||
}
|
||||
|
||||
int getMillis() {
|
||||
int getMillis(bool skipRecord) {
|
||||
return currentTimeMillis;
|
||||
// return frameCount * FRAME_TIME;
|
||||
}
|
||||
|
|
|
@ -88,7 +88,7 @@ void setGamma(int gamma);
|
|||
|
||||
// Timers
|
||||
void setTimerCallback(OSystem_DS::TimerProc proc, int interval); // Setup a callback function at a regular interval
|
||||
int getMillis(); // Return the current runtime in milliseconds
|
||||
int getMillis(bool skipRecord = false); // Return the current runtime in milliseconds
|
||||
void doTimerCallback(); // Call callback function if required
|
||||
|
||||
// Sound
|
||||
|
|
|
@ -656,7 +656,7 @@ bool OSystem_DS::pollEvent(Common::Event &event) {
|
|||
return false;
|
||||
}
|
||||
|
||||
uint32 OSystem_DS::getMillis() {
|
||||
uint32 OSystem_DS::getMillis(bool skipRecord) {
|
||||
return DS::getMillis();
|
||||
}
|
||||
|
||||
|
|
|
@ -117,7 +117,7 @@ public:
|
|||
virtual void setMouseCursor(const void *buf, uint w, uint h, int hotspotX, int hotspotY, u32 keycolor, bool dontScale, const Graphics::PixelFormat *format);
|
||||
|
||||
virtual bool pollEvent(Common::Event &event);
|
||||
virtual uint32 getMillis();
|
||||
virtual uint32 getMillis(bool skipRecord = false);
|
||||
virtual void delayMillis(uint msecs);
|
||||
virtual void getTimeAndDate(TimeDate &t) const;
|
||||
|
||||
|
|
|
@ -166,7 +166,7 @@ void OSystem_IPHONE::suspendLoop() {
|
|||
_timeSuspended += getMillis() - startTime;
|
||||
}
|
||||
|
||||
uint32 OSystem_IPHONE::getMillis() {
|
||||
uint32 OSystem_IPHONE::getMillis(bool skipRecord) {
|
||||
//printf("getMillis()\n");
|
||||
|
||||
struct timeval currentTime;
|
||||
|
|
|
@ -165,7 +165,7 @@ public:
|
|||
virtual void setCursorPalette(const byte *colors, uint start, uint num);
|
||||
|
||||
virtual bool pollEvent(Common::Event &event);
|
||||
virtual uint32 getMillis();
|
||||
virtual uint32 getMillis(bool skipRecord = false);
|
||||
virtual void delayMillis(uint msecs);
|
||||
|
||||
virtual MutexRef createMutex(void);
|
||||
|
|
|
@ -184,7 +184,7 @@ public:
|
|||
virtual void setCursorPalette(const byte *colors, uint start, uint num);
|
||||
|
||||
virtual bool pollEvent(Common::Event &event);
|
||||
virtual uint32 getMillis();
|
||||
virtual uint32 getMillis(bool skipRecord = false);
|
||||
virtual void delayMillis(uint msecs);
|
||||
|
||||
virtual MutexRef createMutex(void);
|
||||
|
|
|
@ -810,7 +810,7 @@ void OSystem_N64::setMouseCursor(const void *buf, uint w, uint h, int hotspotX,
|
|||
return;
|
||||
}
|
||||
|
||||
uint32 OSystem_N64::getMillis() {
|
||||
uint32 OSystem_N64::getMillis(bool skipRecord) {
|
||||
return getMilliTick();
|
||||
}
|
||||
|
||||
|
|
|
@ -49,7 +49,7 @@ public:
|
|||
|
||||
virtual bool pollEvent(Common::Event &event);
|
||||
|
||||
virtual uint32 getMillis();
|
||||
virtual uint32 getMillis(bool skipRecord = false);
|
||||
virtual void delayMillis(uint msecs);
|
||||
virtual void getTimeAndDate(TimeDate &t) const {}
|
||||
|
||||
|
|
|
@ -571,7 +571,7 @@ void OSystem_PS2::displayMessageOnOSD(const char *msg) {
|
|||
printf("displayMessageOnOSD: %s\n", msg);
|
||||
}
|
||||
|
||||
uint32 OSystem_PS2::getMillis(void) {
|
||||
uint32 OSystem_PS2::getMillis(bool skipRecord) {
|
||||
return msecCount;
|
||||
}
|
||||
|
||||
|
|
|
@ -82,7 +82,7 @@ public:
|
|||
virtual void warpMouse(int x, int y);
|
||||
virtual void setMouseCursor(const void *buf, uint w, uint h, int hotspot_x, int hotspot_y, uint32 keycolor, bool dontScale = false, const Graphics::PixelFormat *format = 0);
|
||||
|
||||
virtual uint32 getMillis();
|
||||
virtual uint32 getMillis(bool skipRecord = false);
|
||||
virtual void delayMillis(uint msecs);
|
||||
virtual bool pollEvent(Common::Event &event);
|
||||
|
||||
|
|
|
@ -349,7 +349,7 @@ bool OSystem_PSP::pollEvent(Common::Event &event) {
|
|||
return _inputHandler.getAllInputs(event);
|
||||
}
|
||||
|
||||
uint32 OSystem_PSP::getMillis() {
|
||||
uint32 OSystem_PSP::getMillis(bool skipRecord) {
|
||||
return PspRtc::instance().getMillis();
|
||||
}
|
||||
|
||||
|
|
|
@ -125,7 +125,7 @@ public:
|
|||
bool processInput(Common::Event &event);
|
||||
|
||||
// Time
|
||||
uint32 getMillis();
|
||||
uint32 getMillis(bool skipRecord = false);
|
||||
void delayMillis(uint msecs);
|
||||
|
||||
// Timer
|
||||
|
|
|
@ -52,7 +52,7 @@ void PspRtc::init() { // init our starting ticks
|
|||
|
||||
// Note that after we fill up 32 bits ie 50 days we'll loop back to 0, which may cause
|
||||
// unpredictable results
|
||||
uint32 PspRtc::getMillis() {
|
||||
uint32 PspRtc::getMillis(bool skipRecord) {
|
||||
uint32 ticks[2];
|
||||
|
||||
sceRtcGetCurrentTick((u64 *)ticks); // can introduce weird thread delays
|
||||
|
|
|
@ -40,7 +40,7 @@ public:
|
|||
init();
|
||||
}
|
||||
void init();
|
||||
uint32 getMillis();
|
||||
uint32 getMillis(bool skipRecord = false);
|
||||
uint32 getMicros();
|
||||
};
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
|
||||
#include "backends/platform/sdl/sdl.h"
|
||||
#include "common/config-manager.h"
|
||||
#include "common/EventRecorder.h"
|
||||
#include "gui/EventRecorder.h"
|
||||
#include "common/taskbar.h"
|
||||
#include "common/textconsole.h"
|
||||
|
||||
|
@ -97,7 +97,9 @@ OSystem_SDL::~OSystem_SDL() {
|
|||
_audiocdManager = 0;
|
||||
delete _mixerManager;
|
||||
_mixerManager = 0;
|
||||
delete _timerManager;
|
||||
|
||||
delete g_eventRec.getTimerManager();
|
||||
|
||||
_timerManager = 0;
|
||||
delete _mutexManager;
|
||||
_mutexManager = 0;
|
||||
|
@ -131,9 +133,6 @@ void OSystem_SDL::init() {
|
|||
if (_mutexManager == 0)
|
||||
_mutexManager = new SdlMutexManager();
|
||||
|
||||
if (_timerManager == 0)
|
||||
_timerManager = new SdlTimerManager();
|
||||
|
||||
#if defined(USE_TASKBAR)
|
||||
if (_taskbarManager == 0)
|
||||
_taskbarManager = new Common::TaskbarManager();
|
||||
|
@ -191,10 +190,12 @@ void OSystem_SDL::initBackend() {
|
|||
|
||||
if (_mixerManager == 0) {
|
||||
_mixerManager = new SdlMixerManager();
|
||||
|
||||
// Setup and start mixer
|
||||
_mixerManager->init();
|
||||
}
|
||||
g_eventRec.registerMixerManager(_mixerManager);
|
||||
|
||||
g_eventRec.registerTimerManager(new SdlTimerManager());
|
||||
|
||||
if (_audiocdManager == 0) {
|
||||
// Audio CD support was removed with SDL 1.3
|
||||
|
@ -466,14 +467,15 @@ void OSystem_SDL::setupIcon() {
|
|||
free(icon);
|
||||
}
|
||||
|
||||
uint32 OSystem_SDL::getMillis() {
|
||||
|
||||
uint32 OSystem_SDL::getMillis(bool skipRecord) {
|
||||
uint32 millis = SDL_GetTicks();
|
||||
g_eventRec.processMillis(millis);
|
||||
g_eventRec.processMillis(millis, skipRecord);
|
||||
return millis;
|
||||
}
|
||||
|
||||
void OSystem_SDL::delayMillis(uint msecs) {
|
||||
if (!g_eventRec.processDelayMillis(msecs))
|
||||
if (!g_eventRec.processDelayMillis())
|
||||
SDL_Delay(msecs);
|
||||
}
|
||||
|
||||
|
@ -491,12 +493,12 @@ void OSystem_SDL::getTimeAndDate(TimeDate &td) const {
|
|||
|
||||
Audio::Mixer *OSystem_SDL::getMixer() {
|
||||
assert(_mixerManager);
|
||||
return _mixerManager->getMixer();
|
||||
return getMixerManager()->getMixer();
|
||||
}
|
||||
|
||||
SdlMixerManager *OSystem_SDL::getMixerManager() {
|
||||
assert(_mixerManager);
|
||||
return _mixerManager;
|
||||
return g_eventRec.getMixerManager();
|
||||
}
|
||||
|
||||
#ifdef USE_OPENGL
|
||||
|
@ -654,4 +656,8 @@ void OSystem_SDL::setupGraphicsModes() {
|
|||
}
|
||||
}
|
||||
|
||||
Common::TimerManager *OSystem_SDL::getTimerManager() {
|
||||
return g_eventRec.getTimerManager();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -68,10 +68,11 @@ public:
|
|||
|
||||
virtual void setWindowCaption(const char *caption);
|
||||
virtual void addSysArchivesToSearchSet(Common::SearchSet &s, int priority = 0);
|
||||
virtual uint32 getMillis();
|
||||
virtual uint32 getMillis(bool skipRecord = false);
|
||||
virtual void delayMillis(uint msecs);
|
||||
virtual void getTimeAndDate(TimeDate &td) const;
|
||||
virtual Audio::Mixer *getMixer();
|
||||
virtual Common::TimerManager *getTimerManager();
|
||||
|
||||
protected:
|
||||
bool _inited;
|
||||
|
|
|
@ -203,7 +203,7 @@ bool OSystem_Wii::getFeatureState(Feature f) {
|
|||
}
|
||||
}
|
||||
|
||||
uint32 OSystem_Wii::getMillis() {
|
||||
uint32 OSystem_Wii::getMillis(bool skipRecord) {
|
||||
return ticks_to_millisecs(diff_ticks(_startup_time, gettime()));
|
||||
}
|
||||
|
||||
|
|
|
@ -193,7 +193,7 @@ public:
|
|||
const Graphics::PixelFormat *format);
|
||||
|
||||
virtual bool pollEvent(Common::Event &event);
|
||||
virtual uint32 getMillis();
|
||||
virtual uint32 getMillis(bool skipRecord = false);
|
||||
virtual void delayMillis(uint msecs);
|
||||
|
||||
virtual MutexRef createMutex();
|
||||
|
|
35
backends/saves/recorder/recorder-saves.cpp
Normal file
|
@ -0,0 +1,35 @@
|
|||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "backends/saves/recorder/recorder-saves.h"
|
||||
#include "gui/EventRecorder.h"
|
||||
#include "common/savefile.h"
|
||||
|
||||
Common::InSaveFile *RecorderSaveFileManager::openForLoading(const Common::String &filename) {
|
||||
Common::InSaveFile *result = g_eventRec.processSaveStream(filename);
|
||||
return result;
|
||||
}
|
||||
|
||||
Common::StringArray RecorderSaveFileManager::listSaveFiles(const Common::String &pattern) {
|
||||
return g_eventRec.listSaveFiles(pattern);
|
||||
}
|
||||
|
36
backends/saves/recorder/recorder-saves.h
Normal file
|
@ -0,0 +1,36 @@
|
|||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef BACKEND_SAVES_RECORDER_H
|
||||
#define BACKEND_SAVES_RECORDER_H
|
||||
|
||||
#include "backends/saves/default/default-saves.h"
|
||||
|
||||
/**
|
||||
* Provides a savefile manager implementation for event recorder.
|
||||
*/
|
||||
class RecorderSaveFileManager : public DefaultSaveFileManager {
|
||||
virtual Common::StringArray listSaveFiles(const Common::String &pattern);
|
||||
virtual Common::InSaveFile *openForLoading(const Common::String &filename);
|
||||
};
|
||||
|
||||
#endif
|
|
@ -80,7 +80,7 @@ DefaultTimerManager::~DefaultTimerManager() {
|
|||
void DefaultTimerManager::handler() {
|
||||
Common::StackLock lock(_mutex);
|
||||
|
||||
const uint32 curTime = g_system->getMillis();
|
||||
uint32 curTime = g_system->getMillis(true);
|
||||
|
||||
// Repeat as long as there is a TimerSlot that is scheduled to fire.
|
||||
TimerSlot *slot = _head->next;
|
||||
|
|
|
@ -118,6 +118,13 @@ static const char HELP_STRING[] =
|
|||
" --aspect-ratio Enable aspect ratio correction\n"
|
||||
" --render-mode=MODE Enable additional render modes (cga, ega, hercGreen,\n"
|
||||
" hercAmber, amiga)\n"
|
||||
#ifdef ENABLE_EVENTRECORDER
|
||||
" --record-mode=MODE Specify record mode for event recorder (record, playback,\n"
|
||||
" passthrough [default])\n"
|
||||
" --record-file=FILE Specify record file name\n"
|
||||
" --disable-display Disable any gfx output. Used for headless events\n"
|
||||
" playback by Event Recorder\n"
|
||||
#endif
|
||||
"\n"
|
||||
#if defined(ENABLE_SKY) || defined(ENABLE_QUEEN)
|
||||
" --alt-intro Use alternative intro for CD versions of Beneath a\n"
|
||||
|
@ -232,10 +239,9 @@ void registerDefaults() {
|
|||
ConfMan.registerDefault("confirm_exit", false);
|
||||
ConfMan.registerDefault("disable_sdl_parachute", false);
|
||||
|
||||
ConfMan.registerDefault("disable_display", false);
|
||||
ConfMan.registerDefault("record_mode", "none");
|
||||
ConfMan.registerDefault("record_file_name", "record.bin");
|
||||
ConfMan.registerDefault("record_temp_file_name", "record.tmp");
|
||||
ConfMan.registerDefault("record_time_file_name", "record.time");
|
||||
|
||||
ConfMan.registerDefault("gui_saveload_chooser", "grid");
|
||||
ConfMan.registerDefault("gui_saveload_last_pos", "0");
|
||||
|
@ -424,6 +430,17 @@ Common::String parseCommandLine(Common::StringMap &settings, int argc, const cha
|
|||
DO_OPTION_BOOL('f', "fullscreen")
|
||||
END_OPTION
|
||||
|
||||
#ifdef ENABLE_EVENTRECORDER
|
||||
DO_LONG_OPTION_INT("disable-display")
|
||||
END_OPTION
|
||||
|
||||
DO_LONG_OPTION("record-mode")
|
||||
END_OPTION
|
||||
|
||||
DO_LONG_OPTION("record-file-name")
|
||||
END_OPTION
|
||||
#endif
|
||||
|
||||
DO_LONG_OPTION("opl-driver")
|
||||
END_OPTION
|
||||
|
||||
|
@ -569,18 +586,6 @@ Common::String parseCommandLine(Common::StringMap &settings, int argc, const cha
|
|||
END_OPTION
|
||||
#endif
|
||||
|
||||
DO_LONG_OPTION("record-mode")
|
||||
END_OPTION
|
||||
|
||||
DO_LONG_OPTION("record-file-name")
|
||||
END_OPTION
|
||||
|
||||
DO_LONG_OPTION("record-temp-file-name")
|
||||
END_OPTION
|
||||
|
||||
DO_LONG_OPTION("record-time-file-name")
|
||||
END_OPTION
|
||||
|
||||
#ifdef IPHONE
|
||||
// This is automatically set when launched from the Springboard.
|
||||
DO_LONG_OPTION_OPT("launchedFromSB", 0)
|
||||
|
|
|
@ -42,8 +42,11 @@
|
|||
#include "common/debug.h"
|
||||
#include "common/debug-channels.h" /* for debug manager */
|
||||
#include "common/events.h"
|
||||
#include "common/EventRecorder.h"
|
||||
#include "gui/EventRecorder.h"
|
||||
#include "common/fs.h"
|
||||
#ifdef ENABLE_EVENTRECORDER
|
||||
#include "common/recorderfile.h"
|
||||
#endif
|
||||
#include "common/system.h"
|
||||
#include "common/textconsole.h"
|
||||
#include "common/tokenizer.h"
|
||||
|
@ -409,7 +412,9 @@ extern "C" int scummvm_main(int argc, const char * const argv[]) {
|
|||
settings["gfx-mode"] = "default";
|
||||
}
|
||||
}
|
||||
|
||||
if (settings.contains("disable-display")) {
|
||||
ConfMan.setInt("disable-display", 1, Common::ConfigManager::kTransientDomain);
|
||||
}
|
||||
setupGraphics(system);
|
||||
|
||||
// Init the different managers that are used by the engines.
|
||||
|
@ -428,7 +433,7 @@ extern "C" int scummvm_main(int argc, const char * const argv[]) {
|
|||
// TODO: This is just to match the current behavior, when we further extend
|
||||
// our event recorder, we might do this at another place. Or even change
|
||||
// the whole API for that ;-).
|
||||
g_eventRec.init();
|
||||
g_eventRec.RegisterEventSource();
|
||||
|
||||
// Now as the event manager is created, setup the keymapper
|
||||
setupKeymapper(system);
|
||||
|
@ -448,6 +453,21 @@ extern "C" int scummvm_main(int argc, const char * const argv[]) {
|
|||
// to save memory
|
||||
PluginManager::instance().unloadPluginsExcept(PLUGIN_TYPE_ENGINE, plugin);
|
||||
|
||||
#ifdef ENABLE_EVENTRECORDER
|
||||
Common::String recordMode = ConfMan.get("record_mode");
|
||||
Common::String recordFileName = ConfMan.get("record_file_name");
|
||||
|
||||
if (recordMode == "record") {
|
||||
g_eventRec.init(g_eventRec.generateRecordFileName(ConfMan.getActiveDomainName()), GUI::EventRecorder::kRecorderRecord);
|
||||
} else if (recordMode == "playback") {
|
||||
g_eventRec.init(recordFileName, GUI::EventRecorder::kRecorderPlayback);
|
||||
} else if ((recordMode == "info") && (!recordFileName.empty())) {
|
||||
Common::PlaybackFile record;
|
||||
record.openRead(recordFileName);
|
||||
debug("info:author=%s name=%s description=%s", record.getHeader().author.c_str(), record.getHeader().name.c_str(), record.getHeader().description.c_str());
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
// Try to run the game
|
||||
Common::Error result = runGame(plugin, system, specialDebug);
|
||||
|
||||
|
@ -478,6 +498,11 @@ extern "C" int scummvm_main(int argc, const char * const argv[]) {
|
|||
#ifdef FORCE_RTL
|
||||
g_system->getEventManager()->resetQuit();
|
||||
#endif
|
||||
#ifdef ENABLE_EVENTRECORDER
|
||||
if (g_eventRec.checkForContinueGame()) {
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Discard any command line options. It's unlikely that the user
|
||||
// wanted to apply them to *all* games ever launched.
|
||||
|
@ -501,7 +526,7 @@ extern "C" int scummvm_main(int argc, const char * const argv[]) {
|
|||
GUI::GuiManager::destroy();
|
||||
Common::ConfigManager::destroy();
|
||||
Common::DebugManager::destroy();
|
||||
Common::EventRecorder::destroy();
|
||||
GUI::EventRecorder::destroy();
|
||||
Common::SearchManager::destroy();
|
||||
#ifdef USE_TRANSLATION
|
||||
Common::TranslationManager::destroy();
|
||||
|
|
|
@ -38,7 +38,9 @@ EventDispatcher::~EventDispatcher() {
|
|||
delete i->observer;
|
||||
}
|
||||
|
||||
delete _mapper;
|
||||
if (_autoFreeMapper) {
|
||||
delete _mapper;
|
||||
}
|
||||
_mapper = 0;
|
||||
}
|
||||
|
||||
|
@ -68,11 +70,15 @@ void EventDispatcher::dispatch() {
|
|||
}
|
||||
}
|
||||
|
||||
void EventDispatcher::registerMapper(EventMapper *mapper) {
|
||||
delete _mapper;
|
||||
void EventDispatcher::registerMapper(EventMapper *mapper, bool autoFree) {
|
||||
if (_autoFreeMapper) {
|
||||
delete _mapper;
|
||||
}
|
||||
_mapper = mapper;
|
||||
_autoFreeMapper = autoFree;
|
||||
}
|
||||
|
||||
|
||||
void EventDispatcher::registerSource(EventSource *source, bool autoFree) {
|
||||
SourceEntry newEntry;
|
||||
|
||||
|
|
|
@ -1,428 +0,0 @@
|
|||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "common/EventRecorder.h"
|
||||
|
||||
#include "common/bufferedstream.h"
|
||||
#include "common/config-manager.h"
|
||||
#include "common/random.h"
|
||||
#include "common/savefile.h"
|
||||
#include "common/textconsole.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
DECLARE_SINGLETON(EventRecorder);
|
||||
|
||||
#define RECORD_SIGNATURE 0x54455354
|
||||
#define RECORD_VERSION 1
|
||||
|
||||
uint32 readTime(ReadStream *inFile) {
|
||||
uint32 d = inFile->readByte();
|
||||
if (d == 0xff) {
|
||||
d = inFile->readUint32LE();
|
||||
}
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
void writeTime(WriteStream *outFile, uint32 d) {
|
||||
//Simple RLE compression
|
||||
if (d >= 0xff) {
|
||||
outFile->writeByte(0xff);
|
||||
outFile->writeUint32LE(d);
|
||||
} else {
|
||||
outFile->writeByte(d);
|
||||
}
|
||||
}
|
||||
|
||||
void readRecord(SeekableReadStream *inFile, uint32 &diff, Event &event, uint32 &millis) {
|
||||
millis = readTime(inFile);
|
||||
|
||||
diff = inFile->readUint32LE();
|
||||
|
||||
event.type = (EventType)inFile->readUint32LE();
|
||||
|
||||
switch (event.type) {
|
||||
case EVENT_KEYDOWN:
|
||||
case EVENT_KEYUP:
|
||||
event.kbd.keycode = (KeyCode)inFile->readSint32LE();
|
||||
event.kbd.ascii = inFile->readUint16LE();
|
||||
event.kbd.flags = inFile->readByte();
|
||||
break;
|
||||
case EVENT_MOUSEMOVE:
|
||||
case EVENT_LBUTTONDOWN:
|
||||
case EVENT_LBUTTONUP:
|
||||
case EVENT_RBUTTONDOWN:
|
||||
case EVENT_RBUTTONUP:
|
||||
case EVENT_WHEELUP:
|
||||
case EVENT_WHEELDOWN:
|
||||
case EVENT_MBUTTONDOWN:
|
||||
case EVENT_MBUTTONUP:
|
||||
event.mouse.x = inFile->readSint16LE();
|
||||
event.mouse.y = inFile->readSint16LE();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void writeRecord(WriteStream *outFile, uint32 diff, const Event &event, uint32 millis) {
|
||||
writeTime(outFile, millis);
|
||||
|
||||
outFile->writeUint32LE(diff);
|
||||
|
||||
outFile->writeUint32LE((uint32)event.type);
|
||||
|
||||
switch (event.type) {
|
||||
case EVENT_KEYDOWN:
|
||||
case EVENT_KEYUP:
|
||||
outFile->writeSint32LE(event.kbd.keycode);
|
||||
outFile->writeUint16LE(event.kbd.ascii);
|
||||
outFile->writeByte(event.kbd.flags);
|
||||
break;
|
||||
case EVENT_MOUSEMOVE:
|
||||
case EVENT_LBUTTONDOWN:
|
||||
case EVENT_LBUTTONUP:
|
||||
case EVENT_RBUTTONDOWN:
|
||||
case EVENT_RBUTTONUP:
|
||||
case EVENT_WHEELUP:
|
||||
case EVENT_WHEELDOWN:
|
||||
case EVENT_MBUTTONDOWN:
|
||||
case EVENT_MBUTTONUP:
|
||||
outFile->writeSint16LE(event.mouse.x);
|
||||
outFile->writeSint16LE(event.mouse.y);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
EventRecorder::EventRecorder() {
|
||||
_recordFile = NULL;
|
||||
_recordTimeFile = NULL;
|
||||
_playbackFile = NULL;
|
||||
_playbackTimeFile = NULL;
|
||||
_timeMutex = g_system->createMutex();
|
||||
_recorderMutex = g_system->createMutex();
|
||||
|
||||
_eventCount = 0;
|
||||
_lastEventCount = 0;
|
||||
_lastMillis = 0;
|
||||
_lastEventMillis = 0;
|
||||
|
||||
_recordMode = kPassthrough;
|
||||
}
|
||||
|
||||
EventRecorder::~EventRecorder() {
|
||||
deinit();
|
||||
|
||||
g_system->deleteMutex(_timeMutex);
|
||||
g_system->deleteMutex(_recorderMutex);
|
||||
}
|
||||
|
||||
void EventRecorder::init() {
|
||||
String recordModeString = ConfMan.get("record_mode");
|
||||
if (recordModeString.compareToIgnoreCase("record") == 0) {
|
||||
_recordMode = kRecorderRecord;
|
||||
|
||||
debug(3, "EventRecorder: record");
|
||||
} else {
|
||||
if (recordModeString.compareToIgnoreCase("playback") == 0) {
|
||||
_recordMode = kRecorderPlayback;
|
||||
debug(3, "EventRecorder: playback");
|
||||
} else {
|
||||
_recordMode = kPassthrough;
|
||||
debug(3, "EventRecorder: passthrough");
|
||||
}
|
||||
}
|
||||
|
||||
_recordFileName = ConfMan.get("record_file_name");
|
||||
if (_recordFileName.empty()) {
|
||||
_recordFileName = "record.bin";
|
||||
}
|
||||
_recordTempFileName = ConfMan.get("record_temp_file_name");
|
||||
if (_recordTempFileName.empty()) {
|
||||
_recordTempFileName = "record.tmp";
|
||||
}
|
||||
_recordTimeFileName = ConfMan.get("record_time_file_name");
|
||||
if (_recordTimeFileName.empty()) {
|
||||
_recordTimeFileName = "record.time";
|
||||
}
|
||||
|
||||
// recorder stuff
|
||||
if (_recordMode == kRecorderRecord) {
|
||||
_recordCount = 0;
|
||||
_recordTimeCount = 0;
|
||||
_recordFile = wrapBufferedWriteStream(g_system->getSavefileManager()->openForSaving(_recordTempFileName), 128 * 1024);
|
||||
_recordTimeFile = wrapBufferedWriteStream(g_system->getSavefileManager()->openForSaving(_recordTimeFileName), 128 * 1024);
|
||||
_recordSubtitles = ConfMan.getBool("subtitles");
|
||||
}
|
||||
|
||||
uint32 sign;
|
||||
uint32 randomSourceCount;
|
||||
if (_recordMode == kRecorderPlayback) {
|
||||
_playbackCount = 0;
|
||||
_playbackTimeCount = 0;
|
||||
_playbackFile = wrapBufferedSeekableReadStream(g_system->getSavefileManager()->openForLoading(_recordFileName), 128 * 1024, DisposeAfterUse::YES);
|
||||
_playbackTimeFile = wrapBufferedSeekableReadStream(g_system->getSavefileManager()->openForLoading(_recordTimeFileName), 128 * 1024, DisposeAfterUse::YES);
|
||||
|
||||
if (!_playbackFile) {
|
||||
warning("Cannot open playback file %s. Playback was switched off", _recordFileName.c_str());
|
||||
_recordMode = kPassthrough;
|
||||
}
|
||||
|
||||
if (!_playbackTimeFile) {
|
||||
warning("Cannot open playback time file %s. Playback was switched off", _recordTimeFileName.c_str());
|
||||
_recordMode = kPassthrough;
|
||||
}
|
||||
}
|
||||
|
||||
if (_recordMode == kRecorderPlayback) {
|
||||
sign = _playbackFile->readUint32LE();
|
||||
if (sign != RECORD_SIGNATURE) {
|
||||
error("Unknown record file signature");
|
||||
}
|
||||
|
||||
_playbackFile->readUint32LE(); // version
|
||||
|
||||
// conf vars
|
||||
ConfMan.setBool("subtitles", _playbackFile->readByte() != 0);
|
||||
|
||||
_recordCount = _playbackFile->readUint32LE();
|
||||
_recordTimeCount = _playbackFile->readUint32LE();
|
||||
|
||||
randomSourceCount = _playbackFile->readUint32LE();
|
||||
for (uint i = 0; i < randomSourceCount; ++i) {
|
||||
RandomSourceRecord rec;
|
||||
rec.name = "";
|
||||
uint32 sLen = _playbackFile->readUint32LE();
|
||||
for (uint j = 0; j < sLen; ++j) {
|
||||
char c = _playbackFile->readSByte();
|
||||
rec.name += c;
|
||||
}
|
||||
rec.seed = _playbackFile->readUint32LE();
|
||||
_randomSourceRecords.push_back(rec);
|
||||
}
|
||||
|
||||
_hasPlaybackEvent = false;
|
||||
}
|
||||
|
||||
g_system->getEventManager()->getEventDispatcher()->registerSource(this, false);
|
||||
g_system->getEventManager()->getEventDispatcher()->registerObserver(this, EventManager::kEventRecorderPriority, false, true);
|
||||
}
|
||||
|
||||
void EventRecorder::deinit() {
|
||||
debug(3, "EventRecorder: deinit");
|
||||
|
||||
g_system->getEventManager()->getEventDispatcher()->unregisterSource(this);
|
||||
g_system->getEventManager()->getEventDispatcher()->unregisterObserver(this);
|
||||
|
||||
g_system->lockMutex(_timeMutex);
|
||||
g_system->lockMutex(_recorderMutex);
|
||||
_recordMode = kPassthrough;
|
||||
g_system->unlockMutex(_timeMutex);
|
||||
g_system->unlockMutex(_recorderMutex);
|
||||
|
||||
delete _playbackFile;
|
||||
delete _playbackTimeFile;
|
||||
|
||||
if (_recordFile != NULL) {
|
||||
_recordFile->finalize();
|
||||
delete _recordFile;
|
||||
_recordTimeFile->finalize();
|
||||
delete _recordTimeFile;
|
||||
|
||||
_playbackFile = g_system->getSavefileManager()->openForLoading(_recordTempFileName);
|
||||
|
||||
assert(_playbackFile);
|
||||
|
||||
_recordFile = g_system->getSavefileManager()->openForSaving(_recordFileName);
|
||||
_recordFile->writeUint32LE(RECORD_SIGNATURE);
|
||||
_recordFile->writeUint32LE(RECORD_VERSION);
|
||||
|
||||
// conf vars
|
||||
_recordFile->writeByte(_recordSubtitles ? 1 : 0);
|
||||
|
||||
_recordFile->writeUint32LE(_recordCount);
|
||||
_recordFile->writeUint32LE(_recordTimeCount);
|
||||
|
||||
_recordFile->writeUint32LE(_randomSourceRecords.size());
|
||||
for (uint i = 0; i < _randomSourceRecords.size(); ++i) {
|
||||
_recordFile->writeUint32LE(_randomSourceRecords[i].name.size());
|
||||
_recordFile->writeString(_randomSourceRecords[i].name);
|
||||
_recordFile->writeUint32LE(_randomSourceRecords[i].seed);
|
||||
}
|
||||
|
||||
for (uint i = 0; i < _recordCount; ++i) {
|
||||
uint32 tempDiff;
|
||||
Event tempEvent;
|
||||
uint32 millis;
|
||||
readRecord(_playbackFile, tempDiff, tempEvent, millis);
|
||||
writeRecord(_recordFile, tempDiff, tempEvent, millis);
|
||||
}
|
||||
|
||||
_recordFile->finalize();
|
||||
delete _recordFile;
|
||||
delete _playbackFile;
|
||||
|
||||
//TODO: remove recordTempFileName'ed file
|
||||
}
|
||||
}
|
||||
|
||||
void EventRecorder::registerRandomSource(RandomSource &rnd, const String &name) {
|
||||
if (_recordMode == kRecorderRecord) {
|
||||
RandomSourceRecord rec;
|
||||
rec.name = name;
|
||||
rec.seed = rnd.getSeed();
|
||||
_randomSourceRecords.push_back(rec);
|
||||
}
|
||||
|
||||
if (_recordMode == kRecorderPlayback) {
|
||||
for (uint i = 0; i < _randomSourceRecords.size(); ++i) {
|
||||
if (_randomSourceRecords[i].name == name) {
|
||||
rnd.setSeed(_randomSourceRecords[i].seed);
|
||||
_randomSourceRecords.remove_at(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EventRecorder::processMillis(uint32 &millis) {
|
||||
uint32 d;
|
||||
if (_recordMode == kPassthrough) {
|
||||
return;
|
||||
}
|
||||
|
||||
g_system->lockMutex(_timeMutex);
|
||||
if (_recordMode == kRecorderRecord) {
|
||||
d = millis - _lastMillis;
|
||||
writeTime(_recordTimeFile, d);
|
||||
|
||||
_recordTimeCount++;
|
||||
}
|
||||
|
||||
if (_recordMode == kRecorderPlayback) {
|
||||
if (_recordTimeCount > _playbackTimeCount) {
|
||||
d = readTime(_playbackTimeFile);
|
||||
|
||||
while ((_lastMillis + d > millis) && (_lastMillis + d - millis > 50)) {
|
||||
_recordMode = kPassthrough;
|
||||
g_system->delayMillis(50);
|
||||
millis = g_system->getMillis();
|
||||
_recordMode = kRecorderPlayback;
|
||||
}
|
||||
|
||||
millis = _lastMillis + d;
|
||||
_playbackTimeCount++;
|
||||
}
|
||||
}
|
||||
|
||||
_lastMillis = millis;
|
||||
g_system->unlockMutex(_timeMutex);
|
||||
}
|
||||
|
||||
bool EventRecorder::processDelayMillis(uint &msecs) {
|
||||
if (_recordMode == kRecorderPlayback) {
|
||||
_recordMode = kPassthrough;
|
||||
|
||||
uint32 millis = g_system->getMillis();
|
||||
|
||||
_recordMode = kRecorderPlayback;
|
||||
|
||||
if (_lastMillis > millis) {
|
||||
// Skip delay if we're getting late
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool EventRecorder::notifyEvent(const Event &ev) {
|
||||
if (_recordMode != kRecorderRecord)
|
||||
return false;
|
||||
|
||||
StackLock lock(_recorderMutex);
|
||||
++_eventCount;
|
||||
|
||||
writeRecord(_recordFile, _eventCount - _lastEventCount, ev, _lastMillis - _lastEventMillis);
|
||||
|
||||
_recordCount++;
|
||||
_lastEventCount = _eventCount;
|
||||
_lastEventMillis = _lastMillis;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool EventRecorder::notifyPoll() {
|
||||
if (_recordMode != kRecorderRecord)
|
||||
return false;
|
||||
|
||||
++_eventCount;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool EventRecorder::pollEvent(Event &ev) {
|
||||
uint32 millis;
|
||||
|
||||
if (_recordMode != kRecorderPlayback)
|
||||
return false;
|
||||
|
||||
StackLock lock(_recorderMutex);
|
||||
++_eventCount;
|
||||
|
||||
if (!_hasPlaybackEvent) {
|
||||
if (_recordCount > _playbackCount) {
|
||||
readRecord(_playbackFile, const_cast<uint32&>(_playbackDiff), _playbackEvent, millis);
|
||||
_playbackCount++;
|
||||
_hasPlaybackEvent = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (_hasPlaybackEvent) {
|
||||
if (_playbackDiff <= (_eventCount - _lastEventCount)) {
|
||||
switch (_playbackEvent.type) {
|
||||
case EVENT_MOUSEMOVE:
|
||||
case EVENT_LBUTTONDOWN:
|
||||
case EVENT_LBUTTONUP:
|
||||
case EVENT_RBUTTONDOWN:
|
||||
case EVENT_RBUTTONUP:
|
||||
case EVENT_WHEELUP:
|
||||
case EVENT_WHEELDOWN:
|
||||
g_system->warpMouse(_playbackEvent.mouse.x, _playbackEvent.mouse.y);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
ev = _playbackEvent;
|
||||
_hasPlaybackEvent = false;
|
||||
_lastEventCount = _eventCount;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // End of namespace Common
|
|
@ -1,110 +0,0 @@
|
|||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef COMMON_EVENTRECORDER_H
|
||||
#define COMMON_EVENTRECORDER_H
|
||||
|
||||
#include "common/scummsys.h"
|
||||
#include "common/events.h"
|
||||
#include "common/singleton.h"
|
||||
#include "common/mutex.h"
|
||||
#include "common/array.h"
|
||||
|
||||
#define g_eventRec (Common::EventRecorder::instance())
|
||||
|
||||
namespace Common {
|
||||
|
||||
class RandomSource;
|
||||
class SeekableReadStream;
|
||||
class WriteStream;
|
||||
|
||||
/**
|
||||
* Our generic event recorder.
|
||||
*
|
||||
* TODO: Add more documentation.
|
||||
*/
|
||||
class EventRecorder : private EventSource, private EventObserver, public Singleton<EventRecorder> {
|
||||
friend class Singleton<SingletonBaseType>;
|
||||
EventRecorder();
|
||||
~EventRecorder();
|
||||
public:
|
||||
void init();
|
||||
void deinit();
|
||||
|
||||
/** Register random source so it can be serialized in game test purposes */
|
||||
void registerRandomSource(RandomSource &rnd, const String &name);
|
||||
|
||||
/** TODO: Add documentation, this is only used by the backend */
|
||||
void processMillis(uint32 &millis);
|
||||
|
||||
/** TODO: Add documentation, this is only used by the backend */
|
||||
bool processDelayMillis(uint &msecs);
|
||||
|
||||
private:
|
||||
bool notifyEvent(const Event &ev);
|
||||
bool notifyPoll();
|
||||
bool pollEvent(Event &ev);
|
||||
bool allowMapping() const { return false; }
|
||||
|
||||
class RandomSourceRecord {
|
||||
public:
|
||||
String name;
|
||||
uint32 seed;
|
||||
};
|
||||
Array<RandomSourceRecord> _randomSourceRecords;
|
||||
|
||||
bool _recordSubtitles;
|
||||
volatile uint32 _recordCount;
|
||||
volatile uint32 _lastRecordEvent;
|
||||
volatile uint32 _recordTimeCount;
|
||||
volatile uint32 _lastEventMillis;
|
||||
WriteStream *_recordFile;
|
||||
WriteStream *_recordTimeFile;
|
||||
MutexRef _timeMutex;
|
||||
MutexRef _recorderMutex;
|
||||
volatile uint32 _lastMillis;
|
||||
|
||||
volatile uint32 _playbackCount;
|
||||
volatile uint32 _playbackDiff;
|
||||
volatile bool _hasPlaybackEvent;
|
||||
volatile uint32 _playbackTimeCount;
|
||||
Event _playbackEvent;
|
||||
SeekableReadStream *_playbackFile;
|
||||
SeekableReadStream *_playbackTimeFile;
|
||||
|
||||
volatile uint32 _eventCount;
|
||||
volatile uint32 _lastEventCount;
|
||||
|
||||
enum RecordMode {
|
||||
kPassthrough = 0,
|
||||
kRecorderRecord = 1,
|
||||
kRecorderPlayback = 2
|
||||
};
|
||||
volatile RecordMode _recordMode;
|
||||
String _recordFileName;
|
||||
String _recordTempFileName;
|
||||
String _recordTimeFileName;
|
||||
};
|
||||
|
||||
} // End of namespace Common
|
||||
|
||||
#endif
|
|
@ -117,5 +117,9 @@ void debugCN(uint32 debugChannels, const char *s, ...) GCC_PRINTF(2, 3);
|
|||
*/
|
||||
extern int gDebugLevel;
|
||||
|
||||
//Global constant for EventRecorder debug channel
|
||||
enum GlobalDebugLevels {
|
||||
kDebugLevelEventRec = 1 << 30
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -288,11 +288,14 @@ public:
|
|||
* to the EventDispatcher, thus it will be deleted
|
||||
* with "delete", when EventDispatcher is destroyed.
|
||||
*
|
||||
* Note there is only one mapper per EventDispatcher
|
||||
* possible, thus when this method is called twice,
|
||||
* the former mapper will be destroied.
|
||||
* @param autoFree Destroy previous mapper [default]
|
||||
* Normally we allow only one event mapper to exists,
|
||||
* However Event Recorder must intervent into normal
|
||||
* event flow without altering its semantics. Thus during
|
||||
* Event Recorder playback and recording we allow
|
||||
* two mappers.
|
||||
*/
|
||||
void registerMapper(EventMapper *mapper);
|
||||
void registerMapper(EventMapper *mapper, bool autoFree = true);
|
||||
|
||||
/**
|
||||
* Queries the setup event mapper.
|
||||
|
@ -326,6 +329,7 @@ public:
|
|||
*/
|
||||
void unregisterObserver(EventObserver *obs);
|
||||
private:
|
||||
bool _autoFreeMapper;
|
||||
EventMapper *_mapper;
|
||||
|
||||
struct Entry {
|
||||
|
|
|
@ -89,8 +89,9 @@ public:
|
|||
*/
|
||||
class MemoryWriteStream : public WriteStream {
|
||||
private:
|
||||
byte *_ptr;
|
||||
const uint32 _bufSize;
|
||||
protected:
|
||||
byte *_ptr;
|
||||
uint32 _pos;
|
||||
bool _err;
|
||||
public:
|
||||
|
@ -116,6 +117,40 @@ public:
|
|||
virtual void clearErr() { _err = false; }
|
||||
};
|
||||
|
||||
/**
|
||||
* MemoryWriteStream subclass with ability to set stream position indicator.
|
||||
*/
|
||||
class SeekableMemoryWriteStream : public MemoryWriteStream {
|
||||
private:
|
||||
byte *_ptrOrig;
|
||||
public:
|
||||
SeekableMemoryWriteStream(byte *buf, uint32 len) : MemoryWriteStream(buf, len), _ptrOrig(buf) {}
|
||||
uint32 seek(uint32 offset, int whence = SEEK_SET) {
|
||||
switch (whence) {
|
||||
case SEEK_END:
|
||||
// SEEK_END works just like SEEK_SET, only 'reversed',
|
||||
// i.e. from the end.
|
||||
offset = size() + offset;
|
||||
// Fall through
|
||||
case SEEK_SET:
|
||||
_ptr = _ptrOrig + offset;
|
||||
_pos = offset;
|
||||
break;
|
||||
case SEEK_CUR:
|
||||
_ptr += offset;
|
||||
_pos += offset;
|
||||
break;
|
||||
}
|
||||
// Post-Condition
|
||||
if (_pos > size()) {
|
||||
_pos = size();
|
||||
_ptr = _ptrOrig + _pos;
|
||||
}
|
||||
return _pos;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A sort of hybrid between MemoryWriteStream and Array classes. A stream
|
||||
* that grows as it's written to.
|
||||
|
|
|
@ -10,7 +10,6 @@ MODULE_OBJS := \
|
|||
error.o \
|
||||
EventDispatcher.o \
|
||||
EventMapper.o \
|
||||
EventRecorder.o \
|
||||
file.o \
|
||||
fs.o \
|
||||
gui_options.o \
|
||||
|
@ -51,5 +50,10 @@ MODULE_OBJS += \
|
|||
rdft.o \
|
||||
sinetables.o
|
||||
|
||||
ifdef ENABLE_EVENTRECORDER
|
||||
MODULE_OBJS += \
|
||||
recorderfile.o
|
||||
endif
|
||||
|
||||
# Include common rules
|
||||
include $(srcdir)/rules.mk
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
#include "common/random.h"
|
||||
#include "common/system.h"
|
||||
#include "common/EventRecorder.h"
|
||||
#include "gui/EventRecorder.h"
|
||||
|
||||
|
||||
namespace Common {
|
||||
|
@ -30,13 +30,8 @@ RandomSource::RandomSource(const String &name) {
|
|||
// Use system time as RNG seed. Normally not a good idea, if you are using
|
||||
// a RNG for security purposes, but good enough for our purposes.
|
||||
assert(g_system);
|
||||
uint32 seed = g_system->getMillis();
|
||||
uint32 seed = g_eventRec.getRandomSeed(name);
|
||||
setSeed(seed);
|
||||
|
||||
// Register this random source with the event recorder. This may end
|
||||
// up querying or resetting the current seed, so we must call it
|
||||
// *after* the initial seed has been set.
|
||||
g_eventRec.registerRandomSource(*this, name);
|
||||
}
|
||||
|
||||
void RandomSource::setSeed(uint32 seed) {
|
||||
|
|
708
common/recorderfile.cpp
Normal file
|
@ -0,0 +1,708 @@
|
|||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "common/system.h"
|
||||
#include "gui/EventRecorder.h"
|
||||
#include "common/md5.h"
|
||||
#include "common/recorderfile.h"
|
||||
#include "common/savefile.h"
|
||||
#include "common/bufferedstream.h"
|
||||
#include "graphics/thumbnail.h"
|
||||
#include "graphics/surface.h"
|
||||
#include "graphics/scaler.h"
|
||||
|
||||
#define RECORD_VERSION 1
|
||||
|
||||
namespace Common {
|
||||
|
||||
PlaybackFile::PlaybackFile() : _tmpRecordFile(_tmpBuffer, kRecordBuffSize), _tmpPlaybackFile(_tmpBuffer, kRecordBuffSize) {
|
||||
_readStream = NULL;
|
||||
_writeStream = NULL;
|
||||
_screenshotsFile = NULL;
|
||||
_mode = kClosed;
|
||||
}
|
||||
|
||||
PlaybackFile::~PlaybackFile() {
|
||||
close();
|
||||
}
|
||||
|
||||
bool PlaybackFile::openWrite(const String &fileName) {
|
||||
close();
|
||||
_header.fileName = fileName;
|
||||
_writeStream = wrapBufferedWriteStream(g_system->getSavefileManager()->openForSaving(fileName), 128 * 1024);
|
||||
_headerDumped = false;
|
||||
_recordCount = 0;
|
||||
if (_writeStream == NULL) {
|
||||
return false;
|
||||
}
|
||||
_mode = kWrite;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PlaybackFile::openRead(const String &fileName) {
|
||||
close();
|
||||
_header.fileName = fileName;
|
||||
_eventsSize = 0;
|
||||
_tmpPlaybackFile.seek(0);
|
||||
_readStream = wrapBufferedSeekableReadStream(g_system->getSavefileManager()->openForLoading(fileName), 128 * 1024, DisposeAfterUse::YES);
|
||||
if (_readStream == NULL) {
|
||||
debugC(1, kDebugLevelEventRec, "playback:action=\"Load File\" result=fail reason=\"file %s not found\"", fileName.c_str());
|
||||
return false;
|
||||
}
|
||||
if (!parseHeader()) {
|
||||
debugC(1, kDebugLevelEventRec, "playback:action=\"Load File\" result=fail reason=\"header parsing failed\"");
|
||||
return false;
|
||||
}
|
||||
_screenshotsFile = wrapBufferedWriteStream(g_system->getSavefileManager()->openForSaving("screenshots.bin"), 128 * 1024);
|
||||
debugC(1, kDebugLevelEventRec, "playback:action=\"Load File\" result=success");
|
||||
_mode = kRead;
|
||||
return true;
|
||||
}
|
||||
|
||||
void PlaybackFile::close() {
|
||||
delete _readStream;
|
||||
_readStream = NULL;
|
||||
if (_writeStream != NULL) {
|
||||
dumpRecordsToFile();
|
||||
_writeStream->finalize();
|
||||
delete _writeStream;
|
||||
_writeStream = NULL;
|
||||
updateHeader();
|
||||
}
|
||||
if (_screenshotsFile != NULL) {
|
||||
_screenshotsFile->finalize();
|
||||
delete _screenshotsFile;
|
||||
_screenshotsFile = NULL;
|
||||
}
|
||||
for (HashMap<String, SaveFileBuffer>::iterator i = _header.saveFiles.begin(); i != _header.saveFiles.end(); ++i) {
|
||||
free(i->_value.buffer);
|
||||
}
|
||||
_header.saveFiles.clear();
|
||||
_mode = kClosed;
|
||||
}
|
||||
|
||||
bool PlaybackFile::parseHeader() {
|
||||
PlaybackFileHeader result;
|
||||
ChunkHeader nextChunk;
|
||||
_playbackParseState = kFileStateCheckFormat;
|
||||
if (!readChunkHeader(nextChunk)) {
|
||||
_playbackParseState = kFileStateError;
|
||||
return false;
|
||||
}
|
||||
while ((_playbackParseState != kFileStateDone) && (_playbackParseState != kFileStateError)) {
|
||||
if (processChunk(nextChunk)) {
|
||||
if (!readChunkHeader(nextChunk)) {
|
||||
warning("Error in header parsing");
|
||||
_playbackParseState = kFileStateError;
|
||||
}
|
||||
}
|
||||
}
|
||||
return _playbackParseState == kFileStateDone;
|
||||
}
|
||||
|
||||
bool PlaybackFile::checkPlaybackFileVersion() {
|
||||
uint32 version;
|
||||
version = _readStream->readUint32LE();
|
||||
if (version != RECORD_VERSION) {
|
||||
warning("Incorrect playback file version. Expected version %d, but got %d.", RECORD_VERSION, version);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
String PlaybackFile::readString(int len) {
|
||||
String result;
|
||||
char buf[50];
|
||||
int readSize = 49;
|
||||
while (len > 0) {
|
||||
if (len <= 49) {
|
||||
readSize = len;
|
||||
}
|
||||
_readStream->read(buf, readSize);
|
||||
buf[readSize] = 0;
|
||||
result += buf;
|
||||
len -= readSize;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool PlaybackFile::readChunkHeader(PlaybackFile::ChunkHeader &nextChunk) {
|
||||
nextChunk.id = (FileTag)_readStream->readUint32LE();
|
||||
nextChunk.len = _readStream->readUint32LE();
|
||||
return !_readStream->err() && !_readStream->eos();
|
||||
}
|
||||
|
||||
bool PlaybackFile::processChunk(ChunkHeader &nextChunk) {
|
||||
switch (_playbackParseState) {
|
||||
case kFileStateCheckFormat:
|
||||
if (nextChunk.id == kFormatIdTag) {
|
||||
_playbackParseState = kFileStateCheckVersion;
|
||||
} else {
|
||||
warning("Unknown playback file signature");
|
||||
_playbackParseState = kFileStateError;
|
||||
}
|
||||
break;
|
||||
case kFileStateCheckVersion:
|
||||
if ((nextChunk.id == kVersionTag) && checkPlaybackFileVersion()) {
|
||||
_playbackParseState = kFileStateSelectSection;
|
||||
} else {
|
||||
_playbackParseState = kFileStateError;
|
||||
}
|
||||
break;
|
||||
case kFileStateSelectSection:
|
||||
switch (nextChunk.id) {
|
||||
case kHeaderSectionTag:
|
||||
_playbackParseState = kFileStateProcessHeader;
|
||||
break;
|
||||
case kHashSectionTag:
|
||||
_playbackParseState = kFileStateProcessHash;
|
||||
break;
|
||||
case kRandomSectionTag:
|
||||
_playbackParseState = kFileStateProcessRandom;
|
||||
break;
|
||||
case kEventTag:
|
||||
case kScreenShotTag:
|
||||
_readStream->seek(-8, SEEK_CUR);
|
||||
_playbackParseState = kFileStateDone;
|
||||
return false;
|
||||
case kSaveTag:
|
||||
_playbackParseState = kFileStateProcessSave;
|
||||
break;
|
||||
case kSettingsSectionTag:
|
||||
_playbackParseState = kFileStateProcessSettings;
|
||||
warning("Loading record header");
|
||||
break;
|
||||
default:
|
||||
_readStream->skip(nextChunk.len);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case kFileStateProcessSave:
|
||||
if (nextChunk.id == kSaveRecordTag) {
|
||||
readSaveRecord();
|
||||
} else {
|
||||
_playbackParseState = kFileStateSelectSection;
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case kFileStateProcessHeader:
|
||||
switch (nextChunk.id) {
|
||||
case kAuthorTag:
|
||||
_header.author = readString(nextChunk.len);
|
||||
break;
|
||||
case kCommentsTag:
|
||||
_header.notes = readString(nextChunk.len);
|
||||
break;
|
||||
case kNameTag:
|
||||
_header.name = readString(nextChunk.len);
|
||||
break;
|
||||
default:
|
||||
_playbackParseState = kFileStateSelectSection;
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case kFileStateProcessHash:
|
||||
if (nextChunk.id == kHashRecordTag) {
|
||||
readHashMap(nextChunk);
|
||||
} else {
|
||||
_playbackParseState = kFileStateSelectSection;
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case kFileStateProcessRandom:
|
||||
if (nextChunk.id == kRandomRecordTag) {
|
||||
processRndSeedRecord(nextChunk);
|
||||
} else {
|
||||
_playbackParseState = kFileStateSelectSection;
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case kFileStateProcessSettings:
|
||||
if (nextChunk.id == kSettingsRecordTag) {
|
||||
if (!processSettingsRecord()) {
|
||||
_playbackParseState = kFileStateError;
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
_playbackParseState = kFileStateSelectSection;
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void PlaybackFile::returnToChunkHeader() {
|
||||
_readStream->seek(-8, SEEK_CUR);
|
||||
}
|
||||
|
||||
void PlaybackFile::readHashMap(ChunkHeader chunk) {
|
||||
String hashName = readString(chunk.len - 32);
|
||||
String hashMd5 = readString(32);
|
||||
_header.hashRecords[hashName] = hashMd5;
|
||||
}
|
||||
|
||||
void PlaybackFile::processRndSeedRecord(ChunkHeader chunk) {
|
||||
String randomSourceName = readString(chunk.len - 4);
|
||||
uint32 randomSourceSeed = _readStream->readUint32LE();
|
||||
_header.randomSourceRecords[randomSourceName] = randomSourceSeed;
|
||||
}
|
||||
|
||||
bool PlaybackFile::processSettingsRecord() {
|
||||
ChunkHeader keyChunk;
|
||||
if (!readChunkHeader(keyChunk) || (keyChunk.id != kSettingsRecordKeyTag)) {
|
||||
warning("Invalid format of settings section");
|
||||
return false;
|
||||
}
|
||||
String key = readString(keyChunk.len);
|
||||
ChunkHeader valueChunk;
|
||||
if (!readChunkHeader(valueChunk) || (valueChunk.id != kSettingsRecordValueTag)) {
|
||||
warning("Invalid format of settings section");
|
||||
return false;
|
||||
}
|
||||
String value = readString(valueChunk.len);
|
||||
_header.settingsRecords[key] = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool PlaybackFile::readSaveRecord() {
|
||||
ChunkHeader fileNameChunk;
|
||||
if (!readChunkHeader(fileNameChunk) || (fileNameChunk.id != kSaveRecordNameTag)) {
|
||||
warning("Invalid format of save section");
|
||||
return false;
|
||||
}
|
||||
String fileName = readString(fileNameChunk.len);
|
||||
ChunkHeader saveBufferChunk;
|
||||
if (!readChunkHeader(saveBufferChunk) || (saveBufferChunk.id != kSaveRecordBufferTag)) {
|
||||
warning("Invalid format of save section");
|
||||
return false;
|
||||
}
|
||||
SaveFileBuffer buf;
|
||||
buf.size = saveBufferChunk.len;
|
||||
buf.buffer = (byte *)malloc(saveBufferChunk.len);
|
||||
_readStream->read(buf.buffer, buf.size);
|
||||
_header.saveFiles[fileName] = buf;
|
||||
debugC(1, kDebugLevelEventRec, "playback:action=\"Load save file\" filename=%s len=%d", fileName.c_str(), buf.size);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
RecorderEvent PlaybackFile::getNextEvent() {
|
||||
assert(_mode == kRead);
|
||||
if (isEventsBufferEmpty()) {
|
||||
PlaybackFile::ChunkHeader header;
|
||||
header.id = kFormatIdTag;
|
||||
while (header.id != kEventTag) {
|
||||
if (!readChunkHeader(header) || _readStream->eos()) {
|
||||
break;
|
||||
}
|
||||
switch (header.id) {
|
||||
case kEventTag:
|
||||
readEventsToBuffer(header.len);
|
||||
break;
|
||||
case kScreenShotTag:
|
||||
_readStream->seek(-4, SEEK_CUR);
|
||||
header.len = _readStream->readUint32BE();
|
||||
_readStream->skip(header.len-8);
|
||||
break;
|
||||
case kMD5Tag:
|
||||
checkRecordedMD5();
|
||||
break;
|
||||
default:
|
||||
_readStream->skip(header.len);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
RecorderEvent result;
|
||||
readEvent(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool PlaybackFile::isEventsBufferEmpty() {
|
||||
return (uint32)_tmpPlaybackFile.pos() == _eventsSize;
|
||||
}
|
||||
|
||||
void PlaybackFile::readEvent(RecorderEvent& event) {
|
||||
event.recordedtype = (RecorderEventType)_tmpPlaybackFile.readByte();
|
||||
switch (event.recordedtype) {
|
||||
case kRecorderEventTypeTimer:
|
||||
event.time = _tmpPlaybackFile.readUint32LE();
|
||||
break;
|
||||
case kRecorderEventTypeNormal:
|
||||
event.type = (EventType)_tmpPlaybackFile.readUint32LE();
|
||||
switch (event.type) {
|
||||
case EVENT_KEYDOWN:
|
||||
case EVENT_KEYUP:
|
||||
event.time = _tmpPlaybackFile.readUint32LE();
|
||||
event.kbd.keycode = (KeyCode)_tmpPlaybackFile.readSint32LE();
|
||||
event.kbd.ascii = _tmpPlaybackFile.readUint16LE();
|
||||
event.kbd.flags = _tmpPlaybackFile.readByte();
|
||||
break;
|
||||
case EVENT_MOUSEMOVE:
|
||||
case EVENT_LBUTTONDOWN:
|
||||
case EVENT_LBUTTONUP:
|
||||
case EVENT_RBUTTONDOWN:
|
||||
case EVENT_RBUTTONUP:
|
||||
case EVENT_WHEELUP:
|
||||
case EVENT_WHEELDOWN:
|
||||
case EVENT_MBUTTONDOWN:
|
||||
case EVENT_MBUTTONUP:
|
||||
event.time = _tmpPlaybackFile.readUint32LE();
|
||||
event.mouse.x = _tmpPlaybackFile.readSint16LE();
|
||||
event.mouse.y = _tmpPlaybackFile.readSint16LE();
|
||||
break;
|
||||
default:
|
||||
event.time = _tmpPlaybackFile.readUint32LE();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
event.synthetic = true;
|
||||
}
|
||||
|
||||
void PlaybackFile::readEventsToBuffer(uint32 size) {
|
||||
_readStream->read(_tmpBuffer, size);
|
||||
_tmpPlaybackFile.seek(0);
|
||||
_eventsSize = size;
|
||||
}
|
||||
|
||||
void PlaybackFile::saveScreenShot(Graphics::Surface &screen, byte md5[16]) {
|
||||
dumpRecordsToFile();
|
||||
_writeStream->writeUint32LE(kMD5Tag);
|
||||
_writeStream->writeUint32LE(16);
|
||||
_writeStream->write(md5, 16);
|
||||
Graphics::saveThumbnail(*_writeStream, screen);
|
||||
}
|
||||
|
||||
void PlaybackFile::dumpRecordsToFile() {
|
||||
if (!_headerDumped) {
|
||||
dumpHeaderToFile();
|
||||
_headerDumped = true;
|
||||
}
|
||||
if (_recordCount == 0) {
|
||||
return;
|
||||
}
|
||||
_writeStream->writeUint32LE(kEventTag);
|
||||
_writeStream->writeUint32LE(_tmpRecordFile.pos());
|
||||
_writeStream->write(_tmpBuffer, _tmpRecordFile.pos());
|
||||
_tmpRecordFile.seek(0);
|
||||
_recordCount = 0;
|
||||
}
|
||||
|
||||
void PlaybackFile::dumpHeaderToFile() {
|
||||
_writeStream->writeUint32LE(kFormatIdTag);
|
||||
// Specify size for first tag as NULL since we cannot calculate
|
||||
// size of the file at time of the header dumping
|
||||
_writeStream->writeUint32LE(0);
|
||||
_writeStream->writeUint32LE(kVersionTag);
|
||||
_writeStream->writeUint32LE(4);
|
||||
_writeStream->writeUint32LE(RECORD_VERSION);
|
||||
writeHeaderSection();
|
||||
writeGameHash();
|
||||
writeRandomRecords();
|
||||
writeGameSettings();
|
||||
writeSaveFilesSection();
|
||||
}
|
||||
|
||||
void PlaybackFile::writeHeaderSection() {
|
||||
uint32 headerSize = 0;
|
||||
if (!_header.author.empty()) {
|
||||
headerSize = _header.author.size() + 8;
|
||||
}
|
||||
if (!_header.notes.empty()) {
|
||||
headerSize += _header.notes.size() + 8;
|
||||
}
|
||||
if (!_header.name.empty()) {
|
||||
headerSize += _header.name.size() + 8;
|
||||
}
|
||||
if (headerSize == 0) {
|
||||
return;
|
||||
}
|
||||
_writeStream->writeUint32LE(kHeaderSectionTag);
|
||||
_writeStream->writeUint32LE(headerSize);
|
||||
if (!_header.author.empty()) {
|
||||
_writeStream->writeUint32LE(kAuthorTag);
|
||||
_writeStream->writeUint32LE(_header.author.size());
|
||||
_writeStream->writeString(_header.author);
|
||||
}
|
||||
if (!_header.notes.empty()) {
|
||||
_writeStream->writeUint32LE(kCommentsTag);
|
||||
_writeStream->writeUint32LE(_header.notes.size());
|
||||
_writeStream->writeString(_header.notes);
|
||||
}
|
||||
if (!_header.name.empty()) {
|
||||
_writeStream->writeUint32LE(kNameTag);
|
||||
_writeStream->writeUint32LE(_header.name.size());
|
||||
_writeStream->writeString(_header.name);
|
||||
}
|
||||
}
|
||||
|
||||
void PlaybackFile::writeGameHash() {
|
||||
uint32 hashSectionSize = 0;
|
||||
for (StringMap::iterator i = _header.hashRecords.begin(); i != _header.hashRecords.end(); ++i) {
|
||||
hashSectionSize = hashSectionSize + i->_key.size() + i->_value.size() + 8;
|
||||
}
|
||||
if (_header.hashRecords.size() == 0) {
|
||||
return;
|
||||
}
|
||||
_writeStream->writeUint32LE(kHashSectionTag);
|
||||
_writeStream->writeUint32LE(hashSectionSize);
|
||||
for (StringMap::iterator i = _header.hashRecords.begin(); i != _header.hashRecords.end(); ++i) {
|
||||
_writeStream->writeUint32LE(kHashRecordTag);
|
||||
_writeStream->writeUint32LE(i->_key.size() + i->_value.size());
|
||||
_writeStream->writeString(i->_key);
|
||||
_writeStream->writeString(i->_value);
|
||||
}
|
||||
}
|
||||
|
||||
void PlaybackFile::writeRandomRecords() {
|
||||
uint32 randomSectionSize = 0;
|
||||
for (RandomSeedsDictionary::iterator i = _header.randomSourceRecords.begin(); i != _header.randomSourceRecords.end(); ++i) {
|
||||
randomSectionSize = randomSectionSize + i->_key.size() + 12;
|
||||
}
|
||||
if (_header.randomSourceRecords.size() == 0) {
|
||||
return;
|
||||
}
|
||||
_writeStream->writeUint32LE(kRandomSectionTag);
|
||||
_writeStream->writeUint32LE(randomSectionSize);
|
||||
for (RandomSeedsDictionary::iterator i = _header.randomSourceRecords.begin(); i != _header.randomSourceRecords.end(); ++i) {
|
||||
_writeStream->writeUint32LE(kRandomRecordTag);
|
||||
_writeStream->writeUint32LE(i->_key.size() + 4);
|
||||
_writeStream->writeString(i->_key);
|
||||
_writeStream->writeUint32LE(i->_value);
|
||||
}
|
||||
}
|
||||
|
||||
void PlaybackFile::writeEvent(const RecorderEvent &event) {
|
||||
assert(_mode == kWrite);
|
||||
_recordCount++;
|
||||
_tmpRecordFile.writeByte(event.recordedtype);
|
||||
switch (event.recordedtype) {
|
||||
case kRecorderEventTypeTimer:
|
||||
_tmpRecordFile.writeUint32LE(event.time);
|
||||
break;
|
||||
case kRecorderEventTypeNormal:
|
||||
_tmpRecordFile.writeUint32LE((uint32)event.type);
|
||||
switch(event.type) {
|
||||
case EVENT_KEYDOWN:
|
||||
case EVENT_KEYUP:
|
||||
_tmpRecordFile.writeUint32LE(event.time);
|
||||
_tmpRecordFile.writeSint32LE(event.kbd.keycode);
|
||||
_tmpRecordFile.writeUint16LE(event.kbd.ascii);
|
||||
_tmpRecordFile.writeByte(event.kbd.flags);
|
||||
break;
|
||||
case EVENT_MOUSEMOVE:
|
||||
case EVENT_LBUTTONDOWN:
|
||||
case EVENT_LBUTTONUP:
|
||||
case EVENT_RBUTTONDOWN:
|
||||
case EVENT_RBUTTONUP:
|
||||
case EVENT_WHEELUP:
|
||||
case EVENT_WHEELDOWN:
|
||||
case EVENT_MBUTTONDOWN:
|
||||
case EVENT_MBUTTONUP:
|
||||
_tmpRecordFile.writeUint32LE(event.time);
|
||||
_tmpRecordFile.writeSint16LE(event.mouse.x);
|
||||
_tmpRecordFile.writeSint16LE(event.mouse.y);
|
||||
break;
|
||||
default:
|
||||
_tmpRecordFile.writeUint32LE(event.time);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (_recordCount == kMaxBufferedRecords) {
|
||||
dumpRecordsToFile();
|
||||
}
|
||||
}
|
||||
|
||||
void PlaybackFile::writeGameSettings() {
|
||||
_writeStream->writeUint32LE(kSettingsSectionTag);
|
||||
uint32 settingsSectionSize = 0;
|
||||
for (StringMap::iterator i = _header.settingsRecords.begin(); i != _header.settingsRecords.end(); ++i) {
|
||||
settingsSectionSize += i->_key.size() + i->_value.size() + 24;
|
||||
}
|
||||
_writeStream->writeUint32LE(settingsSectionSize);
|
||||
for (StringMap::iterator i = _header.settingsRecords.begin(); i != _header.settingsRecords.end(); ++i) {
|
||||
_writeStream->writeUint32LE(kSettingsRecordTag);
|
||||
_writeStream->writeUint32LE(i->_key.size() + i->_value.size() + 16);
|
||||
_writeStream->writeUint32LE(kSettingsRecordKeyTag);
|
||||
_writeStream->writeUint32LE(i->_key.size());
|
||||
_writeStream->writeString(i->_key);
|
||||
_writeStream->writeUint32LE(kSettingsRecordValueTag);
|
||||
_writeStream->writeUint32LE(i->_value.size());
|
||||
_writeStream->writeString(i->_value);
|
||||
}
|
||||
}
|
||||
|
||||
int PlaybackFile::getScreensCount() {
|
||||
if (_mode != kRead) {
|
||||
return 0;
|
||||
}
|
||||
_readStream->seek(0);
|
||||
int result = 0;
|
||||
while (skipToNextScreenshot()) {
|
||||
uint32 size = _readStream->readUint32BE();
|
||||
_readStream->skip(size-8);
|
||||
++result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool PlaybackFile::skipToNextScreenshot() {
|
||||
while (true) {
|
||||
FileTag id = (FileTag)_readStream->readUint32LE();
|
||||
if (_readStream->eos()) {
|
||||
break;
|
||||
}
|
||||
if (id == kScreenShotTag) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
uint32 size = _readStream->readUint32LE();
|
||||
_readStream->skip(size);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Graphics::Surface *PlaybackFile::getScreenShot(int number) {
|
||||
if (_mode != kRead) {
|
||||
return NULL;
|
||||
}
|
||||
_readStream->seek(0);
|
||||
int screenCount = 1;
|
||||
while (skipToNextScreenshot()) {
|
||||
if (screenCount == number) {
|
||||
screenCount++;
|
||||
_readStream->seek(-4, SEEK_CUR);
|
||||
return Graphics::loadThumbnail(*_readStream);
|
||||
} else {
|
||||
uint32 size = _readStream->readUint32BE();
|
||||
_readStream->skip(size-8);
|
||||
screenCount++;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void PlaybackFile::updateHeader() {
|
||||
if (_mode == kWrite) {
|
||||
_readStream = g_system->getSavefileManager()->openForLoading(_header.fileName);
|
||||
}
|
||||
_readStream->seek(0);
|
||||
skipHeader();
|
||||
String tmpFilename = "_" + _header.fileName;
|
||||
_writeStream = g_system->getSavefileManager()->openForSaving(tmpFilename);
|
||||
dumpHeaderToFile();
|
||||
uint32 readedSize = 0;
|
||||
do {
|
||||
readedSize = _readStream->read(_tmpBuffer, kRecordBuffSize);
|
||||
_writeStream->write(_tmpBuffer, readedSize);
|
||||
} while (readedSize != 0);
|
||||
delete _writeStream;
|
||||
_writeStream = NULL;
|
||||
delete _readStream;
|
||||
_readStream = NULL;
|
||||
g_system->getSavefileManager()->removeSavefile(_header.fileName);
|
||||
g_system->getSavefileManager()->renameSavefile(tmpFilename, _header.fileName);
|
||||
if (_mode == kRead) {
|
||||
openRead(_header.fileName);
|
||||
}
|
||||
}
|
||||
|
||||
void PlaybackFile::skipHeader() {
|
||||
while (true) {
|
||||
uint32 id = _readStream->readUint32LE();
|
||||
if (_readStream->eos()) {
|
||||
break;
|
||||
}
|
||||
if ((id == kScreenShotTag) || (id == kEventTag) || (id == kMD5Tag)) {
|
||||
_readStream->seek(-4, SEEK_CUR);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
uint32 size = _readStream->readUint32LE();
|
||||
_readStream->skip(size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PlaybackFile::addSaveFile(const String &fileName, InSaveFile *saveStream) {
|
||||
uint oldPos = saveStream->pos();
|
||||
saveStream->seek(0);
|
||||
_header.saveFiles[fileName].buffer = (byte *)malloc(saveStream->size());
|
||||
_header.saveFiles[fileName].size = saveStream->size();
|
||||
saveStream->read(_header.saveFiles[fileName].buffer, saveStream->size());
|
||||
saveStream->seek(oldPos);
|
||||
}
|
||||
|
||||
void PlaybackFile::writeSaveFilesSection() {
|
||||
uint size = 0;
|
||||
for (HashMap<String, SaveFileBuffer>::iterator i = _header.saveFiles.begin(); i != _header.saveFiles.end(); ++i) {
|
||||
size += i->_value.size + i->_key.size() + 24;
|
||||
}
|
||||
if (size == 0) {
|
||||
return;
|
||||
}
|
||||
_writeStream->writeSint32LE(kSaveTag);
|
||||
_writeStream->writeSint32LE(size);
|
||||
for (HashMap<String, SaveFileBuffer>::iterator i = _header.saveFiles.begin(); i != _header.saveFiles.end(); ++i) {
|
||||
_writeStream->writeSint32LE(kSaveRecordTag);
|
||||
_writeStream->writeSint32LE(i->_key.size() + i->_value.size + 16);
|
||||
_writeStream->writeSint32LE(kSaveRecordNameTag);
|
||||
_writeStream->writeSint32LE(i->_key.size());
|
||||
_writeStream->writeString(i->_key);
|
||||
_writeStream->writeSint32LE(kSaveRecordBufferTag);
|
||||
_writeStream->writeSint32LE(i->_value.size);
|
||||
_writeStream->write(i->_value.buffer, i->_value.size);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void PlaybackFile::checkRecordedMD5() {
|
||||
uint8 currentMD5[16];
|
||||
uint8 savedMD5[16];
|
||||
Graphics::Surface screen;
|
||||
_readStream->read(savedMD5, 16);
|
||||
if (!g_eventRec.grabScreenAndComputeMD5(screen, currentMD5)) {
|
||||
return;
|
||||
}
|
||||
uint32 seconds = g_system->getMillis(true) / 1000;
|
||||
String screenTime = String::format("%.2d:%.2d:%.2d", seconds / 3600 % 24, seconds / 60 % 60, seconds % 60);
|
||||
if (memcmp(savedMD5, currentMD5, 16) != 0) {
|
||||
debugC(1, kDebugLevelEventRec, "playback:action=\"Check screenshot\" time=%s result = fail", screenTime.c_str());
|
||||
warning("Recorded and current screenshots are different");
|
||||
} else {
|
||||
debugC(1, kDebugLevelEventRec, "playback:action=\"Check screenshot\" time=%s result = success", screenTime.c_str());
|
||||
}
|
||||
Graphics::saveThumbnail(*_screenshotsFile, screen);
|
||||
screen.free();
|
||||
}
|
||||
|
||||
|
||||
}
|
180
common/recorderfile.h
Normal file
|
@ -0,0 +1,180 @@
|
|||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef COMMON_RECORDERFILE_H
|
||||
#define COMMON_RECORDERFILE_H
|
||||
|
||||
#include "common/scummsys.h"
|
||||
#include "common/events.h"
|
||||
#include "common/mutex.h"
|
||||
#include "common/memstream.h"
|
||||
#include "common/config-manager.h"
|
||||
#include "common/savefile.h"
|
||||
|
||||
//capacity of records buffer
|
||||
#define kMaxBufferedRecords 10000
|
||||
#define kRecordBuffSize sizeof(RecorderEvent) * kMaxBufferedRecords
|
||||
|
||||
namespace Common {
|
||||
|
||||
enum RecorderEventType {
|
||||
kRecorderEventTypeNormal = 0,
|
||||
kRecorderEventTypeTimer = 1
|
||||
};
|
||||
|
||||
struct RecorderEvent : Event {
|
||||
RecorderEventType recordedtype;
|
||||
uint32 time;
|
||||
};
|
||||
|
||||
|
||||
|
||||
class PlaybackFile {
|
||||
typedef HashMap<String, uint32, IgnoreCase_Hash, IgnoreCase_EqualTo> RandomSeedsDictionary;
|
||||
enum fileMode {
|
||||
kRead = 0,
|
||||
kWrite = 1,
|
||||
kClosed = 2
|
||||
};
|
||||
enum PlaybackFileState {
|
||||
kFileStateCheckFormat,
|
||||
kFileStateCheckVersion,
|
||||
kFileStateProcessHash,
|
||||
kFileStateProcessHeader,
|
||||
kFileStateProcessRandom,
|
||||
kFileStateSelectSection,
|
||||
kFileStateProcessSettings,
|
||||
kFileStateProcessSave,
|
||||
kFileStateDone,
|
||||
kFileStateError
|
||||
};
|
||||
enum FileTag {
|
||||
kFormatIdTag = MKTAG('P','B','C','K'),
|
||||
kVersionTag = MKTAG('V','E','R','S'),
|
||||
kHeaderSectionTag = MKTAG('H','E','A','D'),
|
||||
kHashSectionTag = MKTAG('H','A','S','H'),
|
||||
kRandomSectionTag = MKTAG('R','A','N','D'),
|
||||
kEventTag = MKTAG('E','V','N','T'),
|
||||
kScreenShotTag = MKTAG('B','M','H','T'),
|
||||
kSettingsSectionTag = MKTAG('S','E','T','T'),
|
||||
kAuthorTag = MKTAG('H','A','U','T'),
|
||||
kCommentsTag = MKTAG('H','C','M','T'),
|
||||
kNameTag = MKTAG('H','N','A','M'),
|
||||
kHashRecordTag = MKTAG('H','R','C','D'),
|
||||
kRandomRecordTag = MKTAG('R','R','C','D'),
|
||||
kSettingsRecordTag = MKTAG('S','R','E','C'),
|
||||
kSettingsRecordKeyTag = MKTAG('S','K','E','Y'),
|
||||
kSettingsRecordValueTag = MKTAG('S','V','A','L'),
|
||||
kSaveTag = MKTAG('S','A','V','E'),
|
||||
kSaveRecordTag = MKTAG('R','S','A','V'),
|
||||
kSaveRecordNameTag = MKTAG('S','N','A','M'),
|
||||
kSaveRecordBufferTag = MKTAG('S','B','U','F'),
|
||||
kMD5Tag = MKTAG('M','D','5',' ')
|
||||
};
|
||||
struct ChunkHeader {
|
||||
FileTag id;
|
||||
uint32 len;
|
||||
};
|
||||
public:
|
||||
struct SaveFileBuffer {
|
||||
byte *buffer;
|
||||
uint32 size;
|
||||
};
|
||||
struct PlaybackFileHeader {
|
||||
String fileName;
|
||||
String author;
|
||||
String name;
|
||||
String notes;
|
||||
String description;
|
||||
StringMap hashRecords;
|
||||
StringMap settingsRecords;
|
||||
HashMap<String, SaveFileBuffer> saveFiles;
|
||||
RandomSeedsDictionary randomSourceRecords;
|
||||
};
|
||||
PlaybackFile();
|
||||
~PlaybackFile();
|
||||
|
||||
bool openWrite(const String &fileName);
|
||||
bool openRead(const String &fileName);
|
||||
void close();
|
||||
|
||||
RecorderEvent getNextEvent();
|
||||
void writeEvent(const RecorderEvent &event);
|
||||
|
||||
void saveScreenShot(Graphics::Surface &screen, byte md5[16]);
|
||||
Graphics::Surface *getScreenShot(int number);
|
||||
int getScreensCount();
|
||||
|
||||
bool isEventsBufferEmpty();
|
||||
PlaybackFileHeader &getHeader() {return _header;}
|
||||
void updateHeader();
|
||||
void addSaveFile(const String &fileName, InSaveFile *saveStream);
|
||||
private:
|
||||
WriteStream *_recordFile;
|
||||
WriteStream *_writeStream;
|
||||
WriteStream *_screenshotsFile;
|
||||
MemoryReadStream _tmpPlaybackFile;
|
||||
SeekableReadStream *_readStream;
|
||||
SeekableMemoryWriteStream _tmpRecordFile;
|
||||
|
||||
fileMode _mode;
|
||||
bool _headerDumped;
|
||||
int _recordCount;
|
||||
uint32 _eventsSize;
|
||||
byte _tmpBuffer[kRecordBuffSize];
|
||||
PlaybackFileHeader _header;
|
||||
PlaybackFileState _playbackParseState;
|
||||
|
||||
void skipHeader();
|
||||
bool parseHeader();
|
||||
bool processChunk(ChunkHeader &nextChunk);
|
||||
void returnToChunkHeader();
|
||||
|
||||
bool readSaveRecord();
|
||||
void checkRecordedMD5();
|
||||
bool readChunkHeader(ChunkHeader &nextChunk);
|
||||
void processRndSeedRecord(ChunkHeader chunk);
|
||||
bool processSettingsRecord();
|
||||
|
||||
bool checkPlaybackFileVersion();
|
||||
|
||||
void dumpHeaderToFile();
|
||||
void writeSaveFilesSection();
|
||||
void writeGameSettings();
|
||||
void writeHeaderSection();
|
||||
void writeGameHash();
|
||||
void writeRandomRecords();
|
||||
|
||||
void dumpRecordsToFile();
|
||||
|
||||
String readString(int len);
|
||||
void readHashMap(ChunkHeader chunk);
|
||||
|
||||
bool skipToNextScreenshot();
|
||||
void readEvent(RecorderEvent& event);
|
||||
void readEventsToBuffer(uint32 size);
|
||||
bool grabScreenAndComputeMD5(Graphics::Surface &screen, uint8 md5[16]);
|
||||
};
|
||||
|
||||
} // End of namespace Common
|
||||
|
||||
#endif
|
|
@ -30,6 +30,7 @@
|
|||
#include "common/taskbar.h"
|
||||
#include "common/updates.h"
|
||||
#include "common/textconsole.h"
|
||||
#include "gui/EventRecorder.h"
|
||||
|
||||
#include "backends/audiocd/default/default-audiocd.h"
|
||||
#include "backends/fs/fs-factory.h"
|
||||
|
@ -84,7 +85,7 @@ void OSystem::initBackend() {
|
|||
error("Backend failed to instantiate audio CD manager");
|
||||
if (!_eventManager)
|
||||
error("Backend failed to instantiate event manager");
|
||||
if (!_timerManager)
|
||||
if (!getTimerManager())
|
||||
error("Backend failed to instantiate timer manager");
|
||||
|
||||
// TODO: We currently don't check _savefileManager, because at least
|
||||
|
@ -152,3 +153,11 @@ Common::String OSystem::getDefaultConfigFileName() {
|
|||
Common::String OSystem::getSystemLanguage() const {
|
||||
return "en_US";
|
||||
}
|
||||
|
||||
Common::TimerManager *OSystem::getTimerManager() {
|
||||
return _timerManager;
|
||||
}
|
||||
|
||||
Common::SaveFileManager *OSystem::getSavefileManager() {
|
||||
return g_eventRec.getSaveManager(_savefileManager);
|
||||
}
|
||||
|
|
|
@ -890,8 +890,14 @@ public:
|
|||
/** @name Events and Time */
|
||||
//@{
|
||||
|
||||
/** Get the number of milliseconds since the program was started. */
|
||||
virtual uint32 getMillis() = 0;
|
||||
/** Get the number of milliseconds since the program was started.
|
||||
|
||||
@param skipRecord Skip recording of this value by event recorder.
|
||||
This could be needed particularly when we are in
|
||||
an on-screen GUI loop where player can pause
|
||||
the recording.
|
||||
*/
|
||||
virtual uint32 getMillis(bool skipRecord = false) = 0;
|
||||
|
||||
/** Delay/sleep for the specified amount of milliseconds. */
|
||||
virtual void delayMillis(uint msecs) = 0;
|
||||
|
@ -907,9 +913,7 @@ public:
|
|||
* Return the timer manager singleton. For more information, refer
|
||||
* to the TimerManager documentation.
|
||||
*/
|
||||
inline Common::TimerManager *getTimerManager() {
|
||||
return _timerManager;
|
||||
}
|
||||
virtual Common::TimerManager *getTimerManager();
|
||||
|
||||
/**
|
||||
* Return the event manager singleton. For more information, refer
|
||||
|
@ -1086,9 +1090,7 @@ public:
|
|||
* and other modifiable persistent game data. For more information,
|
||||
* refer to the SaveFileManager documentation.
|
||||
*/
|
||||
inline Common::SaveFileManager *getSavefileManager() {
|
||||
return _savefileManager;
|
||||
}
|
||||
Common::SaveFileManager *getSavefileManager();
|
||||
|
||||
#if defined(USE_TASKBAR)
|
||||
/**
|
||||
|
|
29
configure
vendored
|
@ -138,9 +138,10 @@ _build_hq_scalers=yes
|
|||
_enable_prof=no
|
||||
_global_constructors=no
|
||||
_bink=yes
|
||||
# Default vkeybd/keymapper options
|
||||
# Default vkeybd/keymapper/eventrec options
|
||||
_vkeybd=no
|
||||
_keymapper=no
|
||||
_eventrec=auto
|
||||
# GUI translation options
|
||||
_translation=yes
|
||||
# Default platform settings
|
||||
|
@ -1036,6 +1037,8 @@ for ac_option in $@; do
|
|||
--disable-vkeybd) _vkeybd=no ;;
|
||||
--enable-keymapper) _keymapper=yes ;;
|
||||
--disable-keymapper) _keymapper=no ;;
|
||||
--enable-eventrecorder) _eventrec=yes ;;
|
||||
--disable-eventrecorder) _eventrec=no ;;
|
||||
--enable-text-console) _text_console=yes ;;
|
||||
--disable-text-console) _text_console=no ;;
|
||||
--with-fluidsynth-prefix=*)
|
||||
|
@ -2881,6 +2884,20 @@ case $_backend in
|
|||
;;
|
||||
esac
|
||||
|
||||
#
|
||||
# Enable Event Recorder only for backends that support it
|
||||
#
|
||||
case $_backend in
|
||||
sdl)
|
||||
if test "$_eventrec" = auto ; then
|
||||
_eventrec=yes
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
_eventrec=no
|
||||
;;
|
||||
esac
|
||||
|
||||
#
|
||||
# Disable savegame timestamp support for backends which don't have a reliable real time clock
|
||||
#
|
||||
|
@ -3797,10 +3814,12 @@ fi
|
|||
define_in_config_if_yes $_nasm 'USE_NASM'
|
||||
|
||||
#
|
||||
# Enable vkeybd / keymapper
|
||||
# Enable vkeybd / keymapper / event recorder
|
||||
#
|
||||
define_in_config_if_yes $_vkeybd 'ENABLE_VKEYBD'
|
||||
define_in_config_if_yes $_keymapper 'ENABLE_KEYMAPPER'
|
||||
define_in_config_if_yes $_eventrec 'ENABLE_EVENTRECORDER'
|
||||
add_line_to_config_mk_if_yes $_eventrec "ENABLE_EVENTRECORDER = 1"
|
||||
|
||||
# Check whether to build translation support
|
||||
#
|
||||
|
@ -3946,7 +3965,11 @@ if test "$_vkeybd" = yes ; then
|
|||
fi
|
||||
|
||||
if test "$_keymapper" = yes ; then
|
||||
echo ", keymapper"
|
||||
echo_n ", keymapper"
|
||||
fi
|
||||
|
||||
if test "$_eventrec" = yes ; then
|
||||
echo ", event recorder"
|
||||
else
|
||||
echo
|
||||
fi
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
#include "common/system.h"
|
||||
#include "common/textconsole.h"
|
||||
#include "common/translation.h"
|
||||
|
||||
#include "gui/EventRecorder.h"
|
||||
#include "engines/advancedDetector.h"
|
||||
#include "engines/obsolete.h"
|
||||
|
||||
|
@ -301,6 +301,7 @@ Common::Error AdvancedMetaEngine::createInstance(OSystem *syst, Engine **engine)
|
|||
return Common::kUserCanceled;
|
||||
|
||||
debug(2, "Running %s", gameDescriptor.description().c_str());
|
||||
initSubSystems(agdDesc);
|
||||
if (!createInstance(syst, engine, agdDesc))
|
||||
return Common::kNoGameDataFoundError;
|
||||
else
|
||||
|
@ -606,3 +607,9 @@ AdvancedMetaEngine::AdvancedMetaEngine(const void *descs, uint descItemSize, con
|
|||
_maxScanDepth = 1;
|
||||
_directoryGlobs = NULL;
|
||||
}
|
||||
|
||||
void AdvancedMetaEngine::initSubSystems(const ADGameDescription *gameDesc) const {
|
||||
if (gameDesc) {
|
||||
g_eventRec.processGameDescription(gameDesc);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -280,6 +280,9 @@ protected:
|
|||
return 0;
|
||||
}
|
||||
|
||||
private:
|
||||
void initSubSystems(const ADGameDescription *gameDesc) const;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Detect games in specified directory.
|
||||
|
|
|
@ -25,7 +25,6 @@
|
|||
#include "common/debug.h"
|
||||
#include "common/debug-channels.h"
|
||||
#include "common/error.h"
|
||||
#include "common/EventRecorder.h"
|
||||
#include "common/file.h"
|
||||
#include "common/fs.h"
|
||||
#include "engines/advancedDetector.h"
|
||||
|
|
|
@ -23,7 +23,6 @@
|
|||
#include "common/config-manager.h"
|
||||
#include "common/debug-channels.h"
|
||||
#include "common/events.h"
|
||||
#include "common/EventRecorder.h"
|
||||
#include "common/file.h"
|
||||
#include "common/func.h"
|
||||
#include "common/system.h"
|
||||
|
|
|
@ -41,6 +41,7 @@
|
|||
#include "sword25/gfx/renderobjectmanager.h"
|
||||
|
||||
#include "common/system.h"
|
||||
#include "graphics/thumbnail.h"
|
||||
|
||||
namespace Sword25 {
|
||||
|
||||
|
@ -509,60 +510,4 @@ void RenderedImage::checkForTransparency() {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scales a passed surface, creating a new surface with the result
|
||||
* @param srcImage Source image to scale
|
||||
* @param scaleFactor Scale amount. Must be between 0 and 1.0 (but not zero)
|
||||
* @remarks Caller is responsible for freeing the returned surface
|
||||
*/
|
||||
Graphics::Surface *RenderedImage::scale(const Graphics::Surface &srcImage, int xSize, int ySize) {
|
||||
Graphics::Surface *s = new Graphics::Surface();
|
||||
s->create(xSize, ySize, srcImage.format);
|
||||
|
||||
int *horizUsage = scaleLine(xSize, srcImage.w);
|
||||
int *vertUsage = scaleLine(ySize, srcImage.h);
|
||||
|
||||
// Loop to create scaled version
|
||||
for (int yp = 0; yp < ySize; ++yp) {
|
||||
const byte *srcP = (const byte *)srcImage.getBasePtr(0, vertUsage[yp]);
|
||||
byte *destP = (byte *)s->getBasePtr(0, yp);
|
||||
|
||||
for (int xp = 0; xp < xSize; ++xp) {
|
||||
const byte *tempSrcP = srcP + (horizUsage[xp] * srcImage.format.bytesPerPixel);
|
||||
for (int byteCtr = 0; byteCtr < srcImage.format.bytesPerPixel; ++byteCtr) {
|
||||
*destP++ = *tempSrcP++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Delete arrays and return surface
|
||||
delete[] horizUsage;
|
||||
delete[] vertUsage;
|
||||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array indicating which pixels of a source image horizontally or vertically get
|
||||
* included in a scaled image
|
||||
*/
|
||||
int *RenderedImage::scaleLine(int size, int srcSize) {
|
||||
int scale = 100 * size / srcSize;
|
||||
assert(scale > 0);
|
||||
int *v = new int[size];
|
||||
Common::fill(v, &v[size], 0);
|
||||
|
||||
int distCtr = 0;
|
||||
int *destP = v;
|
||||
for (int distIndex = 0; distIndex < srcSize; ++distIndex) {
|
||||
distCtr += scale;
|
||||
while (distCtr >= 100) {
|
||||
assert(destP < &v[size]);
|
||||
*destP++ = distIndex;
|
||||
distCtr -= 100;
|
||||
}
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
} // End of namespace Sword25
|
||||
|
|
|
@ -104,8 +104,6 @@ public:
|
|||
return true;
|
||||
}
|
||||
|
||||
static Graphics::Surface *scale(const Graphics::Surface &srcImage, int xSize, int ySize);
|
||||
|
||||
void setIsTransparent(bool isTransparent) { _isTransparent = isTransparent; }
|
||||
virtual bool isSolid() const { return !_isTransparent; }
|
||||
|
||||
|
@ -119,7 +117,6 @@ private:
|
|||
Graphics::Surface *_backSurface;
|
||||
|
||||
void checkForTransparency();
|
||||
static int *scaleLine(int size, int srcSize);
|
||||
};
|
||||
|
||||
} // End of namespace Sword25
|
||||
|
|
|
@ -26,7 +26,6 @@
|
|||
#include "common/debug.h"
|
||||
#include "common/debug-channels.h"
|
||||
#include "common/error.h"
|
||||
#include "common/EventRecorder.h"
|
||||
#include "common/file.h"
|
||||
#include "common/fs.h"
|
||||
#include "common/tokenizer.h"
|
||||
|
|
|
@ -48,6 +48,9 @@ bool CursorManager::isVisible() {
|
|||
bool CursorManager::showMouse(bool visible) {
|
||||
if (_cursorStack.empty())
|
||||
return false;
|
||||
if (_locked) {
|
||||
return false;
|
||||
}
|
||||
|
||||
_cursorStack.top()->_visible = visible;
|
||||
|
||||
|
@ -225,6 +228,10 @@ void CursorManager::replaceCursorPalette(const byte *colors, uint start, uint nu
|
|||
}
|
||||
}
|
||||
|
||||
void CursorManager::lock(bool locked) {
|
||||
_locked = locked;
|
||||
}
|
||||
|
||||
CursorManager::Cursor::Cursor(const void *data, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor, bool dontScale, const Graphics::PixelFormat *format) {
|
||||
#ifdef USE_RGB_COLOR
|
||||
if (!format)
|
||||
|
|
|
@ -160,12 +160,15 @@ public:
|
|||
*/
|
||||
void replaceCursorPalette(const byte *colors, uint start, uint num);
|
||||
|
||||
void lock(bool locked);
|
||||
private:
|
||||
friend class Common::Singleton<SingletonBaseType>;
|
||||
// Even though this is basically the default constructor we implement it
|
||||
// ourselves, so it is private and thus there is no way to create this class
|
||||
// except from the Singleton code.
|
||||
CursorManager() {}
|
||||
CursorManager() {
|
||||
_locked = false;
|
||||
}
|
||||
~CursorManager();
|
||||
|
||||
struct Cursor {
|
||||
|
@ -198,6 +201,7 @@ private:
|
|||
};
|
||||
Common::Stack<Cursor *> _cursorStack;
|
||||
Common::Stack<Palette *> _cursorPaletteStack;
|
||||
bool _locked;
|
||||
};
|
||||
|
||||
} // End of namespace Graphics
|
||||
|
|
|
@ -89,4 +89,10 @@ extern bool createThumbnailFromScreen(Graphics::Surface *surf);
|
|||
*/
|
||||
extern bool createThumbnail(Graphics::Surface *surf, const uint8 *pixels, int w, int h, const uint8 *palette);
|
||||
|
||||
/**
|
||||
* Downscale screenshot to thumbnale size.
|
||||
*
|
||||
*/
|
||||
extern bool createThumbnail(Graphics::Surface &out, Graphics::Surface &in);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -134,7 +134,7 @@ static bool grabScreen565(Graphics::Surface *surf) {
|
|||
return true;
|
||||
}
|
||||
|
||||
static bool createThumbnail(Graphics::Surface &out, Graphics::Surface &in) {
|
||||
bool createThumbnail(Graphics::Surface &out, Graphics::Surface &in) {
|
||||
uint16 width = in.w;
|
||||
uint16 inHeight = in.h;
|
||||
|
||||
|
@ -206,7 +206,7 @@ static bool createThumbnail(Graphics::Surface &out, Graphics::Surface &in) {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool createThumbnailFromScreen(Graphics::Surface* surf) {
|
||||
bool createThumbnailFromScreen(Graphics::Surface *surf) {
|
||||
assert(surf);
|
||||
|
||||
Graphics::Surface screen;
|
||||
|
@ -236,3 +236,31 @@ bool createThumbnail(Graphics::Surface *surf, const uint8 *pixels, int w, int h,
|
|||
|
||||
return createThumbnail(*surf, screen);
|
||||
}
|
||||
|
||||
// this is somewhat awkward, but createScreenShot should logically be in graphics,
|
||||
// but moving other functions in this file into that namespace breaks several engines
|
||||
namespace Graphics {
|
||||
bool createScreenShot(Graphics::Surface &surf) {
|
||||
Graphics::PixelFormat screenFormat = g_system->getScreenFormat();
|
||||
//convert surface to 2 bytes pixel format to avoid problems with palette saving and loading
|
||||
if ((screenFormat.bytesPerPixel == 1) || (screenFormat.bytesPerPixel == 2)) {
|
||||
return grabScreen565(&surf);
|
||||
} else {
|
||||
Graphics::Surface *screen = g_system->lockScreen();
|
||||
if (!screen) {
|
||||
return false;
|
||||
}
|
||||
surf.create(screen->w, screen->h, Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0));
|
||||
for (uint y = 0; y < screen->h; ++y) {
|
||||
for (uint x = 0; x < screen->w; ++x) {
|
||||
byte r = 0, g = 0, b = 0, a = 0;
|
||||
uint32 col = READ_UINT32(screen->getBasePtr(x, y));
|
||||
screenFormat.colorToARGB(col, a, r, g, b);
|
||||
((uint32 *)surf.pixels)[y * surf.w + x] = Graphics::ARGBToColor<Graphics::ColorMasks<8888> >(a, r, g, b);
|
||||
}
|
||||
}
|
||||
g_system->unlockScreen();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} // End of namespace Graphics
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
#include "graphics/scaler.h"
|
||||
#include "graphics/colormasks.h"
|
||||
#include "common/endian.h"
|
||||
#include "common/algorithm.h"
|
||||
#include "common/system.h"
|
||||
#include "common/stream.h"
|
||||
#include "common/textconsole.h"
|
||||
|
@ -143,7 +144,6 @@ Graphics::Surface *loadThumbnail(Common::SeekableReadStream &in) {
|
|||
assert(0);
|
||||
}
|
||||
}
|
||||
|
||||
return to;
|
||||
}
|
||||
|
||||
|
@ -216,4 +216,55 @@ bool saveThumbnail(Common::WriteStream &out, const Graphics::Surface &thumb) {
|
|||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns an array indicating which pixels of a source image horizontally or vertically get
|
||||
* included in a scaled image
|
||||
*/
|
||||
int *scaleLine(int size, int srcSize) {
|
||||
int scale = 100 * size / srcSize;
|
||||
assert(scale > 0);
|
||||
int *v = new int[size];
|
||||
Common::fill(v, &v[size], 0);
|
||||
|
||||
int distCtr = 0;
|
||||
int *destP = v;
|
||||
for (int distIndex = 0; distIndex < srcSize; ++distIndex) {
|
||||
distCtr += scale;
|
||||
while (distCtr >= 100) {
|
||||
assert(destP < &v[size]);
|
||||
*destP++ = distIndex;
|
||||
distCtr -= 100;
|
||||
}
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
Graphics::Surface *scale(const Graphics::Surface &srcImage, int xSize, int ySize) {
|
||||
Graphics::Surface *s = new Graphics::Surface();
|
||||
s->create(xSize, ySize, srcImage.format);
|
||||
|
||||
int *horizUsage = scaleLine(xSize, srcImage.w);
|
||||
int *vertUsage = scaleLine(ySize, srcImage.h);
|
||||
|
||||
// Loop to create scaled version
|
||||
for (int yp = 0; yp < ySize; ++yp) {
|
||||
const byte *srcP = (const byte *)srcImage.getBasePtr(0, vertUsage[yp]);
|
||||
byte *destP = (byte *)s->getBasePtr(0, yp);
|
||||
|
||||
for (int xp = 0; xp < xSize; ++xp) {
|
||||
const byte *tempSrcP = srcP + (horizUsage[xp] * srcImage.format.bytesPerPixel);
|
||||
for (int byteCtr = 0; byteCtr < srcImage.format.bytesPerPixel; ++byteCtr) {
|
||||
*destP++ = *tempSrcP++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Delete arrays and return surface
|
||||
delete[] horizUsage;
|
||||
delete[] vertUsage;
|
||||
return s;
|
||||
}
|
||||
|
||||
} // End of namespace Graphics
|
||||
|
|
|
@ -64,6 +64,24 @@ bool saveThumbnail(Common::WriteStream &out);
|
|||
*/
|
||||
bool saveThumbnail(Common::WriteStream &out, const Graphics::Surface &thumb);
|
||||
|
||||
/**
|
||||
* Grabs framebuffer into surface
|
||||
*
|
||||
* @param surf a surface
|
||||
* @return false if a error occurred
|
||||
*/
|
||||
bool createScreenShot(Graphics::Surface &surf);
|
||||
|
||||
/**
|
||||
* Scales a passed surface, creating a new surface with the result
|
||||
* @param srcImage Source image to scale
|
||||
* @param xSize New surface width
|
||||
* @param ySize New surface height
|
||||
* @remarks Caller is responsible for freeing the returned surface
|
||||
*/
|
||||
Graphics::Surface *scale(const Graphics::Surface &srcImage, int xSize, int ySize);
|
||||
|
||||
|
||||
} // End of namespace Graphics
|
||||
|
||||
#endif
|
||||
|
|
699
gui/EventRecorder.cpp
Normal file
|
@ -0,0 +1,699 @@
|
|||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include "gui/EventRecorder.h"
|
||||
|
||||
namespace Common {
|
||||
DECLARE_SINGLETON(GUI::EventRecorder);
|
||||
}
|
||||
|
||||
#ifdef ENABLE_EVENTRECORDER
|
||||
|
||||
#include "common/debug-channels.h"
|
||||
#include "backends/timer/sdl/sdl-timer.h"
|
||||
#include "backends/mixer/sdl/sdl-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;
|
||||
const int kDefaultBPP = 2;
|
||||
|
||||
uint32 readTime(Common::ReadStream *inFile) {
|
||||
uint32 d = inFile->readByte();
|
||||
if (d == 0xff) {
|
||||
d = inFile->readUint32LE();
|
||||
}
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
void writeTime(Common::WriteStream *outFile, uint32 d) {
|
||||
//Simple RLE compression
|
||||
if (d >= 0xff) {
|
||||
outFile->writeByte(0xff);
|
||||
outFile->writeUint32LE(d);
|
||||
} else {
|
||||
outFile->writeByte(d);
|
||||
}
|
||||
}
|
||||
|
||||
EventRecorder::EventRecorder() {
|
||||
_timerManager = NULL;
|
||||
_recordMode = kPassthrough;
|
||||
_fakeMixerManager = NULL;
|
||||
_initialized = false;
|
||||
_needRedraw = false;
|
||||
_fastPlayback = false;
|
||||
DebugMan.addDebugChannel(kDebugLevelEventRec, "EventRec", "Event recorder debug level");
|
||||
}
|
||||
|
||||
EventRecorder::~EventRecorder() {
|
||||
if (_timerManager != NULL) {
|
||||
delete _timerManager;
|
||||
}
|
||||
}
|
||||
|
||||
void EventRecorder::deinit() {
|
||||
if (!_initialized) {
|
||||
return;
|
||||
}
|
||||
setFileHeader();
|
||||
_needRedraw = false;
|
||||
_initialized = false;
|
||||
_recordMode = kPassthrough;
|
||||
delete _fakeMixerManager;
|
||||
_fakeMixerManager = NULL;
|
||||
controlPanel->close();
|
||||
delete controlPanel;
|
||||
debugC(1, kDebugLevelEventRec, "playback:action=stopplayback");
|
||||
g_system->getEventManager()->getEventDispatcher()->unregisterSource(this);
|
||||
_recordMode = kPassthrough;
|
||||
_playbackFile->close();
|
||||
delete _playbackFile;
|
||||
switchMixer();
|
||||
switchTimerManagers();
|
||||
DebugMan.disableDebugChannel("EventRec");
|
||||
}
|
||||
|
||||
void EventRecorder::processMillis(uint32 &millis, bool skipRecord) {
|
||||
if (!_initialized) {
|
||||
return;
|
||||
}
|
||||
if (skipRecord) {
|
||||
millis = _fakeTimer;
|
||||
return;
|
||||
}
|
||||
if (_recordMode == kRecorderPlaybackPause) {
|
||||
millis = _fakeTimer;
|
||||
}
|
||||
uint32 millisDelay;
|
||||
Common::RecorderEvent timerEvent;
|
||||
switch (_recordMode) {
|
||||
case kRecorderRecord:
|
||||
updateSubsystems();
|
||||
millisDelay = millis - _lastMillis;
|
||||
_lastMillis = millis;
|
||||
_fakeTimer += millisDelay;
|
||||
controlPanel->setReplayedTime(_fakeTimer);
|
||||
timerEvent.recordedtype = Common::kRecorderEventTypeTimer;
|
||||
timerEvent.time = _fakeTimer;
|
||||
_playbackFile->writeEvent(timerEvent);
|
||||
takeScreenshot();
|
||||
_timerManager->handler();
|
||||
break;
|
||||
case kRecorderPlayback:
|
||||
updateSubsystems();
|
||||
if (_nextEvent.recordedtype == Common::kRecorderEventTypeTimer) {
|
||||
_fakeTimer = _nextEvent.time;
|
||||
_nextEvent = _playbackFile->getNextEvent();
|
||||
_timerManager->handler();
|
||||
} else {
|
||||
if (_nextEvent.type == Common::EVENT_RTL) {
|
||||
error("playback:action=stopplayback");
|
||||
} else {
|
||||
uint32 seconds = _fakeTimer / 1000;
|
||||
Common::String screenTime = Common::String::format("%.2d:%.2d:%.2d", seconds / 3600 % 24, seconds / 60 % 60, seconds % 60);
|
||||
error("playback:action=error reason=\"synchronization error\" time = %s", screenTime.c_str());
|
||||
}
|
||||
}
|
||||
millis = _fakeTimer;
|
||||
controlPanel->setReplayedTime(_fakeTimer);
|
||||
break;
|
||||
case kRecorderPlaybackPause:
|
||||
millis = _fakeTimer;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool EventRecorder::processDelayMillis() {
|
||||
return _fastPlayback;
|
||||
}
|
||||
|
||||
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.synthetic)) {
|
||||
togglePause();
|
||||
}
|
||||
}
|
||||
|
||||
bool EventRecorder::pollEvent(Common::Event &ev) {
|
||||
if ((_recordMode != kRecorderPlayback) || !_initialized)
|
||||
return false;
|
||||
|
||||
if ((_nextEvent.recordedtype == Common::kRecorderEventTypeTimer) || (_nextEvent.type == Common::EVENT_INVALID)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (_nextEvent.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(_nextEvent.mouse.x, _nextEvent.mouse.y);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
ev = _nextEvent;
|
||||
_nextEvent = _playbackFile->getNextEvent();
|
||||
return true;
|
||||
}
|
||||
|
||||
void EventRecorder::switchFastMode() {
|
||||
if (_recordMode == kRecorderPlaybackPause) {
|
||||
_fastPlayback = !_fastPlayback;
|
||||
}
|
||||
}
|
||||
|
||||
void EventRecorder::togglePause() {
|
||||
RecordMode oldState;
|
||||
switch (_recordMode) {
|
||||
case kRecorderPlayback:
|
||||
case kRecorderRecord:
|
||||
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()->registerMapper(this, false);
|
||||
}
|
||||
|
||||
uint32 EventRecorder::getRandomSeed(const Common::String &name) {
|
||||
uint32 result = g_system->getMillis();
|
||||
if (_recordMode == kRecorderRecord) {
|
||||
_playbackFile->getHeader().randomSourceRecords[name] = result;
|
||||
} else if (_recordMode == kRecorderPlayback) {
|
||||
result = _playbackFile->getHeader().randomSourceRecords[name];
|
||||
}
|
||||
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::init(Common::String recordFileName, RecordMode mode) {
|
||||
_fakeMixerManager = new NullSdlMixerManager();
|
||||
_fakeMixerManager->init();
|
||||
_fakeMixerManager->suspendAudio();
|
||||
_fakeTimer = 0;
|
||||
_lastMillis = g_system->getMillis();
|
||||
_playbackFile = new Common::PlaybackFile();
|
||||
_lastScreenshotTime = 0;
|
||||
_recordMode = mode;
|
||||
_needcontinueGame = false;
|
||||
if (ConfMan.hasKey("disable_display")) {
|
||||
DebugMan.enableDebugChannel("EventRec");
|
||||
gDebugLevel = 1;
|
||||
}
|
||||
if (_recordMode == kRecorderPlayback) {
|
||||
debugC(1, kDebugLevelEventRec, "playback:action=\"Load file\" filename=%s", recordFileName.c_str());
|
||||
}
|
||||
g_system->getEventManager()->getEventDispatcher()->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);
|
||||
}
|
||||
if (_recordMode == kRecorderPlayback) {
|
||||
applyPlaybackSettings();
|
||||
_nextEvent = _playbackFile->getNextEvent();
|
||||
}
|
||||
if (_recordMode == kRecorderRecord) {
|
||||
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:
|
||||
return _playbackFile->openWrite(fileName);
|
||||
case kRecorderPlayback:
|
||||
_recordMode = kPassthrough;
|
||||
result = _playbackFile->openRead(fileName);
|
||||
_recordMode = kRecorderPlayback;
|
||||
return result;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EventRecorder::checkGameHash(const ADGameDescription *gameDesc) {
|
||||
if ((gameDesc == NULL) && (_playbackFile->getHeader().hashRecords.size() != 0)) {
|
||||
warning("Engine doesn't contain description table");
|
||||
return false;
|
||||
}
|
||||
for (const ADGameFileDescription *fileDesc = gameDesc->filesDescriptions; fileDesc->fileName; fileDesc++) {
|
||||
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(SdlMixerManager *mixerManager) {
|
||||
_realMixerManager = mixerManager;
|
||||
}
|
||||
|
||||
void EventRecorder::switchMixer() {
|
||||
if (_recordMode == kPassthrough) {
|
||||
_realMixerManager->resumeAudio();
|
||||
} else {
|
||||
_realMixerManager->suspendAudio();
|
||||
_fakeMixerManager->resumeAudio();
|
||||
}
|
||||
}
|
||||
|
||||
SdlMixerManager *EventRecorder::getMixerManager() {
|
||||
if (_recordMode == kPassthrough) {
|
||||
return _realMixerManager;
|
||||
} else {
|
||||
return _fakeMixerManager;
|
||||
}
|
||||
}
|
||||
|
||||
void EventRecorder::getConfigFromDomain(Common::ConfigManager::Domain *domain) {
|
||||
for (Common::ConfigManager::Domain::iterator entry = domain->begin(); entry!= domain->end(); ++entry) {
|
||||
_playbackFile->getHeader().settingsRecords[entry->_key] = entry->_value;
|
||||
}
|
||||
}
|
||||
|
||||
void EventRecorder::getConfig() {
|
||||
getConfigFromDomain(ConfMan.getDomain(ConfMan.kApplicationDomain));
|
||||
getConfigFromDomain(ConfMan.getActiveDomain());
|
||||
_playbackFile->getHeader().settingsRecords["save_slot"] = ConfMan.get("save_slot");
|
||||
}
|
||||
|
||||
|
||||
void EventRecorder::applyPlaybackSettings() {
|
||||
for (Common::StringMap::iterator i = _playbackFile->getHeader().settingsRecords.begin(); i != _playbackFile->getHeader().settingsRecords.end(); ++i) {
|
||||
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::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;
|
||||
}
|
||||
|
||||
Common::List<Common::Event> EventRecorder::mapEvent(const Common::Event &ev, Common::EventSource *source) {
|
||||
if ((!_initialized) && (_recordMode != kRecorderPlaybackPause)) {
|
||||
return DefaultEventMapper::mapEvent(ev, source);
|
||||
}
|
||||
|
||||
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 kRecorderPlayback:
|
||||
if (ev.synthetic != true) {
|
||||
return Common::List<Common::Event>();
|
||||
}
|
||||
return Common::DefaultEventMapper::mapEvent(ev, source);
|
||||
break;
|
||||
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 Common::List<Common::Event>();
|
||||
} else {
|
||||
Common::RecorderEvent e;
|
||||
memcpy(&e, &ev, sizeof(ev));
|
||||
e.recordedtype = Common::kRecorderEventTypeNormal;
|
||||
e.time = _fakeTimer;
|
||||
_playbackFile->writeEvent(e);
|
||||
return DefaultEventMapper::mapEvent(ev, source);
|
||||
}
|
||||
break;
|
||||
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 Common::List<Common::Event>();
|
||||
}
|
||||
return Common::DefaultEventMapper::mapEvent(dialogEvent, source);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return Common::DefaultEventMapper::mapEvent(ev, source);
|
||||
}
|
||||
}
|
||||
|
||||
void EventRecorder::setGameMd5(const ADGameDescription *gameDesc) {
|
||||
for (const ADGameFileDescription *fileDesc = gameDesc->filesDescriptions; fileDesc->fileName; fileDesc++) {
|
||||
if (fileDesc->md5 != NULL) {
|
||||
_playbackFile->getHeader().hashRecords[fileDesc->fileName] = fileDesc->md5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EventRecorder::processGameDescription(const ADGameDescription *desc) {
|
||||
if (_recordMode == kRecorderRecord) {
|
||||
setGameMd5(desc);
|
||||
}
|
||||
if ((_recordMode == kRecorderPlayback) && !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;
|
||||
_playbackFile->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.pixels, 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 != NULL) {
|
||||
_playbackFile->addSaveFile(fileName, saveFile);
|
||||
saveFile->seek(0);
|
||||
}
|
||||
return saveFile;
|
||||
default:
|
||||
return NULL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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.theme()->clearAll();
|
||||
g_gui.theme()->openDialog(true, GUI::ThemeEngine::kShadingNone);
|
||||
controlPanel->drawDialog();
|
||||
g_gui.theme()->finishBuffering();
|
||||
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) {
|
||||
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, true)) {
|
||||
result.push_back(i->_key);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
} else {
|
||||
return _realSaveManager->listSavefiles(pattern);
|
||||
}
|
||||
}
|
||||
|
||||
void EventRecorder::setFileHeader() {
|
||||
if (_recordMode != kRecorderRecord) {
|
||||
return;
|
||||
}
|
||||
TimeDate t;
|
||||
const EnginePlugin *plugin = 0;
|
||||
GameDescriptor desc = EngineMan.findGame(ConfMan.getActiveDomainName(), &plugin);
|
||||
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, 1900 + t.tm_year) + desc.description());
|
||||
}
|
||||
_playbackFile->getHeader().author = _author;
|
||||
_playbackFile->getHeader().notes = _desc;
|
||||
_playbackFile->getHeader().name = _name;
|
||||
}
|
||||
|
||||
SDL_Surface *EventRecorder::getSurface(int width, int height) {
|
||||
SDL_Surface *surface = new SDL_Surface();
|
||||
surface->format = new SDL_PixelFormat();
|
||||
surface->flags = 0;
|
||||
surface->format->palette = NULL;
|
||||
surface->format->BitsPerPixel = 16;
|
||||
surface->format->BytesPerPixel = 2;
|
||||
surface->format->Rloss = 3;
|
||||
surface->format->Gloss = 2;
|
||||
surface->format->Bloss = 3;
|
||||
surface->format->Aloss = 8;
|
||||
surface->format->Rshift = 11;
|
||||
surface->format->Gshift = 5;
|
||||
surface->format->Bshift = 0;
|
||||
surface->format->Ashift = 0;
|
||||
surface->format->Rmask = 63488;
|
||||
surface->format->Gmask = 2016;
|
||||
surface->format->Bmask = 31;
|
||||
surface->format->Amask = 0;
|
||||
surface->format->colorkey = 0;
|
||||
surface->format->alpha = 255;
|
||||
surface->w = width;
|
||||
surface->h = height;
|
||||
surface->pitch = width * 2;
|
||||
surface->pixels = (char *)malloc(surface->pitch * surface->h);
|
||||
surface->offset = 0;
|
||||
surface->hwdata = NULL;
|
||||
surface->clip_rect.x = 0;
|
||||
surface->clip_rect.y = 0;
|
||||
surface->clip_rect.w = width;
|
||||
surface->clip_rect.h = height;
|
||||
surface->unused1 = 0;
|
||||
surface->locked = 0;
|
||||
surface->map = NULL;
|
||||
surface->format_version = 4;
|
||||
surface->refcount = 1;
|
||||
return surface;
|
||||
}
|
||||
|
||||
bool EventRecorder::switchMode() {
|
||||
const Common::String gameId = ConfMan.get("gameid");
|
||||
const EnginePlugin *plugin = 0;
|
||||
EngineMan.findGame(gameId, &plugin);
|
||||
bool metaInfoSupport = (*plugin)->hasFeature(MetaEngine::kSavesSupportMetaInfo);
|
||||
bool featuresSupport = metaInfoSupport &&
|
||||
g_engine->canSaveGameStateCurrently() &&
|
||||
(*plugin)->hasFeature(MetaEngine::kSupportsListSaves) &&
|
||||
(*plugin)->hasFeature(MetaEngine::kSupportsDeleteSave);
|
||||
if (!featuresSupport) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int emptySlot = 1;
|
||||
SaveStateList saveList = (*plugin)->listSaves(gameId.c_str());
|
||||
for (SaveStateList::const_iterator x = saveList.begin(); x != saveList.end(); ++x) {
|
||||
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 eventRTL;
|
||||
eventRTL.type = Common::EVENT_RTL;
|
||||
g_system->getEventManager()->pushEvent(eventRTL);
|
||||
}
|
||||
}
|
||||
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 Common::String gameId = ConfMan.get("gameid");
|
||||
const EnginePlugin *plugin = 0;
|
||||
EngineMan.findGame(gameId, &plugin);
|
||||
(*plugin)->removeSaveState(gameId.c_str(), _temporarySlot);
|
||||
_temporarySlot = -1;
|
||||
}
|
||||
|
||||
} // End of namespace GUI
|
||||
|
||||
#endif // ENABLE_EVENTRECORDER
|
||||
|
293
gui/EventRecorder.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 2
|
||||
* of the License, or (at your option) any later version.
|
||||
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#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/keymapper/keymapper.h"
|
||||
#include "backends/mixer/sdl/sdl-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/nullmixer/nullsdl-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::DefaultEventMapper {
|
||||
friend class Common::Singleton<SingletonBaseType>;
|
||||
EventRecorder();
|
||||
~EventRecorder();
|
||||
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 /**< kRecordetPlaybackPause, interal state when user pauses the playback */
|
||||
};
|
||||
|
||||
void init(Common::String recordFileName, RecordMode mode);
|
||||
void deinit();
|
||||
bool processDelayMillis();
|
||||
uint32 getRandomSeed(const Common::String &name);
|
||||
void processMillis(uint32 &millis, bool skipRecord);
|
||||
bool processAudio(uint32 &samples, bool paused);
|
||||
void processGameDescription(const ADGameDescription *desc);
|
||||
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(SdlMixerManager *mixerManager);
|
||||
void registerTimerManager(DefaultTimerManager *timerManager);
|
||||
|
||||
SdlMixerManager *getMixerManager();
|
||||
DefaultTimerManager *getTimerManager();
|
||||
|
||||
void deleteRecord(const Common::String& fileName);
|
||||
bool checkForContinueGame();
|
||||
|
||||
void suspendRecording() {
|
||||
_savedState = _initialized;
|
||||
_initialized = false;
|
||||
}
|
||||
|
||||
void resumeRecording() {
|
||||
_initialized = _savedState;
|
||||
}
|
||||
|
||||
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:
|
||||
virtual Common::List<Common::Event> mapEvent(const Common::Event &ev, Common::EventSource *source);
|
||||
bool notifyPoll();
|
||||
bool pollEvent(Common::Event &ev);
|
||||
bool _initialized;
|
||||
volatile uint32 _fakeTimer;
|
||||
bool _savedState;
|
||||
bool _needcontinueGame;
|
||||
int _temporarySlot;
|
||||
Common::String _author;
|
||||
Common::String _desc;
|
||||
Common::String _name;
|
||||
|
||||
Common::SaveFileManager *_realSaveManager;
|
||||
SdlMixerManager *_realMixerManager;
|
||||
DefaultTimerManager *_timerManager;
|
||||
RecorderSaveFileManager _fakeSaveManager;
|
||||
NullSdlMixerManager *_fakeMixerManager;
|
||||
GUI::OnScreenDialog *controlPanel;
|
||||
Common::RecorderEvent _nextEvent;
|
||||
|
||||
void setFileHeader();
|
||||
void setGameMd5(const ADGameDescription *gameDesc);
|
||||
void getConfig();
|
||||
void getConfigFromDomain(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);
|
||||
bool allowMapping() const { return false; }
|
||||
|
||||
volatile uint32 _lastMillis;
|
||||
uint32 _lastScreenshotTime;
|
||||
uint32 _screenshotPeriod;
|
||||
Common::PlaybackFile *_playbackFile;
|
||||
|
||||
void saveScreenShot();
|
||||
void checkRecordedMD5();
|
||||
void deleteTemporarySave();
|
||||
volatile RecordMode _recordMode;
|
||||
Common::String _recordFileName;
|
||||
bool _fastPlayback;
|
||||
bool _needRedraw;
|
||||
};
|
||||
|
||||
} // End of namespace GUI
|
||||
|
||||
#else
|
||||
|
||||
#ifdef SDL_BACKEND
|
||||
#include "backends/timer/default/default-timer.h"
|
||||
#include "backends/mixer/sdl/sdl-mixer.h"
|
||||
#endif
|
||||
|
||||
#define g_eventRec (GUI::EventRecorder::instance())
|
||||
|
||||
namespace GUI {
|
||||
|
||||
class EventRecorder : private Common::EventSource, public Common::Singleton<EventRecorder>, private Common::DefaultEventMapper {
|
||||
friend class Common::Singleton<SingletonBaseType>;
|
||||
|
||||
public:
|
||||
EventRecorder() {
|
||||
#ifdef SDL_BACKEND
|
||||
_timerManager = NULL;
|
||||
_realMixerManager = NULL;
|
||||
#endif
|
||||
}
|
||||
~EventRecorder() {}
|
||||
|
||||
bool pollEvent(Common::Event &ev) { return false; }
|
||||
void RegisterEventSource() {}
|
||||
void deinit() {}
|
||||
void suspendRecording() {}
|
||||
void resumeRecording() {}
|
||||
void preDrawOverlayGui() {}
|
||||
void postDrawOverlayGui() {}
|
||||
void processGameDescription(const ADGameDescription *desc) {}
|
||||
void updateSubsystems() {}
|
||||
uint32 getRandomSeed(const Common::String &name) { return g_system->getMillis(); }
|
||||
Common::SaveFileManager *getSaveManager(Common::SaveFileManager *realSaveManager) { return realSaveManager; }
|
||||
|
||||
#ifdef SDL_BACKEND
|
||||
private:
|
||||
DefaultTimerManager *_timerManager;
|
||||
SdlMixerManager *_realMixerManager;
|
||||
|
||||
public:
|
||||
DefaultTimerManager *getTimerManager() { return _timerManager; }
|
||||
void registerTimerManager(DefaultTimerManager *timerManager) { _timerManager = timerManager; }
|
||||
|
||||
SdlMixerManager *getMixerManager() { return _realMixerManager; }
|
||||
void registerMixerManager(SdlMixerManager *mixerManager) { _realMixerManager = mixerManager; }
|
||||
|
||||
void processMillis(uint32 &millis, bool skipRecord) {}
|
||||
bool processDelayMillis() { return false; }
|
||||
#endif
|
||||
|
||||
};
|
||||
|
||||
} // namespace GUI
|
||||
|
||||
#endif // ENABLE_EVENTRECORDER
|
||||
|
||||
#endif
|
|
@ -50,6 +50,14 @@ const char * const ThemeEngine::kImageEraser = "eraser.bmp";
|
|||
const char * const ThemeEngine::kImageDelbtn = "delbtn.bmp";
|
||||
const char * const ThemeEngine::kImageList = "list.bmp";
|
||||
const char * const ThemeEngine::kImageGrid = "grid.bmp";
|
||||
const char * const ThemeEngine::kImageStopbtn = "stopbtn.bmp";
|
||||
const char * const ThemeEngine::kImageEditbtn = "editbtn.bmp";
|
||||
const char * const ThemeEngine::kImageSwitchModebtn = "switchbtn.bmp";
|
||||
const char * const ThemeEngine::kImageFastReplaybtn = "fastreplay.bmp";
|
||||
const char * const ThemeEngine::kImageStopSmallbtn = "stopbtn_small.bmp";
|
||||
const char * const ThemeEngine::kImageEditSmallbtn = "editbtn_small.bmp";
|
||||
const char * const ThemeEngine::kImageSwitchModeSmallbtn = "switchbtn_small.bmp";
|
||||
const char * const ThemeEngine::kImageFastReplaySmallbtn = "fastreplay_small.bmp";
|
||||
|
||||
struct TextDrawData {
|
||||
const Graphics::Font *_fontPtr;
|
||||
|
@ -465,11 +473,7 @@ void ThemeEngine::enable() {
|
|||
if (_enabled)
|
||||
return;
|
||||
|
||||
if (_useCursor) {
|
||||
CursorMan.pushCursorPalette(_cursorPal, 0, _cursorPalSize);
|
||||
CursorMan.pushCursor(_cursor, _cursorWidth, _cursorHeight, _cursorHotspotX, _cursorHotspotY, 255, true);
|
||||
CursorMan.showMouse(true);
|
||||
}
|
||||
showCursor();
|
||||
|
||||
_system->showOverlay();
|
||||
clearAll();
|
||||
|
@ -482,10 +486,8 @@ void ThemeEngine::disable() {
|
|||
|
||||
_system->hideOverlay();
|
||||
|
||||
if (_useCursor) {
|
||||
CursorMan.popCursorPalette();
|
||||
CursorMan.popCursor();
|
||||
}
|
||||
hideCursor();
|
||||
|
||||
|
||||
_enabled = false;
|
||||
}
|
||||
|
@ -1787,5 +1789,20 @@ Common::String ThemeEngine::getThemeId(const Common::String &filename) {
|
|||
}
|
||||
}
|
||||
|
||||
void ThemeEngine::showCursor() {
|
||||
if (_useCursor) {
|
||||
CursorMan.pushCursorPalette(_cursorPal, 0, _cursorPalSize);
|
||||
CursorMan.pushCursor(_cursor, _cursorWidth, _cursorHeight, _cursorHotspotX, _cursorHotspotY, 255, true);
|
||||
CursorMan.showMouse(true);
|
||||
}
|
||||
}
|
||||
|
||||
void ThemeEngine::hideCursor() {
|
||||
if (_useCursor) {
|
||||
CursorMan.popCursorPalette();
|
||||
CursorMan.popCursor();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // End of namespace GUI.
|
||||
|
|
|
@ -234,6 +234,14 @@ public:
|
|||
static const char *const kImageDelbtn; ///< 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 kImageStopbtn; ///< Stop recording button in recorder onscreen dialog
|
||||
static const char *const kImageEditbtn; ///< Edit recording metadata in recorder onscreen dialog
|
||||
static const char *const kImageSwitchModebtn; ///< Switch mode button in recorder onscreen dialog
|
||||
static const char *const kImageFastReplaybtn; ///< Fast playback mode button in recorder onscreen dialog
|
||||
static const char *const kImageStopSmallbtn; ///< Stop recording button in recorder onscreen dialog (for 320xY)
|
||||
static const char *const kImageEditSmallbtn; ///< Edit recording metadata in recorder onscreen dialog (for 320xY)
|
||||
static const char *const kImageSwitchModeSmallbtn; ///< Switch mode button in recorder onscreen dialog (for 320xY)
|
||||
static const char *const kImageFastReplaySmallbtn; ///< Fast playback mode button in recorder onscreen dialog (for 320xY)
|
||||
|
||||
/**
|
||||
* Graphics mode enumeration.
|
||||
|
@ -275,8 +283,13 @@ public:
|
|||
|
||||
void refresh();
|
||||
void enable();
|
||||
|
||||
void showCursor();
|
||||
void hideCursor();
|
||||
|
||||
void disable();
|
||||
|
||||
|
||||
/**
|
||||
* Query the set up pixel format.
|
||||
*/
|
||||
|
|
|
@ -37,6 +37,8 @@ struct Event;
|
|||
|
||||
namespace GUI {
|
||||
|
||||
class EventRecorder;
|
||||
|
||||
class Widget;
|
||||
|
||||
// Some "common" commands sent to handleCommand()
|
||||
|
@ -47,6 +49,7 @@ enum {
|
|||
|
||||
class Dialog : public GuiObject {
|
||||
friend class GuiManager;
|
||||
friend class EventRecorder;
|
||||
friend class Tooltip;
|
||||
protected:
|
||||
Widget *_mouseWidget;
|
||||
|
|
87
gui/editrecorddialog.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 2
|
||||
* of the License, or (at your option) any later version.
|
||||
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "editrecorddialog.h"
|
||||
#include "gui/widgets/edittext.h"
|
||||
#include "common/translation.h"
|
||||
|
||||
|
||||
namespace GUI {
|
||||
|
||||
const Common::String EditRecordDialog::getAuthor() {
|
||||
return _authorEdit->getEditString();
|
||||
}
|
||||
|
||||
void EditRecordDialog::setAuthor(const Common::String &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::String author, const Common::String name, const Common::String notes) : Dialog("EditRecordDialog") {
|
||||
new StaticTextWidget(this,"EditRecordDialog.AuthorLabel",_("Author:"));
|
||||
new StaticTextWidget(this,"EditRecordDialog.NameLabel",_("Name:"));
|
||||
new StaticTextWidget(this,"EditRecordDialog.NotesLabel",_("Notes:"));
|
||||
_authorEdit = new EditTextWidget(this, "EditRecordDialog.AuthorEdit","");
|
||||
_notesEdit = new EditTextWidget(this, "EditRecordDialog.NotesEdit","");
|
||||
_nameEdit = new EditTextWidget(this, "EditRecordDialog.NameEdit","");
|
||||
_authorEdit->setEditString(author);
|
||||
_notesEdit->setEditString(notes);
|
||||
_nameEdit->setEditString(name);
|
||||
new GUI::ButtonWidget(this, "EditRecordDialog.Cancel", _("Cancel"), 0, kCloseCmd);
|
||||
new GUI::ButtonWidget(this, "EditRecordDialog.OK", _("Ok"), 0, 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
56
gui/editrecorddialog.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 2
|
||||
* of the License, or (at your option) any later version.
|
||||
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#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::String author, const Common::String name, const Common::String notes);
|
||||
~EditRecordDialog();
|
||||
|
||||
const Common::String getAuthor();
|
||||
const Common::String getNotes();
|
||||
const Common::String getName();
|
||||
|
||||
void setAuthor(const Common::String &author);
|
||||
void setNotes(const Common::String &desc);
|
||||
void setName(const Common::String &name);
|
||||
|
||||
virtual void handleCommand(GUI::CommandSender *sender, uint32 cmd, uint32 data);
|
||||
};
|
||||
|
||||
}// End of namespace GUI
|
||||
|
||||
#endif
|
|
@ -27,6 +27,7 @@
|
|||
#include "common/rect.h"
|
||||
#include "common/textconsole.h"
|
||||
#include "common/translation.h"
|
||||
#include "gui/EventRecorder.h"
|
||||
|
||||
#include "backends/keymapper/keymapper.h"
|
||||
|
||||
|
@ -253,12 +254,13 @@ Dialog *GuiManager::getTopDialog() const {
|
|||
void GuiManager::runLoop() {
|
||||
Dialog * const activeDialog = getTopDialog();
|
||||
bool didSaveState = false;
|
||||
int button;
|
||||
uint32 time;
|
||||
|
||||
if (activeDialog == 0)
|
||||
return;
|
||||
|
||||
// Suspend recording while GUI is shown
|
||||
g_eventRec.suspendRecording();
|
||||
|
||||
if (!_stateIsSaved) {
|
||||
saveState();
|
||||
_theme->enable();
|
||||
|
@ -296,10 +298,10 @@ void GuiManager::runLoop() {
|
|||
// _theme->updateScreen();
|
||||
// _system->updateScreen();
|
||||
|
||||
if (lastRedraw + waitTime < _system->getMillis()) {
|
||||
if (lastRedraw + waitTime < _system->getMillis(true)) {
|
||||
_theme->updateScreen();
|
||||
_system->updateScreen();
|
||||
lastRedraw = _system->getMillis();
|
||||
lastRedraw = _system->getMillis(true);
|
||||
}
|
||||
|
||||
Common::Event event;
|
||||
|
@ -314,72 +316,21 @@ void GuiManager::runLoop() {
|
|||
if (activeDialog != getTopDialog() && event.type != Common::EVENT_SCREEN_CHANGED)
|
||||
continue;
|
||||
|
||||
Common::Point mouse(event.mouse.x - 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:
|
||||
activeDialog->handleMouseMoved(mouse.x, mouse.y, 0);
|
||||
|
||||
if (mouse.x != _lastMousePosition.x || mouse.y != _lastMousePosition.y) {
|
||||
_lastMousePosition.x = mouse.x;
|
||||
_lastMousePosition.y = mouse.y;
|
||||
_lastMousePosition.time = _system->getMillis();
|
||||
}
|
||||
processEvent(event, activeDialog);
|
||||
|
||||
if (event.type == Common::EVENT_MOUSEMOVE) {
|
||||
tooltipCheck = true;
|
||||
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();
|
||||
if (_lastClick.count && (time < _lastClick.time + 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:
|
||||
#ifdef ENABLE_KEYMAPPER
|
||||
activeDialog->handleOtherEvent(event);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
||||
if (lastRedraw + waitTime < _system->getMillis()) {
|
||||
|
||||
if (lastRedraw + waitTime < _system->getMillis(true)) {
|
||||
_theme->updateScreen();
|
||||
_system->updateScreen();
|
||||
lastRedraw = _system->getMillis();
|
||||
lastRedraw = _system->getMillis(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (tooltipCheck && _lastMousePosition.time + kTooltipDelay < _system->getMillis()) {
|
||||
if (tooltipCheck && _lastMousePosition.time + kTooltipDelay < _system->getMillis(true)) {
|
||||
Widget *wdg = activeDialog->findWidget(_lastMousePosition.x, _lastMousePosition.y);
|
||||
if (wdg && wdg->hasTooltip() && !(wdg->getFlags() & WIDGET_PRESSED)) {
|
||||
Tooltip *tooltip = new Tooltip();
|
||||
|
@ -409,6 +360,9 @@ void GuiManager::runLoop() {
|
|||
restoreState();
|
||||
_useStdCursor = false;
|
||||
}
|
||||
|
||||
// Resume recording once GUI is shown
|
||||
g_eventRec.resumeRecording();
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
@ -492,7 +446,7 @@ void GuiManager::setupCursor() {
|
|||
// very much. We could plug in a different cursor here if we like to.
|
||||
|
||||
void GuiManager::animateCursor() {
|
||||
int time = _system->getMillis();
|
||||
int time = _system->getMillis(true);
|
||||
if (time > _cursorAnimateTimer + kCursorAnimateDelay) {
|
||||
for (int i = 0; i < 15; i++) {
|
||||
if ((i < 6) || (i > 8)) {
|
||||
|
@ -537,4 +491,64 @@ void GuiManager::screenChange() {
|
|||
_system->updateScreen();
|
||||
}
|
||||
|
||||
void GuiManager::processEvent(const Common::Event &event, Dialog *const activeDialog) {
|
||||
int button;
|
||||
uint32 time;
|
||||
Common::Point mouse(event.mouse.x - 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:
|
||||
activeDialog->handleMouseMoved(mouse.x, mouse.y, 0);
|
||||
|
||||
if (mouse.x != _lastMousePosition.x || mouse.y != _lastMousePosition.y) {
|
||||
_lastMousePosition.x = mouse.x;
|
||||
_lastMousePosition.y = mouse.y;
|
||||
_lastMousePosition.time = _system->getMillis(true);
|
||||
}
|
||||
|
||||
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 + 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:
|
||||
#ifdef ENABLE_KEYMAPPER
|
||||
activeDialog->handleOtherEvent(event);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // End of namespace GUI
|
||||
|
|
|
@ -35,6 +35,10 @@ namespace Graphics {
|
|||
class Font;
|
||||
}
|
||||
|
||||
namespace Common {
|
||||
struct Event;
|
||||
}
|
||||
|
||||
namespace GUI {
|
||||
|
||||
class Dialog;
|
||||
|
@ -67,6 +71,8 @@ public:
|
|||
// until no dialogs are active anymore.
|
||||
void runLoop();
|
||||
|
||||
void processEvent(const Common::Event &event, Dialog *const activeDialog);
|
||||
|
||||
bool isActive() const { return ! _dialogStack.empty(); }
|
||||
|
||||
bool loadNewTheme(Common::String id, ThemeEngine::GraphicsMode gfx = ThemeEngine::kGfxDisabled, bool force = false);
|
||||
|
|
|
@ -37,6 +37,11 @@
|
|||
#include "gui/message.h"
|
||||
#include "gui/gui-manager.h"
|
||||
#include "gui/options.h"
|
||||
#ifdef ENABLE_EVENTRECORDER
|
||||
#include "gui/onscreendialog.h"
|
||||
#include "gui/recorderdialog.h"
|
||||
#include "gui/EventRecorder.h"
|
||||
#endif
|
||||
#include "gui/saveload.h"
|
||||
#include "gui/widgets/edittext.h"
|
||||
#include "gui/widgets/list.h"
|
||||
|
@ -596,7 +601,6 @@ void EditGameDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 dat
|
|||
LauncherDialog::LauncherDialog()
|
||||
: Dialog(0, 0, 320, 200) {
|
||||
_backgroundType = GUI::ThemeEngine::kDialogBackgroundMain;
|
||||
|
||||
const int screenW = g_system->getOverlayWidth();
|
||||
const int screenH = g_system->getOverlayHeight();
|
||||
|
||||
|
@ -779,10 +783,9 @@ void LauncherDialog::updateListing() {
|
|||
}
|
||||
|
||||
void LauncherDialog::addGame() {
|
||||
int modifiers = g_system->getEventManager()->getModifierState();
|
||||
|
||||
#ifndef DISABLE_MASS_ADD
|
||||
const bool massAdd = (modifiers & Common::KBD_SHIFT) != 0;
|
||||
const bool massAdd = checkModifier(Common::KBD_SHIFT);
|
||||
|
||||
if (massAdd) {
|
||||
MessageDialog alert(_("Do you really want to run the mass game detector? "
|
||||
|
@ -975,6 +978,49 @@ void LauncherDialog::editGame(int item) {
|
|||
}
|
||||
}
|
||||
|
||||
void LauncherDialog::loadGameButtonPressed(int item) {
|
||||
#ifdef ENABLE_EVENTRECORDER
|
||||
const bool shiftPressed = checkModifier(Common::KBD_SHIFT);
|
||||
if (shiftPressed) {
|
||||
recordGame(item);
|
||||
} else {
|
||||
loadGame(item);
|
||||
}
|
||||
updateButtons();
|
||||
#else
|
||||
loadGame(item);
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef ENABLE_EVENTRECORDER
|
||||
void LauncherDialog::recordGame(int item) {
|
||||
RecorderDialog recorderDialog;
|
||||
MessageDialog alert(_("Do you want to load savegame?"),
|
||||
_("Yes"), _("No"));
|
||||
switch(recorderDialog.runModal(_domains[item])) {
|
||||
case RecorderDialog::kRecordDialogClose:
|
||||
break;
|
||||
case RecorderDialog::kRecordDialogPlayback:
|
||||
ConfMan.setActiveDomain(_domains[item]);
|
||||
close();
|
||||
ConfMan.set("record_mode", "playback", ConfigManager::kTransientDomain);
|
||||
ConfMan.set("record_file_name", recorderDialog.getFileName(), ConfigManager::kTransientDomain);
|
||||
break;
|
||||
case RecorderDialog::kRecordDialogRecord:
|
||||
ConfMan.setActiveDomain(_domains[item]);
|
||||
if (alert.runModal() == GUI::kMessageOK) {
|
||||
loadGame(item);
|
||||
}
|
||||
close();
|
||||
g_eventRec.setAuthor(recorderDialog._author);
|
||||
g_eventRec.setName(recorderDialog._name);
|
||||
g_eventRec.setNotes(recorderDialog._notes);
|
||||
ConfMan.set("record_mode", "record", ConfigManager::kTransientDomain);
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void LauncherDialog::loadGame(int item) {
|
||||
String gameId = ConfMan.get("gameid", _domains[item]);
|
||||
if (gameId.empty())
|
||||
|
@ -1039,7 +1085,7 @@ void LauncherDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 dat
|
|||
editGame(item);
|
||||
break;
|
||||
case kLoadGameCmd:
|
||||
loadGame(item);
|
||||
loadGameButtonPressed(item);
|
||||
break;
|
||||
case kOptionsCmd: {
|
||||
GlobalOptionsDialog options;
|
||||
|
@ -1109,20 +1155,28 @@ void LauncherDialog::updateButtons() {
|
|||
_loadButton->setEnabled(en);
|
||||
_loadButton->draw();
|
||||
}
|
||||
switchButtonsText(_addButton, "~A~dd Game...", "Mass Add...");
|
||||
#ifdef ENABLE_EVENTRECORDER
|
||||
switchButtonsText(_loadButton, "~L~oad...", "Record...");
|
||||
#endif
|
||||
}
|
||||
|
||||
// Update the label of the "Add" button depending on whether shift is pressed or not
|
||||
int modifiers = g_system->getEventManager()->getModifierState();
|
||||
const bool massAdd = (modifiers & Common::KBD_SHIFT) != 0;
|
||||
// Update the label of the button depending on whether shift is pressed or not
|
||||
void LauncherDialog::switchButtonsText(ButtonWidget *button, const char *normalText, const char *shiftedText) {
|
||||
const bool shiftPressed = checkModifier(Common::KBD_SHIFT);
|
||||
const bool lowRes = g_system->getOverlayWidth() <= 320;
|
||||
|
||||
const char *newAddButtonLabel = massAdd
|
||||
? (lowRes ? _c("Mass Add...", "lowres") : _("Mass Add..."))
|
||||
: (lowRes ? _c("~A~dd Game...", "lowres") : _("~A~dd Game..."));
|
||||
const char *newAddButtonLabel = shiftPressed
|
||||
? (lowRes ? _c(shiftedText, "lowres") : _(shiftedText))
|
||||
: (lowRes ? _c(normalText, "lowres") : _(normalText));
|
||||
|
||||
if (_addButton->getLabel() != newAddButtonLabel)
|
||||
_addButton->setLabel(newAddButtonLabel);
|
||||
if (button->getLabel() != newAddButtonLabel)
|
||||
button->setLabel(newAddButtonLabel);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void LauncherDialog::reflowLayout() {
|
||||
#ifndef DISABLE_FANCY_THEMES
|
||||
if (g_gui.xmlEval()->getVar("Globals.ShowLauncherLogo") == 1 && g_gui.theme()->supportsImages()) {
|
||||
|
@ -1186,4 +1240,9 @@ void LauncherDialog::reflowLayout() {
|
|||
Dialog::reflowLayout();
|
||||
}
|
||||
|
||||
bool LauncherDialog::checkModifier(int checkedModifier) {
|
||||
int modifiers = g_system->getEventManager()->getModifierState();
|
||||
return (modifiers & checkedModifier) != 0;
|
||||
}
|
||||
|
||||
} // End of namespace GUI
|
||||
|
|
|
@ -56,7 +56,7 @@ protected:
|
|||
ListWidget *_list;
|
||||
ButtonWidget *_addButton;
|
||||
Widget *_startButton;
|
||||
Widget *_loadButton;
|
||||
ButtonWidget *_loadButton;
|
||||
Widget *_editButton;
|
||||
Widget *_removeButton;
|
||||
#ifndef DISABLE_FANCY_THEMES
|
||||
|
@ -80,6 +80,7 @@ protected:
|
|||
void updateListing();
|
||||
|
||||
void updateButtons();
|
||||
void switchButtonsText(ButtonWidget *button, const char *normalText, const char *shiftedText);
|
||||
|
||||
void open();
|
||||
void close();
|
||||
|
@ -99,6 +100,16 @@ protected:
|
|||
*/
|
||||
void editGame(int item);
|
||||
|
||||
/**
|
||||
* Facade for "Load..."/"Record..." buttons.
|
||||
*/
|
||||
void loadGameButtonPressed(int item);
|
||||
|
||||
/**
|
||||
* Handle "Record..." button.
|
||||
*/
|
||||
void recordGame(int item);
|
||||
|
||||
/**
|
||||
* Handle "Load..." button.
|
||||
*/
|
||||
|
@ -111,6 +122,8 @@ protected:
|
|||
* @target name of target to select
|
||||
*/
|
||||
void selectTarget(const String &target);
|
||||
private:
|
||||
bool checkModifier(int modifier);
|
||||
};
|
||||
|
||||
} // End of namespace GUI
|
||||
|
|
|
@ -7,6 +7,7 @@ MODULE_OBJS := \
|
|||
debugger.o \
|
||||
dialog.o \
|
||||
error.o \
|
||||
EventRecorder.o \
|
||||
gui-manager.o \
|
||||
launcher.o \
|
||||
massadd.o \
|
||||
|
@ -38,6 +39,13 @@ MODULE_OBJS += \
|
|||
browser.o
|
||||
endif
|
||||
|
||||
ifdef ENABLE_EVENTRECORDER
|
||||
MODULE_OBJS += \
|
||||
editrecorddialog.o \
|
||||
onscreendialog.o \
|
||||
recorderdialog.o
|
||||
endif
|
||||
|
||||
ifdef USE_FLUIDSYNTH
|
||||
MODULE_OBJS += \
|
||||
fluidsynth-dialog.o
|
||||
|
|
231
gui/onscreendialog.cpp
Normal file
|
@ -0,0 +1,231 @@
|
|||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "common/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() {
|
||||
GuiObject::reflowLayout();
|
||||
}
|
||||
|
||||
void OnScreenDialog::releaseFocus() {
|
||||
}
|
||||
|
||||
OnScreenDialog::OnScreenDialog(bool isRecord) : Dialog("OnScreenDialog") {
|
||||
_x = _y = 0;
|
||||
|
||||
#ifndef DISABLE_FANCY_THEMES
|
||||
if (g_gui.xmlEval()->getVar("Globals.OnScreenDialog.ShowPics") == 1 && g_gui.theme()->supportsImages()) {
|
||||
GUI::PicButtonWidget *btn;
|
||||
btn = new PicButtonWidget(this, "OnScreenDialog.StopButton", 0, kStopCmd, 0);
|
||||
btn->useThemeTransparency(true);
|
||||
|
||||
if (g_system->getOverlayWidth() > 320)
|
||||
btn->setGfx(g_gui.theme()->getImageSurface(ThemeEngine::kImageStopbtn));
|
||||
else
|
||||
btn->setGfx(g_gui.theme()->getImageSurface(ThemeEngine::kImageStopSmallbtn));
|
||||
|
||||
if (isRecord) {
|
||||
btn = new PicButtonWidget(this, "OnScreenDialog.EditButton", 0, kEditCmd, 0);
|
||||
btn->useThemeTransparency(true);
|
||||
|
||||
if (g_system->getOverlayWidth() > 320)
|
||||
btn->setGfx(g_gui.theme()->getImageSurface(ThemeEngine::kImageEditbtn));
|
||||
else
|
||||
btn->setGfx(g_gui.theme()->getImageSurface(ThemeEngine::kImageEditSmallbtn));
|
||||
} else {
|
||||
btn = new PicButtonWidget(this, "OnScreenDialog.SwitchModeButton", 0, kSwitchModeCmd, 0);
|
||||
btn->useThemeTransparency(true);
|
||||
if (g_system->getOverlayWidth() > 320)
|
||||
btn->setGfx(g_gui.theme()->getImageSurface(ThemeEngine::kImageSwitchModebtn));
|
||||
else
|
||||
btn->setGfx(g_gui.theme()->getImageSurface(ThemeEngine::kImageSwitchModeSmallbtn));
|
||||
|
||||
btn = new PicButtonWidget(this, "OnScreenDialog.FastReplayButton", 0, kFastModeCmd, 0);
|
||||
btn->useThemeTransparency(true);
|
||||
if (g_system->getOverlayWidth() > 320)
|
||||
btn->setGfx(g_gui.theme()->getImageSurface(ThemeEngine::kImageFastReplaybtn));
|
||||
else
|
||||
btn->setGfx(g_gui.theme()->getImageSurface(ThemeEngine::kImageFastReplaySmallbtn));
|
||||
}
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
GUI::ButtonWidget *btn;
|
||||
if (g_system->getOverlayWidth() > 320)
|
||||
btn = new ButtonWidget(this, "OnScreenDialog.StopButton", "[ ]", _("Stop"), kStopCmd);
|
||||
else
|
||||
btn = new ButtonWidget(this, "OnScreenDialog.StopButton", "[]", _("Stop"), kStopCmd);
|
||||
|
||||
if (isRecord) {
|
||||
btn = new ButtonWidget(this, "OnScreenDialog.EditButton", "E", _("Edit record description"), kEditCmd);
|
||||
} else {
|
||||
btn = new ButtonWidget(this, "OnScreenDialog.SwitchModeButton", "G", _("Switch to Game"), kSwitchModeCmd);
|
||||
|
||||
btn = new ButtonWidget(this, "OnScreenDialog.FastReplayButton", ">>", _("Fast replay"), kFastModeCmd);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
text = new GUI::StaticTextWidget(this, "OnScreenDialog.TimeLabel", "00:00:00");
|
||||
_enableDrag = false;
|
||||
_mouseOver = false;
|
||||
_editDlgShown = false;
|
||||
}
|
||||
|
||||
void OnScreenDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {
|
||||
Common::Event eventRTL;
|
||||
switch (cmd) {
|
||||
case kStopCmd:
|
||||
eventRTL.type = Common::EVENT_RTL;
|
||||
g_system->getEventManager()->pushEvent(eventRTL);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
64
gui/onscreendialog.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 2
|
||||
* of the License, or (at your option) any later version.
|
||||
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#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();
|
||||
virtual void close();
|
||||
virtual bool isVisible() const;
|
||||
virtual void reflowLayout();
|
||||
|
||||
void setReplayedTime(uint32 newTime);
|
||||
|
||||
virtual void handleMouseMoved(int x, int y, int button);
|
||||
virtual void handleMouseDown(int x, int y, int button, int clickCount);
|
||||
virtual void handleMouseUp(int x, int y, int button, int clickCount);
|
||||
void handleCommand(CommandSender *sender, uint32 cmd, uint32 data);
|
||||
|
||||
bool isMouseOver();
|
||||
bool isEditDlgVisible();
|
||||
Dialog *getActiveDlg();
|
||||
protected:
|
||||
virtual void releaseFocus();
|
||||
};
|
||||
|
||||
} // End of namespace GUI
|
||||
|
||||
#endif
|
291
gui/recorderdialog.cpp
Normal file
|
@ -0,0 +1,291 @@
|
|||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "common/algorithm.h"
|
||||
#include "common/bufferedstream.h"
|
||||
#include "common/savefile.h"
|
||||
#include "common/system.h"
|
||||
#include "graphics/colormasks.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 "gui/saveload.h"
|
||||
#include "common/system.h"
|
||||
#include "gui/ThemeEval.h"
|
||||
#include "gui/gui-manager.h"
|
||||
#include "recorderdialog.h"
|
||||
|
||||
#define MAX_RECORDS_NAMES 0xFF
|
||||
|
||||
namespace GUI {
|
||||
|
||||
enum {
|
||||
kRecordCmd = 'RCRD',
|
||||
kPlaybackCmd = 'PBCK',
|
||||
kDeleteCmd = 'DEL ',
|
||||
kNextScreenshotCmd = 'NEXT',
|
||||
kPrevScreenshotCmd = 'PREV',
|
||||
kEditRecordCmd = 'EDIT'
|
||||
};
|
||||
|
||||
RecorderDialog::RecorderDialog() : Dialog("RecorderDialog"), _list(0), _currentScreenshot(0) {
|
||||
_backgroundType = ThemeEngine::kDialogBackgroundSpecial;
|
||||
|
||||
new StaticTextWidget(this, "SaveLoadChooser.Title", _("Recorder or Playback Gameplay"));
|
||||
|
||||
_list = new GUI::ListWidget(this, "RecorderDialog.List");
|
||||
_list->setNumberingMode(GUI::kListNumberingOff);
|
||||
|
||||
_deleteButton = new GUI::ButtonWidget(this, "RecorderDialog.Delete", _("Delete"), 0, kDeleteCmd);
|
||||
new GUI::ButtonWidget(this, "RecorderDialog.Cancel", _("Cancel"), 0, kCloseCmd);
|
||||
new GUI::ButtonWidget(this, "RecorderDialog.Record", _("Record"), 0, kRecordCmd);
|
||||
_playbackButton = new GUI::ButtonWidget(this, "RecorderDialog.Playback", _("Playback"), 0, kPlaybackCmd);
|
||||
|
||||
_editButton = new GUI::ButtonWidget(this, "RecorderDialog.Edit", _("Edit"), 0, kEditRecordCmd);
|
||||
|
||||
_editButton->setEnabled(false);
|
||||
_deleteButton->setEnabled(false);
|
||||
_playbackButton->setEnabled(false);
|
||||
|
||||
_gfxWidget = new GUI::GraphicsWidget(this, 0, 0, 10, 10);
|
||||
_container = new GUI::ContainerWidget(this, 0, 0, 10, 10);
|
||||
if (g_gui.xmlEval()->getVar("Globals.RecorderDialog.ExtInfo.Visible") == 1) {
|
||||
new GUI::ButtonWidget(this,"RecorderDialog.NextScreenShotButton", "<", 0, kPrevScreenshotCmd);
|
||||
new GUI::ButtonWidget(this, "RecorderDialog.PreviousScreenShotButton", ">", 0, kNextScreenshotCmd);
|
||||
_currentScreenshotText = new StaticTextWidget(this, "RecorderDialog.currentScreenshot", "0/0");
|
||||
_authorText = new StaticTextWidget(this, "RecorderDialog.Author", _("Author: "));
|
||||
_notesText = new StaticTextWidget(this, "RecorderDialog.Notes", _("Notes: "));
|
||||
}
|
||||
if (_gfxWidget)
|
||||
_gfxWidget->setGfx(0);
|
||||
}
|
||||
|
||||
|
||||
void RecorderDialog::reflowLayout() {
|
||||
if (g_gui.xmlEval()->getVar("Globals.RecorderDialog.ExtInfo.Visible") == 1) {
|
||||
int16 x, y;
|
||||
uint16 w, h;
|
||||
|
||||
if (!g_gui.xmlEval()->getWidgetData("RecorderDialog.Thumbnail", x, y, w, h)) {
|
||||
error("Error when loading position data for Recorder Thumbnails");
|
||||
}
|
||||
|
||||
int thumbW = kThumbnailWidth;
|
||||
int thumbH = kThumbnailHeight2;
|
||||
int thumbX = x + (w >> 1) - (thumbW >> 1);
|
||||
int thumbY = y + kLineHeight;
|
||||
|
||||
_container->resize(x, y, w, h);
|
||||
_gfxWidget->resize(thumbX, thumbY, thumbW, thumbH);
|
||||
|
||||
_container->setVisible(true);
|
||||
_gfxWidget->setVisible(true);
|
||||
updateSelection(false);
|
||||
} else {
|
||||
_container->setVisible(false);
|
||||
_gfxWidget->setVisible(false);
|
||||
}
|
||||
Dialog::reflowLayout();
|
||||
}
|
||||
|
||||
|
||||
|
||||
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;
|
||||
Common::String gameId = ConfMan.get("gameid", _target);
|
||||
const EnginePlugin *plugin = 0;
|
||||
GameDescriptor desc = EngineMan.findGame(gameId, &plugin);
|
||||
g_system->getTimeAndDate(t);
|
||||
EditRecordDialog editDlg("Unknown Author", Common::String::format("%.2d.%.2d.%.4d ", t.tm_mday, t.tm_mon, 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);
|
||||
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::StringArray namesList;
|
||||
_fileHeaders.clear();
|
||||
for (Common::StringArray::iterator i = files.begin(); i != files.end(); ++i) {
|
||||
if (file.openRead(*i)) {
|
||||
namesList.push_back(file.getHeader().name);
|
||||
_fileHeaders.push_back(file.getHeader());
|
||||
}
|
||||
file.close();
|
||||
}
|
||||
_list->setList(namesList);
|
||||
_list->draw();
|
||||
}
|
||||
|
||||
int RecorderDialog::runModal(Common::String &target) {
|
||||
_target = target;
|
||||
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;
|
||||
updateScreenShotsText();
|
||||
if (_list->getSelected() >= 0) {
|
||||
_authorText->setLabel(_("Author: ") + _fileHeaders[_list->getSelected()].author);
|
||||
_notesText->setLabel(_("Notes: ") + _fileHeaders[_list->getSelected()].notes);
|
||||
|
||||
_firstScreenshotUpdate = true;
|
||||
updateScreenshot();
|
||||
if ((_screenShotsCount) > 0) {
|
||||
_currentScreenshot = 1;
|
||||
}
|
||||
updateScreenshot();
|
||||
} else {
|
||||
_authorText->setLabel(_("Author: "));
|
||||
_notesText->setLabel(_("Notes: "));
|
||||
_screenShotsCount = -1;
|
||||
_currentScreenshot = 0;
|
||||
_gfxWidget->setGfx(-1, -1, 0, 0, 0);
|
||||
_gfxWidget->draw();
|
||||
updateScreenShotsText();
|
||||
}
|
||||
}
|
||||
|
||||
void RecorderDialog::updateScreenshot() {
|
||||
if (_list->getSelected() == -1) {
|
||||
return;
|
||||
}
|
||||
if (_currentScreenshot < 1) {
|
||||
_currentScreenshot = _screenShotsCount;
|
||||
}
|
||||
if (_currentScreenshot > _screenShotsCount) {
|
||||
_currentScreenshot = 1;
|
||||
}
|
||||
if (_firstScreenshotUpdate) {
|
||||
_playbackFile.openRead(_fileHeaders[_list->getSelected()].fileName);
|
||||
_screenShotsCount = _playbackFile.getScreensCount();
|
||||
_firstScreenshotUpdate = false;
|
||||
}
|
||||
Graphics::Surface *srcsf = _playbackFile.getScreenShot(_currentScreenshot);
|
||||
if (srcsf != NULL) {
|
||||
Graphics::Surface *destsf = Graphics::scale(*srcsf, _gfxWidget->getWidth(), _gfxWidget->getHeight());
|
||||
_gfxWidget->setGfx(destsf);
|
||||
updateScreenShotsText();
|
||||
delete destsf;
|
||||
delete srcsf;
|
||||
} else {
|
||||
_gfxWidget->setGfx(-1, -1, 0, 0, 0);
|
||||
}
|
||||
_gfxWidget->draw();
|
||||
}
|
||||
|
||||
void RecorderDialog::updateScreenShotsText() {
|
||||
if (_screenShotsCount == -1) {
|
||||
_currentScreenshotText->setLabel(Common::String::format("%d / ?", _currentScreenshot));
|
||||
} else {
|
||||
_currentScreenshotText->setLabel(Common::String::format("%d / %d", _currentScreenshot, _screenShotsCount));
|
||||
}
|
||||
}
|
||||
|
||||
} // End of namespace GUI
|
81
gui/recorderdialog.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 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#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 {
|
||||
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;
|
||||
|
||||
void updateList();
|
||||
void updateScreenShotsText();
|
||||
void updateSelection(bool redraw);
|
||||
void updateScreenshot();
|
||||
public:
|
||||
Common::String _author;
|
||||
Common::String _name;
|
||||
Common::String _notes;
|
||||
enum DialogResult {
|
||||
kRecordDialogClose,
|
||||
kRecordDialogRecord,
|
||||
kRecordDialogPlayback
|
||||
};
|
||||
RecorderDialog();
|
||||
~RecorderDialog();
|
||||
|
||||
virtual void handleCommand(GUI::CommandSender *sender, uint32 cmd, uint32 data);
|
||||
virtual void reflowLayout();
|
||||
|
||||
int runModal(Common::String &target);
|
||||
const Common::String getFileName() {return _filename;}
|
||||
};
|
||||
|
||||
} // End of namespace GUI
|
||||
|
||||
|
||||
#endif
|
|
@ -36,6 +36,9 @@
|
|||
<def var = 'ShowChooserPageDisplay' value = '1'/>
|
||||
|
||||
<def var = 'SaveLoadChooser.ExtInfo.Visible' value = '1'/>
|
||||
<def var = 'RecorderDialog.ExtInfo.Visible' value = '1'/>
|
||||
|
||||
<def var = 'OnScreenDialog.ShowPics' value = '0'/>
|
||||
|
||||
<def var = 'KeyMapper.Spacing' value = '10'/>
|
||||
<def var = 'KeyMapper.LabelWidth' value = '100'/>
|
||||
|
@ -101,6 +104,12 @@
|
|||
size = '15, 18'
|
||||
padding = '0, 3, 4, 0'
|
||||
/>
|
||||
<widget name = 'EditRecordLabel'
|
||||
size = '60, 25'
|
||||
/>
|
||||
<widget name = 'EditRecord'
|
||||
size = '240, 25'
|
||||
/>
|
||||
</globals>
|
||||
|
||||
<dialog name = 'Launcher' overlays = 'screen'>
|
||||
|
@ -1019,6 +1028,125 @@
|
|||
</layout>
|
||||
</dialog>
|
||||
|
||||
<dialog name = 'RecorderDialog' overlays = 'screen' inset = '8' shading = 'dim'>
|
||||
<layout type = 'vertical' padding = '8, 8, 8, 32' center = 'true'>
|
||||
<widget name = 'Title'
|
||||
height = 'Globals.Line.Height'
|
||||
/>
|
||||
<layout type = 'horizontal' padding = '0, 0, 0, 16' spacing = '16'>
|
||||
<widget name = 'List' />
|
||||
<layout type = 'vertical' padding = '0, 0, 0, 0'>
|
||||
<widget name = 'Thumbnail'
|
||||
width = '180'
|
||||
height = '170'
|
||||
/>
|
||||
<layout type = 'horizontal' padding = '0, 0, 0, 0'>
|
||||
<widget name = 'NextScreenShotButton'
|
||||
width = '25'
|
||||
height = '25'
|
||||
/>
|
||||
<widget name = 'currentScreenshot'
|
||||
width = '125'
|
||||
height = '25'
|
||||
textalign = 'center'
|
||||
/>
|
||||
<widget name = 'PreviousScreenShotButton'
|
||||
width = '25'
|
||||
height = '25'
|
||||
/>
|
||||
</layout>
|
||||
<widget name = 'Author' height = 'Globals.Line.Height' />
|
||||
<widget name = 'Notes' height = 'Globals.Line.Height' />
|
||||
</layout>
|
||||
</layout>
|
||||
<layout type = 'horizontal' padding = '0, 0, 0, 0'>
|
||||
<widget name = 'Delete'
|
||||
type = 'Button'
|
||||
/>
|
||||
<space size = '16'/>
|
||||
<widget name = 'Cancel'
|
||||
type = 'Button'
|
||||
/>
|
||||
<space size = '16'/>
|
||||
<widget name = 'Edit'
|
||||
type = 'Button'
|
||||
/>
|
||||
<widget name = 'Record'
|
||||
type = 'Button'
|
||||
/>
|
||||
<widget name = 'Playback'
|
||||
type = 'Button'
|
||||
/>
|
||||
</layout>
|
||||
</layout>
|
||||
</dialog>
|
||||
|
||||
<dialog name = 'OnScreenDialog' overlays = 'screen_center'>
|
||||
<layout type = 'horizontal' spacing = '5' padding = '5, 3, 5, 3' center = 'true'>
|
||||
<widget name = 'StopButton'
|
||||
width = '32'
|
||||
height = '32'
|
||||
/>
|
||||
<widget name = 'EditButton'
|
||||
width = '32'
|
||||
height = '32'
|
||||
/>
|
||||
<widget name = 'SwitchModeButton'
|
||||
width = '32'
|
||||
height = '32'
|
||||
/>
|
||||
<widget name = 'FastReplayButton'
|
||||
width = '32'
|
||||
height = '32'
|
||||
/>
|
||||
<widget name = 'TimeLabel'
|
||||
width = '50'
|
||||
height = '30'
|
||||
/>
|
||||
</layout>
|
||||
</dialog>
|
||||
|
||||
<dialog name = 'EditRecordDialog' overlays = 'screen_center'>
|
||||
<layout type = 'vertical' padding = '8, 8, 8, 8' center = 'true'>
|
||||
<widget name = 'Title'
|
||||
width = '320'
|
||||
height = 'Globals.Line.Height'
|
||||
/>
|
||||
<layout type = 'horizontal' spacing = '5' padding = '0, 0, 0, 10'>
|
||||
<widget name = 'AuthorLabel'
|
||||
type = 'EditRecordLabel'
|
||||
/>
|
||||
<widget name = 'AuthorEdit'
|
||||
type = 'EditRecord'
|
||||
/>
|
||||
</layout>
|
||||
<layout type = 'horizontal' spacing = '5' padding = '0, 0, 0, 10'>
|
||||
<widget name = 'NameLabel'
|
||||
type = 'EditRecordLabel'
|
||||
/>
|
||||
<widget name = 'NameEdit'
|
||||
type = 'EditRecord'
|
||||
/>
|
||||
</layout>
|
||||
<layout type = 'horizontal' spacing = '5' padding = '0, 0, 0, 10'>
|
||||
<widget name = 'NotesLabel'
|
||||
type = 'EditRecordLabel'
|
||||
/>
|
||||
<widget name = 'NotesEdit'
|
||||
type = 'EditRecord'
|
||||
/>
|
||||
</layout>
|
||||
<layout type = 'horizontal' spacing = '5' padding = '0, 0, 0, 10'>
|
||||
<widget name = 'Cancel'
|
||||
type = 'Button'
|
||||
/>
|
||||
<widget name = 'OK'
|
||||
type = 'Button'
|
||||
/>
|
||||
</layout>
|
||||
</layout>
|
||||
</dialog>
|
||||
|
||||
<dialog name = 'ScummHelp' overlays = 'screen_center'>
|
||||
<layout type = 'vertical' padding = '8, 8, 8, 8' center = 'true'>
|
||||
<widget name = 'Title'
|
||||
|
|
|
@ -37,6 +37,9 @@
|
|||
<def var = 'ShowChooserPageDisplay' value = '0'/>
|
||||
|
||||
<def var = 'SaveLoadChooser.ExtInfo.Visible' value = '0'/>
|
||||
<def var = 'RecorderDialog.ExtInfo.Visible' value = '0'/>
|
||||
|
||||
<def var = 'OnScreenDialog.ShowPics' value = '0'/>
|
||||
|
||||
<def var = 'KeyMapper.Spacing' value = '5'/>
|
||||
<def var = 'KeyMapper.LabelWidth' value = '80'/>
|
||||
|
@ -99,6 +102,12 @@
|
|||
size = '32, 18'
|
||||
padding = '0, 0, 1, 0'
|
||||
/>
|
||||
<widget name = 'EditRecordLabel'
|
||||
size = '60, Globals.Line.Height'
|
||||
/>
|
||||
<widget name = 'EditRecord'
|
||||
size = '120, 15'
|
||||
/>
|
||||
</globals>
|
||||
|
||||
<dialog name = 'Launcher' overlays = 'screen'>
|
||||
|
@ -1013,6 +1022,101 @@
|
|||
</layout>
|
||||
</dialog>
|
||||
|
||||
<dialog name = 'RecorderDialog' overlays = 'screen' inset = '8' shading = 'dim'>
|
||||
<layout type = 'vertical' padding = '8, 8, 8, 4' center = 'true'>
|
||||
<widget name = 'Title'
|
||||
height = 'Globals.Line.Height'
|
||||
/>
|
||||
<widget name = 'List' />
|
||||
<layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '2'>
|
||||
<widget name = 'Edit'
|
||||
type = 'Button'
|
||||
/>
|
||||
<space />
|
||||
<widget name = 'Record'
|
||||
type = 'Button'
|
||||
/>
|
||||
</layout>
|
||||
<layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '2'>
|
||||
<widget name = 'Delete'
|
||||
type = 'Button'
|
||||
/>
|
||||
<space />
|
||||
<widget name = 'Cancel'
|
||||
type = 'Button'
|
||||
/>
|
||||
<widget name = 'Playback'
|
||||
type = 'Button'
|
||||
/>
|
||||
</layout>
|
||||
</layout>
|
||||
</dialog>
|
||||
|
||||
<dialog name = 'OnScreenDialog' overlays = 'screen_center'>
|
||||
<layout type = 'horizontal' spacing = '5' padding = '3, 2, 3, 2' center = 'true'>
|
||||
<widget name = 'StopButton'
|
||||
width = '16'
|
||||
height = '16'
|
||||
/>
|
||||
<widget name = 'EditButton'
|
||||
width = '16'
|
||||
height = '16'
|
||||
/>
|
||||
<widget name = 'SwitchModeButton'
|
||||
width = '16'
|
||||
height = '16'
|
||||
/>
|
||||
<widget name = 'FastReplayButton'
|
||||
width = '16'
|
||||
height = '16'
|
||||
/>
|
||||
<widget name = 'TimeLabel'
|
||||
width = '50'
|
||||
height = '16'
|
||||
/>
|
||||
</layout>
|
||||
</dialog>
|
||||
|
||||
<dialog name = 'EditRecordDialog' overlays = 'screen_center'>
|
||||
<layout type = 'vertical' padding = '8, 8, 8, 8' center = 'true'>
|
||||
<widget name = 'Title'
|
||||
height = 'Globals.Line.Height'
|
||||
/>
|
||||
<layout type = 'horizontal' spacing = '5' padding = '0, 0, 0, 10'>
|
||||
<widget name = 'AuthorLabel'
|
||||
type = 'EditRecordLabel'
|
||||
/>
|
||||
<widget name = 'AuthorEdit'
|
||||
type = 'EditRecord'
|
||||
/>
|
||||
</layout>
|
||||
<layout type = 'horizontal' spacing = '5' padding = '0, 0, 0, 10'>
|
||||
<widget name = 'NameLabel'
|
||||
type = 'EditRecordLabel'
|
||||
/>
|
||||
<widget name = 'NameEdit'
|
||||
type = 'EditRecord'
|
||||
/>
|
||||
</layout>
|
||||
<layout type = 'horizontal' spacing = '5' padding = '0, 0, 0, 10'>
|
||||
<widget name = 'NotesLabel'
|
||||
type = 'EditRecordLabel'
|
||||
/>
|
||||
<widget name = 'NotesEdit'
|
||||
type = 'EditRecord'
|
||||
/>
|
||||
</layout>
|
||||
<layout type = 'horizontal' spacing = '5' padding = '0, 0, 0, 0'>
|
||||
<widget name = 'Cancel'
|
||||
type = 'Button'
|
||||
/>
|
||||
<widget name = 'OK'
|
||||
type = 'Button'
|
||||
/>
|
||||
</layout>
|
||||
</layout>
|
||||
</dialog>
|
||||
|
||||
<dialog name = 'ScummHelp' overlays = 'screen'>
|
||||
<layout type = 'vertical' padding = '8, 8, 8, 8'>
|
||||
<widget name = 'Title'
|
||||
|
|
BIN
gui/themes/scummmodern/editbtn.bmp
Normal file
After Width: | Height: | Size: 3.1 KiB |
BIN
gui/themes/scummmodern/editbtn_small.bmp
Normal file
After Width: | Height: | Size: 822 B |
BIN
gui/themes/scummmodern/fastreplay.bmp
Normal file
After Width: | Height: | Size: 3.1 KiB |
BIN
gui/themes/scummmodern/fastreplay_small.bmp
Normal file
After Width: | Height: | Size: 822 B |
|
@ -103,6 +103,14 @@
|
|||
<bitmap filename = 'delbtn.bmp'/>
|
||||
<bitmap filename = 'list.bmp'/>
|
||||
<bitmap filename = 'grid.bmp'/>
|
||||
<bitmap filename = 'stopbtn.bmp'/>
|
||||
<bitmap filename = 'editbtn.bmp'/>
|
||||
<bitmap filename = 'switchbtn.bmp'/>
|
||||
<bitmap filename = 'fastreplay.bmp'/>
|
||||
<bitmap filename = 'stopbtn_small.bmp'/>
|
||||
<bitmap filename = 'editbtn_small.bmp'/>
|
||||
<bitmap filename = 'switchbtn_small.bmp'/>
|
||||
<bitmap filename = 'fastreplay_small.bmp'/>
|
||||
</bitmaps>
|
||||
|
||||
<fonts>
|
||||
|
|
|
@ -43,6 +43,9 @@
|
|||
<def var = 'ShowChooserPageDisplay' value = '1'/>
|
||||
|
||||
<def var = 'SaveLoadChooser.ExtInfo.Visible' value = '1'/>
|
||||
<def var = 'RecorderDialog.ExtInfo.Visible' value = '1'/>
|
||||
|
||||
<def var = 'OnScreenDialog.ShowPics' value = '1'/>
|
||||
|
||||
<def var = 'KeyMapper.Spacing' value = '10'/>
|
||||
<def var = 'KeyMapper.LabelWidth' value = '100'/>
|
||||
|
@ -106,6 +109,13 @@
|
|||
size = '15, 18'
|
||||
padding = '0, 3, 4, 0'
|
||||
/>
|
||||
|
||||
<widget name = 'EditRecordLabel'
|
||||
size = '60, 25'
|
||||
/>
|
||||
<widget name = 'EditRecord'
|
||||
size = '240, 25'
|
||||
/>
|
||||
</globals>
|
||||
|
||||
<dialog name = 'Launcher' overlays = 'screen'>
|
||||
|
@ -1032,6 +1042,126 @@
|
|||
</layout>
|
||||
</dialog>
|
||||
|
||||
<dialog name = 'RecorderDialog' overlays = 'screen' inset = '8' shading = 'dim'>
|
||||
<layout type = 'vertical' padding = '8, 8, 8, 32' center = 'true'>
|
||||
<widget name = 'Title'
|
||||
height = 'Globals.Line.Height'
|
||||
/>
|
||||
<layout type = 'horizontal' padding = '0, 0, 0, 16' spacing = '16'>
|
||||
<widget name = 'List' />
|
||||
<layout type = 'vertical' padding = '0, 0, 0, 0'>
|
||||
<widget name = 'Thumbnail'
|
||||
width = '180'
|
||||
height = '170'
|
||||
/>
|
||||
<layout type = 'horizontal' padding = '0, 0, 0, 0'>
|
||||
<widget name = 'NextScreenShotButton'
|
||||
width = '25'
|
||||
height = '25'
|
||||
/>
|
||||
<widget name = 'currentScreenshot'
|
||||
width = '125'
|
||||
height = '25'
|
||||
textalign = 'center'
|
||||
/>
|
||||
<widget name = 'PreviousScreenShotButton'
|
||||
width = '25'
|
||||
height = '25'
|
||||
/>
|
||||
</layout>
|
||||
<widget name = 'Author' height = 'Globals.Line.Height' />
|
||||
<widget name = 'Notes' height = 'Globals.Line.Height' />
|
||||
</layout>
|
||||
</layout>
|
||||
<layout type = 'horizontal' padding = '0, 0, 0, 0'>
|
||||
<space/>
|
||||
<widget name = 'Delete'
|
||||
type = 'Button'
|
||||
/>
|
||||
<space size = '16'/>
|
||||
<widget name = 'Cancel'
|
||||
type = 'Button'
|
||||
/>
|
||||
<space size = '16'/>
|
||||
<widget name = 'Edit'
|
||||
type = 'Button'
|
||||
/>
|
||||
<widget name = 'Record'
|
||||
type = 'Button'
|
||||
/>
|
||||
<widget name = 'Playback'
|
||||
type = 'Button'
|
||||
/>
|
||||
</layout>
|
||||
</layout>
|
||||
</dialog>
|
||||
|
||||
<dialog name = 'OnScreenDialog' overlays = 'screen_center'>
|
||||
<layout type = 'horizontal' spacing = '5' padding = '5, 3, 5, 3' center = 'true'>
|
||||
<widget name = 'StopButton'
|
||||
width = '32'
|
||||
height = '32'
|
||||
/>
|
||||
<widget name = 'EditButton'
|
||||
width = '32'
|
||||
height = '32'
|
||||
/>
|
||||
<widget name = 'SwitchModeButton'
|
||||
width = '32'
|
||||
height = '32'
|
||||
/>
|
||||
<widget name = 'FastReplayButton'
|
||||
width = '32'
|
||||
height = '32'
|
||||
/>
|
||||
<widget name = 'TimeLabel'
|
||||
width = '50'
|
||||
height = '30'
|
||||
/>
|
||||
</layout>
|
||||
</dialog>
|
||||
|
||||
<dialog name = 'EditRecordDialog' overlays = 'screen_center'>
|
||||
<layout type = 'vertical' padding = '8, 8, 8, 8' center = 'true'>
|
||||
<widget name = 'Title'
|
||||
width = '320'
|
||||
height = 'Globals.Line.Height'
|
||||
/>
|
||||
<layout type = 'horizontal' spacing = '5' padding = '0, 0, 0, 10'>
|
||||
<widget name = 'AuthorLabel'
|
||||
type = 'EditRecordLabel'
|
||||
/>
|
||||
<widget name = 'AuthorEdit'
|
||||
type = 'EditRecord'
|
||||
/>
|
||||
</layout>
|
||||
<layout type = 'horizontal' spacing = '5' padding = '0, 0, 0, 10'>
|
||||
<widget name = 'NameLabel'
|
||||
type = 'EditRecordLabel'
|
||||
/>
|
||||
<widget name = 'NameEdit'
|
||||
type = 'EditRecord'
|
||||
/>
|
||||
</layout>
|
||||
<layout type = 'horizontal' spacing = '5' padding = '0, 0, 0, 10'>
|
||||
<widget name = 'NotesLabel'
|
||||
type = 'EditRecordLabel'
|
||||
/>
|
||||
<widget name = 'NotesEdit'
|
||||
type = 'EditRecord'
|
||||
/>
|
||||
</layout>
|
||||
<layout type = 'horizontal' spacing = '5' padding = '0, 0, 0, 10'>
|
||||
<widget name = 'Cancel'
|
||||
type = 'Button'
|
||||
/>
|
||||
<widget name = 'OK'
|
||||
type = 'Button'
|
||||
/>
|
||||
</layout>
|
||||
</layout>
|
||||
</dialog>
|
||||
|
||||
<dialog name = 'ScummHelp' overlays = 'screen_center'>
|
||||
<layout type = 'vertical' padding = '8, 8, 8, 8' center = 'true'>
|
||||
<widget name = 'Title'
|
||||
|
|
|
@ -35,6 +35,9 @@
|
|||
<def var = 'ShowChooserPageDisplay' value = '0'/>
|
||||
|
||||
<def var = 'SaveLoadChooser.ExtInfo.Visible' value = '0'/>
|
||||
<def var = 'RecorderDialog.ExtInfo.Visible' value = '0'/>
|
||||
|
||||
<def var = 'OnScreenDialog.ShowPics' value = '1'/>
|
||||
|
||||
<def var = 'Predictive.Button.Width' value = '45' />
|
||||
<def var = 'Predictive.Button.Height' value = '15' />
|
||||
|
@ -97,6 +100,12 @@
|
|||
size = '32, 18'
|
||||
padding = '0, 0, 2, 0'
|
||||
/>
|
||||
<widget name = 'EditRecordLabel'
|
||||
size = '60, Globals.Line.Height'
|
||||
/>
|
||||
<widget name = 'EditRecord'
|
||||
size = '120, 15'
|
||||
/>
|
||||
</globals>
|
||||
|
||||
<dialog name = 'Launcher' overlays = 'screen'>
|
||||
|
@ -1012,6 +1021,122 @@
|
|||
</layout>
|
||||
</dialog>
|
||||
|
||||
<dialog name = 'SavenameDialog' overlays = 'screen_center'>
|
||||
<layout type = 'vertical' padding = '8, 8, 8, 8'>
|
||||
<widget name = 'DescriptionText'
|
||||
width = '320'
|
||||
height = 'Globals.Line.Height'
|
||||
/>
|
||||
<widget name = 'Description'
|
||||
height = '19'
|
||||
/>
|
||||
<layout type = 'horizontal' padding = '0, 0, 16, 0'>
|
||||
<widget name = 'Cancel'
|
||||
type = 'Button'
|
||||
/>
|
||||
<space size = '96'/>
|
||||
<widget name = 'Ok'
|
||||
type = 'Button'
|
||||
/>
|
||||
</layout>
|
||||
</layout>
|
||||
</dialog>
|
||||
|
||||
<dialog name = 'RecorderDialog' overlays = 'screen' inset = '8' shading = 'dim'>
|
||||
<layout type = 'vertical' padding = '8, 8, 8, 4' center = 'true'>
|
||||
<widget name = 'Title'
|
||||
height = 'Globals.Line.Height'
|
||||
/>
|
||||
<widget name = 'List' />
|
||||
<layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '2'>
|
||||
<widget name = 'Edit'
|
||||
type = 'Button'
|
||||
/>
|
||||
<space />
|
||||
<widget name = 'Record'
|
||||
type = 'Button'
|
||||
/>
|
||||
</layout>
|
||||
<layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '2'>
|
||||
<widget name = 'Delete'
|
||||
type = 'Button'
|
||||
/>
|
||||
<space />
|
||||
<widget name = 'Cancel'
|
||||
type = 'Button'
|
||||
/>
|
||||
<widget name = 'Playback'
|
||||
type = 'Button'
|
||||
/>
|
||||
</layout>
|
||||
</layout>
|
||||
</dialog>
|
||||
|
||||
<dialog name = 'OnScreenDialog' overlays = 'screen_center'>
|
||||
<layout type = 'horizontal' spacing = '5' padding = '3, 2, 3, 2' center = 'true'>
|
||||
<widget name = 'StopButton'
|
||||
width = '16'
|
||||
height = '16'
|
||||
/>
|
||||
<widget name = 'EditButton'
|
||||
width = '16'
|
||||
height = '16'
|
||||
/>
|
||||
<widget name = 'SwitchModeButton'
|
||||
width = '16'
|
||||
height = '16'
|
||||
/>
|
||||
<widget name = 'FastReplayButton'
|
||||
width = '16'
|
||||
height = '16'
|
||||
/>
|
||||
<widget name = 'TimeLabel'
|
||||
width = '50'
|
||||
height = '16'
|
||||
/>
|
||||
</layout>
|
||||
</dialog>
|
||||
|
||||
<dialog name = 'EditRecordDialog' overlays = 'screen_center'>
|
||||
<layout type = 'vertical' padding = '8, 8, 8, 8' center = 'true'>
|
||||
<widget name = 'Title'
|
||||
height = 'Globals.Line.Height'
|
||||
/>
|
||||
<layout type = 'horizontal' spacing = '5' padding = '0, 0, 0, 10'>
|
||||
<widget name = 'AuthorLabel'
|
||||
type = 'EditRecordLabel'
|
||||
/>
|
||||
<widget name = 'AuthorEdit'
|
||||
type = 'EditRecord'
|
||||
/>
|
||||
</layout>
|
||||
<layout type = 'horizontal' spacing = '5' padding = '0, 0, 0, 10'>
|
||||
<widget name = 'NameLabel'
|
||||
type = 'EditRecordLabel'
|
||||
/>
|
||||
<widget name = 'NameEdit'
|
||||
type = 'EditRecord'
|
||||
/>
|
||||
</layout>
|
||||
<layout type = 'horizontal' spacing = '5' padding = '0, 0, 0, 10'>
|
||||
<widget name = 'NotesLabel'
|
||||
type = 'EditRecordLabel'
|
||||
/>
|
||||
<widget name = 'NotesEdit'
|
||||
type = 'EditRecord'
|
||||
/>
|
||||
</layout>
|
||||
<layout type = 'horizontal' spacing = '5' padding = '0, 0, 0, 0'>
|
||||
<widget name = 'Cancel'
|
||||
type = 'Button'
|
||||
/>
|
||||
<widget name = 'OK'
|
||||
type = 'Button'
|
||||
/>
|
||||
</layout>
|
||||
</layout>
|
||||
</dialog>
|
||||
|
||||
<dialog name = 'ScummHelp' overlays = 'screen' inset = '8'>
|
||||
<layout type = 'vertical' padding = '8, 8, 8, 8'>
|
||||
<widget name = 'Title'
|
||||
|
|
BIN
gui/themes/scummmodern/stopbtn.bmp
Normal file
After Width: | Height: | Size: 3.1 KiB |
BIN
gui/themes/scummmodern/stopbtn_small.bmp
Normal file
After Width: | Height: | Size: 822 B |
BIN
gui/themes/scummmodern/switchbtn.bmp
Normal file
After Width: | Height: | Size: 3.1 KiB |
BIN
gui/themes/scummmodern/switchbtn_small.bmp
Normal file
After Width: | Height: | Size: 822 B |