1 // This file is part of Golly.
2 // See docs/License.html for the copyright notice.
3
4 #include "wx/wxprec.h" // for compilers that support precompilation
5 #ifndef WX_PRECOMP
6 #include "wx/wx.h" // for all others include the necessary headers
7 #endif
8
9 #if wxUSE_TOOLTIPS
10 #include "wx/tooltip.h" // for wxToolTip
11 #endif
12 #include "wx/dcbuffer.h" // for wxBufferedPaintDC
13
14 #include "bigint.h"
15 #include "lifealgo.h"
16
17 #include "wxgolly.h" // for viewptr, statusptr, mainptr
18 #include "wxmain.h" // for mainptr->...
19 #include "wxutils.h" // for Warning, etc
20 #include "wxprefs.h" // for showtimeline, etc
21 #include "wxstatus.h" // for statusptr->...
22 #include "wxscript.h" // for inscript
23 #include "wxview.h" // for viewptr->...
24 #include "wxlayer.h" // for currlayer
25 #include "wxtimeline.h"
26
27 // bitmaps for timeline bar buttons
28 #include "bitmaps/record.xpm"
29 #include "bitmaps/stop.xpm"
30 #include "bitmaps/backwards.xpm"
31 #include "bitmaps/forwards.xpm"
32 #include "bitmaps/stopplay.xpm"
33 #include "bitmaps/deltime.xpm"
34
35 // -----------------------------------------------------------------------------
36
37 // Define timeline bar window:
38
39 enum {
40 // ids for bitmap buttons in timeline bar
41 RECORD_BUTT = 0,
42 STOPREC_BUTT,
43 BACKWARDS_BUTT,
44 FORWARDS_BUTT,
45 STOPPLAY_BUTT,
46 DELETE_BUTT,
47 NUM_BUTTONS, // must be after all buttons
48 ID_SLIDER, // for slider
49 ID_SCROLLBAR, // for scroll bar
50 ID_AUTOTIMER // for autotimer
51 };
52
53 // derive from wxPanel so we get current theme's background color on Windows
54 class TimelineBar : public wxPanel
55 {
56 public:
57 TimelineBar(wxWindow* parent, wxCoord xorg, wxCoord yorg, int wd, int ht);
58 ~TimelineBar();
59
60 void AddButton(int id, const wxString& tip);
61 void AddSeparator();
62 void EnableButton(int id, bool enable);
63 void UpdateButtons();
64 void UpdateSlider();
65 void UpdateScrollBar();
66 void DisplayCurrentFrame();
67 void StartAutoTimer();
68 void StopAutoTimer();
69
70 // detect press and release of a bitmap button
71 void OnButtonDown(wxMouseEvent& event);
72 void OnButtonUp(wxMouseEvent& event);
73 void OnKillFocus(wxFocusEvent& event);
74
75 wxTimer* autotimer; // timer for auto play
76 wxSlider* slider; // slider for controlling autoplay speed
77 wxScrollBar* framebar; // scroll bar for displaying timeline frames
78
79 // positioning data used by AddButton and AddSeparator
80 int ypos, xpos, smallgap, biggap;
81
82 private:
83 // any class wishing to process wxWidgets events must use this macro
84 DECLARE_EVENT_TABLE()
85
86 // event handlers
87 void OnPaint(wxPaintEvent& event);
88 void OnMouseDown(wxMouseEvent& event);
89 void OnButton(wxCommandEvent& event);
90 void OnSlider(wxScrollEvent& event);
91 void OnScroll(wxScrollEvent& event);
92 void OnAutoTimer(wxTimerEvent& event);
93
94 void SetTimelineFont(wxDC& dc);
95 void DisplayText(wxDC& dc, const wxString& s, wxCoord x, wxCoord y);
96 void DrawTimelineBar(wxDC& dc, int wd, int ht);
97
98 // bitmaps for normal buttons
99 wxBitmap normbutt[NUM_BUTTONS];
100
101 #ifdef __WXMSW__
102 // on Windows we need bitmaps for disabled buttons
103 wxBitmap disnormbutt[NUM_BUTTONS];
104 #endif
105
106 // remember state of buttons to avoid unnecessary updates
107 int buttstate[NUM_BUTTONS];
108
109 wxBitmap* timelinebitmap; // timeline bar bitmap
110 int timelinebitmapwd; // width of timeline bar bitmap
111 int timelinebitmapht; // height of timeline bar bitmap
112
113 int digitwd; // width of digit in timeline bar font
114 int digitht; // height of digit in timeline bar font
115 int textascent; // vertical adjustment used in DrawText calls
116 wxFont* timelinefont; // timeline bar font
117 };
118
119 BEGIN_EVENT_TABLE(TimelineBar, wxPanel)
120 EVT_PAINT ( TimelineBar::OnPaint)
121 EVT_LEFT_DOWN ( TimelineBar::OnMouseDown)
122 EVT_BUTTON (wxID_ANY, TimelineBar::OnButton)
123 EVT_COMMAND_SCROLL (ID_SLIDER, TimelineBar::OnSlider)
124 EVT_COMMAND_SCROLL (ID_SCROLLBAR, TimelineBar::OnScroll)
125 EVT_TIMER (ID_AUTOTIMER, TimelineBar::OnAutoTimer)
126 END_EVENT_TABLE()
127
128 // -----------------------------------------------------------------------------
129
130 static TimelineBar* tbarptr = NULL; // global pointer to timeline bar
131 static int mindelpos; // minimum x position of DELETE_BUTT
132
133 const int TBARHT = 32; // height of timeline bar
134 const int SCROLLHT = 17; // height of scroll bar
135 const int PAGESIZE = 10; // scroll amount when paging
136
137 const int MINSPEED = -10; // minimum autoplay speed
138 const int MAXSPEED = 10; // maximum autoplay speed
139
140 // width and height of bitmap buttons
141 #if defined(__WXOSX_COCOA__) && wxCHECK_VERSION(3,0,0)
142 const int BUTTON_WD = 24;
143 const int BUTTON_HT = 24;
144 #elif defined(__WXOSX_COCOA__) || defined(__WXGTK__)
145 const int BUTTON_WD = 28;
146 const int BUTTON_HT = 28;
147 #else
148 const int BUTTON_WD = 24;
149 const int BUTTON_HT = 24;
150 #endif
151
152 // timeline bar buttons (must be global to use Connect/Disconnect on Windows)
153 static wxBitmapButton* tlbutt[NUM_BUTTONS];
154
155 // -----------------------------------------------------------------------------
156
TimelineBar(wxWindow * parent,wxCoord xorg,wxCoord yorg,int wd,int ht)157 TimelineBar::TimelineBar(wxWindow* parent, wxCoord xorg, wxCoord yorg, int wd, int ht)
158 : wxPanel(parent, wxID_ANY, wxPoint(xorg,yorg), wxSize(wd,ht),
159 #ifdef __WXMSW__
160 // need this to avoid buttons flashing on Windows
161 wxNO_FULL_REPAINT_ON_RESIZE
162 #else
163 // better for Mac and Linux
164 wxFULL_REPAINT_ON_RESIZE
165 #endif
166 )
167 {
168 #ifdef __WXGTK__
169 // avoid erasing background on GTK+
170 SetBackgroundStyle(wxBG_STYLE_CUSTOM);
171 #endif
172
173 // init bitmaps for normal state
174 normbutt[RECORD_BUTT] = XPM_BITMAP(record);
175 normbutt[STOPREC_BUTT] = XPM_BITMAP(stop);
176 normbutt[BACKWARDS_BUTT] = XPM_BITMAP(backwards);
177 normbutt[FORWARDS_BUTT] = XPM_BITMAP(forwards);
178 normbutt[STOPPLAY_BUTT] = XPM_BITMAP(stopplay);
179 normbutt[DELETE_BUTT] = XPM_BITMAP(deltime);
180
181 #ifdef __WXMSW__
182 for (int i = 0; i < NUM_BUTTONS; i++) {
183 CreatePaleBitmap(normbutt[i], disnormbutt[i]);
184 }
185 #endif
186
187 for (int i = 0; i < NUM_BUTTONS; i++) {
188 buttstate[i] = 0;
189 }
190
191 // init position variables used by AddButton and AddSeparator
192 #ifdef __WXGTK__
193 // buttons are a different size in wxGTK
194 xpos = 2;
195 ypos = 2;
196 smallgap = 6;
197 #else
198 xpos = 4;
199 ypos = (32 - BUTTON_HT) / 2;
200 smallgap = 4;
201 #endif
202 biggap = 16;
203
204 // add buttons
205 AddButton(RECORD_BUTT, _("Start recording"));
206 AddSeparator();
207 AddButton(BACKWARDS_BUTT, _("Play backwards"));
208 AddButton(FORWARDS_BUTT, _("Play forwards"));
209
210 // create font for text in timeline bar and set textascent for use in DisplayText
211 #ifdef __WXMSW__
212 // use smaller, narrower font on Windows
213 timelinefont = wxFont::New(8, wxDEFAULT, wxNORMAL, wxNORMAL);
214 int major, minor;
215 wxGetOsVersion(&major, &minor);
216 if ( major > 5 || (major == 5 && minor >= 1) ) {
217 // 5.1+ means XP or later (Vista if major >= 6)
218 textascent = 11;
219 } else {
220 textascent = 10;
221 }
222 #elif defined(__WXGTK__)
223 // use smaller font on GTK
224 timelinefont = wxFont::New(8, wxMODERN, wxNORMAL, wxNORMAL);
225 textascent = 11;
226 #elif defined(__WXOSX_COCOA__)
227 // we need to specify facename to get Monaco instead of Courier
228 timelinefont = wxFont::New(10, wxMODERN, wxNORMAL, wxNORMAL, false, wxT("Monaco"));
229 textascent = 10;
230 #else
231 timelinefont = wxFont::New(10, wxMODERN, wxNORMAL, wxNORMAL);
232 textascent = 10;
233 #endif
234 if (timelinefont == NULL) Fatal(_("Failed to create timeline bar font!"));
235
236 wxClientDC dc(this);
237 SetTimelineFont(dc);
238 dc.GetTextExtent(_("9"), &digitwd, &digitht);
239 digitht -= 4;
240
241 timelinebitmap = NULL;
242 timelinebitmapwd = -1;
243 timelinebitmapht = -1;
244
245 // add slider
246 int x, y;
247 int sliderwd = 80;
248 #ifdef __WXMAC__
249 int sliderht = 15;
250 #else
251 int sliderht = 24; // for Windows and wxGTK
252 #endif
253 x = xpos + 20 - smallgap;
254 y = (TBARHT - (sliderht + 1)) / 2;
255 slider = new wxSlider(this, ID_SLIDER, 0, MINSPEED, MAXSPEED, wxPoint(x, y),
256 wxSize(sliderwd, sliderht),
257 wxSL_HORIZONTAL);
258 if (slider == NULL) Fatal(_("Failed to create timeline slider!"));
259 #ifdef __WXMAC__
260 slider->SetWindowVariant(wxWINDOW_VARIANT_SMALL);
261 slider->Move(x, y+1);
262 #endif
263 xpos = x + sliderwd;
264
265 // add scroll bar
266 int scrollbarwd = 60; // minimum width (ResizeTimelineBar will alter it)
267 #ifdef __WXMAC__
268 int scrollbarht = 15; // must be this height on Mac
269 #else
270 int scrollbarht = SCROLLHT;
271 #endif
272 x = xpos + 20;
273 y = (TBARHT - (scrollbarht + 1)) / 2;
274 framebar = new wxScrollBar(this, ID_SCROLLBAR, wxPoint(x, y),
275 wxSize(scrollbarwd, scrollbarht),
276 wxSB_HORIZONTAL);
277 if (framebar == NULL) Fatal(_("Failed to create timeline scroll bar!"));
278
279 xpos = x + scrollbarwd + 4;
280 mindelpos = xpos;
281 AddButton(DELETE_BUTT, _("Delete timeline"));
282 // ResizeTimelineBar will move this button to right end of scroll bar
283
284 // create timer for auto play
285 autotimer = new wxTimer(this, ID_AUTOTIMER);
286 }
287
288 // -----------------------------------------------------------------------------
289
~TimelineBar()290 TimelineBar::~TimelineBar()
291 {
292 delete timelinefont;
293 delete timelinebitmap;
294 delete slider;
295 delete framebar;
296 delete autotimer;
297 }
298
299 // -----------------------------------------------------------------------------
300
SetTimelineFont(wxDC & dc)301 void TimelineBar::SetTimelineFont(wxDC& dc)
302 {
303 dc.SetFont(*timelinefont);
304 dc.SetTextForeground(*wxBLACK);
305 dc.SetBrush(*wxBLACK_BRUSH);
306 dc.SetBackgroundMode(wxTRANSPARENT);
307 }
308
309 // -----------------------------------------------------------------------------
310
DisplayText(wxDC & dc,const wxString & s,wxCoord x,wxCoord y)311 void TimelineBar::DisplayText(wxDC& dc, const wxString& s, wxCoord x, wxCoord y)
312 {
313 // DrawText's y parameter is top of text box but we pass in baseline
314 // so adjust by textascent which depends on platform and OS version -- yuk!
315 dc.DrawText(s, x, y - textascent);
316 }
317
318 // -----------------------------------------------------------------------------
319
DrawTimelineBar(wxDC & dc,int wd,int ht)320 void TimelineBar::DrawTimelineBar(wxDC& dc, int wd, int ht)
321 {
322 wxRect r = wxRect(0, 0, wd, ht);
323
324 #ifdef __WXMAC__
325 wxBrush brush(wxColor(202,202,202));
326 FillRect(dc, r, brush);
327 #endif
328
329 #ifdef __WXMSW__
330 // use theme background color on Windows
331 wxBrush brush(GetBackgroundColour());
332 FillRect(dc, r, brush);
333 #endif
334
335 // draw gray border line at top edge
336 #if defined(__WXMSW__)
337 dc.SetPen(*wxGREY_PEN);
338 #elif defined(__WXMAC__)
339 wxPen linepen(wxColor(140,140,140));
340 dc.SetPen(linepen);
341 #else
342 dc.SetPen(*wxLIGHT_GREY_PEN);
343 #endif
344 dc.DrawLine(0, 0, r.width, 0);
345 dc.SetPen(wxNullPen);
346
347 if (currlayer->algo->hyperCapable()) {
348 bool canplay = TimelineExists() && !currlayer->algo->isrecording();
349 tlbutt[RECORD_BUTT]->Show(true);
350 tlbutt[BACKWARDS_BUTT]->Show(canplay);
351 tlbutt[FORWARDS_BUTT]->Show(canplay);
352 tlbutt[DELETE_BUTT]->Show(canplay);
353 slider->Show(canplay);
354 framebar->Show(canplay);
355
356 if (currlayer->algo->isrecording()) {
357 // show number of frames recorded so far
358 SetTimelineFont(dc);
359 dc.SetPen(*wxBLACK_PEN);
360 int x = smallgap + BUTTON_WD + 10;
361 int y = TBARHT - 8;
362 wxString str;
363 str.Printf(_("Frames recorded: %d"), currlayer->algo->getframecount());
364 DisplayText(dc, str, x, y - (SCROLLHT - digitht)/2);
365 dc.SetPen(wxNullPen);
366 }
367
368 } else {
369 tlbutt[RECORD_BUTT]->Show(false);
370 tlbutt[BACKWARDS_BUTT]->Show(false);
371 tlbutt[FORWARDS_BUTT]->Show(false);
372 tlbutt[DELETE_BUTT]->Show(false);
373 slider->Show(false);
374 framebar->Show(false);
375
376 SetTimelineFont(dc);
377 dc.SetPen(*wxBLACK_PEN);
378 int x = 6;
379 int y = TBARHT - 8;
380 DisplayText(dc, _("The current algorithm does not support timelines."),
381 x, y - (SCROLLHT - digitht)/2);
382 dc.SetPen(wxNullPen);
383 }
384 }
385
386 // -----------------------------------------------------------------------------
387
OnPaint(wxPaintEvent & WXUNUSED (event))388 void TimelineBar::OnPaint(wxPaintEvent& WXUNUSED(event))
389 {
390 int wd, ht;
391 GetClientSize(&wd, &ht);
392 // wd or ht might be < 1 on Windows
393 if (wd < 1) wd = 1;
394 if (ht < 1) ht = 1;
395
396 #if defined(__WXMAC__) || defined(__WXGTK__)
397 // windows on Mac OS X and GTK+ 2.0 are automatically buffered
398 wxPaintDC dc(this);
399 #else
400 // use wxWidgets buffering to avoid flicker
401 if (wd != timelinebitmapwd || ht != timelinebitmapht) {
402 // need to create a new bitmap for timeline bar
403 delete timelinebitmap;
404 timelinebitmap = new wxBitmap(wd, ht);
405 timelinebitmapwd = wd;
406 timelinebitmapht = ht;
407 }
408 if (timelinebitmap == NULL) Fatal(_("Not enough memory to render timeline bar!"));
409 wxBufferedPaintDC dc(this, *timelinebitmap);
410 #endif
411
412 if (showtimeline) DrawTimelineBar(dc, wd, ht);
413 }
414
415 // -----------------------------------------------------------------------------
416
OnMouseDown(wxMouseEvent & WXUNUSED (event))417 void TimelineBar::OnMouseDown(wxMouseEvent& WXUNUSED(event))
418 {
419 // on Win/Linux we need to reset keyboard focus to viewport window
420 viewptr->SetFocus();
421
422 mainptr->showbanner = false;
423 statusptr->ClearMessage();
424 }
425
426 // -----------------------------------------------------------------------------
427
OnButton(wxCommandEvent & event)428 void TimelineBar::OnButton(wxCommandEvent& event)
429 {
430 #ifdef __WXMAC__
431 // close any open tool tip window (fixes wxMac bug?)
432 wxToolTip::RemoveToolTips();
433 #endif
434
435 mainptr->showbanner = false;
436 statusptr->ClearMessage();
437
438 int id = event.GetId();
439
440 int cmdid;
441 switch (id) {
442 case RECORD_BUTT: cmdid = ID_RECORD; break;
443 case BACKWARDS_BUTT: PlayTimeline(-1); return;
444 case FORWARDS_BUTT: PlayTimeline(1); return;
445 case DELETE_BUTT: cmdid = ID_DELTIME; break;
446 default: Warning(_("Unexpected button id!")); return;
447 }
448
449 // call MainFrame::OnMenu after OnButton finishes
450 wxCommandEvent cmdevt(wxEVT_COMMAND_MENU_SELECTED, cmdid);
451 wxPostEvent(mainptr->GetEventHandler(), cmdevt);
452
453 // avoid possible problems
454 viewptr->SetFocus();
455 }
456
457 // -----------------------------------------------------------------------------
458
OnSlider(wxScrollEvent & event)459 void TimelineBar::OnSlider(wxScrollEvent& event)
460 {
461 WXTYPE type = event.GetEventType();
462
463 if (type == wxEVT_SCROLL_LINEUP) {
464 currlayer->tlspeed--;
465 if (currlayer->tlspeed < MINSPEED) currlayer->tlspeed = MINSPEED;
466 StartAutoTimer();
467
468 } else if (type == wxEVT_SCROLL_LINEDOWN) {
469 currlayer->tlspeed++;
470 if (currlayer->tlspeed > MAXSPEED) currlayer->tlspeed = MAXSPEED;
471 StartAutoTimer();
472
473 } else if (type == wxEVT_SCROLL_PAGEUP) {
474 currlayer->tlspeed -= PAGESIZE;
475 if (currlayer->tlspeed < MINSPEED) currlayer->tlspeed = MINSPEED;
476 StartAutoTimer();
477
478 } else if (type == wxEVT_SCROLL_PAGEDOWN) {
479 currlayer->tlspeed += PAGESIZE;
480 if (currlayer->tlspeed > MAXSPEED) currlayer->tlspeed = MAXSPEED;
481 StartAutoTimer();
482
483 } else if (type == wxEVT_SCROLL_THUMBTRACK) {
484 currlayer->tlspeed = event.GetPosition();
485 if (currlayer->tlspeed < MINSPEED) currlayer->tlspeed = MINSPEED;
486 if (currlayer->tlspeed > MAXSPEED) currlayer->tlspeed = MAXSPEED;
487 StartAutoTimer();
488
489 } else if (type == wxEVT_SCROLL_THUMBRELEASE) {
490 UpdateSlider();
491 StartAutoTimer();
492 }
493
494 #ifndef __WXMAC__
495 viewptr->SetFocus(); // need on Win/Linux
496 #endif
497 }
498
499 // -----------------------------------------------------------------------------
500
DisplayCurrentFrame()501 void TimelineBar::DisplayCurrentFrame()
502 {
503 currlayer->algo->gotoframe(currlayer->currframe);
504
505 // FitInView(0) would be less jerky but has the disadvantage that
506 // scale won't change if a pattern shrinks when going backwards
507 if (currlayer->autofit) viewptr->FitInView(1);
508
509 mainptr->UpdatePatternAndStatus();
510 }
511
512 // -----------------------------------------------------------------------------
513
OnScroll(wxScrollEvent & event)514 void TimelineBar::OnScroll(wxScrollEvent& event)
515 {
516 WXTYPE type = event.GetEventType();
517
518 // stop autoplay if scroll bar is used
519 if (currlayer->autoplay != 0) {
520 currlayer->autoplay = 0;
521 StopAutoTimer();
522 mainptr->UpdateUserInterface();
523 }
524
525 if (type == wxEVT_SCROLL_LINEUP) {
526 currlayer->currframe--;
527 if (currlayer->currframe < 0) currlayer->currframe = 0;
528 DisplayCurrentFrame();
529
530 } else if (type == wxEVT_SCROLL_LINEDOWN) {
531 currlayer->currframe++;
532 if (currlayer->currframe >= currlayer->algo->getframecount())
533 currlayer->currframe = currlayer->algo->getframecount() - 1;
534 DisplayCurrentFrame();
535
536 } else if (type == wxEVT_SCROLL_PAGEUP) {
537 currlayer->currframe -= PAGESIZE;
538 if (currlayer->currframe < 0) currlayer->currframe = 0;
539 DisplayCurrentFrame();
540
541 } else if (type == wxEVT_SCROLL_PAGEDOWN) {
542 currlayer->currframe += PAGESIZE;
543 if (currlayer->currframe >= currlayer->algo->getframecount())
544 currlayer->currframe = currlayer->algo->getframecount() - 1;
545 DisplayCurrentFrame();
546
547 } else if (type == wxEVT_SCROLL_THUMBTRACK) {
548 currlayer->currframe = event.GetPosition();
549 if (currlayer->currframe < 0)
550 currlayer->currframe = 0;
551 if (currlayer->currframe >= currlayer->algo->getframecount())
552 currlayer->currframe = currlayer->algo->getframecount() - 1;
553 DisplayCurrentFrame();
554
555 } else if (type == wxEVT_SCROLL_THUMBRELEASE) {
556 UpdateScrollBar();
557 }
558
559 #ifndef __WXMAC__
560 viewptr->SetFocus(); // need on Win/Linux
561 #endif
562 }
563
564 // -----------------------------------------------------------------------------
565
OnKillFocus(wxFocusEvent & event)566 void TimelineBar::OnKillFocus(wxFocusEvent& event)
567 {
568 int id = event.GetId();
569 tlbutt[id]->SetFocus(); // don't let button lose focus
570 }
571
572 // -----------------------------------------------------------------------------
573
OnButtonDown(wxMouseEvent & event)574 void TimelineBar::OnButtonDown(wxMouseEvent& event)
575 {
576 // timeline bar button has been pressed
577 int id = event.GetId();
578
579 // connect a handler that keeps focus with the pressed button
580 tlbutt[id]->Connect(id, wxEVT_KILL_FOCUS, wxFocusEventHandler(TimelineBar::OnKillFocus));
581
582 event.Skip();
583 }
584
585 // -----------------------------------------------------------------------------
586
OnButtonUp(wxMouseEvent & event)587 void TimelineBar::OnButtonUp(wxMouseEvent& event)
588 {
589 // timeline bar button has been released
590 int id = event.GetId();
591
592 wxPoint pt = tlbutt[id]->ScreenToClient( wxGetMousePosition() );
593
594 int wd, ht;
595 tlbutt[id]->GetClientSize(&wd, &ht);
596 wxRect r(0, 0, wd, ht);
597
598 // diconnect kill-focus handler
599 tlbutt[id]->Disconnect(id, wxEVT_KILL_FOCUS, wxFocusEventHandler(TimelineBar::OnKillFocus));
600 viewptr->SetFocus();
601
602 if (r.Contains(pt)) {
603 // call OnButton
604 wxCommandEvent buttevt(wxEVT_COMMAND_BUTTON_CLICKED, id);
605 buttevt.SetEventObject(tlbutt[id]);
606 tlbutt[id]->GetEventHandler()->ProcessEvent(buttevt);
607 }
608 }
609
610 // -----------------------------------------------------------------------------
611
AddButton(int id,const wxString & tip)612 void TimelineBar::AddButton(int id, const wxString& tip)
613 {
614 tlbutt[id] = new wxBitmapButton(this, id, normbutt[id], wxPoint(xpos,ypos),
615 #if defined(__WXOSX_COCOA__) && wxCHECK_VERSION(3,0,0)
616 wxSize(BUTTON_WD, BUTTON_HT), wxBORDER_SIMPLE
617 #else
618 wxSize(BUTTON_WD, BUTTON_HT)
619 #endif
620 );
621 if (tlbutt[id] == NULL) {
622 Fatal(_("Failed to create timeline bar button!"));
623 } else {
624 xpos += BUTTON_WD + smallgap;
625 tlbutt[id]->SetToolTip(tip);
626 #ifdef __WXMSW__
627 // fix problem with timeline bar buttons when generating/inscript
628 // due to focus being changed to viewptr
629 tlbutt[id]->Connect(id, wxEVT_LEFT_DOWN, wxMouseEventHandler(TimelineBar::OnButtonDown));
630 tlbutt[id]->Connect(id, wxEVT_LEFT_UP, wxMouseEventHandler(TimelineBar::OnButtonUp));
631 #endif
632 }
633 }
634
635 // -----------------------------------------------------------------------------
636
AddSeparator()637 void TimelineBar::AddSeparator()
638 {
639 xpos += biggap - smallgap;
640 }
641
642 // -----------------------------------------------------------------------------
643
EnableButton(int id,bool enable)644 void TimelineBar::EnableButton(int id, bool enable)
645 {
646 if (enable == tlbutt[id]->IsEnabled()) return;
647
648 #ifdef __WXMSW__
649 tlbutt[id]->SetBitmapDisabled(disnormbutt[id]);
650 #endif
651
652 tlbutt[id]->Enable(enable);
653 }
654
655 // -----------------------------------------------------------------------------
656
UpdateButtons()657 void TimelineBar::UpdateButtons()
658 {
659 if (currlayer->algo->isrecording()) {
660 if (buttstate[RECORD_BUTT] != -1) {
661 buttstate[RECORD_BUTT] = -1;
662 tlbutt[RECORD_BUTT]->SetBitmapLabel(normbutt[STOPREC_BUTT]);
663 tlbutt[RECORD_BUTT]->SetToolTip(_("Stop recording"));
664 if (showtimeline) tlbutt[RECORD_BUTT]->Refresh(false);
665 }
666 } else {
667 if (buttstate[RECORD_BUTT] != 1) {
668 buttstate[RECORD_BUTT] = 1;
669 tlbutt[RECORD_BUTT]->SetBitmapLabel(normbutt[RECORD_BUTT]);
670 tlbutt[RECORD_BUTT]->SetToolTip(_("Start recording"));
671 if (showtimeline) tlbutt[RECORD_BUTT]->Refresh(false);
672 }
673 }
674
675 // these buttons are only shown if there is a timeline and we're not recording
676 // (see DrawTimelineBar)
677 if (TimelineExists() && !currlayer->algo->isrecording()) {
678 if (currlayer->autoplay == 0) {
679 if (buttstate[BACKWARDS_BUTT] != 1) {
680 buttstate[BACKWARDS_BUTT] = 1;
681 tlbutt[BACKWARDS_BUTT]->SetBitmapLabel(normbutt[BACKWARDS_BUTT]);
682 tlbutt[BACKWARDS_BUTT]->SetToolTip(_("Play backwards"));
683 if (showtimeline) tlbutt[BACKWARDS_BUTT]->Refresh(false);
684 }
685 if (buttstate[FORWARDS_BUTT] != 1) {
686 buttstate[FORWARDS_BUTT] = 1;
687 tlbutt[FORWARDS_BUTT]->SetBitmapLabel(normbutt[FORWARDS_BUTT]);
688 tlbutt[FORWARDS_BUTT]->SetToolTip(_("Play forwards"));
689 if (showtimeline) tlbutt[FORWARDS_BUTT]->Refresh(false);
690 }
691 } else if (currlayer->autoplay > 0) {
692 if (buttstate[BACKWARDS_BUTT] != 1) {
693 buttstate[BACKWARDS_BUTT] = 1;
694 tlbutt[BACKWARDS_BUTT]->SetBitmapLabel(normbutt[BACKWARDS_BUTT]);
695 tlbutt[BACKWARDS_BUTT]->SetToolTip(_("Play backwards"));
696 if (showtimeline) tlbutt[BACKWARDS_BUTT]->Refresh(false);
697 }
698 if (buttstate[FORWARDS_BUTT] != -1) {
699 buttstate[FORWARDS_BUTT] = -1;
700 tlbutt[FORWARDS_BUTT]->SetBitmapLabel(normbutt[STOPPLAY_BUTT]);
701 tlbutt[FORWARDS_BUTT]->SetToolTip(_("Stop playing"));
702 if (showtimeline) tlbutt[FORWARDS_BUTT]->Refresh(false);
703 }
704 } else { // currlayer->autoplay < 0
705 if (buttstate[BACKWARDS_BUTT] != -1) {
706 buttstate[BACKWARDS_BUTT] = -1;
707 tlbutt[BACKWARDS_BUTT]->SetBitmapLabel(normbutt[STOPPLAY_BUTT]);
708 tlbutt[BACKWARDS_BUTT]->SetToolTip(_("Stop playing"));
709 if (showtimeline) tlbutt[BACKWARDS_BUTT]->Refresh(false);
710 }
711 if (buttstate[FORWARDS_BUTT] != 1) {
712 buttstate[FORWARDS_BUTT] = 1;
713 tlbutt[FORWARDS_BUTT]->SetBitmapLabel(normbutt[FORWARDS_BUTT]);
714 tlbutt[FORWARDS_BUTT]->SetToolTip(_("Play forwards"));
715 if (showtimeline) tlbutt[FORWARDS_BUTT]->Refresh(false);
716 }
717 }
718 }
719 }
720
721 // -----------------------------------------------------------------------------
722
UpdateSlider()723 void TimelineBar::UpdateSlider()
724 {
725 slider->SetValue(currlayer->tlspeed);
726 }
727
728 // -----------------------------------------------------------------------------
729
UpdateScrollBar()730 void TimelineBar::UpdateScrollBar()
731 {
732 framebar->SetScrollbar(currlayer->currframe, 1,
733 currlayer->algo->getframecount(), PAGESIZE, true);
734 }
735
736 // -----------------------------------------------------------------------------
737
OnAutoTimer(wxTimerEvent & WXUNUSED (event))738 void TimelineBar::OnAutoTimer(wxTimerEvent& WXUNUSED(event))
739 {
740 if (currlayer->autoplay == 0) return;
741 if (currlayer->algo->isrecording()) return;
742 // assume currlayer->algo->getframecount() > 0
743
744 int frameinc = 1;
745 if (currlayer->tlspeed > 0) {
746 // skip 2^tlspeed frames
747 frameinc = 1 << currlayer->tlspeed;
748 }
749
750 if (currlayer->autoplay > 0) {
751 // play timeline forwards
752 currlayer->currframe += frameinc;
753 if (currlayer->currframe >= currlayer->algo->getframecount() - 1) {
754 currlayer->currframe = currlayer->algo->getframecount() - 1;
755 currlayer->autoplay = -1; // reverse direction when we hit last frame
756 UpdateButtons();
757 }
758 } else {
759 // currlayer->autoplay < 0 so play timeline backwards
760 currlayer->currframe -= frameinc;
761 if (currlayer->currframe <= 0) {
762 currlayer->currframe = 0;
763 currlayer->autoplay = 1; // reverse direction when we hit first frame
764 UpdateButtons();
765 }
766 }
767
768 DisplayCurrentFrame();
769 UpdateScrollBar();
770 }
771
772 // -----------------------------------------------------------------------------
773
StartAutoTimer()774 void TimelineBar::StartAutoTimer()
775 {
776 if (currlayer->autoplay == 0) return;
777
778 int interval = SIXTY_HERTZ; // do ~60 calls of OnAutoTimer per sec
779
780 // increase interval if user wants a delay between each frame
781 if (currlayer->tlspeed < 0) {
782 interval = 100 * (-currlayer->tlspeed);
783 // if tlspeed is -1 then interval is 100; ie. call OnAutoTimer 10 times per sec
784 }
785
786 StopAutoTimer();
787 autotimer->Start(interval, wxTIMER_CONTINUOUS);
788 }
789
790 // -----------------------------------------------------------------------------
791
StopAutoTimer()792 void TimelineBar::StopAutoTimer()
793 {
794 if (autotimer->IsRunning()) autotimer->Stop();
795 }
796
797 // -----------------------------------------------------------------------------
798
CreateTimelineBar(wxWindow * parent)799 void CreateTimelineBar(wxWindow* parent)
800 {
801 // create timeline bar underneath given window
802 int wd, ht;
803 parent->GetClientSize(&wd, &ht);
804
805 tbarptr = new TimelineBar(parent, 0, ht - TBARHT, wd, TBARHT);
806 if (tbarptr == NULL) Fatal(_("Failed to create timeline bar!"));
807
808 tbarptr->Show(showtimeline);
809 }
810
811 // -----------------------------------------------------------------------------
812
TimelineBarHeight()813 int TimelineBarHeight() {
814 return (showtimeline ? TBARHT : 0);
815 }
816
817 // -----------------------------------------------------------------------------
818
UpdateTimelineBar()819 void UpdateTimelineBar()
820 {
821 if (tbarptr && showtimeline && !mainptr->IsIconized()) {
822 bool active = !inscript;
823
824 // may need to change bitmaps in some buttons
825 tbarptr->UpdateButtons();
826
827 tbarptr->EnableButton(RECORD_BUTT, active && currlayer->algo->hyperCapable());
828
829 // note that slider, scroll bar and some buttons are only shown if there is
830 // a timeline and we're not recording (see DrawTimelineBar)
831 if (TimelineExists() && !currlayer->algo->isrecording()) {
832 tbarptr->EnableButton(BACKWARDS_BUTT, active);
833 tbarptr->EnableButton(FORWARDS_BUTT, active);
834 tbarptr->EnableButton(DELETE_BUTT, active);
835 tbarptr->UpdateSlider();
836 tbarptr->UpdateScrollBar();
837 }
838
839 if (currlayer->algo->isrecording()) {
840 // don't refresh RECORD_BUTT (otherwise button flickers on Windows)
841 int wd, ht;
842 tbarptr->GetClientSize(&wd, &ht);
843 wxRect r(BUTTON_WD + tbarptr->smallgap * 2, 0, wd, ht);
844 tbarptr->RefreshRect(r, false);
845 } else {
846 tbarptr->Refresh(false);
847 }
848 #ifdef __WXGTK__
849 // avoid bug that can cause buttons to lose their bitmaps
850 tbarptr->Update();
851 #endif
852 }
853 }
854
855 // -----------------------------------------------------------------------------
856
ResizeTimelineBar(int y,int wd)857 void ResizeTimelineBar(int y, int wd)
858 {
859 if (tbarptr && showtimeline) {
860 tbarptr->SetSize(0, y, wd, TBARHT);
861 // change width of scroll bar to nearly fill timeline bar
862 wxRect r = tbarptr->framebar->GetRect();
863 r.width = wd - r.x - 20 - BUTTON_WD - 20;
864 if (r.width < 0) r.width = 0;
865 tbarptr->framebar->SetSize(r);
866
867 // move DELETE_BUTT to right edge of timeline bar
868 r = tlbutt[DELETE_BUTT]->GetRect();
869 r.x = wd - 20 - BUTTON_WD;
870 if (r.x < mindelpos && TimelineExists()) r.x = mindelpos;
871 tlbutt[DELETE_BUTT]->SetSize(r);
872 }
873 }
874
875 // -----------------------------------------------------------------------------
876
ToggleTimelineBar()877 void ToggleTimelineBar()
878 {
879 showtimeline = !showtimeline;
880 mainptr->ResizeBigView();
881 tbarptr->Show(showtimeline); // needed on Windows
882 mainptr->UpdateEverything();
883 }
884
885 // -----------------------------------------------------------------------------
886
StartStopRecording()887 void StartStopRecording()
888 {
889 if (!inscript && currlayer->algo->hyperCapable()) {
890 if (currlayer->algo->isrecording()) {
891 mainptr->Stop();
892 // StopGenerating() has called currlayer->algo->stoprecording()
893
894 } else {
895 tbarptr->StopAutoTimer();
896
897 if (currlayer->algo->isEmpty()) {
898 statusptr->ErrorMessage(_("There is no pattern to record."));
899 return;
900 }
901
902 if (!showtimeline) ToggleTimelineBar();
903
904 if (currlayer->algo->getframecount() == MAX_FRAME_COUNT) {
905 wxString msg;
906 msg.Printf(_("The timeline can't be extended any further (max frames = %d)."),
907 MAX_FRAME_COUNT);
908 statusptr->ErrorMessage(msg);
909 return;
910 }
911
912 // record a new timeline, or extend the existing one
913 if (currlayer->algo->startrecording(currlayer->currbase, currlayer->currexpo) > 0) {
914 if (currlayer->algo->getGeneration() == currlayer->startgen) {
915 // ensure the SaveStartingPattern call in DeleteTimeline will
916 // create a new temporary .mc file (with only one frame)
917 currlayer->savestart = true;
918 }
919
920 mainptr->StartGenerating();
921
922 } else {
923 // this should never happen
924 Warning(_("Bug: could not start recording!"));
925 }
926 }
927 }
928 }
929
930 // -----------------------------------------------------------------------------
931
DeleteTimeline()932 void DeleteTimeline()
933 {
934 if (!inscript && TimelineExists() && !currlayer->algo->isrecording()) {
935
936 tbarptr->StopAutoTimer();
937
938 if (currlayer->currframe > 0) {
939 // tell writeNativeFormat to only save the current frame
940 // so that the temporary .mc files created by SaveStartingPattern and
941 // RememberGenStart/Finish won't store the entire timeline
942 currlayer->algo->savetimelinewithframe(0);
943
944 // do stuff so user can select Reset/Undo to go back to 1st frame
945 currlayer->algo->gotoframe(0);
946 if (currlayer->autofit) viewptr->FitInView(1);
947 if (currlayer->algo->getGeneration() == currlayer->startgen) {
948 mainptr->SaveStartingPattern();
949 }
950 if (allowundo) currlayer->undoredo->RememberGenStart();
951
952 // return to the current frame
953 currlayer->algo->gotoframe(currlayer->currframe);
954 if (currlayer->autofit) viewptr->FitInView(1);
955 if (allowundo) currlayer->undoredo->RememberGenFinish();
956
957 // restore flag that tells writeNativeFormat to save entire timeline
958 currlayer->algo->savetimelinewithframe(1);
959 }
960
961 currlayer->algo->destroytimeline();
962 mainptr->UpdateUserInterface();
963 }
964 }
965
966 // -----------------------------------------------------------------------------
967
InitTimelineFrame()968 void InitTimelineFrame()
969 {
970 // the user has just loaded a .mc file with a timeline,
971 // so prepare to display the first frame
972 currlayer->algo->gotoframe(0);
973 currlayer->currframe = 0;
974 currlayer->autoplay = 0;
975 currlayer->tlspeed = 0;
976 tbarptr->StopAutoTimer();
977
978 // first frame is starting gen (needed for DeleteTimeline)
979 currlayer->startgen = currlayer->algo->getGeneration();
980
981 // ensure SaveStartingPattern call in DeleteTimeline will create
982 // a new temporary .mc file with one frame
983 currlayer->savestart = true;
984 }
985
986 // -----------------------------------------------------------------------------
987
TimelineExists()988 bool TimelineExists()
989 {
990 return currlayer->algo->getframecount() > 0;
991 }
992
993 // -----------------------------------------------------------------------------
994
PlayTimeline(int direction)995 void PlayTimeline(int direction)
996 {
997 if (currlayer->algo->isrecording()) return;
998 if (direction > 0 && currlayer->autoplay > 0) {
999 currlayer->autoplay = 0;
1000 } else if (direction < 0 && currlayer->autoplay < 0) {
1001 currlayer->autoplay = 0;
1002 } else {
1003 currlayer->autoplay = direction;
1004 }
1005
1006 if (currlayer->autoplay == 0) {
1007 tbarptr->StopAutoTimer();
1008 } else {
1009 tbarptr->StartAutoTimer();
1010 }
1011
1012 mainptr->UpdateUserInterface();
1013 }
1014
1015 // -----------------------------------------------------------------------------
1016
PlayTimelineFaster()1017 void PlayTimelineFaster()
1018 {
1019 if (currlayer->algo->isrecording()) return;
1020 if (currlayer->tlspeed < MAXSPEED) {
1021 currlayer->tlspeed++;
1022 if (showtimeline) tbarptr->UpdateSlider();
1023 tbarptr->StartAutoTimer();
1024 }
1025 }
1026
1027 // -----------------------------------------------------------------------------
1028
PlayTimelineSlower()1029 void PlayTimelineSlower()
1030 {
1031 if (currlayer->algo->isrecording()) return;
1032 if (currlayer->tlspeed > MINSPEED) {
1033 currlayer->tlspeed--;
1034 if (showtimeline) tbarptr->UpdateSlider();
1035 tbarptr->StartAutoTimer();
1036 }
1037 }
1038
1039 // -----------------------------------------------------------------------------
1040
ResetTimelineSpeed()1041 void ResetTimelineSpeed()
1042 {
1043 if (currlayer->algo->isrecording()) return;
1044 currlayer->tlspeed = 0;
1045 if (showtimeline) tbarptr->UpdateSlider();
1046 tbarptr->StartAutoTimer();
1047 }
1048
1049 // -----------------------------------------------------------------------------
1050
TimelineIsPlaying()1051 bool TimelineIsPlaying()
1052 {
1053 return currlayer->autoplay != 0;
1054 }
1055