Porting over win32 TAS modules. In work.

This commit is contained in:
mjbudd77 2021-10-23 05:06:08 -04:00
parent a61a92f1e0
commit 019c30b229
21 changed files with 5194 additions and 0 deletions

View File

@ -534,6 +534,9 @@ set(SRC_DRIVERS_SDL
${CMAKE_CURRENT_SOURCE_DIR}/drivers/Qt/avi/fileio.cpp
${CMAKE_CURRENT_SOURCE_DIR}/drivers/Qt/avi/gwavi.cpp
${CMAKE_CURRENT_SOURCE_DIR}/drivers/Qt/TasEditor/TasEditorWindow.cpp
${CMAKE_CURRENT_SOURCE_DIR}/drivers/Qt/TasEditor/taseditor_config.cpp
${CMAKE_CURRENT_SOURCE_DIR}/drivers/Qt/TasEditor/selection.cpp
${CMAKE_CURRENT_SOURCE_DIR}/drivers/Qt/TasEditor/inputlog.cpp
${CMAKE_CURRENT_SOURCE_DIR}/drivers/Qt/TasEditor/laglog.cpp
)

View File

@ -37,6 +37,7 @@
#include "Qt/TasEditor/TasEditorWindow.h"
TasEditorWindow *tasWin = NULL;
MARKERS_MANAGER *markersManager = NULL;
//----------------------------------------------------------------------------
//---- Main TAS Editor Window
@ -77,6 +78,9 @@ TasEditorWindow::TasEditorWindow(QWidget *parent)
QMenuBar *menuBar;
tasWin = this;
::taseditorConfig = &this->taseditorConfig;
::markersManager = &this->markersManager;
::splicer = &this->splicer;
setWindowTitle("TAS Editor");

View File

@ -30,6 +30,22 @@
#include <QAction>
#include <QFont>
#include "Qt/TasEditor/taseditor_config.h"
//#include "Qt/TasEditor/greenzone.h"
#include "Qt/TasEditor/selection.h"
#include "Qt/TasEditor/markers_manager.h"
//#include "Qt/TasEditor/snapshot.h"
//#include "Qt/TasEditor/bookmarks.h"
//#include "Qt/TasEditor/branches.h"
//#include "Qt/TasEditor/history.h"
//#include "Qt/TasEditor/playback.h"
#include "Qt/TasEditor/recorder.h"
//#include "Qt/TasEditor/taseditor_lua.h"
#include "Qt/TasEditor/splicer.h"
//#include "Qt/TasEditor/editor.h"
//#include "Qt/TasEditor/popup_display.h"
class TasEditorWindow;
class QPianoRoll : public QWidget
@ -73,6 +89,12 @@ class TasEditorWindow : public QDialog
QPianoRoll *pianoRoll;
TASEDITOR_CONFIG taseditorConfig;
MARKERS_MANAGER markersManager;
//PIANO_ROLL pianoRoll;
SPLICER splicer;
//EDITOR editor;
//GREENZONE greenzone;
protected:
void closeEvent(QCloseEvent *event);
@ -140,6 +162,10 @@ class TasEditorWindow : public QDialog
private slots:
};
extern TASEDITOR_CONFIG *taseditorConfig;
extern MARKERS_MANAGER *markersManager;
extern SPLICER *splicer;
bool tasWindowIsOpen(void);
void tasWindowSetFocus(bool val);

View File

@ -0,0 +1,654 @@
/* ---------------------------------------------------------------------------------
Implementation file of InputLog class
Copyright (c) 2011-2013 AnS
(The MIT License)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
------------------------------------------------------------------------------------
InputLog - Log of Input
* stores the data about Input state: size, type of Input, Input Log data (commands and joysticks)
* optionally can store map of Hot Changes
* implements InputLog creation: copying Input, copying Hot Changes
* implements full/partial restoring of data from InputLog: Input, Hot Changes
* implements compression and decompression of stored data
* saves and loads the data from a project file. On error: sends warning to caller
* implements searching of first mismatch comparing two InputLogs or comparing this InputLog to a movie
* provides interface for reading specific data: reading Input of any given frame, reading value at any point of Hot Changes map
* implements all operations with Hot Changes maps: copying (full/partial), updating/fading, setting new hot places by comparing two InputLogs
------------------------------------------------------------------------------------ */
#include <zlib.h>
#include "Qt/TasEditor/inputlog.h"
#include "Qt/TasEditor/taseditor_project.h"
extern SELECTION selection;
extern int getInputType(MovieData& md);
int joysticksPerFrame[INPUT_TYPES_TOTAL] = {1, 2, 4};
INPUTLOG::INPUTLOG()
{
}
void INPUTLOG::init(MovieData& md, bool hotchanges, int force_input_type)
{
hasHotChanges = hotchanges;
if (force_input_type < 0)
inputType = getInputType(md);
else
inputType = force_input_type;
int num_joys = joysticksPerFrame[inputType];
// retrieve Input data from movie data
size = md.getNumRecords();
joysticks.resize(BYTES_PER_JOYSTICK * num_joys * size); // it's much faster to have this format than have [frame][joy] or other structures
commands.resize(size); // commands take 1 byte per frame
if (hasHotChanges)
initHotChanges();
// fill Input vector
int joy;
for (int frame = 0; frame < size; ++frame)
{
for (joy = num_joys - 1; joy >= 0; joy--)
joysticks[frame * num_joys * BYTES_PER_JOYSTICK + joy * BYTES_PER_JOYSTICK] = md.records[frame].joysticks[joy];
commands[frame] = md.records[frame].commands;
}
alreadyCompressed = false;
}
// this function only updates one frame of Input Log and Hot Changes data
// the function should only be used when combining consecutive Recordings
void INPUTLOG::reinit(MovieData& md, bool hotchanges, int frame_of_change)
{
hasHotChanges = hotchanges;
int num_joys = joysticksPerFrame[inputType];
int joy;
// retrieve Input data from movie data
size = md.getNumRecords();
joysticks.resize(BYTES_PER_JOYSTICK * num_joys * size, 0);
commands.resize(size);
if (hasHotChanges)
{
// resize Hot Changes
initHotChanges();
// compare current movie data at the frame_of_change to current state of InputLog at the frame_of_change
uint8 my_joy, their_joy;
for (joy = num_joys - 1; joy >= 0; joy--)
{
my_joy = getJoystickData(frame_of_change, joy);
their_joy = md.records[frame_of_change].joysticks[joy];
if (my_joy != their_joy)
setMaxHotChangeBits(frame_of_change, joy, my_joy ^ their_joy);
}
} else
{
// if user switches Hot Changes off inbetween two consecutive Recordings
hotChanges.resize(0);
}
// update Input vector
for (joy = num_joys - 1; joy >= 0; joy--)
joysticks[frame_of_change * num_joys * BYTES_PER_JOYSTICK + joy * BYTES_PER_JOYSTICK] = md.records[frame_of_change].joysticks[joy];
commands[frame_of_change] = md.records[frame_of_change].commands;
alreadyCompressed = false;
}
void INPUTLOG::toMovie(MovieData& md, int start, int end)
{
if (end < 0 || end >= size) end = size - 1;
// write Input data to movie data
md.records.resize(end + 1);
int num_joys = joysticksPerFrame[inputType];
int joy;
for (int frame = start; frame <= end; ++frame)
{
for (joy = num_joys - 1; joy >= 0; joy--)
md.records[frame].joysticks[joy] = joysticks[frame * num_joys * BYTES_PER_JOYSTICK + joy * BYTES_PER_JOYSTICK];
md.records[frame].commands = commands[frame];
}
}
void INPUTLOG::compressData()
{
// compress joysticks
int len = joysticks.size();
uLongf comprlen = (len>>9)+12 + len;
compressedJoysticks.resize(comprlen);
compress(&compressedJoysticks[0], &comprlen, &joysticks[0], len);
compressedJoysticks.resize(comprlen);
// compress commands
len = commands.size();
comprlen = (len>>9)+12 + len;
compressedCommands.resize(comprlen);
compress(&compressedCommands[0], &comprlen, &commands[0], len);
compressedCommands.resize(comprlen);
if (hasHotChanges)
{
// compress hot_changes
len = hotChanges.size();
comprlen = (len>>9)+12 + len;
compressedHotChanges.resize(comprlen);
compress(&compressedHotChanges[0], &comprlen, &hotChanges[0], len);
compressedHotChanges.resize(comprlen);
}
// don't recompress anymore
alreadyCompressed = true;
}
bool INPUTLOG::isAlreadyCompressed()
{
return alreadyCompressed;
}
void INPUTLOG::save(EMUFILE *os)
{
// write vars
write32le(size, os);
write8le(inputType, os);
// write data
if (!alreadyCompressed)
compressData();
// save joysticks data
write32le(compressedJoysticks.size(), os);
os->fwrite(&compressedJoysticks[0], compressedJoysticks.size());
// save commands data
write32le(compressedCommands.size(), os);
os->fwrite(&compressedCommands[0], compressedCommands.size());
if (hasHotChanges)
{
write8le((uint8)1, os);
// save hot_changes data
write32le(compressedHotChanges.size(), os);
os->fwrite(&compressedHotChanges[0], compressedHotChanges.size());
} else
{
write8le((uint8)0, os);
}
}
// returns true if couldn't load
bool INPUTLOG::load(EMUFILE *is)
{
uint8 tmp;
// read vars
if (!read32le(&size, is)) return true;
if (!read8le(&tmp, is)) return true;
inputType = tmp;
// read data
alreadyCompressed = true;
int comprlen;
uLongf destlen;
// read and uncompress joysticks data
destlen = size * BYTES_PER_JOYSTICK * joysticksPerFrame[inputType];
joysticks.resize(destlen);
// read size
if (!read32le(&comprlen, is)) return true;
if (comprlen <= 0) return true;
compressedJoysticks.resize(comprlen);
if (is->fread(&compressedJoysticks[0], comprlen) != comprlen) return true;
int e = uncompress(&joysticks[0], &destlen, &compressedJoysticks[0], comprlen);
if (e != Z_OK && e != Z_BUF_ERROR) return true;
// read and uncompress commands data
destlen = size;
commands.resize(destlen);
// read size
if (!read32le(&comprlen, is)) return true;
if (comprlen <= 0) return true;
compressedCommands.resize(comprlen);
if (is->fread(&compressedCommands[0], comprlen) != comprlen) return true;
e = uncompress(&commands[0], &destlen, &compressedCommands[0], comprlen);
if (e != Z_OK && e != Z_BUF_ERROR) return true;
// read hotchanges
if (!read8le(&tmp, is)) return true;
hasHotChanges = (tmp != 0);
if (hasHotChanges)
{
// read and uncompress hot_changes data
destlen = size * joysticksPerFrame[inputType] * HOTCHANGE_BYTES_PER_JOY;
hotChanges.resize(destlen);
// read size
if (!read32le(&comprlen, is)) return true;
if (comprlen <= 0) return true;
compressedHotChanges.resize(comprlen);
if (is->fread(&compressedHotChanges[0], comprlen) != comprlen) return true;
e = uncompress(&hotChanges[0], &destlen, &compressedHotChanges[0], comprlen);
if (e != Z_OK && e != Z_BUF_ERROR) return true;
}
return false;
}
bool INPUTLOG::skipLoad(EMUFILE *is)
{
int tmp;
uint8 tmp1;
// skip vars
if (is->fseek(sizeof(int) + // size
sizeof(uint8) // input_type
, SEEK_CUR)) return true;
// skip joysticks data
if (!read32le(&tmp, is)) return true;
if (is->fseek(tmp, SEEK_CUR) != 0) return true;
// skip commands data
if (!read32le(&tmp, is)) return true;
if (is->fseek(tmp, SEEK_CUR) != 0) return true;
// skip hot_changes data
if (!read8le(&tmp1, is)) return true;
if (tmp1)
{
if (!read32le(&tmp, is)) return true;
if (is->fseek(tmp, SEEK_CUR) != 0) return true;
}
return false;
}
// --------------------------------------------------------------------------------------------
// return number of first frame of difference between two InputLogs
int INPUTLOG::findFirstChange(INPUTLOG& theirLog, int start, int end)
{
// search for differences to the specified end (or to the end of this InputLog)
if (end < 0 || end >= size) end = size-1;
int their_log_end = theirLog.size;
int joy;
int num_joys = joysticksPerFrame[inputType];
for (int frame = start; frame <= end; ++frame)
{
for (joy = num_joys - 1; joy >= 0; joy--)
if (getJoystickData(frame, joy) != theirLog.getJoystickData(frame, joy)) return frame;
if (getCommandsData(frame) != theirLog.getCommandsData(frame)) return frame;
}
// no difference was found
// if my_size is less then their_size, return last frame + 1 (= size) as the frame of difference
if (size < their_log_end) return size;
// no changes were found
return -1;
}
// return number of first frame of difference between this InputLog and MovieData
int INPUTLOG::findFirstChange(MovieData& md, int start, int end)
{
// search for differences to the specified end (or to the end of this InputLog / to the end of the movie data)
if (end < 0 || end >= size) end = size - 1;
if (end >= md.getNumRecords()) end = md.getNumRecords() - 1;
int joy;
int num_joys = joysticksPerFrame[inputType];
for (int frame = start; frame <= end; ++frame)
{
for (joy = num_joys - 1; joy >= 0; joy--)
if (getJoystickData(frame, joy) != md.records[frame].joysticks[joy]) return frame;
if (getCommandsData(frame) != md.records[frame].commands) return frame;
}
// no difference was found
// if sizes differ, return last frame + 1 from the lesser of them
if (size < md.getNumRecords() && end >= size - 1)
return size;
else if (size > md.getNumRecords() && end >= md.getNumRecords() - 1)
return md.getNumRecords();
return -1;
}
int INPUTLOG::getJoystickData(int frame, int joy)
{
if (frame < 0 || frame >= size)
return 0;
if (joy > joysticksPerFrame[inputType])
return 0;
return joysticks[frame * BYTES_PER_JOYSTICK * joysticksPerFrame[inputType] + joy];
}
int INPUTLOG::getCommandsData(int frame)
{
if (frame < 0 || frame >= size)
return 0;
return commands[frame];
}
void INPUTLOG::insertFrames(int at, int frames)
{
size += frames;
if (at == -1)
{
// append frames to the end
commands.resize(size);
joysticks.resize(BYTES_PER_JOYSTICK * joysticksPerFrame[inputType] * size);
if (hasHotChanges)
{
hotChanges.resize(joysticksPerFrame[inputType] * size * HOTCHANGE_BYTES_PER_JOY);
// fill new hotchanges with max value
int lower_limit = joysticksPerFrame[inputType] * (size - frames) * HOTCHANGE_BYTES_PER_JOY;
for (int i = hotChanges.size() - 1; i >= lower_limit; i--)
hotChanges[i] = BYTE_VALUE_CONTAINING_MAX_HOTCHANGES;
}
} else
{
// insert frames
// insert 1 byte of commands
commands.insert(commands.begin() + at, frames, 0);
// insert X bytes of joystics
int bytes = BYTES_PER_JOYSTICK * joysticksPerFrame[inputType];
joysticks.insert(joysticks.begin() + (at * bytes), frames * bytes, 0);
if (hasHotChanges)
{
// insert X bytes of hot_changes
bytes = joysticksPerFrame[inputType] * HOTCHANGE_BYTES_PER_JOY;
hotChanges.insert(hotChanges.begin() + (at * bytes), frames * bytes, BYTE_VALUE_CONTAINING_MAX_HOTCHANGES);
}
}
// data was changed
alreadyCompressed = false;
}
void INPUTLOG::eraseFrame(int frame)
{
// erase 1 byte of commands
commands.erase(commands.begin() + frame);
// erase X bytes of joystics
int bytes = BYTES_PER_JOYSTICK * joysticksPerFrame[inputType];
joysticks.erase(joysticks.begin() + (frame * bytes), joysticks.begin() + ((frame + 1) * bytes));
if (hasHotChanges)
{
// erase X bytes of hot_changes
bytes = joysticksPerFrame[inputType] * HOTCHANGE_BYTES_PER_JOY;
hotChanges.erase(hotChanges.begin() + (frame * bytes), hotChanges.begin() + ((frame + 1) * bytes));
}
size--;
// data was changed
alreadyCompressed = false;
}
// -----------------------------------------------------------------------------------------------
void INPUTLOG::initHotChanges()
{
hotChanges.resize(joysticksPerFrame[inputType] * size * HOTCHANGE_BYTES_PER_JOY);
}
void INPUTLOG::copyHotChanges(INPUTLOG* sourceOfHotChanges, int limiterFrameOfSource)
{
// copy hot changes from source InputLog
if (sourceOfHotChanges && sourceOfHotChanges->hasHotChanges && sourceOfHotChanges->inputType == inputType)
{
int frames_to_copy = sourceOfHotChanges->size;
if (frames_to_copy > size)
frames_to_copy = size;
// special case for Branches: if limit_frame if specified, then copy only hotchanges from 0 to limit_frame
if (limiterFrameOfSource >= 0 && frames_to_copy > limiterFrameOfSource)
frames_to_copy = limiterFrameOfSource;
int bytes_to_copy = frames_to_copy * joysticksPerFrame[inputType] * HOTCHANGE_BYTES_PER_JOY;
memcpy(&hotChanges[0], &sourceOfHotChanges->hotChanges[0], bytes_to_copy);
}
}
void INPUTLOG::inheritHotChanges(INPUTLOG* sourceOfHotChanges)
{
// copy hot changes from source InputLog and fade them
if (sourceOfHotChanges && sourceOfHotChanges->hasHotChanges && sourceOfHotChanges->inputType == inputType)
{
int frames_to_copy = sourceOfHotChanges->size;
if (frames_to_copy > size)
frames_to_copy = size;
int bytes_to_copy = frames_to_copy * joysticksPerFrame[inputType] * HOTCHANGE_BYTES_PER_JOY;
memcpy(&hotChanges[0], &sourceOfHotChanges->hotChanges[0], bytes_to_copy);
fadeHotChanges();
}
}
void INPUTLOG::inheritHotChanges_DeleteSelection(INPUTLOG* sourceOfHotChanges, RowsSelection* frameset)
{
// copy hot changes from source InputLog, but omit deleted frames (which are represented by the "frameset")
if (sourceOfHotChanges && sourceOfHotChanges->hasHotChanges && sourceOfHotChanges->inputType == inputType)
{
int bytes = joysticksPerFrame[inputType] * HOTCHANGE_BYTES_PER_JOY;
int frame = 0, pos = 0, source_pos = 0;
int this_size = hotChanges.size(), source_size = sourceOfHotChanges->hotChanges.size();
RowsSelection::iterator it(frameset->begin());
RowsSelection::iterator frameset_end(frameset->end());
while (pos < this_size && source_pos < source_size)
{
if (it != frameset_end && frame == *it)
{
// omit the frame
it++;
source_pos += bytes;
} else
{
// copy hotchanges of this frame
memcpy(&hotChanges[pos], &sourceOfHotChanges->hotChanges[source_pos], bytes);
pos += bytes;
source_pos += bytes;
}
frame++;
}
fadeHotChanges();
}
}
void INPUTLOG::inheritHotChanges_InsertSelection(INPUTLOG* sourceOfHotChanges, RowsSelection* frameset)
{
// copy hot changes from source InputLog, but insert filled lines for inserted frames (which are represented by the "frameset")
RowsSelection::iterator it(frameset->begin());
RowsSelection::iterator frameset_end(frameset->end());
if (sourceOfHotChanges && sourceOfHotChanges->hasHotChanges && sourceOfHotChanges->inputType == inputType)
{
int bytes = joysticksPerFrame[inputType] * HOTCHANGE_BYTES_PER_JOY;
int frame = 0, region_len = 0, pos = 0, source_pos = 0;
int this_size = hotChanges.size(), source_size = sourceOfHotChanges->hotChanges.size();
while (pos < this_size)
{
if (it != frameset_end && frame == *it)
{
// omit the frame
it++;
region_len++;
// set filled line to the frame
memset(&hotChanges[pos], BYTE_VALUE_CONTAINING_MAX_HOTCHANGES, bytes);
} else if (source_pos < source_size)
{
// this frame should be copied
frame -= region_len;
region_len = 0;
// copy hotchanges of this frame
memcpy(&hotChanges[pos], &sourceOfHotChanges->hotChanges[source_pos], bytes);
fadeHotChanges(pos, pos + bytes);
source_pos += bytes;
}
pos += bytes;
frame++;
}
} else
{
// no old data, just fill "frameset" lines
int bytes = joysticksPerFrame[inputType] * HOTCHANGE_BYTES_PER_JOY;
int frame = 0, region_len = 0, pos = 0;
int this_size = hotChanges.size();
while (pos < this_size)
{
if (it != frameset_end && frame == *it)
{
// this frame is selected
it++;
region_len++;
// set filled line to the frame
memset(&hotChanges[pos], BYTE_VALUE_CONTAINING_MAX_HOTCHANGES, bytes);
// exit loop when all frames in the Selection are handled
if (it == frameset_end) break;
} else
{
// this frame is not selected
frame -= region_len;
region_len = 0;
// leave zeros in this frame
}
pos += bytes;
frame++;
}
}
}
void INPUTLOG::inheritHotChanges_DeleteNum(INPUTLOG* sourceOfHotChanges, int start, int frames, bool fadeOld)
{
int bytes = joysticksPerFrame[inputType] * HOTCHANGE_BYTES_PER_JOY;
// copy hot changes from source InputLog up to "start" and from "start+frames" to end
if (sourceOfHotChanges && sourceOfHotChanges->hasHotChanges && sourceOfHotChanges->inputType == inputType)
{
int this_size = hotChanges.size(), source_size = sourceOfHotChanges->hotChanges.size();
int bytes_to_copy = bytes * start;
int dest_pos = 0, source_pos = 0;
if (bytes_to_copy > source_size)
bytes_to_copy = source_size;
memcpy(&hotChanges[dest_pos], &sourceOfHotChanges->hotChanges[source_pos], bytes_to_copy);
dest_pos += bytes_to_copy;
source_pos += bytes_to_copy + bytes * frames;
bytes_to_copy = this_size - dest_pos;
if (bytes_to_copy > source_size - source_pos)
bytes_to_copy = source_size - source_pos;
memcpy(&hotChanges[dest_pos], &sourceOfHotChanges->hotChanges[source_pos], bytes_to_copy);
if (fadeOld)
fadeHotChanges();
}
}
void INPUTLOG::inheritHotChanges_InsertNum(INPUTLOG* sourceOfHotChanges, int start, int frames, bool fadeOld)
{
int bytes = joysticksPerFrame[inputType] * HOTCHANGE_BYTES_PER_JOY;
// copy hot changes from source InputLog up to "start", then make a gap, then copy from "start+frames" to end
if (sourceOfHotChanges && sourceOfHotChanges->hasHotChanges && sourceOfHotChanges->inputType == inputType)
{
int this_size = hotChanges.size(), source_size = sourceOfHotChanges->hotChanges.size();
int bytes_to_copy = bytes * start;
int dest_pos = 0, source_pos = 0;
if (bytes_to_copy > source_size)
bytes_to_copy = source_size;
memcpy(&hotChanges[dest_pos], &sourceOfHotChanges->hotChanges[source_pos], bytes_to_copy);
dest_pos += bytes_to_copy + bytes * frames;
source_pos += bytes_to_copy;
bytes_to_copy = this_size - dest_pos;
if (bytes_to_copy > source_size - source_pos)
bytes_to_copy = source_size - source_pos;
memcpy(&hotChanges[dest_pos], &sourceOfHotChanges->hotChanges[source_pos], bytes_to_copy);
if (fadeOld)
fadeHotChanges();
}
// fill the gap with max_hot lines on frames from "start" to "start+frames"
memset(&hotChanges[bytes * start], BYTE_VALUE_CONTAINING_MAX_HOTCHANGES, bytes * frames);
}
void INPUTLOG::inheritHotChanges_PasteInsert(INPUTLOG* sourceOfHotChanges, RowsSelection* insertedSet)
{
// copy hot changes from source InputLog and insert filled lines for inserted frames (which are represented by "inserted_set")
int bytes = joysticksPerFrame[inputType] * HOTCHANGE_BYTES_PER_JOY;
int frame = 0, pos = 0;
int this_size = hotChanges.size();
RowsSelection::iterator it(insertedSet->begin());
RowsSelection::iterator inserted_set_end(insertedSet->end());
if (sourceOfHotChanges && sourceOfHotChanges->hasHotChanges && sourceOfHotChanges->inputType == inputType)
{
int source_pos = 0;
int source_size = sourceOfHotChanges->hotChanges.size();
while (pos < this_size)
{
if (it != inserted_set_end && frame == *it)
{
// this frame was inserted
it++;
// set filled line to the frame
memset(&hotChanges[pos], BYTE_VALUE_CONTAINING_MAX_HOTCHANGES, bytes);
} else if (source_pos < source_size)
{
// copy hotchanges of this frame
memcpy(&hotChanges[pos], &sourceOfHotChanges->hotChanges[source_pos], bytes);
fadeHotChanges(pos, pos + bytes);
source_pos += bytes;
}
pos += bytes;
frame++;
}
} else
{
// no old data, just fill selected lines
while (pos < this_size)
{
if (it != inserted_set_end && frame == *it)
{
// this frame was inserted
it++;
// set filled line to the frame
memset(&hotChanges[pos], BYTE_VALUE_CONTAINING_MAX_HOTCHANGES, bytes);
pos += bytes;
// exit loop when all inserted_set frames are handled
if (it == inserted_set_end) break;
} else
{
// leave zeros in this frame
pos += bytes;
}
frame++;
}
}
}
void INPUTLOG::fillHotChanges(INPUTLOG& theirLog, int start, int end)
{
// compare InputLogs to the specified end (or to the end of this InputLog)
if (end < 0 || end >= size) end = size-1;
uint8 my_joy, their_joy;
for (int joy = joysticksPerFrame[inputType] - 1; joy >= 0; joy--)
{
for (int frame = start; frame <= end; ++frame)
{
my_joy = getJoystickData(frame, joy);
their_joy = theirLog.getJoystickData(frame, joy);
if (my_joy != their_joy)
setMaxHotChangeBits(frame, joy, my_joy ^ their_joy);
}
}
}
void INPUTLOG::setMaxHotChangeBits(int frame, int joypad, uint8 joyBits)
{
uint8 mask = 1;
// check all 8 buttons and set max hot_changes for bits that are set
for (int i = 0; i < BUTTONS_PER_JOYSTICK; ++i)
{
if (joyBits & mask)
setMaxHotChanges(frame, joypad * BUTTONS_PER_JOYSTICK + i);
mask <<= 1;
}
}
void INPUTLOG::setMaxHotChanges(int frame, int absoluteButtonNumber)
{
if (frame < 0 || frame >= size || !hasHotChanges) return;
// set max value to the button hotness
if (absoluteButtonNumber & 1)
hotChanges[frame * (HOTCHANGE_BYTES_PER_JOY * joysticksPerFrame[inputType]) + (absoluteButtonNumber >> 1)] |= BYTE_VALUE_CONTAINING_MAX_HOTCHANGE_HI;
else
hotChanges[frame * (HOTCHANGE_BYTES_PER_JOY * joysticksPerFrame[inputType]) + (absoluteButtonNumber >> 1)] |= BYTE_VALUE_CONTAINING_MAX_HOTCHANGE_LO;
}
void INPUTLOG::fadeHotChanges(int startByte, int endByte)
{
uint8 hi_half, low_half;
if (endByte < 0)
endByte = hotChanges.size();
for (int i = endByte - 1; i >= startByte; i--)
{
if (hotChanges[i])
{
hi_half = hotChanges[i] >> HOTCHANGE_BITS_PER_VALUE;
low_half = hotChanges[i] & HOTCHANGE_BITMASK;
if (hi_half) hi_half--;
if (low_half) low_half--;
hotChanges[i] = (hi_half << HOTCHANGE_BITS_PER_VALUE) | low_half;
}
}
}
int INPUTLOG::getHotChangesInfo(int frame, int absoluteButtonNumber)
{
if (!hasHotChanges || frame < 0 || frame >= size || absoluteButtonNumber < 0 || absoluteButtonNumber >= NUM_JOYPAD_BUTTONS * joysticksPerFrame[inputType])
return 0;
uint8 val = hotChanges[frame * (HOTCHANGE_BYTES_PER_JOY * joysticksPerFrame[inputType]) + (absoluteButtonNumber >> 1)];
if (absoluteButtonNumber & 1)
// odd buttons (B, T, D, R) take upper 4 bits of the byte
return val >> HOTCHANGE_BITS_PER_VALUE;
else
// even buttons (A, S, U, L) take lower 4 bits of the byte
return val & HOTCHANGE_BITMASK;
}

View File

@ -0,0 +1,92 @@
// Specification file for InputLog class
#pragma once
#include <stdint.h>
#include <vector>
#include "fceu.h"
#include "movie.h"
#include "Qt/TasEditor/selection.h"
enum INPUT_TYPES
{
INPUT_TYPE_1P,
INPUT_TYPE_2P,
INPUT_TYPE_FOURSCORE,
// ...
INPUT_TYPES_TOTAL
};
#define BUTTONS_PER_JOYSTICK 8
#define BYTES_PER_JOYSTICK 1 // 1 byte per 1 joystick (8 buttons)
#define HOTCHANGE_BITS_PER_VALUE 4 // any HotChange value takes 4 bits
#define HOTCHANGE_BITMASK 0xF // "1111"
#define HOTCHANGE_MAX_VALUE 0xF // "1111" max
#define HOTCHANGE_VALUES_PER_BYTE 2 // hence 2 HotChange values fit into 1 byte
#define BYTE_VALUE_CONTAINING_MAX_HOTCHANGES ((HOTCHANGE_MAX_VALUE << HOTCHANGE_BITS_PER_VALUE) | HOTCHANGE_MAX_VALUE) // "0xFF"
#define BYTE_VALUE_CONTAINING_MAX_HOTCHANGE_HI (HOTCHANGE_MAX_VALUE << HOTCHANGE_BITS_PER_VALUE) // "0xF0"
#define BYTE_VALUE_CONTAINING_MAX_HOTCHANGE_LO HOTCHANGE_MAX_VALUE // "0x0F"
#define HOTCHANGE_BYTES_PER_JOY (BYTES_PER_JOYSTICK * HOTCHANGE_BITS_PER_VALUE) // 4 bytes per 8 buttons
class INPUTLOG
{
public:
INPUTLOG();
void init(MovieData& md, bool hotchanges, int force_input_type = -1);
void reinit(MovieData& md, bool hotchanges, int frame_of_change); // used when combining consecutive Recordings
void toMovie(MovieData& md, int start = 0, int end = -1);
void save(EMUFILE *os);
bool load(EMUFILE *is);
bool skipLoad(EMUFILE *is);
void compressData(void);
bool isAlreadyCompressed(void);
int findFirstChange(INPUTLOG& theirLog, int start = 0, int end = -1);
int findFirstChange(MovieData& md, int start = 0, int end = -1);
int getJoystickData(int frame, int joy);
int getCommandsData(int frame);
void insertFrames(int at, int frames);
void eraseFrame(int frame);
void initHotChanges();
void copyHotChanges(INPUTLOG* sourceOfHotChanges, int limiterFrameOfSource = -1);
void inheritHotChanges(INPUTLOG* sourceOfHotChanges);
void inheritHotChanges_DeleteSelection(INPUTLOG* sourceOfHotChanges, RowsSelection* frameset);
void inheritHotChanges_InsertSelection(INPUTLOG* sourceOfHotChanges, RowsSelection* frameset);
void inheritHotChanges_DeleteNum(INPUTLOG* sourceOfHotChanges, int start, int frames, bool fadeOld);
void inheritHotChanges_InsertNum(INPUTLOG* sourceOfHotChanges, int start, int frames, bool fadeOld);
void inheritHotChanges_PasteInsert(INPUTLOG* sourceOfHotChanges, RowsSelection* insertedSet);
void fillHotChanges(INPUTLOG& theirLog, int start = 0, int end = -1);
void setMaxHotChangeBits(int frame, int joypad, uint8_t joyBits);
void setMaxHotChanges(int frame, int absoluteButtonNumber);
void fadeHotChanges(int startByte = 0, int endByte = -1);
int getHotChangesInfo(int frame, int absoluteButtonNumber);
// saved data
int size; // in frames
int inputType; // theoretically TAS Editor can support any other Input types
bool hasHotChanges;
private:
// also saved data
std::vector<uint8_t> compressedJoysticks;
std::vector<uint8_t> compressedCommands;
std::vector<uint8_t> compressedHotChanges;
// not saved data
std::vector<uint8_t> hotChanges; // Format: buttons01joy0-for-frame0, buttons23joy0-for-frame0, buttons45joy0-for-frame0, buttons67joy0-for-frame0, buttons01joy1-for-frame0, ...
std::vector<uint8_t> joysticks; // Format: joy0-for-frame0, joy1-for-frame0, joy2-for-frame0, joy3-for-frame0, joy0-for-frame1, joy1-for-frame1, ...
std::vector<uint8_t> commands; // Format: commands-for-frame0, commands-for-frame1, ...
bool alreadyCompressed; // to compress only once
};

View File

@ -0,0 +1,123 @@
/* ---------------------------------------------------------------------------------
Implementation file of Markers class
Copyright (c) 2011-2013 AnS
(The MIT License)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
------------------------------------------------------------------------------------
Markers - Snapshot of Markers state
* stores the data about Markers state: array of distributing Markers among movie frames, and array of Notes
* implements compression and decompression of stored data
* saves and loads the data from a project file. On error: sends warning to caller
* stores resources: max length of a Note
------------------------------------------------------------------------------------ */
#include "../common.h"
#include "markers.h"
#include "zlib.h"
MARKERS::MARKERS()
{
alreadyCompressed = false;
}
void MARKERS::save(EMUFILE *os)
{
// write size
int size = markersArray.size();
int len;
write32le(size, os);
// write array
if (!alreadyCompressed)
compressData();
write32le(compressedMarkersArray.size(), os);
os->fwrite(&compressedMarkersArray[0], compressedMarkersArray.size());
// write notes
size = notes.size();
write32le(size, os);
for (int i = 0; i < size; ++i)
{
len = notes[i].length() + 1;
if (len > MAX_NOTE_LEN) len = MAX_NOTE_LEN;
write32le(len, os);
os->fwrite(notes[i].c_str(), len);
}
}
// returns true if couldn't load
bool MARKERS::load(EMUFILE *is)
{
int size;
if (read32le(&size, is))
{
markersArray.resize(size);
// read and uncompress array
alreadyCompressed = true;
int comprlen, len;
uLongf destlen = size * sizeof(int);
if (!read32le(&comprlen, is)) return true;
if (comprlen <= 0) return true;
compressedMarkersArray.resize(comprlen);
if (is->fread(&compressedMarkersArray[0], comprlen) != comprlen) return true;
int e = uncompress((uint8*)&markersArray[0], &destlen, &compressedMarkersArray[0], comprlen);
if (e != Z_OK && e != Z_BUF_ERROR) return true;
// read notes
if (read32le(&size, is) && size >= 0)
{
notes.resize(size);
char temp_str[MAX_NOTE_LEN];
for (int i = 0; i < size; ++i)
{
if (!read32le(&len, is) || len < 0) return true;
if ((int)is->fread(temp_str, len) < len) return true;
notes[i] = temp_str;
}
// all ok
return false;
}
}
return true;
}
bool MARKERS::skipLoad(EMUFILE *is)
{
if (!(is->fseek(sizeof(int), SEEK_CUR)))
{
// read array
int comprlen, len;
if (!read32le(&comprlen, is)) return true;
if (is->fseek(comprlen, SEEK_CUR) != 0) return true;
// read notes
if (read32le(&comprlen, is) && comprlen >= 0)
{
for (int i = 0; i < comprlen; ++i)
{
if (!read32le(&len, is) || len < 0) return true;
if (is->fseek(len, SEEK_CUR) != 0) return true;
}
// all ok
return false;
}
}
return true;
}
void MARKERS::compressData()
{
int len = markersArray.size() * sizeof(int);
uLongf comprlen = (len>>9)+12 + len;
compressedMarkersArray.resize(comprlen);
compress(&compressedMarkersArray[0], &comprlen, (uint8*)&markersArray[0], len);
compressedMarkersArray.resize(comprlen);
alreadyCompressed = true;
}
bool MARKERS::isAalreadyCompressed()
{
return alreadyCompressed;
}
void MARKERS::resetCompressedStatus()
{
alreadyCompressed = false;
}

View File

@ -0,0 +1,32 @@
// Specification file for Markers class
#pragma once
#include <stdint.h>
#include <vector>
#include <string>
#define MAX_NOTE_LEN 100
class MARKERS
{
public:
MARKERS();
void save(EMUFILE *os);
bool load(EMUFILE *is);
bool skipLoad(EMUFILE *is);
void compressData(void);
bool isAalreadyCompressed(void);
void resetCompressedStatus(void);
// saved data
std::vector<std::string> notes; // Format: 0th - note for intro (Marker 0), 1st - note for Marker1, 2nd - note for Marker2, ...
// not saved data
std::vector<int> markersArray; // Format: 0th = Marker number (id) for frame 0, 1st = Marker number for frame 1, ...
private:
// also saved data
std::vector<uint8_t> compressedMarkersArray;
bool alreadyCompressed; // to compress only once
};

View File

@ -0,0 +1,752 @@
/* ---------------------------------------------------------------------------------
Implementation file of Markers_manager class
Copyright (c) 2011-2013 AnS
(The MIT License)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
------------------------------------------------------------------------------------
Markers_manager - Manager of Markers
[Single instance]
* stores one snapshot of Markers, representing current state of Markers in the project
* saves and loads the data from a project file. On error: clears the data
* regularly ensures that the size of current Markers array is not less than the number of frames in current Input
* implements all operations with Markers: setting Marker to a frame, removing Marker, inserting/deleting frames between Markers, truncating Markers array, changing Notes, finding frame for any given Marker, access to the data of Snapshot of Markers state
* implements full/partial copying of data between two Snapshots of Markers state, and searching for first difference between two Snapshots of Markers state
* also here's the code of searching for "similar" Notes
* also here's the code of editing Marker Notes
* also here's the code of Find Note dialog
* stores resources: save id, properties of searching for similar Notes
------------------------------------------------------------------------------------ */
#include "taseditor_project.h"
#include <Shlwapi.h> // for StrStrI
#pragma comment(lib, "Shlwapi.lib")
extern TASEDITOR_CONFIG taseditorConfig;
extern TASEDITOR_WINDOW taseditorWindow;
extern PLAYBACK playback;
extern SELECTION selection;
extern HISTORY history;
// resources
char markers_save_id[MARKERS_ID_LEN] = "MARKERS";
char markers_skipsave_id[MARKERS_ID_LEN] = "MARKERX";
char keywordDelimiters[] = " !\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~";
MARKERS_MANAGER::MARKERS_MANAGER()
{
memset(findNoteString, 0, MAX_NOTE_LEN);
}
void MARKERS_MANAGER::init()
{
reset();
}
void MARKERS_MANAGER::free()
{
markers.markersArray.resize(0);
markers.notes.resize(0);
}
void MARKERS_MANAGER::reset()
{
free();
markerNoteEditMode = MARKER_NOTE_EDIT_NONE;
currentIterationOfFindSimilar = 0;
markers.notes.resize(1);
markers.notes[0] = "Power on";
update();
}
void MARKERS_MANAGER::update()
{
// the size of current markers_array must be no less then the size of Input
if ((int)markers.markersArray.size() < currMovieData.getNumRecords())
markers.markersArray.resize(currMovieData.getNumRecords());
}
void MARKERS_MANAGER::save(EMUFILE *os, bool really_save)
{
if (really_save)
{
// write "MARKERS" string
os->fwrite(markers_save_id, MARKERS_ID_LEN);
markers.resetCompressedStatus(); // must recompress data, because most likely it has changed since last compression
markers.save(os);
} else
{
// write "MARKERX" string, meaning that Markers are not saved
os->fwrite(markers_skipsave_id, MARKERS_ID_LEN);
}
}
// returns true if couldn't load
bool MARKERS_MANAGER::load(EMUFILE *is, unsigned int offset)
{
if (offset)
{
if (is->fseek(offset, SEEK_SET)) goto error;
} else
{
reset();
return false;
}
// read "MARKERS" string
char save_id[MARKERS_ID_LEN];
if ((int)is->fread(save_id, MARKERS_ID_LEN) < MARKERS_ID_LEN) goto error;
if (!strcmp(markers_skipsave_id, save_id))
{
// string says to skip loading Markers
FCEU_printf("No Markers in the file\n");
reset();
return false;
}
if (strcmp(markers_save_id, save_id)) goto error; // string is not valid
if (markers.load(is)) goto error;
// all ok
return false;
error:
FCEU_printf("Error loading Markers\n");
reset();
return true;
}
// -----------------------------------------------------------------------------------------
int MARKERS_MANAGER::getMarkersArraySize()
{
return markers.markersArray.size();
}
bool MARKERS_MANAGER::setMarkersArraySize(int newSize)
{
// if we are truncating, clear Markers that are gonna be erased (so that obsolete notes will be erased too)
bool markers_changed = false;
for (int i = markers.markersArray.size() - 1; i >= newSize; i--)
{
if (markers.markersArray[i])
{
removeMarkerFromFrame(i);
markers_changed = true;
}
}
markers.markersArray.resize(newSize);
return markers_changed;
}
int MARKERS_MANAGER::getMarkerAtFrame(int frame)
{
if (frame >= 0 && frame < (int)markers.markersArray.size())
return markers.markersArray[frame];
else
return 0;
}
// finds and returns # of Marker starting from start_frame and searching up
int MARKERS_MANAGER::getMarkerAboveFrame(int startFrame)
{
if (startFrame >= (int)markers.markersArray.size())
startFrame = markers.markersArray.size() - 1;
for (; startFrame >= 0; startFrame--)
if (markers.markersArray[startFrame]) return markers.markersArray[startFrame];
return 0;
}
// special version of the function
int MARKERS_MANAGER::getMarkerAboveFrame(MARKERS& targetMarkers, int startFrame)
{
if (startFrame >= (int)targetMarkers.markersArray.size())
startFrame = targetMarkers.markersArray.size() - 1;
for (; startFrame >= 0; startFrame--)
if (targetMarkers.markersArray[startFrame]) return targetMarkers.markersArray[startFrame];
return 0;
}
// finds frame where the Marker is set
int MARKERS_MANAGER::getMarkerFrameNumber(int marker_id)
{
for (int i = markers.markersArray.size() - 1; i >= 0; i--)
if (markers.markersArray[i] == marker_id) return i;
// didn't find
return -1;
}
// returns number of new Marker
int MARKERS_MANAGER::setMarkerAtFrame(int frame)
{
if (frame < 0)
return 0;
else if (frame >= (int)markers.markersArray.size())
markers.markersArray.resize(frame + 1);
else if (markers.markersArray[frame])
return markers.markersArray[frame];
int marker_num = getMarkerAboveFrame(frame) + 1;
markers.markersArray[frame] = marker_num;
if (taseditorConfig.emptyNewMarkerNotes)
markers.notes.insert(markers.notes.begin() + marker_num, 1, "");
else
// copy previous Marker note
markers.notes.insert(markers.notes.begin() + marker_num, 1, markers.notes[marker_num - 1]);
// increase following Markers' ids
int size = markers.markersArray.size();
for (frame++; frame < size; ++frame)
if (markers.markersArray[frame])
markers.markersArray[frame]++;
return marker_num;
}
void MARKERS_MANAGER::removeMarkerFromFrame(int frame)
{
if (markers.markersArray[frame])
{
// erase corresponding note
markers.notes.erase(markers.notes.begin() + markers.markersArray[frame]);
// clear Marker
markers.markersArray[frame] = 0;
// decrease following Markers' ids
int size = markers.markersArray.size();
for (frame++; frame < size; ++frame)
if (markers.markersArray[frame])
markers.markersArray[frame]--;
}
}
void MARKERS_MANAGER::toggleMarkerAtFrame(int frame)
{
if (frame >= 0 && frame < (int)markers.markersArray.size())
{
if (markers.markersArray[frame])
removeMarkerFromFrame(frame);
else
setMarkerAtFrame(frame);
}
}
bool MARKERS_MANAGER::eraseMarker(int frame, int numFrames)
{
bool markers_changed = false;
if (frame < (int)markers.markersArray.size())
{
if (numFrames == 1)
{
// erase 1 frame
// if there's a Marker, first clear it
if (markers.markersArray[frame])
{
removeMarkerFromFrame(frame);
markers_changed = true;
}
// erase 1 frame
markers.markersArray.erase(markers.markersArray.begin() + frame);
} else
{
// erase many frames
if (frame + numFrames > (int)markers.markersArray.size())
numFrames = (int)markers.markersArray.size() - frame;
// if there are Markers at those frames, first clear them
for (int i = frame; i < (frame + numFrames); ++i)
{
if (markers.markersArray[i])
{
removeMarkerFromFrame(i);
markers_changed = true;
}
}
// erase frames
markers.markersArray.erase(markers.markersArray.begin() + frame, markers.markersArray.begin() + (frame + numFrames));
}
// check if there were some Markers after this frame
// since these Markers were shifted, markers_changed should be set to true
if (!markers_changed)
{
for (int i = markers.markersArray.size() - 1; i >= frame; i--)
{
if (markers.markersArray[i])
{
markers_changed = true; // Markers moved
break;
}
}
}
}
return markers_changed;
}
bool MARKERS_MANAGER::insertEmpty(int at, int numFrames)
{
if (at == -1)
{
// append blank frames
markers.markersArray.resize(markers.markersArray.size() + numFrames);
return false;
} else
{
bool markers_changed = false;
// first check if there are Markers after the frame
for (int i = markers.markersArray.size() - 1; i >= at; i--)
{
if (markers.markersArray[i])
{
markers_changed = true; // Markers moved
break;
}
}
markers.markersArray.insert(markers.markersArray.begin() + at, numFrames, 0);
return markers_changed;
}
}
int MARKERS_MANAGER::getNotesSize()
{
return markers.notes.size();
}
std::string MARKERS_MANAGER::getNoteCopy(int index)
{
if (index >= 0 && index < (int)markers.notes.size())
return markers.notes[index];
else
return markers.notes[0];
}
// special version of the function
std::string MARKERS_MANAGER::getNoteCopy(MARKERS& targetMarkers, int index)
{
if (index >= 0 && index < (int)targetMarkers.notes.size())
return targetMarkers.notes[index];
else
return targetMarkers.notes[0];
}
void MARKERS_MANAGER::setNote(int index, const char* newText)
{
if (index >= 0 && index < (int)markers.notes.size())
markers.notes[index] = newText;
}
// ---------------------------------------------------------------------------------------
void MARKERS_MANAGER::makeCopyOfCurrentMarkersTo(MARKERS& destination)
{
destination.markersArray = markers.markersArray;
destination.notes = markers.notes;
destination.resetCompressedStatus();
}
void MARKERS_MANAGER::restoreMarkersFromCopy(MARKERS& source)
{
markers.markersArray = source.markersArray;
markers.notes = source.notes;
}
// return true only when difference is found before end frame (not including end frame)
bool MARKERS_MANAGER::checkMarkersDiff(MARKERS& theirMarkers)
{
int end_my = getMarkersArraySize() - 1;
int end_their = theirMarkers.markersArray.size() - 1;
int min_end = end_my;
int i;
// 1 - check if there are any Markers after min_end
if (end_my < end_their)
{
for (i = end_their; i > min_end; i--)
if (theirMarkers.markersArray[i])
return true;
} else if (end_my > end_their)
{
min_end = end_their;
for (i = end_my; i > min_end; i--)
if (markers.markersArray[i])
return true;
}
// 2 - check if there's any difference before min_end
for (i = min_end; i >= 0; i--)
{
if (markers.markersArray[i] != theirMarkers.markersArray[i])
return true;
else if (markers.markersArray[i] && // not empty
markers.notes[markers.markersArray[i]].compare(theirMarkers.notes[theirMarkers.markersArray[i]])) // notes differ
return true;
}
// 3 - check if there's difference between 0th Notes
if (markers.notes[0].compare(theirMarkers.notes[0]))
return true;
// no difference found
return false;
}
// ------------------------------------------------------------------------------------
// custom ordering function, used by std::sort
bool ordering(const std::pair<int, double>& d1, const std::pair<int, double>& d2)
{
return d1.second < d2.second;
}
void MARKERS_MANAGER::findSimilarNote()
{
currentIterationOfFindSimilar = 0;
findNextSimilarNote();
}
void MARKERS_MANAGER::findNextSimilarNote()
{
int i, t;
int sourceMarker = playback.displayedMarkerNumber;
char sourceNote[MAX_NOTE_LEN];
strcpy(sourceNote, getNoteCopy(sourceMarker).c_str());
// check if playback_marker_text is empty
if (!sourceNote[0])
{
MessageBox(taseditorWindow.hwndTASEditor, "Marker Note under Playback cursor is empty!", "Find Similar Note", MB_OK);
return;
}
// check if there's at least one note (not counting zeroth note)
if (markers.notes.size() <= 0)
{
MessageBox(taseditorWindow.hwndTASEditor, "This project doesn't have any Markers!", "Find Similar Note", MB_OK);
return;
}
// 0 - divide source string into keywords
int totalSourceKeywords = 0;
char sourceKeywords[MAX_NUM_KEYWORDS][MAX_NOTE_LEN] = {0};
int current_line_pos = 0;
char sourceKeywordsLine[MAX_NUM_KEYWORDS] = {0};
char* pch;
// divide into tokens
pch = strtok(sourceNote, keywordDelimiters);
while (pch != NULL)
{
if (strlen(pch) >= KEYWORD_MIN_LEN)
{
// check if same keyword already appeared in the string
for (t = totalSourceKeywords - 1; t >= 0; t--)
if (!_stricmp(sourceKeywords[t], pch)) break;
if (t < 0)
{
// save new keyword
strcpy(sourceKeywords[totalSourceKeywords], pch);
// also set its id into the line
sourceKeywordsLine[current_line_pos++] = totalSourceKeywords + 1;
totalSourceKeywords++;
} else
{
// same keyword found
sourceKeywordsLine[current_line_pos++] = t + 1;
}
}
pch = strtok(NULL, keywordDelimiters);
}
// we found the line (sequence) of keywords
sourceKeywordsLine[current_line_pos] = 0;
if (!totalSourceKeywords)
{
MessageBox(taseditorWindow.hwndTASEditor, "Marker Note under Playback cursor doesn't have keywords!", "Find Similar Note", MB_OK);
return;
}
// 1 - find how frequently each keyword appears in notes
std::vector<int> keywordFound(totalSourceKeywords);
char checkedNote[MAX_NOTE_LEN];
for (i = markers.notes.size() - 1; i > 0; i--)
{
if (i != sourceMarker)
{
strcpy(checkedNote, markers.notes[i].c_str());
for (t = totalSourceKeywords - 1; t >= 0; t--)
if (StrStrI(checkedNote, sourceKeywords[t]))
keywordFound[t]++;
}
}
// findmax
int maxFound = 0;
for (t = totalSourceKeywords - 1; t >= 0; t--)
if (maxFound < keywordFound[t])
maxFound = keywordFound[t];
// and then calculate weight of each keyword: the more often it appears in Markers, the less weight it has
std::vector<double> keywordWeight(totalSourceKeywords);
for (t = totalSourceKeywords - 1; t >= 0; t--)
keywordWeight[t] = KEYWORD_WEIGHT_BASE + KEYWORD_WEIGHT_FACTOR * (keywordFound[t] / (double)maxFound);
// start accumulating priorities
std::vector<std::pair<int, double>> notePriority(markers.notes.size());
// 2 - find keywords in notes (including cases when keyword appears inside another word)
for (i = notePriority.size() - 1; i > 0; i--)
{
notePriority[i].first = i;
if (i != sourceMarker)
{
strcpy(checkedNote, markers.notes[i].c_str());
for (t = totalSourceKeywords - 1; t >= 0; t--)
{
if (StrStrI(checkedNote, sourceKeywords[t]))
notePriority[i].second += KEYWORD_CASEINSENTITIVE_BONUS_PER_CHAR * keywordWeight[t] * strlen(sourceKeywords[t]);
if (strstr(checkedNote, sourceKeywords[t]))
notePriority[i].second += KEYWORD_CASESENTITIVE_BONUS_PER_CHAR * keywordWeight[t] * strlen(sourceKeywords[t]);
}
}
}
// 3 - search sequences of keywords from all other notes
current_line_pos = 0;
char checkedKeywordsLine[MAX_NUM_KEYWORDS] = {0};
int keyword_id;
for (i = markers.notes.size() - 1; i > 0; i--)
{
if (i != sourceMarker)
{
strcpy(checkedNote, markers.notes[i].c_str());
// divide into tokens
pch = strtok(checkedNote, keywordDelimiters);
while (pch != NULL)
{
if (strlen(pch) >= KEYWORD_MIN_LEN)
{
// check if the keyword is one of sourceKeywords
for (t = totalSourceKeywords - 1; t >= 0; t--)
if (!_stricmp(sourceKeywords[t], pch)) break;
if (t >= 0)
{
// the keyword is one of sourceKeywords - set its id into the line
checkedKeywordsLine[current_line_pos++] = t + 1;
} else
{
// found keyword that doesn't appear in sourceNote, give penalty
notePriority[i].second -= KEYWORD_PENALTY_FOR_STRANGERS * strlen(pch);
// since the keyword breaks our sequence of coincident keywords, check if that sequence is similar to sourceKeywordsLine
if (current_line_pos >= KEYWORDS_LINE_MIN_SEQUENCE)
{
checkedKeywordsLine[current_line_pos] = 0;
// search checkedKeywordsLine in sourceKeywordsLine
if (strstr(sourceKeywordsLine, checkedKeywordsLine))
{
// found same sequence of keywords! add priority to this checkedNote
for (t = current_line_pos - 1; t >= 0; t--)
{
// add bonus for every keyword in the sequence
keyword_id = checkedKeywordsLine[t] - 1;
notePriority[i].second += current_line_pos * KEYWORD_SEQUENCE_BONUS_PER_CHAR * keywordWeight[keyword_id] * strlen(sourceKeywords[keyword_id]);
}
}
}
// clear checkedKeywordsLine
memset(checkedKeywordsLine, 0, MAX_NUM_KEYWORDS);
current_line_pos = 0;
}
}
pch = strtok(NULL, keywordDelimiters);
}
// finished dividing into tokens
if (current_line_pos >= KEYWORDS_LINE_MIN_SEQUENCE)
{
checkedKeywordsLine[current_line_pos] = 0;
// search checkedKeywordsLine in sourceKeywordsLine
if (strstr(sourceKeywordsLine, checkedKeywordsLine))
{
// found same sequence of keywords! add priority to this checkedNote
for (t = current_line_pos - 1; t >= 0; t--)
{
// add bonus for every keyword in the sequence
keyword_id = checkedKeywordsLine[t] - 1;
notePriority[i].second += current_line_pos * KEYWORD_SEQUENCE_BONUS_PER_CHAR * keywordWeight[keyword_id] * strlen(sourceKeywords[keyword_id]);
}
}
}
// clear checkedKeywordsLine
memset(checkedKeywordsLine, 0, MAX_NUM_KEYWORDS);
current_line_pos = 0;
}
}
// 4 - sort notePriority by second member of the pair
std::sort(notePriority.begin(), notePriority.end(), ordering);
/*
// debug trace
FCEU_printf("\n\n\n\n\n\n\n\n\n\n");
for (t = totalSourceKeywords - 1; t >= 0; t--)
FCEU_printf("Keyword: %s, %d, %f\n", sourceKeywords[t], keywordFound[t], keywordWeight[t]);
for (i = notePriority.size() - 1; i > 0; i--)
{
int marker_id = notePriority[i].first;
FCEU_printf("Result: %s, %d, %f\n", notes[marker_id].c_str(), marker_id, notePriority[i].second);
}
*/
// Send Selection to the Marker found
int index = notePriority.size()-1 - currentIterationOfFindSimilar;
if (index >= 0 && notePriority[index].second >= MIN_PRIORITY_TRESHOLD)
{
int marker_id = notePriority[index].first;
int frame = getMarkerFrameNumber(marker_id);
if (frame >= 0)
selection.jumpToFrame(frame);
} else
{
if (currentIterationOfFindSimilar)
MessageBox(taseditorWindow.hwndTASEditor, "Could not find more Notes similar to Marker Note under Playback cursor!", "Find Similar Note", MB_OK);
else
MessageBox(taseditorWindow.hwndTASEditor, "Could not find anything similar to Marker Note under Playback cursor!", "Find Similar Note", MB_OK);
}
// increase currentIterationOfFindSimilar so that next time we'll find another note
currentIterationOfFindSimilar++;
}
// ------------------------------------------------------------------------------------
void MARKERS_MANAGER::updateEditedMarkerNote()
{
if (!markerNoteEditMode) return;
char new_text[MAX_NOTE_LEN];
if (markerNoteEditMode == MARKER_NOTE_EDIT_UPPER)
{
int len = SendMessage(playback.hwndPlaybackMarkerEditField, WM_GETTEXT, MAX_NOTE_LEN, (LPARAM)new_text);
new_text[len] = 0;
// check changes
if (strcmp(getNoteCopy(playback.displayedMarkerNumber).c_str(), new_text))
{
setNote(playback.displayedMarkerNumber, new_text);
if (playback.displayedMarkerNumber)
history.registerMarkersChange(MODTYPE_MARKER_RENAME, getMarkerFrameNumber(playback.displayedMarkerNumber), -1, new_text);
else
// zeroth Marker - just assume it's set on frame 0
history.registerMarkersChange(MODTYPE_MARKER_RENAME, 0, -1, new_text);
// notify Selection to change text in the lower Marker (in case both are showing same Marker)
selection.mustFindCurrentMarker = true;
}
} else if (markerNoteEditMode == MARKER_NOTE_EDIT_LOWER)
{
int len = SendMessage(selection.hwndSelectionMarkerEditField, WM_GETTEXT, MAX_NOTE_LEN, (LPARAM)new_text);
new_text[len] = 0;
// check changes
if (strcmp(getNoteCopy(selection.displayedMarkerNumber).c_str(), new_text))
{
setNote(selection.displayedMarkerNumber, new_text);
if (selection.displayedMarkerNumber)
history.registerMarkersChange(MODTYPE_MARKER_RENAME, getMarkerFrameNumber(selection.displayedMarkerNumber), -1, new_text);
else
// zeroth Marker - just assume it's set on frame 0
history.registerMarkersChange(MODTYPE_MARKER_RENAME, 0, -1, new_text);
// notify Playback to change text in upper Marker (in case both are showing same Marker)
playback.mustFindCurrentMarker = true;
}
}
}
// ------------------------------------------------------------------------------------
INT_PTR CALLBACK findNoteWndProc(HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
extern MARKERS_MANAGER markersManager;
switch (message)
{
case WM_INITDIALOG:
{
if (taseditorConfig.findnoteWindowX == -32000) taseditorConfig.findnoteWindowX = 0; //Just in case
if (taseditorConfig.findnoteWindowY == -32000) taseditorConfig.findnoteWindowY = 0;
SetWindowPos(hwndDlg, 0, taseditorConfig.findnoteWindowX, taseditorConfig.findnoteWindowY, 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER);
CheckDlgButton(hwndDlg, IDC_MATCH_CASE, taseditorConfig.findnoteMatchCase ? BST_CHECKED : BST_UNCHECKED);
if (taseditorConfig.findnoteSearchUp)
Button_SetCheck(GetDlgItem(hwndDlg, IDC_RADIO_UP), BST_CHECKED);
else
Button_SetCheck(GetDlgItem(hwndDlg, IDC_RADIO_DOWN), BST_CHECKED);
HWND hwndEdit = GetDlgItem(hwndDlg, IDC_NOTE_TO_FIND);
SendMessage(hwndEdit, EM_SETLIMITTEXT, MAX_NOTE_LEN - 1, 0);
SetWindowText(hwndEdit, markersManager.findNoteString);
if (GetDlgCtrlID((HWND)wParam) != IDC_NOTE_TO_FIND)
{
SetFocus(hwndEdit);
return false;
}
return true;
}
case WM_MOVE:
{
if (!IsIconic(hwndDlg))
{
RECT wrect;
GetWindowRect(hwndDlg, &wrect);
taseditorConfig.findnoteWindowX = wrect.left;
taseditorConfig.findnoteWindowY = wrect.top;
WindowBoundsCheckNoResize(taseditorConfig.findnoteWindowX, taseditorConfig.findnoteWindowY, wrect.right);
}
break;
}
case WM_COMMAND:
{
switch (LOWORD(wParam))
{
case IDC_NOTE_TO_FIND:
{
if (HIWORD(wParam) == EN_CHANGE)
{
if (GetWindowTextLength(GetDlgItem(hwndDlg, IDC_NOTE_TO_FIND)))
EnableWindow(GetDlgItem(hwndDlg, IDOK), true);
else
EnableWindow(GetDlgItem(hwndDlg, IDOK), false);
}
break;
}
case IDC_RADIO_UP:
taseditorConfig.findnoteSearchUp = true;
break;
case IDC_RADIO_DOWN:
taseditorConfig.findnoteSearchUp = false;
break;
case IDC_MATCH_CASE:
taseditorConfig.findnoteMatchCase ^= 1;
CheckDlgButton(hwndDlg, IDC_MATCH_CASE, taseditorConfig.findnoteMatchCase? BST_CHECKED : BST_UNCHECKED);
break;
case IDOK:
{
int len = SendMessage(GetDlgItem(hwndDlg, IDC_NOTE_TO_FIND), WM_GETTEXT, MAX_NOTE_LEN, (LPARAM)markersManager.findNoteString);
markersManager.findNoteString[len] = 0;
// scan frames from current Selection to the border
int cur_marker = 0;
bool result;
int movie_size = currMovieData.getNumRecords();
int current_frame = selection.getCurrentRowsSelectionBeginning();
if (current_frame < 0 && taseditorConfig.findnoteSearchUp)
current_frame = movie_size;
while (true)
{
// move forward
if (taseditorConfig.findnoteSearchUp)
{
current_frame--;
if (current_frame < 0)
{
MessageBox(taseditorWindow.hwndFindNote, "Nothing was found.", "Find Note", MB_OK);
break;
}
} else
{
current_frame++;
if (current_frame >= movie_size)
{
MessageBox(taseditorWindow.hwndFindNote, "Nothing was found!", "Find Note", MB_OK);
break;
}
}
// scan marked frames
cur_marker = markersManager.getMarkerAtFrame(current_frame);
if (cur_marker)
{
if (taseditorConfig.findnoteMatchCase)
result = (strstr(markersManager.getNoteCopy(cur_marker).c_str(), markersManager.findNoteString) != 0);
else
result = (StrStrI(markersManager.getNoteCopy(cur_marker).c_str(), markersManager.findNoteString) != 0);
if (result)
{
// found note containing searched string - jump there
selection.jumpToFrame(current_frame);
break;
}
}
}
return TRUE;
}
case IDCANCEL:
DestroyWindow(taseditorWindow.hwndFindNote);
taseditorWindow.hwndFindNote = 0;
return TRUE;
}
break;
}
case WM_CLOSE:
case WM_QUIT:
{
DestroyWindow(taseditorWindow.hwndFindNote);
taseditorWindow.hwndFindNote = 0;
break;
}
}
return FALSE;
}

View File

@ -0,0 +1,76 @@
// Specification file for Markers_manager class
#include "markers.h"
#define MARKERS_ID_LEN 8
// constants for "Find Similar Note" algorithm (may need finetuning)
#define KEYWORD_MIN_LEN 2
#define MAX_NUM_KEYWORDS (MAX_NOTE_LEN / (KEYWORD_MIN_LEN+1)) + 1
#define KEYWORD_WEIGHT_BASE 2.0
#define KEYWORD_WEIGHT_FACTOR -1.0
#define KEYWORD_CASEINSENTITIVE_BONUS_PER_CHAR 1.0 // these two should be small, because they also work when keyword is inside another keyword, giving irrelevant results
#define KEYWORD_CASESENTITIVE_BONUS_PER_CHAR 1.0
#define KEYWORD_SEQUENCE_BONUS_PER_CHAR 5.0
#define KEYWORD_PENALTY_FOR_STRANGERS 0.2
#define KEYWORDS_LINE_MIN_SEQUENCE 1
#define MIN_PRIORITY_TRESHOLD 5.0
enum MARKER_NOTE_EDIT_MODES
{
MARKER_NOTE_EDIT_NONE,
MARKER_NOTE_EDIT_UPPER,
MARKER_NOTE_EDIT_LOWER
};
class MARKERS_MANAGER
{
public:
MARKERS_MANAGER();
void init();
void free();
void reset();
void update();
void save(EMUFILE *os, bool really_save = true);
bool load(EMUFILE *is, unsigned int offset);
int getMarkersArraySize();
bool setMarkersArraySize(int newSize);
int getMarkerAtFrame(int frame);
int getMarkerAboveFrame(int startFrame);
int getMarkerAboveFrame(MARKERS& targetMarkers, int startFrame); // special version of the function
int getMarkerFrameNumber(int markerID);
int setMarkerAtFrame(int frame);
void removeMarkerFromFrame(int frame);
void toggleMarkerAtFrame(int frame);
bool eraseMarker(int frame, int numFrames = 1);
bool insertEmpty(int at, int numFrames);
int getNotesSize();
std::string getNoteCopy(int index);
std::string getNoteCopy(MARKERS& targetMarkers, int index); // special version of the function
void setNote(int index, const char* newText);
void makeCopyOfCurrentMarkersTo(MARKERS& destination);
void restoreMarkersFromCopy(MARKERS& source);
bool checkMarkersDiff(MARKERS& theirMarkers);
void findSimilarNote();
void findNextSimilarNote();
void updateEditedMarkerNote();
// not saved vars
int markerNoteEditMode;
char findNoteString[MAX_NOTE_LEN];
int currentIterationOfFindSimilar;
private:
// saved vars
MARKERS markers;
};

View File

@ -0,0 +1,627 @@
/* ---------------------------------------------------------------------------------
Implementation file of Playback class
Copyright (c) 2011-2013 AnS
(The MIT License)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
------------------------------------------------------------------------------------
Playback - Player of emulation states
[Single instance]
* implements the working of movie player: show any frame (jump), run/cancel seekng. pause, rewinding
* regularly tracks and controls emulation process, prompts redrawing of Piano Roll List rows, finishes seeking when reaching target frame, animates target frame, makes Piano Roll follow Playback cursor, detects if Playback cursor moved to another Marker and updates Note in the upper text field
* implements the working of upper buttons << and >> (jumping on Markers)
* implements the working of buttons < and > (frame-by-frame movement)
* implements the working of button || (pause) and middle mouse button, also reacts on external changes of emulation pause
* implements the working of progressbar: init, reset, set value, click (cancel seeking)
* also here's the code of upper text field (for editing Marker Notes)
* stores resources: upper text field prefix, timings of target frame animation, response times of GUI buttons, progressbar scale
------------------------------------------------------------------------------------ */
#include "taseditor_project.h"
#include "../taseditor.h"
#include "../../../fceu.h"
#ifdef _S9XLUA_H
extern void ForceExecuteLuaFrameFunctions();
#endif
extern bool mustRewindNow;
extern bool turbo;
extern TASEDITOR_CONFIG taseditorConfig;
extern TASEDITOR_WINDOW taseditorWindow;
extern SELECTION selection;
extern MARKERS_MANAGER markersManager;
extern GREENZONE greenzone;
extern PIANO_ROLL pianoRoll;
extern BOOKMARKS bookmarks;
extern void Update_RAM_Search();
LRESULT APIENTRY UpperMarkerEditWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
WNDPROC playbackMarkerEdit_oldWndproc;
// resources
char upperMarkerText[] = "Marker ";
PLAYBACK::PLAYBACK()
{
}
void PLAYBACK::init()
{
hwndProgressbar = GetDlgItem(taseditorWindow.hwndTASEditor, IDC_PROGRESS1);
SendMessage(hwndProgressbar, PBM_SETRANGE, 0, MAKELPARAM(0, PROGRESSBAR_WIDTH));
hwndRewind = GetDlgItem(taseditorWindow.hwndTASEditor, TASEDITOR_REWIND);
hwndForward = GetDlgItem(taseditorWindow.hwndTASEditor, TASEDITOR_FORWARD);
hwndRewindFull = GetDlgItem(taseditorWindow.hwndTASEditor, TASEDITOR_REWIND_FULL);
hwndForwardFull = GetDlgItem(taseditorWindow.hwndTASEditor, TASEDITOR_FORWARD_FULL);
hwndPlaybackMarkerNumber = GetDlgItem(taseditorWindow.hwndTASEditor, IDC_PLAYBACK_MARKER);
SendMessage(hwndPlaybackMarkerNumber, WM_SETFONT, (WPARAM)pianoRoll.hMarkersFont, 0);
hwndPlaybackMarkerEditField = GetDlgItem(taseditorWindow.hwndTASEditor, IDC_PLAYBACK_MARKER_EDIT);
SendMessage(hwndPlaybackMarkerEditField, EM_SETLIMITTEXT, MAX_NOTE_LEN - 1, 0);
SendMessage(hwndPlaybackMarkerEditField, WM_SETFONT, (WPARAM)pianoRoll.hMarkersEditFont, 0);
// subclass the edit control
playbackMarkerEdit_oldWndproc = (WNDPROC)SetWindowLongPtr(hwndPlaybackMarkerEditField, GWLP_WNDPROC, (LONG_PTR)UpperMarkerEditWndProc);
reset();
}
void PLAYBACK::reset()
{
mustAutopauseAtTheEnd = true;
mustFindCurrentMarker = true;
displayedMarkerNumber = 0;
lastCursorPos = currFrameCounter;
lastPositionFrame = pauseFrame = oldPauseFrame = 0;
lastPositionIsStable = oldStateOfShowPauseFrame = showPauseFrame = false;
rewindButtonOldState = rewindButtonState = false;
forwardButtonOldState = forwardButtonState = false;
rewindFullButtonOldState = rewindFullButtonState = false;
forwardFullButtonOldState = forwardFullButtonState = false;
emuPausedOldState = emuPausedState = true;
stopSeeking();
}
void PLAYBACK::update()
{
// controls:
// update < and > buttons
rewindButtonOldState = rewindButtonState;
rewindButtonState = ((Button_GetState(hwndRewind) & BST_PUSHED) != 0 || mustRewindNow);
if (rewindButtonState)
{
if (!rewindButtonOldState)
{
buttonHoldTimer = clock();
handleRewindFrame();
} else if (buttonHoldTimer + BUTTON_HOLD_REPEAT_DELAY < clock())
{
handleRewindFrame();
}
}
forwardButtonOldState = forwardButtonState;
forwardButtonState = (Button_GetState(hwndForward) & BST_PUSHED) != 0;
if (forwardButtonState && !rewindButtonState)
{
if (!forwardButtonOldState)
{
buttonHoldTimer = clock();
handleForwardFrame();
} else if (buttonHoldTimer + BUTTON_HOLD_REPEAT_DELAY < clock())
{
handleForwardFrame();
}
}
// update << and >> buttons
rewindFullButtonOldState = rewindFullButtonState;
rewindFullButtonState = ((Button_GetState(hwndRewindFull) & BST_PUSHED) != 0);
if (rewindFullButtonState && !rewindButtonState && !forwardButtonState)
{
if (!rewindFullButtonOldState)
{
buttonHoldTimer = clock();
handleRewindFull();
} else if (buttonHoldTimer + BUTTON_HOLD_REPEAT_DELAY < clock())
{
handleRewindFull();
}
}
forwardFullButtonOldState = forwardFullButtonState;
forwardFullButtonState = (Button_GetState(hwndForwardFull) & BST_PUSHED) != 0;
if (forwardFullButtonState && !rewindButtonState && !forwardButtonState && !rewindFullButtonState)
{
if (!forwardFullButtonOldState)
{
buttonHoldTimer = clock();
handleForwardFull();
} else if (buttonHoldTimer + BUTTON_HOLD_REPEAT_DELAY < clock())
{
handleForwardFull();
}
}
// update the Playback cursor
if (currFrameCounter != lastCursorPos)
{
// update gfx of the old and new rows
pianoRoll.redrawRow(lastCursorPos);
bookmarks.redrawChangedBookmarks(lastCursorPos);
pianoRoll.redrawRow(currFrameCounter);
bookmarks.redrawChangedBookmarks(currFrameCounter);
lastCursorPos = currFrameCounter;
// follow the Playback cursor, but in case of seeking don't follow it
pianoRoll.followPlaybackCursorIfNeeded(false); //pianoRoll.updatePlaybackCursorPositionInPianoRoll(); // an unfinished experiment
// enforce redrawing now
UpdateWindow(pianoRoll.hwndList);
// lazy update of "Playback's Marker text"
int current_marker = markersManager.getMarkerAboveFrame(currFrameCounter);
if (displayedMarkerNumber != current_marker)
{
markersManager.updateEditedMarkerNote();
displayedMarkerNumber = current_marker;
redrawMarkerData();
mustFindCurrentMarker = false;
}
}
// [non-lazy] update "Playback's Marker text" if needed
if (mustFindCurrentMarker)
{
markersManager.updateEditedMarkerNote();
displayedMarkerNumber = markersManager.getMarkerAboveFrame(currFrameCounter);
redrawMarkerData();
mustFindCurrentMarker = false;
}
// pause when seeking hits pause_frame
if (pauseFrame && currFrameCounter + 1 >= pauseFrame)
stopSeeking();
else if (currFrameCounter >= getLastPosition() && currFrameCounter >= currMovieData.getNumRecords() - 1 && mustAutopauseAtTheEnd && taseditorConfig.autopauseAtTheEndOfMovie && !isTaseditorRecording())
// pause at the end of the movie
pauseEmulation();
// update flashing pauseframe
if (oldPauseFrame != pauseFrame && oldPauseFrame)
{
// pause_frame was changed, clear old_pauseframe gfx
pianoRoll.redrawRow(oldPauseFrame-1);
bookmarks.redrawChangedBookmarks(oldPauseFrame-1);
}
oldPauseFrame = pauseFrame;
oldStateOfShowPauseFrame = showPauseFrame;
if (pauseFrame)
{
if (emuPausedState)
showPauseFrame = (int)(clock() / PAUSEFRAME_BLINKING_PERIOD_WHEN_PAUSED) & 1;
else
showPauseFrame = (int)(clock() / PAUSEFRAME_BLINKING_PERIOD_WHEN_SEEKING) & 1;
} else showPauseFrame = false;
if (oldStateOfShowPauseFrame != showPauseFrame)
{
// update pauseframe gfx
pianoRoll.redrawRow(pauseFrame - 1);
bookmarks.redrawChangedBookmarks(pauseFrame - 1);
}
// update seeking progressbar
emuPausedOldState = emuPausedState;
emuPausedState = (FCEUI_EmulationPaused() != 0);
if (pauseFrame)
{
if (oldStateOfShowPauseFrame != showPauseFrame) // update progressbar from time to time
// display seeking progress
setProgressbar(currFrameCounter - seekingBeginningFrame, pauseFrame - seekingBeginningFrame);
} else if (emuPausedOldState != emuPausedState)
{
// emulator got paused/unpaused externally
if (emuPausedOldState && !emuPausedState)
{
// externally unpaused - show empty progressbar
setProgressbar(0, 1);
} else
{
// externally paused - progressbar should be full
setProgressbar(1, 1);
}
}
// prepare to stop at the end of the movie in case user unpauses emulator
if (emuPausedState)
{
if (currFrameCounter < currMovieData.getNumRecords() - 1)
mustAutopauseAtTheEnd = true;
else
mustAutopauseAtTheEnd = false;
}
// this little statement is very important for adequate work of the "green arrow" and "Restore last position"
if (!emuPausedState)
// when emulating, lost_position_frame becomes unstable (which means that it's probably not equal to the end of current segment anymore)
lastPositionIsStable = false;
}
// called after saving the project, because saving uses the progressbar for itself
void PLAYBACK::updateProgressbar()
{
if (pauseFrame)
{
setProgressbar(currFrameCounter - seekingBeginningFrame, pauseFrame - seekingBeginningFrame);
} else
{
if (emuPausedState)
// full progressbar
setProgressbar(1, 1);
else
// cleared progressbar
setProgressbar(0, 1);
}
RedrawWindow(hwndProgressbar, NULL, NULL, RDW_INVALIDATE);
}
void PLAYBACK::toggleEmulationPause()
{
if (FCEUI_EmulationPaused())
unpauseEmulation();
else
pauseEmulation();
}
void PLAYBACK::pauseEmulation()
{
FCEUI_SetEmulationPaused(EMULATIONPAUSED_PAUSED);
}
void PLAYBACK::unpauseEmulation()
{
FCEUI_SetEmulationPaused(0);
}
void PLAYBACK::restoreLastPosition()
{
if (getLastPosition() > currFrameCounter)
{
if (emuPausedState)
startSeekingToFrame(getLastPosition());
else
pauseEmulation();
}
}
void PLAYBACK::handleMiddleButtonClick()
{
if (emuPausedState)
{
// Unpause or start seeking
// works only when right mouse button is released
if (GetAsyncKeyState(GetSystemMetrics(SM_SWAPBUTTON) ? VK_LBUTTON : VK_RBUTTON) >= 0)
{
if (GetAsyncKeyState(VK_SHIFT) < 0)
{
// if Shift is held, seek to nearest Marker
int last_frame = markersManager.getMarkersArraySize() - 1; // the end of movie Markers
int target_frame = currFrameCounter + 1;
for (; target_frame <= last_frame; ++target_frame)
if (markersManager.getMarkerAtFrame(target_frame)) break;
if (target_frame <= last_frame)
startSeekingToFrame(target_frame);
} else if (GetAsyncKeyState(VK_CONTROL) < 0)
{
// if Ctrl is held, seek to Selection cursor or replay from Selection cursor
int selection_beginning = selection.getCurrentRowsSelectionBeginning();
if (selection_beginning > currFrameCounter)
{
startSeekingToFrame(selection_beginning);
} else if (selection_beginning < currFrameCounter)
{
int saved_currFrameCounter = currFrameCounter;
if (selection_beginning < 0)
selection_beginning = 0;
jump(selection_beginning);
startSeekingToFrame(saved_currFrameCounter);
}
} else if (getPauseFrame() < 0 && getLastPosition() >= greenzone.getSize())
{
restoreLastPosition();
} else
{
unpauseEmulation();
}
}
} else
{
pauseEmulation();
}
}
void PLAYBACK::startSeekingToFrame(int frame)
{
if ((pauseFrame - 1) != frame)
{
seekingBeginningFrame = currFrameCounter;
pauseFrame = frame + 1;
}
if (taseditorConfig.turboSeek)
turbo = true;
unpauseEmulation();
}
void PLAYBACK::stopSeeking()
{
pauseFrame = 0;
turbo = false;
pauseEmulation();
setProgressbar(1, 1);
}
void PLAYBACK::handleRewindFrame()
{
if (pauseFrame && !emuPausedState) return;
if (currFrameCounter > 0)
jump(currFrameCounter - 1);
else
// cursor is at frame 0 - can't rewind, but still must make cursor visible if needed
pianoRoll.followPlaybackCursorIfNeeded(true);
if (!pauseFrame)
pauseEmulation();
}
void PLAYBACK::handleForwardFrame()
{
if (pauseFrame && !emuPausedState) return;
jump(currFrameCounter + 1);
if (!pauseFrame)
pauseEmulation();
turbo = false;
}
void PLAYBACK::handleRewindFull(int speed)
{
int index = currFrameCounter - 1;
// jump trough "speed" amount of previous Markers
while (speed > 0)
{
for (; index >= 0; index--)
if (markersManager.getMarkerAtFrame(index)) break;
speed--;
}
if (index >= 0)
jump(index); // jump to the Marker
else
jump(0); // jump to the beginning of Piano Roll
}
void PLAYBACK::handleForwardFull(int speed)
{
int last_frame = markersManager.getMarkersArraySize() - 1; // the end of movie Markers
int index = currFrameCounter + 1;
// jump trough "speed" amount of next Markers
while (speed > 0)
{
for (; index <= last_frame; ++index)
if (markersManager.getMarkerAtFrame(index)) break;
speed--;
}
if (index <= last_frame)
jump(index); // jump to Marker
else
jump(currMovieData.getNumRecords() - 1); // jump to the end of Piano Roll
}
void PLAYBACK::redrawMarkerData()
{
// redraw Marker num
char new_text[MAX_NOTE_LEN] = {0};
if (displayedMarkerNumber <= 9999) // if there's too many digits in the number then don't show the word "Marker" before the number
strcpy(new_text, upperMarkerText);
char num[11];
_itoa(displayedMarkerNumber, num, 10);
strcat(new_text, num);
strcat(new_text, " ");
SetWindowText(hwndPlaybackMarkerNumber, new_text);
// change Marker Note
strcpy(new_text, markersManager.getNoteCopy(displayedMarkerNumber).c_str());
SetWindowText(hwndPlaybackMarkerEditField, new_text);
// reset search_similar_marker, because source Marker changed
markersManager.currentIterationOfFindSimilar = 0;
}
void PLAYBACK::restartPlaybackFromZeroGround()
{
poweron(true);
FCEUMOV_ClearCommands(); // clear POWER SWITCH command caused by poweron()
currFrameCounter = 0;
// if there's no frames in current movie, create initial frame record
if (currMovieData.getNumRecords() == 0)
currMovieData.insertEmpty(-1, 1);
}
void PLAYBACK::ensurePlaybackIsInsideGreenzone(bool executeLua)
{
// set the Playback cursor to the frame or at least above the frame
if (setPlaybackAboveOrToFrame(greenzone.getSize() - 1))
{
// since the game state was changed by this jump, we must update possible Lua callbacks and other tools that would normally only update in FCEUI_Emulate
if (executeLua)
ForceExecuteLuaFrameFunctions();
Update_RAM_Search(); // Update_RAM_Watch() is also called.
}
// follow the Playback cursor, but in case of seeking don't follow it
pianoRoll.followPlaybackCursorIfNeeded(false);
}
// an interface for sending Playback cursor to any frame
void PLAYBACK::jump(int frame, bool forceStateReload, bool executeLua, bool followPauseframe)
{
if (frame < 0) return;
int lastCursor = currFrameCounter;
// 1 - set the Playback cursor to the frame or at least above the frame
if (setPlaybackAboveOrToFrame(frame, forceStateReload))
{
// since the game state was changed by this jump, we must update possible Lua callbacks and other tools that would normally only update in FCEUI_Emulate
if (executeLua)
ForceExecuteLuaFrameFunctions();
Update_RAM_Search(); // Update_RAM_Watch() is also called.
}
// 2 - seek from the current frame if we still aren't at the needed frame
if (frame > currFrameCounter)
{
startSeekingToFrame(frame);
} else
{
// the Playback is already at the needed frame
if (pauseFrame) // if Playback was seeking, pause emulation right here
stopSeeking();
}
// follow the Playback cursor, and optionally follow pauseframe (if seeking was launched)
pianoRoll.followPlaybackCursorIfNeeded(followPauseframe);
// redraw respective Piano Roll lines if needed
if (lastCursor != currFrameCounter)
{
// redraw row where Playback cursor was (in case there's two or more drags before playback.update())
pianoRoll.redrawRow(lastCursor);
bookmarks.redrawChangedBookmarks(lastCursor);
}
}
// returns true if the game state was changed (loaded)
bool PLAYBACK::setPlaybackAboveOrToFrame(int frame, bool forceStateReload)
{
bool state_changed = false;
// search backwards for an earlier frame with valid savestate
int i = greenzone.getSize() - 1;
if (i > frame)
i = frame;
for (; i >= 0; i--)
{
if (!forceStateReload && !state_changed && i == currFrameCounter)
{
// we can remain at current game state
break;
} else if (!greenzone.isSavestateEmpty(i))
{
state_changed = true; // after we once tried loading a savestate, we cannot use currFrameCounter state anymore, because the game state might have been corrupted by this loading attempt
if (greenzone.loadSavestateOfFrame(i))
break;
}
}
if (i < 0)
{
// couldn't find a savestate
restartPlaybackFromZeroGround();
state_changed = true;
}
return state_changed;
}
void PLAYBACK::setLastPosition(int frame)
{
if ((lastPositionFrame - 1 < frame) || (lastPositionFrame - 1 >= frame && !lastPositionIsStable))
{
if (lastPositionFrame)
pianoRoll.redrawRow(lastPositionFrame - 1);
lastPositionFrame = frame + 1;
lastPositionIsStable = true;
}
}
int PLAYBACK::getLastPosition()
{
return lastPositionFrame - 1;
}
int PLAYBACK::getPauseFrame()
{
return pauseFrame - 1;
}
int PLAYBACK::getFlashingPauseFrame()
{
if (showPauseFrame)
return pauseFrame;
else
return 0;
}
void PLAYBACK::setProgressbar(int a, int b)
{
SendMessage(hwndProgressbar, PBM_SETPOS, PROGRESSBAR_WIDTH * a / b, 0);
}
void PLAYBACK::cancelSeeking()
{
if (pauseFrame)
stopSeeking();
}
// -------------------------------------------------------------------------
LRESULT APIENTRY UpperMarkerEditWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
extern PLAYBACK playback;
extern SELECTION selection;
switch(msg)
{
case WM_SETFOCUS:
{
markersManager.markerNoteEditMode = MARKER_NOTE_EDIT_UPPER;
// enable editing
SendMessage(playback.hwndPlaybackMarkerEditField, EM_SETREADONLY, false, 0);
// disable FCEUX keyboard
disableGeneralKeyboardInput();
break;
}
case WM_KILLFOCUS:
{
// if we were editing, save and finish editing
if (markersManager.markerNoteEditMode == MARKER_NOTE_EDIT_UPPER)
{
markersManager.updateEditedMarkerNote();
markersManager.markerNoteEditMode = MARKER_NOTE_EDIT_NONE;
}
// disable editing (make the bg grayed)
SendMessage(playback.hwndPlaybackMarkerEditField, EM_SETREADONLY, true, 0);
// enable FCEUX keyboard
if (taseditorWindow.TASEditorIsInFocus)
enableGeneralKeyboardInput();
break;
}
case WM_CHAR:
case WM_KEYDOWN:
{
if (markersManager.markerNoteEditMode == MARKER_NOTE_EDIT_UPPER)
{
switch(wParam)
{
case VK_ESCAPE:
// revert text to original note text
SetWindowText(playback.hwndPlaybackMarkerEditField, markersManager.getNoteCopy(playback.displayedMarkerNumber).c_str());
SetFocus(pianoRoll.hwndList);
return 0;
case VK_RETURN:
// exit and save text changes
SetFocus(pianoRoll.hwndList);
return 0;
case VK_TAB:
{
// switch to lower edit control (also exit and save text changes)
SetFocus(selection.hwndSelectionMarkerEditField);
// scroll to the Marker
if (taseditorConfig.followMarkerNoteContext)
pianoRoll.followMarker(selection.displayedMarkerNumber);
return 0;
}
}
}
break;
}
case WM_MBUTTONDOWN:
case WM_MBUTTONDBLCLK:
{
playback.handleMiddleButtonClick();
return 0;
}
case WM_LBUTTONDOWN:
case WM_RBUTTONDOWN:
{
// scroll to the Marker
if (taseditorConfig.followMarkerNoteContext)
pianoRoll.followMarker(playback.displayedMarkerNumber);
break;
}
}
return CallWindowProc(playbackMarkerEdit_oldWndproc, hWnd, msg, wParam, lParam);
}

View File

@ -0,0 +1,79 @@
// Specification file for Playback class
#pragma once
#define PROGRESSBAR_WIDTH 200
#define PAUSEFRAME_BLINKING_PERIOD_WHEN_SEEKING 100
#define PAUSEFRAME_BLINKING_PERIOD_WHEN_PAUSED 250
#define BUTTON_HOLD_REPEAT_DELAY 250 // in milliseconds
class PLAYBACK
{
public:
PLAYBACK();
void init();
void reset();
void update();
void ensurePlaybackIsInsideGreenzone(bool executeLua = true);
void jump(int frame, bool forceStateReload = false, bool executeLua = true, bool followPauseframe = true);
void updateProgressbar();
void startSeekingToFrame(int frame);
void stopSeeking();
void cancelSeeking();
void toggleEmulationPause();
void pauseEmulation();
void unpauseEmulation();
void restoreLastPosition();
void handleMiddleButtonClick();
void handleRewindFrame();
void handleForwardFrame();
void handleRewindFull(int speed = 1);
void handleForwardFull(int speed = 1);
void redrawMarkerData();
void restartPlaybackFromZeroGround();
int getLastPosition(); // actually returns lost_position_frame-1
void setLastPosition(int frame);
int getPauseFrame();
int getFlashingPauseFrame();
void setProgressbar(int a, int b);
bool mustFindCurrentMarker;
int displayedMarkerNumber;
//HWND hwndProgressbar, hwndRewind, hwndForward, hwndRewindFull, hwndForwardFull;
//HWND hwndPlaybackMarkerNumber, hwndPlaybackMarkerEditField;
private:
bool setPlaybackAboveOrToFrame(int frame, bool forceStateReload = false);
int pauseFrame;
int lastPositionFrame;
bool lastPositionIsStable; // for when Greenzone invalidates several times, but the end of current segment must remain the same
bool mustAutopauseAtTheEnd;
bool emuPausedState, emuPausedOldState;
int oldPauseFrame;
bool showPauseFrame, oldStateOfShowPauseFrame;
int lastCursorPos; // but for currentCursor we use external variable currFrameCounter
bool rewindButtonState, rewindButtonOldState;
bool forwardButtonState, forwardButtonOldState;
bool rewindFullButtonState, rewindFullButtonOldState;
bool forwardFullButtonState, forwardFullButtonOldState;
int buttonHoldTimer;
int seekingBeginningFrame;
};

View File

@ -0,0 +1,326 @@
/* ---------------------------------------------------------------------------------
Implementation file of RECORDER class
Copyright (c) 2011-2013 AnS
(The MIT License)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
------------------------------------------------------------------------------------
Recorder - Tool for Input recording
[Single instance]
* at the moment of recording movie Input (at the very end of a frame) by emulator's call the Recorder intercepts Input data and applies its filters (multitracking/etc), then reflects Input changes into History and Greenzone
* regularly tracks virtual joypad buttonpresses and provides data for Piano Roll List Header lights. Also reacts on external changes of Recording status, and updates GUI (Recorder panel and Bookmarks/Branches caption)
* implements Input editing in Read-only mode (ColumnSet by pressing buttons on virtual joypad)
* stores resources: ids and names of multitracking modes, suffixes for TAS Editor window caption
------------------------------------------------------------------------------------ */
#include "taseditor_project.h"
extern int joysticksPerFrame[INPUT_TYPES_TOTAL];
extern uint32 GetGamepadPressedImmediate();
extern int getInputType(MovieData& md);
extern char lagFlag;
extern TASEDITOR_CONFIG taseditorConfig;
extern TASEDITOR_WINDOW taseditorWindow;
extern BOOKMARKS bookmarks;
extern HISTORY history;
extern GREENZONE greenzone;
extern PIANO_ROLL pianoRoll;
extern EDITOR editor;
// resources
const char recordingCheckbox[11] = " Recording";
const char recordingCheckboxBlankPattern[17] = " Recording blank";
const char recordingModes[5][4] = { "All",
"1P",
"2P",
"3P",
"4P"};
const char recordingCaptions[5][17] = { " (Recording All)",
" (Recording 1P)",
" (Recording 2P)",
" (Recording 3P)",
" (Recording 4P)"};
RECORDER::RECORDER()
{
}
void RECORDER::init()
{
hwndRecordingCheckbox = GetDlgItem(taseditorWindow.hwndTASEditor, IDC_RECORDING);
hwndRadioButtonRecordAll = GetDlgItem(taseditorWindow.hwndTASEditor, IDC_RADIO_ALL);
hwndRadioButtonRecord1P = GetDlgItem(taseditorWindow.hwndTASEditor, IDC_RADIO_1P);
hwndRadioButtonRecord2P = GetDlgItem(taseditorWindow.hwndTASEditor, IDC_RADIO_2P);
hwndRadioButtonRecord3P = GetDlgItem(taseditorWindow.hwndTASEditor, IDC_RADIO_3P);
hwndRadioButtonRecord4P = GetDlgItem(taseditorWindow.hwndTASEditor, IDC_RADIO_4P);
oldMultitrackRecordingJoypadNumber = multitrackRecordingJoypadNumber;
oldCurrentPattern = oldPatternOffset = 0;
mustIncreasePatternOffset = false;
oldStateOfMovieReadonly = movie_readonly;
oldJoyData.resize(MAX_NUM_JOYPADS);
newJoyData.resize(MAX_NUM_JOYPADS);
currentJoypadData.resize(MAX_NUM_JOYPADS);
}
void RECORDER::reset()
{
movie_readonly = true;
stateWasLoadedInReadWriteMode = false;
multitrackRecordingJoypadNumber = MULTITRACK_RECORDING_ALL;
patternOffset = 0;
mustIncreasePatternOffset = false;
uncheckRecordingRadioButtons();
recheckRecordingRadioButtons();
switch (getInputType(currMovieData))
{
case INPUT_TYPE_FOURSCORE:
{
// enable all 4 radiobuttons
EnableWindow(hwndRadioButtonRecord1P, true);
EnableWindow(hwndRadioButtonRecord2P, true);
EnableWindow(hwndRadioButtonRecord3P, true);
EnableWindow(hwndRadioButtonRecord4P, true);
break;
}
case INPUT_TYPE_2P:
{
// enable radiobuttons 1 and 2
EnableWindow(hwndRadioButtonRecord1P, true);
EnableWindow(hwndRadioButtonRecord2P, true);
// disable radiobuttons 3 and 4
EnableWindow(hwndRadioButtonRecord3P, false);
EnableWindow(hwndRadioButtonRecord4P, false);
break;
}
case INPUT_TYPE_1P:
{
// enable radiobutton 1
EnableWindow(hwndRadioButtonRecord1P, true);
// disable radiobuttons 2, 3 and 4
EnableWindow(hwndRadioButtonRecord2P, false);
EnableWindow(hwndRadioButtonRecord3P, false);
EnableWindow(hwndRadioButtonRecord4P, false);
break;
}
}
}
void RECORDER::update()
{
// update window caption if needed
if (oldStateOfMovieReadonly != movie_readonly || oldMultitrackRecordingJoypadNumber != multitrackRecordingJoypadNumber)
taseditorWindow.updateCaption();
// update Bookmarks/Branches groupbox caption if needed
if (taseditorConfig.oldControlSchemeForBranching && oldStateOfMovieReadonly != movie_readonly)
bookmarks.redrawBookmarksSectionCaption();
// update "Recording" checkbox state
if (oldStateOfMovieReadonly != movie_readonly)
{
Button_SetCheck(hwndRecordingCheckbox, movie_readonly?BST_UNCHECKED : BST_CHECKED);
oldStateOfMovieReadonly = movie_readonly;
if (movie_readonly)
stateWasLoadedInReadWriteMode = false;
}
// reset pattern_offset if current_pattern has changed
if (oldCurrentPattern != taseditorConfig.currentPattern)
patternOffset = 0;
// increase pattern_offset if needed
if (mustIncreasePatternOffset)
{
mustIncreasePatternOffset = false;
if (!taseditorConfig.autofirePatternSkipsLag || lagFlag == 0)
{
patternOffset++;
if (patternOffset >= (int)editor.patterns[oldCurrentPattern].size())
patternOffset -= editor.patterns[oldCurrentPattern].size();
}
}
// update "Recording" checkbox text if something changed in pattern
if (oldCurrentPattern != taseditorConfig.currentPattern || oldPatternOffset != patternOffset)
{
oldCurrentPattern = taseditorConfig.currentPattern;
oldPatternOffset = patternOffset;
if (!taseditorConfig.recordingUsePattern || editor.patterns[oldCurrentPattern][patternOffset])
// either not using Patterns or current pattern has 1 in current offset
SetWindowText(hwndRecordingCheckbox, recordingCheckbox);
else
// current pattern has 0 in current offset, this means next recorded frame will be blank
SetWindowText(hwndRecordingCheckbox, recordingCheckboxBlankPattern);
}
// update recording radio buttons if user changed multitrack_recording_joypad
if (oldMultitrackRecordingJoypadNumber != multitrackRecordingJoypadNumber)
{
uncheckRecordingRadioButtons();
recheckRecordingRadioButtons();
}
int num_joys = joysticksPerFrame[getInputType(currMovieData)];
// save previous state
oldJoyData[0] = currentJoypadData[0];
oldJoyData[1] = currentJoypadData[1];
oldJoyData[2] = currentJoypadData[2];
oldJoyData[3] = currentJoypadData[3];
// fill current_joy data for Piano Roll header lights
uint32 joypads = GetGamepadPressedImmediate();
currentJoypadData[0] = (joypads & 0xFF);
currentJoypadData[1] = ((joypads >> 8) & 0xFF);
currentJoypadData[2] = ((joypads >> 16) & 0xFF);
currentJoypadData[3] = ((joypads >> 24) & 0xFF);
// filter out joysticks that should not be recorded (according to multitrack_recording_joypad)
if (multitrackRecordingJoypadNumber != MULTITRACK_RECORDING_ALL)
{
int joy = multitrackRecordingJoypadNumber - 1;
// substitute target joypad with 1p joypad
if (multitrackRecordingJoypadNumber > MULTITRACK_RECORDING_1P && taseditorConfig.use1PKeysForAllSingleRecordings)
currentJoypadData[joy] = currentJoypadData[0];
// clear all other joypads (pressing them does not count)
for (int i = 0; i < num_joys; ++i)
if (i != joy)
currentJoypadData[i] = 0;
}
// call ColumnSet if needed
if (taseditorConfig.useInputKeysForColumnSet && movie_readonly && taseditorWindow.TASEditorIsInFocus)
{
// if Ctrl or Shift is held, do not call ColumnSet, because maybe this is accelerator
if ((GetAsyncKeyState(VK_CONTROL) >= 0) && (GetAsyncKeyState(VK_SHIFT) >= 0))
{
bool alt_pressed = ((GetAsyncKeyState(VK_MENU) & 0x8000) != 0);
for (int joy = 0; joy < num_joys; ++joy)
{
for (int button = 0; button < NUM_JOYPAD_BUTTONS; ++button)
{
// if the button was pressed right now
if ((currentJoypadData[joy] & (1 << button)) && !(oldJoyData[joy] & (1 << button)))
pianoRoll.handleColumnSet(COLUMN_JOYPAD1_A + joy * NUM_JOYPAD_BUTTONS + button, alt_pressed);
}
}
}
}
}
// ------------------------------------------------------------------------------------
void RECORDER::uncheckRecordingRadioButtons()
{
Button_SetCheck(hwndRadioButtonRecordAll, BST_UNCHECKED);
Button_SetCheck(hwndRadioButtonRecord1P, BST_UNCHECKED);
Button_SetCheck(hwndRadioButtonRecord2P, BST_UNCHECKED);
Button_SetCheck(hwndRadioButtonRecord3P, BST_UNCHECKED);
Button_SetCheck(hwndRadioButtonRecord4P, BST_UNCHECKED);
}
void RECORDER::recheckRecordingRadioButtons()
{
oldMultitrackRecordingJoypadNumber = multitrackRecordingJoypadNumber;
switch(multitrackRecordingJoypadNumber)
{
case MULTITRACK_RECORDING_ALL:
Button_SetCheck(hwndRadioButtonRecordAll, BST_CHECKED);
break;
case MULTITRACK_RECORDING_1P:
Button_SetCheck(hwndRadioButtonRecord1P, BST_CHECKED);
break;
case MULTITRACK_RECORDING_2P:
Button_SetCheck(hwndRadioButtonRecord2P, BST_CHECKED);
break;
case MULTITRACK_RECORDING_3P:
Button_SetCheck(hwndRadioButtonRecord3P, BST_CHECKED);
break;
case MULTITRACK_RECORDING_4P:
Button_SetCheck(hwndRadioButtonRecord4P, BST_CHECKED);
break;
default:
multitrackRecordingJoypadNumber = MULTITRACK_RECORDING_ALL;
Button_SetCheck(hwndRadioButtonRecordAll, BST_CHECKED);
break;
}
}
void RECORDER::recordInput()
{
bool changes_made = false;
uint32 joypad_diff_bits = 0;
int num_joys = joysticksPerFrame[getInputType(currMovieData)];
// take previous values from current snapshot, new Input from current movie
for (int i = 0; i < num_joys; ++i)
{
oldJoyData[i] = history.getCurrentSnapshot().inputlog.getJoystickData(currFrameCounter, i);
if (!taseditorConfig.recordingUsePattern || editor.patterns[oldCurrentPattern][patternOffset])
newJoyData[i] = currMovieData.records[currFrameCounter].joysticks[i];
else
newJoyData[i] = 0; // blank
}
if (taseditorConfig.recordingUsePattern)
// postpone incrementing pattern_offset to the end of the frame (when lagFlag will be known)
mustIncreasePatternOffset = true;
// combine old and new data (superimpose) and filter out joystics that should not be recorded
if (multitrackRecordingJoypadNumber == MULTITRACK_RECORDING_ALL)
{
for (int i = num_joys-1; i >= 0; i--)
{
// superimpose (bitwise OR) if needed
if (taseditorConfig.superimpose == SUPERIMPOSE_CHECKED || (taseditorConfig.superimpose == SUPERIMPOSE_INDETERMINATE && newJoyData[i] == 0))
newJoyData[i] |= oldJoyData[i];
// change this joystick
currMovieData.records[currFrameCounter].joysticks[i] = newJoyData[i];
if (newJoyData[i] != oldJoyData[i])
{
changes_made = true;
joypad_diff_bits |= (1 << (i + 1)); // bit 0 = Commands, bit 1 = Joypad 1, bit 2 = Joypad 2, bit 3 = Joypad 3, bit 4 = Joypad 4
// set lights for changed buttons
for (int button = 0; button < NUM_JOYPAD_BUTTONS; ++button)
if ((newJoyData[i] & (1 << button)) && !(oldJoyData[i] & (1 << button)))
pianoRoll.setLightInHeaderColumn(COLUMN_JOYPAD1_A + i * NUM_JOYPAD_BUTTONS + button, HEADER_LIGHT_MAX);
}
}
} else
{
int joy = multitrackRecordingJoypadNumber - 1;
// substitute target joypad with 1p joypad
if (multitrackRecordingJoypadNumber > MULTITRACK_RECORDING_1P && taseditorConfig.use1PKeysForAllSingleRecordings)
newJoyData[joy] = newJoyData[0];
// superimpose (bitwise OR) if needed
if (taseditorConfig.superimpose == SUPERIMPOSE_CHECKED || (taseditorConfig.superimpose == SUPERIMPOSE_INDETERMINATE && newJoyData[joy] == 0))
newJoyData[joy] |= oldJoyData[joy];
// other joysticks should not be changed
for (int i = num_joys-1; i >= 0; i--)
currMovieData.records[currFrameCounter].joysticks[i] = oldJoyData[i]; // revert to old
// change only this joystick
currMovieData.records[currFrameCounter].joysticks[joy] = newJoyData[joy];
if (newJoyData[joy] != oldJoyData[joy])
{
changes_made = true;
joypad_diff_bits |= (1 << (joy + 1)); // bit 0 = Commands, bit 1 = Joypad 1, bit 2 = Joypad 2, bit 3 = Joypad 3, bit 4 = Joypad 4
// set lights for changed buttons
for (int button = 0; button < NUM_JOYPAD_BUTTONS; ++button)
if ((newJoyData[joy] & (1 << button)) && !(oldJoyData[joy] & (1 << button)))
pianoRoll.setLightInHeaderColumn(COLUMN_JOYPAD1_A + joy * NUM_JOYPAD_BUTTONS + button, HEADER_LIGHT_MAX);
}
}
// check if new commands were recorded
if (currMovieData.records[currFrameCounter].commands != history.getCurrentSnapshot().inputlog.getCommandsData(currFrameCounter))
{
changes_made = true;
joypad_diff_bits |= 1; // bit 0 = Commands, bit 1 = Joypad 1, bit 2 = Joypad 2, bit 3 = Joypad 3, bit 4 = Joypad 4
}
// register changes
if (changes_made)
{
history.registerRecording(currFrameCounter, joypad_diff_bits);
greenzone.invalidate(currFrameCounter);
}
}
// getters
const char* RECORDER::getRecordingMode()
{
return recordingModes[multitrackRecordingJoypadNumber];
}
const char* RECORDER::getRecordingCaption()
{
return recordingCaptions[multitrackRecordingJoypadNumber];
}

View File

@ -0,0 +1,54 @@
// Specification file for RECORDER class
#pragma once
#include <stdint.h>
#include <vector>
enum MULTITRACK_RECORDING_MODES
{
MULTITRACK_RECORDING_ALL = 0,
MULTITRACK_RECORDING_1P = 1,
MULTITRACK_RECORDING_2P = 2,
MULTITRACK_RECORDING_3P = 3,
MULTITRACK_RECORDING_4P = 4,
};
enum SUPERIMPOSE_OPTIONS
{
SUPERIMPOSE_UNCHECKED = 0,
SUPERIMPOSE_CHECKED = 1,
SUPERIMPOSE_INDETERMINATE = 2,
};
class RECORDER
{
public:
RECORDER(void);
void init(void);
void reset(void);
void update(void);
void uncheckRecordingRadioButtons(void);
void recheckRecordingRadioButtons(void);
void recordInput(void);
const char* getRecordingMode(void);
const char* getRecordingCaption(void);
int multitrackRecordingJoypadNumber;
int patternOffset;
std::vector<uint8_t> currentJoypadData;
bool stateWasLoadedInReadWriteMode;
private:
int oldMultitrackRecordingJoypadNumber;
int oldCurrentPattern, oldPatternOffset;
bool oldStateOfMovieReadonly;
bool mustIncreasePatternOffset;
//HWND hwndRecordingCheckbox, hwndRadioButtonRecordAll, hwndRadioButtonRecord1P, hwndRadioButtonRecord2P, hwndRadioButtonRecord3P, hwndRadioButtonRecord4P;
// temps
std::vector<uint8_t> oldJoyData;
std::vector<uint8_t> newJoyData;
};

View File

@ -0,0 +1,774 @@
/* ---------------------------------------------------------------------------------
Implementation file of SELECTION class
Copyright (c) 2011-2013 AnS
(The MIT License)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
------------------------------------------------------------------------------------
Selection - Manager of selections
[Single instance]
* contains definition of the type "Set of selected frames"
* stores array of Sets of selected frames (History of selections)
* saves and loads the data from a project file. On error: clears the array and starts new history by making empty selection
* constantly tracks changes in selected rows of Piano Roll List, and makes a decision to create new point of Selection rollback
* implements all Selection restoring operations: undo, redo
* on demand: changes current selection: remove selection, jump to a frame with Selection cursor, select region, select all, select between Markers, reselect clipboard
* regularly ensures that Selection doesn't go beyond curent Piano Roll limits, detects if Selection moved to another Marker and updates Note in the lower text field
* implements the working of lower buttons << and >> (jumping on Markers)
* also here's the code of lower text field (for editing Marker Notes)
* stores resource: save id, lower text field prefix
------------------------------------------------------------------------------------ */
#include "fceu.h"
#include "Qt/TasEditor/inputlog.h"
#include "Qt/TasEditor/playback.h"
#include "Qt/TasEditor/taseditor_project.h"
#include "Qt/TasEditor/TasEditorWindow.h"
//extern TASEDITOR_CONFIG taseditorConfig;
//extern TASEDITOR_WINDOW taseditorWindow;
//extern MARKERS_MANAGER markersManager;
//extern PIANO_ROLL pianoRoll;
//extern SPLICER splicer;
//extern EDITOR editor;
//extern GREENZONE greenzone;
extern int joysticksPerFrame[INPUT_TYPES_TOTAL];
//LRESULT APIENTRY LowerMarkerEditWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
//WNDPROC selectionMarkerEdit_oldWndproc;
// resources
char selection_save_id[SELECTION_ID_LEN] = "SELECTION";
char selection_skipsave_id[SELECTION_ID_LEN] = "SELECTIOX";
char lowerMarkerText[] = "Marker ";
SELECTION::SELECTION()
{
}
void SELECTION::init()
{
//hwndPreviousMarkerButton = GetDlgItem(taseditorWindow.hwndTASEditor, TASEDITOR_PREV_MARKER);
//hwndNextMarkerButton = GetDlgItem(taseditorWindow.hwndTASEditor, TASEDITOR_NEXT_MARKER);
//hwndSelectionMarkerNumber = GetDlgItem(taseditorWindow.hwndTASEditor, IDC_SELECTION_MARKER);
//SendMessage(hwndSelectionMarkerNumber, WM_SETFONT, (WPARAM)pianoRoll.hMarkersFont, 0);
//hwndSelectionMarkerEditField = GetDlgItem(taseditorWindow.hwndTASEditor, IDC_SELECTION_MARKER_EDIT);
//SendMessage(hwndSelectionMarkerEditField, EM_SETLIMITTEXT, MAX_NOTE_LEN - 1, 0);
//SendMessage(hwndSelectionMarkerEditField, WM_SETFONT, (WPARAM)pianoRoll.hMarkersEditFont, 0);
// subclass the edit control
//selectionMarkerEdit_oldWndproc = (WNDPROC)SetWindowLongPtr(hwndSelectionMarkerEditField, GWLP_WNDPROC, (LONG_PTR)LowerMarkerEditWndProc);
reset();
}
void SELECTION::free()
{
// clear history
rowsSelectionHistory.resize(0);
historyTotalItems = 0;
tempRowsSelection.clear();
}
void SELECTION::reset()
{
free();
// init vars
displayedMarkerNumber = 0;
lastSelectionBeginning = -1;
historySize = taseditorConfig->maxUndoLevels + 1;
rowsSelectionHistory.resize(historySize);
historyStartPos = 0;
historyCursorPos = -1;
// create initial selection
addNewSelectionToHistory();
trackSelectionChanges = true;
reset_vars();
}
void SELECTION::reset_vars()
{
previousMarkerButtonOldState = previousMarkerButtonState = false;
nextMarkerButtonOldState = nextMarkerButtonState = false;
mustFindCurrentMarker = true;
}
void SELECTION::update()
{
updateSelectionSize();
// update << and >> buttons
previousMarkerButtonOldState = previousMarkerButtonState;
//previousMarkerButtonState = ((Button_GetState(hwndPreviousMarkerButton) & BST_PUSHED) != 0);
if (previousMarkerButtonState)
{
if (!previousMarkerButtonOldState)
{
buttonHoldTimer = clock();
jumpToPreviousMarker();
} else if (buttonHoldTimer + BUTTON_HOLD_REPEAT_DELAY < clock())
{
jumpToPreviousMarker();
}
}
nextMarkerButtonOldState = nextMarkerButtonState;
//nextMarkerButtonState = (Button_GetState(hwndNextMarkerButton) & BST_PUSHED) != 0;
if (nextMarkerButtonState)
{
if (!nextMarkerButtonOldState)
{
buttonHoldTimer = clock();
jumpToNextMarker();
} else if (buttonHoldTimer + BUTTON_HOLD_REPEAT_DELAY < clock())
{
jumpToNextMarker();
}
}
// track changes of Selection beginning (Selection cursor)
if (lastSelectionBeginning != getCurrentRowsSelectionBeginning())
{
lastSelectionBeginning = getCurrentRowsSelectionBeginning();
mustFindCurrentMarker = true;
}
// update "Selection's Marker text" if needed
if (mustFindCurrentMarker)
{
markersManager->updateEditedMarkerNote();
displayedMarkerNumber = markersManager->getMarkerAboveFrame(lastSelectionBeginning);
redrawMarkerData();
mustFindCurrentMarker = false;
}
}
void SELECTION::updateSelectionSize()
{
// keep Selection within Piano Roll limits
if (getCurrentRowsSelection().size())
{
int delete_index;
int movie_size = currMovieData.getNumRecords();
while (true)
{
delete_index = *getCurrentRowsSelection().rbegin();
if (delete_index < movie_size) break;
getCurrentRowsSelection().erase(delete_index);
if (!getCurrentRowsSelection().size()) break;
}
}
}
void SELECTION::updateHistoryLogSize()
{
int new_history_size = taseditorConfig->maxUndoLevels + 1;
std::vector<RowsSelection> new_selections_history(new_history_size);
int pos = historyCursorPos, source_pos = historyCursorPos;
if (pos >= new_history_size)
pos = new_history_size - 1;
int new_history_cursor_pos = pos;
// copy old "undo" snapshots
while (pos >= 0)
{
new_selections_history[pos] = rowsSelectionHistory[(historyStartPos + source_pos) % historySize];
pos--;
source_pos--;
}
// copy old "redo" snapshots
int num_redo_snapshots = historyTotalItems - (historyCursorPos + 1);
int space_available = new_history_size - (new_history_cursor_pos + 1);
int i = (num_redo_snapshots <= space_available) ? num_redo_snapshots : space_available;
int new_history_total_items = new_history_cursor_pos + i + 1;
for (; i > 0; i--)
new_selections_history[new_history_cursor_pos + i] = rowsSelectionHistory[(historyStartPos + historyCursorPos + i) % historySize];
// finish
rowsSelectionHistory = new_selections_history;
historySize = new_history_size;
historyStartPos = 0;
historyCursorPos = new_history_cursor_pos;
historyTotalItems = new_history_total_items;
}
void SELECTION::redrawMarkerData()
{
// redraw Marker num
char new_text[MAX_NOTE_LEN] = {0};
if (displayedMarkerNumber <= 9999) // if there's too many digits in the number then don't show the word "Marker" before the number
strcpy(new_text, lowerMarkerText);
char num[11];
//_itoa(displayedMarkerNumber, num, 10);
strcat(new_text, num);
strcat(new_text, " ");
//SetWindowText(hwndSelectionMarkerNumber, new_text);
// change Marker Note
strcpy(new_text, markersManager->getNoteCopy(displayedMarkerNumber).c_str());
//SetWindowText(hwndSelectionMarkerEditField, new_text);
}
void SELECTION::jumpToPreviousMarker(int speed)
{
// if nothing is selected, consider Playback cursor as current selection
int index = getCurrentRowsSelectionBeginning();
if (index < 0) index = currFrameCounter;
// jump trough "speed" amount of previous Markers
while (speed > 0)
{
for (index--; index >= 0; index--)
if (markersManager->getMarkerAtFrame(index)) break;
speed--;
}
if (index >= 0)
jumpToFrame(index); // jump to the Marker
else
jumpToFrame(0); // jump to the beginning of Piano Roll
}
void SELECTION::jumpToNextMarker(int speed)
{
// if nothing is selected, consider Playback cursor as current selection
int index = getCurrentRowsSelectionBeginning();
if (index < 0) index = currFrameCounter;
int last_frame = currMovieData.getNumRecords() - 1; // the end of Piano Roll
// jump trough "speed" amount of previous Markers
while (speed > 0)
{
for (++index; index <= last_frame; ++index)
if (markersManager->getMarkerAtFrame(index)) break;
speed--;
}
if (index <= last_frame)
jumpToFrame(index); // jump to Marker
else
jumpToFrame(last_frame); // jump to the end of Piano Roll
}
void SELECTION::jumpToFrame(int frame)
{
clearAllRowsSelection();
setRowSelection(frame);
//pianoRoll.followSelection();
}
// ----------------------------------------------------------
void SELECTION::save(EMUFILE *os, bool really_save)
{
if (really_save)
{
// write "SELECTION" string
os->fwrite(selection_save_id, SELECTION_ID_LEN);
// write vars
write32le(historyCursorPos, os);
write32le(historyTotalItems, os);
// write selections starting from history_start_pos
for (int i = 0; i < historyTotalItems; ++i)
{
saveSelection(rowsSelectionHistory[(historyStartPos + i) % historySize], os);
}
// write clipboard_selection
saveSelection(splicer->getClipboardSelection(), os);
} else
{
// write "SELECTIOX" string
os->fwrite(selection_skipsave_id, SELECTION_ID_LEN);
}
}
// returns true if couldn't load
bool SELECTION::load(EMUFILE *is, unsigned int offset)
{
int i, total;
if (offset)
{
if (is->fseek(offset, SEEK_SET)) goto error;
} else
{
reset();
return false;
}
// read "SELECTION" string
char save_id[SELECTION_ID_LEN];
if ((int)is->fread(save_id, SELECTION_ID_LEN) < SELECTION_ID_LEN) goto error;
if (!strcmp(selection_skipsave_id, save_id))
{
// string says to skip loading Selection
FCEU_printf("No Selection in the file\n");
reset();
return false;
}
if (strcmp(selection_save_id, save_id)) goto error; // string is not valid
// read vars
if (!read32le(&historyCursorPos, is)) goto error;
if (!read32le(&historyTotalItems, is)) goto error;
if (historyCursorPos > historyTotalItems) goto error;
historyStartPos = 0;
// read selections
total = historyTotalItems;
if (historyTotalItems > historySize)
{
// user can't afford that much undo levels, skip some selections
int num_selections_to_skip = historyTotalItems - historySize;
// first try to skip selections over history_cursor_pos (future selections), because "redo" is less important than "undo"
int num_redo_selections = historyTotalItems-1 - historyCursorPos;
if (num_selections_to_skip >= num_redo_selections)
{
// skip all redo selections
historyTotalItems = historyCursorPos+1;
num_selections_to_skip -= num_redo_selections;
// and still need to skip some undo selections
for (i = 0; i < num_selections_to_skip; ++i)
if (skipLoadSelection(is)) goto error;
total -= num_selections_to_skip;
historyCursorPos -= num_selections_to_skip;
}
historyTotalItems -= num_selections_to_skip;
}
// load selections
for (i = 0; i < historyTotalItems; ++i)
{
if (loadSelection(rowsSelectionHistory[i], is)) goto error;
}
// skip redo selections if needed
for (; i < total; ++i)
if (skipLoadSelection(is)) goto error;
// read clipboard_selection
if (loadSelection(splicer->getClipboardSelection(), is)) goto error;
// all ok
enforceRowsSelectionToList();
reset_vars();
return false;
error:
FCEU_printf("Error loading Selection\n");
reset();
return true;
}
void SELECTION::saveSelection(RowsSelection& selection, EMUFILE *os)
{
write32le(selection.size(), os);
if (selection.size())
{
for(RowsSelection::iterator it(selection.begin()); it != selection.end(); it++)
write32le(*it, os);
}
}
bool SELECTION::loadSelection(RowsSelection& selection, EMUFILE *is)
{
int temp_int, temp_size;
if (!read32le(&temp_size, is)) return true;
selection.clear();
for(; temp_size > 0; temp_size--)
{
if (!read32le(&temp_int, is)) return true;
selection.insert(temp_int);
}
return false;
}
bool SELECTION::skipLoadSelection(EMUFILE *is)
{
int temp_size;
if (!read32le(&temp_size, is)) return true;
if (is->fseek(temp_size * sizeof(int), SEEK_CUR)) return true;
return false;
}
// ----------------------------------------------------------
// used to track selection
//void SELECTION::noteThatItemRangeChanged(NMLVODSTATECHANGE* info)
//{
// bool ON = !(info->uOldState & LVIS_SELECTED) && (info->uNewState & LVIS_SELECTED);
// bool OFF = (info->uOldState & LVIS_SELECTED) && !(info->uNewState & LVIS_SELECTED);
//
// if (ON)
// for(int i = info->iFrom; i <= info->iTo; ++i)
// getCurrentRowsSelection().insert(i);
// else
// for(int i = info->iFrom; i <= info->iTo; ++i)
// getCurrentRowsSelection().erase(i);
//
// splicer->mustRedrawInfoAboutSelection = true;
//}
//void SELECTION::noteThatItemChanged(NMLISTVIEW* info)
//{
// int item = info->iItem;
//
// bool ON = !(info->uOldState & LVIS_SELECTED) && (info->uNewState & LVIS_SELECTED);
// bool OFF = (info->uOldState & LVIS_SELECTED) && !(info->uNewState & LVIS_SELECTED);
//
// //if the item is -1, apply the change to all items
// if (item == -1)
// {
// if (OFF)
// {
// // clear all (actually add new empty Selection to history)
// if (getCurrentRowsSelection().size() && trackSelectionChanges)
// addNewSelectionToHistory();
// } else if (ON)
// {
// // select all
// for(int i = currMovieData.getNumRecords() - 1; i >= 0; i--)
// getCurrentRowsSelection().insert(i);
// }
// } else
// {
// if (ON)
// getCurrentRowsSelection().insert(item);
// else if (OFF)
// getCurrentRowsSelection().erase(item);
// }
//
// splicer->mustRedrawInfoAboutSelection = true;
//}
// ----------------------------------------------------------
void SELECTION::addNewSelectionToHistory()
{
// create new empty selection
RowsSelection selectionFrames;
// increase current position
// history uses ring buffer (vector with fixed size) to avoid resizing
if (historyCursorPos+1 >= historySize)
{
// reached the end of available history_size - move history_start_pos (thus deleting oldest selection)
historyCursorPos = historySize-1;
historyStartPos = (historyStartPos + 1) % historySize;
} else
{
// didn't reach the end of history yet
historyCursorPos++;
if (historyCursorPos >= historyTotalItems)
historyTotalItems = historyCursorPos+1;
}
// add
rowsSelectionHistory[(historyStartPos + historyCursorPos) % historySize] = selectionFrames;
}
void SELECTION::addCurrentSelectionToHistory()
{
// create the copy of current selection
RowsSelection selectionFrames = rowsSelectionHistory[(historyStartPos + historyCursorPos) % historySize];
// increase current position
// history uses ring buffer (vector with fixed size) to avoid resizing
if (historyCursorPos+1 >= historySize)
{
// reached the end of available history_size - move history_start_pos (thus deleting oldest selection)
historyCursorPos = historySize-1;
historyStartPos = (historyStartPos + 1) % historySize;
} else
{
// didn't reach the end of history yet
historyCursorPos++;
if (historyCursorPos >= historyTotalItems)
historyTotalItems = historyCursorPos+1;
}
// add
rowsSelectionHistory[(historyStartPos + historyCursorPos) % historySize] = selectionFrames;
}
void SELECTION::jumpInTime(int new_pos)
{
if (new_pos < 0) new_pos = 0; else if (new_pos >= historyTotalItems) new_pos = historyTotalItems-1;
if (new_pos == historyCursorPos) return;
// make jump
historyCursorPos = new_pos;
// update Piano Roll items
enforceRowsSelectionToList();
// also keep Selection within Piano Roll
updateSelectionSize();
}
void SELECTION::undo()
{
jumpInTime(historyCursorPos - 1);
}
void SELECTION::redo()
{
jumpInTime(historyCursorPos + 1);
}
// ----------------------------------------------------------
bool SELECTION::isRowSelected(int index)
{
/*
if (CurrentSelection().find(frame) == CurrentSelection().end())
return false;
return true;
*/
return false; // ListView_GetItemState(pianoRoll.hwndList, index, LVIS_SELECTED) != 0;
}
void SELECTION::clearAllRowsSelection()
{
//ListView_SetItemState(pianoRoll.hwndList, -1, 0, LVIS_SELECTED);
}
void SELECTION::clearSingleRowSelection(int index)
{
//ListView_SetItemState(pianoRoll.hwndList, index, 0, LVIS_SELECTED);
}
void SELECTION::clearRegionOfRowsSelection(int start, int end)
{
//for (int i = start; i < end; ++i)
// ListView_SetItemState(pianoRoll.hwndList, i, 0, LVIS_SELECTED);
}
void SELECTION::selectAllRows()
{
//ListView_SetItemState(pianoRoll.hwndList, -1, LVIS_SELECTED, LVIS_SELECTED);
}
void SELECTION::setRowSelection(int index)
{
//ListView_SetItemState(pianoRoll.hwndList, index, LVIS_SELECTED, LVIS_SELECTED);
}
void SELECTION::setRegionOfRowsSelection(int start, int end)
{
//for (int i = start; i < end; ++i)
// ListView_SetItemState(pianoRoll.hwndList, i, LVIS_SELECTED, LVIS_SELECTED);
}
void SELECTION::setRegionOfRowsSelectionUsingPattern(int start, int end)
{
//int pattern_offset = 0, current_pattern = taseditorConfig.currentPattern;
//for (int i = start; i <= end; ++i)
//{
// // skip lag frames
// if (taseditorConfig.autofirePatternSkipsLag && greenzone.lagLog.getLagInfoAtFrame(i) == LAGGED_YES)
// continue;
// if (editor.patterns[current_pattern][pattern_offset])
// {
// ListView_SetItemState(pianoRoll.hwndList, i, LVIS_SELECTED, LVIS_SELECTED);
// } else
// {
// ListView_SetItemState(pianoRoll.hwndList, i, 0, LVIS_SELECTED);
// }
// pattern_offset++;
// if (pattern_offset >= (int)editor.patterns[current_pattern].size())
// pattern_offset -= editor.patterns[current_pattern].size();
//}
}
void SELECTION::selectAllRowsBetweenMarkers()
{
int center, upper_border, lower_border;
int upper_marker, lower_marker;
int movie_size = currMovieData.getNumRecords();
// if nothing is selected then Playback cursor serves as Selection cursor
if (getCurrentRowsSelection().size())
{
upper_border = center = *getCurrentRowsSelection().begin();
lower_border = *getCurrentRowsSelection().rbegin();
} else lower_border = upper_border = center = currFrameCounter;
// find Markers
// searching up starting from center-0
for (upper_marker = center; upper_marker >= 0; upper_marker--)
if (markersManager->getMarkerAtFrame(upper_marker)) break;
// searching down starting from center+1
for (lower_marker = center+1; lower_marker < movie_size; ++lower_marker)
if (markersManager->getMarkerAtFrame(lower_marker)) break;
clearAllRowsSelection();
// special case
if (upper_marker == -1 && lower_marker == movie_size)
{
selectAllRows();
return;
}
// selecting circle: 1-2-3-4-1-2-3-4...
//if (upper_border > upper_marker+1 || lower_border < lower_marker-1 || lower_border > lower_marker)
//{
// // 1 - default: select all between Markers, not including lower Marker
// if (upper_marker < 0) upper_marker = 0;
// for (int i = upper_marker; i < lower_marker; ++i)
// {
// ListView_SetItemState(pianoRoll.hwndList, i, LVIS_SELECTED, LVIS_SELECTED);
// }
//} else if (upper_border == upper_marker && lower_border == lower_marker-1)
//{
// // 2 - selected all between Markers and upper Marker selected too: select all between Markers, not including Markers
// for (int i = upper_marker+1; i < lower_marker; ++i)
// {
// ListView_SetItemState(pianoRoll.hwndList, i, LVIS_SELECTED, LVIS_SELECTED);
// }
//} else if (upper_border == upper_marker+1 && lower_border == lower_marker-1)
//{
// // 3 - selected all between Markers, nut including Markers: select all between Markers, not including upper Marker
// if (lower_marker >= movie_size) lower_marker = movie_size - 1;
// for (int i = upper_marker+1; i <= lower_marker; ++i)
// {
// ListView_SetItemState(pianoRoll.hwndList, i, LVIS_SELECTED, LVIS_SELECTED);
// }
//} else if (upper_border == upper_marker+1 && lower_border == lower_marker)
//{
// // 4 - selected all between Markers and lower Marker selected too: select all bertween Markers, including Markers
// if (upper_marker < 0) upper_marker = 0;
// if (lower_marker >= movie_size) lower_marker = movie_size - 1;
// for (int i = upper_marker; i <= lower_marker; ++i)
// {
// ListView_SetItemState(pianoRoll.hwndList, i, LVIS_SELECTED, LVIS_SELECTED);
// }
//} else
//{
// // return to 1
// if (upper_marker < 0) upper_marker = 0;
// for (int i = upper_marker; i < lower_marker; ++i)
// {
// ListView_SetItemState(pianoRoll.hwndList, i, LVIS_SELECTED, LVIS_SELECTED);
// }
//}
}
void SELECTION::reselectClipboard()
{
RowsSelection clipboard_selection = splicer->getClipboardSelection();
if (clipboard_selection.size() == 0) return;
clearAllRowsSelection();
getCurrentRowsSelection() = clipboard_selection;
enforceRowsSelectionToList();
// also keep Selection within Piano Roll
updateSelectionSize();
}
void SELECTION::transposeVertically(int shift)
{
if (!shift) return;
//RowsSelection* current_selection = getCopyOfCurrentRowsSelection();
//if (current_selection->size())
//{
// clearAllRowsSelection();
// int pos;
// if (shift > 0)
// {
// int movie_size = currMovieData.getNumRecords();
// RowsSelection::reverse_iterator current_selection_rend(current_selection->rend());
// for(RowsSelection::reverse_iterator it(current_selection->rbegin()); it != current_selection_rend; it++)
// {
// pos = (*it) + shift;
// if (pos < movie_size)
// ListView_SetItemState(pianoRoll.hwndList, pos, LVIS_SELECTED, LVIS_SELECTED);
// }
// } else
// {
// RowsSelection::iterator current_selection_end(current_selection->end());
// for(RowsSelection::iterator it(current_selection->begin()); it != current_selection_end; it++)
// {
// pos = (*it) + shift;
// if (pos >= 0)
// ListView_SetItemState(pianoRoll.hwndList, pos, LVIS_SELECTED, LVIS_SELECTED);
// }
// }
//}
}
void SELECTION::enforceRowsSelectionToList()
{
trackSelectionChanges = false;
clearAllRowsSelection();
//for(RowsSelection::reverse_iterator it(getCurrentRowsSelection().rbegin()); it != getCurrentRowsSelection().rend(); it++)
//{
// ListView_SetItemState(pianoRoll.hwndList, *it, LVIS_SELECTED, LVIS_SELECTED);
//}
trackSelectionChanges = true;
}
// getters
int SELECTION::getCurrentRowsSelectionSize()
{
return rowsSelectionHistory[(historyStartPos + historyCursorPos) % historySize].size();
}
int SELECTION::getCurrentRowsSelectionBeginning()
{
if (rowsSelectionHistory[(historyStartPos + historyCursorPos) % historySize].size())
return *rowsSelectionHistory[(historyStartPos + historyCursorPos) % historySize].begin();
else
return -1;
}
int SELECTION::getCurrentRowsSelectionEnd()
{
if (rowsSelectionHistory[(historyStartPos + historyCursorPos) % historySize].size())
return *rowsSelectionHistory[(historyStartPos + historyCursorPos) % historySize].rbegin();
else
return -1;
}
RowsSelection* SELECTION::getCopyOfCurrentRowsSelection()
{
// copy current Selection to temp_selection
tempRowsSelection = rowsSelectionHistory[(historyStartPos + historyCursorPos) % historySize];
return &tempRowsSelection;
}
// this getter is private
RowsSelection& SELECTION::getCurrentRowsSelection()
{
return rowsSelectionHistory[(historyStartPos + historyCursorPos) % historySize];
}
// -------------------------------------------------------------------------
//LRESULT APIENTRY LowerMarkerEditWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
//{
// extern PLAYBACK playback;
// extern SELECTION selection;
// switch(msg)
// {
// case WM_SETFOCUS:
// {
// markersManager.markerNoteEditMode = MARKER_NOTE_EDIT_LOWER;
// // enable editing
// SendMessage(selection.hwndSelectionMarkerEditField, EM_SETREADONLY, false, 0);
// // disable FCEUX keyboard
// disableGeneralKeyboardInput();
// break;
// }
// case WM_KILLFOCUS:
// {
// if (markersManager.markerNoteEditMode == MARKER_NOTE_EDIT_LOWER)
// {
// markersManager.updateEditedMarkerNote();
// markersManager.markerNoteEditMode = MARKER_NOTE_EDIT_NONE;
// }
// // disable editing (make the bg grayed)
// SendMessage(selection.hwndSelectionMarkerEditField, EM_SETREADONLY, true, 0);
// // enable FCEUX keyboard
// if (taseditorWindow.TASEditorIsInFocus)
// enableGeneralKeyboardInput();
// break;
// }
// case WM_CHAR:
// case WM_KEYDOWN:
// {
// if (markersManager.markerNoteEditMode == MARKER_NOTE_EDIT_LOWER)
// {
// switch(wParam)
// {
// case VK_ESCAPE:
// // revert text to original note text
// //SetWindowText(selection.hwndSelectionMarkerEditField, markersManager.getNoteCopy(selection.displayedMarkerNumber).c_str());
// //SetFocus(pianoRoll.hwndList);
// return 0;
// case VK_RETURN:
// // exit and save text changes
// //SetFocus(pianoRoll.hwndList);
// return 0;
// case VK_TAB:
// {
// // switch to upper edit control (also exit and save text changes)
// //SetFocus(playback.hwndPlaybackMarkerEditField);
// // scroll to the Marker
// //if (taseditorConfig.followMarkerNoteContext)
// // pianoRoll.followMarker(playback.displayedMarkerNumber);
// return 0;
// }
// }
// }
// break;
// }
// case WM_MBUTTONDOWN:
// case WM_MBUTTONDBLCLK:
// {
// playback.handleMiddleButtonClick();
// return 0;
// }
// case WM_LBUTTONDOWN:
// case WM_RBUTTONDOWN:
// {
// // scroll to the Marker
// if (taseditorConfig.followMarkerNoteContext)
// pianoRoll.followMarker(selection.displayedMarkerNumber);
// break;
// }
// }
// return CallWindowProc(selectionMarkerEdit_oldWndproc, hWnd, msg, wParam, lParam);
//}

View File

@ -0,0 +1,97 @@
// Specification file for SELECTION class
#pragma once
#include <set>
typedef std::set<int> RowsSelection;
#define SELECTION_ID_LEN 10
class SELECTION
{
public:
SELECTION();
void init();
void free();
void reset();
void reset_vars();
void update();
void updateSelectionSize();
void updateHistoryLogSize();
void redrawMarkerData();
void save(EMUFILE *os, bool really_save = true);
bool load(EMUFILE *is, unsigned int offset);
void saveSelection(RowsSelection& selection, EMUFILE *os);
bool loadSelection(RowsSelection& selection, EMUFILE *is);
bool skipLoadSelection(EMUFILE *is);
//void noteThatItemRangeChanged(NMLVODSTATECHANGE* info);
//void noteThatItemChanged(NMLISTVIEW* info);
void addNewSelectionToHistory();
void addCurrentSelectionToHistory();
void undo();
void redo();
bool isRowSelected(int index);
void clearAllRowsSelection();
void clearSingleRowSelection(int index);
void clearRegionOfRowsSelection(int start, int end);
void selectAllRows();
void setRowSelection(int index);
void setRegionOfRowsSelection(int start, int end);
void setRegionOfRowsSelectionUsingPattern(int start, int end);
void selectAllRowsBetweenMarkers();
void reselectClipboard();
void transposeVertically(int shift);
void jumpToPreviousMarker(int speed = 1);
void jumpToNextMarker(int speed = 1);
void jumpToFrame(int frame);
// getters
int getCurrentRowsSelectionSize();
int getCurrentRowsSelectionBeginning();
int getCurrentRowsSelectionEnd();
RowsSelection* getCopyOfCurrentRowsSelection();
bool mustFindCurrentMarker;
int displayedMarkerNumber;
//HWND hwndPreviousMarkerButton, hwndNextMarkerButton;
//HWND hwndSelectionMarkerNumber, hwndSelectionMarkerEditField;
private:
void jumpInTime(int new_pos);
void enforceRowsSelectionToList();
RowsSelection& getCurrentRowsSelection();
bool trackSelectionChanges;
int lastSelectionBeginning;
bool previousMarkerButtonState, previousMarkerButtonOldState;
bool nextMarkerButtonState, nextMarkerButtonOldState;
int buttonHoldTimer;
std::vector<RowsSelection> rowsSelectionHistory;
int historyCursorPos;
int historyStartPos;
int historySize;
int historyTotalItems;
RowsSelection tempRowsSelection;
};

View File

@ -0,0 +1,759 @@
/* ---------------------------------------------------------------------------------
Implementation file of SPLICER class
Copyright (c) 2011-2013 AnS
(The MIT License)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
------------------------------------------------------------------------------------
Splicer - Tool for montage
[Single instance]
* implements operations of mass-changing Input: copy/paste, cloning, clearing region, insertion and deletion of frames, truncating
* stores data about the Selection used in last "Copy to Clipboard" operation
* regularly checks the state of current Selection and displays info on GUI, also displays info about Input in Clipboard
* when launching TAS Editor, it checks Clipboard contents
* stores resources: mnemonics of buttons, texts for selection/clipboard info on GUI
------------------------------------------------------------------------------------ */
#include <sstream>
#include "taseditor_project.h"
#include "../Win32InputBox.h"
extern TASEDITOR_WINDOW taseditorWindow;
extern TASEDITOR_CONFIG taseditorConfig;
extern HISTORY history;
extern MARKERS_MANAGER markersManager;
extern PLAYBACK playback;
extern GREENZONE greenzone;
extern PIANO_ROLL pianoRoll;
extern SELECTION selection;
extern int joysticksPerFrame[INPUT_TYPES_TOTAL];
extern int getInputType(MovieData& md);
// resources
char buttonNames[NUM_JOYPAD_BUTTONS][2] = {"A", "B", "S", "T", "U", "D", "L", "R"};
char selectionText[] = "Selection: ";
char selectionEmptyText[] = "Selection: no";
char numTextRow[] = "1 row, ";
char numTextRows[] = " rows, ";
char numTextColumn[] = "1 column";
char numTextColumns[] = " columns";
char clipboardText[] = "Clipboard: ";
char clipboardEmptyText[] = "Clipboard: empty";
SPLICER::SPLICER()
{
}
void SPLICER::init()
{
hwndSelectionInfo = GetDlgItem(taseditorWindow.hwndTASEditor, IDC_TEXT_SELECTION);
hwndClipboardInfo = GetDlgItem(taseditorWindow.hwndTASEditor, IDC_TEXT_CLIPBOARD);
reset();
if (clipboardSelection.empty())
checkClipboardContents();
redrawInfoAboutClipboard();
}
void SPLICER::reset()
{
mustRedrawInfoAboutSelection = true;
}
void SPLICER::update()
{
// redraw Selection info text of needed
if (mustRedrawInfoAboutSelection)
{
int size = selection.getCurrentRowsSelectionSize();
if (size)
{
char new_text[100];
strcpy(new_text, selectionText);
char num[11];
// rows
if (size > 1)
{
_itoa(size, num, 10);
strcat(new_text, num);
strcat(new_text, numTextRows);
} else
{
strcat(new_text, numTextRow);
}
// columns
int columns = NUM_JOYPAD_BUTTONS * joysticksPerFrame[getInputType(currMovieData)]; // in future the number of columns will depend on selected columns
if (columns > 1)
{
_itoa(columns, num, 10);
strcat(new_text, num);
strcat(new_text, numTextColumns);
} else
{
strcat(new_text, numTextColumn);
}
SetWindowText(hwndSelectionInfo, new_text);
} else
{
SetWindowText(hwndSelectionInfo, selectionEmptyText);
}
mustRedrawInfoAboutSelection = false;
}
}
// ----------------------------------------------------------------------------------------------
void SPLICER::cloneSelectedFrames()
{
RowsSelection* current_selection = selection.getCopyOfCurrentRowsSelection();
int frames = current_selection->size();
if (!frames) return;
selection.clearAllRowsSelection(); // Selection will be moved down, so that same frames are selected
bool markers_changed = false;
currMovieData.records.reserve(currMovieData.getNumRecords() + frames);
// insert frames before each selection, but consecutive Selection lines are accounted as single region
RowsSelection::reverse_iterator next_it;
RowsSelection::reverse_iterator current_selection_rend = current_selection->rend();
int shift = frames;
frames = 1;
for(RowsSelection::reverse_iterator it(current_selection->rbegin()); it != current_selection_rend; it++)
{
next_it = it;
next_it++;
if (next_it == current_selection_rend || (int)*next_it < ((int)*it - 1))
{
// end of current region
currMovieData.cloneRegion(*it, frames);
greenzone.lagLog.insertFrame(*it, false, frames);
if (taseditorConfig.bindMarkersToInput)
{
// Markers are not cloned
if (markersManager.insertEmpty(*it,frames))
markers_changed = true;
}
selection.setRegionOfRowsSelection((*it) + shift, (*it) + shift + frames);
shift -= frames;
// start accumulating next region
frames = 1;
} else frames++;
}
// check and register changes
int first_changes = history.registerChanges(MODTYPE_CLONE, *current_selection->begin(), -1, 0, NULL, 0, current_selection);
if (first_changes >= 0)
{
greenzone.invalidateAndUpdatePlayback(first_changes);
} else if (markers_changed)
{
history.registerMarkersChange(MODTYPE_MARKER_SHIFT, *current_selection->begin());
pianoRoll.redraw();
}
if (markers_changed)
selection.mustFindCurrentMarker = playback.mustFindCurrentMarker = true;
}
void SPLICER::insertSelectedFrames()
{
RowsSelection* current_selection = selection.getCopyOfCurrentRowsSelection();
int frames = current_selection->size();
if (!frames) return;
selection.clearAllRowsSelection(); // Selection will be moved down, so that same frames are selected
bool markers_changed = false;
currMovieData.records.reserve(currMovieData.getNumRecords() + frames);
// insert frames before each selection, but consecutive Selection lines are accounted as single region
RowsSelection::reverse_iterator next_it;
RowsSelection::reverse_iterator current_selection_rend = current_selection->rend();
int shift = frames;
frames = 1;
for(RowsSelection::reverse_iterator it(current_selection->rbegin()); it != current_selection_rend; it++)
{
next_it = it;
next_it++;
if (next_it == current_selection_rend || (int)*next_it < ((int)*it - 1))
{
// end of current region
currMovieData.insertEmpty(*it, frames);
greenzone.lagLog.insertFrame(*it, false, frames);
if (taseditorConfig.bindMarkersToInput)
{
if (markersManager.insertEmpty(*it, frames))
markers_changed = true;
}
selection.setRegionOfRowsSelection((*it) + shift, (*it) + shift + frames);
shift -= frames;
// start accumulating next region
frames = 1;
} else frames++;
}
// check and register changes
int first_changes = history.registerChanges(MODTYPE_INSERT, *current_selection->begin(), -1, 0, NULL, 0, current_selection);
if (first_changes >= 0)
{
greenzone.invalidateAndUpdatePlayback(first_changes);
} else if (markers_changed)
{
history.registerMarkersChange(MODTYPE_MARKER_SHIFT, *current_selection->begin());
pianoRoll.redraw();
}
if (markers_changed)
selection.mustFindCurrentMarker = playback.mustFindCurrentMarker = true;
}
void SPLICER::insertNumberOfFrames()
{
RowsSelection* current_selection = selection.getCopyOfCurrentRowsSelection();
int frames = current_selection->size();
if (CWin32InputBox::GetInteger("Insert number of Frames", "How many frames?", frames, taseditorWindow.hwndTASEditor) == IDOK)
{
if (frames > 0)
{
bool markers_changed = false;
int index;
if (current_selection->size())
{
// insert at selection
index = *current_selection->begin();
} else
{
// insert at Playback cursor
index = currFrameCounter;
}
currMovieData.insertEmpty(index, frames);
greenzone.lagLog.insertFrame(index, false, frames);
if (taseditorConfig.bindMarkersToInput)
{
if (markersManager.insertEmpty(index, frames))
markers_changed = true;
}
if (current_selection->size())
{
// shift Selection down, so that same frames are selected
pianoRoll.updateLinesCount();
selection.clearAllRowsSelection();
RowsSelection::iterator current_selection_end = current_selection->end();
for(RowsSelection::iterator it(current_selection->begin()); it != current_selection_end; it++)
selection.setRowSelection((*it) + frames);
}
// check and register changes
int first_changes = history.registerChanges(MODTYPE_INSERTNUM, index, -1, frames);
if (first_changes >= 0)
{
greenzone.invalidateAndUpdatePlayback(first_changes);
} else if (markers_changed)
{
history.registerMarkersChange(MODTYPE_MARKER_SHIFT, index);
pianoRoll.redraw();
}
if (markers_changed)
selection.mustFindCurrentMarker = playback.mustFindCurrentMarker = true;
}
}
}
void SPLICER::deleteSelectedFrames()
{
RowsSelection* current_selection = selection.getCopyOfCurrentRowsSelection();
if (current_selection->size() == 0) return;
bool markers_changed = false;
int start_index = *current_selection->begin();
int end_index = *current_selection->rbegin();
RowsSelection::reverse_iterator current_selection_rend = current_selection->rend();
// delete frames on each selection, going backwards
for(RowsSelection::reverse_iterator it(current_selection->rbegin()); it != current_selection_rend; it++)
{
currMovieData.eraseRecords(*it);
greenzone.lagLog.eraseFrame(*it);
if (taseditorConfig.bindMarkersToInput)
{
if (markersManager.eraseMarker(*it))
markers_changed = true;
}
}
// check if user deleted all frames
if (!currMovieData.getNumRecords())
playback.restartPlaybackFromZeroGround();
// reduce Piano Roll
pianoRoll.updateLinesCount();
// check and register changes
int result = history.registerChanges(MODTYPE_DELETE, start_index, -1, 0, NULL, 0, current_selection);
if (result >= 0)
{
greenzone.invalidateAndUpdatePlayback(result);
} else
{
// check for special case: user deleted a bunch of empty frames the end of the movie
greenzone.invalidateAndUpdatePlayback(currMovieData.getNumRecords() - 1);
if (markers_changed)
history.registerMarkersChange(MODTYPE_MARKER_SHIFT, start_index);
}
if (markers_changed)
selection.mustFindCurrentMarker = playback.mustFindCurrentMarker = true;
}
void SPLICER::clearSelectedFrames(RowsSelection* currentSelectionOverride)
{
bool cut = true;
if (!currentSelectionOverride)
{
cut = false;
currentSelectionOverride = selection.getCopyOfCurrentRowsSelection();
if (currentSelectionOverride->size() == 0) return;
}
// clear Input on each selected frame
RowsSelection::iterator current_selection_end(currentSelectionOverride->end());
for(RowsSelection::iterator it(currentSelectionOverride->begin()); it != current_selection_end; it++)
{
currMovieData.records[*it].clear();
}
if (cut)
greenzone.invalidateAndUpdatePlayback(history.registerChanges(MODTYPE_CUT, *currentSelectionOverride->begin(), *currentSelectionOverride->rbegin()));
else
greenzone.invalidateAndUpdatePlayback(history.registerChanges(MODTYPE_CLEAR, *currentSelectionOverride->begin(), *currentSelectionOverride->rbegin()));
}
void SPLICER::truncateMovie()
{
int frame = selection.getCurrentRowsSelectionBeginning();
if (frame < 0) frame = currFrameCounter;
if (currMovieData.getNumRecords() > frame+1)
{
int last_frame_was = currMovieData.getNumRecords() - 1;
currMovieData.truncateAt(frame+1);
bool markers_changed = false;
if (taseditorConfig.bindMarkersToInput)
{
if (markersManager.setMarkersArraySize(frame+1))
{
markers_changed = true;
selection.mustFindCurrentMarker = playback.mustFindCurrentMarker = true;
}
}
pianoRoll.updateLinesCount();
int result = history.registerChanges(MODTYPE_TRUNCATE, frame + 1);
if (result >= 0)
{
greenzone.invalidateAndUpdatePlayback(result);
} else
{
// check for special case: user truncated empty frames of the movie
greenzone.invalidateAndUpdatePlayback(currMovieData.getNumRecords() - 1);
if (markers_changed)
history.registerMarkersChange(MODTYPE_MARKER_REMOVE, frame+1, last_frame_was);
}
}
}
bool SPLICER::copySelectedInputToClipboard(RowsSelection* currentSelectionOverride)
{
if (!currentSelectionOverride)
{
currentSelectionOverride = selection.getCopyOfCurrentRowsSelection();
if (currentSelectionOverride->size() == 0) return false;
}
RowsSelection::iterator current_selection_begin(currentSelectionOverride->begin());
RowsSelection::iterator current_selection_end(currentSelectionOverride->end());
int num_joypads = joysticksPerFrame[getInputType(currMovieData)];
int cframe = (*current_selection_begin) - 1;
try
{
int range = (*currentSelectionOverride->rbegin() - *current_selection_begin) + 1;
std::stringstream clipString;
clipString << "TAS " << range << std::endl;
for(RowsSelection::iterator it(current_selection_begin); it != current_selection_end; it++)
{
if (*it > cframe+1)
{
clipString << '+' << (*it-cframe) << '|';
}
cframe=*it;
int cjoy=0;
for (int joy = 0; joy < num_joypads; ++joy)
{
while (currMovieData.records[*it].joysticks[joy] && cjoy<joy)
{
clipString << '|';
++cjoy;
}
for (int bit=0; bit<NUM_JOYPAD_BUTTONS; ++bit)
{
if (currMovieData.records[*it].joysticks[joy] & (1<<bit))
{
clipString << buttonNames[bit];
}
}
}
clipString << std::endl;
}
// write data to clipboard
if (!OpenClipboard(taseditorWindow.hwndTASEditor))
return false;
EmptyClipboard();
HGLOBAL hGlobal = GlobalAlloc(GMEM_MOVEABLE, clipString.str().size()+1);
if (hGlobal==INVALID_HANDLE_VALUE)
{
CloseClipboard();
return false;
}
char *pGlobal = (char*)GlobalLock(hGlobal);
strcpy(pGlobal, clipString.str().c_str());
GlobalUnlock(hGlobal);
SetClipboardData(CF_TEXT, hGlobal);
CloseClipboard();
}
catch (std::bad_alloc e)
{
return false;
}
// copied successfully
// memorize currently strobed Selection data to clipboard_selection
clipboardSelection = *currentSelectionOverride;
redrawInfoAboutClipboard();
return true;
}
void SPLICER::cutSelectedInputToClipboard()
{
RowsSelection* current_selection = selection.getCopyOfCurrentRowsSelection();
if (current_selection->size() == 0) return;
if (copySelectedInputToClipboard(current_selection))
{
clearSelectedFrames(current_selection);
}
}
bool SPLICER::pasteInputFromClipboard()
{
RowsSelection* current_selection = selection.getCopyOfCurrentRowsSelection();
if (current_selection->size() == 0) return false;
if (!OpenClipboard(taseditorWindow.hwndTASEditor)) return false;
int num_joypads = joysticksPerFrame[getInputType(currMovieData)];
bool result = false;
int pos = *(current_selection->begin());
HANDLE hGlobal = GetClipboardData(CF_TEXT);
if (hGlobal)
{
char *pGlobal = (char*)GlobalLock((HGLOBAL)hGlobal);
// TAS recording info starts with "TAS "
if (pGlobal[0]=='T' && pGlobal[1]=='A' && pGlobal[2]=='S')
{
// Extract number of frames
int range;
sscanf (pGlobal+3, "%d", &range);
if (currMovieData.getNumRecords() < pos+range)
{
currMovieData.insertEmpty(currMovieData.getNumRecords(),pos+range-currMovieData.getNumRecords());
markersManager.update();
}
pGlobal = strchr(pGlobal, '\n');
int joy = 0;
uint8 new_buttons = 0;
std::vector<uint8> flash_joy(num_joypads);
char* frame;
pos--;
while (pGlobal++ && *pGlobal!='\0')
{
// Detect skipped frames in paste
frame = pGlobal;
if (frame[0]=='+')
{
pos += atoi(frame+1);
while (*frame && *frame != '\n' && *frame!='|')
++frame;
if (*frame=='|') ++frame;
} else
{
pos++;
}
if (taseditorConfig.superimpose == SUPERIMPOSE_UNCHECKED)
{
currMovieData.records[pos].joysticks[0] = 0;
currMovieData.records[pos].joysticks[1] = 0;
currMovieData.records[pos].joysticks[2] = 0;
currMovieData.records[pos].joysticks[3] = 0;
}
// read this frame Input
joy = 0;
new_buttons = 0;
while (*frame && *frame != '\n' && *frame !='\r')
{
switch (*frame)
{
case '|': // Joystick mark
// flush buttons to movie data
if (taseditorConfig.superimpose == SUPERIMPOSE_CHECKED || (taseditorConfig.superimpose == SUPERIMPOSE_INDETERMINATE && new_buttons == 0))
{
flash_joy[joy] |= (new_buttons & (~currMovieData.records[pos].joysticks[joy])); // highlight buttons that are new
currMovieData.records[pos].joysticks[joy] |= new_buttons;
} else
{
flash_joy[joy] |= new_buttons; // highlight buttons that were added
currMovieData.records[pos].joysticks[joy] = new_buttons;
}
joy++;
new_buttons = 0;
break;
default:
for (int bit = 0; bit < NUM_JOYPAD_BUTTONS; ++bit)
{
if (*frame == buttonNames[bit][0])
{
new_buttons |= (1<<bit);
break;
}
}
break;
}
frame++;
}
// before going to next frame, flush buttons to movie data
if (taseditorConfig.superimpose == SUPERIMPOSE_CHECKED || (taseditorConfig.superimpose == SUPERIMPOSE_INDETERMINATE && new_buttons == 0))
{
flash_joy[joy] |= (new_buttons & (~currMovieData.records[pos].joysticks[joy])); // highlight buttons that are new
currMovieData.records[pos].joysticks[joy] |= new_buttons;
} else
{
flash_joy[joy] |= new_buttons; // highlight buttons that were added
currMovieData.records[pos].joysticks[joy] = new_buttons;
}
// find CRLF
pGlobal = strchr(pGlobal, '\n');
}
greenzone.invalidateAndUpdatePlayback(history.registerChanges(MODTYPE_PASTE, *(current_selection->begin()), pos));
// flash Piano Roll header columns that were changed during paste
for (int joy = 0; joy < num_joypads; ++joy)
{
for (int btn = 0; btn < NUM_JOYPAD_BUTTONS; ++btn)
{
if (flash_joy[joy] & (1 << btn))
pianoRoll.setLightInHeaderColumn(COLUMN_JOYPAD1_A + joy * NUM_JOYPAD_BUTTONS + btn, HEADER_LIGHT_MAX);
}
}
result = true;
} else
{
SetWindowText(hwndClipboardInfo, clipboardEmptyText);
}
GlobalUnlock(hGlobal);
}
CloseClipboard();
return result;
}
bool SPLICER::pasteInsertInputFromClipboard()
{
RowsSelection* current_selection = selection.getCopyOfCurrentRowsSelection();
if (current_selection->size() == 0) return false;
if (!OpenClipboard(taseditorWindow.hwndTASEditor)) return false;
RowsSelection::iterator current_selection_begin(current_selection->begin());
int num_joypads = joysticksPerFrame[getInputType(currMovieData)];
bool result = false;
bool markers_changed = false;
int pos = *current_selection_begin;
HANDLE hGlobal = GetClipboardData(CF_TEXT);
if (hGlobal)
{
char *pGlobal = (char*)GlobalLock((HGLOBAL)hGlobal);
// TAS recording info starts with "TAS "
if (pGlobal[0]=='T' && pGlobal[1]=='A' && pGlobal[2]=='S')
{
// make sure Markers have the same size as movie
markersManager.update();
// create inserted_set (for Input history hot changes)
RowsSelection inserted_set;
// Extract number of frames
int range;
sscanf (pGlobal+3, "%d", &range);
pGlobal = strchr(pGlobal, '\n');
char* frame;
int joy=0;
std::vector<uint8> flash_joy(num_joypads);
pos--;
while (pGlobal++ && *pGlobal!='\0')
{
// Detect skipped frames in paste
frame = pGlobal;
if (frame[0]=='+')
{
pos += atoi(frame+1);
if (currMovieData.getNumRecords() < pos)
{
currMovieData.insertEmpty(currMovieData.getNumRecords(), pos - currMovieData.getNumRecords());
markersManager.update();
}
while (*frame && *frame != '\n' && *frame != '|')
++frame;
if (*frame=='|') ++frame;
} else
{
pos++;
}
// insert new frame
currMovieData.insertEmpty(pos, 1);
greenzone.lagLog.insertFrame(pos, false, 1);
if (taseditorConfig.bindMarkersToInput)
{
if (markersManager.insertEmpty(pos, 1))
markers_changed = true;
}
inserted_set.insert(pos);
// read this frame Input
int joy = 0;
while (*frame && *frame != '\n' && *frame !='\r')
{
switch (*frame)
{
case '|': // Joystick mark
joy++;
break;
default:
for (int bit = 0; bit < NUM_JOYPAD_BUTTONS; ++bit)
{
if (*frame == buttonNames[bit][0])
{
currMovieData.records[pos].joysticks[joy] |= (1<<bit);
flash_joy[joy] |= (1<<bit); // highlight buttons
break;
}
}
break;
}
frame++;
}
pGlobal = strchr(pGlobal, '\n');
}
markersManager.update();
int first_changes = history.registerChanges(MODTYPE_PASTEINSERT, *current_selection_begin, -1, 0, NULL, 0, &inserted_set);
if (first_changes >= 0)
{
greenzone.invalidateAndUpdatePlayback(first_changes);
} else if (markers_changed)
{
history.registerMarkersChange(MODTYPE_MARKER_SHIFT, *current_selection->begin());
pianoRoll.redraw();
}
if (markers_changed)
selection.mustFindCurrentMarker = playback.mustFindCurrentMarker = true;
// flash Piano Roll header columns that were changed during paste
for (int joy = 0; joy < num_joypads; ++joy)
{
for (int btn = 0; btn < NUM_JOYPAD_BUTTONS; ++btn)
{
if (flash_joy[joy] & (1 << btn))
pianoRoll.setLightInHeaderColumn(COLUMN_JOYPAD1_A + joy * NUM_JOYPAD_BUTTONS + btn, HEADER_LIGHT_MAX);
}
}
result = true;
} else
{
SetWindowText(hwndClipboardInfo, clipboardEmptyText);
}
GlobalUnlock(hGlobal);
}
CloseClipboard();
return result;
}
// ----------------------------------------------------------------------------------------------
// retrieves some information from clipboard to clipboard_selection
void SPLICER::checkClipboardContents()
{
if (OpenClipboard(taseditorWindow.hwndTASEditor))
{
// check if clipboard contains TAS Editor Input data
HANDLE hGlobal = GetClipboardData(CF_TEXT);
if (hGlobal)
{
clipboardSelection.clear();
int current_pos = -1;
char *pGlobal = (char*)GlobalLock((HGLOBAL)hGlobal);
// TAS recording info starts with "TAS "
if (pGlobal[0]=='T' && pGlobal[1]=='A' && pGlobal[2]=='S')
{
// Extract number of frames
int range;
sscanf (pGlobal+3, "%d", &range);
pGlobal = strchr(pGlobal, '\n');
while (pGlobal++ && *pGlobal!='\0')
{
// Detect skipped frames in paste
char *frame = pGlobal;
if (frame[0]=='+')
{
current_pos += atoi(frame+1);
while (*frame && *frame != '\n' && *frame != '|')
++frame;
if (*frame=='|') ++frame;
} else
current_pos++;
clipboardSelection.insert(current_pos);
// skip Input
pGlobal = strchr(pGlobal, '\n');
}
}
GlobalUnlock(hGlobal);
}
CloseClipboard();
}
}
void SPLICER::redrawInfoAboutClipboard()
{
if (clipboardSelection.size())
{
char new_text[100];
strcpy(new_text, clipboardText);
char num[11];
// rows
if (clipboardSelection.size() > 1)
{
_itoa(clipboardSelection.size(), num, 10);
strcat(new_text, num);
strcat(new_text, numTextRows);
} else
{
strcat(new_text, numTextRow);
}
// columns
int columns = NUM_JOYPAD_BUTTONS * joysticksPerFrame[getInputType(currMovieData)]; // in future the number of columns will depend on selected columns
if (columns > 1)
{
_itoa(columns, num, 10);
strcat(new_text, num);
strcat(new_text, numTextColumns);
} else
{
strcat(new_text, numTextColumn);
}
SetWindowText(hwndClipboardInfo, new_text);
} else
SetWindowText(hwndClipboardInfo, clipboardEmptyText);
}
RowsSelection& SPLICER::getClipboardSelection()
{
return clipboardSelection;
}

View File

@ -0,0 +1,36 @@
// Specification file for SPLICER class
#pragma once
#include "Qt/TasEditor/selection.h"
class SPLICER
{
public:
SPLICER();
void init();
void reset();
void update();
void cloneSelectedFrames();
void insertSelectedFrames();
void insertNumberOfFrames();
void deleteSelectedFrames();
void clearSelectedFrames(RowsSelection* currentSelectionOverride = 0);
void truncateMovie();
bool copySelectedInputToClipboard(RowsSelection* currentSelectionOverride = 0);
void cutSelectedInputToClipboard();
bool pasteInputFromClipboard();
bool pasteInsertInputFromClipboard();
void redrawInfoAboutClipboard();
RowsSelection& getClipboardSelection();
bool mustRedrawInfoAboutSelection;
private:
void checkClipboardContents();
RowsSelection clipboardSelection;
//HWND hwndSelectionInfo, hwndClipboardInfo;
};

View File

@ -0,0 +1,103 @@
/* ---------------------------------------------------------------------------------
Implementation file of TASEDITOR_CONFIG class
Copyright (c) 2011-2013 AnS
(The MIT License)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
------------------------------------------------------------------------------------
Config - Current settings
[Single instance]
* stores current state of all TAS Editor settings
* all TAS Editor modules can get or set any data within Config
* when launching FCEUX, the emulator writes data from fceux.cfg file to the Config, when exiting it reads the data back to fceux.cfg
* stores resources: default values of all settings, min/max values of settings
------------------------------------------------------------------------------------ */
#include "Qt/TasEditor/recorder.h"
#include "Qt/TasEditor/inputlog.h"
#include "Qt/TasEditor/taseditor_config.h"
TASEDITOR_CONFIG::TASEDITOR_CONFIG(void)
{
// set default values
windowX = 0;
windowY = 0;
windowWidth = 0;
windowHeight = 0;
savedWindowX = 0;
savedWindowY = 0;
savedWindowWidth = 0;
savedWindowHeight = 0;
windowIsMaximized = false;
findnoteWindowX = 0;
findnoteWindowY = 0;
findnoteMatchCase = false;
findnoteSearchUp = false;
followPlaybackCursor = true;
turboSeek = false;
autoRestoreLastPlaybackPosition = false;
superimpose = SUPERIMPOSE_UNCHECKED;
recordingUsePattern = false;
enableLuaAutoFunction = true;
displayBranchesTree = false;
displayBranchScreenshots = true;
displayBranchDescriptions = true;
enableHotChanges = true;
followUndoContext = true;
followMarkerNoteContext = true;
greenzoneCapacity = GREENZONE_CAPACITY_DEFAULT;
maxUndoLevels = UNDO_LEVELS_DEFAULT;
enableGreenzoning = true;
autofirePatternSkipsLag = true;
autoAdjustInputAccordingToLag = true;
drawInputByDragging = true;
combineConsecutiveRecordingsAndDraws = false;
use1PKeysForAllSingleRecordings = true;
useInputKeysForColumnSet = false;
bindMarkersToInput = true;
emptyNewMarkerNotes = true;
oldControlSchemeForBranching = false;
branchesRestoreEntireMovie = true;
HUDInBranchScreenshots = true;
autopauseAtTheEndOfMovie = true;
lastExportedInputType = INPUT_TYPE_1P;
lastExportedSubtitlesStatus = false;
projectSavingOptions_SaveInBinary = true;
projectSavingOptions_SaveMarkers = true;
projectSavingOptions_SaveBookmarks = true;
projectSavingOptions_SaveHistory = true;
projectSavingOptions_SavePianoRoll = true;
projectSavingOptions_SaveSelection = true;
projectSavingOptions_GreenzoneSavingMode = GREENZONE_SAVING_MODE_ALL;
saveCompact_SaveInBinary = true;
saveCompact_SaveMarkers = true;
saveCompact_SaveBookmarks = true;
saveCompact_SaveHistory = false;
saveCompact_SavePianoRoll = true;
saveCompact_SaveSelection = false;
saveCompact_GreenzoneSavingMode = GREENZONE_SAVING_MODE_NO;
autosaveEnabled = true;
autosavePeriod = AUTOSAVE_PERIOD_DEFAULT;
autosaveSilent = true;
tooltipsEnabled = true;
currentPattern = 0;
lastAuthorName[0] = 0; // empty name
}

View File

@ -0,0 +1,112 @@
// Specification file for TASEDITOR_CONFIG class
#pragma once
#define GREENZONE_CAPACITY_MIN 1
#define GREENZONE_CAPACITY_MAX 50000 // this limitation is here just because we're running in 32-bit OS, so there's 2GB limit of RAM
#define GREENZONE_CAPACITY_DEFAULT 10000
#define UNDO_LEVELS_MIN 1
#define UNDO_LEVELS_MAX 1000 // this limitation is here just because we're running in 32-bit OS, so there's 2GB limit of RAM
#define UNDO_LEVELS_DEFAULT 100
#define AUTOSAVE_PERIOD_MIN 0 // 0 = auto-save immediately after every change in the project
#define AUTOSAVE_PERIOD_MAX 1440 // 24 hours
#define AUTOSAVE_PERIOD_DEFAULT 15 // in minutes
enum GREENZONE_SAVING_MODES
{
GREENZONE_SAVING_MODE_ALL,
GREENZONE_SAVING_MODE_16TH,
GREENZONE_SAVING_MODE_MARKED,
GREENZONE_SAVING_MODE_NO,
// ...
GREENZONE_SAVING_MODES_TOTAL
};
#define AUTHOR_NAME_MAX_LEN 100
class TASEDITOR_CONFIG
{
public:
TASEDITOR_CONFIG(void);
// vars saved in fceux.cfg file
int windowX;
int windowY;
int windowWidth;
int windowHeight;
int savedWindowX;
int savedWindowY;
int savedWindowWidth;
int savedWindowHeight;
bool windowIsMaximized;
int findnoteWindowX;
int findnoteWindowY;
bool findnoteMatchCase;
bool findnoteSearchUp;
bool followPlaybackCursor;
bool turboSeek;
bool autoRestoreLastPlaybackPosition;
int superimpose;
bool recordingUsePattern;
bool enableLuaAutoFunction;
bool displayBranchesTree;
bool displayBranchScreenshots;
bool displayBranchDescriptions;
bool enableHotChanges;
bool followUndoContext;
bool followMarkerNoteContext;
int greenzoneCapacity;
int maxUndoLevels;
bool enableGreenzoning;
bool autofirePatternSkipsLag;
bool autoAdjustInputAccordingToLag;
bool drawInputByDragging;
bool combineConsecutiveRecordingsAndDraws;
bool use1PKeysForAllSingleRecordings;
bool useInputKeysForColumnSet;
bool bindMarkersToInput;
bool emptyNewMarkerNotes;
bool oldControlSchemeForBranching;
bool branchesRestoreEntireMovie;
bool HUDInBranchScreenshots;
bool autopauseAtTheEndOfMovie;
int lastExportedInputType;
bool lastExportedSubtitlesStatus;
bool projectSavingOptions_SaveInBinary;
bool projectSavingOptions_SaveMarkers;
bool projectSavingOptions_SaveBookmarks;
bool projectSavingOptions_SaveHistory;
bool projectSavingOptions_SavePianoRoll;
bool projectSavingOptions_SaveSelection;
int projectSavingOptions_GreenzoneSavingMode;
bool saveCompact_SaveInBinary;
bool saveCompact_SaveMarkers;
bool saveCompact_SaveBookmarks;
bool saveCompact_SaveHistory;
bool saveCompact_SavePianoRoll;
bool saveCompact_SaveSelection;
int saveCompact_GreenzoneSavingMode;
bool autosaveEnabled;
int autosavePeriod;
bool autosaveSilent;
bool tooltipsEnabled;
int currentPattern;
char lastAuthorName[AUTHOR_NAME_MAX_LEN];
private:
};

View File

@ -0,0 +1,390 @@
/* ---------------------------------------------------------------------------------
Implementation file of TASEDITOR_PROJECT class
Copyright (c) 2011-2013 AnS
(The MIT License)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
------------------------------------------------------------------------------------
Project - Manager of working project
[Single instance]
* stores the info about current project filename and about having unsaved changes
* implements saving and loading project files from filesystem
* implements autosave function
* stores resources: autosave period scale, default filename, fm3 format offsets
------------------------------------------------------------------------------------ */
#include "taseditor_project.h"
#include "utils/xstring.h"
#include "version.h"
extern TASEDITOR_CONFIG taseditorConfig;
extern TASEDITOR_WINDOW taseditorWindow;
extern MARKERS_MANAGER markersManager;
extern BOOKMARKS bookmarks;
extern POPUP_DISPLAY popupDisplay;
extern GREENZONE greenzone;
extern PLAYBACK playback;
extern RECORDER recorder;
extern HISTORY history;
extern PIANO_ROLL pianoRoll;
extern SELECTION selection;
extern SPLICER splicer;
extern FCEUGI *GameInfo;
extern void FCEU_PrintError(const char *format, ...);
extern bool saveProject(bool save_compact = false);
extern bool saveProjectAs(bool save_compact = false);
extern int getInputType(MovieData& md);
extern void setInputType(MovieData& md, int new_input_type);
TASEDITOR_PROJECT::TASEDITOR_PROJECT()
{
}
void TASEDITOR_PROJECT::init()
{
// default filename for a new project is blank
projectFile = "";
projectName = "";
fm2FileName = "";
reset();
}
void TASEDITOR_PROJECT::reset()
{
changed = false;
}
void TASEDITOR_PROJECT::update()
{
// if it's time to autosave - pop Save As dialog
if (changed && taseditorWindow.TASEditorIsInFocus && taseditorConfig.autosaveEnabled && !projectFile.empty() && clock() >= nextSaveShedule && pianoRoll.dragMode == DRAG_MODE_NONE)
{
if (taseditorConfig.autosaveSilent)
saveProject();
else
saveProjectAs();
// in case user pressed Cancel, postpone saving to next time
sheduleNextAutosave();
}
}
bool TASEDITOR_PROJECT::save(const char* differentName, bool inputInBinary, bool saveMarkers, bool saveBookmarks, int saveGreenzone, bool saveHistory, bool savePianoRoll, bool saveSelection)
{
if (!differentName && getProjectFile().empty())
// no different name specified, and there's no current filename of the project
return false;
// check MD5
char md5OfMovie[256];
char md5OfRom[256];
strcpy(md5OfMovie, md5_asciistr(currMovieData.romChecksum));
strcpy(md5OfRom, md5_asciistr(GameInfo->MD5));
if (strcmp(md5OfMovie, md5OfRom))
{
// checksums mismatch, check if they both aren't zero
unsigned int k, count1 = 0, count2 = 0;
for(k = 0; k < strlen(md5OfMovie); k++) count1 += md5OfMovie[k] - '0';
for(k = 0; k < strlen(md5OfRom); k++) count2 += md5OfRom[k] - '0';
if (count1 && count2)
{
// ask user if he wants to fix the checksum before saving
char message[2048] = {0};
strcpy(message, "Movie ROM:\n");
strncat(message, currMovieData.romFilename.c_str(), 2047 - strlen(message));
strncat(message, "\nMD5: ", 2047 - strlen(message));
strncat(message, md5OfMovie, 2047 - strlen(message));
strncat(message, "\n\nCurrent ROM:\n", 2047 - strlen(message));
strncat(message, GameInfo->filename, 2047 - strlen(message));
strncat(message, "\nMD5: ", 2047 - strlen(message));
strncat(message, md5OfRom, 2047 - strlen(message));
strncat(message, "\n\nFix the movie header before saving? ", 2047 - strlen(message));
int answer = MessageBox(taseditorWindow.hwndTASEditor, message, "ROM Checksum Mismatch", MB_YESNOCANCEL);
if (answer == IDCANCEL)
{
// cancel saving
return false;
} else if (answer == IDYES)
{
// change ROM data in the movie to current ROM
currMovieData.romFilename = GameInfo->filename;
currMovieData.romChecksum = GameInfo->MD5;
}
}
}
// open file for write
EMUFILE_FILE* ofs = 0;
if (differentName)
ofs = FCEUD_UTF8_fstream(differentName, "wb");
else
ofs = FCEUD_UTF8_fstream(getProjectFile().c_str(), "wb");
if (ofs)
{
// change cursor to hourglass
SetCursor(LoadCursor(0, IDC_WAIT));
// save fm2 data to the project file
currMovieData.loadFrameCount = currMovieData.records.size();
currMovieData.emuVersion = FCEU_VERSION_NUMERIC;
currMovieData.dump(ofs, inputInBinary);
unsigned int taseditorDataOffset = ofs->ftell();
// save header: fm3 version + saved_stuff
write32le(PROJECT_FILE_CURRENT_VERSION, ofs);
unsigned int savedStuffMap = 0;
if (saveMarkers) savedStuffMap |= MARKERS_SAVED;
if (saveBookmarks) savedStuffMap |= BOOKMARKS_SAVED;
if (saveGreenzone != GREENZONE_SAVING_MODE_NO) savedStuffMap |= GREENZONE_SAVED;
if (saveHistory) savedStuffMap |= HISTORY_SAVED;
if (savePianoRoll) savedStuffMap |= PIANO_ROLL_SAVED;
if (saveSelection) savedStuffMap |= SELECTION_SAVED;
write32le(savedStuffMap, ofs);
unsigned int numberOfPointers = DEFAULT_NUMBER_OF_POINTERS;
write32le(numberOfPointers, ofs);
// write dummy zeros to the file, where the offsets will be
for (unsigned int i = 0; i < numberOfPointers; ++i)
write32le(0, ofs);
// save specified modules
unsigned int markersOffset = ofs->ftell();
markersManager.save(ofs, saveMarkers);
unsigned int bookmarksOffset = ofs->ftell();
bookmarks.save(ofs, saveBookmarks);
unsigned int greenzoneOffset = ofs->ftell();
greenzone.save(ofs, saveGreenzone);
unsigned int historyOffset = ofs->ftell();
history.save(ofs, saveHistory);
unsigned int pianoRollOffset = ofs->ftell();
pianoRoll.save(ofs, savePianoRoll);
unsigned int selectionOffset = ofs->ftell();
selection.save(ofs, saveSelection);
// now write offsets (pointers)
ofs->fseek(taseditorDataOffset + PROJECT_FILE_OFFSET_OF_POINTERS_DATA, SEEK_SET);
write32le(markersOffset, ofs);
write32le(bookmarksOffset, ofs);
write32le(greenzoneOffset, ofs);
write32le(historyOffset, ofs);
write32le(pianoRollOffset, ofs);
write32le(selectionOffset, ofs);
// finish
delete ofs;
playback.updateProgressbar();
// also set project.changed to false, unless it was SaveCompact
if (!differentName)
reset();
// restore cursor
taseditorWindow.mustUpdateMouseCursor = true;
return true;
} else
{
return false;
}
}
bool TASEDITOR_PROJECT::load(const char* fullName)
{
bool loadAll = true;
unsigned int taseditorDataOffset = 0;
EMUFILE_FILE ifs(fullName, "rb");
if (ifs.fail())
{
FCEU_PrintError("Error opening %s!", fullName);
return false;
}
// change cursor to hourglass
SetCursor(LoadCursor(0, IDC_WAIT));
// load fm2 data from the project file
MovieData tempMovieData = MovieData();
extern bool LoadFM2(MovieData& movieData, EMUFILE* fp, int size, bool stopAfterHeader);
if (LoadFM2(tempMovieData, &ifs, ifs.size(), false))
{
// check MD5
char md5OfOriginal[256];
char md5OfCurrent[256];
strcpy(md5OfOriginal, md5_asciistr(tempMovieData.romChecksum));
strcpy(md5OfCurrent, md5_asciistr(GameInfo->MD5));
if (strcmp(md5OfOriginal, md5OfCurrent))
{
// checksums mismatch, check if they both aren't zero
unsigned int k, count1 = 0, count2 = 0;
for(k = 0; k < strlen(md5OfOriginal); k++) count1 += md5OfOriginal[k] - '0';
for(k = 0; k < strlen(md5OfCurrent); k++) count2 += md5OfCurrent[k] - '0';
if (count1 && count2)
{
// ask user if he really wants to load the project
char message[2048] = {0};
strcpy(message, "This project was made using different ROM!\n\n");
strcat(message, "Original ROM:\n");
strncat(message, tempMovieData.romFilename.c_str(), 2047 - strlen(message));
strncat(message, "\nMD5: ", 2047 - strlen(message));
strncat(message, md5OfOriginal, 2047 - strlen(message));
strncat(message, "\n\nCurrent ROM:\n", 2047 - strlen(message));
strncat(message, GameInfo->filename, 2047 - strlen(message));
strncat(message, "\nMD5: ", 2047 - strlen(message));
strncat(message, md5OfCurrent, 2047 - strlen(message));
strncat(message, "\n\nLoad the project anyway?", 2047 - strlen(message));
int answer = MessageBox(taseditorWindow.hwndTASEditor, message, "ROM Checksum Mismatch", MB_YESNO);
if (answer == IDNO)
return false;
}
}
taseditorDataOffset = ifs.ftell();
// load fm3 version from header and check it
unsigned int projectFileVersion;
if (read32le(&projectFileVersion, &ifs))
{
if (projectFileVersion != PROJECT_FILE_CURRENT_VERSION)
{
char message[2048] = {0};
strcpy(message, "This project was saved using different version of TAS Editor!\n\n");
strcat(message, "Original version: ");
char versionNum[11];
_itoa(projectFileVersion, versionNum, 10);
strncat(message, versionNum, 2047 - strlen(message));
strncat(message, "\nCurrent version: ", 2047 - strlen(message));
_itoa(PROJECT_FILE_CURRENT_VERSION, versionNum, 10);
strncat(message, versionNum, 2047 - strlen(message));
strncat(message, "\n\nClick Yes to try loading all data from the file (may crash).\n", 2047 - strlen(message));
strncat(message, "Click No to only load movie data.\n", 2047 - strlen(message));
strncat(message, "Click Cancel to abort loading.", 2047 - strlen(message));
int answer = MessageBox(taseditorWindow.hwndTASEditor, message, "FM3 Version Mismatch", MB_YESNOCANCEL);
if (answer == IDCANCEL)
return false;
else if (answer == IDNO)
loadAll = false;
}
} else
{
// couldn't even load header, this seems like an FM2
loadAll = false;
char message[2048];
strcpy(message, "This file doesn't seem to be an FM3 project.\nIt only contains FM2 movie data. Load it anyway?");
int answer = MessageBox(taseditorWindow.hwndTASEditor, message, "Opening FM2 file", MB_YESNO);
if (answer == IDNO)
return false;
}
// save data to currMovieData and continue loading
FCEU_printf("\nLoading TAS Editor project %s...\n", fullName);
currMovieData = tempMovieData;
LoadSubtitles(currMovieData);
// ensure that movie has correct set of ports/fourscore
setInputType(currMovieData, getInputType(currMovieData));
} else
{
FCEU_PrintError("Error loading movie data from %s!", fullName);
// do not alter the project
return false;
}
unsigned int savedStuff = 0;
unsigned int numberOfPointers = 0;
unsigned int dataOffset = 0;
unsigned int pointerOffset = taseditorDataOffset + PROJECT_FILE_OFFSET_OF_POINTERS_DATA;
if (loadAll)
{
read32le(&savedStuff, &ifs);
read32le(&numberOfPointers, &ifs);
// load modules
if (numberOfPointers-- && !(ifs.fseek(pointerOffset, SEEK_SET)) && read32le(&dataOffset, &ifs))
pointerOffset += sizeof(unsigned int);
else
dataOffset = 0;
markersManager.load(&ifs, dataOffset);
if (numberOfPointers-- && !(ifs.fseek(pointerOffset, SEEK_SET)) && read32le(&dataOffset, &ifs))
pointerOffset += sizeof(unsigned int);
else
dataOffset = 0;
bookmarks.load(&ifs, dataOffset);
if (numberOfPointers-- && !(ifs.fseek(pointerOffset, SEEK_SET)) && read32le(&dataOffset, &ifs))
pointerOffset += sizeof(unsigned int);
else
dataOffset = 0;
greenzone.load(&ifs, dataOffset);
if (numberOfPointers-- && !(ifs.fseek(pointerOffset, SEEK_SET)) && read32le(&dataOffset, &ifs))
pointerOffset += sizeof(unsigned int);
else
dataOffset = 0;
history.load(&ifs, dataOffset);
if (numberOfPointers-- && !(ifs.fseek(pointerOffset, SEEK_SET)) && read32le(&dataOffset, &ifs))
pointerOffset += sizeof(unsigned int);
else
dataOffset = 0;
pianoRoll.load(&ifs, dataOffset);
if (numberOfPointers-- && !(ifs.fseek(pointerOffset, SEEK_SET)) && read32le(&dataOffset, &ifs))
pointerOffset += sizeof(unsigned int);
else
dataOffset = 0;
selection.load(&ifs, dataOffset);
} else
{
// reset modules
markersManager.load(&ifs, 0);
bookmarks.load(&ifs, 0);
greenzone.load(&ifs, 0);
history.load(&ifs, 0);
pianoRoll.load(&ifs, 0);
selection.load(&ifs, 0);
}
// reset other modules
playback.reset();
recorder.reset();
splicer.reset();
popupDisplay.reset();
reset();
renameProject(fullName, loadAll);
// restore mouse cursor shape
taseditorWindow.mustUpdateMouseCursor = true;
return true;
}
void TASEDITOR_PROJECT::renameProject(const char* newFullName, bool filenameIsCorrect)
{
projectFile = newFullName;
char drv[512], dir[512], name[512], ext[512]; // For getting the filename
splitpath(newFullName, drv, dir, name, ext);
projectName = name;
std::string thisfm2name = name;
thisfm2name.append(".fm2");
fm2FileName = thisfm2name;
// if filename is not correct (for example, user opened a corrupted FM3) clear the filename, so on Ctrl+S user will be forwarded to SaveAs
if (!filenameIsCorrect)
projectFile.clear();
}
// -----------------------------------------------------------------
std::string TASEDITOR_PROJECT::getProjectFile()
{
return projectFile;
}
std::string TASEDITOR_PROJECT::getProjectName()
{
return projectName;
}
std::string TASEDITOR_PROJECT::getFM2Name()
{
return fm2FileName;
}
void TASEDITOR_PROJECT::setProjectChanged()
{
if (!changed)
{
changed = true;
taseditorWindow.updateCaption();
sheduleNextAutosave();
}
}
bool TASEDITOR_PROJECT::getProjectChanged()
{
return changed;
}
void TASEDITOR_PROJECT::sheduleNextAutosave()
{
nextSaveShedule = clock() + taseditorConfig.autosavePeriod * AUTOSAVE_PERIOD_SCALE;
}

View File

@ -0,0 +1,75 @@
// Specification file for the TASEDITOR_PROJECT class
#include <time.h>
#include "movie.h"
#include "Qt/TasEditor/taseditor_config.h"
//#include "Qt/TasEditor/greenzone.h"
#include "Qt/TasEditor/selection.h"
//#include "Qt/TasEditor/markers_manager.h"
//#include "Qt/TasEditor/snapshot.h"
//#include "Qt/TasEditor/bookmarks.h"
//#include "Qt/TasEditor/branches.h"
//#include "Qt/TasEditor/history.h"
#include "Qt/TasEditor/playback.h"
//#include "Qt/TasEditor/recorder.h"
//#include "Qt/TasEditor/piano_roll.h"
//#include "Qt/TasEditor/taseditor_lua.h"
#include "Qt/TasEditor/splicer.h"
//#include "Qt/TasEditor/editor.h"
//#include "Qt/TasEditor/popup_display.h"
//not available unless we #define _WIN32_WINNT >= 0x501 (XP) and we're trying very hard to keep 2000 support.
#ifndef LVS_EX_DOUBLEBUFFER
#define LVS_EX_DOUBLEBUFFER 0x00010000
#endif
#define AUTOSAVE_PERIOD_SCALE 60000 // = 1 minute in milliseconds
#define MARKERS_SAVED 1
#define BOOKMARKS_SAVED 2
#define GREENZONE_SAVED 4
#define HISTORY_SAVED 8
#define PIANO_ROLL_SAVED 16
#define SELECTION_SAVED 32
#define PROJECT_FILE_CURRENT_VERSION 3
#define PROJECT_FILE_OFFSET_OF_VERSION_NUMBER 0
#define PROJECT_FILE_OFFSET_OF_SAVED_MODULES_MAP (PROJECT_FILE_OFFSET_OF_VERSION_NUMBER + 4)
#define PROJECT_FILE_OFFSET_OF_NUMBER_OF_POINTERS (PROJECT_FILE_OFFSET_OF_SAVED_MODULES_MAP + 4)
#define DEFAULT_NUMBER_OF_POINTERS 6
#define PROJECT_FILE_OFFSET_OF_POINTERS_DATA (PROJECT_FILE_OFFSET_OF_NUMBER_OF_POINTERS + 4)
#define NUM_JOYPAD_BUTTONS 8
class TASEDITOR_PROJECT
{
public:
TASEDITOR_PROJECT();
void init();
void reset();
void update();
bool save(const char* differentName = 0, bool inputInBinary = true, bool saveMarkers = true, bool saveBookmarks = true, int saveGreenzone = GREENZONE_SAVING_MODE_ALL, bool saveHistory = true, bool savePianoRoll = true, bool saveSelection = true);
bool load(const char* fullName);
void renameProject(const char* newFullName, bool filenameIsCorrect);
std::string getProjectFile();
std::string getProjectName();
std::string getFM2Name();
void setProjectChanged();
bool getProjectChanged();
void sheduleNextAutosave();
private:
bool changed;
int nextSaveShedule;
std::string projectFile; // full path
std::string projectName; // file name only
std::string fm2FileName; // same as projectName but with .fm2 extension instead of .fm3
};