Porting over win32 TAS modules. In work.
This commit is contained in:
parent
a61a92f1e0
commit
019c30b229
|
@ -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
|
||||
)
|
||||
|
||||
|
|
|
@ -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");
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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
|
||||
};
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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
|
||||
};
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
@ -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;
|
||||
|
||||
};
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
};
|
|
@ -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];
|
||||
}
|
||||
|
|
@ -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;
|
||||
};
|
|
@ -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);
|
||||
//}
|
||||
|
|
@ -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;
|
||||
|
||||
};
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
@ -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;
|
||||
|
||||
};
|
|
@ -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
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
@ -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:
|
||||
|
||||
};
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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
|
||||
|
||||
};
|
Loading…
Reference in New Issue