Make various instance stuff to take references to other instance objs
[lsnes.git] / src / platform / wxwidgets / editor-movie.cpp
blobfbb991c828cdc89a9a4f4af3969b7a7000ee7d33
1 #include "core/framebuffer.hpp"
2 #include "core/instance.hpp"
3 #include "core/moviedata.hpp"
4 #include "core/dispatch.hpp"
5 #include "core/window.hpp"
7 #include "interface/controller.hpp"
8 #include "core/mainloop.hpp"
9 #include "core/mbranch.hpp"
10 #include "core/project.hpp"
11 #include "platform/wxwidgets/loadsave.hpp"
12 #include "platform/wxwidgets/platform.hpp"
13 #include "platform/wxwidgets/scrollbar.hpp"
14 #include "platform/wxwidgets/textrender.hpp"
15 #include "library/minmax.hpp"
16 #include "library/string.hpp"
17 #include "library/utf8.hpp"
19 #include <algorithm>
20 #include <cstring>
21 #include <limits>
22 #include <wx/wx.h>
23 #include <wx/event.h>
24 #include <wx/control.h>
25 #include <wx/combobox.h>
26 #include <wx/clipbrd.h>
28 extern "C"
30 #ifndef UINT64_C
31 #define UINT64_C(val) val##ULL
32 #endif
33 #include <libswscale/swscale.h>
36 enum
38 wxID_TOGGLE = wxID_HIGHEST + 1,
39 wxID_CHANGE,
40 wxID_SWEEP,
41 wxID_APPEND_FRAME,
42 wxID_CHANGE_LINECOUNT,
43 wxID_INSERT_AFTER,
44 wxID_INSERT_AFTER_MULTIPLE,
45 wxID_DELETE_FRAME,
46 wxID_DELETE_SUBFRAME,
47 wxID_POSITION_LOCK,
48 wxID_RUN_TO_FRAME,
49 wxID_APPEND_FRAMES,
50 wxID_TRUNCATE,
51 wxID_SCROLL_FRAME,
52 wxID_SCROLL_CURRENT_FRAME,
53 wxID_COPY_FRAMES,
54 wxID_CUT_FRAMES,
55 wxID_PASTE_FRAMES,
56 wxID_PASTE_APPEND,
57 wxID_INSERT_CONTROLLER_AFTER,
58 wxID_DELETE_CONTROLLER_SUBFRAMES,
59 wxID_MBRANCH_NEW,
60 wxID_MBRANCH_IMPORT,
61 wxID_MBRANCH_EXPORT,
62 wxID_MBRANCH_RENAME,
63 wxID_MBRANCH_DELETE,
64 wxID_MBRANCH_FIRST,
65 wxID_MBRANCH_LAST = wxID_MBRANCH_FIRST + 1024
68 void update_movie_state();
70 namespace
72 unsigned lines_to_display = 28;
73 uint64_t divs[] = {1000000, 100000, 10000, 1000, 100, 10, 1};
74 uint64_t divsl[] = {1000000, 100000, 10000, 1000, 100, 10, 0};
75 const unsigned divcnt = sizeof(divs)/sizeof(divs[0]);
77 class exp_imp_type
79 public:
80 typedef std::pair<std::string, int> returntype;
81 exp_imp_type()
84 filedialog_input_params input(bool save) const
86 filedialog_input_params ip;
87 ip.types.push_back(filedialog_type_entry("Input tracks (text)", "*.lstt", "lstt"));
88 ip.types.push_back(filedialog_type_entry("Input tracks (binary)", "*.lstb", "lstb"));
89 if(!save)
90 ip.types.push_back(filedialog_type_entry("Movie files", "*.lsmv", "lsmv"));
91 ip.default_type = 1;
92 return ip;
94 std::pair<std::string, int> output(const filedialog_output_params& p, bool save) const
96 int m;
97 switch(p.typechoice) {
98 case 0: m = MBRANCH_IMPORT_TEXT; break;
99 case 1: m = MBRANCH_IMPORT_BINARY; break;
100 case 2: m = MBRANCH_IMPORT_MOVIE; break;
102 return std::make_pair(p.path, m);
104 private:
108 struct control_info
110 unsigned position_left;
111 unsigned reserved; //Must be at least 6 for axes.
112 unsigned index; //Index in poll vector.
113 int type; //-2 => Port, -1 => Fixed, 0 => Button, 1 => axis.
114 char32_t ch;
115 std::u32string title;
116 unsigned port;
117 unsigned controller;
118 port_controller_button::_type axistype;
119 int rmin;
120 int rmax;
121 static control_info portinfo(unsigned& p, unsigned port, unsigned controller);
122 static control_info fixedinfo(unsigned& p, const std::u32string& str);
123 static control_info buttoninfo(unsigned& p, char32_t character, const std::u32string& title, unsigned idx,
124 unsigned port, unsigned controller);
125 static control_info axisinfo(unsigned& p, const std::u32string& title, unsigned idx,
126 unsigned port, unsigned controller, port_controller_button::_type _axistype, int _rmin, int _rmax);
129 control_info control_info::portinfo(unsigned& p, unsigned port, unsigned controller)
131 control_info i;
132 i.position_left = p;
133 i.reserved = (stringfmt() << port << "-" << controller).str32().length();
134 p += i.reserved;
135 i.index = 0;
136 i.type = -2;
137 i.ch = 0;
138 i.title = U"";
139 i.port = port;
140 i.controller = controller;
141 return i;
144 control_info control_info::fixedinfo(unsigned& p, const std::u32string& str)
146 control_info i;
147 i.position_left = p;
148 i.reserved = str.length();
149 p += i.reserved;
150 i.index = 0;
151 i.type = -1;
152 i.ch = 0;
153 i.title = str;
154 i.port = 0;
155 i.controller = 0;
156 return i;
159 control_info control_info::buttoninfo(unsigned& p, char32_t character, const std::u32string& title, unsigned idx,
160 unsigned port, unsigned controller)
162 control_info i;
163 i.position_left = p;
164 i.reserved = 1;
165 p += i.reserved;
166 i.index = idx;
167 i.type = 0;
168 i.ch = character;
169 i.title = title;
170 i.port = port;
171 i.controller = controller;
172 return i;
175 control_info control_info::axisinfo(unsigned& p, const std::u32string& title, unsigned idx,
176 unsigned port, unsigned controller, port_controller_button::_type _axistype, int _rmin, int _rmax)
178 control_info i;
179 i.position_left = p;
180 i.reserved = title.length();
181 if(i.reserved < 6)
182 i.reserved = 6;
183 p += i.reserved;
184 i.index = idx;
185 i.type = 1;
186 i.ch = 0;
187 i.title = title;
188 i.port = port;
189 i.controller = controller;
190 i.axistype = _axistype;
191 i.rmin = _rmin;
192 i.rmax = _rmax;
193 return i;
196 class frame_controls
198 public:
199 frame_controls();
200 void set_types(controller_frame& f);
201 short read_index(controller_frame& f, unsigned idx);
202 void write_index(controller_frame& f, unsigned idx, short value);
203 uint32_t read_pollcount(pollcounter_vector& v, unsigned idx);
204 const std::list<control_info>& get_controlinfo() { return controlinfo; }
205 std::u32string line1() { return _line1; }
206 std::u32string line2() { return _line2; }
207 size_t width() { return _width; }
208 private:
209 size_t _width;
210 std::u32string _line1;
211 std::u32string _line2;
212 void format_lines();
213 void add_port(unsigned& c, unsigned pid, const port_type& p, const port_type_set& pts);
214 std::list<control_info> controlinfo;
218 frame_controls::frame_controls()
220 _width = 0;
223 void frame_controls::set_types(controller_frame& f)
225 unsigned nextp = 0;
226 controlinfo.clear();
227 const port_type_set& pts = f.porttypes();
228 unsigned pcnt = pts.ports();
229 for(unsigned i = 0; i < pcnt; i++)
230 add_port(nextp, i, pts.port_type(i), pts);
231 format_lines();
234 void frame_controls::add_port(unsigned& c, unsigned pid, const port_type& p, const port_type_set& pts)
236 const port_controller_set& pci = *(p.controller_info);
237 for(unsigned i = 0; i < pci.controllers.size(); i++) {
238 const port_controller& pc = pci.controllers[i];
239 if(pid || i)
240 controlinfo.push_back(control_info::fixedinfo(c, U"\u2502"));
241 unsigned nextp = c;
242 controlinfo.push_back(control_info::portinfo(nextp, pid, i + 1));
243 bool last_multibyte = false;
244 for(unsigned j = 0; j < pc.buttons.size(); j++) {
245 const port_controller_button& pcb = pc.buttons[j];
246 unsigned idx = pts.triple_to_index(pid, i, j);
247 if(idx == 0xFFFFFFFFUL)
248 continue;
249 if(pcb.type == port_controller_button::TYPE_BUTTON) {
250 if(last_multibyte)
251 c++;
252 controlinfo.push_back(control_info::buttoninfo(c, pcb.symbol, utf8::to32(pcb.name),
253 idx, pid, i));
254 last_multibyte = false;
255 } else if(pcb.type == port_controller_button::TYPE_AXIS ||
256 pcb.type == port_controller_button::TYPE_RAXIS ||
257 pcb.type == port_controller_button::TYPE_TAXIS ||
258 pcb.type == port_controller_button::TYPE_LIGHTGUN) {
259 if(j)
260 c++;
261 controlinfo.push_back(control_info::axisinfo(c, utf8::to32(pcb.name), idx, pid, i,
262 pcb.type, pcb.rmin, pcb.rmax));
263 last_multibyte = true;
266 if(nextp > c)
267 c = nextp;
271 short frame_controls::read_index(controller_frame& f, unsigned idx)
273 if(idx == 0)
274 return f.sync() ? 1 : 0;
275 return f.axis2(idx);
278 void frame_controls::write_index(controller_frame& f, unsigned idx, short value)
280 if(idx == 0)
281 return f.sync(value);
282 return f.axis2(idx, value);
285 uint32_t frame_controls::read_pollcount(pollcounter_vector& v, unsigned idx)
287 if(idx == 0)
288 return max(v.max_polls(), (uint32_t)1);
289 for(auto i : controlinfo)
290 if(idx == i.index && i.port == 0 && i.controller == 0)
291 return max(v.get_polls(idx), (uint32_t)(v.get_framepflag() ? 1 : 0));
292 return v.get_polls(idx);
295 void frame_controls::format_lines()
297 _width = 0;
298 for(auto i : controlinfo) {
299 if(i.position_left + i.reserved > _width)
300 _width = i.position_left + i.reserved;
302 std::u32string cp1;
303 std::u32string cp2;
304 uint32_t off = divcnt + 1;
305 cp1.resize(_width + divcnt + 1);
306 cp2.resize(_width + divcnt + 1);
307 for(unsigned i = 0; i < cp1.size(); i++)
308 cp1[i] = cp2[i] = 32;
309 cp1[divcnt] = 0x2502;
310 cp2[divcnt] = 0x2502;
311 //Line1
312 //For every port-controller, find the least coordinate.
313 for(auto i : controlinfo) {
314 if(i.type == -1) {
315 auto _title = i.title;
316 std::copy(_title.begin(), _title.end(), &cp1[i.position_left + off]);
317 } else if(i.type == -2) {
318 auto _title = (stringfmt() << i.port << "-" << i.controller).str32();
319 std::copy(_title.begin(), _title.end(), &cp1[i.position_left + off]);
322 //Line2
323 for(auto i : controlinfo) {
324 auto _title = i.title;
325 if(i.type == -1 || i.type == 1)
326 std::copy(_title.begin(), _title.end(), &cp2[i.position_left + off]);
327 if(i.type == 0)
328 cp2[i.position_left + off] = i.ch;
330 _line1 = cp1;
331 _line2 = cp2;
334 namespace
336 //TODO: Use real clipboard.
337 std::string clipboard;
339 void copy_to_clipboard(const std::string& text)
341 clipboard = text;
344 bool clipboard_has_text()
346 return (clipboard.length() > 0);
349 void clear_clipboard()
351 clipboard = "";
354 std::string copy_from_clipboard()
356 return clipboard;
359 std::string encode_line(controller_frame& f)
361 char buffer[512];
362 f.serialize(buffer);
363 return buffer;
366 std::string encode_line(frame_controls& info, controller_frame& f, unsigned port, unsigned controller)
368 std::ostringstream x;
369 bool last_axis = false;
370 bool first = true;
371 for(auto i : info.get_controlinfo()) {
372 if(i.port != port)
373 continue;
374 if(i.controller != controller)
375 continue;
376 switch(i.type) {
377 case 0: //Button.
378 if(last_axis)
379 x << " ";
380 if(info.read_index(f, i.index)) {
381 char32_t tmp1[2];
382 tmp1[0] = i.ch;
383 tmp1[1] = 0;
384 x << utf8::to8(std::u32string(tmp1));
385 } else
386 x << "-";
387 last_axis = false;
388 first = false;
389 break;
390 case 1: //Axis.
391 if(!first)
392 x << " ";
393 x << info.read_index(f, i.index);
394 first = false;
395 last_axis = true;
396 break;
399 return x.str();
402 short read_short(const std::u32string& s, size_t& r)
404 unsigned short _res = 0;
405 bool negative = false;
406 if(r < s.length() && s[r] == '-') {
407 negative = true;
408 r++;
410 while(r < s.length() && s[r] >= 48 && s[r] <= 57) {
411 _res = _res * 10 + (s[r] - 48);
412 r++;
414 return negative ? -_res : _res;
417 void decode_line(frame_controls& info, controller_frame& f, std::string line, unsigned port,
418 unsigned controller)
420 std::u32string _line = utf8::to32(line);
421 bool last_axis = false;
422 bool first = true;
423 short y;
424 char32_t y2;
425 size_t ridx = 0;
426 for(auto i : info.get_controlinfo()) {
427 if(i.port != port)
428 continue;
429 if(i.controller != controller)
430 continue;
431 switch(i.type) {
432 case 0: //Button.
433 if(last_axis) {
434 ridx++;
435 while(ridx < _line.length() && (_line[ridx] == 9 || _line[ridx] == 10 ||
436 _line[ridx] == 13 || _line[ridx] == 32))
437 ridx++;
439 y2 = (ridx < _line.length()) ? _line[ridx++] : 0;
440 if(y2 == U'-' || y2 == 0)
441 info.write_index(f, i.index, 0);
442 else
443 info.write_index(f, i.index, 1);
444 last_axis = false;
445 first = false;
446 break;
447 case 1: //Axis.
448 if(!first)
449 ridx++;
450 while(ridx < _line.length() && (_line[ridx] == 9 || _line[ridx] == 10 ||
451 _line[ridx] == 13 || _line[ridx] == 32))
452 ridx++;
453 y = read_short(_line, ridx);
454 info.write_index(f, i.index, y);
455 first = false;
456 last_axis = true;
457 break;
462 std::string encode_lines(controller_frame_vector& fv, uint64_t start, uint64_t end)
464 std::ostringstream x;
465 x << "lsnes-moviedata-whole" << std::endl;
466 for(uint64_t i = start; i < end; i++) {
467 controller_frame tmp = fv[i];
468 x << encode_line(tmp) << std::endl;
470 return x.str();
473 std::string encode_lines(frame_controls& info, controller_frame_vector& fv, uint64_t start, uint64_t end,
474 unsigned port, unsigned controller)
476 std::ostringstream x;
477 x << "lsnes-moviedata-controller" << std::endl;
478 for(uint64_t i = start; i < end; i++) {
479 controller_frame tmp = fv[i];
480 x << encode_line(info, tmp, port, controller) << std::endl;
482 return x.str();
485 int clipboard_get_data_type()
487 if(!clipboard_has_text())
488 return -1;
489 std::string y = copy_from_clipboard();
490 std::istringstream x(y);
491 std::string hdr;
492 std::getline(x, hdr);
493 if(hdr == "lsnes-moviedata-whole")
494 return 1;
495 if(hdr == "lsnes-moviedata-controller")
496 return 0;
497 return -1;
500 std::set<unsigned> controller_index_set(frame_controls& info, unsigned port, unsigned controller)
502 std::set<unsigned> r;
503 for(auto i : info.get_controlinfo()) {
504 if(i.port == port && i.controller == controller && (i.type == 0 || i.type == 1))
505 r.insert(i.index);
507 return r;
510 void move_index_set(frame_controls& info, controller_frame_vector& fv, uint64_t src, uint64_t dst,
511 uint64_t len, const std::set<unsigned>& indices)
513 if(src == dst)
514 return;
515 controller_frame_vector::notify_freeze freeze(fv);
516 if(src > dst) {
517 //Copy forwards.
518 uint64_t shift = src - dst;
519 for(uint64_t i = dst; i < dst + len; i++) {
520 controller_frame _src = fv[i + shift];
521 controller_frame _dst = fv[i];
522 for(auto j : indices)
523 info.write_index(_dst, j, info.read_index(_src, j));
525 } else {
526 //Copy backwards.
527 uint64_t shift = dst - src;
528 for(uint64_t i = src + len - 1; i >= src && i < src + len; i--) {
529 controller_frame _src = fv[i];
530 controller_frame _dst = fv[i + shift];
531 for(auto j : indices)
532 info.write_index(_dst, j, info.read_index(_src, j));
537 void zero_index_set(frame_controls& info, controller_frame_vector& fv, uint64_t dst, uint64_t len,
538 const std::set<unsigned>& indices)
540 controller_frame_vector::notify_freeze freeze(fv);
541 for(uint64_t i = dst; i < dst + len; i++) {
542 controller_frame _dst = fv[i];
543 for(auto j : indices)
544 info.write_index(_dst, j, 0);
548 control_info find_paired(control_info ci, const std::list<control_info>& info)
550 if(ci.axistype == port_controller_button::TYPE_TAXIS)
551 return ci;
552 bool even = true;
553 bool next_flag = false;
554 control_info previous;
555 for(auto i : info) {
556 if(i.port != ci.port || i.controller != ci.controller)
557 continue;
558 if(i.axistype != port_controller_button::TYPE_AXIS &&
559 i.axistype != port_controller_button::TYPE_RAXIS &&
560 i.axistype != port_controller_button::TYPE_LIGHTGUN)
561 continue;
562 if(next_flag)
563 return i;
564 if(i.index == ci.index) {
565 //This and...
566 if(even)
567 next_flag = true; //Next.
568 else
569 return previous; //Pevious.
571 previous = i;
572 even = !even;
574 //Huh, no pair.
575 return ci;
578 int32_t value_to_coordinate(int32_t rmin, int32_t rmax, int32_t val, int32_t dim)
580 //Scale the values to be zero-based.
581 val = min(max(val, rmin), rmax);
582 rmax -= rmin;
583 val -= rmin;
584 int32_t center = rmax / 2;
585 int32_t cc = (dim - 1) / 2;
586 if(val == center)
587 return cc;
588 if(val < center) {
589 //0 => 0, center => cc.
590 return (val * (int64_t)cc + (center / 2)) / center;
592 if(val > center) {
593 //center => cc, rmax => dim - 1.
594 val -= center;
595 rmax -= center;
596 int32_t cc2 = (dim - 1 - cc);
597 return (val * (int64_t)cc2 + (rmax / 2)) / rmax + cc;
599 return 0; //NOTREACHED.
602 int32_t coordinate_to_value(int32_t rmin, int32_t rmax, int32_t val, int32_t dim)
604 if(dim == rmin - rmax + 1) {
605 return val + rmin;
607 val = min(max(val, (int32_t)0), dim - 1);
608 int32_t center = (rmax + rmin) / 2;
609 int32_t cc = (dim - 1) / 2;
610 if(val == cc)
611 return center;
612 if(val < cc) {
613 //0 => rmin, cc => center.
614 return ((center - rmin) * (int64_t)val + cc / 2) / cc + rmin;
616 if(val > cc) {
617 //cc => center, dim - 1 => rmax.
618 uint32_t cc2 = (dim - 1 - cc);
619 return ((rmax - center) * (int64_t)(val - cc) + cc2 / 2) / cc2 + center;
621 return 0; //NOTREACHED.
624 std::string windowname(control_info X, control_info Y)
626 if(X.index == Y.index)
627 return (stringfmt() << utf8::to8(X.title)).str();
628 else
629 return (stringfmt() << utf8::to8(X.title) << "/" << utf8::to8(Y.title)).str();
632 class window_prompt : public wxDialog
634 public:
635 window_prompt(wxWindow* parent, uint8_t* _bitmap, unsigned _width,
636 unsigned _height, control_info X, control_info Y, unsigned posX, unsigned posY)
637 : wxDialog(parent, wxID_ANY, towxstring(windowname(X, Y)), wxPoint(posX, posY))
639 dirty = false;
640 bitmap = _bitmap;
641 width = _width;
642 height = _height;
643 cX = X;
644 cY = Y;
645 oneaxis = false;
646 if(X.index == Y.index) {
647 //One-axis never has a bitmap.
648 bitmap = NULL;
649 height = 32;
650 oneaxis = true;
652 wxSizer* s = new wxBoxSizer(wxVERTICAL);
653 SetSizer(s);
654 s->Add(panel = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxSize(width, height)), 0,
655 wxGROW);
656 panel->Connect(wxEVT_PAINT, wxPaintEventHandler(window_prompt::on_paint), NULL, this);
657 panel->Connect(wxEVT_ERASE_BACKGROUND, wxEraseEventHandler(window_prompt::on_erase), NULL,
658 this);
659 Connect(wxEVT_CLOSE_WINDOW, wxCloseEventHandler(window_prompt::on_wclose));
660 panel->Connect(wxEVT_LEFT_DOWN, wxMouseEventHandler(window_prompt::on_mouse), NULL, this);
661 panel->Connect(wxEVT_MOTION, wxMouseEventHandler(window_prompt::on_mouse), NULL, this);
662 Fit();
664 void on_wclose(wxCloseEvent& e)
666 EndModal(wxID_CANCEL);
668 void on_erase(wxEraseEvent& e)
670 //Blank.
672 void on_paint(wxPaintEvent& e)
674 wxPaintDC dc(panel);
675 if(bitmap) {
676 wxBitmap bmp(wxImage(width, height, bitmap, true));
677 dc.DrawBitmap(bmp, 0, 0, false);
678 } else {
679 dc.SetBackground(*wxWHITE_BRUSH);
680 dc.Clear();
681 auto xval = value_to_coordinate(cX.rmin, cX.rmax, 0, width);
682 auto yval = value_to_coordinate(cY.rmin, cY.rmax, 0, height);
683 dc.SetPen(*wxBLACK_PEN);
684 if(cX.rmin < 0 && cX.rmax > 0)
685 dc.DrawLine(xval, 0, xval, height);
686 if(!oneaxis && cY.rmin < 0 && cY.rmax > 0)
687 dc.DrawLine(0, yval, width, yval);
689 dc.SetPen(*wxRED_PEN);
690 dc.DrawLine(mouseX, 0, mouseX, height);
691 if(!oneaxis)
692 dc.DrawLine(0, mouseY, width, mouseY);
693 dirty = false;
695 void on_mouse(wxMouseEvent& e)
697 if(e.LeftDown()) {
698 result.first = coordinate_to_value(cX.rmin, cX.rmax, e.GetX(), width);
699 if(!oneaxis)
700 result.second = coordinate_to_value(cY.rmin, cY.rmax, e.GetY(), height);
701 else
702 result.second = 0;
703 EndModal(wxID_OK);
705 mouseX = e.GetX();
706 mouseY = e.GetY();
707 if(!dirty) {
708 dirty = true;
709 panel->Refresh();
712 std::pair<int, int> get_results()
714 return result;
716 private:
717 std::pair<int, int> result;
718 wxPanel* panel;
719 bool oneaxis;
720 bool dirty;
721 int mouseX;
722 int mouseY;
723 int height;
724 int width;
725 control_info cX;
726 control_info cY;
727 uint8_t* bitmap;
730 std::pair<int, int> prompt_coodinates_window(wxWindow* parent, uint8_t* bitmap, unsigned width,
731 unsigned height, control_info X, control_info Y, unsigned posX, unsigned posY)
733 window_prompt* p = new window_prompt(parent, bitmap, width, height, X, Y, posX, posY);
734 if(p->ShowModal() == wxID_CANCEL) {
735 delete p;
736 throw canceled_exception();
738 auto r = p->get_results();
739 delete p;
740 return r;
744 class wxeditor_movie : public wxDialog
746 public:
747 wxeditor_movie(wxWindow* parent);
748 ~wxeditor_movie() throw();
749 bool ShouldPreventAppExit() const;
750 void on_close(wxCommandEvent& e);
751 void on_wclose(wxCloseEvent& e);
752 void on_focus_wrong(wxFocusEvent& e);
753 void on_keyboard_down(wxKeyEvent& e);
754 void on_keyboard_up(wxKeyEvent& e);
755 scroll_bar* get_scroll();
756 void update();
757 private:
758 struct _moviepanel : public wxPanel
760 _moviepanel(wxeditor_movie* v);
761 ~_moviepanel() throw();
762 void signal_repaint();
763 void on_paint(wxPaintEvent& e);
764 void on_erase(wxEraseEvent& e);
765 void on_mouse(wxMouseEvent& e);
766 void on_popup_menu(wxCommandEvent& e);
767 uint64_t moviepos;
768 private:
769 int get_lines();
770 void render(text_framebuffer& fb, unsigned long long pos);
771 void on_mouse0(unsigned x, unsigned y, bool polarity, bool shift, unsigned X, unsigned Y);
772 void on_mouse1(unsigned x, unsigned y, bool polarity);
773 void on_mouse2(unsigned x, unsigned y, bool polarity);
774 void popup_axis_panel(uint64_t row, control_info ci, unsigned screenX, unsigned screenY);
775 void do_toggle_buttons(unsigned idx, uint64_t row1, uint64_t row2, bool force_false);
776 void do_alter_axis(unsigned idx, uint64_t row1, uint64_t row2);
777 void do_sweep_axis(unsigned idx, uint64_t row1, uint64_t row2);
778 void do_append_frames(uint64_t count);
779 void do_append_frames();
780 void do_insert_frame_after(uint64_t row, bool multi);
781 void do_delete_frame(uint64_t row1, uint64_t row2, bool wholeframe);
782 void do_truncate(uint64_t row);
783 void do_set_stop_at_frame();
784 void do_scroll_to_frame();
785 void do_scroll_to_current_frame();
786 void do_copy(uint64_t row1, uint64_t row2, unsigned port, unsigned controller);
787 void do_copy(uint64_t row1, uint64_t row2);
788 void do_cut(uint64_t row1, uint64_t row2, unsigned port, unsigned controller);
789 void do_cut(uint64_t row1, uint64_t row2);
790 void do_paste(uint64_t row, unsigned port, unsigned controller, bool append);
791 void do_paste(uint64_t row, bool append);
792 void do_insert_controller(uint64_t row, unsigned port, unsigned controller);
793 void do_delete_controller(uint64_t row1, uint64_t row2, unsigned port, unsigned controller);
794 uint64_t first_editable(unsigned index);
795 uint64_t first_nextframe();
796 int width(controller_frame& f);
797 std::u32string render_line1(controller_frame& f);
798 std::u32string render_line2(controller_frame& f);
799 void render_linen(text_framebuffer& fb, controller_frame& f, uint64_t sfn, int y);
800 unsigned long long spos;
801 void* prev_obj;
802 uint64_t prev_seqno;
803 void update_cache();
804 std::map<uint64_t, uint64_t> subframe_to_frame;
805 uint64_t max_subframe;
806 frame_controls fcontrols;
807 wxeditor_movie* m;
808 bool requested;
809 text_framebuffer fb;
810 uint64_t movielines;
811 unsigned new_width;
812 unsigned new_height;
813 std::vector<uint8_t> pixels;
814 unsigned press_x;
815 uint64_t press_line;
816 uint64_t rpress_line;
817 unsigned press_index;
818 bool pressed;
819 bool recursing;
820 uint64_t linecount;
821 uint64_t cached_cffs;
822 bool position_locked;
823 wxMenu* current_popup;
824 std::map<int, std::string> branch_names;
826 _moviepanel* moviepanel;
827 wxButton* closebutton;
828 scroll_bar* moviescroll;
829 bool closing;
832 namespace
834 wxeditor_movie* movieeditor_open;
836 //Find the first real editable subframe.
837 //Call only in emulator thread.
838 uint64_t real_first_editable(frame_controls& fc, unsigned idx)
840 uint64_t cffs = lsnes_instance.mlogic.get_movie().get_current_frame_first_subframe();
841 controller_frame_vector& fv = *lsnes_instance.mlogic.get_mfile().input;
842 pollcounter_vector& pv = lsnes_instance.mlogic.get_movie().get_pollcounters();
843 uint64_t vsize = fv.size();
844 uint32_t pc = fc.read_pollcount(pv, idx);
845 for(uint32_t i = 1; i < pc; i++)
846 if(cffs + i >= vsize || fv[cffs + i].sync())
847 return cffs + i;
848 return cffs + pc;
851 uint64_t real_first_editable(frame_controls& fc, std::set<unsigned> idx)
853 uint64_t m = 0;
854 for(auto i : idx)
855 m = max(m, real_first_editable(fc, i));
856 return m;
859 //Find the first real editable whole frame.
860 //Call only in emulator thread.
861 uint64_t real_first_nextframe(frame_controls& fc)
863 uint64_t base = real_first_editable(fc, 0);
864 controller_frame_vector& fv = *lsnes_instance.mlogic.get_mfile().input;
865 uint64_t vsize = fv.size();
866 for(uint32_t i = 0;; i++)
867 if(base + i >= vsize || fv[base + i].sync())
868 return base + i;
872 wxeditor_movie::_moviepanel::~_moviepanel() throw() {}
873 wxeditor_movie::~wxeditor_movie() throw() {}
875 wxeditor_movie::_moviepanel::_moviepanel(wxeditor_movie* v)
876 : wxPanel(v, wxID_ANY, wxDefaultPosition, wxSize(100, 100), wxWANTS_CHARS)
878 m = v;
879 Connect(wxEVT_PAINT, wxPaintEventHandler(_moviepanel::on_paint), NULL, this);
880 Connect(wxEVT_ERASE_BACKGROUND, wxEraseEventHandler(_moviepanel::on_erase), NULL, this);
881 new_width = 0;
882 new_height = 0;
883 moviepos = 0;
884 spos = 0;
885 prev_obj = NULL;
886 prev_seqno = 0;
887 max_subframe = 0;
888 recursing = false;
889 position_locked = true;
890 current_popup = NULL;
892 Connect(wxEVT_LEFT_DOWN, wxMouseEventHandler(_moviepanel::on_mouse), NULL, this);
893 Connect(wxEVT_LEFT_UP, wxMouseEventHandler(_moviepanel::on_mouse), NULL, this);
894 Connect(wxEVT_MIDDLE_DOWN, wxMouseEventHandler(_moviepanel::on_mouse), NULL, this);
895 Connect(wxEVT_MIDDLE_UP, wxMouseEventHandler(_moviepanel::on_mouse), NULL, this);
896 Connect(wxEVT_RIGHT_DOWN, wxMouseEventHandler(_moviepanel::on_mouse), NULL, this);
897 Connect(wxEVT_RIGHT_UP, wxMouseEventHandler(_moviepanel::on_mouse), NULL, this);
898 Connect(wxEVT_MOUSEWHEEL, wxMouseEventHandler(_moviepanel::on_mouse), NULL, this);
900 signal_repaint();
901 requested = false;
904 void wxeditor_movie::_moviepanel::update_cache()
906 movie& m = lsnes_instance.mlogic.get_movie();
907 controller_frame_vector& fv = *lsnes_instance.mlogic.get_mfile().input;
908 if(&m == prev_obj && prev_seqno == m.get_seqno()) {
909 //Just process new subframes if any.
910 for(uint64_t i = max_subframe; i < fv.size(); i++) {
911 uint64_t prev = (i > 0) ? subframe_to_frame[i - 1] : 0;
912 controller_frame f = fv[i];
913 if(f.sync())
914 subframe_to_frame[i] = prev + 1;
915 else
916 subframe_to_frame[i] = prev;
918 max_subframe = fv.size();
919 return;
921 //Reprocess all subframes.
922 for(uint64_t i = 0; i < fv.size(); i++) {
923 uint64_t prev = (i > 0) ? subframe_to_frame[i - 1] : 0;
924 controller_frame f = fv[i];
925 if(f.sync())
926 subframe_to_frame[i] = prev + 1;
927 else
928 subframe_to_frame[i] = prev;
930 max_subframe = fv.size();
931 controller_frame model = fv.blank_frame(false);
932 fcontrols.set_types(model);
933 prev_obj = &m;
934 prev_seqno = m.get_seqno();
937 int wxeditor_movie::_moviepanel::width(controller_frame& f)
939 update_cache();
940 return divcnt + 1 + fcontrols.width();
943 std::u32string wxeditor_movie::_moviepanel::render_line1(controller_frame& f)
945 update_cache();
946 return fcontrols.line1();
949 std::u32string wxeditor_movie::_moviepanel::render_line2(controller_frame& f)
951 update_cache();
952 return fcontrols.line2();
955 void wxeditor_movie::_moviepanel::render_linen(text_framebuffer& fb, controller_frame& f, uint64_t sfn, int y)
957 update_cache();
958 size_t fbstride = fb.get_stride();
959 text_framebuffer::element* _fb = fb.get_buffer();
960 text_framebuffer::element e;
961 e.bg = 0xFFFFFF;
962 e.fg = 0x000000;
963 for(unsigned i = 0; i < divcnt; i++) {
964 uint64_t fn = subframe_to_frame[sfn];
965 e.ch = (fn >= divsl[i]) ? (((fn / divs[i]) % 10) + 48) : 32;
966 _fb[y * fbstride + i] = e;
968 e.ch = 0x2502;
969 _fb[y * fbstride + divcnt] = e;
970 const std::list<control_info>& ctrlinfo = fcontrols.get_controlinfo();
971 uint64_t curframe = lsnes_instance.mlogic.get_movie().get_current_frame();
972 pollcounter_vector& pv = lsnes_instance.mlogic.get_movie().get_pollcounters();
973 uint64_t cffs = lsnes_instance.mlogic.get_movie().get_current_frame_first_subframe();
974 cached_cffs = cffs;
975 int past = -1;
976 if(!lsnes_instance.mlogic.get_movie().readonly_mode())
977 past = 1;
978 else if(subframe_to_frame[sfn] < curframe)
979 past = 1;
980 else if(subframe_to_frame[sfn] > curframe)
981 past = 0;
982 bool now = (subframe_to_frame[sfn] == curframe);
983 unsigned xcord = 32768;
984 if(pressed)
985 xcord = press_x;
987 for(auto i : ctrlinfo) {
988 int rpast = past;
989 unsigned off = divcnt + 1;
990 bool cselected = (xcord >= i.position_left + off && xcord < i.position_left + i.reserved + off);
991 if(rpast == -1) {
992 unsigned polls = fcontrols.read_pollcount(pv, i.index);
993 rpast = ((cffs + polls) > sfn) ? 1 : 0;
995 uint32_t bgc = 0xC0C0C0;
996 if(rpast)
997 bgc |= 0x0000FF;
998 if(now)
999 bgc |= 0xFF0000;
1000 if(cselected)
1001 bgc |= 0x00FF00;
1002 if(bgc == 0xC0C0C0)
1003 bgc = 0xFFFFFF;
1004 if(i.type == -1) {
1005 //Separator.
1006 fb.write(i.title, 0, divcnt + 1 + i.position_left, y, 0x000000, 0xFFFFFF);
1007 } else if(i.type == 0) {
1008 //Button.
1009 char32_t c[2];
1010 bool v = (fcontrols.read_index(f, i.index) != 0);
1011 c[0] = i.ch;
1012 c[1] = 0;
1013 fb.write(c, 0, divcnt + 1 + i.position_left, y, v ? 0x000000 : 0xC8C8C8, bgc);
1014 } else if(i.type == 1) {
1015 //Axis.
1016 char c[7];
1017 sprintf(c, "%6d", fcontrols.read_index(f, i.index));
1018 fb.write(c, 0, divcnt + 1 + i.position_left, y, 0x000000, bgc);
1023 void wxeditor_movie::_moviepanel::render(text_framebuffer& fb, unsigned long long pos)
1025 spos = pos;
1026 controller_frame_vector& fv = *lsnes_instance.mlogic.get_mfile().input;
1027 controller_frame cf = fv.blank_frame(false);
1028 int _width = width(cf);
1029 fb.set_size(_width, lines_to_display + 3);
1030 size_t fbstride = fb.get_stride();
1031 auto fbsize = fb.get_characters();
1032 text_framebuffer::element* _fb = fb.get_buffer();
1033 fb.write((stringfmt() << "Current frame: " << lsnes_instance.mlogic.get_movie().get_current_frame() << " of "
1034 << lsnes_instance.mlogic.get_movie().get_frame_count()).str(), _width, 0, 0,
1035 0x000000, 0xFFFFFF);
1036 fb.write(render_line1(cf), _width, 0, 1, 0x000000, 0xFFFFFF);
1037 fb.write(render_line2(cf), _width, 0, 2, 0x000000, 0xFFFFFF);
1038 unsigned long long lines = fv.size();
1039 unsigned long long i;
1040 unsigned j;
1041 for(i = pos, j = 3; i < pos + lines_to_display; i++, j++) {
1042 text_framebuffer::element e;
1043 if(i >= lines) {
1044 //Out of range.
1045 e.bg = 0xFFFFFF;
1046 e.fg = 0x000000;
1047 e.ch = 32;
1048 for(unsigned k = 0; k < fbsize.first; k++)
1049 _fb[j * fbstride + k] = e;
1050 } else {
1051 controller_frame frame = fv[i];
1052 render_linen(fb, frame, i, j);
1057 void wxeditor_movie::_moviepanel::do_toggle_buttons(unsigned idx, uint64_t row1, uint64_t row2, bool force_false)
1059 frame_controls* _fcontrols = &fcontrols;
1060 uint64_t _press_line = row1;
1061 uint64_t line = row2;
1062 bool _force_false = force_false;
1063 if(_press_line > line)
1064 std::swap(_press_line, line);
1065 recursing = true;
1066 lsnes_instance.iqueue.run([idx, _press_line, line, _fcontrols, _force_false]() {
1067 if(!lsnes_instance.mlogic.get_movie().readonly_mode())
1068 return;
1069 uint64_t fedit = real_first_editable(*_fcontrols, idx);
1070 controller_frame_vector& fv = *lsnes_instance.mlogic.get_mfile().input;
1071 controller_frame_vector::notify_freeze freeze(fv);
1072 for(uint64_t i = _press_line; i <= line; i++) {
1073 if(i < fedit || i >= fv.size())
1074 continue;
1075 controller_frame cf = fv[i];
1076 if(!_force_false)
1077 _fcontrols->write_index(cf, idx, !_fcontrols->read_index(cf, idx));
1078 else
1079 _fcontrols->write_index(cf, idx, 0);
1082 recursing = false;
1083 if(idx == 0)
1084 max_subframe = _press_line; //Reparse.
1085 signal_repaint();
1088 void wxeditor_movie::_moviepanel::do_alter_axis(unsigned idx, uint64_t row1, uint64_t row2)
1090 frame_controls* _fcontrols = &fcontrols;
1091 uint64_t line = row1;
1092 uint64_t line2 = row2;
1093 short value;
1094 bool valid = true;
1095 lsnes_instance.iqueue.run([idx, line, &value, _fcontrols, &valid]() {
1096 if(!lsnes_instance.mlogic.get_movie().readonly_mode()) {
1097 valid = false;
1098 return;
1100 uint64_t fedit = real_first_editable(*_fcontrols, idx);
1101 controller_frame_vector& fv = *lsnes_instance.mlogic.get_mfile().input;
1102 if(line < fedit || line >= fv.size()) {
1103 valid = false;
1104 return;
1106 controller_frame_vector::notify_freeze freeze(fv);
1107 controller_frame cf = fv[line];
1108 value = _fcontrols->read_index(cf, idx);
1110 if(!valid)
1111 return;
1112 try {
1113 std::string text = pick_text(m, "Set value", "Enter new value:", (stringfmt() << value).str());
1114 value = parse_value<short>(text);
1115 } catch(canceled_exception& e) {
1116 return;
1117 } catch(std::exception& e) {
1118 wxMessageBox(wxT("Invalid value"), _T("Error"), wxICON_EXCLAMATION | wxOK, m);
1119 return;
1121 if(line > line2)
1122 std::swap(line, line2);
1123 lsnes_instance.iqueue.run([idx, line, line2, value, _fcontrols]() {
1124 uint64_t fedit = real_first_editable(*_fcontrols, idx);
1125 controller_frame_vector& fv = *lsnes_instance.mlogic.get_mfile().input;
1126 controller_frame_vector::notify_freeze freeze(fv);
1127 for(uint64_t i = line; i <= line2; i++) {
1128 if(i < fedit || i >= fv.size())
1129 continue;
1130 controller_frame cf = fv[i];
1131 _fcontrols->write_index(cf, idx, value);
1134 signal_repaint();
1137 void wxeditor_movie::_moviepanel::do_sweep_axis(unsigned idx, uint64_t row1, uint64_t row2)
1139 frame_controls* _fcontrols = &fcontrols;
1140 uint64_t line = row1;
1141 uint64_t line2 = row2;
1142 short value;
1143 short value2;
1144 bool valid = true;
1145 if(line > line2)
1146 std::swap(line, line2);
1147 lsnes_instance.iqueue.run([idx, line, line2, &value, &value2, _fcontrols, &valid]() {
1148 if(!lsnes_instance.mlogic.get_movie().readonly_mode()) {
1149 valid = false;
1150 return;
1152 uint64_t fedit = real_first_editable(*_fcontrols, idx);
1153 controller_frame_vector& fv = *lsnes_instance.mlogic.get_mfile().input;
1154 if(line2 < fedit || line2 >= fv.size()) {
1155 valid = false;
1156 return;
1158 controller_frame cf = fv[line];
1159 value = _fcontrols->read_index(cf, idx);
1160 controller_frame cf2 = fv[line2];
1161 value2 = _fcontrols->read_index(cf2, idx);
1163 if(!valid)
1164 return;
1165 lsnes_instance.iqueue.run([idx, line, line2, value, value2, _fcontrols]() {
1166 uint64_t fedit = real_first_editable(*_fcontrols, idx);
1167 controller_frame_vector& fv = *lsnes_instance.mlogic.get_mfile().input;
1168 controller_frame_vector::notify_freeze freeze(fv);
1169 for(uint64_t i = line + 1; i <= line2 - 1; i++) {
1170 if(i < fedit || i >= fv.size())
1171 continue;
1172 controller_frame cf = fv[i];
1173 auto tmp2 = static_cast<int64_t>(i - line) * (value2 - value) /
1174 static_cast<int64_t>(line2 - line);
1175 short tmp = value + tmp2;
1176 _fcontrols->write_index(cf, idx, tmp);
1179 signal_repaint();
1182 void wxeditor_movie::_moviepanel::do_append_frames(uint64_t count)
1184 recursing = true;
1185 uint64_t _count = count;
1186 lsnes_instance.iqueue.run([_count]() {
1187 if(!lsnes_instance.mlogic.get_movie().readonly_mode())
1188 return;
1189 controller_frame_vector& fv = *lsnes_instance.mlogic.get_mfile().input;
1190 controller_frame_vector::notify_freeze freeze(fv);
1191 for(uint64_t i = 0; i < _count; i++)
1192 fv.append(fv.blank_frame(true));
1194 recursing = false;
1195 signal_repaint();
1198 void wxeditor_movie::_moviepanel::do_append_frames()
1200 uint64_t value;
1201 try {
1202 std::string text = pick_text(m, "Append frames", "Enter number of frames to append:", "");
1203 value = parse_value<uint64_t>(text);
1204 } catch(canceled_exception& e) {
1205 return;
1206 } catch(std::exception& e) {
1207 wxMessageBox(wxT("Invalid value"), _T("Error"), wxICON_EXCLAMATION | wxOK, m);
1208 return;
1210 do_append_frames(value);
1211 signal_repaint();
1214 void wxeditor_movie::_moviepanel::do_insert_frame_after(uint64_t row, bool multi)
1216 uint64_t multicount = 1;
1217 if(multi) {
1218 try {
1219 std::string text = pick_text(m, "Append frames", "Enter number of frames to insert:", "");
1220 multicount = parse_value<uint64_t>(text);
1221 } catch(canceled_exception& e) {
1222 return;
1223 } catch(std::exception& e) {
1224 wxMessageBox(wxT("Invalid value"), _T("Error"), wxICON_EXCLAMATION | wxOK, m);
1225 return;
1228 recursing = true;
1229 frame_controls* _fcontrols = &fcontrols;
1230 uint64_t _row = row;
1231 lsnes_instance.iqueue.run([_row, _fcontrols, multicount]() {
1232 if(!lsnes_instance.mlogic.get_movie().readonly_mode())
1233 return;
1234 controller_frame_vector& fv = *lsnes_instance.mlogic.get_mfile().input;
1235 uint64_t fedit = real_first_editable(*_fcontrols, 0);
1236 //Find the start of the next frame.
1237 uint64_t nframe = _row + 1;
1238 uint64_t vsize = fv.size();
1239 while(nframe < vsize && !fv[nframe].sync())
1240 nframe++;
1241 if(nframe < fedit)
1242 return;
1243 controller_frame_vector::notify_freeze freeze(fv);
1244 for(uint64_t k = 0; k < multicount; k++)
1245 fv.append(fv.blank_frame(true));
1246 if(nframe < vsize) {
1247 //Okay, gotta copy all data after this point. nframe has to be at least 1.
1248 for(uint64_t i = vsize - 1; i >= nframe; i--)
1249 fv[i + multicount] = fv[i];
1250 for(uint64_t k = 0; k < multicount; k++)
1251 fv[nframe + k] = fv.blank_frame(true);
1254 max_subframe = row;
1255 recursing = false;
1256 signal_repaint();
1259 void wxeditor_movie::_moviepanel::do_delete_frame(uint64_t row1, uint64_t row2, bool wholeframe)
1261 recursing = true;
1262 uint64_t _row1 = row1;
1263 uint64_t _row2 = row2;
1264 bool _wholeframe = wholeframe;
1265 frame_controls* _fcontrols = &fcontrols;
1266 if(_row1 > _row2) std::swap(_row1, _row2);
1267 lsnes_instance.iqueue.run([_row1, _row2, _wholeframe, _fcontrols]() {
1268 controller_frame_vector& fv = *lsnes_instance.mlogic.get_mfile().input;
1269 uint64_t vsize = fv.size();
1270 if(_row1 >= vsize)
1271 return; //Nothing to do.
1272 controller_frame_vector::notify_freeze freeze(fv);
1273 uint64_t row2 = min(_row2, vsize - 1);
1274 uint64_t row1 = min(_row1, vsize - 1);
1275 row1 = max(row1, real_first_editable(*_fcontrols, 0));
1276 if(_wholeframe) {
1277 if(_row2 < real_first_nextframe(*_fcontrols))
1278 return; //Nothing to do.
1279 //Scan backwards for the first subframe of this frame and forwards for the last.
1280 uint64_t fsf = row1;
1281 uint64_t lsf = row2;
1282 if(fv[_row2].sync())
1283 lsf++; //Bump by one so it finds the end.
1284 while(fsf < vsize && !fv[fsf].sync())
1285 fsf--;
1286 while(lsf < vsize && !fv[lsf].sync())
1287 lsf++;
1288 fsf = max(fsf, real_first_editable(*_fcontrols, 0));
1289 uint64_t tonuke = lsf - fsf;
1290 int64_t frames_tonuke = 0;
1291 //Count frames nuked.
1292 for(uint64_t i = fsf; i < lsf; i++)
1293 if(fv[i].sync())
1294 frames_tonuke++;
1295 //Nuke from fsf to lsf.
1296 for(uint64_t i = fsf; i < vsize - tonuke; i++)
1297 fv[i] = fv[i + tonuke];
1298 fv.resize(vsize - tonuke);
1299 } else {
1300 if(row2 < real_first_editable(*_fcontrols, 0))
1301 return; //Nothing to do.
1302 //The sync flag needs to be inherited if:
1303 //1) Some deleted subframe has sync flag AND
1304 //2) The subframe immediately after deleted region doesn't.
1305 bool inherit_sync = false;
1306 for(uint64_t i = row1; i <= row2; i++)
1307 inherit_sync = inherit_sync || fv[i].sync();
1308 inherit_sync = inherit_sync && (row2 + 1 < vsize && !fv[_row2 + 1].sync());
1309 int64_t frames_tonuke = 0;
1310 //Count frames nuked.
1311 for(uint64_t i = row1; i <= row2; i++)
1312 if(fv[i].sync())
1313 frames_tonuke++;
1314 //If sync is inherited, one less frame is nuked.
1315 if(inherit_sync) frames_tonuke--;
1316 //Nuke the subframes.
1317 uint64_t tonuke = row2 - row1 + 1;
1318 for(uint64_t i = row1; i < vsize - tonuke; i++)
1319 fv[i] = fv[i + tonuke];
1320 fv.resize(vsize - tonuke);
1321 //Next subframe inherits the sync flag.
1322 if(inherit_sync)
1323 fv[row1].sync(true);
1326 max_subframe = _row1;
1327 recursing = false;
1328 signal_repaint();
1331 void wxeditor_movie::_moviepanel::do_truncate(uint64_t row)
1333 recursing = true;
1334 uint64_t _row = row;
1335 frame_controls* _fcontrols = &fcontrols;
1336 lsnes_instance.iqueue.run([_row, _fcontrols]() {
1337 controller_frame_vector& fv = *lsnes_instance.mlogic.get_mfile().input;
1338 uint64_t vsize = fv.size();
1339 if(_row >= vsize)
1340 return;
1341 if(_row < real_first_editable(*_fcontrols, 0))
1342 return;
1343 int64_t delete_count = 0;
1344 for(uint64_t i = _row; i < vsize; i++)
1345 if(fv[i].sync())
1346 delete_count--;
1347 fv.resize(_row);
1349 max_subframe = row;
1350 recursing = false;
1351 signal_repaint();
1354 void wxeditor_movie::_moviepanel::do_set_stop_at_frame()
1356 uint64_t curframe;
1357 uint64_t frame;
1358 lsnes_instance.iqueue.run([&curframe]() {
1359 curframe = lsnes_instance.mlogic.get_movie().get_current_frame();
1361 try {
1362 std::string text = pick_text(m, "Frame", (stringfmt() << "Enter frame to stop at (currently at "
1363 << curframe << "):").str(), "");
1364 frame = parse_value<uint64_t>(text);
1365 } catch(canceled_exception& e) {
1366 return;
1367 } catch(std::exception& e) {
1368 wxMessageBox(wxT("Invalid value"), _T("Error"), wxICON_EXCLAMATION | wxOK, m);
1369 return;
1371 if(frame < curframe) {
1372 wxMessageBox(wxT("The movie is already past that point"), _T("Error"), wxICON_EXCLAMATION | wxOK, m);
1373 return;
1375 lsnes_instance.iqueue.run([frame]() {
1376 set_stop_at_frame(frame);
1380 void wxeditor_movie::_moviepanel::on_mouse0(unsigned x, unsigned y, bool polarity, bool shift, unsigned X, unsigned Y)
1382 if(y < 3)
1383 return;
1384 if(polarity) {
1385 press_x = x;
1386 press_line = spos + y - 3;
1388 pressed = polarity;
1389 if(polarity) {
1390 signal_repaint();
1391 return;
1393 uint64_t line = spos + y - 3;
1394 if(press_x < divcnt && x < divcnt) {
1395 //Press on frame count.
1396 uint64_t row1 = press_line;
1397 uint64_t row2 = line;
1398 if(row1 > row2)
1399 std::swap(row1, row2);
1400 do_append_frames(row2 - row1 + 1);
1402 for(auto i : fcontrols.get_controlinfo()) {
1403 unsigned off = divcnt + 1;
1404 unsigned idx = i.index;
1405 if((press_x >= i.position_left + off && press_x < i.position_left + i.reserved + off) &&
1406 (x >= i.position_left + off && x < i.position_left + i.reserved + off)) {
1407 if(i.type == 0)
1408 do_toggle_buttons(idx, press_line, line, false);
1409 else if(i.type == 1) {
1410 if(shift) {
1411 if(press_line == line && (i.port || i.controller))
1412 try {
1413 wxPoint spos = GetScreenPosition();
1414 popup_axis_panel(line, i, spos.x + X, spos.y + Y);
1415 } catch(canceled_exception& e) {
1417 } else
1418 do_alter_axis(idx, press_line, line);
1424 void wxeditor_movie::_moviepanel::popup_axis_panel(uint64_t row, control_info ci, unsigned screenX, unsigned screenY)
1426 control_info ciX;
1427 control_info ciY;
1428 control_info ci2 = find_paired(ci, fcontrols.get_controlinfo());
1429 if(ci.index == ci2.index) {
1430 ciX = ciY = ci;
1431 } else if(ci2.index < ci.index) {
1432 ciX = ci2;
1433 ciY = ci;
1434 } else {
1435 ciX = ci;
1436 ciY = ci2;
1438 frame_controls* _fcontrols = &fcontrols;
1439 if(ciX.index == ciY.index) {
1440 auto c = prompt_coodinates_window(m, NULL, 256, 0, ciX, ciX, screenX, screenY);
1441 lsnes_instance.iqueue.run([ciX, row, c, _fcontrols]() {
1442 uint64_t fedit = real_first_editable(*_fcontrols, ciX.index);
1443 if(row < fedit) return;
1444 controller_frame_vector& fv = *lsnes_instance.mlogic.get_mfile().input;
1445 controller_frame cf = fv[row];
1446 _fcontrols->write_index(cf, ciX.index, c.first);
1448 signal_repaint();
1449 } else if(ci.axistype == port_controller_button::TYPE_LIGHTGUN) {
1450 framebuffer::raw& _fb = lsnes_instance.fbuf.render_get_latest_screen();
1451 framebuffer::fb<false> fb;
1452 auto osize = std::make_pair(_fb.get_width(), _fb.get_height());
1453 auto size = our_rom.rtype->lightgun_scale();
1454 fb.reallocate(osize.first, osize.second, false);
1455 fb.copy_from(_fb, 1, 1);
1456 lsnes_instance.fbuf.render_get_latest_screen_end();
1457 std::vector<uint8_t> buf;
1458 buf.resize(3 * (ciX.rmax - ciX.rmin + 1) * (ciY.rmax - ciY.rmin + 1));
1459 unsigned offX = -ciX.rmin;
1460 unsigned offY = -ciY.rmin;
1461 struct SwsContext* ctx = sws_getContext(osize.first, osize.second, PIX_FMT_RGBA,
1462 size.first, size.second, PIX_FMT_BGR24, SWS_POINT, NULL, NULL, NULL);
1463 uint8_t* srcp[1];
1464 int srcs[1];
1465 uint8_t* dstp[1];
1466 int dsts[1];
1467 srcs[0] = 4 * (fb.rowptr(1) - fb.rowptr(0));
1468 dsts[0] = 3 * (ciX.rmax - ciX.rmin + 1);
1469 srcp[0] = reinterpret_cast<unsigned char*>(fb.rowptr(0));
1470 dstp[0] = &buf[3 * (offY * (ciX.rmax - ciX.rmin + 1) + offX)];
1471 memset(&buf[0], 0, buf.size());
1472 sws_scale(ctx, srcp, srcs, 0, size.second, dstp, dsts);
1473 sws_freeContext(ctx);
1474 auto c = prompt_coodinates_window(m, &buf[0], (ciX.rmax - ciX.rmin + 1), (ciY.rmax - ciY.rmin + 1),
1475 ciX, ciY, screenX, screenY);
1476 lsnes_instance.iqueue.run([ciX, ciY, row, c, _fcontrols]() {
1477 uint64_t fedit = real_first_editable(*_fcontrols, ciX.index);
1478 fedit = max(fedit, real_first_editable(*_fcontrols, ciY.index));
1479 if(row < fedit) return;
1480 controller_frame_vector& fv = *lsnes_instance.mlogic.get_mfile().input;
1481 controller_frame cf = fv[row];
1482 _fcontrols->write_index(cf, ciX.index, c.first);
1483 _fcontrols->write_index(cf, ciY.index, c.second);
1485 signal_repaint();
1486 } else {
1487 auto c = prompt_coodinates_window(m, NULL, 256, 256, ciX, ciY, screenX, screenY);
1488 lsnes_instance.iqueue.run([ciX, ciY, row, c, _fcontrols]() {
1489 uint64_t fedit = real_first_editable(*_fcontrols, ciX.index);
1490 fedit = max(fedit, real_first_editable(*_fcontrols, ciY.index));
1491 if(row < fedit) return;
1492 controller_frame_vector& fv = *lsnes_instance.mlogic.get_mfile().input;
1493 controller_frame cf = fv[row];
1494 _fcontrols->write_index(cf, ciX.index, c.first);
1495 _fcontrols->write_index(cf, ciY.index, c.second);
1497 signal_repaint();
1501 void wxeditor_movie::_moviepanel::do_scroll_to_frame()
1503 uint64_t frame;
1504 try {
1505 std::string text = pick_text(m, "Frame", (stringfmt() << "Enter frame to scroll to:").str(), "");
1506 frame = parse_value<uint64_t>(text);
1507 } catch(canceled_exception& e) {
1508 return;
1509 } catch(std::exception& e) {
1510 wxMessageBox(wxT("Invalid value"), _T("Error"), wxICON_EXCLAMATION | wxOK, m);
1511 return;
1513 uint64_t wouldbe = 0;
1514 uint64_t low = 0;
1515 uint64_t high = max_subframe;
1516 while(low < high) {
1517 wouldbe = (low + high) / 2;
1518 if(subframe_to_frame[wouldbe] < frame)
1519 low = wouldbe;
1520 else if(subframe_to_frame[wouldbe] > frame)
1521 high = wouldbe;
1522 else
1523 break;
1525 while(wouldbe > 1 && subframe_to_frame[wouldbe - 1] == frame)
1526 wouldbe--;
1527 moviepos = wouldbe;
1528 signal_repaint();
1531 void wxeditor_movie::_moviepanel::do_scroll_to_current_frame()
1533 moviepos = cached_cffs;
1534 signal_repaint();
1537 void wxeditor_movie::_moviepanel::on_popup_menu(wxCommandEvent& e)
1539 wxMenuItem* tmpitem;
1540 int id = e.GetId();
1542 unsigned port = 0;
1543 unsigned controller = 0;
1544 for(auto i : fcontrols.get_controlinfo())
1545 if(i.index == press_index) {
1546 port = i.port;
1547 controller = i.controller;
1550 switch(id) {
1551 case wxID_TOGGLE:
1552 do_toggle_buttons(press_index, rpress_line, press_line, false);
1553 return;
1554 case wxID_CHANGE:
1555 do_alter_axis(press_index, rpress_line, press_line);
1556 return;
1557 case wxID_CLEAR:
1558 do_toggle_buttons(press_index, rpress_line, press_line, true);
1559 return;
1560 case wxID_SWEEP:
1561 do_sweep_axis(press_index, rpress_line, press_line);
1562 return;
1563 case wxID_APPEND_FRAME:
1564 do_append_frames(1);
1565 return;
1566 case wxID_APPEND_FRAMES:
1567 do_append_frames();
1568 return;
1569 case wxID_INSERT_AFTER:
1570 do_insert_frame_after(press_line, false);
1571 return;
1572 case wxID_INSERT_AFTER_MULTIPLE:
1573 do_insert_frame_after(press_line, true);
1574 return;
1575 case wxID_DELETE_FRAME:
1576 do_delete_frame(press_line, rpress_line, true);
1577 return;
1578 case wxID_DELETE_SUBFRAME:
1579 do_delete_frame(press_line, rpress_line, false);
1580 return;
1581 case wxID_TRUNCATE:
1582 do_truncate(press_line);
1583 return;
1584 case wxID_RUN_TO_FRAME:
1585 do_set_stop_at_frame();
1586 return;
1587 case wxID_SCROLL_FRAME:
1588 do_scroll_to_frame();
1589 return;
1590 case wxID_SCROLL_CURRENT_FRAME:
1591 do_scroll_to_current_frame();
1592 return;
1593 case wxID_POSITION_LOCK:
1594 if(!current_popup)
1595 return;
1596 tmpitem = current_popup->FindItem(wxID_POSITION_LOCK);
1597 position_locked = tmpitem->IsChecked();
1598 return;
1599 case wxID_CHANGE_LINECOUNT:
1600 try {
1601 std::string text = pick_text(m, "Set number of lines", "Set number of lines visible:",
1602 (stringfmt() << lines_to_display).str());
1603 unsigned tmp = parse_value<unsigned>(text);
1604 if(tmp < 1 || tmp > 255)
1605 throw std::runtime_error("Value out of range");
1606 lines_to_display = tmp;
1607 m->get_scroll()->set_page_size(lines_to_display);
1608 } catch(canceled_exception& e) {
1609 return;
1610 } catch(std::exception& e) {
1611 wxMessageBox(wxT("Invalid value"), _T("Error"), wxICON_EXCLAMATION | wxOK, m);
1612 return;
1614 signal_repaint();
1615 return;
1616 case wxID_COPY_FRAMES:
1617 if(press_index == std::numeric_limits<unsigned>::max())
1618 do_copy(rpress_line, press_line);
1619 else
1620 do_copy(rpress_line, press_line, port, controller);
1621 return;
1622 case wxID_CUT_FRAMES:
1623 if(press_index == std::numeric_limits<unsigned>::max())
1624 do_cut(rpress_line, press_line);
1625 else
1626 do_cut(rpress_line, press_line, port, controller);
1627 return;
1628 case wxID_PASTE_FRAMES:
1629 if(press_index == std::numeric_limits<unsigned>::max() || clipboard_get_data_type() == 1)
1630 do_paste(press_line, false);
1631 else
1632 do_paste(press_line, port, controller, false);
1633 return;
1634 case wxID_PASTE_APPEND:
1635 if(press_index == std::numeric_limits<unsigned>::max() || clipboard_get_data_type() == 1)
1636 do_paste(press_line, true);
1637 else
1638 do_paste(press_line, port, controller, true);
1639 return;
1640 case wxID_INSERT_CONTROLLER_AFTER:
1641 if(press_index == std::numeric_limits<unsigned>::max())
1643 else
1644 do_insert_controller(press_line, port, controller);
1645 return;
1646 case wxID_DELETE_CONTROLLER_SUBFRAMES:
1647 if(press_index == std::numeric_limits<unsigned>::max())
1649 else
1650 do_delete_controller(press_line, rpress_line, port, controller);
1651 return;
1652 case wxID_MBRANCH_NEW:
1653 try {
1654 std::string newname;
1655 std::string oldname;
1656 lsnes_instance.iqueue.run([&oldname]() { oldname = lsnes_instance.mbranch.get(); });
1657 newname = pick_text(this, "Enter new branch name", "Enter name for a new branch (to fork "
1658 "from " + lsnes_instance.mbranch.name(oldname) + "):", "", false);
1659 lsnes_instance.iqueue.run_async([this, oldname, newname] {
1660 lsnes_instance.mbranch._new(newname, oldname);
1661 }, [this](std::exception& e) {
1662 show_exception(this, "Error creating branch", "Can't create branch", e);
1664 } catch(canceled_exception& e) {
1666 return;
1667 case wxID_MBRANCH_IMPORT:
1668 try {
1669 int mode;
1670 std::string filename;
1671 std::string branch;
1672 std::string dbranch;
1673 auto g = choose_file_load(this, "Choose file to import", lsnes_instance.project.moviepath(),
1674 exp_imp_type());
1675 filename = g.first;
1676 mode = g.second;
1677 if(mode == MBRANCH_IMPORT_MOVIE) {
1678 std::set<std::string> brlist;
1679 try {
1680 lsnes_instance.iqueue.run([this, filename, &brlist]() {
1681 brlist = lsnes_instance.mbranch._movie_branches(filename);
1683 } catch(std::exception& e) {
1684 show_exception(this, "Can't get branches in movie", "", e);
1685 return;
1687 if(brlist.size() == 0) {
1688 show_message_ok(this, "No branches in movie file",
1689 "Can't import movie file as it has no branches", wxICON_EXCLAMATION);
1690 return;
1691 } else if(brlist.size() == 1) {
1692 branch = *brlist.begin();
1693 } else {
1694 std::vector<std::string> choices(brlist.begin(), brlist.end());
1695 branch = pick_among(this, "Select branch to import",
1696 "Select branch to import", choices, 0);
1698 //Import from movie.
1700 dbranch = pick_text(this, "Enter new branch name", "Enter name for an imported branch:",
1701 branch, false);
1702 lsnes_instance.iqueue.run_async([this, filename, branch, dbranch, mode]() {
1703 lsnes_instance.mbranch.import_branch(filename, branch, dbranch, mode);
1704 }, [this](std::exception& e) {
1705 show_exception(this, "Can't import branch", "", e);
1707 } catch(canceled_exception& e) {
1709 return;
1710 case wxID_MBRANCH_EXPORT:
1711 try {
1712 int mode;
1713 std::string file;
1714 auto g = choose_file_save(this, "Choose file to export", lsnes_instance.project.moviepath(),
1715 exp_imp_type());
1716 file = g.first;
1717 mode = g.second;
1718 lsnes_instance.iqueue.run_async([this, file, mode]() {
1719 std::string bname = lsnes_instance.mbranch.get();
1720 lsnes_instance.mbranch.export_branch(file, bname, mode == MBRANCH_IMPORT_BINARY);
1721 }, [this](std::exception& e) {
1722 show_exception(this, "Can't export branch", "", e);
1724 } catch(canceled_exception& e) {
1726 return;
1727 case wxID_MBRANCH_RENAME:
1728 try {
1729 std::string newname;
1730 std::string oldname;
1731 std::set<std::string> list;
1732 lsnes_instance.iqueue.run([&list]() { list = lsnes_instance.mbranch.enumerate(); });
1733 std::vector<std::string> choices(list.begin(), list.end());
1734 oldname = pick_among(this, "Select branch to rename", "Select branch to rename",
1735 choices, 0);
1736 newname = pick_text(this, "Enter new branch name", "Enter name for a new branch (to rename "
1737 "'" + lsnes_instance.mbranch.name(oldname) + "'):", oldname, false);
1738 lsnes_instance.iqueue.run_async([this, oldname, newname] {
1739 lsnes_instance.mbranch.rename(oldname, newname);
1740 }, [this](std::exception& e) {
1741 show_exception(this, "Error renaming branch", "Can't rename branch", e);
1743 } catch(canceled_exception& e) {
1745 return;
1746 case wxID_MBRANCH_DELETE:
1747 try {
1748 std::string oldname;
1749 std::set<std::string> list;
1750 lsnes_instance.iqueue.run([&list]() { list = lsnes_instance.mbranch.enumerate(); });
1751 std::vector<std::string> choices(list.begin(), list.end());
1752 oldname = pick_among(this, "Select branch to delete", "Select branch to delete",
1753 choices, 0);
1754 lsnes_instance.iqueue.run_async([this, oldname] {
1755 lsnes_instance.mbranch._delete(oldname);
1756 }, [this](std::exception& e) {
1757 show_exception(this, "Error deleting branch", "Can't delete branch", e);
1759 } catch(canceled_exception& e) {
1761 return;
1763 if(id >= wxID_MBRANCH_FIRST && id <= wxID_MBRANCH_LAST) {
1764 if(!branch_names.count(id)) return;
1765 std::string name = branch_names[id];
1766 lsnes_instance.iqueue.run_async([this, name]() {
1767 lsnes_instance.mbranch.set(name);
1768 }, [this](std::exception& e) {
1769 show_exception(this, "Error changing branch", "Can't change branch", e);
1774 uint64_t wxeditor_movie::_moviepanel::first_editable(unsigned index)
1776 uint64_t cffs = cached_cffs;
1777 if(!subframe_to_frame.count(cffs))
1778 return cffs;
1779 uint64_t f = subframe_to_frame[cffs];
1780 pollcounter_vector& pv = lsnes_instance.mlogic.get_movie().get_pollcounters();
1781 uint32_t pc = fcontrols.read_pollcount(pv, index);
1782 for(uint32_t i = 1; i < pc; i++)
1783 if(!subframe_to_frame.count(cffs + i) || subframe_to_frame[cffs + i] > f)
1784 return cffs + i;
1785 return cffs + pc;
1788 uint64_t wxeditor_movie::_moviepanel::first_nextframe()
1790 uint64_t base = first_editable(0);
1791 if(!subframe_to_frame.count(cached_cffs))
1792 return cached_cffs;
1793 uint64_t f = subframe_to_frame[cached_cffs];
1794 for(uint32_t i = 0;; i++)
1795 if(!subframe_to_frame.count(base + i) || subframe_to_frame[base + i] > f)
1796 return base + i;
1799 void wxeditor_movie::_moviepanel::on_mouse1(unsigned x, unsigned y, bool polarity) {}
1800 void wxeditor_movie::_moviepanel::on_mouse2(unsigned x, unsigned y, bool polarity)
1802 if(polarity) {
1803 //Pressing mouse, just record line it was pressed on.
1804 rpress_line = spos + y - 3;
1805 press_x = x;
1806 pressed = true;
1807 signal_repaint();
1808 return;
1810 //Releasing mouse, open popup menu.
1811 pressed = false;
1812 unsigned off = divcnt + 1;
1813 press_x = x;
1814 if(y < 3) {
1815 signal_repaint();
1816 return;
1818 press_line = spos + y - 3;
1819 wxMenu menu;
1820 current_popup = &menu;
1821 menu.Connect(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(wxeditor_movie::_moviepanel::on_popup_menu),
1822 NULL, this);
1824 //Find what controller is the click on.
1825 bool clicked_button = false;
1826 control_info clicked;
1827 std::string controller_name;
1828 if(press_x < off) {
1829 clicked_button = false;
1830 press_index = std::numeric_limits<unsigned>::max();
1831 } else {
1832 for(auto i : fcontrols.get_controlinfo())
1833 if(press_x >= i.position_left + off && press_x < i.position_left + i.reserved + off) {
1834 if(i.type == 0 || i.type == 1) {
1835 clicked_button = true;
1836 clicked = i;
1837 controller_name = (stringfmt() << "controller " << i.port << "-"
1838 << (i.controller + 1)).str();
1839 press_index = i.index;
1844 //Find first editable frame, controllerframe and buttonframe.
1845 bool not_editable = !lsnes_instance.mlogic.get_movie().readonly_mode();
1846 uint64_t eframe_low = first_editable(0);
1847 uint64_t ebutton_low = clicked_button ? first_editable(clicked.index) : std::numeric_limits<uint64_t>::max();
1848 uint64_t econtroller_low = ebutton_low;
1849 for(auto i : fcontrols.get_controlinfo())
1850 if(i.port == clicked.port && i.controller == clicked.controller && (i.type == 0 || i.type == 1))
1851 econtroller_low = max(econtroller_low, first_editable(i.index));
1853 bool click_zero = (clicked_button && !clicked.port && !clicked.controller);
1854 bool enable_append_frame = !not_editable;
1855 bool enable_toggle_button = false;
1856 bool enable_change_axis = false;
1857 bool enable_sweep_axis = false;
1858 bool enable_insert_frame = false;
1859 bool enable_insert_controller = false;
1860 bool enable_delete_frame = false;
1861 bool enable_delete_subframe = false;
1862 bool enable_delete_controller_subframe = false;
1863 bool enable_truncate_movie = false;
1864 bool enable_cut_frame = false;
1865 bool enable_copy_frame = false;
1866 bool enable_paste_frame = false;
1867 bool enable_paste_append = false;
1868 std::string copy_title;
1869 std::string paste_title;
1871 //Toggle button is enabled if clicked on button and either end is in valid range.
1872 enable_toggle_button = (!not_editable && clicked_button && clicked.type == 0 && ((press_line >= ebutton_low &&
1873 press_line < linecount) || (rpress_line >= ebutton_low && rpress_line < linecount)));
1874 //Change axis is enabled in similar conditions, except if type is axis.
1875 enable_change_axis = (!not_editable && clicked_button && clicked.type == 1 && ((press_line >= ebutton_low &&
1876 press_line < linecount) || (rpress_line >= ebutton_low && rpress_line < linecount)));
1877 //Sweep axis is enabled if change axis is enabled and lines don't match.
1878 enable_sweep_axis = (enable_change_axis && press_line != rpress_line);
1879 //Insert frame is enabled if this frame is completely editable and press and release lines match.
1880 enable_insert_frame = (!not_editable && press_line + 1 >= eframe_low && press_line < linecount &&
1881 press_line == rpress_line);
1882 //Insert controller frame is enabled if controller is completely editable and lines match.
1883 enable_insert_controller = (!not_editable && clicked_button && press_line >= econtroller_low &&
1884 press_line < linecount && press_line == rpress_line);
1885 enable_insert_controller = enable_insert_controller && (clicked.port || clicked.controller);
1886 //Delete frame is enabled if range is completely editable (relative to next-frame).
1887 enable_delete_frame = (!not_editable && press_line >= first_nextframe() && press_line < linecount &&
1888 rpress_line >= first_nextframe() && rpress_line < linecount);
1889 //Delete subframe is enabled if range is completely editable.
1890 enable_delete_subframe = (!not_editable && press_line >= eframe_low && press_line < linecount &&
1891 rpress_line >= eframe_low && rpress_line < linecount);
1892 //Delete controller subframe is enabled if range is completely controller-editable.
1893 enable_delete_controller_subframe = (!not_editable && clicked_button && press_line >= econtroller_low &&
1894 press_line < linecount && rpress_line >= econtroller_low && rpress_line < linecount);
1895 enable_delete_controller_subframe = enable_delete_controller_subframe && (clicked.port || clicked.controller);
1896 //Truncate movie is enabled if lines match and is completely editable.
1897 enable_truncate_movie = (!not_editable && press_line == rpress_line && press_line >= eframe_low &&
1898 press_line < linecount);
1899 //Cut frames is enabled if range is editable (possibly controller-editable).
1900 if(clicked_button)
1901 enable_cut_frame = (!not_editable && press_line >= econtroller_low && press_line < linecount
1902 && rpress_line >= econtroller_low && rpress_line < linecount && !click_zero);
1903 else
1904 enable_cut_frame = (!not_editable && press_line >= eframe_low && press_line < linecount
1905 && rpress_line >= eframe_low && rpress_line < linecount);
1906 if(clicked_button && clipboard_get_data_type() == 0) {
1907 enable_paste_append = (!not_editable && linecount >= eframe_low);
1908 enable_paste_frame = (!not_editable && press_line >= econtroller_low && press_line < linecount
1909 && rpress_line >= econtroller_low && rpress_line < linecount && !click_zero);
1910 } else if(clipboard_get_data_type() == 1) {
1911 enable_paste_append = (!not_editable && linecount >= econtroller_low);
1912 enable_paste_frame = (!not_editable && press_line >= eframe_low && press_line < linecount
1913 && rpress_line >= eframe_low && rpress_line < linecount);
1915 //Copy frames is enabled if range exists.
1916 enable_copy_frame = (press_line < linecount && rpress_line < linecount);
1917 copy_title = (clicked_button ? controller_name : "frames");
1918 paste_title = ((clipboard_get_data_type() == 0) ? copy_title : "frames");
1920 if(clipboard_get_data_type() == 0 && click_zero) enable_paste_append = enable_paste_frame = false;
1922 if(enable_toggle_button)
1923 menu.Append(wxID_TOGGLE, towxstring(U"Toggle " + clicked.title));
1924 if(enable_change_axis)
1925 menu.Append(wxID_CHANGE, towxstring(U"Change " + clicked.title));
1926 if(enable_sweep_axis)
1927 menu.Append(wxID_SWEEP, towxstring(U"Sweep " + clicked.title));
1928 if(enable_toggle_button || enable_change_axis)
1929 menu.Append(wxID_CLEAR, towxstring(U"Clear " + clicked.title));
1930 if(enable_toggle_button || enable_change_axis || enable_sweep_axis)
1931 menu.AppendSeparator();
1932 menu.Append(wxID_INSERT_AFTER, wxT("Insert frame after"))->Enable(enable_insert_frame);
1933 menu.Append(wxID_INSERT_AFTER_MULTIPLE, wxT("Insert frames after"))->Enable(enable_insert_frame);
1934 menu.Append(wxID_INSERT_CONTROLLER_AFTER, wxT("Insert controller frame"))
1935 ->Enable(enable_insert_controller);
1936 menu.Append(wxID_APPEND_FRAME, wxT("Append frame"))->Enable(enable_append_frame);
1937 menu.Append(wxID_APPEND_FRAMES, wxT("Append frames..."))->Enable(enable_append_frame);
1938 menu.AppendSeparator();
1939 menu.Append(wxID_DELETE_FRAME, wxT("Delete frame(s)"))->Enable(enable_delete_frame);
1940 menu.Append(wxID_DELETE_SUBFRAME, wxT("Delete subframe(s)"))->Enable(enable_delete_subframe);
1941 menu.Append(wxID_DELETE_CONTROLLER_SUBFRAMES, wxT("Delete controller subframes(s)"))
1942 ->Enable(enable_delete_controller_subframe);
1943 menu.AppendSeparator();
1944 menu.Append(wxID_TRUNCATE, wxT("Truncate movie"))->Enable(enable_truncate_movie);
1945 menu.AppendSeparator();
1946 menu.Append(wxID_CUT_FRAMES, towxstring("Cut " + copy_title))->Enable(enable_cut_frame);
1947 menu.Append(wxID_COPY_FRAMES, towxstring("Copy " + copy_title))->Enable(enable_copy_frame);
1948 menu.Append(wxID_PASTE_FRAMES, towxstring("Paste " + paste_title))->Enable(enable_paste_frame);
1949 menu.Append(wxID_PASTE_APPEND, towxstring("Paste append " + paste_title))->Enable(enable_paste_append);
1950 menu.AppendSeparator();
1951 menu.Append(wxID_SCROLL_FRAME, wxT("Scroll to frame..."));
1952 menu.Append(wxID_SCROLL_CURRENT_FRAME, wxT("Scroll to current frame"));
1953 menu.Append(wxID_RUN_TO_FRAME, wxT("Run to frame..."));
1954 menu.Append(wxID_CHANGE_LINECOUNT, wxT("Change number of lines visible"));
1955 menu.AppendCheckItem(wxID_POSITION_LOCK, wxT("Lock scroll to playback"))->Check(position_locked);
1956 menu.AppendSeparator();
1958 wxMenu* branches_submenu = new wxMenu();
1959 branches_submenu->Append(wxID_MBRANCH_NEW, wxT("New branch..."));
1960 branches_submenu->Append(wxID_MBRANCH_IMPORT, wxT("Import branch..."));
1961 branches_submenu->Append(wxID_MBRANCH_EXPORT, wxT("Export branch..."));
1962 branches_submenu->Append(wxID_MBRANCH_RENAME, wxT("Rename branch..."));
1963 branches_submenu->Append(wxID_MBRANCH_DELETE, wxT("Delete branch..."));
1964 branches_submenu->AppendSeparator();
1965 std::set<std::string> list;
1966 std::string current;
1967 bool ro;
1968 lsnes_instance.iqueue.run([&list, &current, &ro]() {
1969 list = lsnes_instance.mbranch.enumerate();
1970 current = lsnes_instance.mbranch.get();
1971 ro = lsnes_instance.mlogic.get_movie().readonly_mode();
1973 int ass_id = wxID_MBRANCH_FIRST;
1974 for(auto i : list) {
1975 bool selected = (i == current);
1976 wxMenuItem* it;
1977 it = branches_submenu->AppendCheckItem(ass_id, towxstring(lsnes_instance.mbranch.name(i)));
1978 branch_names[ass_id++] = i;
1979 if(selected) it->Check(selected);
1980 it->Enable(ro);
1982 menu.AppendSubMenu(branches_submenu, wxT("Branches"));
1983 menu.Connect(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(wxeditor_movie::_moviepanel::on_popup_menu),
1984 NULL, this);
1985 branches_submenu->Connect(wxEVT_COMMAND_MENU_SELECTED,
1986 wxCommandEventHandler(wxeditor_movie::_moviepanel::on_popup_menu), NULL, this);
1987 PopupMenu(&menu);
1988 //delete branches_submenu;
1989 signal_repaint();
1992 int wxeditor_movie::_moviepanel::get_lines()
1994 controller_frame_vector& fv = *lsnes_instance.mlogic.get_mfile().input;
1995 return fv.size();
1998 void wxeditor_movie::_moviepanel::signal_repaint()
2000 if(requested || recursing)
2001 return;
2002 auto s = m->get_scroll();
2003 requested = true;
2004 uint32_t width, height;
2005 uint64_t lines;
2006 wxeditor_movie* m2 = m;
2007 uint64_t old_cached_cffs = cached_cffs;
2008 uint32_t prev_width, prev_height;
2009 bool done_again = false;
2010 do_again:
2011 lsnes_instance.iqueue.run([&lines, &width, &height, m2, this]() {
2012 lines = this->get_lines();
2013 if(lines < lines_to_display)
2014 this->moviepos = 0;
2015 else if(this->moviepos > lines - lines_to_display)
2016 this->moviepos = lines - lines_to_display;
2017 this->render(fb, moviepos);
2018 auto x = fb.get_characters();
2019 width = x.first;
2020 height = x.second;
2022 if(old_cached_cffs != cached_cffs && position_locked && !done_again) {
2023 moviepos = cached_cffs;
2024 done_again = true;
2025 goto do_again;
2027 prev_width = new_width;
2028 prev_height = new_height;
2029 new_width = width;
2030 new_height = height;
2031 movielines = lines;
2032 if(s) {
2033 s->set_range(lines);
2034 s->set_position(moviepos);
2036 auto size = fb.get_pixels();
2037 pixels.resize(size.first * size.second * 3);
2038 fb.render((char*)&pixels[0]);
2039 if(prev_width != new_width || prev_height != new_height) {
2040 auto cell = fb.get_cell();
2041 SetMinSize(wxSize(new_width * cell.first, (lines_to_display + 3) * cell.second));
2042 if(new_width > 0 && s)
2043 m->Fit();
2045 linecount = lines;
2046 Refresh();
2049 void wxeditor_movie::_moviepanel::on_mouse(wxMouseEvent& e)
2051 auto cell = fb.get_cell();
2052 if(e.LeftDown() && !e.ControlDown())
2053 on_mouse0(e.GetX() / cell.first, e.GetY() / cell.second, true, e.ShiftDown(), e.GetX(), e.GetY());
2054 if(e.LeftUp() && !e.ControlDown())
2055 on_mouse0(e.GetX() / cell.first, e.GetY() / cell.second, false, e.ShiftDown(), e.GetX(), e.GetY());
2056 if(e.MiddleDown())
2057 on_mouse1(e.GetX() / cell.first, e.GetY() / cell.second, true);
2058 if(e.MiddleUp())
2059 on_mouse1(e.GetX() / cell.first, e.GetY() / cell.second, false);
2060 if(e.RightDown() || (e.LeftDown() && e.ControlDown()))
2061 on_mouse2(e.GetX() / cell.first, e.GetY() / cell.second, true);
2062 if(e.RightUp() || (e.LeftUp() && e.ControlDown()))
2063 on_mouse2(e.GetX() / cell.first, e.GetY() / cell.second, false);
2064 auto s = m->get_scroll();
2065 unsigned speed = 1;
2066 if(e.ShiftDown())
2067 speed = 10;
2068 if(e.ShiftDown() && e.ControlDown())
2069 speed = 50;
2070 s->apply_wheel(e.GetWheelRotation(), e.GetWheelDelta(), speed);
2073 void wxeditor_movie::_moviepanel::on_erase(wxEraseEvent& e)
2075 //Blank.
2078 void wxeditor_movie::_moviepanel::on_paint(wxPaintEvent& e)
2080 auto size = fb.get_pixels();
2081 if(!size.first || !size.second) {
2082 wxPaintDC dc(this);
2083 dc.Clear();
2084 requested = false;
2085 return;
2087 wxPaintDC dc(this);
2088 wxBitmap bmp(wxImage(size.first, size.second, &pixels[0], true));
2089 dc.DrawBitmap(bmp, 0, 0, false);
2090 requested = false;
2093 void wxeditor_movie::_moviepanel::do_copy(uint64_t row1, uint64_t row2, unsigned port, unsigned controller)
2095 frame_controls* _fcontrols = &fcontrols;
2096 uint64_t line = row1;
2097 uint64_t line2 = row2;
2098 if(line2 < line)
2099 std::swap(line, line2);
2100 std::string copied;
2101 lsnes_instance.iqueue.run([port, controller, line, line2, _fcontrols, &copied]() {
2102 controller_frame_vector& fv = *lsnes_instance.mlogic.get_mfile().input;
2103 uint64_t vsize = fv.size();
2104 if(!vsize)
2105 return;
2106 uint64_t _line = min(line, vsize - 1);
2107 uint64_t _line2 = min(line2, vsize - 1);
2108 copied = encode_lines(*_fcontrols, fv, _line, _line2 + 1, port, controller);
2110 copy_to_clipboard(copied);
2113 void wxeditor_movie::_moviepanel::do_copy(uint64_t row1, uint64_t row2)
2115 uint64_t line = row1;
2116 uint64_t line2 = row2;
2117 if(line2 < line)
2118 std::swap(line, line2);
2119 std::string copied;
2120 lsnes_instance.iqueue.run([line, line2, &copied]() {
2121 controller_frame_vector& fv = *lsnes_instance.mlogic.get_mfile().input;
2122 uint64_t vsize = fv.size();
2123 if(!vsize)
2124 return;
2125 uint64_t _line = min(line, vsize - 1);
2126 uint64_t _line2 = min(line2, vsize - 1);
2127 copied = encode_lines(fv, _line, _line2 + 1);
2129 copy_to_clipboard(copied);
2132 void wxeditor_movie::_moviepanel::do_cut(uint64_t row1, uint64_t row2, unsigned port, unsigned controller)
2134 do_copy(row1, row2, port, controller);
2135 do_delete_controller(row1, row2, port, controller);
2138 void wxeditor_movie::_moviepanel::do_cut(uint64_t row1, uint64_t row2)
2140 do_copy(row1, row2);
2141 do_delete_frame(row1, row2, false);
2144 void wxeditor_movie::_moviepanel::do_paste(uint64_t row, bool append)
2146 frame_controls* _fcontrols = &fcontrols;
2147 recursing = true;
2148 uint64_t _gapstart = row;
2149 std::string cliptext = copy_from_clipboard();
2150 lsnes_instance.iqueue.run([_fcontrols, &cliptext, _gapstart, append]() {
2151 //Insert enough lines for the pasted content.
2152 uint64_t gapstart = _gapstart;
2153 if(!lsnes_instance.mlogic.get_movie().readonly_mode())
2154 return;
2155 uint64_t gaplen = 0;
2156 int64_t newframes = 0;
2158 std::istringstream y(cliptext);
2159 std::string z;
2160 if(!std::getline(y, z))
2161 return;
2162 istrip_CR(z);
2163 if(z != "lsnes-moviedata-whole")
2164 return;
2165 while(std::getline(y, z))
2166 gaplen++;
2168 controller_frame_vector& fv = *lsnes_instance.mlogic.get_mfile().input;
2169 uint64_t vsize = fv.size();
2170 if(gapstart < real_first_editable(*_fcontrols, 0))
2171 return;
2172 if(gapstart > vsize)
2173 return;
2174 controller_frame_vector::notify_freeze freeze(fv);
2175 if(append) gapstart = vsize;
2176 for(uint64_t i = 0; i < gaplen; i++)
2177 fv.append(fv.blank_frame(false));
2178 for(uint64_t i = vsize - 1; i >= gapstart && i <= vsize; i--)
2179 fv[i + gaplen] = fv[i];
2180 //Write the pasted frames.
2182 std::istringstream y(cliptext);
2183 std::string z;
2184 std::getline(y, z);
2185 uint64_t idx = gapstart;
2186 while(std::getline(y, z)) {
2187 fv[idx++].deserialize(z.c_str());
2188 if(fv[idx - 1].sync())
2189 newframes++;
2193 recursing = false;
2194 signal_repaint();
2197 void wxeditor_movie::_moviepanel::do_paste(uint64_t row, unsigned port, unsigned controller, bool append)
2199 if(!port && !controller)
2200 return;
2201 frame_controls* _fcontrols = &fcontrols;
2202 auto iset = controller_index_set(fcontrols, port, controller);
2203 recursing = true;
2204 uint64_t _gapstart = row;
2205 std::string cliptext = copy_from_clipboard();
2206 lsnes_instance.iqueue.run([_fcontrols, iset, &cliptext, _gapstart, port, controller, append]() {
2207 //Insert enough lines for the pasted content.
2208 uint64_t gapstart = _gapstart;
2209 if(!lsnes_instance.mlogic.get_movie().readonly_mode())
2210 return;
2211 uint64_t gaplen = 0;
2212 int64_t newframes = 0;
2214 std::istringstream y(cliptext);
2215 std::string z;
2216 if(!std::getline(y, z))
2217 return;
2218 istrip_CR(z);
2219 if(z != "lsnes-moviedata-controller")
2220 return;
2221 while(std::getline(y, z)) {
2222 gaplen++;
2223 newframes++;
2226 controller_frame_vector& fv = *lsnes_instance.mlogic.get_mfile().input;
2227 uint64_t vsize = fv.size();
2228 if(gapstart < real_first_editable(*_fcontrols, iset))
2229 return;
2230 if(gapstart > vsize)
2231 return;
2232 controller_frame_vector::notify_freeze freeze(fv);
2233 if(append) gapstart = vsize;
2234 for(uint64_t i = 0; i < gaplen; i++)
2235 fv.append(fv.blank_frame(true));
2236 move_index_set(*_fcontrols, fv, gapstart, gapstart + gaplen, vsize - gapstart, iset);
2237 //Write the pasted frames.
2239 std::istringstream y(cliptext);
2240 std::string z;
2241 std::getline(y, z);
2242 uint64_t idx = gapstart;
2243 while(std::getline(y, z)) {
2244 controller_frame f = fv[idx++];
2245 decode_line(*_fcontrols, f, z, port, controller);
2249 recursing = false;
2250 signal_repaint();
2253 void wxeditor_movie::_moviepanel::do_insert_controller(uint64_t row, unsigned port, unsigned controller)
2255 if(!port && !controller)
2256 return;
2257 frame_controls* _fcontrols = &fcontrols;
2258 auto iset = controller_index_set(fcontrols, port, controller);
2259 recursing = true;
2260 uint64_t gapstart = row;
2261 lsnes_instance.iqueue.run([_fcontrols, iset, gapstart, port, controller]() {
2262 //Insert enough lines for the pasted content.
2263 if(!lsnes_instance.mlogic.get_movie().readonly_mode())
2264 return;
2265 controller_frame_vector& fv = *lsnes_instance.mlogic.get_mfile().input;
2266 uint64_t vsize = fv.size();
2267 if(gapstart < real_first_editable(*_fcontrols, iset))
2268 return;
2269 if(gapstart > vsize)
2270 return;
2271 fv.append(fv.blank_frame(true));
2272 move_index_set(*_fcontrols, fv, gapstart, gapstart + 1, vsize - gapstart, iset);
2273 zero_index_set(*_fcontrols, fv, gapstart, 1, iset);
2275 recursing = false;
2276 signal_repaint();
2279 void wxeditor_movie::_moviepanel::do_delete_controller(uint64_t row1, uint64_t row2, unsigned port,
2280 unsigned controller)
2282 if(!port && !controller)
2283 return;
2284 frame_controls* _fcontrols = &fcontrols;
2285 auto iset = controller_index_set(fcontrols, port, controller);
2286 recursing = true;
2287 if(row1 > row2) std::swap(row1, row2);
2288 uint64_t gapstart = row1;
2289 uint64_t gaplen = row2 - row1 + 1;
2290 lsnes_instance.iqueue.run([_fcontrols, iset, gapstart, gaplen, port, controller]() {
2291 //Insert enough lines for the pasted content.
2292 if(!lsnes_instance.mlogic.get_movie().readonly_mode())
2293 return;
2294 controller_frame_vector& fv = *lsnes_instance.mlogic.get_mfile().input;
2295 uint64_t vsize = fv.size();
2296 if(gapstart < real_first_editable(*_fcontrols, iset))
2297 return;
2298 if(gapstart > vsize)
2299 return;
2300 move_index_set(*_fcontrols, fv, gapstart + gaplen, gapstart, vsize - gapstart - gaplen, iset);
2301 zero_index_set(*_fcontrols, fv, vsize - gaplen, gaplen, iset);
2303 recursing = false;
2304 signal_repaint();
2308 wxeditor_movie::wxeditor_movie(wxWindow* parent)
2309 : wxDialog(parent, wxID_ANY, wxT("lsnes: Edit movie"), wxDefaultPosition, wxSize(-1, -1))
2311 closing = false;
2312 Centre();
2313 wxFlexGridSizer* top_s = new wxFlexGridSizer(2, 1, 0, 0);
2314 SetSizer(top_s);
2316 wxBoxSizer* panel_s = new wxBoxSizer(wxHORIZONTAL);
2317 moviescroll = NULL;
2318 panel_s->Add(moviepanel = new _moviepanel(this), 1, wxGROW);
2319 panel_s->Add(moviescroll = new scroll_bar(this, wxID_ANY, true), 0, wxGROW);
2320 top_s->Add(panel_s, 1, wxGROW);
2322 moviescroll->set_page_size(lines_to_display);
2323 moviescroll->set_handler([this](scroll_bar& s) {
2324 this->moviepanel->moviepos = s.get_position();
2325 this->moviepanel->signal_repaint();
2327 moviepanel->Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(wxeditor_movie::on_keyboard_down), NULL, this);
2328 moviepanel->Connect(wxEVT_KEY_UP, wxKeyEventHandler(wxeditor_movie::on_keyboard_up), NULL, this);
2330 wxBoxSizer* pbutton_s = new wxBoxSizer(wxHORIZONTAL);
2331 pbutton_s->AddStretchSpacer();
2332 pbutton_s->Add(closebutton = new wxButton(this, wxID_OK, wxT("Close")), 0, wxGROW);
2333 closebutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
2334 wxCommandEventHandler(wxeditor_movie::on_close), NULL, this);
2335 top_s->Add(pbutton_s, 0, wxGROW);
2337 moviepanel->SetFocus();
2338 moviescroll->Connect(wxEVT_SET_FOCUS, wxFocusEventHandler(wxeditor_movie::on_focus_wrong), NULL, this);
2339 closebutton->Connect(wxEVT_SET_FOCUS, wxFocusEventHandler(wxeditor_movie::on_focus_wrong), NULL, this);
2340 Connect(wxEVT_SET_FOCUS, wxFocusEventHandler(wxeditor_movie::on_focus_wrong), NULL, this);
2342 panel_s->SetSizeHints(this);
2343 pbutton_s->SetSizeHints(this);
2344 top_s->SetSizeHints(this);
2345 Connect(wxEVT_CLOSE_WINDOW, wxCloseEventHandler(wxeditor_movie::on_wclose));
2346 Fit();
2348 moviepanel->signal_repaint();
2351 bool wxeditor_movie::ShouldPreventAppExit() const { return false; }
2353 void wxeditor_movie::on_close(wxCommandEvent& e)
2355 movieeditor_open = NULL;
2356 Destroy();
2357 closing = true;
2360 void wxeditor_movie::on_wclose(wxCloseEvent& e)
2362 bool wasc = closing;
2363 closing = true;
2364 movieeditor_open = NULL;
2365 if(!wasc)
2366 Destroy();
2369 void wxeditor_movie::update()
2371 moviepanel->signal_repaint();
2374 scroll_bar* wxeditor_movie::get_scroll()
2376 return moviescroll;
2379 void wxeditor_movie::on_focus_wrong(wxFocusEvent& e)
2381 moviepanel->SetFocus();
2384 void wxeditor_movie_display(wxWindow* parent)
2386 if(movieeditor_open)
2387 return;
2388 wxeditor_movie* v = new wxeditor_movie(parent);
2389 v->Show();
2390 movieeditor_open = v;
2393 void wxeditor_movie::on_keyboard_down(wxKeyEvent& e)
2395 handle_wx_keyboard(e, true);
2398 void wxeditor_movie::on_keyboard_up(wxKeyEvent& e)
2400 handle_wx_keyboard(e, false);
2403 void wxeditor_movie_update()
2405 if(movieeditor_open)
2406 movieeditor_open->update();