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