Fix scaling-related crashes
[lsnes.git] / src / platform / wxwidgets / editor-movie.cpp
blob9e964dd193c44739d5d7eb9ef2affcc2418e3302
1 #include <wx/wx.h>
2 #include <wx/event.h>
3 #include <wx/control.h>
4 #include <wx/combobox.h>
5 #include <wx/clipbrd.h>
7 #include "core/framebuffer.hpp"
8 #include "core/instance.hpp"
9 #include "core/instance-map.hpp"
10 #include "core/moviedata.hpp"
11 #include "core/dispatch.hpp"
12 #include "core/window.hpp"
13 #include "core/ui-services.hpp"
15 #include "interface/controller.hpp"
16 #include "core/mainloop.hpp"
17 #include "core/mbranch.hpp"
18 #include "core/project.hpp"
19 #include "platform/wxwidgets/loadsave.hpp"
20 #include "platform/wxwidgets/platform.hpp"
21 #include "platform/wxwidgets/scrollbar.hpp"
22 #include "platform/wxwidgets/textrender.hpp"
23 #include "library/minmax.hpp"
24 #include "library/string.hpp"
25 #include "library/utf8.hpp"
27 #include <algorithm>
28 #include <cstring>
29 #include <limits>
31 extern "C"
33 #ifndef UINT64_C
34 #define UINT64_C(val) val##ULL
35 #endif
36 #include <libswscale/swscale.h>
39 enum
41 wxID_TOGGLE = wxID_HIGHEST + 1,
42 wxID_CHANGE,
43 wxID_SWEEP,
44 wxID_APPEND_FRAME,
45 wxID_CHANGE_LINECOUNT,
46 wxID_INSERT_AFTER,
47 wxID_INSERT_AFTER_MULTIPLE,
48 wxID_DELETE_FRAME,
49 wxID_DELETE_SUBFRAME,
50 wxID_POSITION_LOCK,
51 wxID_RUN_TO_FRAME,
52 wxID_APPEND_FRAMES,
53 wxID_TRUNCATE,
54 wxID_SCROLL_FRAME,
55 wxID_SCROLL_CURRENT_FRAME,
56 wxID_COPY_FRAMES,
57 wxID_CUT_FRAMES,
58 wxID_PASTE_FRAMES,
59 wxID_PASTE_APPEND,
60 wxID_INSERT_CONTROLLER_AFTER,
61 wxID_DELETE_CONTROLLER_SUBFRAMES,
62 wxID_MBRANCH_NEW,
63 wxID_MBRANCH_IMPORT,
64 wxID_MBRANCH_EXPORT,
65 wxID_MBRANCH_RENAME,
66 wxID_MBRANCH_DELETE,
67 wxID_MBRANCH_FIRST,
68 wxID_MBRANCH_LAST = wxID_MBRANCH_FIRST + 1024
71 namespace
73 unsigned lines_to_display = 28;
74 uint64_t divs[] = {1000000, 100000, 10000, 1000, 100, 10, 1};
75 uint64_t divsl[] = {1000000, 100000, 10000, 1000, 100, 10, 0};
76 const unsigned divcnt = sizeof(divs)/sizeof(divs[0]);
78 class exp_imp_type
80 public:
81 typedef std::pair<std::string, int> returntype;
82 exp_imp_type()
85 filedialog_input_params input(bool save) const
87 filedialog_input_params ip;
88 ip.types.push_back(filedialog_type_entry("Input tracks (text)", "*.lstt", "lstt"));
89 ip.types.push_back(filedialog_type_entry("Input tracks (binary)", "*.lstb", "lstb"));
90 if(!save)
91 ip.types.push_back(filedialog_type_entry("Movie files", "*.lsmv", "lsmv"));
92 ip.default_type = 1;
93 return ip;
95 std::pair<std::string, int> output(const filedialog_output_params& p, bool save) const
97 int m;
98 switch(p.typechoice) {
99 case 0: m = MBRANCH_IMPORT_TEXT; break;
100 case 1: m = MBRANCH_IMPORT_BINARY; break;
101 case 2: m = MBRANCH_IMPORT_MOVIE; break;
103 return std::make_pair(p.path, m);
105 private:
109 struct control_info
111 unsigned position_left;
112 unsigned reserved; //Must be at least 6 for axes.
113 unsigned index; //Index in poll vector.
114 int type; //-2 => Port, -1 => Fixed, 0 => Button, 1 => axis.
115 char32_t ch;
116 std::u32string title;
117 unsigned port;
118 unsigned controller;
119 portctrl::button::_type axistype;
120 int rmin;
121 int rmax;
122 static control_info portinfo(unsigned& p, unsigned port, unsigned controller);
123 static control_info fixedinfo(unsigned& p, const std::u32string& str);
124 static control_info buttoninfo(unsigned& p, char32_t character, const std::u32string& title, unsigned idx,
125 unsigned port, unsigned controller);
126 static control_info axisinfo(unsigned& p, const std::u32string& title, unsigned idx,
127 unsigned port, unsigned controller, portctrl::button::_type _axistype, int _rmin,
128 int _rmax);
131 control_info control_info::portinfo(unsigned& p, unsigned port, unsigned controller)
133 control_info i;
134 i.position_left = p;
135 i.reserved = (stringfmt() << port << "-" << controller).str32().length();
136 p += i.reserved;
137 i.index = 0;
138 i.type = -2;
139 i.ch = 0;
140 i.title = U"";
141 i.port = port;
142 i.controller = controller;
143 return i;
146 control_info control_info::fixedinfo(unsigned& p, const std::u32string& str)
148 control_info i;
149 i.position_left = p;
150 i.reserved = str.length();
151 p += i.reserved;
152 i.index = 0;
153 i.type = -1;
154 i.ch = 0;
155 i.title = str;
156 i.port = 0;
157 i.controller = 0;
158 return i;
161 control_info control_info::buttoninfo(unsigned& p, char32_t character, const std::u32string& title, unsigned idx,
162 unsigned port, unsigned controller)
164 control_info i;
165 i.position_left = p;
166 i.reserved = 1;
167 p += i.reserved;
168 i.index = idx;
169 i.type = 0;
170 i.ch = character;
171 i.title = title;
172 i.port = port;
173 i.controller = controller;
174 return i;
177 control_info control_info::axisinfo(unsigned& p, const std::u32string& title, unsigned idx,
178 unsigned port, unsigned controller, portctrl::button::_type _axistype, int _rmin, int _rmax)
180 control_info i;
181 i.position_left = p;
182 i.reserved = title.length();
183 if(i.reserved < 6)
184 i.reserved = 6;
185 p += i.reserved;
186 i.index = idx;
187 i.type = 1;
188 i.ch = 0;
189 i.title = title;
190 i.port = port;
191 i.controller = controller;
192 i.axistype = _axistype;
193 i.rmin = _rmin;
194 i.rmax = _rmax;
195 return i;
198 class frame_controls
200 public:
201 frame_controls();
202 void set_types(portctrl::frame& f);
203 short read_index(portctrl::frame& f, unsigned idx);
204 void write_index(portctrl::frame& f, unsigned idx, short value);
205 uint32_t read_pollcount(portctrl::counters& v, unsigned idx);
206 const std::list<control_info>& get_controlinfo() { return controlinfo; }
207 std::u32string line1() { return _line1; }
208 std::u32string line2() { return _line2; }
209 size_t width() { return _width; }
210 private:
211 size_t _width;
212 std::u32string _line1;
213 std::u32string _line2;
214 void format_lines();
215 void add_port(unsigned& c, unsigned pid, const portctrl::type& p, const portctrl::type_set& pts);
216 std::list<control_info> controlinfo;
220 frame_controls::frame_controls()
222 _width = 0;
225 void frame_controls::set_types(portctrl::frame& f)
227 unsigned nextp = 0;
228 controlinfo.clear();
229 const portctrl::type_set& pts = f.porttypes();
230 unsigned pcnt = pts.ports();
231 for(unsigned i = 0; i < pcnt; i++)
232 add_port(nextp, i, pts.port_type(i), pts);
233 format_lines();
236 void frame_controls::add_port(unsigned& c, unsigned pid, const portctrl::type& p, const portctrl::type_set& pts)
238 const portctrl::controller_set& pci = *(p.controller_info);
239 for(unsigned i = 0; i < pci.controllers.size(); i++) {
240 const portctrl::controller& pc = pci.controllers[i];
241 if(pid || i)
242 controlinfo.push_back(control_info::fixedinfo(c, U"\u2502"));
243 unsigned nextp = c;
244 controlinfo.push_back(control_info::portinfo(nextp, pid, i + 1));
245 bool last_multibyte = false;
246 for(unsigned j = 0; j < pc.buttons.size(); j++) {
247 const portctrl::button& pcb = pc.buttons[j];
248 unsigned idx = pts.triple_to_index(pid, i, j);
249 if(idx == 0xFFFFFFFFUL)
250 continue;
251 if(pcb.type == portctrl::button::TYPE_BUTTON) {
252 if(last_multibyte)
253 c++;
254 controlinfo.push_back(control_info::buttoninfo(c, pcb.symbol, utf8::to32(pcb.name),
255 idx, pid, i));
256 last_multibyte = false;
257 } else if(pcb.type == portctrl::button::TYPE_AXIS ||
258 pcb.type == portctrl::button::TYPE_RAXIS ||
259 pcb.type == portctrl::button::TYPE_TAXIS ||
260 pcb.type == portctrl::button::TYPE_LIGHTGUN) {
261 if(j)
262 c++;
263 controlinfo.push_back(control_info::axisinfo(c, utf8::to32(pcb.name), idx, pid, i,
264 pcb.type, pcb.rmin, pcb.rmax));
265 last_multibyte = true;
268 if(nextp > c)
269 c = nextp;
273 short frame_controls::read_index(portctrl::frame& f, unsigned idx)
275 if(idx == 0)
276 return f.sync() ? 1 : 0;
277 return f.axis2(idx);
280 void frame_controls::write_index(portctrl::frame& f, unsigned idx, short value)
282 if(idx == 0)
283 return f.sync(value);
284 return f.axis2(idx, value);
287 uint32_t frame_controls::read_pollcount(portctrl::counters& v, unsigned idx)
289 if(idx == 0)
290 return max(v.max_polls(), (uint32_t)1);
291 for(auto i : controlinfo)
292 if(idx == i.index && i.port == 0 && i.controller == 0)
293 return max(v.get_polls(idx), (uint32_t)(v.get_framepflag() ? 1 : 0));
294 return v.get_polls(idx);
297 void frame_controls::format_lines()
299 _width = 0;
300 for(auto i : controlinfo) {
301 if(i.position_left + i.reserved > _width)
302 _width = i.position_left + i.reserved;
304 std::u32string cp1;
305 std::u32string cp2;
306 uint32_t off = divcnt + 1;
307 cp1.resize(_width + divcnt + 1);
308 cp2.resize(_width + divcnt + 1);
309 for(unsigned i = 0; i < cp1.size(); i++)
310 cp1[i] = cp2[i] = 32;
311 cp1[divcnt] = 0x2502;
312 cp2[divcnt] = 0x2502;
313 //Line1
314 //For every port-controller, find the least coordinate.
315 for(auto i : controlinfo) {
316 if(i.type == -1) {
317 auto _title = i.title;
318 std::copy(_title.begin(), _title.end(), &cp1[i.position_left + off]);
319 } else if(i.type == -2) {
320 auto _title = (stringfmt() << i.port << "-" << i.controller).str32();
321 std::copy(_title.begin(), _title.end(), &cp1[i.position_left + off]);
324 //Line2
325 for(auto i : controlinfo) {
326 auto _title = i.title;
327 if(i.type == -1 || i.type == 1)
328 std::copy(_title.begin(), _title.end(), &cp2[i.position_left + off]);
329 if(i.type == 0)
330 cp2[i.position_left + off] = i.ch;
332 _line1 = cp1;
333 _line2 = cp2;
336 namespace
338 //TODO: Use real clipboard.
339 std::string clipboard;
341 void copy_to_clipboard(const std::string& text)
343 clipboard = text;
346 bool clipboard_has_text()
348 return (clipboard.length() > 0);
351 std::string copy_from_clipboard()
353 return clipboard;
356 std::string encode_line(portctrl::frame& f)
358 char buffer[512];
359 f.serialize(buffer);
360 return buffer;
363 std::string encode_line(frame_controls& info, portctrl::frame& f, unsigned port,
364 unsigned controller)
366 std::ostringstream x;
367 bool last_axis = false;
368 bool first = true;
369 for(auto i : info.get_controlinfo()) {
370 if(i.port != port)
371 continue;
372 if(i.controller != controller)
373 continue;
374 switch(i.type) {
375 case 0: //Button.
376 if(last_axis)
377 x << " ";
378 if(info.read_index(f, i.index)) {
379 char32_t tmp1[2];
380 tmp1[0] = i.ch;
381 tmp1[1] = 0;
382 x << utf8::to8(std::u32string(tmp1));
383 } else
384 x << "-";
385 last_axis = false;
386 first = false;
387 break;
388 case 1: //Axis.
389 if(!first)
390 x << " ";
391 x << info.read_index(f, i.index);
392 first = false;
393 last_axis = true;
394 break;
397 return x.str();
400 short read_short(const std::u32string& s, size_t& r)
402 unsigned short _res = 0;
403 bool negative = false;
404 if(r < s.length() && s[r] == '-') {
405 negative = true;
406 r++;
408 while(r < s.length() && s[r] >= 48 && s[r] <= 57) {
409 _res = _res * 10 + (s[r] - 48);
410 r++;
412 return negative ? -_res : _res;
415 void decode_line(frame_controls& info, portctrl::frame& f, std::string line, unsigned port,
416 unsigned controller)
418 std::u32string _line = utf8::to32(line);
419 bool last_axis = false;
420 bool first = true;
421 short y;
422 char32_t y2;
423 size_t ridx = 0;
424 for(auto i : info.get_controlinfo()) {
425 if(i.port != port)
426 continue;
427 if(i.controller != controller)
428 continue;
429 switch(i.type) {
430 case 0: //Button.
431 if(last_axis) {
432 ridx++;
433 while(ridx < _line.length() && (_line[ridx] == 9 || _line[ridx] == 10 ||
434 _line[ridx] == 13 || _line[ridx] == 32))
435 ridx++;
437 y2 = (ridx < _line.length()) ? _line[ridx++] : 0;
438 if(y2 == U'-' || y2 == 0)
439 info.write_index(f, i.index, 0);
440 else
441 info.write_index(f, i.index, 1);
442 last_axis = false;
443 first = false;
444 break;
445 case 1: //Axis.
446 if(!first)
447 ridx++;
448 while(ridx < _line.length() && (_line[ridx] == 9 || _line[ridx] == 10 ||
449 _line[ridx] == 13 || _line[ridx] == 32))
450 ridx++;
451 y = read_short(_line, ridx);
452 info.write_index(f, i.index, y);
453 first = false;
454 last_axis = true;
455 break;
460 std::string encode_lines(portctrl::frame_vector& fv, uint64_t start, uint64_t end)
462 std::ostringstream x;
463 x << "lsnes-moviedata-whole" << std::endl;
464 for(uint64_t i = start; i < end; i++) {
465 portctrl::frame tmp = fv[i];
466 x << encode_line(tmp) << std::endl;
468 return x.str();
471 std::string encode_lines(frame_controls& info, portctrl::frame_vector& fv, uint64_t start,
472 uint64_t end, unsigned port, unsigned controller)
474 std::ostringstream x;
475 x << "lsnes-moviedata-controller" << std::endl;
476 for(uint64_t i = start; i < end; i++) {
477 portctrl::frame tmp = fv[i];
478 x << encode_line(info, tmp, port, controller) << std::endl;
480 return x.str();
483 int clipboard_get_data_type()
485 if(!clipboard_has_text())
486 return -1;
487 std::string y = copy_from_clipboard();
488 std::istringstream x(y);
489 std::string hdr;
490 std::getline(x, hdr);
491 if(hdr == "lsnes-moviedata-whole")
492 return 1;
493 if(hdr == "lsnes-moviedata-controller")
494 return 0;
495 return -1;
498 std::set<unsigned> controller_index_set(frame_controls& info, unsigned port, unsigned controller)
500 std::set<unsigned> r;
501 for(auto i : info.get_controlinfo()) {
502 if(i.port == port && i.controller == controller && (i.type == 0 || i.type == 1))
503 r.insert(i.index);
505 return r;
508 void move_index_set(frame_controls& info, portctrl::frame_vector& fv, uint64_t src, uint64_t dst,
509 uint64_t len, const std::set<unsigned>& indices)
511 if(src == dst)
512 return;
513 portctrl::frame_vector::notify_freeze freeze(fv);
514 if(src > dst) {
515 //Copy forwards.
516 uint64_t shift = src - dst;
517 for(uint64_t i = dst; i < dst + len; i++) {
518 portctrl::frame _src = fv[i + shift];
519 portctrl::frame _dst = fv[i];
520 for(auto j : indices)
521 info.write_index(_dst, j, info.read_index(_src, j));
523 } else {
524 //Copy backwards.
525 uint64_t shift = dst - src;
526 for(uint64_t i = src + len - 1; i >= src && i < src + len; i--) {
527 portctrl::frame _src = fv[i];
528 portctrl::frame _dst = fv[i + shift];
529 for(auto j : indices)
530 info.write_index(_dst, j, info.read_index(_src, j));
535 void zero_index_set(frame_controls& info, portctrl::frame_vector& fv, uint64_t dst, uint64_t len,
536 const std::set<unsigned>& indices)
538 portctrl::frame_vector::notify_freeze freeze(fv);
539 for(uint64_t i = dst; i < dst + len; i++) {
540 portctrl::frame _dst = fv[i];
541 for(auto j : indices)
542 info.write_index(_dst, j, 0);
546 control_info find_paired(control_info ci, const std::list<control_info>& info)
548 if(ci.axistype == portctrl::button::TYPE_TAXIS)
549 return ci;
550 bool even = true;
551 bool next_flag = false;
552 control_info previous;
553 for(auto i : info) {
554 if(i.port != ci.port || i.controller != ci.controller)
555 continue;
556 if(i.axistype != portctrl::button::TYPE_AXIS &&
557 i.axistype != portctrl::button::TYPE_RAXIS &&
558 i.axistype != portctrl::button::TYPE_LIGHTGUN)
559 continue;
560 if(next_flag)
561 return i;
562 if(i.index == ci.index) {
563 //This and...
564 if(even)
565 next_flag = true; //Next.
566 else
567 return previous; //Pevious.
569 previous = i;
570 even = !even;
572 //Huh, no pair.
573 return ci;
576 int32_t value_to_coordinate(int32_t rmin, int32_t rmax, int32_t val, int32_t dim)
578 //Scale the values to be zero-based.
579 val = min(max(val, rmin), rmax);
580 rmax -= rmin;
581 val -= rmin;
582 int32_t center = rmax / 2;
583 int32_t cc = (dim - 1) / 2;
584 if(val == center)
585 return cc;
586 if(val < center) {
587 //0 => 0, center => cc.
588 return (val * (int64_t)cc + (center / 2)) / center;
590 if(val > center) {
591 //center => cc, rmax => dim - 1.
592 val -= center;
593 rmax -= center;
594 int32_t cc2 = (dim - 1 - cc);
595 return (val * (int64_t)cc2 + (rmax / 2)) / rmax + cc;
597 return 0; //NOTREACHED.
600 int32_t coordinate_to_value(int32_t rmin, int32_t rmax, int32_t val, int32_t dim)
602 if(dim == rmin - rmax + 1) {
603 return val + rmin;
605 val = min(max(val, (int32_t)0), dim - 1);
606 int32_t center = (rmax + rmin) / 2;
607 int32_t cc = (dim - 1) / 2;
608 if(val == cc)
609 return center;
610 if(val < cc) {
611 //0 => rmin, cc => center.
612 return ((center - rmin) * (int64_t)val + cc / 2) / cc + rmin;
614 if(val > cc) {
615 //cc => center, dim - 1 => rmax.
616 uint32_t cc2 = (dim - 1 - cc);
617 return ((rmax - center) * (int64_t)(val - cc) + cc2 / 2) / cc2 + center;
619 return 0; //NOTREACHED.
622 std::string windowname(control_info X, control_info Y)
624 if(X.index == Y.index)
625 return (stringfmt() << utf8::to8(X.title)).str();
626 else
627 return (stringfmt() << utf8::to8(X.title) << "/" << utf8::to8(Y.title)).str();
630 class window_prompt : public wxDialog
632 public:
633 window_prompt(wxWindow* parent, uint8_t* _bitmap, unsigned _width,
634 unsigned _height, control_info X, control_info Y, unsigned posX, unsigned posY)
635 : wxDialog(parent, wxID_ANY, towxstring(windowname(X, Y)), wxPoint(posX, posY))
637 CHECK_UI_THREAD;
638 dirty = false;
639 bitmap = _bitmap;
640 width = _width;
641 height = _height;
642 cX = X;
643 cY = Y;
644 oneaxis = false;
645 if(X.index == Y.index) {
646 //One-axis never has a bitmap.
647 bitmap = NULL;
648 height = 32;
649 oneaxis = true;
651 wxSizer* s = new wxBoxSizer(wxVERTICAL);
652 SetSizer(s);
653 s->Add(panel = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxSize(width, height)), 0,
654 wxGROW);
655 panel->Connect(wxEVT_PAINT, wxPaintEventHandler(window_prompt::on_paint), NULL, this);
656 panel->Connect(wxEVT_ERASE_BACKGROUND, wxEraseEventHandler(window_prompt::on_erase), NULL,
657 this);
658 Connect(wxEVT_CLOSE_WINDOW, wxCloseEventHandler(window_prompt::on_wclose));
659 panel->Connect(wxEVT_LEFT_DOWN, wxMouseEventHandler(window_prompt::on_mouse), NULL, this);
660 panel->Connect(wxEVT_MOTION, wxMouseEventHandler(window_prompt::on_mouse), NULL, this);
661 Fit();
663 void on_wclose(wxCloseEvent& e)
665 CHECK_UI_THREAD;
666 EndModal(wxID_CANCEL);
668 void on_erase(wxEraseEvent& e)
670 //Blank.
672 void on_paint(wxPaintEvent& e)
674 CHECK_UI_THREAD;
675 wxPaintDC dc(panel);
676 if(bitmap) {
677 wxBitmap bmp(wxImage(width, height, bitmap, true));
678 dc.DrawBitmap(bmp, 0, 0, false);
679 } else {
680 dc.SetBackground(*wxWHITE_BRUSH);
681 dc.Clear();
682 auto xval = value_to_coordinate(cX.rmin, cX.rmax, 0, width);
683 auto yval = value_to_coordinate(cY.rmin, cY.rmax, 0, height);
684 dc.SetPen(*wxBLACK_PEN);
685 if(cX.rmin < 0 && cX.rmax > 0)
686 dc.DrawLine(xval, 0, xval, height);
687 if(!oneaxis && cY.rmin < 0 && cY.rmax > 0)
688 dc.DrawLine(0, yval, width, yval);
690 dc.SetPen(*wxRED_PEN);
691 dc.DrawLine(mouseX, 0, mouseX, height);
692 if(!oneaxis)
693 dc.DrawLine(0, mouseY, width, mouseY);
694 dirty = false;
696 void on_mouse(wxMouseEvent& e)
698 CHECK_UI_THREAD;
699 if(e.LeftDown()) {
700 result.first = coordinate_to_value(cX.rmin, cX.rmax, e.GetX(), width);
701 if(!oneaxis)
702 result.second = coordinate_to_value(cY.rmin, cY.rmax, e.GetY(), height);
703 else
704 result.second = 0;
705 EndModal(wxID_OK);
707 mouseX = e.GetX();
708 mouseY = e.GetY();
709 if(!dirty) {
710 dirty = true;
711 panel->Refresh();
714 std::pair<int, int> get_results()
716 return result;
718 private:
719 std::pair<int, int> result;
720 wxPanel* panel;
721 bool oneaxis;
722 bool dirty;
723 int mouseX;
724 int mouseY;
725 int height;
726 int width;
727 control_info cX;
728 control_info cY;
729 uint8_t* bitmap;
732 std::pair<int, int> prompt_coodinates_window(wxWindow* parent, uint8_t* bitmap, unsigned width,
733 unsigned height, control_info X, control_info Y, unsigned posX, unsigned posY)
735 CHECK_UI_THREAD;
736 window_prompt* p = new window_prompt(parent, bitmap, width, height, X, Y, posX, posY);
737 if(p->ShowModal() == wxID_CANCEL) {
738 delete p;
739 throw canceled_exception();
741 auto r = p->get_results();
742 delete p;
743 return r;
747 class wxeditor_movie : public wxDialog
749 public:
750 wxeditor_movie(emulator_instance& _inst, wxWindow* parent);
751 ~wxeditor_movie() throw();
752 bool ShouldPreventAppExit() const;
753 void on_close(wxCommandEvent& e);
754 void on_wclose(wxCloseEvent& e);
755 void on_focus_wrong(wxFocusEvent& e);
756 void on_keyboard_down(wxKeyEvent& e);
757 void on_keyboard_up(wxKeyEvent& e);
758 scroll_bar* get_scroll();
759 void update();
760 private:
761 struct _moviepanel : public wxPanel
763 _moviepanel(wxeditor_movie* v, emulator_instance& _inst);
764 ~_moviepanel() throw();
765 void signal_repaint();
766 void on_paint(wxPaintEvent& e);
767 void on_erase(wxEraseEvent& e);
768 void on_mouse(wxMouseEvent& e);
769 void on_popup_menu(wxCommandEvent& e);
770 uint64_t moviepos;
771 private:
772 int get_lines();
773 void render(text_framebuffer& fb, unsigned long long pos);
774 void on_mouse0(unsigned x, unsigned y, bool polarity, bool shift, unsigned X, unsigned Y);
775 void on_mouse1(unsigned x, unsigned y, bool polarity);
776 void on_mouse2(unsigned x, unsigned y, bool polarity);
777 void popup_axis_panel(uint64_t row, control_info ci, unsigned screenX, unsigned screenY);
778 void do_toggle_buttons(unsigned idx, uint64_t row1, uint64_t row2, bool force_false);
779 void do_alter_axis(unsigned idx, uint64_t row1, uint64_t row2);
780 void do_sweep_axis(unsigned idx, uint64_t row1, uint64_t row2);
781 void do_append_frames(uint64_t count);
782 void do_append_frames();
783 void do_insert_frame_after(uint64_t row, bool multi);
784 void do_delete_frame(uint64_t row1, uint64_t row2, bool wholeframe);
785 void do_truncate(uint64_t row);
786 void do_set_stop_at_frame();
787 void do_scroll_to_frame();
788 void do_scroll_to_current_frame();
789 void do_copy(uint64_t row1, uint64_t row2, unsigned port, unsigned controller);
790 void do_copy(uint64_t row1, uint64_t row2);
791 void do_cut(uint64_t row1, uint64_t row2, unsigned port, unsigned controller);
792 void do_cut(uint64_t row1, uint64_t row2);
793 void do_paste(uint64_t row, unsigned port, unsigned controller, bool append);
794 void do_paste(uint64_t row, bool append);
795 void do_insert_controller(uint64_t row, unsigned port, unsigned controller);
796 void do_delete_controller(uint64_t row1, uint64_t row2, unsigned port, unsigned controller);
797 uint64_t first_editable(unsigned index);
798 uint64_t first_nextframe();
799 int width(portctrl::frame& f);
800 std::u32string render_line1(portctrl::frame& f);
801 std::u32string render_line2(portctrl::frame& f);
802 void render_linen(text_framebuffer& fb, portctrl::frame& f, uint64_t sfn, int y);
803 emulator_instance& inst;
804 unsigned long long spos;
805 void* prev_obj;
806 uint64_t prev_seqno;
807 void update_cache();
808 std::map<uint64_t, uint64_t> subframe_to_frame;
809 uint64_t max_subframe;
810 frame_controls fcontrols;
811 wxeditor_movie* m;
812 bool requested;
813 text_framebuffer fb;
814 uint64_t movielines;
815 unsigned new_width;
816 unsigned new_height;
817 std::vector<uint8_t> pixels;
818 unsigned press_x;
819 uint64_t press_line;
820 uint64_t rpress_line;
821 unsigned press_index;
822 bool pressed;
823 bool recursing;
824 uint64_t linecount;
825 uint64_t cached_cffs;
826 bool position_locked;
827 wxMenu* current_popup;
828 std::map<int, std::string> branch_names;
830 emulator_instance& inst;
831 _moviepanel* moviepanel;
832 wxButton* closebutton;
833 scroll_bar* moviescroll;
834 bool closing;
837 namespace
839 instance_map<wxeditor_movie> movieeditors;
841 //Find the first real editable subframe.
842 //Call only in emulator thread.
843 uint64_t real_first_editable(frame_controls& fc, unsigned idx)
845 uint64_t cffs = CORE().mlogic->get_movie().get_current_frame_first_subframe();
846 portctrl::frame_vector& fv = *CORE().mlogic->get_mfile().input;
847 portctrl::counters& pv = CORE().mlogic->get_movie().get_pollcounters();
848 uint64_t vsize = fv.size();
849 uint32_t pc = fc.read_pollcount(pv, idx);
850 for(uint32_t i = 1; i < pc; i++)
851 if(cffs + i >= vsize || fv[cffs + i].sync())
852 return cffs + i;
853 return cffs + pc;
856 uint64_t real_first_editable(frame_controls& fc, std::set<unsigned> idx)
858 uint64_t m = 0;
859 for(auto i : idx)
860 m = max(m, real_first_editable(fc, i));
861 return m;
864 //Find the first real editable whole frame.
865 //Call only in emulator thread.
866 uint64_t real_first_nextframe(frame_controls& fc)
868 uint64_t base = real_first_editable(fc, 0);
869 portctrl::frame_vector& fv = *CORE().mlogic->get_mfile().input;
870 uint64_t vsize = fv.size();
871 for(uint32_t i = 0;; i++)
872 if(base + i >= vsize || fv[base + i].sync())
873 return base + i;
877 wxeditor_movie::_moviepanel::~_moviepanel() throw() {}
878 wxeditor_movie::~wxeditor_movie() throw()
880 movieeditors.remove(inst);
883 wxeditor_movie::_moviepanel::_moviepanel(wxeditor_movie* v, emulator_instance& _inst)
884 : wxPanel(v, wxID_ANY, wxDefaultPosition, wxSize(100, 100), wxWANTS_CHARS), inst(_inst)
886 CHECK_UI_THREAD;
887 m = v;
888 Connect(wxEVT_PAINT, wxPaintEventHandler(_moviepanel::on_paint), NULL, this);
889 Connect(wxEVT_ERASE_BACKGROUND, wxEraseEventHandler(_moviepanel::on_erase), NULL, this);
890 new_width = 0;
891 new_height = 0;
892 moviepos = 0;
893 spos = 0;
894 prev_obj = NULL;
895 prev_seqno = 0;
896 max_subframe = 0;
897 recursing = false;
898 position_locked = true;
899 current_popup = NULL;
901 Connect(wxEVT_LEFT_DOWN, wxMouseEventHandler(_moviepanel::on_mouse), NULL, this);
902 Connect(wxEVT_LEFT_UP, wxMouseEventHandler(_moviepanel::on_mouse), NULL, this);
903 Connect(wxEVT_MIDDLE_DOWN, wxMouseEventHandler(_moviepanel::on_mouse), NULL, this);
904 Connect(wxEVT_MIDDLE_UP, wxMouseEventHandler(_moviepanel::on_mouse), NULL, this);
905 Connect(wxEVT_RIGHT_DOWN, wxMouseEventHandler(_moviepanel::on_mouse), NULL, this);
906 Connect(wxEVT_RIGHT_UP, wxMouseEventHandler(_moviepanel::on_mouse), NULL, this);
907 Connect(wxEVT_MOUSEWHEEL, wxMouseEventHandler(_moviepanel::on_mouse), NULL, this);
909 signal_repaint();
910 requested = false;
913 void wxeditor_movie::_moviepanel::update_cache()
915 movie& m = inst.mlogic->get_movie();
916 portctrl::frame_vector& fv = *inst.mlogic->get_mfile().input;
917 if(&m == prev_obj && prev_seqno == m.get_seqno()) {
918 //Just process new subframes if any.
919 for(uint64_t i = max_subframe; i < fv.size(); i++) {
920 uint64_t prev = (i > 0) ? subframe_to_frame[i - 1] : 0;
921 portctrl::frame f = fv[i];
922 if(f.sync())
923 subframe_to_frame[i] = prev + 1;
924 else
925 subframe_to_frame[i] = prev;
927 max_subframe = fv.size();
928 return;
930 //Reprocess all subframes.
931 for(uint64_t i = 0; i < fv.size(); i++) {
932 uint64_t prev = (i > 0) ? subframe_to_frame[i - 1] : 0;
933 portctrl::frame f = fv[i];
934 if(f.sync())
935 subframe_to_frame[i] = prev + 1;
936 else
937 subframe_to_frame[i] = prev;
939 max_subframe = fv.size();
940 portctrl::frame model = fv.blank_frame(false);
941 fcontrols.set_types(model);
942 prev_obj = &m;
943 prev_seqno = m.get_seqno();
946 int wxeditor_movie::_moviepanel::width(portctrl::frame& f)
948 update_cache();
949 return divcnt + 1 + fcontrols.width();
952 std::u32string wxeditor_movie::_moviepanel::render_line1(portctrl::frame& f)
954 update_cache();
955 return fcontrols.line1();
958 std::u32string wxeditor_movie::_moviepanel::render_line2(portctrl::frame& f)
960 update_cache();
961 return fcontrols.line2();
964 void wxeditor_movie::_moviepanel::render_linen(text_framebuffer& fb, portctrl::frame& f, uint64_t sfn, int y)
966 update_cache();
967 size_t fbstride = fb.get_stride();
968 text_framebuffer::element* _fb = fb.get_buffer();
969 text_framebuffer::element e;
970 e.bg = 0xFFFFFF;
971 e.fg = 0x000000;
972 for(unsigned i = 0; i < divcnt; i++) {
973 uint64_t fn = subframe_to_frame[sfn];
974 e.ch = (fn >= divsl[i]) ? (((fn / divs[i]) % 10) + 48) : 32;
975 _fb[y * fbstride + i] = e;
977 e.ch = 0x2502;
978 _fb[y * fbstride + divcnt] = e;
979 const std::list<control_info>& ctrlinfo = fcontrols.get_controlinfo();
980 uint64_t curframe = inst.mlogic->get_movie().get_current_frame();
981 portctrl::counters& pv = inst.mlogic->get_movie().get_pollcounters();
982 uint64_t cffs = inst.mlogic->get_movie().get_current_frame_first_subframe();
983 cached_cffs = cffs;
984 int past = -1;
985 if(!inst.mlogic->get_movie().readonly_mode())
986 past = 1;
987 else if(subframe_to_frame[sfn] < curframe)
988 past = 1;
989 else if(subframe_to_frame[sfn] > curframe)
990 past = 0;
991 bool now = (subframe_to_frame[sfn] == curframe);
992 unsigned xcord = 32768;
993 if(pressed)
994 xcord = press_x;
996 for(auto i : ctrlinfo) {
997 int rpast = past;
998 unsigned off = divcnt + 1;
999 bool cselected = (xcord >= i.position_left + off && xcord < i.position_left + i.reserved + off);
1000 if(rpast == -1) {
1001 unsigned polls = fcontrols.read_pollcount(pv, i.index);
1002 rpast = ((cffs + polls) > sfn) ? 1 : 0;
1004 uint32_t bgc = 0xC0C0C0;
1005 if(rpast)
1006 bgc |= 0x0000FF;
1007 if(now)
1008 bgc |= 0xFF0000;
1009 if(cselected)
1010 bgc |= 0x00FF00;
1011 if(bgc == 0xC0C0C0)
1012 bgc = 0xFFFFFF;
1013 if(i.type == -1) {
1014 //Separator.
1015 fb.write(i.title, 0, divcnt + 1 + i.position_left, y, 0x000000, 0xFFFFFF);
1016 } else if(i.type == 0) {
1017 //Button.
1018 char32_t c[2];
1019 bool v = (fcontrols.read_index(f, i.index) != 0);
1020 c[0] = i.ch;
1021 c[1] = 0;
1022 fb.write(c, 0, divcnt + 1 + i.position_left, y, v ? 0x000000 : 0xC8C8C8, bgc);
1023 } else if(i.type == 1) {
1024 //Axis.
1025 char c[7];
1026 sprintf(c, "%6d", fcontrols.read_index(f, i.index));
1027 fb.write(c, 0, divcnt + 1 + i.position_left, y, 0x000000, bgc);
1032 void wxeditor_movie::_moviepanel::render(text_framebuffer& fb, unsigned long long pos)
1034 spos = pos;
1035 portctrl::frame_vector& fv = *inst.mlogic->get_mfile().input;
1036 portctrl::frame cf = fv.blank_frame(false);
1037 int _width = width(cf);
1038 fb.set_size(_width, lines_to_display + 3);
1039 size_t fbstride = fb.get_stride();
1040 auto fbsize = fb.get_characters();
1041 text_framebuffer::element* _fb = fb.get_buffer();
1042 fb.write((stringfmt() << "Current frame: " << inst.mlogic->get_movie().get_current_frame() << " of "
1043 << inst.mlogic->get_movie().get_frame_count()).str(), _width, 0, 0,
1044 0x000000, 0xFFFFFF);
1045 fb.write(render_line1(cf), _width, 0, 1, 0x000000, 0xFFFFFF);
1046 fb.write(render_line2(cf), _width, 0, 2, 0x000000, 0xFFFFFF);
1047 unsigned long long lines = fv.size();
1048 unsigned long long i;
1049 unsigned j;
1050 for(i = pos, j = 3; i < pos + lines_to_display; i++, j++) {
1051 text_framebuffer::element e;
1052 if(i >= lines) {
1053 //Out of range.
1054 e.bg = 0xFFFFFF;
1055 e.fg = 0x000000;
1056 e.ch = 32;
1057 for(unsigned k = 0; k < fbsize.first; k++)
1058 _fb[j * fbstride + k] = e;
1059 } else {
1060 portctrl::frame frame = fv[i];
1061 render_linen(fb, frame, i, j);
1066 void wxeditor_movie::_moviepanel::do_toggle_buttons(unsigned idx, uint64_t row1, uint64_t row2, bool force_false)
1068 frame_controls* _fcontrols = &fcontrols;
1069 uint64_t _press_line = row1;
1070 uint64_t line = row2;
1071 bool _force_false = force_false;
1072 if(_press_line > line)
1073 std::swap(_press_line, line);
1074 recursing = true;
1075 inst.iqueue->run([idx, _press_line, line, _fcontrols, _force_false]() {
1076 if(!CORE().mlogic->get_movie().readonly_mode())
1077 return;
1078 uint64_t fedit = real_first_editable(*_fcontrols, idx);
1079 portctrl::frame_vector& fv = *CORE().mlogic->get_mfile().input;
1080 portctrl::frame_vector::notify_freeze freeze(fv);
1081 for(uint64_t i = _press_line; i <= line; i++) {
1082 if(i < fedit || i >= fv.size())
1083 continue;
1084 portctrl::frame cf = fv[i];
1085 if(!_force_false)
1086 _fcontrols->write_index(cf, idx, !_fcontrols->read_index(cf, idx));
1087 else
1088 _fcontrols->write_index(cf, idx, 0);
1091 recursing = false;
1092 if(idx == 0)
1093 max_subframe = _press_line; //Reparse.
1094 signal_repaint();
1097 void wxeditor_movie::_moviepanel::do_alter_axis(unsigned idx, uint64_t row1, uint64_t row2)
1099 CHECK_UI_THREAD;
1100 frame_controls* _fcontrols = &fcontrols;
1101 uint64_t line = row1;
1102 uint64_t line2 = row2;
1103 short value;
1104 bool valid = true;
1105 inst.iqueue->run([idx, line, &value, _fcontrols, &valid]() {
1106 if(!CORE().mlogic->get_movie().readonly_mode()) {
1107 valid = false;
1108 return;
1110 uint64_t fedit = real_first_editable(*_fcontrols, idx);
1111 portctrl::frame_vector& fv = *CORE().mlogic->get_mfile().input;
1112 if(line < fedit || line >= fv.size()) {
1113 valid = false;
1114 return;
1116 portctrl::frame_vector::notify_freeze freeze(fv);
1117 portctrl::frame cf = fv[line];
1118 value = _fcontrols->read_index(cf, idx);
1120 if(!valid)
1121 return;
1122 try {
1123 std::string text = pick_text(m, "Set value", "Enter new value:", (stringfmt() << value).str());
1124 value = parse_value<short>(text);
1125 } catch(canceled_exception& e) {
1126 return;
1127 } catch(std::exception& e) {
1128 wxMessageBox(wxT("Invalid value"), _T("Error"), wxICON_EXCLAMATION | wxOK, m);
1129 return;
1131 if(line > line2)
1132 std::swap(line, line2);
1133 inst.iqueue->run([idx, line, line2, value, _fcontrols]() {
1134 uint64_t fedit = real_first_editable(*_fcontrols, idx);
1135 portctrl::frame_vector& fv = *CORE().mlogic->get_mfile().input;
1136 portctrl::frame_vector::notify_freeze freeze(fv);
1137 for(uint64_t i = line; i <= line2; i++) {
1138 if(i < fedit || i >= fv.size())
1139 continue;
1140 portctrl::frame cf = fv[i];
1141 _fcontrols->write_index(cf, idx, value);
1144 signal_repaint();
1147 void wxeditor_movie::_moviepanel::do_sweep_axis(unsigned idx, uint64_t row1, uint64_t row2)
1149 frame_controls* _fcontrols = &fcontrols;
1150 uint64_t line = row1;
1151 uint64_t line2 = row2;
1152 short value;
1153 short value2;
1154 bool valid = true;
1155 if(line > line2)
1156 std::swap(line, line2);
1157 inst.iqueue->run([idx, line, line2, &value, &value2, _fcontrols, &valid]() {
1158 if(!CORE().mlogic->get_movie().readonly_mode()) {
1159 valid = false;
1160 return;
1162 uint64_t fedit = real_first_editable(*_fcontrols, idx);
1163 portctrl::frame_vector& fv = *CORE().mlogic->get_mfile().input;
1164 if(line2 < fedit || line2 >= fv.size()) {
1165 valid = false;
1166 return;
1168 portctrl::frame cf = fv[line];
1169 value = _fcontrols->read_index(cf, idx);
1170 portctrl::frame cf2 = fv[line2];
1171 value2 = _fcontrols->read_index(cf2, idx);
1173 if(!valid)
1174 return;
1175 inst.iqueue->run([idx, line, line2, value, value2, _fcontrols]() {
1176 uint64_t fedit = real_first_editable(*_fcontrols, idx);
1177 portctrl::frame_vector& fv = *CORE().mlogic->get_mfile().input;
1178 portctrl::frame_vector::notify_freeze freeze(fv);
1179 for(uint64_t i = line + 1; i <= line2 - 1; i++) {
1180 if(i < fedit || i >= fv.size())
1181 continue;
1182 portctrl::frame cf = fv[i];
1183 auto tmp2 = static_cast<int64_t>(i - line) * (value2 - value) /
1184 static_cast<int64_t>(line2 - line);
1185 short tmp = value + tmp2;
1186 _fcontrols->write_index(cf, idx, tmp);
1189 signal_repaint();
1192 void wxeditor_movie::_moviepanel::do_append_frames(uint64_t count)
1194 recursing = true;
1195 uint64_t _count = count;
1196 inst.iqueue->run([_count]() {
1197 if(!CORE().mlogic->get_movie().readonly_mode())
1198 return;
1199 portctrl::frame_vector& fv = *CORE().mlogic->get_mfile().input;
1200 portctrl::frame_vector::notify_freeze freeze(fv);
1201 for(uint64_t i = 0; i < _count; i++)
1202 fv.append(fv.blank_frame(true));
1204 recursing = false;
1205 signal_repaint();
1208 void wxeditor_movie::_moviepanel::do_append_frames()
1210 CHECK_UI_THREAD;
1211 uint64_t value;
1212 try {
1213 std::string text = pick_text(m, "Append frames", "Enter number of frames to append:", "");
1214 value = parse_value<uint64_t>(text);
1215 } catch(canceled_exception& e) {
1216 return;
1217 } catch(std::exception& e) {
1218 wxMessageBox(wxT("Invalid value"), _T("Error"), wxICON_EXCLAMATION | wxOK, m);
1219 return;
1221 do_append_frames(value);
1222 signal_repaint();
1225 void wxeditor_movie::_moviepanel::do_insert_frame_after(uint64_t row, bool multi)
1227 CHECK_UI_THREAD;
1228 uint64_t multicount = 1;
1229 if(multi) {
1230 try {
1231 std::string text = pick_text(m, "Append frames", "Enter number of frames to insert:", "");
1232 multicount = parse_value<uint64_t>(text);
1233 } catch(canceled_exception& e) {
1234 return;
1235 } catch(std::exception& e) {
1236 wxMessageBox(wxT("Invalid value"), _T("Error"), wxICON_EXCLAMATION | wxOK, m);
1237 return;
1240 recursing = true;
1241 frame_controls* _fcontrols = &fcontrols;
1242 uint64_t _row = row;
1243 inst.iqueue->run([_row, _fcontrols, multicount]() {
1244 if(!CORE().mlogic->get_movie().readonly_mode())
1245 return;
1246 portctrl::frame_vector& fv = *CORE().mlogic->get_mfile().input;
1247 uint64_t fedit = real_first_editable(*_fcontrols, 0);
1248 //Find the start of the next frame.
1249 uint64_t nframe = _row + 1;
1250 uint64_t vsize = fv.size();
1251 while(nframe < vsize && !fv[nframe].sync())
1252 nframe++;
1253 if(nframe < fedit)
1254 return;
1255 portctrl::frame_vector::notify_freeze freeze(fv);
1256 for(uint64_t k = 0; k < multicount; k++)
1257 fv.append(fv.blank_frame(true));
1258 if(nframe < vsize) {
1259 //Okay, gotta copy all data after this point. nframe has to be at least 1.
1260 for(uint64_t i = vsize - 1; i >= nframe; i--)
1261 fv[i + multicount] = fv[i];
1262 for(uint64_t k = 0; k < multicount; k++)
1263 fv[nframe + k] = fv.blank_frame(true);
1266 max_subframe = row;
1267 recursing = false;
1268 signal_repaint();
1271 void wxeditor_movie::_moviepanel::do_delete_frame(uint64_t row1, uint64_t row2, bool wholeframe)
1273 recursing = true;
1274 uint64_t _row1 = row1;
1275 uint64_t _row2 = row2;
1276 bool _wholeframe = wholeframe;
1277 frame_controls* _fcontrols = &fcontrols;
1278 if(_row1 > _row2) std::swap(_row1, _row2);
1279 inst.iqueue->run([_row1, _row2, _wholeframe, _fcontrols]() {
1280 portctrl::frame_vector& fv = *CORE().mlogic->get_mfile().input;
1281 uint64_t vsize = fv.size();
1282 if(_row1 >= vsize)
1283 return; //Nothing to do.
1284 portctrl::frame_vector::notify_freeze freeze(fv);
1285 uint64_t row2 = min(_row2, vsize - 1);
1286 uint64_t row1 = min(_row1, vsize - 1);
1287 row1 = max(row1, real_first_editable(*_fcontrols, 0));
1288 if(_wholeframe) {
1289 if(_row2 < real_first_nextframe(*_fcontrols))
1290 return; //Nothing to do.
1291 //Scan backwards for the first subframe of this frame and forwards for the last.
1292 uint64_t fsf = row1;
1293 uint64_t lsf = row2;
1294 if(fv[_row2].sync())
1295 lsf++; //Bump by one so it finds the end.
1296 while(fsf < vsize && !fv[fsf].sync())
1297 fsf--;
1298 while(lsf < vsize && !fv[lsf].sync())
1299 lsf++;
1300 fsf = max(fsf, real_first_editable(*_fcontrols, 0));
1301 uint64_t tonuke = lsf - fsf;
1302 int64_t frames_tonuke = 0;
1303 //Count frames nuked.
1304 for(uint64_t i = fsf; i < lsf; i++)
1305 if(fv[i].sync())
1306 frames_tonuke++;
1307 //Nuke from fsf to lsf.
1308 for(uint64_t i = fsf; i < vsize - tonuke; i++)
1309 fv[i] = fv[i + tonuke];
1310 fv.resize(vsize - tonuke);
1311 } else {
1312 if(row2 < real_first_editable(*_fcontrols, 0))
1313 return; //Nothing to do.
1314 //The sync flag needs to be inherited if:
1315 //1) Some deleted subframe has sync flag AND
1316 //2) The subframe immediately after deleted region doesn't.
1317 bool inherit_sync = false;
1318 for(uint64_t i = row1; i <= row2; i++)
1319 inherit_sync = inherit_sync || fv[i].sync();
1320 inherit_sync = inherit_sync && (row2 + 1 < vsize && !fv[_row2 + 1].sync());
1321 int64_t frames_tonuke = 0;
1322 //Count frames nuked.
1323 for(uint64_t i = row1; i <= row2; i++)
1324 if(fv[i].sync())
1325 frames_tonuke++;
1326 //If sync is inherited, one less frame is nuked.
1327 if(inherit_sync) frames_tonuke--;
1328 //Nuke the subframes.
1329 uint64_t tonuke = row2 - row1 + 1;
1330 for(uint64_t i = row1; i < vsize - tonuke; i++)
1331 fv[i] = fv[i + tonuke];
1332 fv.resize(vsize - tonuke);
1333 //Next subframe inherits the sync flag.
1334 if(inherit_sync)
1335 fv[row1].sync(true);
1338 max_subframe = _row1;
1339 recursing = false;
1340 signal_repaint();
1343 void wxeditor_movie::_moviepanel::do_truncate(uint64_t row)
1345 recursing = true;
1346 uint64_t _row = row;
1347 frame_controls* _fcontrols = &fcontrols;
1348 inst.iqueue->run([_row, _fcontrols]() {
1349 portctrl::frame_vector& fv = *CORE().mlogic->get_mfile().input;
1350 uint64_t vsize = fv.size();
1351 if(_row >= vsize)
1352 return;
1353 if(_row < real_first_editable(*_fcontrols, 0))
1354 return;
1355 int64_t delete_count = 0;
1356 for(uint64_t i = _row; i < vsize; i++)
1357 if(fv[i].sync())
1358 delete_count--;
1359 fv.resize(_row);
1361 max_subframe = row;
1362 recursing = false;
1363 signal_repaint();
1366 void wxeditor_movie::_moviepanel::do_set_stop_at_frame()
1368 CHECK_UI_THREAD;
1369 uint64_t curframe;
1370 uint64_t frame;
1371 inst.iqueue->run([&curframe]() {
1372 curframe = CORE().mlogic->get_movie().get_current_frame();
1374 try {
1375 std::string text = pick_text(m, "Frame", (stringfmt() << "Enter frame to stop at (currently at "
1376 << curframe << "):").str(), "");
1377 frame = parse_value<uint64_t>(text);
1378 } catch(canceled_exception& e) {
1379 return;
1380 } catch(std::exception& e) {
1381 wxMessageBox(wxT("Invalid value"), _T("Error"), wxICON_EXCLAMATION | wxOK, m);
1382 return;
1384 if(frame < curframe) {
1385 wxMessageBox(wxT("The movie is already past that point"), _T("Error"), wxICON_EXCLAMATION | wxOK, m);
1386 return;
1388 inst.iqueue->run([frame]() {
1389 set_stop_at_frame(frame);
1393 void wxeditor_movie::_moviepanel::on_mouse0(unsigned x, unsigned y, bool polarity, bool shift, unsigned X, unsigned Y)
1395 CHECK_UI_THREAD;
1396 if(y < 3)
1397 return;
1398 if(polarity) {
1399 press_x = x;
1400 press_line = spos + y - 3;
1402 pressed = polarity;
1403 if(polarity) {
1404 signal_repaint();
1405 return;
1407 uint64_t line = spos + y - 3;
1408 if(press_x < divcnt && x < divcnt) {
1409 //Press on frame count.
1410 uint64_t row1 = press_line;
1411 uint64_t row2 = line;
1412 if(row1 > row2)
1413 std::swap(row1, row2);
1414 do_append_frames(row2 - row1 + 1);
1416 for(auto i : fcontrols.get_controlinfo()) {
1417 unsigned off = divcnt + 1;
1418 unsigned idx = i.index;
1419 if((press_x >= i.position_left + off && press_x < i.position_left + i.reserved + off) &&
1420 (x >= i.position_left + off && x < i.position_left + i.reserved + off)) {
1421 if(i.type == 0)
1422 do_toggle_buttons(idx, press_line, line, false);
1423 else if(i.type == 1) {
1424 if(shift) {
1425 if(press_line == line && (i.port || i.controller))
1426 try {
1427 wxPoint spos = GetScreenPosition();
1428 popup_axis_panel(line, i, spos.x + X, spos.y + Y);
1429 } catch(canceled_exception& e) {
1431 } else
1432 do_alter_axis(idx, press_line, line);
1438 void wxeditor_movie::_moviepanel::popup_axis_panel(uint64_t row, control_info ci, unsigned screenX, unsigned screenY)
1440 CHECK_UI_THREAD;
1441 control_info ciX;
1442 control_info ciY;
1443 control_info ci2 = find_paired(ci, fcontrols.get_controlinfo());
1444 if(ci.index == ci2.index) {
1445 ciX = ciY = ci;
1446 } else if(ci2.index < ci.index) {
1447 ciX = ci2;
1448 ciY = ci;
1449 } else {
1450 ciX = ci;
1451 ciY = ci2;
1453 frame_controls* _fcontrols = &fcontrols;
1454 if(ciX.index == ciY.index) {
1455 auto c = prompt_coodinates_window(m, NULL, 256, 0, ciX, ciX, screenX, screenY);
1456 inst.iqueue->run([ciX, row, c, _fcontrols]() {
1457 uint64_t fedit = real_first_editable(*_fcontrols, ciX.index);
1458 if(row < fedit) return;
1459 portctrl::frame_vector& fv = *CORE().mlogic->get_mfile().input;
1460 portctrl::frame cf = fv[row];
1461 _fcontrols->write_index(cf, ciX.index, c.first);
1463 signal_repaint();
1464 } else if(ci.axistype == portctrl::button::TYPE_LIGHTGUN) {
1465 framebuffer::raw& _fb = inst.fbuf->render_get_latest_screen();
1466 framebuffer::fb<false> fb;
1467 auto osize = std::make_pair(_fb.get_width(), _fb.get_height());
1468 auto size = inst.rom->lightgun_scale();
1469 fb.reallocate(osize.first, osize.second, false);
1470 fb.copy_from(_fb, 1, 1);
1471 inst.fbuf->render_get_latest_screen_end();
1472 std::vector<uint8_t> buf;
1473 buf.resize(3 * (ciX.rmax - ciX.rmin + 1) * (ciY.rmax - ciY.rmin + 1));
1474 unsigned offX = -ciX.rmin;
1475 unsigned offY = -ciY.rmin;
1476 struct SwsContext* ctx = sws_getContext(osize.first, osize.second, PIX_FMT_RGBA,
1477 size.first, size.second, PIX_FMT_BGR24, SWS_POINT, NULL, NULL, NULL);
1478 uint8_t* srcp[1];
1479 int srcs[1];
1480 uint8_t* dstp[1];
1481 int dsts[1];
1482 srcs[0] = 4 * (fb.rowptr(1) - fb.rowptr(0));
1483 dsts[0] = 3 * (ciX.rmax - ciX.rmin + 1);
1484 srcp[0] = reinterpret_cast<unsigned char*>(fb.rowptr(0));
1485 dstp[0] = &buf[3 * (offY * (ciX.rmax - ciX.rmin + 1) + offX)];
1486 memset(&buf[0], 0, buf.size());
1487 sws_scale(ctx, srcp, srcs, 0, size.second, dstp, dsts);
1488 sws_freeContext(ctx);
1489 auto c = prompt_coodinates_window(m, &buf[0], (ciX.rmax - ciX.rmin + 1), (ciY.rmax - ciY.rmin + 1),
1490 ciX, ciY, screenX, screenY);
1491 inst.iqueue->run([ciX, ciY, row, c, _fcontrols]() {
1492 uint64_t fedit = real_first_editable(*_fcontrols, ciX.index);
1493 fedit = max(fedit, real_first_editable(*_fcontrols, ciY.index));
1494 if(row < fedit) return;
1495 portctrl::frame_vector& fv = *CORE().mlogic->get_mfile().input;
1496 portctrl::frame cf = fv[row];
1497 _fcontrols->write_index(cf, ciX.index, c.first);
1498 _fcontrols->write_index(cf, ciY.index, c.second);
1500 signal_repaint();
1501 } else {
1502 auto c = prompt_coodinates_window(m, NULL, 256, 256, ciX, ciY, screenX, screenY);
1503 inst.iqueue->run([ciX, ciY, row, c, _fcontrols]() {
1504 uint64_t fedit = real_first_editable(*_fcontrols, ciX.index);
1505 fedit = max(fedit, real_first_editable(*_fcontrols, ciY.index));
1506 if(row < fedit) return;
1507 portctrl::frame_vector& fv = *CORE().mlogic->get_mfile().input;
1508 portctrl::frame cf = fv[row];
1509 _fcontrols->write_index(cf, ciX.index, c.first);
1510 _fcontrols->write_index(cf, ciY.index, c.second);
1512 signal_repaint();
1516 void wxeditor_movie::_moviepanel::do_scroll_to_frame()
1518 CHECK_UI_THREAD;
1519 uint64_t frame;
1520 try {
1521 std::string text = pick_text(m, "Frame", (stringfmt() << "Enter frame to scroll to:").str(), "");
1522 frame = parse_value<uint64_t>(text);
1523 } catch(canceled_exception& e) {
1524 return;
1525 } catch(std::exception& e) {
1526 wxMessageBox(wxT("Invalid value"), _T("Error"), wxICON_EXCLAMATION | wxOK, m);
1527 return;
1529 uint64_t wouldbe = 0;
1530 uint64_t low = 0;
1531 uint64_t high = max_subframe;
1532 while(low < high) {
1533 wouldbe = (low + high) / 2;
1534 if(subframe_to_frame[wouldbe] < frame)
1535 low = wouldbe;
1536 else if(subframe_to_frame[wouldbe] > frame)
1537 high = wouldbe;
1538 else
1539 break;
1541 while(wouldbe > 1 && subframe_to_frame[wouldbe - 1] == frame)
1542 wouldbe--;
1543 moviepos = wouldbe;
1544 signal_repaint();
1547 void wxeditor_movie::_moviepanel::do_scroll_to_current_frame()
1549 moviepos = cached_cffs;
1550 signal_repaint();
1553 void wxeditor_movie::_moviepanel::on_popup_menu(wxCommandEvent& e)
1555 CHECK_UI_THREAD;
1556 wxMenuItem* tmpitem;
1557 int id = e.GetId();
1559 unsigned port = 0;
1560 unsigned controller = 0;
1561 for(auto i : fcontrols.get_controlinfo())
1562 if(i.index == press_index) {
1563 port = i.port;
1564 controller = i.controller;
1567 switch(id) {
1568 case wxID_TOGGLE:
1569 do_toggle_buttons(press_index, rpress_line, press_line, false);
1570 return;
1571 case wxID_CHANGE:
1572 do_alter_axis(press_index, rpress_line, press_line);
1573 return;
1574 case wxID_CLEAR:
1575 do_toggle_buttons(press_index, rpress_line, press_line, true);
1576 return;
1577 case wxID_SWEEP:
1578 do_sweep_axis(press_index, rpress_line, press_line);
1579 return;
1580 case wxID_APPEND_FRAME:
1581 do_append_frames(1);
1582 return;
1583 case wxID_APPEND_FRAMES:
1584 do_append_frames();
1585 return;
1586 case wxID_INSERT_AFTER:
1587 do_insert_frame_after(press_line, false);
1588 return;
1589 case wxID_INSERT_AFTER_MULTIPLE:
1590 do_insert_frame_after(press_line, true);
1591 return;
1592 case wxID_DELETE_FRAME:
1593 do_delete_frame(press_line, rpress_line, true);
1594 return;
1595 case wxID_DELETE_SUBFRAME:
1596 do_delete_frame(press_line, rpress_line, false);
1597 return;
1598 case wxID_TRUNCATE:
1599 do_truncate(press_line);
1600 return;
1601 case wxID_RUN_TO_FRAME:
1602 do_set_stop_at_frame();
1603 return;
1604 case wxID_SCROLL_FRAME:
1605 do_scroll_to_frame();
1606 return;
1607 case wxID_SCROLL_CURRENT_FRAME:
1608 do_scroll_to_current_frame();
1609 return;
1610 case wxID_POSITION_LOCK:
1611 if(!current_popup)
1612 return;
1613 tmpitem = current_popup->FindItem(wxID_POSITION_LOCK);
1614 position_locked = tmpitem->IsChecked();
1615 return;
1616 case wxID_CHANGE_LINECOUNT:
1617 try {
1618 std::string text = pick_text(m, "Set number of lines", "Set number of lines visible:",
1619 (stringfmt() << lines_to_display).str());
1620 unsigned tmp = parse_value<unsigned>(text);
1621 if(tmp < 1 || tmp > 255)
1622 throw std::runtime_error("Value out of range");
1623 lines_to_display = tmp;
1624 m->get_scroll()->set_page_size(lines_to_display);
1625 } catch(canceled_exception& e) {
1626 return;
1627 } catch(std::exception& e) {
1628 wxMessageBox(wxT("Invalid value"), _T("Error"), wxICON_EXCLAMATION | wxOK, m);
1629 return;
1631 signal_repaint();
1632 return;
1633 case wxID_COPY_FRAMES:
1634 if(press_index == std::numeric_limits<unsigned>::max())
1635 do_copy(rpress_line, press_line);
1636 else
1637 do_copy(rpress_line, press_line, port, controller);
1638 return;
1639 case wxID_CUT_FRAMES:
1640 if(press_index == std::numeric_limits<unsigned>::max())
1641 do_cut(rpress_line, press_line);
1642 else
1643 do_cut(rpress_line, press_line, port, controller);
1644 return;
1645 case wxID_PASTE_FRAMES:
1646 if(press_index == std::numeric_limits<unsigned>::max() || clipboard_get_data_type() == 1)
1647 do_paste(press_line, false);
1648 else
1649 do_paste(press_line, port, controller, false);
1650 return;
1651 case wxID_PASTE_APPEND:
1652 if(press_index == std::numeric_limits<unsigned>::max() || clipboard_get_data_type() == 1)
1653 do_paste(press_line, true);
1654 else
1655 do_paste(press_line, port, controller, true);
1656 return;
1657 case wxID_INSERT_CONTROLLER_AFTER:
1658 if(press_index == std::numeric_limits<unsigned>::max())
1660 else
1661 do_insert_controller(press_line, port, controller);
1662 return;
1663 case wxID_DELETE_CONTROLLER_SUBFRAMES:
1664 if(press_index == std::numeric_limits<unsigned>::max())
1666 else
1667 do_delete_controller(press_line, rpress_line, port, controller);
1668 return;
1669 case wxID_MBRANCH_NEW:
1670 try {
1671 std::string newname;
1672 std::string oldname;
1673 inst.iqueue->run([&oldname]() { oldname = CORE().mbranch->get(); });
1674 newname = pick_text(this, "Enter new branch name", "Enter name for a new branch (to fork "
1675 "from " + inst.mbranch->name(oldname) + "):", "", false);
1676 inst.iqueue->run_async([this, oldname, newname] {
1677 CORE().mbranch->_new(newname, oldname);
1678 }, [this](std::exception& e) {
1679 show_exception(this, "Error creating branch", "Can't create branch", e);
1681 } catch(canceled_exception& e) {
1683 return;
1684 case wxID_MBRANCH_IMPORT:
1685 try {
1686 int mode;
1687 std::string filename;
1688 std::string branch;
1689 std::string dbranch;
1690 auto g = choose_file_load(this, "Choose file to import", UI_get_project_moviepath(inst),
1691 exp_imp_type());
1692 filename = g.first;
1693 mode = g.second;
1694 if(mode == MBRANCH_IMPORT_MOVIE) {
1695 std::set<std::string> brlist;
1696 try {
1697 inst.iqueue->run([this, filename, &brlist]() {
1698 brlist = CORE().mbranch->_movie_branches(filename);
1700 } catch(std::exception& e) {
1701 show_exception(this, "Can't get branches in movie", "", e);
1702 return;
1704 if(brlist.size() == 0) {
1705 show_message_ok(this, "No branches in movie file",
1706 "Can't import movie file as it has no branches", wxICON_EXCLAMATION);
1707 return;
1708 } else if(brlist.size() == 1) {
1709 branch = *brlist.begin();
1710 } else {
1711 std::vector<std::string> choices(brlist.begin(), brlist.end());
1712 branch = pick_among(this, "Select branch to import",
1713 "Select branch to import", choices, 0);
1715 //Import from movie.
1717 dbranch = pick_text(this, "Enter new branch name", "Enter name for an imported branch:",
1718 branch, false);
1719 inst.iqueue->run_async([this, filename, branch, dbranch, mode]() {
1720 CORE().mbranch->import_branch(filename, branch, dbranch, mode);
1721 }, [this](std::exception& e) {
1722 show_exception(this, "Can't import branch", "", e);
1724 } catch(canceled_exception& e) {
1726 return;
1727 case wxID_MBRANCH_EXPORT:
1728 try {
1729 int mode;
1730 std::string file;
1731 auto g = choose_file_save(this, "Choose file to export", UI_get_project_moviepath(inst),
1732 exp_imp_type());
1733 file = g.first;
1734 mode = g.second;
1735 inst.iqueue->run_async([this, file, mode]() {
1736 std::string bname = CORE().mbranch->get();
1737 CORE().mbranch->export_branch(file, bname, mode == MBRANCH_IMPORT_BINARY);
1738 }, [this](std::exception& e) {
1739 show_exception(this, "Can't export branch", "", e);
1741 } catch(canceled_exception& e) {
1743 return;
1744 case wxID_MBRANCH_RENAME:
1745 try {
1746 std::string newname;
1747 std::string oldname;
1748 std::set<std::string> list;
1749 inst.iqueue->run([&list]() { list = CORE().mbranch->enumerate(); });
1750 std::vector<std::string> choices(list.begin(), list.end());
1751 oldname = pick_among(this, "Select branch to rename", "Select branch to rename",
1752 choices, 0);
1753 newname = pick_text(this, "Enter new branch name", "Enter name for a new branch (to rename "
1754 "'" + inst.mbranch->name(oldname) + "'):", oldname, false);
1755 inst.iqueue->run_async([this, oldname, newname] {
1756 CORE().mbranch->rename(oldname, newname);
1757 }, [this](std::exception& e) {
1758 show_exception(this, "Error renaming branch", "Can't rename branch", e);
1760 } catch(canceled_exception& e) {
1762 return;
1763 case wxID_MBRANCH_DELETE:
1764 try {
1765 std::string oldname;
1766 std::set<std::string> list;
1767 inst.iqueue->run([&list]() { list = CORE().mbranch->enumerate(); });
1768 std::vector<std::string> choices(list.begin(), list.end());
1769 oldname = pick_among(this, "Select branch to delete", "Select branch to delete",
1770 choices, 0);
1771 inst.iqueue->run_async([this, oldname] {
1772 CORE().mbranch->_delete(oldname);
1773 }, [this](std::exception& e) {
1774 show_exception(this, "Error deleting branch", "Can't delete branch", e);
1776 } catch(canceled_exception& e) {
1778 return;
1780 if(id >= wxID_MBRANCH_FIRST && id <= wxID_MBRANCH_LAST) {
1781 if(!branch_names.count(id)) return;
1782 std::string name = branch_names[id];
1783 inst.iqueue->run_async([this, name]() {
1784 CORE().mbranch->set(name);
1785 }, [this](std::exception& e) {
1786 show_exception(this, "Error changing branch", "Can't change branch", e);
1791 uint64_t wxeditor_movie::_moviepanel::first_editable(unsigned index)
1793 uint64_t cffs = cached_cffs;
1794 if(!subframe_to_frame.count(cffs))
1795 return cffs;
1796 uint64_t f = subframe_to_frame[cffs];
1797 portctrl::counters& pv = inst.mlogic->get_movie().get_pollcounters();
1798 uint32_t pc = fcontrols.read_pollcount(pv, index);
1799 for(uint32_t i = 1; i < pc; i++)
1800 if(!subframe_to_frame.count(cffs + i) || subframe_to_frame[cffs + i] > f)
1801 return cffs + i;
1802 return cffs + pc;
1805 uint64_t wxeditor_movie::_moviepanel::first_nextframe()
1807 uint64_t base = first_editable(0);
1808 if(!subframe_to_frame.count(cached_cffs))
1809 return cached_cffs;
1810 uint64_t f = subframe_to_frame[cached_cffs];
1811 for(uint32_t i = 0;; i++)
1812 if(!subframe_to_frame.count(base + i) || subframe_to_frame[base + i] > f)
1813 return base + i;
1816 void wxeditor_movie::_moviepanel::on_mouse1(unsigned x, unsigned y, bool polarity) {}
1817 void wxeditor_movie::_moviepanel::on_mouse2(unsigned x, unsigned y, bool polarity)
1819 CHECK_UI_THREAD;
1820 if(polarity) {
1821 //Pressing mouse, just record line it was pressed on.
1822 rpress_line = spos + y - 3;
1823 press_x = x;
1824 pressed = true;
1825 signal_repaint();
1826 return;
1828 //Releasing mouse, open popup menu.
1829 pressed = false;
1830 unsigned off = divcnt + 1;
1831 press_x = x;
1832 if(y < 3) {
1833 signal_repaint();
1834 return;
1836 press_line = spos + y - 3;
1837 wxMenu menu;
1838 current_popup = &menu;
1839 menu.Connect(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(wxeditor_movie::_moviepanel::on_popup_menu),
1840 NULL, this);
1842 //Find what controller is the click on.
1843 bool clicked_button = false;
1844 control_info clicked;
1845 std::string controller_name;
1846 if(press_x < off) {
1847 clicked_button = false;
1848 press_index = std::numeric_limits<unsigned>::max();
1849 } else {
1850 for(auto i : fcontrols.get_controlinfo())
1851 if(press_x >= i.position_left + off && press_x < i.position_left + i.reserved + off) {
1852 if(i.type == 0 || i.type == 1) {
1853 clicked_button = true;
1854 clicked = i;
1855 controller_name = (stringfmt() << "controller " << i.port << "-"
1856 << (i.controller + 1)).str();
1857 press_index = i.index;
1862 //Find first editable frame, controllerframe and buttonframe.
1863 bool not_editable = !inst.mlogic->get_movie().readonly_mode();
1864 uint64_t eframe_low = first_editable(0);
1865 uint64_t ebutton_low = clicked_button ? first_editable(clicked.index) : std::numeric_limits<uint64_t>::max();
1866 uint64_t econtroller_low = ebutton_low;
1867 for(auto i : fcontrols.get_controlinfo())
1868 if(i.port == clicked.port && i.controller == clicked.controller && (i.type == 0 || i.type == 1))
1869 econtroller_low = max(econtroller_low, first_editable(i.index));
1871 bool click_zero = (clicked_button && !clicked.port && !clicked.controller);
1872 bool enable_append_frame = !not_editable;
1873 bool enable_toggle_button = false;
1874 bool enable_change_axis = false;
1875 bool enable_sweep_axis = false;
1876 bool enable_insert_frame = false;
1877 bool enable_insert_controller = false;
1878 bool enable_delete_frame = false;
1879 bool enable_delete_subframe = false;
1880 bool enable_delete_controller_subframe = false;
1881 bool enable_truncate_movie = false;
1882 bool enable_cut_frame = false;
1883 bool enable_copy_frame = false;
1884 bool enable_paste_frame = false;
1885 bool enable_paste_append = false;
1886 std::string copy_title;
1887 std::string paste_title;
1889 //Toggle button is enabled if clicked on button and either end is in valid range.
1890 enable_toggle_button = (!not_editable && clicked_button && clicked.type == 0 && ((press_line >= ebutton_low &&
1891 press_line < linecount) || (rpress_line >= ebutton_low && rpress_line < linecount)));
1892 //Change axis is enabled in similar conditions, except if type is axis.
1893 enable_change_axis = (!not_editable && clicked_button && clicked.type == 1 && ((press_line >= ebutton_low &&
1894 press_line < linecount) || (rpress_line >= ebutton_low && rpress_line < linecount)));
1895 //Sweep axis is enabled if change axis is enabled and lines don't match.
1896 enable_sweep_axis = (enable_change_axis && press_line != rpress_line);
1897 //Insert frame is enabled if this frame is completely editable and press and release lines match.
1898 enable_insert_frame = (!not_editable && press_line + 1 >= eframe_low && press_line < linecount &&
1899 press_line == rpress_line);
1900 //Insert controller frame is enabled if controller is completely editable and lines match.
1901 enable_insert_controller = (!not_editable && clicked_button && press_line >= econtroller_low &&
1902 press_line < linecount && press_line == rpress_line);
1903 enable_insert_controller = enable_insert_controller && (clicked.port || clicked.controller);
1904 //Delete frame is enabled if range is completely editable (relative to next-frame).
1905 enable_delete_frame = (!not_editable && press_line >= first_nextframe() && press_line < linecount &&
1906 rpress_line >= first_nextframe() && rpress_line < linecount);
1907 //Delete subframe is enabled if range is completely editable.
1908 enable_delete_subframe = (!not_editable && press_line >= eframe_low && press_line < linecount &&
1909 rpress_line >= eframe_low && rpress_line < linecount);
1910 //Delete controller subframe is enabled if range is completely controller-editable.
1911 enable_delete_controller_subframe = (!not_editable && clicked_button && press_line >= econtroller_low &&
1912 press_line < linecount && rpress_line >= econtroller_low && rpress_line < linecount);
1913 enable_delete_controller_subframe = enable_delete_controller_subframe && (clicked.port || clicked.controller);
1914 //Truncate movie is enabled if lines match and is completely editable.
1915 enable_truncate_movie = (!not_editable && press_line == rpress_line && press_line >= eframe_low &&
1916 press_line < linecount);
1917 //Cut frames is enabled if range is editable (possibly controller-editable).
1918 if(clicked_button)
1919 enable_cut_frame = (!not_editable && press_line >= econtroller_low && press_line < linecount
1920 && rpress_line >= econtroller_low && rpress_line < linecount && !click_zero);
1921 else
1922 enable_cut_frame = (!not_editable && press_line >= eframe_low && press_line < linecount
1923 && rpress_line >= eframe_low && rpress_line < linecount);
1924 if(clicked_button && clipboard_get_data_type() == 0) {
1925 enable_paste_append = (!not_editable && linecount >= eframe_low);
1926 enable_paste_frame = (!not_editable && press_line >= econtroller_low && press_line < linecount
1927 && rpress_line >= econtroller_low && rpress_line < linecount && !click_zero);
1928 } else if(clipboard_get_data_type() == 1) {
1929 enable_paste_append = (!not_editable && linecount >= econtroller_low);
1930 enable_paste_frame = (!not_editable && press_line >= eframe_low && press_line < linecount
1931 && rpress_line >= eframe_low && rpress_line < linecount);
1933 //Copy frames is enabled if range exists.
1934 enable_copy_frame = (press_line < linecount && rpress_line < linecount);
1935 copy_title = (clicked_button ? controller_name : "frames");
1936 paste_title = ((clipboard_get_data_type() == 0) ? copy_title : "frames");
1938 if(clipboard_get_data_type() == 0 && click_zero) enable_paste_append = enable_paste_frame = false;
1940 if(enable_toggle_button)
1941 menu.Append(wxID_TOGGLE, towxstring(U"Toggle " + clicked.title));
1942 if(enable_change_axis)
1943 menu.Append(wxID_CHANGE, towxstring(U"Change " + clicked.title));
1944 if(enable_sweep_axis)
1945 menu.Append(wxID_SWEEP, towxstring(U"Sweep " + clicked.title));
1946 if(enable_toggle_button || enable_change_axis)
1947 menu.Append(wxID_CLEAR, towxstring(U"Clear " + clicked.title));
1948 if(enable_toggle_button || enable_change_axis || enable_sweep_axis)
1949 menu.AppendSeparator();
1950 menu.Append(wxID_INSERT_AFTER, wxT("Insert frame after"))->Enable(enable_insert_frame);
1951 menu.Append(wxID_INSERT_AFTER_MULTIPLE, wxT("Insert frames after"))->Enable(enable_insert_frame);
1952 menu.Append(wxID_INSERT_CONTROLLER_AFTER, wxT("Insert controller frame"))
1953 ->Enable(enable_insert_controller);
1954 menu.Append(wxID_APPEND_FRAME, wxT("Append frame"))->Enable(enable_append_frame);
1955 menu.Append(wxID_APPEND_FRAMES, wxT("Append frames..."))->Enable(enable_append_frame);
1956 menu.AppendSeparator();
1957 menu.Append(wxID_DELETE_FRAME, wxT("Delete frame(s)"))->Enable(enable_delete_frame);
1958 menu.Append(wxID_DELETE_SUBFRAME, wxT("Delete subframe(s)"))->Enable(enable_delete_subframe);
1959 menu.Append(wxID_DELETE_CONTROLLER_SUBFRAMES, wxT("Delete controller subframes(s)"))
1960 ->Enable(enable_delete_controller_subframe);
1961 menu.AppendSeparator();
1962 menu.Append(wxID_TRUNCATE, wxT("Truncate movie"))->Enable(enable_truncate_movie);
1963 menu.AppendSeparator();
1964 menu.Append(wxID_CUT_FRAMES, towxstring("Cut " + copy_title))->Enable(enable_cut_frame);
1965 menu.Append(wxID_COPY_FRAMES, towxstring("Copy " + copy_title))->Enable(enable_copy_frame);
1966 menu.Append(wxID_PASTE_FRAMES, towxstring("Paste " + paste_title))->Enable(enable_paste_frame);
1967 menu.Append(wxID_PASTE_APPEND, towxstring("Paste append " + paste_title))->Enable(enable_paste_append);
1968 menu.AppendSeparator();
1969 menu.Append(wxID_SCROLL_FRAME, wxT("Scroll to frame..."));
1970 menu.Append(wxID_SCROLL_CURRENT_FRAME, wxT("Scroll to current frame"));
1971 menu.Append(wxID_RUN_TO_FRAME, wxT("Run to frame..."));
1972 menu.Append(wxID_CHANGE_LINECOUNT, wxT("Change number of lines visible"));
1973 menu.AppendCheckItem(wxID_POSITION_LOCK, wxT("Lock scroll to playback"))->Check(position_locked);
1974 menu.AppendSeparator();
1976 wxMenu* branches_submenu = new wxMenu();
1977 branches_submenu->Append(wxID_MBRANCH_NEW, wxT("New branch..."));
1978 branches_submenu->Append(wxID_MBRANCH_IMPORT, wxT("Import branch..."));
1979 branches_submenu->Append(wxID_MBRANCH_EXPORT, wxT("Export branch..."));
1980 branches_submenu->Append(wxID_MBRANCH_RENAME, wxT("Rename branch..."));
1981 branches_submenu->Append(wxID_MBRANCH_DELETE, wxT("Delete branch..."));
1982 branches_submenu->AppendSeparator();
1983 std::set<std::string> list;
1984 std::string current;
1985 bool ro;
1986 inst.iqueue->run([&list, &current, &ro]() {
1987 list = CORE().mbranch->enumerate();
1988 current = CORE().mbranch->get();
1989 ro = CORE().mlogic->get_movie().readonly_mode();
1991 int ass_id = wxID_MBRANCH_FIRST;
1992 for(auto i : list) {
1993 bool selected = (i == current);
1994 wxMenuItem* it;
1995 it = branches_submenu->AppendCheckItem(ass_id, towxstring(inst.mbranch->name(i)));
1996 branch_names[ass_id++] = i;
1997 if(selected) it->Check(selected);
1998 it->Enable(ro);
2000 menu.AppendSubMenu(branches_submenu, wxT("Branches"));
2001 menu.Connect(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(wxeditor_movie::_moviepanel::on_popup_menu),
2002 NULL, this);
2003 branches_submenu->Connect(wxEVT_COMMAND_MENU_SELECTED,
2004 wxCommandEventHandler(wxeditor_movie::_moviepanel::on_popup_menu), NULL, this);
2005 PopupMenu(&menu);
2006 //delete branches_submenu;
2007 signal_repaint();
2010 int wxeditor_movie::_moviepanel::get_lines()
2012 portctrl::frame_vector& fv = *inst.mlogic->get_mfile().input;
2013 return fv.size();
2016 void wxeditor_movie::_moviepanel::signal_repaint()
2018 CHECK_UI_THREAD;
2019 if(requested || recursing)
2020 return;
2021 auto s = m->get_scroll();
2022 requested = true;
2023 uint32_t width, height;
2024 uint64_t lines;
2025 wxeditor_movie* m2 = m;
2026 uint64_t old_cached_cffs = cached_cffs;
2027 uint32_t prev_width, prev_height;
2028 bool done_again = false;
2029 do_again:
2030 inst.iqueue->run([&lines, &width, &height, m2, this]() {
2031 lines = this->get_lines();
2032 if(lines < lines_to_display)
2033 this->moviepos = 0;
2034 else if(this->moviepos > lines - lines_to_display)
2035 this->moviepos = lines - lines_to_display;
2036 this->render(fb, moviepos);
2037 auto x = fb.get_characters();
2038 width = x.first;
2039 height = x.second;
2041 if(old_cached_cffs != cached_cffs && position_locked && !done_again) {
2042 moviepos = cached_cffs;
2043 done_again = true;
2044 goto do_again;
2046 prev_width = new_width;
2047 prev_height = new_height;
2048 new_width = width;
2049 new_height = height;
2050 movielines = lines;
2051 if(s) {
2052 s->set_range(lines);
2053 s->set_position(moviepos);
2055 auto size = fb.get_pixels();
2056 pixels.resize(size.first * size.second * 3);
2057 fb.render((char*)&pixels[0]);
2058 if(prev_width != new_width || prev_height != new_height) {
2059 auto cell = fb.get_cell();
2060 SetMinSize(wxSize(new_width * cell.first, (lines_to_display + 3) * cell.second));
2061 if(new_width > 0 && s)
2062 m->Fit();
2064 linecount = lines;
2065 Refresh();
2068 void wxeditor_movie::_moviepanel::on_mouse(wxMouseEvent& e)
2070 CHECK_UI_THREAD;
2071 auto cell = fb.get_cell();
2072 if(e.LeftDown() && !e.ControlDown())
2073 on_mouse0(e.GetX() / cell.first, e.GetY() / cell.second, true, e.ShiftDown(), e.GetX(), e.GetY());
2074 if(e.LeftUp() && !e.ControlDown())
2075 on_mouse0(e.GetX() / cell.first, e.GetY() / cell.second, false, e.ShiftDown(), e.GetX(), e.GetY());
2076 if(e.MiddleDown())
2077 on_mouse1(e.GetX() / cell.first, e.GetY() / cell.second, true);
2078 if(e.MiddleUp())
2079 on_mouse1(e.GetX() / cell.first, e.GetY() / cell.second, false);
2080 if(e.RightDown() || (e.LeftDown() && e.ControlDown()))
2081 on_mouse2(e.GetX() / cell.first, e.GetY() / cell.second, true);
2082 if(e.RightUp() || (e.LeftUp() && e.ControlDown()))
2083 on_mouse2(e.GetX() / cell.first, e.GetY() / cell.second, false);
2084 auto s = m->get_scroll();
2085 unsigned speed = 1;
2086 if(e.ShiftDown())
2087 speed = 10;
2088 if(e.ShiftDown() && e.ControlDown())
2089 speed = 50;
2090 s->apply_wheel(e.GetWheelRotation(), e.GetWheelDelta(), speed);
2093 void wxeditor_movie::_moviepanel::on_erase(wxEraseEvent& e)
2095 //Blank.
2098 void wxeditor_movie::_moviepanel::on_paint(wxPaintEvent& e)
2100 CHECK_UI_THREAD;
2101 auto size = fb.get_pixels();
2102 if(!size.first || !size.second) {
2103 wxPaintDC dc(this);
2104 dc.Clear();
2105 requested = false;
2106 return;
2108 wxPaintDC dc(this);
2109 wxBitmap bmp(wxImage(size.first, size.second, &pixels[0], true));
2110 dc.DrawBitmap(bmp, 0, 0, false);
2111 requested = false;
2114 void wxeditor_movie::_moviepanel::do_copy(uint64_t row1, uint64_t row2, unsigned port, unsigned controller)
2116 frame_controls* _fcontrols = &fcontrols;
2117 uint64_t line = row1;
2118 uint64_t line2 = row2;
2119 if(line2 < line)
2120 std::swap(line, line2);
2121 std::string copied;
2122 inst.iqueue->run([port, controller, line, line2, _fcontrols, &copied]() {
2123 portctrl::frame_vector& fv = *CORE().mlogic->get_mfile().input;
2124 uint64_t vsize = fv.size();
2125 if(!vsize)
2126 return;
2127 uint64_t _line = min(line, vsize - 1);
2128 uint64_t _line2 = min(line2, vsize - 1);
2129 copied = encode_lines(*_fcontrols, fv, _line, _line2 + 1, port, controller);
2131 copy_to_clipboard(copied);
2134 void wxeditor_movie::_moviepanel::do_copy(uint64_t row1, uint64_t row2)
2136 uint64_t line = row1;
2137 uint64_t line2 = row2;
2138 if(line2 < line)
2139 std::swap(line, line2);
2140 std::string copied;
2141 inst.iqueue->run([line, line2, &copied]() {
2142 portctrl::frame_vector& fv = *CORE().mlogic->get_mfile().input;
2143 uint64_t vsize = fv.size();
2144 if(!vsize)
2145 return;
2146 uint64_t _line = min(line, vsize - 1);
2147 uint64_t _line2 = min(line2, vsize - 1);
2148 copied = encode_lines(fv, _line, _line2 + 1);
2150 copy_to_clipboard(copied);
2153 void wxeditor_movie::_moviepanel::do_cut(uint64_t row1, uint64_t row2, unsigned port, unsigned controller)
2155 do_copy(row1, row2, port, controller);
2156 do_delete_controller(row1, row2, port, controller);
2159 void wxeditor_movie::_moviepanel::do_cut(uint64_t row1, uint64_t row2)
2161 do_copy(row1, row2);
2162 do_delete_frame(row1, row2, false);
2165 void wxeditor_movie::_moviepanel::do_paste(uint64_t row, bool append)
2167 frame_controls* _fcontrols = &fcontrols;
2168 recursing = true;
2169 uint64_t _gapstart = row;
2170 std::string cliptext = copy_from_clipboard();
2171 inst.iqueue->run([_fcontrols, &cliptext, _gapstart, append]() {
2172 //Insert enough lines for the pasted content.
2173 uint64_t gapstart = _gapstart;
2174 if(!CORE().mlogic->get_movie().readonly_mode())
2175 return;
2176 uint64_t gaplen = 0;
2177 int64_t newframes = 0;
2179 std::istringstream y(cliptext);
2180 std::string z;
2181 if(!std::getline(y, z))
2182 return;
2183 istrip_CR(z);
2184 if(z != "lsnes-moviedata-whole")
2185 return;
2186 while(std::getline(y, z))
2187 gaplen++;
2189 portctrl::frame_vector& fv = *CORE().mlogic->get_mfile().input;
2190 uint64_t vsize = fv.size();
2191 if(gapstart < real_first_editable(*_fcontrols, 0))
2192 return;
2193 if(gapstart > vsize)
2194 return;
2195 portctrl::frame_vector::notify_freeze freeze(fv);
2196 if(append) gapstart = vsize;
2197 for(uint64_t i = 0; i < gaplen; i++)
2198 fv.append(fv.blank_frame(false));
2199 for(uint64_t i = vsize - 1; i >= gapstart && i <= vsize; i--)
2200 fv[i + gaplen] = fv[i];
2201 //Write the pasted frames.
2203 std::istringstream y(cliptext);
2204 std::string z;
2205 std::getline(y, z);
2206 uint64_t idx = gapstart;
2207 while(std::getline(y, z)) {
2208 fv[idx++].deserialize(z.c_str());
2209 if(fv[idx - 1].sync())
2210 newframes++;
2214 recursing = false;
2215 signal_repaint();
2218 void wxeditor_movie::_moviepanel::do_paste(uint64_t row, unsigned port, unsigned controller, bool append)
2220 if(!port && !controller)
2221 return;
2222 frame_controls* _fcontrols = &fcontrols;
2223 auto iset = controller_index_set(fcontrols, port, controller);
2224 recursing = true;
2225 uint64_t _gapstart = row;
2226 std::string cliptext = copy_from_clipboard();
2227 inst.iqueue->run([_fcontrols, iset, &cliptext, _gapstart, port, controller, append]() {
2228 //Insert enough lines for the pasted content.
2229 uint64_t gapstart = _gapstart;
2230 if(!CORE().mlogic->get_movie().readonly_mode())
2231 return;
2232 uint64_t gaplen = 0;
2233 int64_t newframes = 0;
2235 std::istringstream y(cliptext);
2236 std::string z;
2237 if(!std::getline(y, z))
2238 return;
2239 istrip_CR(z);
2240 if(z != "lsnes-moviedata-controller")
2241 return;
2242 while(std::getline(y, z)) {
2243 gaplen++;
2244 newframes++;
2247 portctrl::frame_vector& fv = *CORE().mlogic->get_mfile().input;
2248 uint64_t vsize = fv.size();
2249 if(gapstart < real_first_editable(*_fcontrols, iset))
2250 return;
2251 if(gapstart > vsize)
2252 return;
2253 portctrl::frame_vector::notify_freeze freeze(fv);
2254 if(append) gapstart = vsize;
2255 for(uint64_t i = 0; i < gaplen; i++)
2256 fv.append(fv.blank_frame(true));
2257 move_index_set(*_fcontrols, fv, gapstart, gapstart + gaplen, vsize - gapstart, iset);
2258 //Write the pasted frames.
2260 std::istringstream y(cliptext);
2261 std::string z;
2262 std::getline(y, z);
2263 uint64_t idx = gapstart;
2264 while(std::getline(y, z)) {
2265 portctrl::frame f = fv[idx++];
2266 decode_line(*_fcontrols, f, z, port, controller);
2270 recursing = false;
2271 signal_repaint();
2274 void wxeditor_movie::_moviepanel::do_insert_controller(uint64_t row, unsigned port, unsigned controller)
2276 if(!port && !controller)
2277 return;
2278 frame_controls* _fcontrols = &fcontrols;
2279 auto iset = controller_index_set(fcontrols, port, controller);
2280 recursing = true;
2281 uint64_t gapstart = row;
2282 inst.iqueue->run([_fcontrols, iset, gapstart, port, controller]() {
2283 //Insert enough lines for the pasted content.
2284 if(!CORE().mlogic->get_movie().readonly_mode())
2285 return;
2286 portctrl::frame_vector& fv = *CORE().mlogic->get_mfile().input;
2287 uint64_t vsize = fv.size();
2288 if(gapstart < real_first_editable(*_fcontrols, iset))
2289 return;
2290 if(gapstart > vsize)
2291 return;
2292 fv.append(fv.blank_frame(true));
2293 move_index_set(*_fcontrols, fv, gapstart, gapstart + 1, vsize - gapstart, iset);
2294 zero_index_set(*_fcontrols, fv, gapstart, 1, iset);
2296 recursing = false;
2297 signal_repaint();
2300 void wxeditor_movie::_moviepanel::do_delete_controller(uint64_t row1, uint64_t row2, unsigned port,
2301 unsigned controller)
2303 if(!port && !controller)
2304 return;
2305 frame_controls* _fcontrols = &fcontrols;
2306 auto iset = controller_index_set(fcontrols, port, controller);
2307 recursing = true;
2308 if(row1 > row2) std::swap(row1, row2);
2309 uint64_t gapstart = row1;
2310 uint64_t gaplen = row2 - row1 + 1;
2311 inst.iqueue->run([_fcontrols, iset, gapstart, gaplen, port, controller]() {
2312 //Insert enough lines for the pasted content.
2313 if(!CORE().mlogic->get_movie().readonly_mode())
2314 return;
2315 portctrl::frame_vector& fv = *CORE().mlogic->get_mfile().input;
2316 uint64_t vsize = fv.size();
2317 if(gapstart < real_first_editable(*_fcontrols, iset))
2318 return;
2319 if(gapstart > vsize)
2320 return;
2321 move_index_set(*_fcontrols, fv, gapstart + gaplen, gapstart, vsize - gapstart - gaplen, iset);
2322 zero_index_set(*_fcontrols, fv, vsize - gaplen, gaplen, iset);
2324 recursing = false;
2325 signal_repaint();
2329 wxeditor_movie::wxeditor_movie(emulator_instance& _inst, wxWindow* parent)
2330 : wxDialog(parent, wxID_ANY, wxT("lsnes: Edit movie"), wxDefaultPosition, wxSize(-1, -1)), inst(_inst)
2332 CHECK_UI_THREAD;
2333 closing = false;
2334 Centre();
2335 wxFlexGridSizer* top_s = new wxFlexGridSizer(2, 1, 0, 0);
2336 SetSizer(top_s);
2338 wxBoxSizer* panel_s = new wxBoxSizer(wxHORIZONTAL);
2339 moviescroll = NULL;
2340 panel_s->Add(moviepanel = new _moviepanel(this, inst), 1, wxGROW);
2341 panel_s->Add(moviescroll = new scroll_bar(this, wxID_ANY, true), 0, wxGROW);
2342 top_s->Add(panel_s, 1, wxGROW);
2344 moviescroll->set_page_size(lines_to_display);
2345 moviescroll->set_handler([this](scroll_bar& s) {
2346 this->moviepanel->moviepos = s.get_position();
2347 this->moviepanel->signal_repaint();
2349 moviepanel->Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(wxeditor_movie::on_keyboard_down), NULL, this);
2350 moviepanel->Connect(wxEVT_KEY_UP, wxKeyEventHandler(wxeditor_movie::on_keyboard_up), NULL, this);
2352 wxBoxSizer* pbutton_s = new wxBoxSizer(wxHORIZONTAL);
2353 pbutton_s->AddStretchSpacer();
2354 pbutton_s->Add(closebutton = new wxButton(this, wxID_OK, wxT("Close")), 0, wxGROW);
2355 closebutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
2356 wxCommandEventHandler(wxeditor_movie::on_close), NULL, this);
2357 top_s->Add(pbutton_s, 0, wxGROW);
2359 moviepanel->SetFocus();
2360 moviescroll->Connect(wxEVT_SET_FOCUS, wxFocusEventHandler(wxeditor_movie::on_focus_wrong), NULL, this);
2361 closebutton->Connect(wxEVT_SET_FOCUS, wxFocusEventHandler(wxeditor_movie::on_focus_wrong), NULL, this);
2362 Connect(wxEVT_SET_FOCUS, wxFocusEventHandler(wxeditor_movie::on_focus_wrong), NULL, this);
2364 panel_s->SetSizeHints(this);
2365 pbutton_s->SetSizeHints(this);
2366 top_s->SetSizeHints(this);
2367 Connect(wxEVT_CLOSE_WINDOW, wxCloseEventHandler(wxeditor_movie::on_wclose));
2368 Fit();
2370 moviepanel->signal_repaint();
2373 bool wxeditor_movie::ShouldPreventAppExit() const { return false; }
2375 void wxeditor_movie::on_close(wxCommandEvent& e)
2377 CHECK_UI_THREAD;
2378 Destroy();
2379 closing = true;
2382 void wxeditor_movie::on_wclose(wxCloseEvent& e)
2384 CHECK_UI_THREAD;
2385 bool wasc = closing;
2386 closing = true;
2387 if(!wasc)
2388 Destroy();
2391 void wxeditor_movie::update()
2393 moviepanel->signal_repaint();
2396 scroll_bar* wxeditor_movie::get_scroll()
2398 return moviescroll;
2401 void wxeditor_movie::on_focus_wrong(wxFocusEvent& e)
2403 CHECK_UI_THREAD;
2404 moviepanel->SetFocus();
2407 void wxeditor_movie_display(wxWindow* parent, emulator_instance& inst)
2409 CHECK_UI_THREAD;
2410 auto e = movieeditors.lookup(inst);
2411 if(e) {
2412 e->Raise();
2413 return;
2415 movieeditors.create(inst, parent)->Show();
2418 void wxeditor_movie::on_keyboard_down(wxKeyEvent& e)
2420 CHECK_UI_THREAD;
2421 handle_wx_keyboard(inst, e, true);
2424 void wxeditor_movie::on_keyboard_up(wxKeyEvent& e)
2426 CHECK_UI_THREAD;
2427 handle_wx_keyboard(inst, e, false);
2430 void wxeditor_movie_update(emulator_instance& inst)
2432 auto e = movieeditors.lookup(inst);
2433 if(e) e->update();