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"
34 #define UINT64_C(val) val##ULL
36 #include <libswscale/swscale.h>
41 wxID_TOGGLE
= wxID_HIGHEST
+ 1,
45 wxID_CHANGE_LINECOUNT
,
47 wxID_INSERT_AFTER_MULTIPLE
,
55 wxID_SCROLL_CURRENT_FRAME
,
60 wxID_INSERT_CONTROLLER_AFTER
,
61 wxID_DELETE_CONTROLLER_SUBFRAMES
,
68 wxID_MBRANCH_LAST
= wxID_MBRANCH_FIRST
+ 1024
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]);
81 typedef std::pair
<std::string
, int> returntype
;
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"));
91 ip
.types
.push_back(filedialog_type_entry("Movie files", "*.lsmv", "lsmv"));
95 std::pair
<std::string
, int> output(const filedialog_output_params
& p
, bool save
) const
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
);
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.
116 std::u32string title
;
119 portctrl::button::_type axistype
;
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
,
131 control_info
control_info::portinfo(unsigned& p
, unsigned port
, unsigned controller
)
135 i
.reserved
= (stringfmt() << port
<< "-" << controller
).str32().length();
142 i
.controller
= controller
;
146 control_info
control_info::fixedinfo(unsigned& p
, const std::u32string
& str
)
150 i
.reserved
= str
.length();
161 control_info
control_info::buttoninfo(unsigned& p
, char32_t character
, const std::u32string
& title
, unsigned idx
,
162 unsigned port
, unsigned controller
)
173 i
.controller
= controller
;
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
)
182 i
.reserved
= title
.length();
191 i
.controller
= controller
;
192 i
.axistype
= _axistype
;
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
; }
212 std::u32string _line1
;
213 std::u32string _line2
;
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()
225 void frame_controls::set_types(portctrl::frame
& f
)
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
);
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
];
242 controlinfo
.push_back(control_info::fixedinfo(c
, U
"\u2502"));
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
)
251 if(pcb
.type
== portctrl::button::TYPE_BUTTON
) {
254 controlinfo
.push_back(control_info::buttoninfo(c
, pcb
.symbol
, utf8::to32(pcb
.name
),
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
) {
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;
273 short frame_controls::read_index(portctrl::frame
& f
, unsigned idx
)
276 return f
.sync() ? 1 : 0;
280 void frame_controls::write_index(portctrl::frame
& f
, unsigned idx
, short value
)
283 return f
.sync(value
);
284 return f
.axis2(idx
, value
);
287 uint32_t frame_controls::read_pollcount(portctrl::counters
& v
, unsigned idx
)
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()
300 for(auto i
: controlinfo
) {
301 if(i
.position_left
+ i
.reserved
> _width
)
302 _width
= i
.position_left
+ i
.reserved
;
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;
314 //For every port-controller, find the least coordinate.
315 for(auto i
: controlinfo
) {
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
]);
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
]);
330 cp2
[i
.position_left
+ off
] = i
.ch
;
338 //TODO: Use real clipboard.
339 std::string clipboard
;
341 void copy_to_clipboard(const std::string
& text
)
346 bool clipboard_has_text()
348 return (clipboard
.length() > 0);
351 std::string
copy_from_clipboard()
356 std::string
encode_line(portctrl::frame
& f
)
363 std::string
encode_line(frame_controls
& info
, portctrl::frame
& f
, unsigned port
,
366 std::ostringstream x
;
367 bool last_axis
= false;
369 for(auto i
: info
.get_controlinfo()) {
372 if(i
.controller
!= controller
)
378 if(info
.read_index(f
, i
.index
)) {
382 x
<< utf8::to8(std::u32string(tmp1
));
391 x
<< info
.read_index(f
, i
.index
);
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
] == '-') {
408 while(r
< s
.length() && s
[r
] >= 48 && s
[r
] <= 57) {
409 _res
= _res
* 10 + (s
[r
] - 48);
412 return negative
? -_res
: _res
;
415 void decode_line(frame_controls
& info
, portctrl::frame
& f
, std::string line
, unsigned port
,
418 std::u32string _line
= utf8::to32(line
);
419 bool last_axis
= false;
424 for(auto i
: info
.get_controlinfo()) {
427 if(i
.controller
!= controller
)
433 while(ridx
< _line
.length() && (_line
[ridx
] == 9 || _line
[ridx
] == 10 ||
434 _line
[ridx
] == 13 || _line
[ridx
] == 32))
437 y2
= (ridx
< _line
.length()) ? _line
[ridx
++] : 0;
438 if(y2
== U
'-' || y2
== 0)
439 info
.write_index(f
, i
.index
, 0);
441 info
.write_index(f
, i
.index
, 1);
448 while(ridx
< _line
.length() && (_line
[ridx
] == 9 || _line
[ridx
] == 10 ||
449 _line
[ridx
] == 13 || _line
[ridx
] == 32))
451 y
= read_short(_line
, ridx
);
452 info
.write_index(f
, i
.index
, y
);
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
;
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
;
483 int clipboard_get_data_type()
485 if(!clipboard_has_text())
487 std::string y
= copy_from_clipboard();
488 std::istringstream
x(y
);
490 std::getline(x
, hdr
);
491 if(hdr
== "lsnes-moviedata-whole")
493 if(hdr
== "lsnes-moviedata-controller")
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))
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
)
513 portctrl::frame_vector::notify_freeze
freeze(fv
);
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
));
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
)
551 bool next_flag
= false;
552 control_info previous
;
554 if(i
.port
!= ci
.port
|| i
.controller
!= ci
.controller
)
556 if(i
.axistype
!= portctrl::button::TYPE_AXIS
&&
557 i
.axistype
!= portctrl::button::TYPE_RAXIS
&&
558 i
.axistype
!= portctrl::button::TYPE_LIGHTGUN
)
562 if(i
.index
== ci
.index
) {
565 next_flag
= true; //Next.
567 return previous
; //Pevious.
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
);
582 int32_t center
= rmax
/ 2;
583 int32_t cc
= (dim
- 1) / 2;
587 //0 => 0, center => cc.
588 return (val
* (int64_t)cc
+ (center
/ 2)) / center
;
591 //center => cc, rmax => dim - 1.
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) {
605 val
= min(max(val
, (int32_t)0), dim
- 1);
606 int32_t center
= (rmax
+ rmin
) / 2;
607 int32_t cc
= (dim
- 1) / 2;
611 //0 => rmin, cc => center.
612 return ((center
- rmin
) * (int64_t)val
+ cc
/ 2) / cc
+ rmin
;
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();
627 return (stringfmt() << utf8::to8(X
.title
) << "/" << utf8::to8(Y
.title
)).str();
630 class window_prompt
: public wxDialog
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
))
645 if(X
.index
== Y
.index
) {
646 //One-axis never has a bitmap.
651 wxSizer
* s
= new wxBoxSizer(wxVERTICAL
);
653 s
->Add(panel
= new wxPanel(this, wxID_ANY
, wxDefaultPosition
, wxSize(width
, height
)), 0,
655 panel
->Connect(wxEVT_PAINT
, wxPaintEventHandler(window_prompt::on_paint
), NULL
, this);
656 panel
->Connect(wxEVT_ERASE_BACKGROUND
, wxEraseEventHandler(window_prompt::on_erase
), NULL
,
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);
663 void on_wclose(wxCloseEvent
& e
)
666 EndModal(wxID_CANCEL
);
668 void on_erase(wxEraseEvent
& e
)
672 void on_paint(wxPaintEvent
& e
)
677 wxBitmap
bmp(wxImage(width
, height
, bitmap
, true));
678 dc
.DrawBitmap(bmp
, 0, 0, false);
680 dc
.SetBackground(*wxWHITE_BRUSH
);
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
);
693 dc
.DrawLine(0, mouseY
, width
, mouseY
);
696 void on_mouse(wxMouseEvent
& e
)
700 result
.first
= coordinate_to_value(cX
.rmin
, cX
.rmax
, e
.GetX(), width
);
702 result
.second
= coordinate_to_value(cY
.rmin
, cY
.rmax
, e
.GetY(), height
);
714 std::pair
<int, int> get_results()
719 std::pair
<int, int> result
;
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
)
736 window_prompt
* p
= new window_prompt(parent
, bitmap
, width
, height
, X
, Y
, posX
, posY
);
737 if(p
->ShowModal() == wxID_CANCEL
) {
739 throw canceled_exception();
741 auto r
= p
->get_results();
747 class wxeditor_movie
: public wxDialog
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();
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
);
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
;
808 std::map
<uint64_t, uint64_t> subframe_to_frame
;
809 uint64_t max_subframe
;
810 frame_controls fcontrols
;
817 std::vector
<uint8_t> pixels
;
820 uint64_t rpress_line
;
821 unsigned press_index
;
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
;
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())
856 uint64_t real_first_editable(frame_controls
& fc
, std::set
<unsigned> idx
)
860 m
= max(m
, real_first_editable(fc
, i
));
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())
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
)
888 Connect(wxEVT_PAINT
, wxPaintEventHandler(_moviepanel::on_paint
), NULL
, this);
889 Connect(wxEVT_ERASE_BACKGROUND
, wxEraseEventHandler(_moviepanel::on_erase
), NULL
, this);
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);
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
];
923 subframe_to_frame
[i
] = prev
+ 1;
925 subframe_to_frame
[i
] = prev
;
927 max_subframe
= fv
.size();
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
];
935 subframe_to_frame
[i
] = prev
+ 1;
937 subframe_to_frame
[i
] = prev
;
939 max_subframe
= fv
.size();
940 portctrl::frame model
= fv
.blank_frame(false);
941 fcontrols
.set_types(model
);
943 prev_seqno
= m
.get_seqno();
946 int wxeditor_movie::_moviepanel::width(portctrl::frame
& f
)
949 return divcnt
+ 1 + fcontrols
.width();
952 std::u32string
wxeditor_movie::_moviepanel::render_line1(portctrl::frame
& f
)
955 return fcontrols
.line1();
958 std::u32string
wxeditor_movie::_moviepanel::render_line2(portctrl::frame
& f
)
961 return fcontrols
.line2();
964 void wxeditor_movie::_moviepanel::render_linen(text_framebuffer
& fb
, portctrl::frame
& f
, uint64_t sfn
, int y
)
967 size_t fbstride
= fb
.get_stride();
968 text_framebuffer::element
* _fb
= fb
.get_buffer();
969 text_framebuffer::element e
;
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
;
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();
985 if(!inst
.mlogic
->get_movie().readonly_mode())
987 else if(subframe_to_frame
[sfn
] < curframe
)
989 else if(subframe_to_frame
[sfn
] > curframe
)
991 bool now
= (subframe_to_frame
[sfn
] == curframe
);
992 unsigned xcord
= 32768;
996 for(auto i
: ctrlinfo
) {
998 unsigned off
= divcnt
+ 1;
999 bool cselected
= (xcord
>= i
.position_left
+ off
&& xcord
< i
.position_left
+ i
.reserved
+ off
);
1001 unsigned polls
= fcontrols
.read_pollcount(pv
, i
.index
);
1002 rpast
= ((cffs
+ polls
) > sfn
) ? 1 : 0;
1004 uint32_t bgc
= 0xC0C0C0;
1015 fb
.write(i
.title
, 0, divcnt
+ 1 + i
.position_left
, y
, 0x000000, 0xFFFFFF);
1016 } else if(i
.type
== 0) {
1019 bool v
= (fcontrols
.read_index(f
, i
.index
) != 0);
1022 fb
.write(c
, 0, divcnt
+ 1 + i
.position_left
, y
, v
? 0x000000 : 0xC8C8C8, bgc
);
1023 } else if(i
.type
== 1) {
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
)
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
;
1050 for(i
= pos
, j
= 3; i
< pos
+ lines_to_display
; i
++, j
++) {
1051 text_framebuffer::element e
;
1057 for(unsigned k
= 0; k
< fbsize
.first
; k
++)
1058 _fb
[j
* fbstride
+ k
] = e
;
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
);
1075 inst
.iqueue
->run([idx
, _press_line
, line
, _fcontrols
, _force_false
]() {
1076 if(!CORE().mlogic
->get_movie().readonly_mode())
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())
1084 portctrl::frame cf
= fv
[i
];
1086 _fcontrols
->write_index(cf
, idx
, !_fcontrols
->read_index(cf
, idx
));
1088 _fcontrols
->write_index(cf
, idx
, 0);
1093 max_subframe
= _press_line
; //Reparse.
1097 void wxeditor_movie::_moviepanel::do_alter_axis(unsigned idx
, uint64_t row1
, uint64_t row2
)
1100 frame_controls
* _fcontrols
= &fcontrols
;
1101 uint64_t line
= row1
;
1102 uint64_t line2
= row2
;
1105 inst
.iqueue
->run([idx
, line
, &value
, _fcontrols
, &valid
]() {
1106 if(!CORE().mlogic
->get_movie().readonly_mode()) {
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()) {
1116 portctrl::frame_vector::notify_freeze
freeze(fv
);
1117 portctrl::frame cf
= fv
[line
];
1118 value
= _fcontrols
->read_index(cf
, idx
);
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
) {
1127 } catch(std::exception
& e
) {
1128 wxMessageBox(wxT("Invalid value"), _T("Error"), wxICON_EXCLAMATION
| wxOK
, m
);
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())
1140 portctrl::frame cf
= fv
[i
];
1141 _fcontrols
->write_index(cf
, idx
, value
);
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
;
1156 std::swap(line
, line2
);
1157 inst
.iqueue
->run([idx
, line
, line2
, &value
, &value2
, _fcontrols
, &valid
]() {
1158 if(!CORE().mlogic
->get_movie().readonly_mode()) {
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()) {
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
);
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())
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
);
1192 void wxeditor_movie::_moviepanel::do_append_frames(uint64_t count
)
1195 uint64_t _count
= count
;
1196 inst
.iqueue
->run([_count
]() {
1197 if(!CORE().mlogic
->get_movie().readonly_mode())
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));
1208 void wxeditor_movie::_moviepanel::do_append_frames()
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
) {
1217 } catch(std::exception
& e
) {
1218 wxMessageBox(wxT("Invalid value"), _T("Error"), wxICON_EXCLAMATION
| wxOK
, m
);
1221 do_append_frames(value
);
1225 void wxeditor_movie::_moviepanel::do_insert_frame_after(uint64_t row
, bool multi
)
1228 uint64_t multicount
= 1;
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
) {
1235 } catch(std::exception
& e
) {
1236 wxMessageBox(wxT("Invalid value"), _T("Error"), wxICON_EXCLAMATION
| wxOK
, m
);
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())
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())
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);
1271 void wxeditor_movie::_moviepanel::do_delete_frame(uint64_t row1
, uint64_t row2
, bool wholeframe
)
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();
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));
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())
1298 while(lsf
< vsize
&& !fv
[lsf
].sync())
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
++)
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
);
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
++)
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.
1335 fv
[row1
].sync(true);
1338 max_subframe
= _row1
;
1343 void wxeditor_movie::_moviepanel::do_truncate(uint64_t row
)
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();
1353 if(_row
< real_first_editable(*_fcontrols
, 0))
1355 int64_t delete_count
= 0;
1356 for(uint64_t i
= _row
; i
< vsize
; i
++)
1366 void wxeditor_movie::_moviepanel::do_set_stop_at_frame()
1371 inst
.iqueue
->run([&curframe
]() {
1372 curframe
= CORE().mlogic
->get_movie().get_current_frame();
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
) {
1380 } catch(std::exception
& e
) {
1381 wxMessageBox(wxT("Invalid value"), _T("Error"), wxICON_EXCLAMATION
| wxOK
, m
);
1384 if(frame
< curframe
) {
1385 wxMessageBox(wxT("The movie is already past that point"), _T("Error"), wxICON_EXCLAMATION
| wxOK
, m
);
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
)
1400 press_line
= spos
+ y
- 3;
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
;
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
)) {
1422 do_toggle_buttons(idx
, press_line
, line
, false);
1423 else if(i
.type
== 1) {
1425 if(press_line
== line
&& (i
.port
|| i
.controller
))
1427 wxPoint spos
= GetScreenPosition();
1428 popup_axis_panel(line
, i
, spos
.x
+ X
, spos
.y
+ Y
);
1429 } catch(canceled_exception
& e
) {
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
)
1443 control_info ci2
= find_paired(ci
, fcontrols
.get_controlinfo());
1444 if(ci
.index
== ci2
.index
) {
1446 } else if(ci2
.index
< ci
.index
) {
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
);
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
);
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
);
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
);
1516 void wxeditor_movie::_moviepanel::do_scroll_to_frame()
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
) {
1525 } catch(std::exception
& e
) {
1526 wxMessageBox(wxT("Invalid value"), _T("Error"), wxICON_EXCLAMATION
| wxOK
, m
);
1529 uint64_t wouldbe
= 0;
1531 uint64_t high
= max_subframe
;
1533 wouldbe
= (low
+ high
) / 2;
1534 if(subframe_to_frame
[wouldbe
] < frame
)
1536 else if(subframe_to_frame
[wouldbe
] > frame
)
1541 while(wouldbe
> 1 && subframe_to_frame
[wouldbe
- 1] == frame
)
1547 void wxeditor_movie::_moviepanel::do_scroll_to_current_frame()
1549 moviepos
= cached_cffs
;
1553 void wxeditor_movie::_moviepanel::on_popup_menu(wxCommandEvent
& e
)
1556 wxMenuItem
* tmpitem
;
1560 unsigned controller
= 0;
1561 for(auto i
: fcontrols
.get_controlinfo())
1562 if(i
.index
== press_index
) {
1564 controller
= i
.controller
;
1569 do_toggle_buttons(press_index
, rpress_line
, press_line
, false);
1572 do_alter_axis(press_index
, rpress_line
, press_line
);
1575 do_toggle_buttons(press_index
, rpress_line
, press_line
, true);
1578 do_sweep_axis(press_index
, rpress_line
, press_line
);
1580 case wxID_APPEND_FRAME
:
1581 do_append_frames(1);
1583 case wxID_APPEND_FRAMES
:
1586 case wxID_INSERT_AFTER
:
1587 do_insert_frame_after(press_line
, false);
1589 case wxID_INSERT_AFTER_MULTIPLE
:
1590 do_insert_frame_after(press_line
, true);
1592 case wxID_DELETE_FRAME
:
1593 do_delete_frame(press_line
, rpress_line
, true);
1595 case wxID_DELETE_SUBFRAME
:
1596 do_delete_frame(press_line
, rpress_line
, false);
1599 do_truncate(press_line
);
1601 case wxID_RUN_TO_FRAME
:
1602 do_set_stop_at_frame();
1604 case wxID_SCROLL_FRAME
:
1605 do_scroll_to_frame();
1607 case wxID_SCROLL_CURRENT_FRAME
:
1608 do_scroll_to_current_frame();
1610 case wxID_POSITION_LOCK
:
1613 tmpitem
= current_popup
->FindItem(wxID_POSITION_LOCK
);
1614 position_locked
= tmpitem
->IsChecked();
1616 case wxID_CHANGE_LINECOUNT
:
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
) {
1627 } catch(std::exception
& e
) {
1628 wxMessageBox(wxT("Invalid value"), _T("Error"), wxICON_EXCLAMATION
| wxOK
, m
);
1633 case wxID_COPY_FRAMES
:
1634 if(press_index
== std::numeric_limits
<unsigned>::max())
1635 do_copy(rpress_line
, press_line
);
1637 do_copy(rpress_line
, press_line
, port
, controller
);
1639 case wxID_CUT_FRAMES
:
1640 if(press_index
== std::numeric_limits
<unsigned>::max())
1641 do_cut(rpress_line
, press_line
);
1643 do_cut(rpress_line
, press_line
, port
, controller
);
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);
1649 do_paste(press_line
, port
, controller
, false);
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);
1655 do_paste(press_line
, port
, controller
, true);
1657 case wxID_INSERT_CONTROLLER_AFTER
:
1658 if(press_index
== std::numeric_limits
<unsigned>::max())
1661 do_insert_controller(press_line
, port
, controller
);
1663 case wxID_DELETE_CONTROLLER_SUBFRAMES
:
1664 if(press_index
== std::numeric_limits
<unsigned>::max())
1667 do_delete_controller(press_line
, rpress_line
, port
, controller
);
1669 case wxID_MBRANCH_NEW
:
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
) {
1684 case wxID_MBRANCH_IMPORT
:
1687 std::string filename
;
1689 std::string dbranch
;
1690 auto g
= choose_file_load(this, "Choose file to import", UI_get_project_moviepath(inst
),
1694 if(mode
== MBRANCH_IMPORT_MOVIE
) {
1695 std::set
<std::string
> brlist
;
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
);
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
);
1708 } else if(brlist
.size() == 1) {
1709 branch
= *brlist
.begin();
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:",
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
) {
1727 case wxID_MBRANCH_EXPORT
:
1731 auto g
= choose_file_save(this, "Choose file to export", UI_get_project_moviepath(inst
),
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
) {
1744 case wxID_MBRANCH_RENAME
:
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",
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
) {
1763 case wxID_MBRANCH_DELETE
:
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",
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
) {
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
))
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
)
1805 uint64_t wxeditor_movie::_moviepanel::first_nextframe()
1807 uint64_t base
= first_editable(0);
1808 if(!subframe_to_frame
.count(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
)
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
)
1821 //Pressing mouse, just record line it was pressed on.
1822 rpress_line
= spos
+ y
- 3;
1828 //Releasing mouse, open popup menu.
1830 unsigned off
= divcnt
+ 1;
1836 press_line
= spos
+ y
- 3;
1838 current_popup
= &menu
;
1839 menu
.Connect(wxEVT_COMMAND_MENU_SELECTED
, wxCommandEventHandler(wxeditor_movie::_moviepanel::on_popup_menu
),
1842 //Find what controller is the click on.
1843 bool clicked_button
= false;
1844 control_info clicked
;
1845 std::string controller_name
;
1847 clicked_button
= false;
1848 press_index
= std::numeric_limits
<unsigned>::max();
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;
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).
1919 enable_cut_frame
= (!not_editable
&& press_line
>= econtroller_low
&& press_line
< linecount
1920 && rpress_line
>= econtroller_low
&& rpress_line
< linecount
&& !click_zero
);
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
;
1986 inst
.iqueue
->run([&list
, ¤t
, &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
);
1995 it
= branches_submenu
->AppendCheckItem(ass_id
, towxstring(inst
.mbranch
->name(i
)));
1996 branch_names
[ass_id
++] = i
;
1997 if(selected
) it
->Check(selected
);
2000 menu
.AppendSubMenu(branches_submenu
, wxT("Branches"));
2001 menu
.Connect(wxEVT_COMMAND_MENU_SELECTED
, wxCommandEventHandler(wxeditor_movie::_moviepanel::on_popup_menu
),
2003 branches_submenu
->Connect(wxEVT_COMMAND_MENU_SELECTED
,
2004 wxCommandEventHandler(wxeditor_movie::_moviepanel::on_popup_menu
), NULL
, this);
2006 //delete branches_submenu;
2010 int wxeditor_movie::_moviepanel::get_lines()
2012 portctrl::frame_vector
& fv
= *inst
.mlogic
->get_mfile().input
;
2016 void wxeditor_movie::_moviepanel::signal_repaint()
2019 if(requested
|| recursing
)
2021 auto s
= m
->get_scroll();
2023 uint32_t width
, height
;
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;
2030 inst
.iqueue
->run([&lines
, &width
, &height
, m2
, this]() {
2031 lines
= this->get_lines();
2032 if(lines
< lines_to_display
)
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();
2041 if(old_cached_cffs
!= cached_cffs
&& position_locked
&& !done_again
) {
2042 moviepos
= cached_cffs
;
2046 prev_width
= new_width
;
2047 prev_height
= new_height
;
2049 new_height
= height
;
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
)
2068 void wxeditor_movie::_moviepanel::on_mouse(wxMouseEvent
& e
)
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());
2077 on_mouse1(e
.GetX() / cell
.first
, e
.GetY() / cell
.second
, true);
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();
2088 if(e
.ShiftDown() && e
.ControlDown())
2090 s
->apply_wheel(e
.GetWheelRotation(), e
.GetWheelDelta(), speed
);
2093 void wxeditor_movie::_moviepanel::on_erase(wxEraseEvent
& e
)
2098 void wxeditor_movie::_moviepanel::on_paint(wxPaintEvent
& e
)
2101 auto size
= fb
.get_pixels();
2102 if(!size
.first
|| !size
.second
) {
2109 wxBitmap
bmp(wxImage(size
.first
, size
.second
, &pixels
[0], true));
2110 dc
.DrawBitmap(bmp
, 0, 0, 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
;
2120 std::swap(line
, line2
);
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();
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
;
2139 std::swap(line
, line2
);
2141 inst
.iqueue
->run([line
, line2
, &copied
]() {
2142 portctrl::frame_vector
& fv
= *CORE().mlogic
->get_mfile().input
;
2143 uint64_t vsize
= fv
.size();
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
;
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())
2176 uint64_t gaplen
= 0;
2177 int64_t newframes
= 0;
2179 std::istringstream
y(cliptext
);
2181 if(!std::getline(y
, z
))
2184 if(z
!= "lsnes-moviedata-whole")
2186 while(std::getline(y
, z
))
2189 portctrl::frame_vector
& fv
= *CORE().mlogic
->get_mfile().input
;
2190 uint64_t vsize
= fv
.size();
2191 if(gapstart
< real_first_editable(*_fcontrols
, 0))
2193 if(gapstart
> vsize
)
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
);
2206 uint64_t idx
= gapstart
;
2207 while(std::getline(y
, z
)) {
2208 fv
[idx
++].deserialize(z
.c_str());
2209 if(fv
[idx
- 1].sync())
2218 void wxeditor_movie::_moviepanel::do_paste(uint64_t row
, unsigned port
, unsigned controller
, bool append
)
2220 if(!port
&& !controller
)
2222 frame_controls
* _fcontrols
= &fcontrols
;
2223 auto iset
= controller_index_set(fcontrols
, port
, controller
);
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())
2232 uint64_t gaplen
= 0;
2233 int64_t newframes
= 0;
2235 std::istringstream
y(cliptext
);
2237 if(!std::getline(y
, z
))
2240 if(z
!= "lsnes-moviedata-controller")
2242 while(std::getline(y
, z
)) {
2247 portctrl::frame_vector
& fv
= *CORE().mlogic
->get_mfile().input
;
2248 uint64_t vsize
= fv
.size();
2249 if(gapstart
< real_first_editable(*_fcontrols
, iset
))
2251 if(gapstart
> vsize
)
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
);
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
);
2274 void wxeditor_movie::_moviepanel::do_insert_controller(uint64_t row
, unsigned port
, unsigned controller
)
2276 if(!port
&& !controller
)
2278 frame_controls
* _fcontrols
= &fcontrols
;
2279 auto iset
= controller_index_set(fcontrols
, port
, controller
);
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())
2286 portctrl::frame_vector
& fv
= *CORE().mlogic
->get_mfile().input
;
2287 uint64_t vsize
= fv
.size();
2288 if(gapstart
< real_first_editable(*_fcontrols
, iset
))
2290 if(gapstart
> vsize
)
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
);
2300 void wxeditor_movie::_moviepanel::do_delete_controller(uint64_t row1
, uint64_t row2
, unsigned port
,
2301 unsigned controller
)
2303 if(!port
&& !controller
)
2305 frame_controls
* _fcontrols
= &fcontrols
;
2306 auto iset
= controller_index_set(fcontrols
, port
, controller
);
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())
2315 portctrl::frame_vector
& fv
= *CORE().mlogic
->get_mfile().input
;
2316 uint64_t vsize
= fv
.size();
2317 if(gapstart
< real_first_editable(*_fcontrols
, iset
))
2319 if(gapstart
> vsize
)
2321 move_index_set(*_fcontrols
, fv
, gapstart
+ gaplen
, gapstart
, vsize
- gapstart
- gaplen
, iset
);
2322 zero_index_set(*_fcontrols
, fv
, vsize
- gaplen
, gaplen
, iset
);
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
)
2335 wxFlexGridSizer
* top_s
= new wxFlexGridSizer(2, 1, 0, 0);
2338 wxBoxSizer
* panel_s
= new wxBoxSizer(wxHORIZONTAL
);
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
));
2370 moviepanel
->signal_repaint();
2373 bool wxeditor_movie::ShouldPreventAppExit() const { return false; }
2375 void wxeditor_movie::on_close(wxCommandEvent
& e
)
2382 void wxeditor_movie::on_wclose(wxCloseEvent
& e
)
2385 bool wasc
= closing
;
2391 void wxeditor_movie::update()
2393 moviepanel
->signal_repaint();
2396 scroll_bar
* wxeditor_movie::get_scroll()
2401 void wxeditor_movie::on_focus_wrong(wxFocusEvent
& e
)
2404 moviepanel
->SetFocus();
2407 void wxeditor_movie_display(wxWindow
* parent
, emulator_instance
& inst
)
2410 auto e
= movieeditors
.lookup(inst
);
2415 movieeditors
.create(inst
, parent
)->Show();
2418 void wxeditor_movie::on_keyboard_down(wxKeyEvent
& e
)
2421 handle_wx_keyboard(inst
, e
, true);
2424 void wxeditor_movie::on_keyboard_up(wxKeyEvent
& e
)
2427 handle_wx_keyboard(inst
, e
, false);
2430 void wxeditor_movie_update(emulator_instance
& inst
)
2432 auto e
= movieeditors
.lookup(inst
);