Refactor emulator status reporting (and fix the statusbar doesn't update bug)
[lsnes.git] / src / platform / wxwidgets / editor-movie.cpp
blobba0841557c6b689de9e0f6f641658cbdc5507c40
1 #include "core/framebuffer.hpp"
2 #include "core/movie.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_DELETE_FRAME,
45 wxID_DELETE_SUBFRAME,
46 wxID_POSITION_LOCK,
47 wxID_RUN_TO_FRAME,
48 wxID_APPEND_FRAMES,
49 wxID_TRUNCATE,
50 wxID_SCROLL_FRAME,
51 wxID_SCROLL_CURRENT_FRAME,
52 wxID_COPY_FRAMES,
53 wxID_CUT_FRAMES,
54 wxID_PASTE_FRAMES,
55 wxID_PASTE_APPEND,
56 wxID_INSERT_CONTROLLER_AFTER,
57 wxID_DELETE_CONTROLLER_SUBFRAMES,
58 wxID_MBRANCH_NEW,
59 wxID_MBRANCH_IMPORT,
60 wxID_MBRANCH_EXPORT,
61 wxID_MBRANCH_RENAME,
62 wxID_MBRANCH_DELETE,
63 wxID_MBRANCH_FIRST,
64 wxID_MBRANCH_LAST = wxID_MBRANCH_FIRST + 1024
67 void update_movie_state();
69 namespace
71 unsigned lines_to_display = 28;
72 uint64_t divs[] = {1000000, 100000, 10000, 1000, 100, 10, 1};
73 uint64_t divsl[] = {1000000, 100000, 10000, 1000, 100, 10, 0};
74 const unsigned divcnt = sizeof(divs)/sizeof(divs[0]);
76 class exp_imp_type
78 public:
79 typedef std::pair<std::string, int> returntype;
80 exp_imp_type()
83 filedialog_input_params input(bool save) const
85 filedialog_input_params ip;
86 ip.types.push_back(filedialog_type_entry("Input tracks (text)", "*.lstt", "lstt"));
87 ip.types.push_back(filedialog_type_entry("Input tracks (binary)", "*.lstb", "lstb"));
88 if(!save)
89 ip.types.push_back(filedialog_type_entry("Movie files", "*.lsmv", "lsmv"));
90 ip.default_type = 1;
91 return ip;
93 std::pair<std::string, int> output(const filedialog_output_params& p, bool save) const
95 int m;
96 switch(p.typechoice) {
97 case 0: m = MBRANCH_IMPORT_TEXT; break;
98 case 1: m = MBRANCH_IMPORT_BINARY; break;
99 case 2: m = MBRANCH_IMPORT_MOVIE; break;
101 return std::make_pair(p.path, m);
103 private:
107 struct control_info
109 unsigned position_left;
110 unsigned reserved; //Must be at least 6 for axes.
111 unsigned index; //Index in poll vector.
112 int type; //-2 => Port, -1 => Fixed, 0 => Button, 1 => axis.
113 char32_t ch;
114 std::u32string title;
115 unsigned port;
116 unsigned controller;
117 port_controller_button::_type axistype;
118 int rmin;
119 int rmax;
120 static control_info portinfo(unsigned& p, unsigned port, unsigned controller);
121 static control_info fixedinfo(unsigned& p, const std::u32string& str);
122 static control_info buttoninfo(unsigned& p, char32_t character, const std::u32string& title, unsigned idx,
123 unsigned port, unsigned controller);
124 static control_info axisinfo(unsigned& p, const std::u32string& title, unsigned idx,
125 unsigned port, unsigned controller, port_controller_button::_type _axistype, int _rmin, int _rmax);
128 control_info control_info::portinfo(unsigned& p, unsigned port, unsigned controller)
130 control_info i;
131 i.position_left = p;
132 i.reserved = (stringfmt() << port << "-" << controller).str32().length();
133 p += i.reserved;
134 i.index = 0;
135 i.type = -2;
136 i.ch = 0;
137 i.title = U"";
138 i.port = port;
139 i.controller = controller;
140 return i;
143 control_info control_info::fixedinfo(unsigned& p, const std::u32string& str)
145 control_info i;
146 i.position_left = p;
147 i.reserved = str.length();
148 p += i.reserved;
149 i.index = 0;
150 i.type = -1;
151 i.ch = 0;
152 i.title = str;
153 i.port = 0;
154 i.controller = 0;
155 return i;
158 control_info control_info::buttoninfo(unsigned& p, char32_t character, const std::u32string& title, unsigned idx,
159 unsigned port, unsigned controller)
161 control_info i;
162 i.position_left = p;
163 i.reserved = 1;
164 p += i.reserved;
165 i.index = idx;
166 i.type = 0;
167 i.ch = character;
168 i.title = title;
169 i.port = port;
170 i.controller = controller;
171 return i;
174 control_info control_info::axisinfo(unsigned& p, const std::u32string& title, unsigned idx,
175 unsigned port, unsigned controller, port_controller_button::_type _axistype, int _rmin, int _rmax)
177 control_info i;
178 i.position_left = p;
179 i.reserved = title.length();
180 if(i.reserved < 6)
181 i.reserved = 6;
182 p += i.reserved;
183 i.index = idx;
184 i.type = 1;
185 i.ch = 0;
186 i.title = title;
187 i.port = port;
188 i.controller = controller;
189 i.axistype = _axistype;
190 i.rmin = _rmin;
191 i.rmax = _rmax;
192 return i;
195 class frame_controls
197 public:
198 frame_controls();
199 void set_types(controller_frame& f);
200 short read_index(controller_frame& f, unsigned idx);
201 void write_index(controller_frame& f, unsigned idx, short value);
202 uint32_t read_pollcount(pollcounter_vector& v, unsigned idx);
203 const std::list<control_info>& get_controlinfo() { return controlinfo; }
204 std::u32string line1() { return _line1; }
205 std::u32string line2() { return _line2; }
206 size_t width() { return _width; }
207 private:
208 size_t _width;
209 std::u32string _line1;
210 std::u32string _line2;
211 void format_lines();
212 void add_port(unsigned& c, unsigned pid, const port_type& p, const port_type_set& pts);
213 std::list<control_info> controlinfo;
217 frame_controls::frame_controls()
219 _width = 0;
222 void frame_controls::set_types(controller_frame& f)
224 unsigned nextp = 0;
225 controlinfo.clear();
226 const port_type_set& pts = f.porttypes();
227 unsigned pcnt = pts.ports();
228 for(unsigned i = 0; i < pcnt; i++)
229 add_port(nextp, i, pts.port_type(i), pts);
230 format_lines();
233 void frame_controls::add_port(unsigned& c, unsigned pid, const port_type& p, const port_type_set& pts)
235 const port_controller_set& pci = *(p.controller_info);
236 for(unsigned i = 0; i < pci.controllers.size(); i++) {
237 const port_controller& pc = pci.controllers[i];
238 if(pid || i)
239 controlinfo.push_back(control_info::fixedinfo(c, U"\u2502"));
240 unsigned nextp = c;
241 controlinfo.push_back(control_info::portinfo(nextp, pid, i + 1));
242 bool last_multibyte = false;
243 for(unsigned j = 0; j < pc.buttons.size(); j++) {
244 const port_controller_button& pcb = pc.buttons[j];
245 unsigned idx = pts.triple_to_index(pid, i, j);
246 if(idx == 0xFFFFFFFFUL)
247 continue;
248 if(pcb.type == port_controller_button::TYPE_BUTTON) {
249 if(last_multibyte)
250 c++;
251 controlinfo.push_back(control_info::buttoninfo(c, pcb.symbol, utf8::to32(pcb.name),
252 idx, pid, i));
253 last_multibyte = false;
254 } else if(pcb.type == port_controller_button::TYPE_AXIS ||
255 pcb.type == port_controller_button::TYPE_RAXIS ||
256 pcb.type == port_controller_button::TYPE_TAXIS ||
257 pcb.type == port_controller_button::TYPE_LIGHTGUN) {
258 if(j)
259 c++;
260 controlinfo.push_back(control_info::axisinfo(c, utf8::to32(pcb.name), idx, pid, i,
261 pcb.type, pcb.rmin, pcb.rmax));
262 last_multibyte = true;
265 if(nextp > c)
266 c = nextp;
270 short frame_controls::read_index(controller_frame& f, unsigned idx)
272 if(idx == 0)
273 return f.sync() ? 1 : 0;
274 return f.axis2(idx);
277 void frame_controls::write_index(controller_frame& f, unsigned idx, short value)
279 if(idx == 0)
280 return f.sync(value);
281 return f.axis2(idx, value);
284 uint32_t frame_controls::read_pollcount(pollcounter_vector& v, unsigned idx)
286 if(idx == 0)
287 return max(v.max_polls(), (uint32_t)1);
288 for(auto i : controlinfo)
289 if(idx == i.index && i.port == 0 && i.controller == 0)
290 return max(v.get_polls(idx), (uint32_t)(v.get_framepflag() ? 1 : 0));
291 return v.get_polls(idx);
294 void frame_controls::format_lines()
296 _width = 0;
297 for(auto i : controlinfo) {
298 if(i.position_left + i.reserved > _width)
299 _width = i.position_left + i.reserved;
301 std::u32string cp1;
302 std::u32string cp2;
303 uint32_t off = divcnt + 1;
304 cp1.resize(_width + divcnt + 1);
305 cp2.resize(_width + divcnt + 1);
306 for(unsigned i = 0; i < cp1.size(); i++)
307 cp1[i] = cp2[i] = 32;
308 cp1[divcnt] = 0x2502;
309 cp2[divcnt] = 0x2502;
310 //Line1
311 //For every port-controller, find the least coordinate.
312 for(auto i : controlinfo) {
313 if(i.type == -1) {
314 auto _title = i.title;
315 std::copy(_title.begin(), _title.end(), &cp1[i.position_left + off]);
316 } else if(i.type == -2) {
317 auto _title = (stringfmt() << i.port << "-" << i.controller).str32();
318 std::copy(_title.begin(), _title.end(), &cp1[i.position_left + off]);
321 //Line2
322 for(auto i : controlinfo) {
323 auto _title = i.title;
324 if(i.type == -1 || i.type == 1)
325 std::copy(_title.begin(), _title.end(), &cp2[i.position_left + off]);
326 if(i.type == 0)
327 cp2[i.position_left + off] = i.ch;
329 _line1 = cp1;
330 _line2 = cp2;
333 namespace
335 //TODO: Use real clipboard.
336 std::string clipboard;
338 void copy_to_clipboard(const std::string& text)
340 clipboard = text;
343 bool clipboard_has_text()
345 return (clipboard.length() > 0);
348 void clear_clipboard()
350 clipboard = "";
353 std::string copy_from_clipboard()
355 return clipboard;
358 std::string encode_line(controller_frame& f)
360 char buffer[512];
361 f.serialize(buffer);
362 return buffer;
365 std::string encode_line(frame_controls& info, controller_frame& f, unsigned port, unsigned controller)
367 std::ostringstream x;
368 bool last_axis = false;
369 bool first = true;
370 for(auto i : info.get_controlinfo()) {
371 if(i.port != port)
372 continue;
373 if(i.controller != controller)
374 continue;
375 switch(i.type) {
376 case 0: //Button.
377 if(last_axis)
378 x << " ";
379 if(info.read_index(f, i.index)) {
380 char32_t tmp1[2];
381 tmp1[0] = i.ch;
382 tmp1[1] = 0;
383 x << utf8::to8(std::u32string(tmp1));
384 } else
385 x << "-";
386 last_axis = false;
387 first = false;
388 break;
389 case 1: //Axis.
390 if(!first)
391 x << " ";
392 x << info.read_index(f, i.index);
393 first = false;
394 last_axis = true;
395 break;
398 return x.str();
401 short read_short(const std::u32string& s, size_t& r)
403 unsigned short _res = 0;
404 bool negative = false;
405 if(r < s.length() && s[r] == '-') {
406 negative = true;
407 r++;
409 while(r < s.length() && s[r] >= 48 && s[r] <= 57) {
410 _res = _res * 10 + (s[r] - 48);
411 r++;
413 return negative ? -_res : _res;
416 void decode_line(frame_controls& info, controller_frame& f, std::string line, unsigned port,
417 unsigned controller)
419 std::u32string _line = utf8::to32(line);
420 bool last_axis = false;
421 bool first = true;
422 short y;
423 char32_t y2;
424 size_t ridx = 0;
425 for(auto i : info.get_controlinfo()) {
426 if(i.port != port)
427 continue;
428 if(i.controller != controller)
429 continue;
430 switch(i.type) {
431 case 0: //Button.
432 if(last_axis) {
433 ridx++;
434 while(ridx < _line.length() && (_line[ridx] == 9 || _line[ridx] == 10 ||
435 _line[ridx] == 13 || _line[ridx] == 32))
436 ridx++;
438 y2 = (ridx < _line.length()) ? _line[ridx++] : 0;
439 if(y2 == U'-' || y2 == 0)
440 info.write_index(f, i.index, 0);
441 else
442 info.write_index(f, i.index, 1);
443 last_axis = false;
444 first = false;
445 break;
446 case 1: //Axis.
447 if(!first)
448 ridx++;
449 while(ridx < _line.length() && (_line[ridx] == 9 || _line[ridx] == 10 ||
450 _line[ridx] == 13 || _line[ridx] == 32))
451 ridx++;
452 y = read_short(_line, ridx);
453 info.write_index(f, i.index, y);
454 first = false;
455 last_axis = true;
456 break;
461 std::string encode_lines(controller_frame_vector& fv, uint64_t start, uint64_t end)
463 std::ostringstream x;
464 x << "lsnes-moviedata-whole" << std::endl;
465 for(uint64_t i = start; i < end; i++) {
466 controller_frame tmp = fv[i];
467 x << encode_line(tmp) << std::endl;
469 return x.str();
472 std::string encode_lines(frame_controls& info, controller_frame_vector& fv, uint64_t start, uint64_t end,
473 unsigned port, unsigned controller)
475 std::ostringstream x;
476 x << "lsnes-moviedata-controller" << std::endl;
477 for(uint64_t i = start; i < end; i++) {
478 controller_frame tmp = fv[i];
479 x << encode_line(info, tmp, port, controller) << std::endl;
481 return x.str();
484 int clipboard_get_data_type()
486 if(!clipboard_has_text())
487 return -1;
488 std::string y = copy_from_clipboard();
489 std::istringstream x(y);
490 std::string hdr;
491 std::getline(x, hdr);
492 if(hdr == "lsnes-moviedata-whole")
493 return 1;
494 if(hdr == "lsnes-moviedata-controller")
495 return 0;
496 return -1;
499 std::set<unsigned> controller_index_set(frame_controls& info, unsigned port, unsigned controller)
501 std::set<unsigned> r;
502 for(auto i : info.get_controlinfo()) {
503 if(i.port == port && i.controller == controller && (i.type == 0 || i.type == 1))
504 r.insert(i.index);
506 return r;
509 void move_index_set(frame_controls& info, controller_frame_vector& fv, uint64_t src, uint64_t dst,
510 uint64_t len, const std::set<unsigned>& indices)
512 if(src == dst)
513 return;
514 controller_frame_vector::notify_freeze freeze(fv);
515 if(src > dst) {
516 //Copy forwards.
517 uint64_t shift = src - dst;
518 for(uint64_t i = dst; i < dst + len; i++) {
519 controller_frame _src = fv[i + shift];
520 controller_frame _dst = fv[i];
521 for(auto j : indices)
522 info.write_index(_dst, j, info.read_index(_src, j));
524 } else {
525 //Copy backwards.
526 uint64_t shift = dst - src;
527 for(uint64_t i = src + len - 1; i >= src && i < src + len; i--) {
528 controller_frame _src = fv[i];
529 controller_frame _dst = fv[i + shift];
530 for(auto j : indices)
531 info.write_index(_dst, j, info.read_index(_src, j));
536 void zero_index_set(frame_controls& info, controller_frame_vector& fv, uint64_t dst, uint64_t len,
537 const std::set<unsigned>& indices)
539 controller_frame_vector::notify_freeze freeze(fv);
540 for(uint64_t i = dst; i < dst + len; i++) {
541 controller_frame _dst = fv[i];
542 for(auto j : indices)
543 info.write_index(_dst, j, 0);
547 control_info find_paired(control_info ci, const std::list<control_info>& info)
549 if(ci.axistype == port_controller_button::TYPE_TAXIS)
550 return ci;
551 bool even = true;
552 bool next_flag = false;
553 control_info previous;
554 for(auto i : info) {
555 if(i.port != ci.port || i.controller != ci.controller)
556 continue;
557 if(i.axistype != port_controller_button::TYPE_AXIS &&
558 i.axistype != port_controller_button::TYPE_RAXIS &&
559 i.axistype != port_controller_button::TYPE_LIGHTGUN)
560 continue;
561 if(next_flag)
562 return i;
563 if(i.index == ci.index) {
564 //This and...
565 if(even)
566 next_flag = true; //Next.
567 else
568 return previous; //Pevious.
570 previous = i;
571 even = !even;
573 //Huh, no pair.
574 return ci;
577 int32_t value_to_coordinate(int32_t rmin, int32_t rmax, int32_t val, int32_t dim)
579 //Scale the values to be zero-based.
580 val = min(max(val, rmin), rmax);
581 rmax -= rmin;
582 val -= rmin;
583 int32_t center = rmax / 2;
584 int32_t cc = (dim - 1) / 2;
585 if(val == center)
586 return cc;
587 if(val < center) {
588 //0 => 0, center => cc.
589 return (val * (int64_t)cc + (center / 2)) / center;
591 if(val > center) {
592 //center => cc, rmax => dim - 1.
593 val -= center;
594 rmax -= center;
595 int32_t cc2 = (dim - 1 - cc);
596 return (val * (int64_t)cc2 + (rmax / 2)) / rmax + cc;
598 return 0; //NOTREACHED.
601 int32_t coordinate_to_value(int32_t rmin, int32_t rmax, int32_t val, int32_t dim)
603 if(dim == rmin - rmax + 1) {
604 return val + rmin;
606 val = min(max(val, (int32_t)0), dim - 1);
607 int32_t center = (rmax + rmin) / 2;
608 int32_t cc = (dim - 1) / 2;
609 if(val == cc)
610 return center;
611 if(val < cc) {
612 //0 => rmin, cc => center.
613 return ((center - rmin) * (int64_t)val + cc / 2) / cc + rmin;
615 if(val > cc) {
616 //cc => center, dim - 1 => rmax.
617 uint32_t cc2 = (dim - 1 - cc);
618 return ((rmax - center) * (int64_t)(val - cc) + cc2 / 2) / cc2 + center;
620 return 0; //NOTREACHED.
623 std::string windowname(control_info X, control_info Y)
625 if(X.index == Y.index)
626 return (stringfmt() << utf8::to8(X.title)).str();
627 else
628 return (stringfmt() << utf8::to8(X.title) << "/" << utf8::to8(Y.title)).str();
631 class window_prompt : public wxDialog
633 public:
634 window_prompt(wxWindow* parent, uint8_t* _bitmap, unsigned _width,
635 unsigned _height, control_info X, control_info Y, unsigned posX, unsigned posY)
636 : wxDialog(parent, wxID_ANY, towxstring(windowname(X, Y)), wxPoint(posX, posY))
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 EndModal(wxID_CANCEL);
667 void on_erase(wxEraseEvent& e)
669 //Blank.
671 void on_paint(wxPaintEvent& e)
673 wxPaintDC dc(panel);
674 if(bitmap) {
675 wxBitmap bmp(wxImage(width, height, bitmap, true));
676 dc.DrawBitmap(bmp, 0, 0, false);
677 } else {
678 dc.SetBackground(*wxWHITE_BRUSH);
679 dc.Clear();
680 auto xval = value_to_coordinate(cX.rmin, cX.rmax, 0, width);
681 auto yval = value_to_coordinate(cY.rmin, cY.rmax, 0, height);
682 dc.SetPen(*wxBLACK_PEN);
683 if(cX.rmin < 0 && cX.rmax > 0)
684 dc.DrawLine(xval, 0, xval, height);
685 if(!oneaxis && cY.rmin < 0 && cY.rmax > 0)
686 dc.DrawLine(0, yval, width, yval);
688 dc.SetPen(*wxRED_PEN);
689 dc.DrawLine(mouseX, 0, mouseX, height);
690 if(!oneaxis)
691 dc.DrawLine(0, mouseY, width, mouseY);
692 dirty = false;
694 void on_mouse(wxMouseEvent& e)
696 if(e.LeftDown()) {
697 result.first = coordinate_to_value(cX.rmin, cX.rmax, e.GetX(), width);
698 if(!oneaxis)
699 result.second = coordinate_to_value(cY.rmin, cY.rmax, e.GetY(), height);
700 else
701 result.second = 0;
702 EndModal(wxID_OK);
704 mouseX = e.GetX();
705 mouseY = e.GetY();
706 if(!dirty) {
707 dirty = true;
708 panel->Refresh();
711 std::pair<int, int> get_results()
713 return result;
715 private:
716 std::pair<int, int> result;
717 wxPanel* panel;
718 bool oneaxis;
719 bool dirty;
720 int mouseX;
721 int mouseY;
722 int height;
723 int width;
724 control_info cX;
725 control_info cY;
726 uint8_t* bitmap;
729 std::pair<int, int> prompt_coodinates_window(wxWindow* parent, uint8_t* bitmap, unsigned width,
730 unsigned height, control_info X, control_info Y, unsigned posX, unsigned posY)
732 window_prompt* p = new window_prompt(parent, bitmap, width, height, X, Y, posX, posY);
733 if(p->ShowModal() == wxID_CANCEL) {
734 delete p;
735 throw canceled_exception();
737 auto r = p->get_results();
738 delete p;
739 return r;
743 class wxeditor_movie : public wxDialog
745 public:
746 wxeditor_movie(wxWindow* parent);
747 ~wxeditor_movie() throw();
748 bool ShouldPreventAppExit() const;
749 void on_close(wxCommandEvent& e);
750 void on_wclose(wxCloseEvent& e);
751 void on_focus_wrong(wxFocusEvent& e);
752 void on_keyboard_down(wxKeyEvent& e);
753 void on_keyboard_up(wxKeyEvent& e);
754 scroll_bar* get_scroll();
755 void update();
756 private:
757 struct _moviepanel : public wxPanel
759 _moviepanel(wxeditor_movie* v);
760 ~_moviepanel() throw();
761 void signal_repaint();
762 void on_paint(wxPaintEvent& e);
763 void on_erase(wxEraseEvent& e);
764 void on_mouse(wxMouseEvent& e);
765 void on_popup_menu(wxCommandEvent& e);
766 uint64_t moviepos;
767 private:
768 int get_lines();
769 void render(text_framebuffer& fb, unsigned long long pos);
770 void on_mouse0(unsigned x, unsigned y, bool polarity, bool shift, unsigned X, unsigned Y);
771 void on_mouse1(unsigned x, unsigned y, bool polarity);
772 void on_mouse2(unsigned x, unsigned y, bool polarity);
773 void popup_axis_panel(uint64_t row, control_info ci, unsigned screenX, unsigned screenY);
774 void do_toggle_buttons(unsigned idx, uint64_t row1, uint64_t row2);
775 void do_alter_axis(unsigned idx, uint64_t row1, uint64_t row2);
776 void do_sweep_axis(unsigned idx, uint64_t row1, uint64_t row2);
777 void do_append_frames(uint64_t count);
778 void do_append_frames();
779 void do_insert_frame_after(uint64_t row);
780 void do_delete_frame(uint64_t row1, uint64_t row2, bool wholeframe);
781 void do_truncate(uint64_t row);
782 void do_set_stop_at_frame();
783 void do_scroll_to_frame();
784 void do_scroll_to_current_frame();
785 void do_copy(uint64_t row1, uint64_t row2, unsigned port, unsigned controller);
786 void do_copy(uint64_t row1, uint64_t row2);
787 void do_cut(uint64_t row1, uint64_t row2, unsigned port, unsigned controller);
788 void do_cut(uint64_t row1, uint64_t row2);
789 void do_paste(uint64_t row, unsigned port, unsigned controller, bool append);
790 void do_paste(uint64_t row, bool append);
791 void do_insert_controller(uint64_t row, unsigned port, unsigned controller);
792 void do_delete_controller(uint64_t row1, uint64_t row2, unsigned port, unsigned controller);
793 uint64_t first_editable(unsigned index);
794 uint64_t first_nextframe();
795 int width(controller_frame& f);
796 std::u32string render_line1(controller_frame& f);
797 std::u32string render_line2(controller_frame& f);
798 void render_linen(text_framebuffer& fb, controller_frame& f, uint64_t sfn, int y);
799 unsigned long long spos;
800 void* prev_obj;
801 uint64_t prev_seqno;
802 void update_cache();
803 std::map<uint64_t, uint64_t> subframe_to_frame;
804 uint64_t max_subframe;
805 frame_controls fcontrols;
806 wxeditor_movie* m;
807 bool requested;
808 text_framebuffer fb;
809 uint64_t movielines;
810 unsigned new_width;
811 unsigned new_height;
812 std::vector<uint8_t> pixels;
813 unsigned press_x;
814 uint64_t press_line;
815 uint64_t rpress_line;
816 unsigned press_index;
817 bool pressed;
818 bool recursing;
819 uint64_t linecount;
820 uint64_t cached_cffs;
821 bool position_locked;
822 wxMenu* current_popup;
823 std::map<int, std::string> branch_names;
825 _moviepanel* moviepanel;
826 wxButton* closebutton;
827 scroll_bar* moviescroll;
828 bool closing;
831 namespace
833 wxeditor_movie* movieeditor_open;
835 //Find the first real editable subframe.
836 //Call only in emulator thread.
837 uint64_t real_first_editable(frame_controls& fc, unsigned idx)
839 uint64_t cffs = movb.get_movie().get_current_frame_first_subframe();
840 controller_frame_vector& fv = *movb.get_mfile().input;
841 pollcounter_vector& pv = movb.get_movie().get_pollcounters();
842 uint64_t vsize = fv.size();
843 uint32_t pc = fc.read_pollcount(pv, idx);
844 for(uint32_t i = 1; i < pc; i++)
845 if(cffs + i >= vsize || fv[cffs + i].sync())
846 return cffs + i;
847 return cffs + pc;
850 uint64_t real_first_editable(frame_controls& fc, std::set<unsigned> idx)
852 uint64_t m = 0;
853 for(auto i : idx)
854 m = max(m, real_first_editable(fc, i));
855 return m;
858 //Find the first real editable whole frame.
859 //Call only in emulator thread.
860 uint64_t real_first_nextframe(frame_controls& fc)
862 uint64_t base = real_first_editable(fc, 0);
863 controller_frame_vector& fv = *movb.get_mfile().input;
864 uint64_t vsize = fv.size();
865 for(uint32_t i = 0;; i++)
866 if(base + i >= vsize || fv[base + i].sync())
867 return base + i;
871 wxeditor_movie::_moviepanel::~_moviepanel() throw() {}
872 wxeditor_movie::~wxeditor_movie() throw() {}
874 wxeditor_movie::_moviepanel::_moviepanel(wxeditor_movie* v)
875 : wxPanel(v, wxID_ANY, wxDefaultPosition, wxSize(100, 100), wxWANTS_CHARS)
877 m = v;
878 Connect(wxEVT_PAINT, wxPaintEventHandler(_moviepanel::on_paint), NULL, this);
879 Connect(wxEVT_ERASE_BACKGROUND, wxEraseEventHandler(_moviepanel::on_erase), NULL, this);
880 new_width = 0;
881 new_height = 0;
882 moviepos = 0;
883 spos = 0;
884 prev_obj = NULL;
885 prev_seqno = 0;
886 max_subframe = 0;
887 recursing = false;
888 position_locked = true;
889 current_popup = NULL;
891 Connect(wxEVT_LEFT_DOWN, wxMouseEventHandler(_moviepanel::on_mouse), NULL, this);
892 Connect(wxEVT_LEFT_UP, wxMouseEventHandler(_moviepanel::on_mouse), NULL, this);
893 Connect(wxEVT_MIDDLE_DOWN, wxMouseEventHandler(_moviepanel::on_mouse), NULL, this);
894 Connect(wxEVT_MIDDLE_UP, wxMouseEventHandler(_moviepanel::on_mouse), NULL, this);
895 Connect(wxEVT_RIGHT_DOWN, wxMouseEventHandler(_moviepanel::on_mouse), NULL, this);
896 Connect(wxEVT_RIGHT_UP, wxMouseEventHandler(_moviepanel::on_mouse), NULL, this);
897 Connect(wxEVT_MOUSEWHEEL, wxMouseEventHandler(_moviepanel::on_mouse), NULL, this);
899 signal_repaint();
900 requested = false;
903 void wxeditor_movie::_moviepanel::update_cache()
905 movie& m = movb.get_movie();
906 controller_frame_vector& fv = *movb.get_mfile().input;
907 if(&m == prev_obj && prev_seqno == m.get_seqno()) {
908 //Just process new subframes if any.
909 for(uint64_t i = max_subframe; i < fv.size(); i++) {
910 uint64_t prev = (i > 0) ? subframe_to_frame[i - 1] : 0;
911 controller_frame f = fv[i];
912 if(f.sync())
913 subframe_to_frame[i] = prev + 1;
914 else
915 subframe_to_frame[i] = prev;
917 max_subframe = fv.size();
918 return;
920 //Reprocess all subframes.
921 for(uint64_t i = 0; i < fv.size(); i++) {
922 uint64_t prev = (i > 0) ? subframe_to_frame[i - 1] : 0;
923 controller_frame f = fv[i];
924 if(f.sync())
925 subframe_to_frame[i] = prev + 1;
926 else
927 subframe_to_frame[i] = prev;
929 max_subframe = fv.size();
930 controller_frame model = fv.blank_frame(false);
931 fcontrols.set_types(model);
932 prev_obj = &m;
933 prev_seqno = m.get_seqno();
936 int wxeditor_movie::_moviepanel::width(controller_frame& f)
938 update_cache();
939 return divcnt + 1 + fcontrols.width();
942 std::u32string wxeditor_movie::_moviepanel::render_line1(controller_frame& f)
944 update_cache();
945 return fcontrols.line1();
948 std::u32string wxeditor_movie::_moviepanel::render_line2(controller_frame& f)
950 update_cache();
951 return fcontrols.line2();
954 void wxeditor_movie::_moviepanel::render_linen(text_framebuffer& fb, controller_frame& f, uint64_t sfn, int y)
956 update_cache();
957 size_t fbstride = fb.get_stride();
958 text_framebuffer::element* _fb = fb.get_buffer();
959 text_framebuffer::element e;
960 e.bg = 0xFFFFFF;
961 e.fg = 0x000000;
962 for(unsigned i = 0; i < divcnt; i++) {
963 uint64_t fn = subframe_to_frame[sfn];
964 e.ch = (fn >= divsl[i]) ? (((fn / divs[i]) % 10) + 48) : 32;
965 _fb[y * fbstride + i] = e;
967 e.ch = 0x2502;
968 _fb[y * fbstride + divcnt] = e;
969 const std::list<control_info>& ctrlinfo = fcontrols.get_controlinfo();
970 uint64_t curframe = movb.get_movie().get_current_frame();
971 pollcounter_vector& pv = movb.get_movie().get_pollcounters();
972 uint64_t cffs = movb.get_movie().get_current_frame_first_subframe();
973 cached_cffs = cffs;
974 int past = -1;
975 if(!movb.get_movie().readonly_mode())
976 past = 1;
977 else if(subframe_to_frame[sfn] < curframe)
978 past = 1;
979 else if(subframe_to_frame[sfn] > curframe)
980 past = 0;
981 bool now = (subframe_to_frame[sfn] == curframe);
982 unsigned xcord = 32768;
983 if(pressed)
984 xcord = press_x;
986 for(auto i : ctrlinfo) {
987 int rpast = past;
988 unsigned off = divcnt + 1;
989 bool cselected = (xcord >= i.position_left + off && xcord < i.position_left + i.reserved + off);
990 if(rpast == -1) {
991 unsigned polls = fcontrols.read_pollcount(pv, i.index);
992 rpast = ((cffs + polls) > sfn) ? 1 : 0;
994 uint32_t bgc = 0xC0C0C0;
995 if(rpast)
996 bgc |= 0x0000FF;
997 if(now)
998 bgc |= 0xFF0000;
999 if(cselected)
1000 bgc |= 0x00FF00;
1001 if(bgc == 0xC0C0C0)
1002 bgc = 0xFFFFFF;
1003 if(i.type == -1) {
1004 //Separator.
1005 fb.write(i.title, 0, divcnt + 1 + i.position_left, y, 0x000000, 0xFFFFFF);
1006 } else if(i.type == 0) {
1007 //Button.
1008 char32_t c[2];
1009 bool v = (fcontrols.read_index(f, i.index) != 0);
1010 c[0] = i.ch;
1011 c[1] = 0;
1012 fb.write(c, 0, divcnt + 1 + i.position_left, y, v ? 0x000000 : 0xC8C8C8, bgc);
1013 } else if(i.type == 1) {
1014 //Axis.
1015 char c[7];
1016 sprintf(c, "%6d", fcontrols.read_index(f, i.index));
1017 fb.write(c, 0, divcnt + 1 + i.position_left, y, 0x000000, bgc);
1022 void wxeditor_movie::_moviepanel::render(text_framebuffer& fb, unsigned long long pos)
1024 spos = pos;
1025 controller_frame_vector& fv = *movb.get_mfile().input;
1026 controller_frame cf = fv.blank_frame(false);
1027 int _width = width(cf);
1028 fb.set_size(_width, lines_to_display + 3);
1029 size_t fbstride = fb.get_stride();
1030 auto fbsize = fb.get_characters();
1031 text_framebuffer::element* _fb = fb.get_buffer();
1032 fb.write((stringfmt() << "Current frame: " << movb.get_movie().get_current_frame() << " of "
1033 << movb.get_movie().get_frame_count()).str(), _width, 0, 0,
1034 0x000000, 0xFFFFFF);
1035 fb.write(render_line1(cf), _width, 0, 1, 0x000000, 0xFFFFFF);
1036 fb.write(render_line2(cf), _width, 0, 2, 0x000000, 0xFFFFFF);
1037 unsigned long long lines = fv.size();
1038 unsigned long long i;
1039 unsigned j;
1040 for(i = pos, j = 3; i < pos + lines_to_display; i++, j++) {
1041 text_framebuffer::element e;
1042 if(i >= lines) {
1043 //Out of range.
1044 e.bg = 0xFFFFFF;
1045 e.fg = 0x000000;
1046 e.ch = 32;
1047 for(unsigned k = 0; k < fbsize.first; k++)
1048 _fb[j * fbstride + k] = e;
1049 } else {
1050 controller_frame frame = fv[i];
1051 render_linen(fb, frame, i, j);
1056 void wxeditor_movie::_moviepanel::do_toggle_buttons(unsigned idx, uint64_t row1, uint64_t row2)
1058 frame_controls* _fcontrols = &fcontrols;
1059 uint64_t _press_line = row1;
1060 uint64_t line = row2;
1061 if(_press_line > line)
1062 std::swap(_press_line, line);
1063 recursing = true;
1064 runemufn([idx, _press_line, line, _fcontrols]() {
1065 int64_t adjust = 0;
1066 if(!movb.get_movie().readonly_mode())
1067 return;
1068 uint64_t fedit = real_first_editable(*_fcontrols, idx);
1069 controller_frame_vector& fv = *movb.get_mfile().input;
1070 controller_frame_vector::notify_freeze freeze(fv);
1071 for(uint64_t i = _press_line; i <= line; i++) {
1072 if(i < fedit || i >= fv.size())
1073 continue;
1074 controller_frame cf = fv[i];
1075 bool v = _fcontrols->read_index(cf, idx);
1076 _fcontrols->write_index(cf, idx, !v);
1077 adjust += (v ? -1 : 1);
1080 recursing = false;
1081 if(idx == 0)
1082 max_subframe = _press_line; //Reparse.
1083 signal_repaint();
1086 void wxeditor_movie::_moviepanel::do_alter_axis(unsigned idx, uint64_t row1, uint64_t row2)
1088 frame_controls* _fcontrols = &fcontrols;
1089 uint64_t line = row1;
1090 uint64_t line2 = row2;
1091 short value;
1092 bool valid = true;
1093 runemufn([idx, line, &value, _fcontrols, &valid]() {
1094 if(!movb.get_movie().readonly_mode()) {
1095 valid = false;
1096 return;
1098 uint64_t fedit = real_first_editable(*_fcontrols, idx);
1099 controller_frame_vector& fv = *movb.get_mfile().input;
1100 if(line < fedit || line >= fv.size()) {
1101 valid = false;
1102 return;
1104 controller_frame_vector::notify_freeze freeze(fv);
1105 controller_frame cf = fv[line];
1106 value = _fcontrols->read_index(cf, idx);
1108 if(!valid)
1109 return;
1110 try {
1111 std::string text = pick_text(m, "Set value", "Enter new value:", (stringfmt() << value).str());
1112 value = parse_value<short>(text);
1113 } catch(canceled_exception& e) {
1114 return;
1115 } catch(std::exception& e) {
1116 wxMessageBox(wxT("Invalid value"), _T("Error"), wxICON_EXCLAMATION | wxOK, m);
1117 return;
1119 if(line > line2)
1120 std::swap(line, line2);
1121 runemufn([idx, line, line2, value, _fcontrols]() {
1122 uint64_t fedit = real_first_editable(*_fcontrols, idx);
1123 controller_frame_vector& fv = *movb.get_mfile().input;
1124 controller_frame_vector::notify_freeze freeze(fv);
1125 for(uint64_t i = line; i <= line2; i++) {
1126 if(i < fedit || i >= fv.size())
1127 continue;
1128 controller_frame cf = fv[i];
1129 _fcontrols->write_index(cf, idx, value);
1132 signal_repaint();
1135 void wxeditor_movie::_moviepanel::do_sweep_axis(unsigned idx, uint64_t row1, uint64_t row2)
1137 frame_controls* _fcontrols = &fcontrols;
1138 uint64_t line = row1;
1139 uint64_t line2 = row2;
1140 short value;
1141 short value2;
1142 bool valid = true;
1143 if(line > line2)
1144 std::swap(line, line2);
1145 runemufn([idx, line, line2, &value, &value2, _fcontrols, &valid]() {
1146 if(!movb.get_movie().readonly_mode()) {
1147 valid = false;
1148 return;
1150 uint64_t fedit = real_first_editable(*_fcontrols, idx);
1151 controller_frame_vector& fv = *movb.get_mfile().input;
1152 if(line2 < fedit || line2 >= fv.size()) {
1153 valid = false;
1154 return;
1156 controller_frame cf = fv[line];
1157 value = _fcontrols->read_index(cf, idx);
1158 controller_frame cf2 = fv[line2];
1159 value2 = _fcontrols->read_index(cf2, idx);
1161 if(!valid)
1162 return;
1163 runemufn([idx, line, line2, value, value2, _fcontrols]() {
1164 uint64_t fedit = real_first_editable(*_fcontrols, idx);
1165 controller_frame_vector& fv = *movb.get_mfile().input;
1166 controller_frame_vector::notify_freeze freeze(fv);
1167 for(uint64_t i = line + 1; i <= line2 - 1; i++) {
1168 if(i < fedit || i >= fv.size())
1169 continue;
1170 controller_frame cf = fv[i];
1171 auto tmp2 = static_cast<int64_t>(i - line) * (value2 - value) /
1172 static_cast<int64_t>(line2 - line);
1173 short tmp = value + tmp2;
1174 _fcontrols->write_index(cf, idx, tmp);
1177 signal_repaint();
1180 void wxeditor_movie::_moviepanel::do_append_frames(uint64_t count)
1182 recursing = true;
1183 uint64_t _count = count;
1184 runemufn([_count]() {
1185 if(!movb.get_movie().readonly_mode())
1186 return;
1187 controller_frame_vector& fv = *movb.get_mfile().input;
1188 controller_frame_vector::notify_freeze freeze(fv);
1189 for(uint64_t i = 0; i < _count; i++)
1190 fv.append(fv.blank_frame(true));
1192 recursing = false;
1193 signal_repaint();
1196 void wxeditor_movie::_moviepanel::do_append_frames()
1198 uint64_t value;
1199 try {
1200 std::string text = pick_text(m, "Append frames", "Enter number of frames to append:", "");
1201 value = parse_value<uint64_t>(text);
1202 } catch(canceled_exception& e) {
1203 return;
1204 } catch(std::exception& e) {
1205 wxMessageBox(wxT("Invalid value"), _T("Error"), wxICON_EXCLAMATION | wxOK, m);
1206 return;
1208 do_append_frames(value);
1209 signal_repaint();
1212 void wxeditor_movie::_moviepanel::do_insert_frame_after(uint64_t row)
1214 recursing = true;
1215 frame_controls* _fcontrols = &fcontrols;
1216 uint64_t _row = row;
1217 runemufn([_row, _fcontrols]() {
1218 if(!movb.get_movie().readonly_mode())
1219 return;
1220 controller_frame_vector& fv = *movb.get_mfile().input;
1221 uint64_t fedit = real_first_editable(*_fcontrols, 0);
1222 //Find the start of the next frame.
1223 uint64_t nframe = _row + 1;
1224 uint64_t vsize = fv.size();
1225 while(nframe < vsize && !fv[nframe].sync())
1226 nframe++;
1227 if(nframe < fedit)
1228 return;
1229 controller_frame_vector::notify_freeze freeze(fv);
1230 fv.append(fv.blank_frame(true));
1231 if(nframe < vsize) {
1232 //Okay, gotta copy all data after this point. nframe has to be at least 1.
1233 for(uint64_t i = vsize - 1; i >= nframe; i--)
1234 fv[i + 1] = fv[i];
1235 fv[nframe] = fv.blank_frame(true);
1238 max_subframe = row;
1239 recursing = false;
1240 signal_repaint();
1243 void wxeditor_movie::_moviepanel::do_delete_frame(uint64_t row1, uint64_t row2, bool wholeframe)
1245 recursing = true;
1246 uint64_t _row1 = row1;
1247 uint64_t _row2 = row2;
1248 bool _wholeframe = wholeframe;
1249 frame_controls* _fcontrols = &fcontrols;
1250 if(_row1 > _row2) std::swap(_row1, _row2);
1251 runemufn([_row1, _row2, _wholeframe, _fcontrols]() {
1252 controller_frame_vector& fv = *movb.get_mfile().input;
1253 uint64_t vsize = fv.size();
1254 if(_row1 >= vsize)
1255 return; //Nothing to do.
1256 controller_frame_vector::notify_freeze freeze(fv);
1257 uint64_t row2 = min(_row2, vsize - 1);
1258 uint64_t row1 = min(_row1, vsize - 1);
1259 row1 = max(row1, real_first_editable(*_fcontrols, 0));
1260 if(_wholeframe) {
1261 if(_row2 < real_first_nextframe(*_fcontrols))
1262 return; //Nothing to do.
1263 //Scan backwards for the first subframe of this frame and forwards for the last.
1264 uint64_t fsf = row1;
1265 uint64_t lsf = row2;
1266 if(fv[_row2].sync())
1267 lsf++; //Bump by one so it finds the end.
1268 while(fsf < vsize && !fv[fsf].sync())
1269 fsf--;
1270 while(lsf < vsize && !fv[lsf].sync())
1271 lsf++;
1272 fsf = max(fsf, real_first_editable(*_fcontrols, 0));
1273 uint64_t tonuke = lsf - fsf;
1274 int64_t frames_tonuke = 0;
1275 //Count frames nuked.
1276 for(uint64_t i = fsf; i < lsf; i++)
1277 if(fv[i].sync())
1278 frames_tonuke++;
1279 //Nuke from fsf to lsf.
1280 for(uint64_t i = fsf; i < vsize - tonuke; i++)
1281 fv[i] = fv[i + tonuke];
1282 fv.resize(vsize - tonuke);
1283 } else {
1284 if(row2 < real_first_editable(*_fcontrols, 0))
1285 return; //Nothing to do.
1286 //The sync flag needs to be inherited if:
1287 //1) Some deleted subframe has sync flag AND
1288 //2) The subframe immediately after deleted region doesn't.
1289 bool inherit_sync = false;
1290 for(uint64_t i = row1; i <= row2; i++)
1291 inherit_sync = inherit_sync || fv[i].sync();
1292 inherit_sync = inherit_sync && (row2 + 1 < vsize && !fv[_row2 + 1].sync());
1293 int64_t frames_tonuke = 0;
1294 //Count frames nuked.
1295 for(uint64_t i = row1; i <= row2; i++)
1296 if(fv[i].sync())
1297 frames_tonuke++;
1298 //If sync is inherited, one less frame is nuked.
1299 if(inherit_sync) frames_tonuke--;
1300 //Nuke the subframes.
1301 uint64_t tonuke = row2 - row1 + 1;
1302 for(uint64_t i = row1; i < vsize - tonuke; i++)
1303 fv[i] = fv[i + tonuke];
1304 fv.resize(vsize - tonuke);
1305 //Next subframe inherits the sync flag.
1306 if(inherit_sync)
1307 fv[row1].sync(true);
1310 max_subframe = _row1;
1311 recursing = false;
1312 signal_repaint();
1315 void wxeditor_movie::_moviepanel::do_truncate(uint64_t row)
1317 recursing = true;
1318 uint64_t _row = row;
1319 frame_controls* _fcontrols = &fcontrols;
1320 runemufn([_row, _fcontrols]() {
1321 controller_frame_vector& fv = *movb.get_mfile().input;
1322 uint64_t vsize = fv.size();
1323 if(_row >= vsize)
1324 return;
1325 if(_row < real_first_editable(*_fcontrols, 0))
1326 return;
1327 int64_t delete_count = 0;
1328 for(uint64_t i = _row; i < vsize; i++)
1329 if(fv[i].sync())
1330 delete_count--;
1331 fv.resize(_row);
1333 max_subframe = row;
1334 recursing = false;
1335 signal_repaint();
1338 void wxeditor_movie::_moviepanel::do_set_stop_at_frame()
1340 uint64_t curframe;
1341 uint64_t frame;
1342 runemufn([&curframe]() {
1343 curframe = movb.get_movie().get_current_frame();
1345 try {
1346 std::string text = pick_text(m, "Frame", (stringfmt() << "Enter frame to stop at (currently at "
1347 << curframe << "):").str(), "");
1348 frame = parse_value<uint64_t>(text);
1349 } catch(canceled_exception& e) {
1350 return;
1351 } catch(std::exception& e) {
1352 wxMessageBox(wxT("Invalid value"), _T("Error"), wxICON_EXCLAMATION | wxOK, m);
1353 return;
1355 if(frame < curframe) {
1356 wxMessageBox(wxT("The movie is already past that point"), _T("Error"), wxICON_EXCLAMATION | wxOK, m);
1357 return;
1359 runemufn([frame]() {
1360 set_stop_at_frame(frame);
1364 void wxeditor_movie::_moviepanel::on_mouse0(unsigned x, unsigned y, bool polarity, bool shift, unsigned X, unsigned Y)
1366 if(y < 3)
1367 return;
1368 if(polarity) {
1369 press_x = x;
1370 press_line = spos + y - 3;
1372 pressed = polarity;
1373 if(polarity)
1374 return;
1375 uint64_t line = spos + y - 3;
1376 if(press_x < divcnt && x < divcnt) {
1377 //Press on frame count.
1378 uint64_t row1 = press_line;
1379 uint64_t row2 = line;
1380 if(row1 > row2)
1381 std::swap(row1, row2);
1382 do_append_frames(row2 - row1 + 1);
1384 for(auto i : fcontrols.get_controlinfo()) {
1385 unsigned off = divcnt + 1;
1386 unsigned idx = i.index;
1387 if((press_x >= i.position_left + off && press_x < i.position_left + i.reserved + off) &&
1388 (x >= i.position_left + off && x < i.position_left + i.reserved + off)) {
1389 if(i.type == 0)
1390 do_toggle_buttons(idx, press_line, line);
1391 else if(i.type == 1) {
1392 if(shift) {
1393 if(press_line == line && (i.port || i.controller))
1394 try {
1395 wxPoint spos = GetScreenPosition();
1396 popup_axis_panel(line, i, spos.x + X, spos.y + Y);
1397 } catch(canceled_exception& e) {
1399 } else
1400 do_alter_axis(idx, press_line, line);
1406 void wxeditor_movie::_moviepanel::popup_axis_panel(uint64_t row, control_info ci, unsigned screenX, unsigned screenY)
1408 control_info ciX;
1409 control_info ciY;
1410 control_info ci2 = find_paired(ci, fcontrols.get_controlinfo());
1411 if(ci.index == ci2.index) {
1412 ciX = ciY = ci;
1413 } else if(ci2.index < ci.index) {
1414 ciX = ci2;
1415 ciY = ci;
1416 } else {
1417 ciX = ci;
1418 ciY = ci2;
1420 frame_controls* _fcontrols = &fcontrols;
1421 if(ciX.index == ciY.index) {
1422 auto c = prompt_coodinates_window(m, NULL, 256, 0, ciX, ciX, screenX, screenY);
1423 runemufn([ciX, row, c, _fcontrols]() {
1424 uint64_t fedit = real_first_editable(*_fcontrols, ciX.index);
1425 if(row < fedit) return;
1426 controller_frame_vector& fv = *movb.get_mfile().input;
1427 controller_frame cf = fv[row];
1428 _fcontrols->write_index(cf, ciX.index, c.first);
1430 signal_repaint();
1431 } else if(ci.axistype == port_controller_button::TYPE_LIGHTGUN) {
1432 framebuffer::raw& _fb = render_get_latest_screen();
1433 framebuffer::fb<false> fb;
1434 auto osize = std::make_pair(_fb.get_width(), _fb.get_height());
1435 auto size = our_rom.rtype->lightgun_scale();
1436 fb.reallocate(osize.first, osize.second, false);
1437 fb.copy_from(_fb, 1, 1);
1438 render_get_latest_screen_end();
1439 std::vector<uint8_t> buf;
1440 buf.resize(3 * (ciX.rmax - ciX.rmin + 1) * (ciY.rmax - ciY.rmin + 1));
1441 unsigned offX = -ciX.rmin;
1442 unsigned offY = -ciY.rmin;
1443 struct SwsContext* ctx = sws_getContext(osize.first, osize.second, PIX_FMT_RGBA,
1444 size.first, size.second, PIX_FMT_BGR24, SWS_POINT, NULL, NULL, NULL);
1445 uint8_t* srcp[1];
1446 int srcs[1];
1447 uint8_t* dstp[1];
1448 int dsts[1];
1449 srcs[0] = 4 * (fb.rowptr(1) - fb.rowptr(0));
1450 dsts[0] = 3 * (ciX.rmax - ciX.rmin + 1);
1451 srcp[0] = reinterpret_cast<unsigned char*>(fb.rowptr(0));
1452 dstp[0] = &buf[3 * (offY * (ciX.rmax - ciX.rmin + 1) + offX)];
1453 memset(&buf[0], 0, buf.size());
1454 sws_scale(ctx, srcp, srcs, 0, size.second, dstp, dsts);
1455 sws_freeContext(ctx);
1456 auto c = prompt_coodinates_window(m, &buf[0], (ciX.rmax - ciX.rmin + 1), (ciY.rmax - ciY.rmin + 1),
1457 ciX, ciY, screenX, screenY);
1458 runemufn([ciX, ciY, row, c, _fcontrols]() {
1459 uint64_t fedit = real_first_editable(*_fcontrols, ciX.index);
1460 fedit = max(fedit, real_first_editable(*_fcontrols, ciY.index));
1461 if(row < fedit) return;
1462 controller_frame_vector& fv = *movb.get_mfile().input;
1463 controller_frame cf = fv[row];
1464 _fcontrols->write_index(cf, ciX.index, c.first);
1465 _fcontrols->write_index(cf, ciY.index, c.second);
1467 signal_repaint();
1468 } else {
1469 auto c = prompt_coodinates_window(m, NULL, 256, 256, ciX, ciY, screenX, screenY);
1470 runemufn([ciX, ciY, row, c, _fcontrols]() {
1471 uint64_t fedit = real_first_editable(*_fcontrols, ciX.index);
1472 fedit = max(fedit, real_first_editable(*_fcontrols, ciY.index));
1473 if(row < fedit) return;
1474 controller_frame_vector& fv = *movb.get_mfile().input;
1475 controller_frame cf = fv[row];
1476 _fcontrols->write_index(cf, ciX.index, c.first);
1477 _fcontrols->write_index(cf, ciY.index, c.second);
1479 signal_repaint();
1483 void wxeditor_movie::_moviepanel::do_scroll_to_frame()
1485 uint64_t frame;
1486 try {
1487 std::string text = pick_text(m, "Frame", (stringfmt() << "Enter frame to scroll to:").str(), "");
1488 frame = parse_value<uint64_t>(text);
1489 } catch(canceled_exception& e) {
1490 return;
1491 } catch(std::exception& e) {
1492 wxMessageBox(wxT("Invalid value"), _T("Error"), wxICON_EXCLAMATION | wxOK, m);
1493 return;
1495 uint64_t wouldbe = 0;
1496 uint64_t low = 0;
1497 uint64_t high = max_subframe;
1498 while(low < high) {
1499 wouldbe = (low + high) / 2;
1500 if(subframe_to_frame[wouldbe] < frame)
1501 low = wouldbe;
1502 else if(subframe_to_frame[wouldbe] > frame)
1503 high = wouldbe;
1504 else
1505 break;
1507 while(wouldbe > 1 && subframe_to_frame[wouldbe - 1] == frame)
1508 wouldbe--;
1509 moviepos = wouldbe;
1510 signal_repaint();
1513 void wxeditor_movie::_moviepanel::do_scroll_to_current_frame()
1515 moviepos = cached_cffs;
1516 signal_repaint();
1519 void wxeditor_movie::_moviepanel::on_popup_menu(wxCommandEvent& e)
1521 wxMenuItem* tmpitem;
1522 int id = e.GetId();
1524 unsigned port = 0;
1525 unsigned controller = 0;
1526 for(auto i : fcontrols.get_controlinfo())
1527 if(i.index == press_index) {
1528 port = i.port;
1529 controller = i.controller;
1532 switch(id) {
1533 case wxID_TOGGLE:
1534 do_toggle_buttons(press_index, rpress_line, press_line);
1535 return;
1536 case wxID_CHANGE:
1537 do_alter_axis(press_index, rpress_line, press_line);
1538 return;
1539 case wxID_SWEEP:
1540 do_sweep_axis(press_index, rpress_line, press_line);
1541 return;
1542 case wxID_APPEND_FRAME:
1543 do_append_frames(1);
1544 return;
1545 case wxID_APPEND_FRAMES:
1546 do_append_frames();
1547 return;
1548 case wxID_INSERT_AFTER:
1549 do_insert_frame_after(press_line);
1550 return;
1551 case wxID_DELETE_FRAME:
1552 do_delete_frame(press_line, rpress_line, true);
1553 return;
1554 case wxID_DELETE_SUBFRAME:
1555 do_delete_frame(press_line, rpress_line, false);
1556 return;
1557 case wxID_TRUNCATE:
1558 do_truncate(press_line);
1559 return;
1560 case wxID_RUN_TO_FRAME:
1561 do_set_stop_at_frame();
1562 return;
1563 case wxID_SCROLL_FRAME:
1564 do_scroll_to_frame();
1565 return;
1566 case wxID_SCROLL_CURRENT_FRAME:
1567 do_scroll_to_current_frame();
1568 return;
1569 case wxID_POSITION_LOCK:
1570 if(!current_popup)
1571 return;
1572 tmpitem = current_popup->FindItem(wxID_POSITION_LOCK);
1573 position_locked = tmpitem->IsChecked();
1574 return;
1575 case wxID_CHANGE_LINECOUNT:
1576 try {
1577 std::string text = pick_text(m, "Set number of lines", "Set number of lines visible:",
1578 (stringfmt() << lines_to_display).str());
1579 unsigned tmp = parse_value<unsigned>(text);
1580 if(tmp < 1 || tmp > 255)
1581 throw std::runtime_error("Value out of range");
1582 lines_to_display = tmp;
1583 m->get_scroll()->set_page_size(lines_to_display);
1584 } catch(canceled_exception& e) {
1585 return;
1586 } catch(std::exception& e) {
1587 wxMessageBox(wxT("Invalid value"), _T("Error"), wxICON_EXCLAMATION | wxOK, m);
1588 return;
1590 signal_repaint();
1591 return;
1592 case wxID_COPY_FRAMES:
1593 if(press_index == std::numeric_limits<unsigned>::max())
1594 do_copy(rpress_line, press_line);
1595 else
1596 do_copy(rpress_line, press_line, port, controller);
1597 return;
1598 case wxID_CUT_FRAMES:
1599 if(press_index == std::numeric_limits<unsigned>::max())
1600 do_cut(rpress_line, press_line);
1601 else
1602 do_cut(rpress_line, press_line, port, controller);
1603 return;
1604 case wxID_PASTE_FRAMES:
1605 if(press_index == std::numeric_limits<unsigned>::max() || clipboard_get_data_type() == 1)
1606 do_paste(press_line, false);
1607 else
1608 do_paste(press_line, port, controller, false);
1609 return;
1610 case wxID_PASTE_APPEND:
1611 if(press_index == std::numeric_limits<unsigned>::max() || clipboard_get_data_type() == 1)
1612 do_paste(press_line, true);
1613 else
1614 do_paste(press_line, port, controller, true);
1615 return;
1616 case wxID_INSERT_CONTROLLER_AFTER:
1617 if(press_index == std::numeric_limits<unsigned>::max())
1619 else
1620 do_insert_controller(press_line, port, controller);
1621 return;
1622 case wxID_DELETE_CONTROLLER_SUBFRAMES:
1623 if(press_index == std::numeric_limits<unsigned>::max())
1625 else
1626 do_delete_controller(press_line, rpress_line, port, controller);
1627 return;
1628 case wxID_MBRANCH_NEW:
1629 try {
1630 std::string newname;
1631 std::string oldname;
1632 runemufn([&oldname]() { oldname = mbranch_get(); });
1633 newname = pick_text(this, "Enter new branch name", "Enter name for a new branch (to fork "
1634 "from " + mbranch_name(oldname) + "):", "", false);
1635 runemufn_async([this, oldname, newname] {
1636 try {
1637 mbranch_new(newname, oldname);
1638 } catch(std::exception& e) {
1639 std::string error = e.what();
1640 runuifun([this, error]() {
1641 show_message_ok(this, "Can't create branch",
1642 "Can't create branch: " + error, wxICON_EXCLAMATION);
1646 } catch(canceled_exception& e) {
1648 return;
1649 case wxID_MBRANCH_IMPORT:
1650 try {
1651 int mode;
1652 std::string filename;
1653 std::string branch;
1654 std::string dbranch;
1655 auto g = choose_file_load(this, "Choose file to import", project_moviepath(),
1656 exp_imp_type());
1657 filename = g.first;
1658 mode = g.second;
1659 if(mode == MBRANCH_IMPORT_MOVIE) {
1660 std::set<std::string> brlist;
1661 bool failed = false;
1662 runemufn([this, filename, &brlist, &failed]() {
1663 try {
1664 brlist = mbranch_movie_branches(filename);
1665 } catch(std::exception& e) {
1666 std::string error = e.what();
1667 failed = true;
1668 runuifun([this, error]() {
1669 show_message_ok(this, "Can't get branches in movie",
1670 error, wxICON_EXCLAMATION);
1675 if(failed)
1676 return;
1677 if(brlist.size() == 0) {
1678 show_message_ok(this, "No branches in movie file",
1679 "Can't import movie file as it has no branches", wxICON_EXCLAMATION);
1680 return;
1681 } else if(brlist.size() == 1) {
1682 branch = *brlist.begin();
1683 } else {
1684 std::vector<std::string> choices(brlist.begin(), brlist.end());
1685 branch = pick_among(this, "Select branch to import",
1686 "Select branch to import", choices, 0);
1688 //Import from movie.
1690 dbranch = pick_text(this, "Enter new branch name", "Enter name for an imported branch:",
1691 branch, false);
1692 runemufn_async([this, filename, branch, dbranch, mode]() {
1693 try {
1694 mbranch_import(filename, branch, dbranch, mode);
1695 } catch(std::exception& e) {
1696 std::string error = e.what();
1697 runuifun([this, error]() {
1698 show_message_ok(this, "Can't import branch",
1699 error, wxICON_EXCLAMATION);
1703 } catch(canceled_exception& e) {
1705 return;
1706 case wxID_MBRANCH_EXPORT:
1707 try {
1708 int mode;
1709 std::string file;
1710 auto g = choose_file_save(this, "Choose file to export", project_moviepath(),
1711 exp_imp_type());
1712 file = g.first;
1713 mode = g.second;
1714 runemufn_async([this, file, mode]() {
1715 try {
1716 std::string bname = mbranch_get();
1717 mbranch_export(file, bname, mode == MBRANCH_IMPORT_BINARY);
1718 } catch(std::exception& e) {
1719 std::string error = e.what();
1720 runuifun([this, error]() {
1721 show_message_ok(this, "Can't export branch",
1722 error, wxICON_EXCLAMATION);
1726 } catch(canceled_exception& e) {
1728 return;
1729 case wxID_MBRANCH_RENAME:
1730 try {
1731 std::string newname;
1732 std::string oldname;
1733 std::set<std::string> list;
1734 runemufn([&list]() { list = mbranch_enumerate(); });
1735 std::vector<std::string> choices(list.begin(), list.end());
1736 oldname = pick_among(this, "Select branch to rename", "Select branch to rename",
1737 choices, 0);
1738 newname = pick_text(this, "Enter new branch name", "Enter name for a new branch (to rename "
1739 "'" + mbranch_name(oldname) + "'):", oldname, false);
1740 runemufn_async([this, oldname, newname] {
1741 try {
1742 mbranch_rename(oldname, newname);
1743 } catch(std::exception& e) {
1744 std::string error = e.what();
1745 runuifun([this, error]() {
1746 show_message_ok(this, "Can't rename branch",
1747 "Can't rename branch: " + error, wxICON_EXCLAMATION);
1751 } catch(canceled_exception& e) {
1753 return;
1754 case wxID_MBRANCH_DELETE:
1755 try {
1756 std::string oldname;
1757 std::set<std::string> list;
1758 runemufn([&list]() { list = mbranch_enumerate(); });
1759 std::vector<std::string> choices(list.begin(), list.end());
1760 oldname = pick_among(this, "Select branch to delete", "Select branch to delete",
1761 choices, 0);
1762 runemufn_async([this, oldname] {
1763 try {
1764 mbranch_delete(oldname);
1765 } catch(std::exception& e) {
1766 std::string error = e.what();
1767 runuifun([this, error]() {
1768 show_message_ok(this, "Can't delete branch",
1769 "Can't delete branch: " + error, wxICON_EXCLAMATION);
1773 } catch(canceled_exception& e) {
1775 return;
1777 if(id >= wxID_MBRANCH_FIRST && id <= wxID_MBRANCH_LAST) {
1778 if(!branch_names.count(id)) return;
1779 std::string name = branch_names[id];
1780 runemufn_async([this, name]() {
1781 try {
1782 mbranch_set(name);
1783 } catch(std::exception& e) {
1784 std::string err = e.what();
1785 runuifun([this, err]() {
1786 show_message_ok(this, "Error changing branch",
1787 "Can't change branch: " + err, wxICON_EXCLAMATION);
1794 uint64_t wxeditor_movie::_moviepanel::first_editable(unsigned index)
1796 uint64_t cffs = cached_cffs;
1797 if(!subframe_to_frame.count(cffs))
1798 return cffs;
1799 uint64_t f = subframe_to_frame[cffs];
1800 pollcounter_vector& pv = movb.get_movie().get_pollcounters();
1801 uint32_t pc = fcontrols.read_pollcount(pv, index);
1802 for(uint32_t i = 1; i < pc; i++)
1803 if(!subframe_to_frame.count(cffs + i) || subframe_to_frame[cffs + i] > f)
1804 return cffs + i;
1805 return cffs + pc;
1808 uint64_t wxeditor_movie::_moviepanel::first_nextframe()
1810 uint64_t base = first_editable(0);
1811 if(!subframe_to_frame.count(cached_cffs))
1812 return cached_cffs;
1813 uint64_t f = subframe_to_frame[cached_cffs];
1814 for(uint32_t i = 0;; i++)
1815 if(!subframe_to_frame.count(base + i) || subframe_to_frame[base + i] > f)
1816 return base + i;
1819 void wxeditor_movie::_moviepanel::on_mouse1(unsigned x, unsigned y, bool polarity) {}
1820 void wxeditor_movie::_moviepanel::on_mouse2(unsigned x, unsigned y, bool polarity)
1822 if(polarity) {
1823 //Pressing mouse, just record line it was pressed on.
1824 rpress_line = spos + y - 3;
1825 return;
1827 //Releasing mouse, open popup menu.
1828 unsigned off = divcnt + 1;
1829 press_x = x;
1830 if(y < 3)
1831 return;
1832 press_line = spos + y - 3;
1833 wxMenu menu;
1834 current_popup = &menu;
1835 menu.Connect(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(wxeditor_movie::_moviepanel::on_popup_menu),
1836 NULL, this);
1838 //Find what controller is the click on.
1839 bool clicked_button = false;
1840 control_info clicked;
1841 std::string controller_name;
1842 if(press_x < off) {
1843 clicked_button = false;
1844 press_index = std::numeric_limits<unsigned>::max();
1845 } else {
1846 for(auto i : fcontrols.get_controlinfo())
1847 if(press_x >= i.position_left + off && press_x < i.position_left + i.reserved + off) {
1848 if(i.type == 0 || i.type == 1) {
1849 clicked_button = true;
1850 clicked = i;
1851 controller_name = (stringfmt() << "controller " << i.port << "-"
1852 << (i.controller + 1)).str();
1853 press_index = i.index;
1858 //Find first editable frame, controllerframe and buttonframe.
1859 bool not_editable = !movb.get_movie().readonly_mode();
1860 uint64_t eframe_low = first_editable(0);
1861 uint64_t ebutton_low = clicked_button ? first_editable(clicked.index) : std::numeric_limits<uint64_t>::max();
1862 uint64_t econtroller_low = ebutton_low;
1863 for(auto i : fcontrols.get_controlinfo())
1864 if(i.port == clicked.port && i.controller == clicked.controller && (i.type == 0 || i.type == 1))
1865 econtroller_low = max(econtroller_low, first_editable(i.index));
1867 bool click_zero = (clicked_button && !clicked.port && !clicked.controller);
1868 bool enable_append_frame = !not_editable;
1869 bool enable_toggle_button = false;
1870 bool enable_change_axis = false;
1871 bool enable_sweep_axis = false;
1872 bool enable_insert_frame = false;
1873 bool enable_insert_controller = false;
1874 bool enable_delete_frame = false;
1875 bool enable_delete_subframe = false;
1876 bool enable_delete_controller_subframe = false;
1877 bool enable_truncate_movie = false;
1878 bool enable_cut_frame = false;
1879 bool enable_copy_frame = false;
1880 bool enable_paste_frame = false;
1881 bool enable_paste_append = false;
1882 std::string copy_title;
1883 std::string paste_title;
1885 //Toggle button is enabled if clicked on button and either end is in valid range.
1886 enable_toggle_button = (!not_editable && clicked_button && clicked.type == 0 && ((press_line >= ebutton_low &&
1887 press_line < linecount) || (rpress_line >= ebutton_low && rpress_line < linecount)));
1888 //Change axis is enabled in similar conditions, except if type is axis.
1889 enable_change_axis = (!not_editable && clicked_button && clicked.type == 1 && ((press_line >= ebutton_low &&
1890 press_line < linecount) || (rpress_line >= ebutton_low && rpress_line < linecount)));
1891 //Sweep axis is enabled if change axis is enabled and lines don't match.
1892 enable_sweep_axis = (enable_change_axis && press_line != rpress_line);
1893 //Insert frame is enabled if this frame is completely editable and press and release lines match.
1894 enable_insert_frame = (!not_editable && press_line + 1 >= eframe_low && press_line < linecount &&
1895 press_line == rpress_line);
1896 //Insert controller frame is enabled if controller is completely editable and lines match.
1897 enable_insert_controller = (!not_editable && clicked_button && press_line >= econtroller_low &&
1898 press_line < linecount && press_line == rpress_line);
1899 enable_insert_controller = enable_insert_controller && (clicked.port || clicked.controller);
1900 //Delete frame is enabled if range is completely editable (relative to next-frame).
1901 enable_delete_frame = (!not_editable && press_line >= first_nextframe() && press_line < linecount &&
1902 rpress_line >= first_nextframe() && rpress_line < linecount);
1903 //Delete subframe is enabled if range is completely editable.
1904 enable_delete_subframe = (!not_editable && press_line >= eframe_low && press_line < linecount &&
1905 rpress_line >= eframe_low && rpress_line < linecount);
1906 //Delete controller subframe is enabled if range is completely controller-editable.
1907 enable_delete_controller_subframe = (!not_editable && clicked_button && press_line >= econtroller_low &&
1908 press_line < linecount && rpress_line >= econtroller_low && rpress_line < linecount);
1909 enable_delete_controller_subframe = enable_delete_controller_subframe && (clicked.port || clicked.controller);
1910 //Truncate movie is enabled if lines match and is completely editable.
1911 enable_truncate_movie = (!not_editable && press_line == rpress_line && press_line >= eframe_low &&
1912 press_line < linecount);
1913 //Cut frames is enabled if range is editable (possibly controller-editable).
1914 if(clicked_button)
1915 enable_cut_frame = (!not_editable && press_line >= econtroller_low && press_line < linecount
1916 && rpress_line >= econtroller_low && rpress_line < linecount && !click_zero);
1917 else
1918 enable_cut_frame = (!not_editable && press_line >= eframe_low && press_line < linecount
1919 && rpress_line >= eframe_low && rpress_line < linecount);
1920 if(clicked_button && clipboard_get_data_type() == 0) {
1921 enable_paste_append = (!not_editable && linecount >= eframe_low);
1922 enable_paste_frame = (!not_editable && press_line >= econtroller_low && press_line < linecount
1923 && rpress_line >= econtroller_low && rpress_line < linecount && !click_zero);
1924 } else if(clipboard_get_data_type() == 1) {
1925 enable_paste_append = (!not_editable && linecount >= econtroller_low);
1926 enable_paste_frame = (!not_editable && press_line >= eframe_low && press_line < linecount
1927 && rpress_line >= eframe_low && rpress_line < linecount);
1929 //Copy frames is enabled if range exists.
1930 enable_copy_frame = (press_line < linecount && rpress_line < linecount);
1931 copy_title = (clicked_button ? controller_name : "frames");
1932 paste_title = ((clipboard_get_data_type() == 0) ? copy_title : "frames");
1934 if(clipboard_get_data_type() == 0 && click_zero) enable_paste_append = enable_paste_frame = false;
1936 if(enable_toggle_button)
1937 menu.Append(wxID_TOGGLE, towxstring(U"Toggle " + clicked.title));
1938 if(enable_change_axis)
1939 menu.Append(wxID_CHANGE, towxstring(U"Change " + clicked.title));
1940 if(enable_sweep_axis)
1941 menu.Append(wxID_SWEEP, towxstring(U"Sweep " + clicked.title));
1942 if(enable_toggle_button || enable_change_axis || enable_sweep_axis)
1943 menu.AppendSeparator();
1944 menu.Append(wxID_INSERT_AFTER, wxT("Insert frame after"))->Enable(enable_insert_frame);
1945 menu.Append(wxID_INSERT_CONTROLLER_AFTER, wxT("Insert controller frame"))
1946 ->Enable(enable_insert_controller);
1947 menu.Append(wxID_APPEND_FRAME, wxT("Append frame"))->Enable(enable_append_frame);
1948 menu.Append(wxID_APPEND_FRAMES, wxT("Append frames..."))->Enable(enable_append_frame);
1949 menu.AppendSeparator();
1950 menu.Append(wxID_DELETE_FRAME, wxT("Delete frame(s)"))->Enable(enable_delete_frame);
1951 menu.Append(wxID_DELETE_SUBFRAME, wxT("Delete subframe(s)"))->Enable(enable_delete_subframe);
1952 menu.Append(wxID_DELETE_CONTROLLER_SUBFRAMES, wxT("Delete controller subframes(s)"))
1953 ->Enable(enable_delete_controller_subframe);
1954 menu.AppendSeparator();
1955 menu.Append(wxID_TRUNCATE, wxT("Truncate movie"))->Enable(enable_truncate_movie);
1956 menu.AppendSeparator();
1957 menu.Append(wxID_CUT_FRAMES, towxstring("Cut " + copy_title))->Enable(enable_cut_frame);
1958 menu.Append(wxID_COPY_FRAMES, towxstring("Copy " + copy_title))->Enable(enable_copy_frame);
1959 menu.Append(wxID_PASTE_FRAMES, towxstring("Paste " + paste_title))->Enable(enable_paste_frame);
1960 menu.Append(wxID_PASTE_APPEND, towxstring("Paste append " + paste_title))->Enable(enable_paste_append);
1961 menu.AppendSeparator();
1962 menu.Append(wxID_SCROLL_FRAME, wxT("Scroll to frame..."));
1963 menu.Append(wxID_SCROLL_CURRENT_FRAME, wxT("Scroll to current frame"));
1964 menu.Append(wxID_RUN_TO_FRAME, wxT("Run to frame..."));
1965 menu.Append(wxID_CHANGE_LINECOUNT, wxT("Change number of lines visible"));
1966 menu.AppendCheckItem(wxID_POSITION_LOCK, wxT("Lock scroll to playback"))->Check(position_locked);
1967 menu.AppendSeparator();
1969 wxMenu* branches_submenu = new wxMenu();
1970 branches_submenu->Append(wxID_MBRANCH_NEW, wxT("New branch..."));
1971 branches_submenu->Append(wxID_MBRANCH_IMPORT, wxT("Import branch..."));
1972 branches_submenu->Append(wxID_MBRANCH_EXPORT, wxT("Export branch..."));
1973 branches_submenu->Append(wxID_MBRANCH_RENAME, wxT("Rename branch..."));
1974 branches_submenu->Append(wxID_MBRANCH_DELETE, wxT("Delete branch..."));
1975 branches_submenu->AppendSeparator();
1976 std::set<std::string> list;
1977 std::string current;
1978 bool ro;
1979 runemufn([&list, &current, &ro]() {
1980 list = mbranch_enumerate();
1981 current = mbranch_get();
1982 ro = movb.get_movie().readonly_mode();
1984 int ass_id = wxID_MBRANCH_FIRST;
1985 for(auto i : list) {
1986 bool selected = (i == current);
1987 wxMenuItem* it;
1988 it = branches_submenu->AppendCheckItem(ass_id, towxstring(mbranch_name(i)));
1989 branch_names[ass_id++] = i;
1990 if(selected) it->Check(selected);
1991 it->Enable(ro);
1993 menu.AppendSubMenu(branches_submenu, wxT("Branches"));
1994 menu.Connect(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(wxeditor_movie::_moviepanel::on_popup_menu),
1995 NULL, this);
1996 branches_submenu->Connect(wxEVT_COMMAND_MENU_SELECTED,
1997 wxCommandEventHandler(wxeditor_movie::_moviepanel::on_popup_menu), NULL, this);
1998 PopupMenu(&menu);
1999 //delete branches_submenu;
2002 int wxeditor_movie::_moviepanel::get_lines()
2004 controller_frame_vector& fv = *movb.get_mfile().input;
2005 return fv.size();
2008 void wxeditor_movie::_moviepanel::signal_repaint()
2010 if(requested || recursing)
2011 return;
2012 auto s = m->get_scroll();
2013 requested = true;
2014 uint32_t width, height;
2015 uint64_t lines;
2016 wxeditor_movie* m2 = m;
2017 uint64_t old_cached_cffs = cached_cffs;
2018 uint32_t prev_width, prev_height;
2019 bool done_again = false;
2020 do_again:
2021 runemufn([&lines, &width, &height, m2, this]() {
2022 lines = this->get_lines();
2023 if(lines < lines_to_display)
2024 this->moviepos = 0;
2025 else if(this->moviepos > lines - lines_to_display)
2026 this->moviepos = lines - lines_to_display;
2027 this->render(fb, moviepos);
2028 auto x = fb.get_characters();
2029 width = x.first;
2030 height = x.second;
2032 if(old_cached_cffs != cached_cffs && position_locked && !done_again) {
2033 moviepos = cached_cffs;
2034 done_again = true;
2035 goto do_again;
2037 prev_width = new_width;
2038 prev_height = new_height;
2039 new_width = width;
2040 new_height = height;
2041 movielines = lines;
2042 if(s) {
2043 s->set_range(lines);
2044 s->set_position(moviepos);
2046 auto size = fb.get_pixels();
2047 pixels.resize(size.first * size.second * 3);
2048 fb.render((char*)&pixels[0]);
2049 if(prev_width != new_width || prev_height != new_height) {
2050 auto cell = fb.get_cell();
2051 SetMinSize(wxSize(new_width * cell.first, (lines_to_display + 3) * cell.second));
2052 if(new_width > 0 && s)
2053 m->Fit();
2055 linecount = lines;
2056 Refresh();
2059 void wxeditor_movie::_moviepanel::on_mouse(wxMouseEvent& e)
2061 auto cell = fb.get_cell();
2062 if(e.LeftDown() && !e.ControlDown())
2063 on_mouse0(e.GetX() / cell.first, e.GetY() / cell.second, true, e.ShiftDown(), e.GetX(), e.GetY());
2064 if(e.LeftUp() && !e.ControlDown())
2065 on_mouse0(e.GetX() / cell.first, e.GetY() / cell.second, false, e.ShiftDown(), e.GetX(), e.GetY());
2066 if(e.MiddleDown())
2067 on_mouse1(e.GetX() / cell.first, e.GetY() / cell.second, true);
2068 if(e.MiddleUp())
2069 on_mouse1(e.GetX() / cell.first, e.GetY() / cell.second, false);
2070 if(e.RightDown() || (e.LeftDown() && e.ControlDown()))
2071 on_mouse2(e.GetX() / cell.first, e.GetY() / cell.second, true);
2072 if(e.RightUp() || (e.LeftUp() && e.ControlDown()))
2073 on_mouse2(e.GetX() / cell.first, e.GetY() / cell.second, false);
2074 auto s = m->get_scroll();
2075 unsigned speed = 1;
2076 if(e.ShiftDown())
2077 speed = 10;
2078 if(e.ShiftDown() && e.ControlDown())
2079 speed = 50;
2080 s->apply_wheel(e.GetWheelRotation(), e.GetWheelDelta(), speed);
2083 void wxeditor_movie::_moviepanel::on_erase(wxEraseEvent& e)
2085 //Blank.
2088 void wxeditor_movie::_moviepanel::on_paint(wxPaintEvent& e)
2090 auto size = fb.get_pixels();
2091 if(!size.first || !size.second) {
2092 wxPaintDC dc(this);
2093 dc.Clear();
2094 requested = false;
2095 return;
2097 wxPaintDC dc(this);
2098 wxBitmap bmp(wxImage(size.first, size.second, &pixels[0], true));
2099 dc.DrawBitmap(bmp, 0, 0, false);
2100 requested = false;
2103 void wxeditor_movie::_moviepanel::do_copy(uint64_t row1, uint64_t row2, unsigned port, unsigned controller)
2105 frame_controls* _fcontrols = &fcontrols;
2106 uint64_t line = row1;
2107 uint64_t line2 = row2;
2108 if(line2 < line)
2109 std::swap(line, line2);
2110 std::string copied;
2111 runemufn([port, controller, line, line2, _fcontrols, &copied]() {
2112 controller_frame_vector& fv = *movb.get_mfile().input;
2113 uint64_t vsize = fv.size();
2114 if(!vsize)
2115 return;
2116 uint64_t _line = min(line, vsize - 1);
2117 uint64_t _line2 = min(line2, vsize - 1);
2118 copied = encode_lines(*_fcontrols, fv, _line, _line2 + 1, port, controller);
2120 copy_to_clipboard(copied);
2123 void wxeditor_movie::_moviepanel::do_copy(uint64_t row1, uint64_t row2)
2125 uint64_t line = row1;
2126 uint64_t line2 = row2;
2127 if(line2 < line)
2128 std::swap(line, line2);
2129 std::string copied;
2130 runemufn([line, line2, &copied]() {
2131 controller_frame_vector& fv = *movb.get_mfile().input;
2132 uint64_t vsize = fv.size();
2133 if(!vsize)
2134 return;
2135 uint64_t _line = min(line, vsize - 1);
2136 uint64_t _line2 = min(line2, vsize - 1);
2137 copied = encode_lines(fv, _line, _line2 + 1);
2139 copy_to_clipboard(copied);
2142 void wxeditor_movie::_moviepanel::do_cut(uint64_t row1, uint64_t row2, unsigned port, unsigned controller)
2144 do_copy(row1, row2, port, controller);
2145 do_delete_controller(row1, row2, port, controller);
2148 void wxeditor_movie::_moviepanel::do_cut(uint64_t row1, uint64_t row2)
2150 do_copy(row1, row2);
2151 do_delete_frame(row1, row2, false);
2154 void wxeditor_movie::_moviepanel::do_paste(uint64_t row, bool append)
2156 frame_controls* _fcontrols = &fcontrols;
2157 recursing = true;
2158 uint64_t _gapstart = row;
2159 std::string cliptext = copy_from_clipboard();
2160 runemufn([_fcontrols, &cliptext, _gapstart, append]() {
2161 //Insert enough lines for the pasted content.
2162 uint64_t gapstart = _gapstart;
2163 if(!movb.get_movie().readonly_mode())
2164 return;
2165 uint64_t gaplen = 0;
2166 int64_t newframes = 0;
2168 std::istringstream y(cliptext);
2169 std::string z;
2170 if(!std::getline(y, z))
2171 return;
2172 istrip_CR(z);
2173 if(z != "lsnes-moviedata-whole")
2174 return;
2175 while(std::getline(y, z))
2176 gaplen++;
2178 controller_frame_vector& fv = *movb.get_mfile().input;
2179 uint64_t vsize = fv.size();
2180 if(gapstart < real_first_editable(*_fcontrols, 0))
2181 return;
2182 if(gapstart > vsize)
2183 return;
2184 controller_frame_vector::notify_freeze freeze(fv);
2185 if(append) gapstart = vsize;
2186 for(uint64_t i = 0; i < gaplen; i++)
2187 fv.append(fv.blank_frame(false));
2188 for(uint64_t i = vsize - 1; i >= gapstart && i <= vsize; i--)
2189 fv[i + gaplen] = fv[i];
2190 //Write the pasted frames.
2192 std::istringstream y(cliptext);
2193 std::string z;
2194 std::getline(y, z);
2195 uint64_t idx = gapstart;
2196 while(std::getline(y, z)) {
2197 fv[idx++].deserialize(z.c_str());
2198 if(fv[idx - 1].sync())
2199 newframes++;
2203 recursing = false;
2204 signal_repaint();
2207 void wxeditor_movie::_moviepanel::do_paste(uint64_t row, unsigned port, unsigned controller, bool append)
2209 if(!port && !controller)
2210 return;
2211 frame_controls* _fcontrols = &fcontrols;
2212 auto iset = controller_index_set(fcontrols, port, controller);
2213 recursing = true;
2214 uint64_t _gapstart = row;
2215 std::string cliptext = copy_from_clipboard();
2216 runemufn([_fcontrols, iset, &cliptext, _gapstart, port, controller, append]() {
2217 //Insert enough lines for the pasted content.
2218 uint64_t gapstart = _gapstart;
2219 if(!movb.get_movie().readonly_mode())
2220 return;
2221 uint64_t gaplen = 0;
2222 int64_t newframes = 0;
2224 std::istringstream y(cliptext);
2225 std::string z;
2226 if(!std::getline(y, z))
2227 return;
2228 istrip_CR(z);
2229 if(z != "lsnes-moviedata-controller")
2230 return;
2231 while(std::getline(y, z)) {
2232 gaplen++;
2233 newframes++;
2236 controller_frame_vector& fv = *movb.get_mfile().input;
2237 uint64_t vsize = fv.size();
2238 if(gapstart < real_first_editable(*_fcontrols, iset))
2239 return;
2240 if(gapstart > vsize)
2241 return;
2242 controller_frame_vector::notify_freeze freeze(fv);
2243 if(append) gapstart = vsize;
2244 for(uint64_t i = 0; i < gaplen; i++)
2245 fv.append(fv.blank_frame(true));
2246 move_index_set(*_fcontrols, fv, gapstart, gapstart + gaplen, vsize - gapstart, iset);
2247 //Write the pasted frames.
2249 std::istringstream y(cliptext);
2250 std::string z;
2251 std::getline(y, z);
2252 uint64_t idx = gapstart;
2253 while(std::getline(y, z)) {
2254 controller_frame f = fv[idx++];
2255 decode_line(*_fcontrols, f, z, port, controller);
2259 recursing = false;
2260 signal_repaint();
2263 void wxeditor_movie::_moviepanel::do_insert_controller(uint64_t row, unsigned port, unsigned controller)
2265 if(!port && !controller)
2266 return;
2267 frame_controls* _fcontrols = &fcontrols;
2268 auto iset = controller_index_set(fcontrols, port, controller);
2269 recursing = true;
2270 uint64_t gapstart = row;
2271 runemufn([_fcontrols, iset, gapstart, port, controller]() {
2272 //Insert enough lines for the pasted content.
2273 if(!movb.get_movie().readonly_mode())
2274 return;
2275 controller_frame_vector& fv = *movb.get_mfile().input;
2276 uint64_t vsize = fv.size();
2277 if(gapstart < real_first_editable(*_fcontrols, iset))
2278 return;
2279 if(gapstart > vsize)
2280 return;
2281 fv.append(fv.blank_frame(true));
2282 move_index_set(*_fcontrols, fv, gapstart, gapstart + 1, vsize - gapstart, iset);
2283 zero_index_set(*_fcontrols, fv, gapstart, 1, iset);
2285 recursing = false;
2286 signal_repaint();
2289 void wxeditor_movie::_moviepanel::do_delete_controller(uint64_t row1, uint64_t row2, unsigned port,
2290 unsigned controller)
2292 if(!port && !controller)
2293 return;
2294 frame_controls* _fcontrols = &fcontrols;
2295 auto iset = controller_index_set(fcontrols, port, controller);
2296 recursing = true;
2297 if(row1 > row2) std::swap(row1, row2);
2298 uint64_t gapstart = row1;
2299 uint64_t gaplen = row2 - row1 + 1;
2300 runemufn([_fcontrols, iset, gapstart, gaplen, port, controller]() {
2301 //Insert enough lines for the pasted content.
2302 if(!movb.get_movie().readonly_mode())
2303 return;
2304 controller_frame_vector& fv = *movb.get_mfile().input;
2305 uint64_t vsize = fv.size();
2306 if(gapstart < real_first_editable(*_fcontrols, iset))
2307 return;
2308 if(gapstart > vsize)
2309 return;
2310 move_index_set(*_fcontrols, fv, gapstart + gaplen, gapstart, vsize - gapstart - gaplen, iset);
2311 zero_index_set(*_fcontrols, fv, vsize - gaplen, gaplen, iset);
2313 recursing = false;
2314 signal_repaint();
2318 wxeditor_movie::wxeditor_movie(wxWindow* parent)
2319 : wxDialog(parent, wxID_ANY, wxT("lsnes: Edit movie"), wxDefaultPosition, wxSize(-1, -1))
2321 closing = false;
2322 Centre();
2323 wxFlexGridSizer* top_s = new wxFlexGridSizer(2, 1, 0, 0);
2324 SetSizer(top_s);
2326 wxBoxSizer* panel_s = new wxBoxSizer(wxHORIZONTAL);
2327 moviescroll = NULL;
2328 panel_s->Add(moviepanel = new _moviepanel(this), 1, wxGROW);
2329 panel_s->Add(moviescroll = new scroll_bar(this, wxID_ANY, true), 0, wxGROW);
2330 top_s->Add(panel_s, 1, wxGROW);
2332 moviescroll->set_page_size(lines_to_display);
2333 moviescroll->set_handler([this](scroll_bar& s) {
2334 this->moviepanel->moviepos = s.get_position();
2335 this->moviepanel->signal_repaint();
2337 moviepanel->Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(wxeditor_movie::on_keyboard_down), NULL, this);
2338 moviepanel->Connect(wxEVT_KEY_UP, wxKeyEventHandler(wxeditor_movie::on_keyboard_up), NULL, this);
2340 wxBoxSizer* pbutton_s = new wxBoxSizer(wxHORIZONTAL);
2341 pbutton_s->AddStretchSpacer();
2342 pbutton_s->Add(closebutton = new wxButton(this, wxID_OK, wxT("Close")), 0, wxGROW);
2343 closebutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
2344 wxCommandEventHandler(wxeditor_movie::on_close), NULL, this);
2345 top_s->Add(pbutton_s, 0, wxGROW);
2347 moviepanel->SetFocus();
2348 moviescroll->Connect(wxEVT_SET_FOCUS, wxFocusEventHandler(wxeditor_movie::on_focus_wrong), NULL, this);
2349 closebutton->Connect(wxEVT_SET_FOCUS, wxFocusEventHandler(wxeditor_movie::on_focus_wrong), NULL, this);
2350 Connect(wxEVT_SET_FOCUS, wxFocusEventHandler(wxeditor_movie::on_focus_wrong), NULL, this);
2352 panel_s->SetSizeHints(this);
2353 pbutton_s->SetSizeHints(this);
2354 top_s->SetSizeHints(this);
2355 Connect(wxEVT_CLOSE_WINDOW, wxCloseEventHandler(wxeditor_movie::on_wclose));
2356 Fit();
2358 moviepanel->signal_repaint();
2361 bool wxeditor_movie::ShouldPreventAppExit() const { return false; }
2363 void wxeditor_movie::on_close(wxCommandEvent& e)
2365 movieeditor_open = NULL;
2366 Destroy();
2367 closing = true;
2370 void wxeditor_movie::on_wclose(wxCloseEvent& e)
2372 bool wasc = closing;
2373 closing = true;
2374 movieeditor_open = NULL;
2375 if(!wasc)
2376 Destroy();
2379 void wxeditor_movie::update()
2381 moviepanel->signal_repaint();
2384 scroll_bar* wxeditor_movie::get_scroll()
2386 return moviescroll;
2389 void wxeditor_movie::on_focus_wrong(wxFocusEvent& e)
2391 moviepanel->SetFocus();
2394 void wxeditor_movie_display(wxWindow* parent)
2396 if(movieeditor_open)
2397 return;
2398 wxeditor_movie* v = new wxeditor_movie(parent);
2399 v->Show();
2400 movieeditor_open = v;
2403 void wxeditor_movie::on_keyboard_down(wxKeyEvent& e)
2405 handle_wx_keyboard(e, true);
2408 void wxeditor_movie::on_keyboard_up(wxKeyEvent& e)
2410 handle_wx_keyboard(e, false);
2413 void wxeditor_movie_update()
2415 if(movieeditor_open)
2416 movieeditor_open->update();