Allow button display symbols to be Unicode characters
[lsnes.git] / src / platform / wxwidgets / editor-movie.cpp
blob6a8d7a54cbf7ce7091c01a135929a308772ace82
1 #include "core/movie.hpp"
2 #include "core/moviedata.hpp"
3 #include "core/dispatch.hpp"
4 #include "core/window.hpp"
6 #include "interface/controller.hpp"
7 #include "core/mainloop.hpp"
8 #include "platform/wxwidgets/platform.hpp"
9 #include "platform/wxwidgets/textrender.hpp"
10 #include "library/minmax.hpp"
11 #include "library/string.hpp"
12 #include "library/utf8.hpp"
14 #include <algorithm>
15 #include <cstring>
16 #include <wx/wx.h>
17 #include <wx/event.h>
18 #include <wx/control.h>
19 #include <wx/combobox.h>
21 enum
23 wxID_TOGGLE = wxID_HIGHEST + 1,
24 wxID_CHANGE,
25 wxID_SWEEP,
26 wxID_APPEND_FRAME,
27 wxID_CHANGE_LINECOUNT,
28 wxID_INSERT_AFTER,
29 wxID_DELETE_FRAME,
30 wxID_DELETE_SUBFRAME,
31 wxID_POSITION_LOCK,
32 wxID_RUN_TO_FRAME,
33 wxID_APPEND_FRAMES,
34 wxID_TRUNCATE,
35 wxID_SCROLL_FRAME,
36 wxID_SCROLL_CURRENT_FRAME
39 void update_movie_state();
41 namespace
43 unsigned lines_to_display = 28;
44 uint64_t divs[] = {1000000, 100000, 10000, 1000, 100, 10, 1};
45 uint64_t divsl[] = {1000000, 100000, 10000, 1000, 100, 10, 0};
46 const unsigned divcnt = sizeof(divs)/sizeof(divs[0]);
48 void connect_events(wxScrollBar* s, wxObjectEventFunction fun, wxEvtHandler* obj)
50 s->Connect(wxEVT_SCROLL_THUMBTRACK, fun, NULL, obj);
51 s->Connect(wxEVT_SCROLL_PAGEDOWN, fun, NULL, obj);
52 s->Connect(wxEVT_SCROLL_PAGEUP, fun, NULL, obj);
53 s->Connect(wxEVT_SCROLL_LINEDOWN, fun, NULL, obj);
54 s->Connect(wxEVT_SCROLL_LINEUP, fun, NULL, obj);
55 s->Connect(wxEVT_SCROLL_TOP, fun, NULL, obj);
56 s->Connect(wxEVT_SCROLL_BOTTOM, fun, NULL, obj);
60 struct control_info
62 unsigned position_left;
63 unsigned reserved; //Must be at least 6 for axes.
64 unsigned index; //Index in poll vector.
65 int type; //-2 => Port, -1 => Fixed, 0 => Button, 1 => axis.
66 char32_t ch;
67 std::u32string title;
68 unsigned port;
69 unsigned controller;
70 static control_info portinfo(unsigned& p, unsigned port, unsigned controller);
71 static control_info fixedinfo(unsigned& p, const std::u32string& str);
72 static control_info buttoninfo(unsigned& p, char32_t character, const std::u32string& title, unsigned idx);
73 static control_info axisinfo(unsigned& p, const std::u32string& title, unsigned idx);
76 control_info control_info::portinfo(unsigned& p, unsigned port, unsigned controller)
78 control_info i;
79 i.position_left = p;
80 i.reserved = (stringfmt() << port << "-" << controller).str32().length();
81 p += i.reserved;
82 i.index = 0;
83 i.type = -2;
84 i.ch = 0;
85 i.title = U"";
86 i.port = port;
87 i.controller = controller;
88 return i;
91 control_info control_info::fixedinfo(unsigned& p, const std::u32string& str)
93 control_info i;
94 i.position_left = p;
95 i.reserved = str.length();
96 p += i.reserved;
97 i.index = 0;
98 i.type = -1;
99 i.ch = 0;
100 i.title = str;
101 i.port = 0;
102 i.controller = 0;
103 return i;
106 control_info control_info::buttoninfo(unsigned& p, char32_t character, const std::u32string& title, unsigned idx)
108 control_info i;
109 i.position_left = p;
110 i.reserved = 1;
111 p += i.reserved;
112 i.index = idx;
113 i.type = 0;
114 i.ch = character;
115 i.title = title;
116 i.port = 0;
117 i.controller = 0;
118 return i;
121 control_info control_info::axisinfo(unsigned& p, const std::u32string& title, unsigned idx)
123 control_info i;
124 i.position_left = p;
125 i.reserved = title.length();
126 if(i.reserved < 6)
127 i.reserved = 6;
128 p += i.reserved;
129 i.index = idx;
130 i.type = 1;
131 i.ch = 0;
132 i.title = title;
133 i.port = 0;
134 i.controller = 0;
135 return i;
138 class frame_controls
140 public:
141 frame_controls();
142 void set_types(controller_frame& f);
143 short read_index(controller_frame& f, unsigned idx);
144 void write_index(controller_frame& f, unsigned idx, short value);
145 uint32_t read_pollcount(pollcounter_vector& v, unsigned idx);
146 const std::list<control_info>& get_controlinfo() { return controlinfo; }
147 std::u32string line1() { return _line1; }
148 std::u32string line2() { return _line2; }
149 size_t width() { return _width; }
150 private:
151 size_t _width;
152 std::u32string _line1;
153 std::u32string _line2;
154 void format_lines();
155 void add_port(unsigned& c, unsigned pid, const port_type& p, const port_type_set& pts);
156 std::list<control_info> controlinfo;
160 frame_controls::frame_controls()
162 _width = 0;
165 void frame_controls::set_types(controller_frame& f)
167 unsigned nextp = 0;
168 controlinfo.clear();
169 const port_type_set& pts = f.porttypes();
170 unsigned pcnt = pts.ports();
171 for(unsigned i = 0; i < pcnt; i++)
172 add_port(nextp, i, pts.port_type(i), pts);
173 format_lines();
176 void frame_controls::add_port(unsigned& c, unsigned pid, const port_type& p, const port_type_set& pts)
178 const port_controller_set& pci = *(p.controller_info);
179 for(unsigned i = 0; i < pci.controller_count; i++) {
180 if(!pci.controllers[i])
181 continue;
182 const port_controller& pc = *(pci.controllers[i]);
183 if(pid || i)
184 controlinfo.push_back(control_info::fixedinfo(c, U"\u2502"));
185 unsigned nextp = c;
186 controlinfo.push_back(control_info::portinfo(nextp, pid, i + 1));
187 bool last_multibyte = false;
188 for(unsigned j = 0; j < pc.button_count; j++) {
189 if(!pc.buttons[j])
190 continue;
191 const port_controller_button& pcb = *(pc.buttons[j]);
192 unsigned idx = pts.triple_to_index(pid, i, j);
193 if(idx == 0xFFFFFFFFUL)
194 continue;
195 if(pcb.type == port_controller_button::TYPE_BUTTON) {
196 if(last_multibyte)
197 c++;
198 controlinfo.push_back(control_info::buttoninfo(c, pcb.symbol, to_u32string(pcb.name),
199 idx));
200 last_multibyte = false;
201 } else if(pcb.type == port_controller_button::TYPE_AXIS ||
202 pcb.type == port_controller_button::TYPE_RAXIS ||
203 pcb.type == port_controller_button::TYPE_TAXIS) {
204 if(j)
205 c++;
206 controlinfo.push_back(control_info::axisinfo(c, to_u32string(pcb.name), idx));
207 last_multibyte = true;
210 if(nextp > c)
211 c = nextp;
215 short frame_controls::read_index(controller_frame& f, unsigned idx)
217 if(idx == 0)
218 return f.sync() ? 1 : 0;
219 return f.axis2(idx);
222 void frame_controls::write_index(controller_frame& f, unsigned idx, short value)
224 if(idx == 0)
225 return f.sync(value);
226 return f.axis2(idx, value);
229 uint32_t frame_controls::read_pollcount(pollcounter_vector& v, unsigned idx)
231 if(idx == 0)
232 return max(v.max_polls(), (uint32_t)1);
233 return v.get_polls(idx);
236 void frame_controls::format_lines()
238 _width = 0;
239 for(auto i : controlinfo) {
240 if(i.position_left + i.reserved > _width)
241 _width = i.position_left + i.reserved;
243 std::u32string cp1;
244 std::u32string cp2;
245 uint32_t off = divcnt + 1;
246 cp1.resize(_width + divcnt + 1);
247 cp2.resize(_width + divcnt + 1);
248 for(unsigned i = 0; i < cp1.size(); i++)
249 cp1[i] = cp2[i] = 32;
250 cp1[divcnt] = 0x2502;
251 cp2[divcnt] = 0x2502;
252 //Line1
253 //For every port-controller, find the least coordinate.
254 for(auto i : controlinfo) {
255 if(i.type == -1) {
256 auto _title = i.title;
257 std::copy(_title.begin(), _title.end(), &cp1[i.position_left + off]);
258 } else if(i.type == -2) {
259 auto _title = (stringfmt() << i.port << "-" << i.controller).str32();
260 std::copy(_title.begin(), _title.end(), &cp1[i.position_left + off]);
263 //Line2
264 for(auto i : controlinfo) {
265 auto _title = i.title;
266 if(i.type == -1 || i.type == 1)
267 std::copy(_title.begin(), _title.end(), &cp2[i.position_left + off]);
268 if(i.type == 0)
269 cp2[i.position_left + off] = i.ch;
271 _line1 = cp1;
272 _line2 = cp2;
276 class wxeditor_movie : public wxDialog
278 public:
279 wxeditor_movie(wxWindow* parent);
280 ~wxeditor_movie() throw();
281 bool ShouldPreventAppExit() const;
282 void on_close(wxCommandEvent& e);
283 void on_wclose(wxCloseEvent& e);
284 void on_focus_wrong(wxFocusEvent& e);
285 void on_keyboard_down(wxKeyEvent& e);
286 void on_keyboard_up(wxKeyEvent& e);
287 wxScrollBar* get_scroll();
288 void update();
289 private:
290 struct _moviepanel : public wxPanel, public information_dispatch
292 _moviepanel(wxeditor_movie* v);
293 ~_moviepanel() throw();
294 void signal_repaint();
295 void on_scroll(wxScrollEvent& e);
296 void on_paint(wxPaintEvent& e);
297 void on_erase(wxEraseEvent& e);
298 void on_mouse(wxMouseEvent& e);
299 void on_popup_menu(wxCommandEvent& e);
300 private:
301 int get_lines();
302 void render(text_framebuffer& fb, unsigned long long pos);
303 void on_mouse0(unsigned x, unsigned y, bool polarity);
304 void on_mouse1(unsigned x, unsigned y, bool polarity);
305 void on_mouse2(unsigned x, unsigned y, bool polarity);
306 void do_toggle_buttons(unsigned idx, uint64_t row1, uint64_t row2);
307 void do_alter_axis(unsigned idx, uint64_t row1, uint64_t row2);
308 void do_sweep_axis(unsigned idx, uint64_t row1, uint64_t row2);
309 void do_append_frames(uint64_t count);
310 void do_append_frames();
311 void do_insert_frame_after(uint64_t row);
312 void do_delete_frame(uint64_t row, bool wholeframe);
313 void do_truncate(uint64_t row);
314 void do_set_stop_at_frame();
315 void do_scroll_to_frame();
316 void do_scroll_to_current_frame();
317 uint64_t first_editable(unsigned index);
318 uint64_t first_nextframe();
319 int width(controller_frame& f);
320 std::u32string render_line1(controller_frame& f);
321 std::u32string render_line2(controller_frame& f);
322 void render_linen(text_framebuffer& fb, controller_frame& f, uint64_t sfn, int y);
323 unsigned long long spos;
324 void* prev_obj;
325 uint64_t prev_seqno;
326 void update_cache();
327 std::map<uint64_t, uint64_t> subframe_to_frame;
328 uint64_t max_subframe;
329 frame_controls fcontrols;
330 wxeditor_movie* m;
331 bool requested;
332 text_framebuffer fb;
333 uint64_t movielines;
334 uint64_t moviepos;
335 unsigned new_width;
336 unsigned new_height;
337 std::vector<uint8_t> pixels;
338 int scroll_delta;
339 unsigned press_x;
340 uint64_t press_line;
341 uint64_t rpress_line;
342 unsigned press_index;
343 bool pressed;
344 bool recursing;
345 uint64_t linecount;
346 uint64_t cached_cffs;
347 bool position_locked;
348 wxMenu* current_popup;
350 _moviepanel* moviepanel;
351 wxButton* closebutton;
352 wxScrollBar* moviescroll;
353 bool closing;
356 namespace
358 wxeditor_movie* movieeditor_open;
360 //Find the first real editable subframe.
361 //Call only in emulator thread.
362 uint64_t real_first_editable(frame_controls& fc, unsigned idx)
364 uint64_t cffs = movb.get_movie().get_current_frame_first_subframe();
365 controller_frame_vector& fv = movb.get_movie().get_frame_vector();
366 pollcounter_vector& pv = movb.get_movie().get_pollcounters();
367 uint64_t vsize = fv.size();
368 uint32_t pc = fc.read_pollcount(pv, idx);
369 for(uint32_t i = 1; i < pc; i++)
370 if(cffs + i >= vsize || fv[cffs + i].sync())
371 return cffs + i;
372 return cffs + pc;
375 //Find the first real editable whole frame.
376 //Call only in emulator thread.
377 uint64_t real_first_nextframe(frame_controls& fc)
379 uint64_t base = real_first_editable(fc, 0);
380 controller_frame_vector& fv = movb.get_movie().get_frame_vector();
381 uint64_t vsize = fv.size();
382 for(uint32_t i = 0;; i++)
383 if(base + i >= vsize || fv[base + i].sync())
384 return base + i;
387 //Adjust movie length by specified number of frames.
388 //Call only in emulator thread.
389 void movie_framecount_change(int64_t adjust, bool known = true);
390 void movie_framecount_change(int64_t adjust, bool known)
392 if(known)
393 movb.get_movie().adjust_frame_count(adjust);
394 else
395 movb.get_movie().recount_frames();
396 update_movie_state();
397 graphics_driver_notify_status();
401 wxeditor_movie::_moviepanel::~_moviepanel() throw() {}
402 wxeditor_movie::~wxeditor_movie() throw() {}
404 wxeditor_movie::_moviepanel::_moviepanel(wxeditor_movie* v)
405 : wxPanel(v, wxID_ANY, wxDefaultPosition, wxSize(100, 100), wxWANTS_CHARS),
406 information_dispatch("movieeditor-listener")
408 m = v;
409 Connect(wxEVT_PAINT, wxPaintEventHandler(_moviepanel::on_paint), NULL, this);
410 Connect(wxEVT_ERASE_BACKGROUND, wxEraseEventHandler(_moviepanel::on_erase), NULL, this);
411 new_width = 0;
412 new_height = 0;
413 moviepos = 0;
414 scroll_delta = 0;
415 spos = 0;
416 prev_obj = NULL;
417 prev_seqno = 0;
418 max_subframe = 0;
419 recursing = false;
420 position_locked = true;
421 current_popup = NULL;
423 Connect(wxEVT_LEFT_DOWN, wxMouseEventHandler(_moviepanel::on_mouse), NULL, this);
424 Connect(wxEVT_LEFT_UP, wxMouseEventHandler(_moviepanel::on_mouse), NULL, this);
425 Connect(wxEVT_MIDDLE_DOWN, wxMouseEventHandler(_moviepanel::on_mouse), NULL, this);
426 Connect(wxEVT_MIDDLE_UP, wxMouseEventHandler(_moviepanel::on_mouse), NULL, this);
427 Connect(wxEVT_RIGHT_DOWN, wxMouseEventHandler(_moviepanel::on_mouse), NULL, this);
428 Connect(wxEVT_RIGHT_UP, wxMouseEventHandler(_moviepanel::on_mouse), NULL, this);
429 Connect(wxEVT_MOUSEWHEEL, wxMouseEventHandler(_moviepanel::on_mouse), NULL, this);
431 signal_repaint();
432 requested = false;
435 void wxeditor_movie::_moviepanel::update_cache()
437 movie& m = movb.get_movie();
438 controller_frame_vector& fv = m.get_frame_vector();
439 if(&m == prev_obj && prev_seqno == m.get_seqno()) {
440 //Just process new subframes if any.
441 for(uint64_t i = max_subframe; i < fv.size(); i++) {
442 uint64_t prev = (i > 0) ? subframe_to_frame[i - 1] : 0;
443 controller_frame f = fv[i];
444 if(f.sync())
445 subframe_to_frame[i] = prev + 1;
446 else
447 subframe_to_frame[i] = prev;
449 max_subframe = fv.size();
450 return;
452 //Reprocess all subframes.
453 for(uint64_t i = 0; i < fv.size(); i++) {
454 uint64_t prev = (i > 0) ? subframe_to_frame[i - 1] : 0;
455 controller_frame f = fv[i];
456 if(f.sync())
457 subframe_to_frame[i] = prev + 1;
458 else
459 subframe_to_frame[i] = prev;
461 max_subframe = fv.size();
462 controller_frame model = fv.blank_frame(false);
463 fcontrols.set_types(model);
464 prev_obj = &m;
465 prev_seqno = m.get_seqno();
468 int wxeditor_movie::_moviepanel::width(controller_frame& f)
470 update_cache();
471 return divcnt + 1 + fcontrols.width();
474 std::u32string wxeditor_movie::_moviepanel::render_line1(controller_frame& f)
476 update_cache();
477 return fcontrols.line1();
480 std::u32string wxeditor_movie::_moviepanel::render_line2(controller_frame& f)
482 update_cache();
483 return fcontrols.line2();
486 void wxeditor_movie::_moviepanel::render_linen(text_framebuffer& fb, controller_frame& f, uint64_t sfn, int y)
488 update_cache();
489 size_t fbstride = fb.get_stride();
490 text_framebuffer::element* _fb = fb.get_buffer();
491 text_framebuffer::element e;
492 e.bg = 0xFFFFFF;
493 e.fg = 0x000000;
494 for(unsigned i = 0; i < divcnt; i++) {
495 uint64_t fn = subframe_to_frame[sfn];
496 e.ch = (fn >= divsl[i]) ? (((fn / divs[i]) % 10) + 48) : 32;
497 _fb[y * fbstride + i] = e;
499 e.ch = 0x2502;
500 _fb[y * fbstride + divcnt] = e;
501 const std::list<control_info>& ctrlinfo = fcontrols.get_controlinfo();
502 uint64_t curframe = movb.get_movie().get_current_frame();
503 pollcounter_vector& pv = movb.get_movie().get_pollcounters();
504 uint64_t cffs = movb.get_movie().get_current_frame_first_subframe();
505 cached_cffs = cffs;
506 int past = -1;
507 if(!movb.get_movie().readonly_mode())
508 past = 1;
509 else if(subframe_to_frame[sfn] < curframe)
510 past = 1;
511 else if(subframe_to_frame[sfn] > curframe)
512 past = 0;
513 bool now = (subframe_to_frame[sfn] == curframe);
514 unsigned xcord = 32768;
515 if(pressed)
516 xcord = press_x;
518 for(auto i : ctrlinfo) {
519 int rpast = past;
520 unsigned off = divcnt + 1;
521 bool cselected = (xcord >= i.position_left + off && xcord < i.position_left + i.reserved + off);
522 if(rpast == -1) {
523 unsigned polls = fcontrols.read_pollcount(pv, i.index);
524 rpast = ((cffs + polls) > sfn) ? 1 : 0;
526 uint32_t bgc = 0xC0C0C0;
527 if(rpast)
528 bgc |= 0x0000FF;
529 if(now)
530 bgc |= 0xFF0000;
531 if(cselected)
532 bgc |= 0x00FF00;
533 if(bgc == 0xC0C0C0)
534 bgc = 0xFFFFFF;
535 if(i.type == -1) {
536 //Separator.
537 fb.write(i.title, 0, divcnt + 1 + i.position_left, y, 0x000000, 0xFFFFFF);
538 } else if(i.type == 0) {
539 //Button.
540 char c[2];
541 bool v = (fcontrols.read_index(f, i.index) != 0);
542 c[0] = i.ch;
543 c[1] = 0;
544 fb.write(c, 0, divcnt + 1 + i.position_left, y, v ? 0x000000 : 0xC8C8C8, bgc);
545 } else if(i.type == 1) {
546 //Axis.
547 char c[7];
548 sprintf(c, "%6d", fcontrols.read_index(f, i.index));
549 fb.write(c, 0, divcnt + 1 + i.position_left, y, 0x000000, bgc);
554 void wxeditor_movie::_moviepanel::render(text_framebuffer& fb, unsigned long long pos)
556 spos = pos;
557 controller_frame_vector& fv = movb.get_movie().get_frame_vector();
558 controller_frame cf = fv.blank_frame(false);
559 int _width = width(cf);
560 fb.set_size(_width, lines_to_display + 3);
561 size_t fbstride = fb.get_stride();
562 auto fbsize = fb.get_characters();
563 text_framebuffer::element* _fb = fb.get_buffer();
564 fb.write((stringfmt() << "Current frame: " << movb.get_movie().get_current_frame() << " of "
565 << movb.get_movie().get_frame_count()).str(), _width, 0, 0,
566 0x000000, 0xFFFFFF);
567 fb.write(render_line1(cf), _width, 0, 1, 0x000000, 0xFFFFFF);
568 fb.write(render_line2(cf), _width, 0, 2, 0x000000, 0xFFFFFF);
569 unsigned long long lines = fv.size();
570 unsigned long long i;
571 unsigned j;
572 for(i = pos, j = 3; i < pos + lines_to_display; i++, j++) {
573 text_framebuffer::element e;
574 if(i >= lines) {
575 //Out of range.
576 e.bg = 0xFFFFFF;
577 e.fg = 0x000000;
578 e.ch = 32;
579 for(unsigned k = 0; k < fbsize.first; k++)
580 _fb[j * fbstride + k] = e;
581 } else {
582 controller_frame frame = fv[i];
583 render_linen(fb, frame, i, j);
588 void wxeditor_movie::_moviepanel::do_toggle_buttons(unsigned idx, uint64_t row1, uint64_t row2)
590 frame_controls* _fcontrols = &fcontrols;
591 uint64_t _press_line = row1;
592 uint64_t line = row2;
593 if(_press_line > line)
594 std::swap(_press_line, line);
595 recursing = true;
596 runemufn([idx, _press_line, line, _fcontrols]() {
597 int64_t adjust = 0;
598 if(!movb.get_movie().readonly_mode())
599 return;
600 uint64_t fedit = real_first_editable(*_fcontrols, idx);
601 controller_frame_vector& fv = movb.get_movie().get_frame_vector();
602 for(uint64_t i = _press_line; i <= line; i++) {
603 if(i < fedit || i >= fv.size())
604 continue;
605 controller_frame cf = fv[i];
606 bool v = _fcontrols->read_index(cf, idx);
607 _fcontrols->write_index(cf, idx, !v);
608 adjust += (v ? -1 : 1);
610 if(idx == 0)
611 movie_framecount_change(adjust);
613 recursing = false;
614 if(idx == 0)
615 max_subframe = _press_line; //Reparse.
618 void wxeditor_movie::_moviepanel::do_alter_axis(unsigned idx, uint64_t row1, uint64_t row2)
620 frame_controls* _fcontrols = &fcontrols;
621 uint64_t line = row1;
622 uint64_t line2 = row2;
623 short value;
624 bool valid = true;
625 runemufn([idx, line, &value, _fcontrols, &valid]() {
626 if(!movb.get_movie().readonly_mode()) {
627 valid = false;
628 return;
630 uint64_t fedit = real_first_editable(*_fcontrols, idx);
631 controller_frame_vector& fv = movb.get_movie().get_frame_vector();
632 if(line < fedit || line >= fv.size()) {
633 valid = false;
634 return;
636 controller_frame cf = fv[line];
637 value = _fcontrols->read_index(cf, idx);
639 if(!valid)
640 return;
641 try {
642 std::string text = pick_text(m, "Set value", "Enter new value:", (stringfmt() << value).str());
643 value = parse_value<short>(text);
644 } catch(canceled_exception& e) {
645 return;
646 } catch(std::exception& e) {
647 wxMessageBox(wxT("Invalid value"), _T("Error"), wxICON_EXCLAMATION | wxOK, m);
648 return;
650 if(line > line2)
651 std::swap(line, line2);
652 runemufn([idx, line, line2, value, _fcontrols]() {
653 uint64_t fedit = real_first_editable(*_fcontrols, idx);
654 controller_frame_vector& fv = movb.get_movie().get_frame_vector();
655 for(uint64_t i = line; i <= line2; i++) {
656 if(i < fedit || i >= fv.size())
657 continue;
658 controller_frame cf = fv[i];
659 _fcontrols->write_index(cf, idx, value);
664 void wxeditor_movie::_moviepanel::do_sweep_axis(unsigned idx, uint64_t row1, uint64_t row2)
666 frame_controls* _fcontrols = &fcontrols;
667 uint64_t line = row1;
668 uint64_t line2 = row2;
669 short value;
670 short value2;
671 bool valid = true;
672 if(line > line2)
673 std::swap(line, line2);
674 runemufn([idx, line, line2, &value, &value2, _fcontrols, &valid]() {
675 if(!movb.get_movie().readonly_mode()) {
676 valid = false;
677 return;
679 uint64_t fedit = real_first_editable(*_fcontrols, idx);
680 controller_frame_vector& fv = movb.get_movie().get_frame_vector();
681 if(line2 < fedit || line2 >= fv.size()) {
682 valid = false;
683 return;
685 controller_frame cf = fv[line];
686 value = _fcontrols->read_index(cf, idx);
687 controller_frame cf2 = fv[line2];
688 value2 = _fcontrols->read_index(cf2, idx);
690 if(!valid)
691 return;
692 runemufn([idx, line, line2, value, value2, _fcontrols]() {
693 uint64_t fedit = real_first_editable(*_fcontrols, idx);
694 controller_frame_vector& fv = movb.get_movie().get_frame_vector();
695 for(uint64_t i = line + 1; i <= line2 - 1; i++) {
696 if(i < fedit || i >= fv.size())
697 continue;
698 controller_frame cf = fv[i];
699 auto tmp2 = static_cast<int64_t>(i - line) * (value2 - value) /
700 static_cast<int64_t>(line2 - line);
701 short tmp = value + tmp2;
702 _fcontrols->write_index(cf, idx, tmp);
707 void wxeditor_movie::_moviepanel::do_append_frames(uint64_t count)
709 recursing = true;
710 uint64_t _count = count;
711 runemufn([_count]() {
712 if(!movb.get_movie().readonly_mode())
713 return;
714 controller_frame_vector& fv = movb.get_movie().get_frame_vector();
715 for(uint64_t i = 0; i < _count; i++)
716 fv.append(fv.blank_frame(true));
717 movie_framecount_change(_count);
719 recursing = false;
722 void wxeditor_movie::_moviepanel::do_append_frames()
724 uint64_t value;
725 try {
726 std::string text = pick_text(m, "Append frames", "Enter number of frames to append:", "");
727 value = parse_value<uint64_t>(text);
728 } catch(canceled_exception& e) {
729 return;
730 } catch(std::exception& e) {
731 wxMessageBox(wxT("Invalid value"), _T("Error"), wxICON_EXCLAMATION | wxOK, m);
732 return;
734 do_append_frames(value);
737 void wxeditor_movie::_moviepanel::do_insert_frame_after(uint64_t row)
739 recursing = true;
740 frame_controls* _fcontrols = &fcontrols;
741 uint64_t _row = row;
742 runemufn([_row, _fcontrols]() {
743 if(!movb.get_movie().readonly_mode())
744 return;
745 controller_frame_vector& fv = movb.get_movie().get_frame_vector();
746 uint64_t fedit = real_first_editable(*_fcontrols, 0);
747 //Find the start of the next frame.
748 uint64_t nframe = _row + 1;
749 uint64_t vsize = fv.size();
750 while(nframe < vsize && !fv[nframe].sync())
751 nframe++;
752 if(nframe < fedit)
753 return;
754 fv.append(fv.blank_frame(true));
755 if(nframe < vsize) {
756 //Okay, gotta copy all data after this point. nframe has to be at least 1.
757 for(uint64_t i = vsize - 1; i >= nframe; i--)
758 fv[i + 1] = fv[i];
759 fv[nframe] = fv.blank_frame(true);
761 movie_framecount_change(1);
763 max_subframe = row;
764 recursing = false;
767 void wxeditor_movie::_moviepanel::do_delete_frame(uint64_t row, bool wholeframe)
769 recursing = true;
770 uint64_t _row = row;
771 bool _wholeframe = wholeframe;
772 frame_controls* _fcontrols = &fcontrols;
773 runemufn([_row, _wholeframe, _fcontrols]() {
774 controller_frame_vector& fv = movb.get_movie().get_frame_vector();
775 uint64_t vsize = fv.size();
776 if(_row >= vsize)
777 return;
778 if(_wholeframe) {
779 if(_row < real_first_nextframe(*_fcontrols))
780 return;
781 //Scan backwards for the first subframe of this frame and forwards for the last.
782 uint64_t fsf = _row;
783 uint64_t lsf = _row;
784 if(fv[_row].sync())
785 lsf++; //Bump by one so it finds the end.
786 while(fsf < vsize && !fv[fsf].sync())
787 fsf--;
788 while(lsf < vsize && !fv[lsf].sync())
789 lsf++;
790 uint64_t tonuke = lsf - fsf;
791 //Nuke from fsf to lsf.
792 for(uint64_t i = fsf; i < vsize - tonuke; i++)
793 fv[i] = fv[i + tonuke];
794 fv.resize(vsize - tonuke);
795 movie_framecount_change(-1);
796 } else {
797 if(_row < real_first_editable(*_fcontrols, 0))
798 return;
799 //Is the nuked frame a first subframe?
800 bool is_first = fv[_row].sync();
801 //Nuke the subframe.
802 for(uint64_t i = _row; i < vsize - 1; i++)
803 fv[i] = fv[i + 1];
804 fv.resize(vsize - 1);
805 //Next subframe inherits the sync flag.
806 if(is_first) {
807 if(_row < vsize - 1 && !fv[_row].sync())
808 fv[_row].sync(true);
809 else
810 movie_framecount_change(-1);
815 max_subframe = row;
816 recursing = false;
819 void wxeditor_movie::_moviepanel::do_truncate(uint64_t row)
821 recursing = true;
822 uint64_t _row = row;
823 frame_controls* _fcontrols = &fcontrols;
824 runemufn([_row, _fcontrols]() {
825 controller_frame_vector& fv = movb.get_movie().get_frame_vector();
826 uint64_t vsize = fv.size();
827 if(_row >= vsize)
828 return;
829 if(_row < real_first_editable(*_fcontrols, 0))
830 return;
831 int64_t delete_count = 0;
832 for(uint64_t i = _row; i < vsize; i++)
833 if(fv[i].sync())
834 delete_count--;
835 fv.resize(_row);
836 movie_framecount_change(delete_count);
838 max_subframe = row;
839 recursing = false;
842 void wxeditor_movie::_moviepanel::do_set_stop_at_frame()
844 uint64_t curframe;
845 uint64_t frame;
846 runemufn([&curframe]() {
847 curframe = movb.get_movie().get_current_frame();
849 try {
850 std::string text = pick_text(m, "Frame", (stringfmt() << "Enter frame to stop at (currently at "
851 << curframe << "):").str(), "");
852 frame = parse_value<uint64_t>(text);
853 } catch(canceled_exception& e) {
854 return;
855 } catch(std::exception& e) {
856 wxMessageBox(wxT("Invalid value"), _T("Error"), wxICON_EXCLAMATION | wxOK, m);
857 return;
859 if(frame < curframe) {
860 wxMessageBox(wxT("The movie is already past that point"), _T("Error"), wxICON_EXCLAMATION | wxOK, m);
861 return;
863 runemufn([frame]() {
864 set_stop_at_frame(frame);
868 void wxeditor_movie::_moviepanel::on_mouse0(unsigned x, unsigned y, bool polarity)
870 if(y < 3)
871 return;
872 if(polarity) {
873 press_x = x;
874 press_line = spos + y - 3;
876 pressed = polarity;
877 if(polarity)
878 return;
879 uint64_t line = spos + y - 3;
880 if(press_x < divcnt && x < divcnt) {
881 //Press on frame count.
882 uint64_t row1 = press_line;
883 uint64_t row2 = line;
884 if(row1 > row2)
885 std::swap(row1, row2);
886 do_append_frames(row2 - row1 + 1);
888 for(auto i : fcontrols.get_controlinfo()) {
889 unsigned off = divcnt + 1;
890 unsigned idx = i.index;
891 if((press_x >= i.position_left + off && press_x < i.position_left + i.reserved + off) &&
892 (x >= i.position_left + off && x < i.position_left + i.reserved + off)) {
893 if(i.type == 0)
894 do_toggle_buttons(idx, press_line, line);
895 else if(i.type == 1)
896 do_alter_axis(idx, press_line, line);
901 void wxeditor_movie::_moviepanel::do_scroll_to_frame()
903 uint64_t frame;
904 try {
905 std::string text = pick_text(m, "Frame", (stringfmt() << "Enter frame to scroll to:").str(), "");
906 frame = parse_value<uint64_t>(text);
907 } catch(canceled_exception& e) {
908 return;
909 } catch(std::exception& e) {
910 wxMessageBox(wxT("Invalid value"), _T("Error"), wxICON_EXCLAMATION | wxOK, m);
911 return;
913 uint64_t wouldbe;
914 uint64_t low = 0;
915 uint64_t high = max_subframe;
916 while(low < high) {
917 wouldbe = (low + high) / 2;
918 if(subframe_to_frame[wouldbe] < frame)
919 low = wouldbe;
920 else if(subframe_to_frame[wouldbe] > frame)
921 high = wouldbe;
922 else
923 break;
925 while(wouldbe > 1 && subframe_to_frame[wouldbe - 1] == frame)
926 wouldbe--;
927 moviepos = wouldbe;
928 signal_repaint();
931 void wxeditor_movie::_moviepanel::do_scroll_to_current_frame()
933 moviepos = cached_cffs;
934 signal_repaint();
937 void wxeditor_movie::_moviepanel::on_popup_menu(wxCommandEvent& e)
939 wxMenuItem* tmpitem;
940 int id = e.GetId();
941 switch(id) {
942 case wxID_TOGGLE:
943 do_toggle_buttons(press_index, press_line, press_line);
944 return;
945 case wxID_CHANGE:
946 do_alter_axis(press_index, press_line, press_line);
947 return;
948 case wxID_SWEEP:
949 do_sweep_axis(press_index, rpress_line, press_line);
950 return;
951 case wxID_APPEND_FRAME:
952 do_append_frames(1);
953 return;
954 case wxID_APPEND_FRAMES:
955 do_append_frames();
956 return;
957 case wxID_INSERT_AFTER:
958 do_insert_frame_after(press_line);
959 return;
960 case wxID_DELETE_FRAME:
961 do_delete_frame(press_line, true);
962 return;
963 case wxID_DELETE_SUBFRAME:
964 do_delete_frame(press_line, false);
965 return;
966 case wxID_TRUNCATE:
967 do_truncate(press_line);
968 return;
969 case wxID_RUN_TO_FRAME:
970 do_set_stop_at_frame();
971 return;
972 case wxID_SCROLL_FRAME:
973 do_scroll_to_frame();
974 return;
975 case wxID_SCROLL_CURRENT_FRAME:
976 do_scroll_to_current_frame();
977 return;
978 case wxID_POSITION_LOCK:
979 if(!current_popup)
980 return;
981 tmpitem = current_popup->FindItem(wxID_POSITION_LOCK);
982 position_locked = tmpitem->IsChecked();
983 return;
984 case wxID_CHANGE_LINECOUNT:
985 try {
986 std::string text = pick_text(m, "Set number of lines", "Set number of lines visible:",
987 (stringfmt() << lines_to_display).str());
988 unsigned tmp = parse_value<unsigned>(text);
989 if(tmp < 1 || tmp > 255)
990 throw std::runtime_error("Value out of range");
991 lines_to_display = tmp;
992 } catch(canceled_exception& e) {
993 return;
994 } catch(std::exception& e) {
995 wxMessageBox(wxT("Invalid value"), _T("Error"), wxICON_EXCLAMATION | wxOK, m);
996 return;
998 signal_repaint();
999 return;
1003 uint64_t wxeditor_movie::_moviepanel::first_editable(unsigned index)
1005 uint64_t cffs = cached_cffs;
1006 if(!subframe_to_frame.count(cffs))
1007 return cffs;
1008 uint64_t f = subframe_to_frame[cffs];
1009 pollcounter_vector& pv = movb.get_movie().get_pollcounters();
1010 uint32_t pc = fcontrols.read_pollcount(pv, index);
1011 for(uint32_t i = 1; i < pc; i++)
1012 if(!subframe_to_frame.count(cffs + i) || subframe_to_frame[cffs + i] > f)
1013 return cffs + i;
1014 return cffs + pc;
1017 uint64_t wxeditor_movie::_moviepanel::first_nextframe()
1019 uint64_t base = first_editable(0);
1020 if(!subframe_to_frame.count(cached_cffs))
1021 return cached_cffs;
1022 uint64_t f = subframe_to_frame[cached_cffs];
1023 for(uint32_t i = 0;; i++)
1024 if(!subframe_to_frame.count(base + i) || subframe_to_frame[base + i] > f)
1025 return base + i;
1028 void wxeditor_movie::_moviepanel::on_mouse1(unsigned x, unsigned y, bool polarity) {}
1029 void wxeditor_movie::_moviepanel::on_mouse2(unsigned x, unsigned y, bool polarity)
1031 if(polarity) {
1032 rpress_line = spos + y - 3;
1033 return;
1035 wxMenu menu;
1036 current_popup = &menu;
1037 bool enable_toggle_button = false;
1038 bool enable_change_axis = false;
1039 bool enable_insert_frame = false;
1040 bool enable_delete_frame = false;
1041 bool enable_delete_subframe = false;
1042 std::u32string title;
1043 if(y < 3)
1044 goto outrange;
1045 if(!movb.get_movie().readonly_mode())
1046 goto outrange;
1047 press_x = x;
1048 press_line = spos + y - 3;
1049 for(auto i : fcontrols.get_controlinfo()) {
1050 unsigned off = divcnt + 1;
1051 if(press_x >= i.position_left + off && press_x < i.position_left + i.reserved + off) {
1052 if(i.type == 0 && press_line >= first_editable(i.index) &&
1053 press_line < linecount) {
1054 enable_toggle_button = true;
1055 press_index = i.index;
1056 title = i.title;
1058 if(i.type == 1 && press_line >= first_editable(i.index) &&
1059 press_line < linecount) {
1060 enable_change_axis = true;
1061 press_index = i.index;
1062 title = i.title;
1066 if(press_line + 1 >= first_editable(0) && press_line < linecount)
1067 enable_insert_frame = true;
1068 if(press_line >= first_editable(0) && press_line < linecount)
1069 enable_delete_subframe = true;
1070 if(press_line >= first_nextframe() && press_line < linecount)
1071 enable_delete_frame = true;
1072 if(enable_toggle_button)
1073 menu.Append(wxID_TOGGLE, towxstring(U"Toggle " + title));
1074 if(enable_change_axis)
1075 menu.Append(wxID_CHANGE, towxstring(U"Change " + title));
1076 if(enable_change_axis && rpress_line != press_line)
1077 menu.Append(wxID_SWEEP, towxstring(U"Sweep " + title));
1078 if(enable_toggle_button || enable_change_axis)
1079 menu.AppendSeparator();
1080 menu.Append(wxID_INSERT_AFTER, wxT("Insert frame after"))->Enable(enable_insert_frame);
1081 menu.Append(wxID_APPEND_FRAME, wxT("Append frame"));
1082 menu.Append(wxID_APPEND_FRAMES, wxT("Append frames..."));
1083 menu.AppendSeparator();
1084 menu.Append(wxID_DELETE_FRAME, wxT("Delete frame"))->Enable(enable_delete_frame);
1085 menu.Append(wxID_DELETE_SUBFRAME, wxT("Delete subframe"))->Enable(enable_delete_subframe);
1086 menu.AppendSeparator();
1087 menu.Append(wxID_TRUNCATE, wxT("Truncate movie"))->Enable(enable_delete_subframe);
1088 menu.AppendSeparator();
1089 outrange:
1090 menu.Append(wxID_SCROLL_FRAME, wxT("Scroll to frame..."));
1091 menu.Append(wxID_SCROLL_CURRENT_FRAME, wxT("Scroll to current frame"));
1092 menu.Append(wxID_RUN_TO_FRAME, wxT("Run to frame..."));
1093 menu.Append(wxID_CHANGE_LINECOUNT, wxT("Change number of lines visible"));
1094 menu.AppendCheckItem(wxID_POSITION_LOCK, wxT("Lock scroll to playback"))->Check(position_locked);
1095 menu.Connect(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(wxeditor_movie::_moviepanel::on_popup_menu),
1096 NULL, this);
1097 PopupMenu(&menu);
1100 int wxeditor_movie::_moviepanel::get_lines()
1102 controller_frame_vector& fv = movb.get_movie().get_frame_vector();
1103 return fv.size();
1106 void wxeditor_movie::_moviepanel::signal_repaint()
1108 if(requested || recursing)
1109 return;
1110 auto s = m->get_scroll();
1111 requested = true;
1112 uint32_t width, height;
1113 uint64_t lines;
1114 wxeditor_movie* m2 = m;
1115 uint64_t old_cached_cffs = cached_cffs;
1116 uint32_t prev_width, prev_height;
1117 bool done_again = false;
1118 do_again:
1119 runemufn([&lines, &width, &height, m2, this]() {
1120 lines = this->get_lines();
1121 if(lines < lines_to_display)
1122 this->moviepos = 0;
1123 else if(this->moviepos > lines - lines_to_display)
1124 this->moviepos = lines - lines_to_display;
1125 this->render(fb, moviepos);
1126 auto x = fb.get_characters();
1127 width = x.first;
1128 height = x.second;
1130 if(old_cached_cffs != cached_cffs && position_locked && !done_again) {
1131 moviepos = cached_cffs;
1132 done_again = true;
1133 goto do_again;
1135 prev_width = new_width;
1136 prev_height = new_height;
1137 new_width = width;
1138 new_height = height;
1139 movielines = lines;
1140 if(s)
1141 s->SetScrollbar(moviepos, lines_to_display, lines, lines_to_display - 1);
1142 auto size = fb.get_pixels();
1143 pixels.resize(size.first * size.second * 3);
1144 fb.render((char*)&pixels[0]);
1145 if(prev_width != new_width || prev_height != new_height) {
1146 auto cell = fb.get_cell();
1147 SetMinSize(wxSize(new_width * cell.first, (lines_to_display + 3) * cell.second));
1148 if(new_width > 0 && s)
1149 m->Fit();
1151 linecount = lines;
1152 Refresh();
1155 void wxeditor_movie::_moviepanel::on_mouse(wxMouseEvent& e)
1157 auto cell = fb.get_cell();
1158 if(e.LeftDown() && !e.ControlDown())
1159 on_mouse0(e.GetX() / cell.first, e.GetY() / cell.second, true);
1160 if(e.LeftUp() && !e.ControlDown())
1161 on_mouse0(e.GetX() / cell.first, e.GetY() / cell.second, false);
1162 if(e.MiddleDown())
1163 on_mouse1(e.GetX() / cell.first, e.GetY() / cell.second, true);
1164 if(e.MiddleUp())
1165 on_mouse1(e.GetX() / cell.first, e.GetY() / cell.second, false);
1166 if(e.RightDown() || (e.LeftDown() && e.ControlDown()))
1167 on_mouse2(e.GetX() / cell.first, e.GetY() / cell.second, true);
1168 if(e.RightUp() || (e.LeftUp() && e.ControlDown()))
1169 on_mouse2(e.GetX() / cell.first, e.GetY() / cell.second, false);
1170 int wrotate = e.GetWheelRotation();
1171 int threshold = e.GetWheelDelta();
1172 bool scrolled = false;
1173 auto s = m->get_scroll();
1174 if(threshold)
1175 scroll_delta += wrotate;
1176 while(wrotate && threshold && scroll_delta <= -threshold) {
1177 //Scroll down by line.
1178 moviepos++;
1179 if(movielines <= lines_to_display)
1180 moviepos = 0;
1181 else if(moviepos > movielines - lines_to_display + 1)
1182 moviepos = movielines - lines_to_display + 1;
1183 scrolled = true;
1184 scroll_delta += threshold;
1186 while(wrotate && threshold && scroll_delta >= threshold) {
1187 //Scroll up by line.
1188 if(moviepos > 0)
1189 moviepos--;
1190 scrolled = true;
1191 scroll_delta -= threshold;
1193 if(scrolled)
1194 s->SetThumbPosition(moviepos);
1195 signal_repaint();
1198 void wxeditor_movie::_moviepanel::on_scroll(wxScrollEvent& e)
1200 auto s = m->get_scroll();
1201 if(s)
1202 moviepos = s->GetThumbPosition();
1203 else
1204 moviepos = 0;
1205 signal_repaint();
1208 void wxeditor_movie::_moviepanel::on_erase(wxEraseEvent& e)
1210 //Blank.
1213 void wxeditor_movie::_moviepanel::on_paint(wxPaintEvent& e)
1215 auto size = fb.get_pixels();
1216 if(!size.first || !size.second) {
1217 wxPaintDC dc(this);
1218 dc.Clear();
1219 requested = false;
1220 return;
1222 wxPaintDC dc(this);
1223 wxBitmap bmp(wxImage(size.first, size.second, &pixels[0], true));
1224 dc.DrawBitmap(bmp, 0, 0, false);
1225 requested = false;
1228 wxeditor_movie::wxeditor_movie(wxWindow* parent)
1229 : wxDialog(parent, wxID_ANY, wxT("lsnes: Edit movie"), wxDefaultPosition, wxSize(-1, -1))
1231 closing = false;
1232 Centre();
1233 wxFlexGridSizer* top_s = new wxFlexGridSizer(2, 1, 0, 0);
1234 SetSizer(top_s);
1236 wxBoxSizer* panel_s = new wxBoxSizer(wxHORIZONTAL);
1237 moviescroll = NULL;
1238 panel_s->Add(moviepanel = new _moviepanel(this), 1, wxGROW);
1239 panel_s->Add(moviescroll = new wxScrollBar(this, wxID_ANY, wxDefaultPosition, wxDefaultSize,
1240 wxSB_VERTICAL), 0, wxGROW);
1241 top_s->Add(panel_s, 1, wxGROW);
1242 connect_events(moviescroll, wxScrollEventHandler(wxeditor_movie::_moviepanel::on_scroll), moviepanel);
1243 moviepanel->Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(wxeditor_movie::on_keyboard_down), NULL, this);
1244 moviepanel->Connect(wxEVT_KEY_UP, wxKeyEventHandler(wxeditor_movie::on_keyboard_up), NULL, this);
1246 wxBoxSizer* pbutton_s = new wxBoxSizer(wxHORIZONTAL);
1247 pbutton_s->AddStretchSpacer();
1248 pbutton_s->Add(closebutton = new wxButton(this, wxID_OK, wxT("Close")), 0, wxGROW);
1249 closebutton->Connect(wxEVT_COMMAND_BUTTON_CLICKED,
1250 wxCommandEventHandler(wxeditor_movie::on_close), NULL, this);
1251 top_s->Add(pbutton_s, 0, wxGROW);
1253 moviepanel->SetFocus();
1254 moviescroll->Connect(wxEVT_SET_FOCUS, wxFocusEventHandler(wxeditor_movie::on_focus_wrong), NULL, this);
1255 closebutton->Connect(wxEVT_SET_FOCUS, wxFocusEventHandler(wxeditor_movie::on_focus_wrong), NULL, this);
1256 Connect(wxEVT_SET_FOCUS, wxFocusEventHandler(wxeditor_movie::on_focus_wrong), NULL, this);
1258 panel_s->SetSizeHints(this);
1259 pbutton_s->SetSizeHints(this);
1260 top_s->SetSizeHints(this);
1261 Connect(wxEVT_CLOSE_WINDOW, wxCloseEventHandler(wxeditor_movie::on_wclose));
1262 Fit();
1264 moviepanel->signal_repaint();
1267 bool wxeditor_movie::ShouldPreventAppExit() const { return false; }
1269 void wxeditor_movie::on_close(wxCommandEvent& e)
1271 movieeditor_open = NULL;
1272 Destroy();
1273 closing = true;
1276 void wxeditor_movie::on_wclose(wxCloseEvent& e)
1278 bool wasc = closing;
1279 closing = true;
1280 movieeditor_open = NULL;
1281 if(!wasc)
1282 Destroy();
1285 void wxeditor_movie::update()
1287 moviepanel->signal_repaint();
1290 wxScrollBar* wxeditor_movie::get_scroll()
1292 return moviescroll;
1295 void wxeditor_movie::on_focus_wrong(wxFocusEvent& e)
1297 moviepanel->SetFocus();
1300 void wxeditor_movie_display(wxWindow* parent)
1302 if(movieeditor_open)
1303 return;
1304 wxeditor_movie* v = new wxeditor_movie(parent);
1305 v->Show();
1306 movieeditor_open = v;
1309 void wxeditor_movie::on_keyboard_down(wxKeyEvent& e)
1311 handle_wx_keyboard(e, true);
1314 void wxeditor_movie::on_keyboard_up(wxKeyEvent& e)
1316 handle_wx_keyboard(e, false);
1319 void wxeditor_movie_update()
1321 if(movieeditor_open)
1322 movieeditor_open->update();