// This file is part of Golly. // See docs/License.html for the copyright notice. #include "wx/wxprec.h" // for compilers that support precompilation #ifndef WX_PRECOMP #include "wx/wx.h" // for all others include the necessary headers #endif #if wxUSE_TOOLTIPS #include "wx/tooltip.h" // for wxToolTip #endif #include "wx/dcbuffer.h" // for wxBufferedPaintDC #include "bigint.h" #include "lifealgo.h" #include "wxgolly.h" // for viewptr, statusptr, mainptr #include "wxmain.h" // for mainptr->... #include "wxutils.h" // for Warning, etc #include "wxprefs.h" // for showtimeline, etc #include "wxstatus.h" // for statusptr->... #include "wxscript.h" // for inscript #include "wxview.h" // for viewptr->... #include "wxlayer.h" // for currlayer #include "wxtimeline.h" // bitmaps for timeline bar buttons #include "bitmaps/record.xpm" #include "bitmaps/stop.xpm" #include "bitmaps/backwards.xpm" #include "bitmaps/forwards.xpm" #include "bitmaps/stopplay.xpm" #include "bitmaps/deltime.xpm" // ----------------------------------------------------------------------------- // Define timeline bar window: enum { // ids for bitmap buttons in timeline bar RECORD_BUTT = 0, STOPREC_BUTT, BACKWARDS_BUTT, FORWARDS_BUTT, STOPPLAY_BUTT, DELETE_BUTT, NUM_BUTTONS, // must be after all buttons ID_SLIDER, // for slider ID_SCROLLBAR, // for scroll bar ID_AUTOTIMER // for autotimer }; // derive from wxPanel so we get current theme's background color on Windows class TimelineBar : public wxPanel { public: TimelineBar(wxWindow* parent, wxCoord xorg, wxCoord yorg, int wd, int ht); ~TimelineBar(); void AddButton(int id, const wxString& tip); void AddSeparator(); void EnableButton(int id, bool enable); void UpdateButtons(); void UpdateSlider(); void UpdateScrollBar(); void DisplayCurrentFrame(); void StartAutoTimer(); void StopAutoTimer(); // detect press and release of a bitmap button void OnButtonDown(wxMouseEvent& event); void OnButtonUp(wxMouseEvent& event); void OnKillFocus(wxFocusEvent& event); wxTimer* autotimer; // timer for auto play wxSlider* slider; // slider for controlling autoplay speed wxScrollBar* framebar; // scroll bar for displaying timeline frames // positioning data used by AddButton and AddSeparator int ypos, xpos, smallgap, biggap; private: // any class wishing to process wxWidgets events must use this macro DECLARE_EVENT_TABLE() // event handlers void OnPaint(wxPaintEvent& event); void OnMouseDown(wxMouseEvent& event); void OnButton(wxCommandEvent& event); void OnSlider(wxScrollEvent& event); void OnScroll(wxScrollEvent& event); void OnAutoTimer(wxTimerEvent& event); void SetTimelineFont(wxDC& dc); void DisplayText(wxDC& dc, const wxString& s, wxCoord x, wxCoord y); void DrawTimelineBar(wxDC& dc, int wd, int ht); // bitmaps for normal buttons wxBitmap normbutt[NUM_BUTTONS]; #ifdef __WXMSW__ // on Windows we need bitmaps for disabled buttons wxBitmap disnormbutt[NUM_BUTTONS]; #endif // remember state of buttons to avoid unnecessary updates int buttstate[NUM_BUTTONS]; wxBitmap* timelinebitmap; // timeline bar bitmap int timelinebitmapwd; // width of timeline bar bitmap int timelinebitmapht; // height of timeline bar bitmap int digitwd; // width of digit in timeline bar font int digitht; // height of digit in timeline bar font int textascent; // vertical adjustment used in DrawText calls wxFont* timelinefont; // timeline bar font }; BEGIN_EVENT_TABLE(TimelineBar, wxPanel) EVT_PAINT ( TimelineBar::OnPaint) EVT_LEFT_DOWN ( TimelineBar::OnMouseDown) EVT_BUTTON (wxID_ANY, TimelineBar::OnButton) EVT_COMMAND_SCROLL (ID_SLIDER, TimelineBar::OnSlider) EVT_COMMAND_SCROLL (ID_SCROLLBAR, TimelineBar::OnScroll) EVT_TIMER (ID_AUTOTIMER, TimelineBar::OnAutoTimer) END_EVENT_TABLE() // ----------------------------------------------------------------------------- static TimelineBar* tbarptr = NULL; // global pointer to timeline bar static int mindelpos; // minimum x position of DELETE_BUTT const int TBARHT = 32; // height of timeline bar const int SCROLLHT = 17; // height of scroll bar const int PAGESIZE = 10; // scroll amount when paging const int MINSPEED = -10; // minimum autoplay speed const int MAXSPEED = 10; // maximum autoplay speed // width and height of bitmap buttons #if defined(__WXOSX_COCOA__) && wxCHECK_VERSION(3,0,0) const int BUTTON_WD = 24; const int BUTTON_HT = 24; #elif defined(__WXOSX_COCOA__) || defined(__WXGTK__) const int BUTTON_WD = 28; const int BUTTON_HT = 28; #else const int BUTTON_WD = 24; const int BUTTON_HT = 24; #endif // timeline bar buttons (must be global to use Connect/Disconnect on Windows) static wxBitmapButton* tlbutt[NUM_BUTTONS]; // ----------------------------------------------------------------------------- TimelineBar::TimelineBar(wxWindow* parent, wxCoord xorg, wxCoord yorg, int wd, int ht) : wxPanel(parent, wxID_ANY, wxPoint(xorg,yorg), wxSize(wd,ht), #ifdef __WXMSW__ // need this to avoid buttons flashing on Windows wxNO_FULL_REPAINT_ON_RESIZE #else // better for Mac and Linux wxFULL_REPAINT_ON_RESIZE #endif ) { #ifdef __WXGTK__ // avoid erasing background on GTK+ SetBackgroundStyle(wxBG_STYLE_CUSTOM); #endif // init bitmaps for normal state normbutt[RECORD_BUTT] = XPM_BITMAP(record); normbutt[STOPREC_BUTT] = XPM_BITMAP(stop); normbutt[BACKWARDS_BUTT] = XPM_BITMAP(backwards); normbutt[FORWARDS_BUTT] = XPM_BITMAP(forwards); normbutt[STOPPLAY_BUTT] = XPM_BITMAP(stopplay); normbutt[DELETE_BUTT] = XPM_BITMAP(deltime); #ifdef __WXMSW__ for (int i = 0; i < NUM_BUTTONS; i++) { CreatePaleBitmap(normbutt[i], disnormbutt[i]); } #endif for (int i = 0; i < NUM_BUTTONS; i++) { buttstate[i] = 0; } // init position variables used by AddButton and AddSeparator #ifdef __WXGTK__ // buttons are a different size in wxGTK xpos = 2; ypos = 2; smallgap = 6; #else xpos = 4; ypos = (32 - BUTTON_HT) / 2; smallgap = 4; #endif biggap = 16; // add buttons AddButton(RECORD_BUTT, _("Start recording")); AddSeparator(); AddButton(BACKWARDS_BUTT, _("Play backwards")); AddButton(FORWARDS_BUTT, _("Play forwards")); // create font for text in timeline bar and set textascent for use in DisplayText #ifdef __WXMSW__ // use smaller, narrower font on Windows timelinefont = wxFont::New(8, wxDEFAULT, wxNORMAL, wxNORMAL); int major, minor; wxGetOsVersion(&major, &minor); if ( major > 5 || (major == 5 && minor >= 1) ) { // 5.1+ means XP or later (Vista if major >= 6) textascent = 11; } else { textascent = 10; } #elif defined(__WXGTK__) // use smaller font on GTK timelinefont = wxFont::New(8, wxMODERN, wxNORMAL, wxNORMAL); textascent = 11; #elif defined(__WXOSX_COCOA__) // we need to specify facename to get Monaco instead of Courier timelinefont = wxFont::New(10, wxMODERN, wxNORMAL, wxNORMAL, false, wxT("Monaco")); textascent = 10; #else timelinefont = wxFont::New(10, wxMODERN, wxNORMAL, wxNORMAL); textascent = 10; #endif if (timelinefont == NULL) Fatal(_("Failed to create timeline bar font!")); wxClientDC dc(this); SetTimelineFont(dc); dc.GetTextExtent(_("9"), &digitwd, &digitht); digitht -= 4; timelinebitmap = NULL; timelinebitmapwd = -1; timelinebitmapht = -1; // add slider int x, y; int sliderwd = 80; #ifdef __WXMAC__ int sliderht = 15; #else int sliderht = 24; // for Windows and wxGTK #endif x = xpos + 20 - smallgap; y = (TBARHT - (sliderht + 1)) / 2; slider = new wxSlider(this, ID_SLIDER, 0, MINSPEED, MAXSPEED, wxPoint(x, y), wxSize(sliderwd, sliderht), wxSL_HORIZONTAL); if (slider == NULL) Fatal(_("Failed to create timeline slider!")); #ifdef __WXMAC__ slider->SetWindowVariant(wxWINDOW_VARIANT_SMALL); slider->Move(x, y+1); #endif xpos = x + sliderwd; // add scroll bar int scrollbarwd = 60; // minimum width (ResizeTimelineBar will alter it) #ifdef __WXMAC__ int scrollbarht = 15; // must be this height on Mac #else int scrollbarht = SCROLLHT; #endif x = xpos + 20; y = (TBARHT - (scrollbarht + 1)) / 2; framebar = new wxScrollBar(this, ID_SCROLLBAR, wxPoint(x, y), wxSize(scrollbarwd, scrollbarht), wxSB_HORIZONTAL); if (framebar == NULL) Fatal(_("Failed to create timeline scroll bar!")); xpos = x + scrollbarwd + 4; mindelpos = xpos; AddButton(DELETE_BUTT, _("Delete timeline")); // ResizeTimelineBar will move this button to right end of scroll bar // create timer for auto play autotimer = new wxTimer(this, ID_AUTOTIMER); } // ----------------------------------------------------------------------------- TimelineBar::~TimelineBar() { delete timelinefont; delete timelinebitmap; delete slider; delete framebar; delete autotimer; } // ----------------------------------------------------------------------------- void TimelineBar::SetTimelineFont(wxDC& dc) { dc.SetFont(*timelinefont); dc.SetTextForeground(*wxBLACK); dc.SetBrush(*wxBLACK_BRUSH); dc.SetBackgroundMode(wxTRANSPARENT); } // ----------------------------------------------------------------------------- void TimelineBar::DisplayText(wxDC& dc, const wxString& s, wxCoord x, wxCoord y) { // DrawText's y parameter is top of text box but we pass in baseline // so adjust by textascent which depends on platform and OS version -- yuk! dc.DrawText(s, x, y - textascent); } // ----------------------------------------------------------------------------- void TimelineBar::DrawTimelineBar(wxDC& dc, int wd, int ht) { wxRect r = wxRect(0, 0, wd, ht); #ifdef __WXMAC__ wxBrush brush(wxColor(202,202,202)); FillRect(dc, r, brush); #endif #ifdef __WXMSW__ // use theme background color on Windows wxBrush brush(GetBackgroundColour()); FillRect(dc, r, brush); #endif // draw gray border line at top edge #if defined(__WXMSW__) dc.SetPen(*wxGREY_PEN); #elif defined(__WXMAC__) wxPen linepen(wxColor(140,140,140)); dc.SetPen(linepen); #else dc.SetPen(*wxLIGHT_GREY_PEN); #endif dc.DrawLine(0, 0, r.width, 0); dc.SetPen(wxNullPen); if (currlayer->algo->hyperCapable()) { bool canplay = TimelineExists() && !currlayer->algo->isrecording(); tlbutt[RECORD_BUTT]->Show(true); tlbutt[BACKWARDS_BUTT]->Show(canplay); tlbutt[FORWARDS_BUTT]->Show(canplay); tlbutt[DELETE_BUTT]->Show(canplay); slider->Show(canplay); framebar->Show(canplay); if (currlayer->algo->isrecording()) { // show number of frames recorded so far SetTimelineFont(dc); dc.SetPen(*wxBLACK_PEN); int x = smallgap + BUTTON_WD + 10; int y = TBARHT - 8; wxString str; str.Printf(_("Frames recorded: %d"), currlayer->algo->getframecount()); DisplayText(dc, str, x, y - (SCROLLHT - digitht)/2); dc.SetPen(wxNullPen); } } else { tlbutt[RECORD_BUTT]->Show(false); tlbutt[BACKWARDS_BUTT]->Show(false); tlbutt[FORWARDS_BUTT]->Show(false); tlbutt[DELETE_BUTT]->Show(false); slider->Show(false); framebar->Show(false); SetTimelineFont(dc); dc.SetPen(*wxBLACK_PEN); int x = 6; int y = TBARHT - 8; DisplayText(dc, _("The current algorithm does not support timelines."), x, y - (SCROLLHT - digitht)/2); dc.SetPen(wxNullPen); } } // ----------------------------------------------------------------------------- void TimelineBar::OnPaint(wxPaintEvent& WXUNUSED(event)) { int wd, ht; GetClientSize(&wd, &ht); // wd or ht might be < 1 on Windows if (wd < 1) wd = 1; if (ht < 1) ht = 1; #if defined(__WXMAC__) || defined(__WXGTK__) // windows on Mac OS X and GTK+ 2.0 are automatically buffered wxPaintDC dc(this); #else // use wxWidgets buffering to avoid flicker if (wd != timelinebitmapwd || ht != timelinebitmapht) { // need to create a new bitmap for timeline bar delete timelinebitmap; timelinebitmap = new wxBitmap(wd, ht); timelinebitmapwd = wd; timelinebitmapht = ht; } if (timelinebitmap == NULL) Fatal(_("Not enough memory to render timeline bar!")); wxBufferedPaintDC dc(this, *timelinebitmap); #endif if (showtimeline) DrawTimelineBar(dc, wd, ht); } // ----------------------------------------------------------------------------- void TimelineBar::OnMouseDown(wxMouseEvent& WXUNUSED(event)) { // on Win/Linux we need to reset keyboard focus to viewport window viewptr->SetFocus(); mainptr->showbanner = false; statusptr->ClearMessage(); } // ----------------------------------------------------------------------------- void TimelineBar::OnButton(wxCommandEvent& event) { #ifdef __WXMAC__ // close any open tool tip window (fixes wxMac bug?) wxToolTip::RemoveToolTips(); #endif mainptr->showbanner = false; statusptr->ClearMessage(); int id = event.GetId(); int cmdid; switch (id) { case RECORD_BUTT: cmdid = ID_RECORD; break; case BACKWARDS_BUTT: PlayTimeline(-1); return; case FORWARDS_BUTT: PlayTimeline(1); return; case DELETE_BUTT: cmdid = ID_DELTIME; break; default: Warning(_("Unexpected button id!")); return; } // call MainFrame::OnMenu after OnButton finishes wxCommandEvent cmdevt(wxEVT_COMMAND_MENU_SELECTED, cmdid); wxPostEvent(mainptr->GetEventHandler(), cmdevt); // avoid possible problems viewptr->SetFocus(); } // ----------------------------------------------------------------------------- void TimelineBar::OnSlider(wxScrollEvent& event) { WXTYPE type = event.GetEventType(); if (type == wxEVT_SCROLL_LINEUP) { currlayer->tlspeed--; if (currlayer->tlspeed < MINSPEED) currlayer->tlspeed = MINSPEED; StartAutoTimer(); } else if (type == wxEVT_SCROLL_LINEDOWN) { currlayer->tlspeed++; if (currlayer->tlspeed > MAXSPEED) currlayer->tlspeed = MAXSPEED; StartAutoTimer(); } else if (type == wxEVT_SCROLL_PAGEUP) { currlayer->tlspeed -= PAGESIZE; if (currlayer->tlspeed < MINSPEED) currlayer->tlspeed = MINSPEED; StartAutoTimer(); } else if (type == wxEVT_SCROLL_PAGEDOWN) { currlayer->tlspeed += PAGESIZE; if (currlayer->tlspeed > MAXSPEED) currlayer->tlspeed = MAXSPEED; StartAutoTimer(); } else if (type == wxEVT_SCROLL_THUMBTRACK) { currlayer->tlspeed = event.GetPosition(); if (currlayer->tlspeed < MINSPEED) currlayer->tlspeed = MINSPEED; if (currlayer->tlspeed > MAXSPEED) currlayer->tlspeed = MAXSPEED; StartAutoTimer(); } else if (type == wxEVT_SCROLL_THUMBRELEASE) { UpdateSlider(); StartAutoTimer(); } #ifndef __WXMAC__ viewptr->SetFocus(); // need on Win/Linux #endif } // ----------------------------------------------------------------------------- void TimelineBar::DisplayCurrentFrame() { currlayer->algo->gotoframe(currlayer->currframe); // FitInView(0) would be less jerky but has the disadvantage that // scale won't change if a pattern shrinks when going backwards if (currlayer->autofit) viewptr->FitInView(1); mainptr->UpdatePatternAndStatus(); } // ----------------------------------------------------------------------------- void TimelineBar::OnScroll(wxScrollEvent& event) { WXTYPE type = event.GetEventType(); // stop autoplay if scroll bar is used if (currlayer->autoplay != 0) { currlayer->autoplay = 0; StopAutoTimer(); mainptr->UpdateUserInterface(); } if (type == wxEVT_SCROLL_LINEUP) { currlayer->currframe--; if (currlayer->currframe < 0) currlayer->currframe = 0; DisplayCurrentFrame(); } else if (type == wxEVT_SCROLL_LINEDOWN) { currlayer->currframe++; if (currlayer->currframe >= currlayer->algo->getframecount()) currlayer->currframe = currlayer->algo->getframecount() - 1; DisplayCurrentFrame(); } else if (type == wxEVT_SCROLL_PAGEUP) { currlayer->currframe -= PAGESIZE; if (currlayer->currframe < 0) currlayer->currframe = 0; DisplayCurrentFrame(); } else if (type == wxEVT_SCROLL_PAGEDOWN) { currlayer->currframe += PAGESIZE; if (currlayer->currframe >= currlayer->algo->getframecount()) currlayer->currframe = currlayer->algo->getframecount() - 1; DisplayCurrentFrame(); } else if (type == wxEVT_SCROLL_THUMBTRACK) { currlayer->currframe = event.GetPosition(); if (currlayer->currframe < 0) currlayer->currframe = 0; if (currlayer->currframe >= currlayer->algo->getframecount()) currlayer->currframe = currlayer->algo->getframecount() - 1; DisplayCurrentFrame(); } else if (type == wxEVT_SCROLL_THUMBRELEASE) { UpdateScrollBar(); } #ifndef __WXMAC__ viewptr->SetFocus(); // need on Win/Linux #endif } // ----------------------------------------------------------------------------- void TimelineBar::OnKillFocus(wxFocusEvent& event) { int id = event.GetId(); tlbutt[id]->SetFocus(); // don't let button lose focus } // ----------------------------------------------------------------------------- void TimelineBar::OnButtonDown(wxMouseEvent& event) { // timeline bar button has been pressed int id = event.GetId(); // connect a handler that keeps focus with the pressed button tlbutt[id]->Connect(id, wxEVT_KILL_FOCUS, wxFocusEventHandler(TimelineBar::OnKillFocus)); event.Skip(); } // ----------------------------------------------------------------------------- void TimelineBar::OnButtonUp(wxMouseEvent& event) { // timeline bar button has been released int id = event.GetId(); wxPoint pt = tlbutt[id]->ScreenToClient( wxGetMousePosition() ); int wd, ht; tlbutt[id]->GetClientSize(&wd, &ht); wxRect r(0, 0, wd, ht); // diconnect kill-focus handler tlbutt[id]->Disconnect(id, wxEVT_KILL_FOCUS, wxFocusEventHandler(TimelineBar::OnKillFocus)); viewptr->SetFocus(); if (r.Contains(pt)) { // call OnButton wxCommandEvent buttevt(wxEVT_COMMAND_BUTTON_CLICKED, id); buttevt.SetEventObject(tlbutt[id]); tlbutt[id]->GetEventHandler()->ProcessEvent(buttevt); } } // ----------------------------------------------------------------------------- void TimelineBar::AddButton(int id, const wxString& tip) { tlbutt[id] = new wxBitmapButton(this, id, normbutt[id], wxPoint(xpos,ypos), #if defined(__WXOSX_COCOA__) && wxCHECK_VERSION(3,0,0) wxSize(BUTTON_WD, BUTTON_HT), wxBORDER_SIMPLE #else wxSize(BUTTON_WD, BUTTON_HT) #endif ); if (tlbutt[id] == NULL) { Fatal(_("Failed to create timeline bar button!")); } else { xpos += BUTTON_WD + smallgap; tlbutt[id]->SetToolTip(tip); #ifdef __WXMSW__ // fix problem with timeline bar buttons when generating/inscript // due to focus being changed to viewptr tlbutt[id]->Connect(id, wxEVT_LEFT_DOWN, wxMouseEventHandler(TimelineBar::OnButtonDown)); tlbutt[id]->Connect(id, wxEVT_LEFT_UP, wxMouseEventHandler(TimelineBar::OnButtonUp)); #endif } } // ----------------------------------------------------------------------------- void TimelineBar::AddSeparator() { xpos += biggap - smallgap; } // ----------------------------------------------------------------------------- void TimelineBar::EnableButton(int id, bool enable) { if (enable == tlbutt[id]->IsEnabled()) return; #ifdef __WXMSW__ tlbutt[id]->SetBitmapDisabled(disnormbutt[id]); #endif tlbutt[id]->Enable(enable); } // ----------------------------------------------------------------------------- void TimelineBar::UpdateButtons() { if (currlayer->algo->isrecording()) { if (buttstate[RECORD_BUTT] != -1) { buttstate[RECORD_BUTT] = -1; tlbutt[RECORD_BUTT]->SetBitmapLabel(normbutt[STOPREC_BUTT]); tlbutt[RECORD_BUTT]->SetToolTip(_("Stop recording")); if (showtimeline) tlbutt[RECORD_BUTT]->Refresh(false); } } else { if (buttstate[RECORD_BUTT] != 1) { buttstate[RECORD_BUTT] = 1; tlbutt[RECORD_BUTT]->SetBitmapLabel(normbutt[RECORD_BUTT]); tlbutt[RECORD_BUTT]->SetToolTip(_("Start recording")); if (showtimeline) tlbutt[RECORD_BUTT]->Refresh(false); } } // these buttons are only shown if there is a timeline and we're not recording // (see DrawTimelineBar) if (TimelineExists() && !currlayer->algo->isrecording()) { if (currlayer->autoplay == 0) { if (buttstate[BACKWARDS_BUTT] != 1) { buttstate[BACKWARDS_BUTT] = 1; tlbutt[BACKWARDS_BUTT]->SetBitmapLabel(normbutt[BACKWARDS_BUTT]); tlbutt[BACKWARDS_BUTT]->SetToolTip(_("Play backwards")); if (showtimeline) tlbutt[BACKWARDS_BUTT]->Refresh(false); } if (buttstate[FORWARDS_BUTT] != 1) { buttstate[FORWARDS_BUTT] = 1; tlbutt[FORWARDS_BUTT]->SetBitmapLabel(normbutt[FORWARDS_BUTT]); tlbutt[FORWARDS_BUTT]->SetToolTip(_("Play forwards")); if (showtimeline) tlbutt[FORWARDS_BUTT]->Refresh(false); } } else if (currlayer->autoplay > 0) { if (buttstate[BACKWARDS_BUTT] != 1) { buttstate[BACKWARDS_BUTT] = 1; tlbutt[BACKWARDS_BUTT]->SetBitmapLabel(normbutt[BACKWARDS_BUTT]); tlbutt[BACKWARDS_BUTT]->SetToolTip(_("Play backwards")); if (showtimeline) tlbutt[BACKWARDS_BUTT]->Refresh(false); } if (buttstate[FORWARDS_BUTT] != -1) { buttstate[FORWARDS_BUTT] = -1; tlbutt[FORWARDS_BUTT]->SetBitmapLabel(normbutt[STOPPLAY_BUTT]); tlbutt[FORWARDS_BUTT]->SetToolTip(_("Stop playing")); if (showtimeline) tlbutt[FORWARDS_BUTT]->Refresh(false); } } else { // currlayer->autoplay < 0 if (buttstate[BACKWARDS_BUTT] != -1) { buttstate[BACKWARDS_BUTT] = -1; tlbutt[BACKWARDS_BUTT]->SetBitmapLabel(normbutt[STOPPLAY_BUTT]); tlbutt[BACKWARDS_BUTT]->SetToolTip(_("Stop playing")); if (showtimeline) tlbutt[BACKWARDS_BUTT]->Refresh(false); } if (buttstate[FORWARDS_BUTT] != 1) { buttstate[FORWARDS_BUTT] = 1; tlbutt[FORWARDS_BUTT]->SetBitmapLabel(normbutt[FORWARDS_BUTT]); tlbutt[FORWARDS_BUTT]->SetToolTip(_("Play forwards")); if (showtimeline) tlbutt[FORWARDS_BUTT]->Refresh(false); } } } } // ----------------------------------------------------------------------------- void TimelineBar::UpdateSlider() { slider->SetValue(currlayer->tlspeed); } // ----------------------------------------------------------------------------- void TimelineBar::UpdateScrollBar() { framebar->SetScrollbar(currlayer->currframe, 1, currlayer->algo->getframecount(), PAGESIZE, true); } // ----------------------------------------------------------------------------- void TimelineBar::OnAutoTimer(wxTimerEvent& WXUNUSED(event)) { if (currlayer->autoplay == 0) return; if (currlayer->algo->isrecording()) return; // assume currlayer->algo->getframecount() > 0 int frameinc = 1; if (currlayer->tlspeed > 0) { // skip 2^tlspeed frames frameinc = 1 << currlayer->tlspeed; } if (currlayer->autoplay > 0) { // play timeline forwards currlayer->currframe += frameinc; if (currlayer->currframe >= currlayer->algo->getframecount() - 1) { currlayer->currframe = currlayer->algo->getframecount() - 1; currlayer->autoplay = -1; // reverse direction when we hit last frame UpdateButtons(); } } else { // currlayer->autoplay < 0 so play timeline backwards currlayer->currframe -= frameinc; if (currlayer->currframe <= 0) { currlayer->currframe = 0; currlayer->autoplay = 1; // reverse direction when we hit first frame UpdateButtons(); } } DisplayCurrentFrame(); UpdateScrollBar(); } // ----------------------------------------------------------------------------- void TimelineBar::StartAutoTimer() { if (currlayer->autoplay == 0) return; int interval = SIXTY_HERTZ; // do ~60 calls of OnAutoTimer per sec // increase interval if user wants a delay between each frame if (currlayer->tlspeed < 0) { interval = 100 * (-currlayer->tlspeed); // if tlspeed is -1 then interval is 100; ie. call OnAutoTimer 10 times per sec } StopAutoTimer(); autotimer->Start(interval, wxTIMER_CONTINUOUS); } // ----------------------------------------------------------------------------- void TimelineBar::StopAutoTimer() { if (autotimer->IsRunning()) autotimer->Stop(); } // ----------------------------------------------------------------------------- void CreateTimelineBar(wxWindow* parent) { // create timeline bar underneath given window int wd, ht; parent->GetClientSize(&wd, &ht); tbarptr = new TimelineBar(parent, 0, ht - TBARHT, wd, TBARHT); if (tbarptr == NULL) Fatal(_("Failed to create timeline bar!")); tbarptr->Show(showtimeline); } // ----------------------------------------------------------------------------- int TimelineBarHeight() { return (showtimeline ? TBARHT : 0); } // ----------------------------------------------------------------------------- void UpdateTimelineBar() { if (tbarptr && showtimeline && !mainptr->IsIconized()) { bool active = !inscript; // may need to change bitmaps in some buttons tbarptr->UpdateButtons(); tbarptr->EnableButton(RECORD_BUTT, active && currlayer->algo->hyperCapable()); // note that slider, scroll bar and some buttons are only shown if there is // a timeline and we're not recording (see DrawTimelineBar) if (TimelineExists() && !currlayer->algo->isrecording()) { tbarptr->EnableButton(BACKWARDS_BUTT, active); tbarptr->EnableButton(FORWARDS_BUTT, active); tbarptr->EnableButton(DELETE_BUTT, active); tbarptr->UpdateSlider(); tbarptr->UpdateScrollBar(); } if (currlayer->algo->isrecording()) { // don't refresh RECORD_BUTT (otherwise button flickers on Windows) int wd, ht; tbarptr->GetClientSize(&wd, &ht); wxRect r(BUTTON_WD + tbarptr->smallgap * 2, 0, wd, ht); tbarptr->RefreshRect(r, false); } else { tbarptr->Refresh(false); } #ifdef __WXGTK__ // avoid bug that can cause buttons to lose their bitmaps tbarptr->Update(); #endif } } // ----------------------------------------------------------------------------- void ResizeTimelineBar(int y, int wd) { if (tbarptr && showtimeline) { tbarptr->SetSize(0, y, wd, TBARHT); // change width of scroll bar to nearly fill timeline bar wxRect r = tbarptr->framebar->GetRect(); r.width = wd - r.x - 20 - BUTTON_WD - 20; if (r.width < 0) r.width = 0; tbarptr->framebar->SetSize(r); // move DELETE_BUTT to right edge of timeline bar r = tlbutt[DELETE_BUTT]->GetRect(); r.x = wd - 20 - BUTTON_WD; if (r.x < mindelpos && TimelineExists()) r.x = mindelpos; tlbutt[DELETE_BUTT]->SetSize(r); } } // ----------------------------------------------------------------------------- void ToggleTimelineBar() { showtimeline = !showtimeline; mainptr->ResizeBigView(); tbarptr->Show(showtimeline); // needed on Windows mainptr->UpdateEverything(); } // ----------------------------------------------------------------------------- void StartStopRecording() { if (!inscript && currlayer->algo->hyperCapable()) { if (currlayer->algo->isrecording()) { mainptr->Stop(); // StopGenerating() has called currlayer->algo->stoprecording() } else { tbarptr->StopAutoTimer(); if (currlayer->algo->isEmpty()) { statusptr->ErrorMessage(_("There is no pattern to record.")); return; } if (!showtimeline) ToggleTimelineBar(); if (currlayer->algo->getframecount() == MAX_FRAME_COUNT) { wxString msg; msg.Printf(_("The timeline can't be extended any further (max frames = %d)."), MAX_FRAME_COUNT); statusptr->ErrorMessage(msg); return; } // record a new timeline, or extend the existing one if (currlayer->algo->startrecording(currlayer->currbase, currlayer->currexpo) > 0) { if (currlayer->algo->getGeneration() == currlayer->startgen) { // ensure the SaveStartingPattern call in DeleteTimeline will // create a new temporary .mc file (with only one frame) currlayer->savestart = true; } mainptr->StartGenerating(); } else { // this should never happen Warning(_("Bug: could not start recording!")); } } } } // ----------------------------------------------------------------------------- void DeleteTimeline() { if (!inscript && TimelineExists() && !currlayer->algo->isrecording()) { tbarptr->StopAutoTimer(); if (currlayer->currframe > 0) { // tell writeNativeFormat to only save the current frame // so that the temporary .mc files created by SaveStartingPattern and // RememberGenStart/Finish won't store the entire timeline currlayer->algo->savetimelinewithframe(0); // do stuff so user can select Reset/Undo to go back to 1st frame currlayer->algo->gotoframe(0); if (currlayer->autofit) viewptr->FitInView(1); if (currlayer->algo->getGeneration() == currlayer->startgen) { mainptr->SaveStartingPattern(); } if (allowundo) currlayer->undoredo->RememberGenStart(); // return to the current frame currlayer->algo->gotoframe(currlayer->currframe); if (currlayer->autofit) viewptr->FitInView(1); if (allowundo) currlayer->undoredo->RememberGenFinish(); // restore flag that tells writeNativeFormat to save entire timeline currlayer->algo->savetimelinewithframe(1); } currlayer->algo->destroytimeline(); mainptr->UpdateUserInterface(); } } // ----------------------------------------------------------------------------- void InitTimelineFrame() { // the user has just loaded a .mc file with a timeline, // so prepare to display the first frame currlayer->algo->gotoframe(0); currlayer->currframe = 0; currlayer->autoplay = 0; currlayer->tlspeed = 0; tbarptr->StopAutoTimer(); // first frame is starting gen (needed for DeleteTimeline) currlayer->startgen = currlayer->algo->getGeneration(); // ensure SaveStartingPattern call in DeleteTimeline will create // a new temporary .mc file with one frame currlayer->savestart = true; } // ----------------------------------------------------------------------------- bool TimelineExists() { return currlayer->algo->getframecount() > 0; } // ----------------------------------------------------------------------------- void PlayTimeline(int direction) { if (currlayer->algo->isrecording()) return; if (direction > 0 && currlayer->autoplay > 0) { currlayer->autoplay = 0; } else if (direction < 0 && currlayer->autoplay < 0) { currlayer->autoplay = 0; } else { currlayer->autoplay = direction; } if (currlayer->autoplay == 0) { tbarptr->StopAutoTimer(); } else { tbarptr->StartAutoTimer(); } mainptr->UpdateUserInterface(); } // ----------------------------------------------------------------------------- void PlayTimelineFaster() { if (currlayer->algo->isrecording()) return; if (currlayer->tlspeed < MAXSPEED) { currlayer->tlspeed++; if (showtimeline) tbarptr->UpdateSlider(); tbarptr->StartAutoTimer(); } } // ----------------------------------------------------------------------------- void PlayTimelineSlower() { if (currlayer->algo->isrecording()) return; if (currlayer->tlspeed > MINSPEED) { currlayer->tlspeed--; if (showtimeline) tbarptr->UpdateSlider(); tbarptr->StartAutoTimer(); } } // ----------------------------------------------------------------------------- void ResetTimelineSpeed() { if (currlayer->algo->isrecording()) return; currlayer->tlspeed = 0; if (showtimeline) tbarptr->UpdateSlider(); tbarptr->StartAutoTimer(); } // ----------------------------------------------------------------------------- bool TimelineIsPlaying() { return currlayer->autoplay != 0; }