Movie editor copy&paste

This commit is contained in:
Ilari Liusvaara 2013-07-07 19:53:08 +03:00
parent bcecac1e66
commit 9bcb9c7760

View file

@ -14,10 +14,12 @@
#include <algorithm> #include <algorithm>
#include <cstring> #include <cstring>
#include <limits>
#include <wx/wx.h> #include <wx/wx.h>
#include <wx/event.h> #include <wx/event.h>
#include <wx/control.h> #include <wx/control.h>
#include <wx/combobox.h> #include <wx/combobox.h>
#include <wx/clipbrd.h>
enum enum
{ {
@ -34,7 +36,13 @@ enum
wxID_APPEND_FRAMES, wxID_APPEND_FRAMES,
wxID_TRUNCATE, wxID_TRUNCATE,
wxID_SCROLL_FRAME, wxID_SCROLL_FRAME,
wxID_SCROLL_CURRENT_FRAME wxID_SCROLL_CURRENT_FRAME,
wxID_COPY_FRAMES,
wxID_CUT_FRAMES,
wxID_PASTE_FRAMES,
wxID_PASTE_APPEND,
wxID_INSERT_CONTROLLER_AFTER,
wxID_DELETE_CONTROLLER_SUBFRAMES,
}; };
void update_movie_state(); void update_movie_state();
@ -53,14 +61,16 @@ struct control_info
unsigned reserved; //Must be at least 6 for axes. unsigned reserved; //Must be at least 6 for axes.
unsigned index; //Index in poll vector. unsigned index; //Index in poll vector.
int type; //-2 => Port, -1 => Fixed, 0 => Button, 1 => axis. int type; //-2 => Port, -1 => Fixed, 0 => Button, 1 => axis.
char ch; char32_t ch;
std::u32string title; std::u32string title;
unsigned port; unsigned port;
unsigned controller; unsigned controller;
static control_info portinfo(unsigned& p, unsigned port, unsigned controller); static control_info portinfo(unsigned& p, unsigned port, unsigned controller);
static control_info fixedinfo(unsigned& p, const std::u32string& str); static control_info fixedinfo(unsigned& p, const std::u32string& str);
static control_info buttoninfo(unsigned& p, char character, const std::u32string& title, unsigned idx); static control_info buttoninfo(unsigned& p, char32_t character, const std::u32string& title, unsigned idx,
static control_info axisinfo(unsigned& p, const std::u32string& title, unsigned idx); unsigned port, unsigned controller);
static control_info axisinfo(unsigned& p, const std::u32string& title, unsigned idx,
unsigned port, unsigned controller);
}; };
control_info control_info::portinfo(unsigned& p, unsigned port, unsigned controller) control_info control_info::portinfo(unsigned& p, unsigned port, unsigned controller)
@ -93,7 +103,8 @@ control_info control_info::fixedinfo(unsigned& p, const std::u32string& str)
return i; return i;
} }
control_info control_info::buttoninfo(unsigned& p, char character, const std::u32string& title, unsigned idx) control_info control_info::buttoninfo(unsigned& p, char32_t character, const std::u32string& title, unsigned idx,
unsigned port, unsigned controller)
{ {
control_info i; control_info i;
i.position_left = p; i.position_left = p;
@ -103,12 +114,13 @@ control_info control_info::buttoninfo(unsigned& p, char character, const std::u3
i.type = 0; i.type = 0;
i.ch = character; i.ch = character;
i.title = title; i.title = title;
i.port = 0; i.port = port;
i.controller = 0; i.controller = controller;
return i; return i;
} }
control_info control_info::axisinfo(unsigned& p, const std::u32string& title, unsigned idx) control_info control_info::axisinfo(unsigned& p, const std::u32string& title, unsigned idx,
unsigned port, unsigned controller)
{ {
control_info i; control_info i;
i.position_left = p; i.position_left = p;
@ -120,8 +132,8 @@ control_info control_info::axisinfo(unsigned& p, const std::u32string& title, un
i.type = 1; i.type = 1;
i.ch = 0; i.ch = 0;
i.title = title; i.title = title;
i.port = 0; i.port = port;
i.controller = 0; i.controller = controller;
return i; return i;
} }
@ -158,12 +170,12 @@ void frame_controls::set_types(controller_frame& f)
unsigned nextc = 0; unsigned nextc = 0;
controlinfo.clear(); controlinfo.clear();
controlinfo.push_back(control_info::portinfo(nextp, 0, 0)); controlinfo.push_back(control_info::portinfo(nextp, 0, 0));
controlinfo.push_back(control_info::buttoninfo(nextc, 'F', U"Framesync", 0)); controlinfo.push_back(control_info::buttoninfo(nextc, U'F', U"Framesync", 0, 0, 0));
controlinfo.push_back(control_info::buttoninfo(nextc, 'R', U"Reset", 1)); controlinfo.push_back(control_info::buttoninfo(nextc, U'R', U"Reset", 1, 0, 0));
nextc++; nextc++;
controlinfo.push_back(control_info::axisinfo(nextc, U" rhigh", 2)); controlinfo.push_back(control_info::axisinfo(nextc, U" rhigh", 2, 0, 0));
nextc++; nextc++;
controlinfo.push_back(control_info::axisinfo(nextc, U" rlow", 3)); controlinfo.push_back(control_info::axisinfo(nextc, U" rlow", 3, 0, 0));
if(nextp > nextc) if(nextp > nextc)
nextc = nextp; nextc = nextp;
nextp = nextc; nextp = nextc;
@ -184,10 +196,10 @@ void frame_controls::add_port(unsigned& c, unsigned pid, porttype_info& p)
unsigned b = 0; unsigned b = 0;
if(p.is_analog(i)) { if(p.is_analog(i)) {
controlinfo.push_back(control_info::axisinfo(c, U" xaxis", 4 + ccount * pid + i * controlinfo.push_back(control_info::axisinfo(c, U" xaxis", 4 + ccount * pid + i *
MAX_CONTROLS_PER_CONTROLLER - ccount)); MAX_CONTROLS_PER_CONTROLLER - ccount, pid, i));
c++; c++;
controlinfo.push_back(control_info::axisinfo(c, U" yaxis", 5 + ccount * pid + i * controlinfo.push_back(control_info::axisinfo(c, U" yaxis", 5 + ccount * pid + i *
MAX_CONTROLS_PER_CONTROLLER - ccount)); MAX_CONTROLS_PER_CONTROLLER - ccount, pid, i));
if(p.button_symbols[0]) if(p.button_symbols[0])
c++; c++;
b = 2; b = 2;
@ -201,7 +213,7 @@ void frame_controls::add_port(unsigned& c, unsigned pid, porttype_info& p)
lbid = 0; lbid = 0;
std::u32string name = to_u32string(get_logical_button_name(lbid)); std::u32string name = to_u32string(get_logical_button_name(lbid));
controlinfo.push_back(control_info::buttoninfo(c, p.button_symbols[j], name, 4 + b + ccount * controlinfo.push_back(control_info::buttoninfo(c, p.button_symbols[j], name, 4 + b + ccount *
pid + i * MAX_CONTROLS_PER_CONTROLLER - ccount)); pid + i * MAX_CONTROLS_PER_CONTROLLER - ccount, pid, i));
} }
if(nextp > c) if(nextp > c)
c = nextp; c = nextp;
@ -283,6 +295,218 @@ void frame_controls::format_lines()
_line2 = cp2; _line2 = cp2;
} }
namespace
{
//TODO: Use real clipboard.
std::string clipboard;
void copy_to_clipboard(const std::string& text)
{
clipboard = text;
}
bool clipboard_has_text()
{
return (clipboard.length() > 0);
}
void clear_clipboard()
{
clipboard = "";
}
std::string copy_from_clipboard()
{
return clipboard;
}
std::string encode_line(controller_frame& f)
{
char buffer[512];
f.serialize(buffer);
return buffer;
}
std::string encode_line(frame_controls& info, controller_frame& f, unsigned port, unsigned controller)
{
std::ostringstream x;
bool last_axis = false;
bool first = true;
for(auto i : info.get_controlinfo()) {
if(i.port != port)
continue;
if(i.controller != controller)
continue;
switch(i.type) {
case 0: //Button.
if(last_axis)
x << " ";
if(info.read_index(f, i.index)) {
char32_t tmp1[2];
tmp1[0] = i.ch;
tmp1[1] = 0;
x << to_u8string(std::u32string(tmp1));
} else
x << "-";
last_axis = false;
first = false;
break;
case 1: //Axis.
if(!first)
x << " ";
x << info.read_index(f, i.index);
first = false;
last_axis = true;
break;
}
}
return x.str();
}
short read_short(const std::u32string& s, size_t& r)
{
unsigned short _res = 0;
bool negative = false;
if(r < s.length() && s[r] == '-') {
negative = true;
r++;
}
while(r < s.length() && s[r] >= 48 && s[r] <= 57) {
_res = _res * 10 + (s[r] - 48);
r++;
}
return negative ? -_res : _res;
}
void decode_line(frame_controls& info, controller_frame& f, std::string line, unsigned port,
unsigned controller)
{
std::u32string _line = to_u32string(line);
bool last_axis = false;
bool first = true;
short y;
char32_t y2;
size_t ridx = 0;
for(auto i : info.get_controlinfo()) {
if(i.port != port)
continue;
if(i.controller != controller)
continue;
switch(i.type) {
case 0: //Button.
if(last_axis) {
ridx++;
while(ridx < _line.length() && (_line[ridx] == 9 || _line[ridx] == 10 ||
_line[ridx] == 13 || _line[ridx] == 32))
ridx++;
}
y2 = (ridx < _line.length()) ? _line[ridx++] : 0;
if(y2 == U'-' || y2 == 0)
info.write_index(f, i.index, 0);
else
info.write_index(f, i.index, 1);
last_axis = false;
first = false;
break;
case 1: //Axis.
if(!first)
ridx++;
while(ridx < _line.length() && (_line[ridx] == 9 || _line[ridx] == 10 ||
_line[ridx] == 13 || _line[ridx] == 32))
ridx++;
y = read_short(_line, ridx);
info.write_index(f, i.index, y);
first = false;
last_axis = true;
break;
}
}
}
std::string encode_lines(controller_frame_vector& fv, uint64_t start, uint64_t end)
{
std::ostringstream x;
x << "lsnes-moviedata-whole" << std::endl;
for(uint64_t i = start; i < end; i++) {
controller_frame tmp = fv[i];
x << encode_line(tmp) << std::endl;
}
return x.str();
}
std::string encode_lines(frame_controls& info, controller_frame_vector& fv, uint64_t start, uint64_t end,
unsigned port, unsigned controller)
{
std::ostringstream x;
x << "lsnes-moviedata-controller" << std::endl;
for(uint64_t i = start; i < end; i++) {
controller_frame tmp = fv[i];
x << encode_line(info, tmp, port, controller) << std::endl;
}
return x.str();
}
int clipboard_get_data_type()
{
if(!clipboard_has_text())
return -1;
std::string y = copy_from_clipboard();
std::istringstream x(y);
std::string hdr;
std::getline(x, hdr);
if(hdr == "lsnes-moviedata-whole")
return 1;
if(hdr == "lsnes-moviedata-controller")
return 0;
return -1;
}
std::set<unsigned> controller_index_set(frame_controls& info, unsigned port, unsigned controller)
{
std::set<unsigned> r;
for(auto i : info.get_controlinfo()) {
if(i.port == port && i.controller == controller && (i.type == 0 || i.type == 1))
r.insert(i.index);
}
return r;
}
void move_index_set(frame_controls& info, controller_frame_vector& fv, uint64_t src, uint64_t dst,
uint64_t len, const std::set<unsigned>& indices)
{
if(src == dst)
return;
if(src > dst) {
//Copy forwards.
uint64_t shift = src - dst;
for(uint64_t i = dst; i < dst + len; i++) {
controller_frame _src = fv[i + shift];
controller_frame _dst = fv[i];
for(auto j : indices)
info.write_index(_dst, j, info.read_index(_src, j));
}
} else {
//Copy backwards.
uint64_t shift = dst - src;
for(uint64_t i = src + len - 1; i >= src && i < src + len; i--) {
controller_frame _src = fv[i];
controller_frame _dst = fv[i + shift];
for(auto j : indices)
info.write_index(_dst, j, info.read_index(_src, j));
}
}
}
void zero_index_set(frame_controls& info, controller_frame_vector& fv, uint64_t dst, uint64_t len,
const std::set<unsigned>& indices)
{
for(uint64_t i = dst; i < dst + len; i++) {
controller_frame _dst = fv[i];
for(auto j : indices)
info.write_index(_dst, j, 0);
}
}
}
class wxeditor_movie : public wxDialog class wxeditor_movie : public wxDialog
{ {
@ -320,11 +544,19 @@ private:
void do_append_frames(uint64_t count); void do_append_frames(uint64_t count);
void do_append_frames(); void do_append_frames();
void do_insert_frame_after(uint64_t row); void do_insert_frame_after(uint64_t row);
void do_delete_frame(uint64_t row, bool wholeframe); void do_delete_frame(uint64_t row1, uint64_t row2, bool wholeframe);
void do_truncate(uint64_t row); void do_truncate(uint64_t row);
void do_set_stop_at_frame(); void do_set_stop_at_frame();
void do_scroll_to_frame(); void do_scroll_to_frame();
void do_scroll_to_current_frame(); void do_scroll_to_current_frame();
void do_copy(uint64_t row1, uint64_t row2, unsigned port, unsigned controller);
void do_copy(uint64_t row1, uint64_t row2);
void do_cut(uint64_t row1, uint64_t row2, unsigned port, unsigned controller);
void do_cut(uint64_t row1, uint64_t row2);
void do_paste(uint64_t row, unsigned port, unsigned controller, bool append);
void do_paste(uint64_t row, bool append);
void do_insert_controller(uint64_t row, unsigned port, unsigned controller);
void do_delete_controller(uint64_t row1, uint64_t row2, unsigned port, unsigned controller);
uint64_t first_editable(unsigned index); uint64_t first_editable(unsigned index);
uint64_t first_nextframe(); uint64_t first_nextframe();
int width(controller_frame& f); int width(controller_frame& f);
@ -381,6 +613,14 @@ namespace
return cffs + pc; return cffs + pc;
} }
uint64_t real_first_editable(frame_controls& fc, std::set<unsigned> idx)
{
uint64_t m = 0;
for(auto i : idx)
m = max(m, real_first_editable(fc, i));
return m;
}
//Find the first real editable whole frame. //Find the first real editable whole frame.
//Call only in emulator thread. //Call only in emulator thread.
uint64_t real_first_nextframe(frame_controls& fc) uint64_t real_first_nextframe(frame_controls& fc)
@ -778,55 +1018,75 @@ void wxeditor_movie::_moviepanel::do_insert_frame_after(uint64_t row)
signal_repaint(); signal_repaint();
} }
void wxeditor_movie::_moviepanel::do_delete_frame(uint64_t row, bool wholeframe) void wxeditor_movie::_moviepanel::do_delete_frame(uint64_t row1, uint64_t row2, bool wholeframe)
{ {
recursing = true; recursing = true;
uint64_t _row = row; uint64_t _row1 = row1;
uint64_t _row2 = row2;
bool _wholeframe = wholeframe; bool _wholeframe = wholeframe;
frame_controls* _fcontrols = &fcontrols; frame_controls* _fcontrols = &fcontrols;
runemufn([_row, _wholeframe, _fcontrols]() { if(_row1 > _row2) std::swap(_row1, _row2);
runemufn([_row1, _row2, _wholeframe, _fcontrols]() {
controller_frame_vector& fv = movb.get_movie().get_frame_vector(); controller_frame_vector& fv = movb.get_movie().get_frame_vector();
uint64_t vsize = fv.size(); uint64_t vsize = fv.size();
if(_row >= vsize) if(_row1 >= vsize)
return; return; //Nothing to do.
uint64_t row2 = min(_row2, vsize - 1);
uint64_t row1 = min(_row1, vsize - 1);
row1 = max(row1, real_first_editable(*_fcontrols, 0));
if(_wholeframe) { if(_wholeframe) {
if(_row < real_first_nextframe(*_fcontrols)) if(_row2 < real_first_nextframe(*_fcontrols))
return; return; //Nothing to do.
//Scan backwards for the first subframe of this frame and forwards for the last. //Scan backwards for the first subframe of this frame and forwards for the last.
uint64_t fsf = _row; uint64_t fsf = row1;
uint64_t lsf = _row; uint64_t lsf = row2;
if(fv[_row].sync()) if(fv[_row2].sync())
lsf++; //Bump by one so it finds the end. lsf++; //Bump by one so it finds the end.
while(fsf < vsize && !fv[fsf].sync()) while(fsf < vsize && !fv[fsf].sync())
fsf--; fsf--;
while(lsf < vsize && !fv[lsf].sync()) while(lsf < vsize && !fv[lsf].sync())
lsf++; lsf++;
fsf = max(fsf, real_first_editable(*_fcontrols, 0));
uint64_t tonuke = lsf - fsf; uint64_t tonuke = lsf - fsf;
int64_t frames_tonuke = 0;
//Count frames nuked.
for(uint64_t i = fsf; i < lsf; i++)
if(fv[i].sync())
frames_tonuke++;
//Nuke from fsf to lsf. //Nuke from fsf to lsf.
for(uint64_t i = fsf; i < vsize - tonuke; i++) for(uint64_t i = fsf; i < vsize - tonuke; i++)
fv[i] = fv[i + tonuke]; fv[i] = fv[i + tonuke];
fv.resize(vsize - tonuke); fv.resize(vsize - tonuke);
movie_framecount_change(-1); movie_framecount_change(-frames_tonuke);
} else { } else {
if(_row < real_first_editable(*_fcontrols, 0)) if(row2 < real_first_editable(*_fcontrols, 0))
return; return; //Nothing to do.
//Is the nuked frame a first subframe? //The sync flag needs to be inherited if:
bool is_first = fv[_row].sync(); //1) Some deleted subframe has sync flag AND
//Nuke the subframe. //2) The subframe immediately after deleted region doesn't.
for(uint64_t i = _row; i < vsize - 1; i++) bool inherit_sync = false;
fv[i] = fv[i + 1]; for(uint64_t i = row1; i <= row2; i++)
fv.resize(vsize - 1); inherit_sync = inherit_sync || fv[i].sync();
inherit_sync = inherit_sync && (row2 + 1 < vsize && !fv[_row2 + 1].sync());
int64_t frames_tonuke = 0;
//Count frames nuked.
for(uint64_t i = row1; i <= row2; i++)
if(fv[i].sync())
frames_tonuke++;
//If sync is inherited, one less frame is nuked.
if(inherit_sync) frames_tonuke--;
//Nuke the subframes.
uint64_t tonuke = row2 - row1 + 1;
for(uint64_t i = row1; i < vsize - tonuke; i++)
fv[i] = fv[i + tonuke];
fv.resize(vsize - tonuke);
//Next subframe inherits the sync flag. //Next subframe inherits the sync flag.
if(is_first) { if(inherit_sync)
if(_row < vsize - 1 && !fv[_row].sync()) fv[row1].sync(true);
fv[_row].sync(true); movie_framecount_change(-frames_tonuke);
else
movie_framecount_change(-1);
}
} }
}); });
max_subframe = row; max_subframe = _row1;
recursing = false; recursing = false;
signal_repaint(); signal_repaint();
} }
@ -954,12 +1214,21 @@ void wxeditor_movie::_moviepanel::on_popup_menu(wxCommandEvent& e)
{ {
wxMenuItem* tmpitem; wxMenuItem* tmpitem;
int id = e.GetId(); int id = e.GetId();
unsigned port = 0;
unsigned controller = 0;
for(auto i : fcontrols.get_controlinfo())
if(i.index == press_index) {
port = i.port;
controller = i.controller;
}
switch(id) { switch(id) {
case wxID_TOGGLE: case wxID_TOGGLE:
do_toggle_buttons(press_index, press_line, press_line); do_toggle_buttons(press_index, rpress_line, press_line);
return; return;
case wxID_CHANGE: case wxID_CHANGE:
do_alter_axis(press_index, press_line, press_line); do_alter_axis(press_index, rpress_line, press_line);
return; return;
case wxID_SWEEP: case wxID_SWEEP:
do_sweep_axis(press_index, rpress_line, press_line); do_sweep_axis(press_index, rpress_line, press_line);
@ -974,10 +1243,10 @@ void wxeditor_movie::_moviepanel::on_popup_menu(wxCommandEvent& e)
do_insert_frame_after(press_line); do_insert_frame_after(press_line);
return; return;
case wxID_DELETE_FRAME: case wxID_DELETE_FRAME:
do_delete_frame(press_line, true); do_delete_frame(press_line, rpress_line, true);
return; return;
case wxID_DELETE_SUBFRAME: case wxID_DELETE_SUBFRAME:
do_delete_frame(press_line, false); do_delete_frame(press_line, rpress_line, false);
return; return;
case wxID_TRUNCATE: case wxID_TRUNCATE:
do_truncate(press_line); do_truncate(press_line);
@ -1014,6 +1283,42 @@ void wxeditor_movie::_moviepanel::on_popup_menu(wxCommandEvent& e)
} }
signal_repaint(); signal_repaint();
return; return;
case wxID_COPY_FRAMES:
if(press_index == std::numeric_limits<unsigned>::max())
do_copy(rpress_line, press_line);
else
do_copy(rpress_line, press_line, port, controller);
return;
case wxID_CUT_FRAMES:
if(press_index == std::numeric_limits<unsigned>::max())
do_cut(rpress_line, press_line);
else
do_cut(rpress_line, press_line, port, controller);
return;
case wxID_PASTE_FRAMES:
if(press_index == std::numeric_limits<unsigned>::max() || clipboard_get_data_type() == 1)
do_paste(press_line, false);
else
do_paste(press_line, port, controller, false);
return;
case wxID_PASTE_APPEND:
if(press_index == std::numeric_limits<unsigned>::max() || clipboard_get_data_type() == 1)
do_paste(press_line, true);
else
do_paste(press_line, port, controller, true);
return;
case wxID_INSERT_CONTROLLER_AFTER:
if(press_index == std::numeric_limits<unsigned>::max())
;
else
do_insert_controller(press_line, port, controller);
return;
case wxID_DELETE_CONTROLLER_SUBFRAMES:
if(press_index == std::numeric_limits<unsigned>::max())
;
else
do_delete_controller(press_line, rpress_line, port, controller);
return;
}; };
} }
@ -1046,64 +1351,145 @@ void wxeditor_movie::_moviepanel::on_mouse1(unsigned x, unsigned y, bool polarit
void wxeditor_movie::_moviepanel::on_mouse2(unsigned x, unsigned y, bool polarity) void wxeditor_movie::_moviepanel::on_mouse2(unsigned x, unsigned y, bool polarity)
{ {
if(polarity) { if(polarity) {
//Pressing mouse, just record line it was pressed on.
rpress_line = spos + y - 3; rpress_line = spos + y - 3;
return; return;
} }
//Releasing mouse, open popup menu.
unsigned off = divcnt + 1;
press_x = x;
if(y < 3)
return;
press_line = spos + y - 3;
wxMenu menu; wxMenu menu;
current_popup = &menu; current_popup = &menu;
menu.Connect(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(wxeditor_movie::_moviepanel::on_popup_menu),
NULL, this);
//Find what controller is the click on.
bool clicked_button = false;
control_info clicked;
std::string controller_name;
if(press_x < off) {
clicked_button = false;
press_index = std::numeric_limits<unsigned>::max();
} else {
for(auto i : fcontrols.get_controlinfo())
if(press_x >= i.position_left + off && press_x < i.position_left + i.reserved + off) {
if(i.type == 0 || i.type == 1) {
clicked_button = true;
clicked = i;
controller_name = (stringfmt() << "controller " << i.port << "-"
<< (i.controller + 1)).str();
press_index = i.index;
}
}
}
//Find first editable frame, controllerframe and buttonframe.
bool not_editable = !movb.get_movie().readonly_mode();
uint64_t eframe_low = first_editable(0);
uint64_t ebutton_low = clicked_button ? first_editable(clicked.index) : std::numeric_limits<uint64_t>::max();
uint64_t econtroller_low = ebutton_low;
for(auto i : fcontrols.get_controlinfo())
if(i.port == clicked.port && i.controller == clicked.controller && (i.type == 0 || i.type == 1))
econtroller_low = max(econtroller_low, first_editable(i.index));
bool click_zero = (clicked_button && !clicked.port && !clicked.controller);
bool enable_append_frame = !not_editable;
bool enable_toggle_button = false; bool enable_toggle_button = false;
bool enable_change_axis = false; bool enable_change_axis = false;
bool enable_sweep_axis = false;
bool enable_insert_frame = false; bool enable_insert_frame = false;
bool enable_insert_controller = false;
bool enable_delete_frame = false; bool enable_delete_frame = false;
bool enable_delete_subframe = false; bool enable_delete_subframe = false;
std::u32string title; bool enable_delete_controller_subframe = false;
if(y < 3) bool enable_truncate_movie = false;
goto outrange; bool enable_cut_frame = false;
if(!movb.get_movie().readonly_mode()) bool enable_copy_frame = false;
goto outrange; bool enable_paste_frame = false;
press_x = x; bool enable_paste_append = false;
press_line = spos + y - 3; std::string copy_title;
for(auto i : fcontrols.get_controlinfo()) { std::string paste_title;
unsigned off = divcnt + 1;
if(press_x >= i.position_left + off && press_x < i.position_left + i.reserved + off) { //Toggle button is enabled if clicked on button and either end is in valid range.
if(i.type == 0 && press_line >= first_editable(i.index) && enable_toggle_button = (!not_editable && clicked_button && clicked.type == 0 && ((press_line >= ebutton_low &&
press_line < linecount) { press_line < linecount) || (rpress_line >= ebutton_low && rpress_line < linecount)));
enable_toggle_button = true; //Change axis is enabled in similar conditions, except if type is axis.
press_index = i.index; enable_change_axis = (!not_editable && clicked_button && clicked.type == 1 && ((press_line >= ebutton_low &&
title = i.title; press_line < linecount) || (rpress_line >= ebutton_low && rpress_line < linecount)));
} //Sweep axis is enabled if change axis is enabled and lines don't match.
if(i.type == 1 && press_line >= first_editable(i.index) && enable_sweep_axis = (enable_change_axis && press_line != rpress_line);
press_line < linecount) { //Insert frame is enabled if this frame is completely editable and press and release lines match.
enable_change_axis = true; enable_insert_frame = (!not_editable && press_line + 1 >= eframe_low && press_line < linecount &&
press_index = i.index; press_line == rpress_line);
title = i.title; //Insert controller frame is enabled if controller is completely editable and lines match.
} enable_insert_controller = (!not_editable && clicked_button && press_line >= econtroller_low &&
} press_line < linecount && press_line == rpress_line);
enable_insert_controller = enable_insert_controller && (clicked.port || clicked.controller);
//Delete frame is enabled if range is completely editable (relative to next-frame).
enable_delete_frame = (!not_editable && press_line >= first_nextframe() && press_line < linecount &&
rpress_line >= first_nextframe() && rpress_line < linecount);
//Delete subframe is enabled if range is completely editable.
enable_delete_subframe = (!not_editable && press_line >= eframe_low && press_line < linecount &&
rpress_line >= eframe_low && rpress_line < linecount);
//Delete controller subframe is enabled if range is completely controller-editable.
enable_delete_controller_subframe = (!not_editable && clicked_button && press_line >= econtroller_low &&
press_line < linecount && rpress_line >= econtroller_low && rpress_line < linecount);
enable_delete_controller_subframe = enable_delete_controller_subframe && (clicked.port || clicked.controller);
//Truncate movie is enabled if lines match and is completely editable.
enable_truncate_movie = (!not_editable && press_line == rpress_line && press_line >= eframe_low &&
press_line < linecount);
//Cut frames is enabled if range is editable (possibly controller-editable).
if(clicked_button)
enable_cut_frame = (!not_editable && press_line >= econtroller_low && press_line < linecount
&& rpress_line >= econtroller_low && rpress_line < linecount && !click_zero);
else
enable_cut_frame = (!not_editable && press_line >= eframe_low & press_line < linecount
&& rpress_line >= eframe_low && rpress_line < linecount);
if(clicked_button && clipboard_get_data_type() == 0) {
enable_paste_append = (!not_editable && linecount >= eframe_low);
enable_paste_frame = (!not_editable && press_line >= econtroller_low && press_line < linecount
&& rpress_line >= econtroller_low && rpress_line < linecount && !click_zero);
} else if(clipboard_get_data_type() == 1) {
enable_paste_append = (!not_editable && linecount >= econtroller_low);
enable_paste_frame = (!not_editable && press_line >= eframe_low & press_line < linecount
&& rpress_line >= eframe_low && rpress_line < linecount);
} }
if(press_line + 1 >= first_editable(0) && press_line < linecount) //Copy frames is enabled if range exists.
enable_insert_frame = true; enable_copy_frame = (press_line < linecount && rpress_line < linecount);
if(press_line >= first_editable(0) && press_line < linecount) copy_title = (clicked_button ? controller_name : "frames");
enable_delete_subframe = true; paste_title = ((clipboard_get_data_type() == 0) ? copy_title : "frames");
if(press_line >= first_nextframe() && press_line < linecount)
enable_delete_frame = true; if(clipboard_get_data_type() == 0 && click_zero) enable_paste_append = enable_paste_frame = false;
if(enable_toggle_button) if(enable_toggle_button)
menu.Append(wxID_TOGGLE, towxstring(U"Toggle " + title)); menu.Append(wxID_TOGGLE, towxstring(U"Toggle " + clicked.title));
if(enable_change_axis) if(enable_change_axis)
menu.Append(wxID_CHANGE, towxstring(U"Change " + title)); menu.Append(wxID_CHANGE, towxstring(U"Change " + clicked.title));
if(enable_change_axis && rpress_line != press_line) if(enable_sweep_axis)
menu.Append(wxID_SWEEP, towxstring(U"Sweep " + title)); menu.Append(wxID_SWEEP, towxstring(U"Sweep " + clicked.title));
if(enable_toggle_button || enable_change_axis) if(enable_toggle_button || enable_change_axis || enable_sweep_axis)
menu.AppendSeparator(); menu.AppendSeparator();
menu.Append(wxID_INSERT_AFTER, wxT("Insert frame after"))->Enable(enable_insert_frame); menu.Append(wxID_INSERT_AFTER, wxT("Insert frame after"))->Enable(enable_insert_frame);
menu.Append(wxID_APPEND_FRAME, wxT("Append frame")); menu.Append(wxID_INSERT_CONTROLLER_AFTER, wxT("Insert controller frame"))
menu.Append(wxID_APPEND_FRAMES, wxT("Append frames...")); ->Enable(enable_insert_controller);
menu.Append(wxID_APPEND_FRAME, wxT("Append frame"))->Enable(enable_append_frame);
menu.Append(wxID_APPEND_FRAMES, wxT("Append frames..."))->Enable(enable_append_frame);
menu.AppendSeparator(); menu.AppendSeparator();
menu.Append(wxID_DELETE_FRAME, wxT("Delete frame"))->Enable(enable_delete_frame); menu.Append(wxID_DELETE_FRAME, wxT("Delete frame(s)"))->Enable(enable_delete_frame);
menu.Append(wxID_DELETE_SUBFRAME, wxT("Delete subframe"))->Enable(enable_delete_subframe); menu.Append(wxID_DELETE_SUBFRAME, wxT("Delete subframe(s)"))->Enable(enable_delete_subframe);
menu.Append(wxID_DELETE_CONTROLLER_SUBFRAMES, wxT("Delete controller subframes(s)"))
->Enable(enable_delete_controller_subframe);
menu.AppendSeparator(); menu.AppendSeparator();
menu.Append(wxID_TRUNCATE, wxT("Truncate movie"))->Enable(enable_delete_subframe); menu.Append(wxID_TRUNCATE, wxT("Truncate movie"))->Enable(enable_truncate_movie);
menu.AppendSeparator();
menu.Append(wxID_CUT_FRAMES, towxstring("Cut " + copy_title))->Enable(enable_cut_frame);
menu.Append(wxID_COPY_FRAMES, towxstring("Copy " + copy_title))->Enable(enable_copy_frame);
menu.Append(wxID_PASTE_FRAMES, towxstring("Paste " + paste_title))->Enable(enable_paste_frame);
menu.Append(wxID_PASTE_APPEND, towxstring("Paste append " + paste_title))->Enable(enable_paste_append);
menu.AppendSeparator(); menu.AppendSeparator();
outrange:
menu.Append(wxID_SCROLL_FRAME, wxT("Scroll to frame...")); menu.Append(wxID_SCROLL_FRAME, wxT("Scroll to frame..."));
menu.Append(wxID_SCROLL_CURRENT_FRAME, wxT("Scroll to current frame")); menu.Append(wxID_SCROLL_CURRENT_FRAME, wxT("Scroll to current frame"));
menu.Append(wxID_RUN_TO_FRAME, wxT("Run to frame...")); menu.Append(wxID_RUN_TO_FRAME, wxT("Run to frame..."));
@ -1215,6 +1601,225 @@ void wxeditor_movie::_moviepanel::on_paint(wxPaintEvent& e)
requested = false; requested = false;
} }
void wxeditor_movie::_moviepanel::do_copy(uint64_t row1, uint64_t row2, unsigned port, unsigned controller)
{
frame_controls* _fcontrols = &fcontrols;
uint64_t line = row1;
uint64_t line2 = row2;
if(line2 < line)
std::swap(line, line2);
std::string copied;
runemufn([port, controller, line, line2, _fcontrols, &copied]() {
controller_frame_vector& fv = movb.get_movie().get_frame_vector();
uint64_t vsize = fv.size();
if(!vsize)
return;
uint64_t _line = min(line, vsize - 1);
uint64_t _line2 = min(line2, vsize - 1);
copied = encode_lines(*_fcontrols, fv, _line, _line2 + 1, port, controller);
});
copy_to_clipboard(copied);
}
void wxeditor_movie::_moviepanel::do_copy(uint64_t row1, uint64_t row2)
{
uint64_t line = row1;
uint64_t line2 = row2;
if(line2 < line)
std::swap(line, line2);
std::string copied;
runemufn([line, line2, &copied]() {
controller_frame_vector& fv = movb.get_movie().get_frame_vector();
uint64_t vsize = fv.size();
if(!vsize)
return;
uint64_t _line = min(line, vsize - 1);
uint64_t _line2 = min(line2, vsize - 1);
copied = encode_lines(fv, _line, _line2 + 1);
});
copy_to_clipboard(copied);
}
void wxeditor_movie::_moviepanel::do_cut(uint64_t row1, uint64_t row2, unsigned port, unsigned controller)
{
do_copy(row1, row2, port, controller);
do_delete_controller(row1, row2, port, controller);
}
void wxeditor_movie::_moviepanel::do_cut(uint64_t row1, uint64_t row2)
{
do_copy(row1, row2);
do_delete_frame(row1, row2, false);
}
void wxeditor_movie::_moviepanel::do_paste(uint64_t row, bool append)
{
frame_controls* _fcontrols = &fcontrols;
recursing = true;
uint64_t _gapstart = row;
std::string cliptext = copy_from_clipboard();
runemufn([_fcontrols, &cliptext, _gapstart, append]() {
//Insert enough lines for the pasted content.
uint64_t gapstart = _gapstart;
if(!movb.get_movie().readonly_mode())
return;
uint64_t gaplen = 0;
int64_t newframes = 0;
{
std::istringstream y(cliptext);
std::string z;
if(!std::getline(y, z))
return;
istrip_CR(z);
if(z != "lsnes-moviedata-whole")
return;
while(std::getline(y, z))
gaplen++;
}
controller_frame_vector& fv = movb.get_movie().get_frame_vector();
uint64_t vsize = fv.size();
if(gapstart < real_first_editable(*_fcontrols, 0))
return;
if(gapstart > vsize)
return;
if(append) gapstart = vsize;
for(uint64_t i = 0; i < gaplen; i++)
fv.append(fv.blank_frame(false));
for(uint64_t i = vsize - 1; i >= gapstart && i <= vsize; i--)
fv[i + gaplen] = fv[i];
//Write the pasted frames.
{
std::istringstream y(cliptext);
std::string z;
std::getline(y, z);
uint64_t idx = gapstart;
while(std::getline(y, z)) {
fv[idx++].deserialize(z.c_str());
if(fv[idx - 1].sync())
newframes++;
}
}
movie_framecount_change(newframes);
});
recursing = false;
signal_repaint();
}
void wxeditor_movie::_moviepanel::do_paste(uint64_t row, unsigned port, unsigned controller, bool append)
{
if(!port && !controller)
return;
frame_controls* _fcontrols = &fcontrols;
auto iset = controller_index_set(fcontrols, port, controller);
recursing = true;
uint64_t _gapstart = row;
std::string cliptext = copy_from_clipboard();
runemufn([_fcontrols, iset, &cliptext, _gapstart, port, controller, append]() {
//Insert enough lines for the pasted content.
//TODO: Check that this won't alter the past.
uint64_t gapstart = _gapstart;
if(!movb.get_movie().readonly_mode())
return;
uint64_t gaplen = 0;
int64_t newframes = 0;
{
std::istringstream y(cliptext);
std::string z;
if(!std::getline(y, z))
return;
istrip_CR(z);
if(z != "lsnes-moviedata-controller")
return;
while(std::getline(y, z)) {
gaplen++;
newframes++;
}
}
controller_frame_vector& fv = movb.get_movie().get_frame_vector();
uint64_t vsize = fv.size();
if(gapstart < real_first_editable(*_fcontrols, iset))
return;
if(gapstart > vsize)
return;
if(append) gapstart = vsize;
for(uint64_t i = 0; i < gaplen; i++)
fv.append(fv.blank_frame(true));
move_index_set(*_fcontrols, fv, gapstart, gapstart + gaplen, vsize - gapstart, iset);
//Write the pasted frames.
{
std::istringstream y(cliptext);
std::string z;
std::getline(y, z);
uint64_t idx = gapstart;
while(std::getline(y, z)) {
controller_frame f = fv[idx++];
decode_line(*_fcontrols, f, z, port, controller);
}
}
movie_framecount_change(newframes);
});
recursing = false;
signal_repaint();
}
void wxeditor_movie::_moviepanel::do_insert_controller(uint64_t row, unsigned port, unsigned controller)
{
if(!port && !controller)
return;
frame_controls* _fcontrols = &fcontrols;
auto iset = controller_index_set(fcontrols, port, controller);
recursing = true;
uint64_t gapstart = row;
runemufn([_fcontrols, iset, gapstart, port, controller]() {
//Insert enough lines for the pasted content.
//TODO: Check that this won't alter the past.
if(!movb.get_movie().readonly_mode())
return;
controller_frame_vector& fv = movb.get_movie().get_frame_vector();
uint64_t vsize = fv.size();
if(gapstart < real_first_editable(*_fcontrols, iset))
return;
if(gapstart > vsize)
return;
fv.append(fv.blank_frame(true));
move_index_set(*_fcontrols, fv, gapstart, gapstart + 1, vsize - gapstart, iset);
zero_index_set(*_fcontrols, fv, gapstart, 1, iset);
movie_framecount_change(1);
});
recursing = false;
signal_repaint();
}
void wxeditor_movie::_moviepanel::do_delete_controller(uint64_t row1, uint64_t row2, unsigned port,
unsigned controller)
{
if(!port && !controller)
return;
frame_controls* _fcontrols = &fcontrols;
auto iset = controller_index_set(fcontrols, port, controller);
recursing = true;
if(row1 > row2) std::swap(row1, row2);
uint64_t gapstart = row1;
uint64_t gaplen = row2 - row1 + 1;
runemufn([_fcontrols, iset, gapstart, gaplen, port, controller]() {
//Insert enough lines for the pasted content.
//TODO: Check that this won't alter the past.
if(!movb.get_movie().readonly_mode())
return;
controller_frame_vector& fv = movb.get_movie().get_frame_vector();
uint64_t vsize = fv.size();
if(gapstart < real_first_editable(*_fcontrols, iset))
return;
if(gapstart > vsize)
return;
move_index_set(*_fcontrols, fv, gapstart + gaplen, gapstart, vsize - gapstart - gaplen, iset);
zero_index_set(*_fcontrols, fv, vsize - gaplen, gaplen, iset);
});
recursing = false;
signal_repaint();
}
wxeditor_movie::wxeditor_movie(wxWindow* parent) wxeditor_movie::wxeditor_movie(wxWindow* parent)
: wxDialog(parent, wxID_ANY, wxT("lsnes: Edit movie"), wxDefaultPosition, wxSize(-1, -1)) : wxDialog(parent, wxID_ANY, wxT("lsnes: Edit movie"), wxDefaultPosition, wxSize(-1, -1))
{ {
@ -1228,7 +1833,7 @@ wxeditor_movie::wxeditor_movie(wxWindow* parent)
panel_s->Add(moviepanel = new _moviepanel(this), 1, wxGROW); panel_s->Add(moviepanel = new _moviepanel(this), 1, wxGROW);
panel_s->Add(moviescroll = new scroll_bar(this, wxID_ANY, true), 0, wxGROW); panel_s->Add(moviescroll = new scroll_bar(this, wxID_ANY, true), 0, wxGROW);
top_s->Add(panel_s, 1, wxGROW); top_s->Add(panel_s, 1, wxGROW);
moviescroll->set_page_size(lines_to_display); moviescroll->set_page_size(lines_to_display);
moviescroll->set_handler([this](scroll_bar& s) { moviescroll->set_handler([this](scroll_bar& s) {
this->moviepanel->moviepos = s.get_position(); this->moviepanel->moviepos = s.get_position();