Add basic timeline window.
authorCarl Hetherington <cth@carlh.net>
Mon, 6 May 2013 13:10:14 +0000 (14:10 +0100)
committerCarl Hetherington <cth@carlh.net>
Mon, 6 May 2013 13:10:14 +0000 (14:10 +0100)
17 files changed:
src/lib/audio_content.cc
src/lib/audio_content.h
src/lib/content.h
src/lib/ffmpeg_content.cc
src/lib/ffmpeg_content.h
src/lib/film.cc
src/lib/film.h
src/lib/types.h
src/lib/video_content.cc
src/lib/video_content.h
src/wx/film_editor.cc
src/wx/film_editor.h
src/wx/timeline.cc [new file with mode: 0644]
src/wx/timeline.h [new file with mode: 0644]
src/wx/timeline_dialog.cc [new file with mode: 0644]
src/wx/timeline_dialog.h [new file with mode: 0644]
src/wx/wscript

index 9968f472561121e833f09392302ede079981575f..dfa48d97ee127e95ad685006f01bbe489c3f5322 100644 (file)
@@ -43,3 +43,9 @@ AudioContent::AudioContent (AudioContent const & o)
 {
 
 }
+
+Time
+AudioContent::temporal_length () const
+{
+       return audio_length() / audio_frame_rate();
+}
index 2362786d951cc8c25eebcf23b958a5db3ccca88d..18107843c0cd45b30b32e7d6acb1ced16ec2fa08 100644 (file)
@@ -45,6 +45,8 @@ public:
         virtual int audio_channels () const = 0;
         virtual ContentAudioFrame audio_length () const = 0;
         virtual int audio_frame_rate () const = 0;
+
+       Time temporal_length () const;
 };
 
 #endif
index d39fc9e1ac99d3ca1de4ebd2ad4ab6b475567f5a..e1cf41df09a34096be0b794d9b2c16fe5bf09041 100644 (file)
@@ -25,6 +25,7 @@
 #include <boost/thread/mutex.hpp>
 #include <boost/enable_shared_from_this.hpp>
 #include <libxml++/libxml++.h>
+#include "types.h"
 
 namespace cxml {
        class Node;
@@ -45,6 +46,7 @@ public:
        virtual std::string information () const = 0;
        virtual void as_xml (xmlpp::Node *) const;
        virtual boost::shared_ptr<Content> clone () const = 0;
+       virtual Time temporal_length () const = 0;
        
        boost::filesystem::path file () const {
                boost::mutex::scoped_lock lm (_mutex);
index 719c4cb53ecf03488d8266954b6309205657bb5e..a61f777c85d48a26db912d24c51e956dd0b191d4 100644 (file)
@@ -280,3 +280,9 @@ FFmpegContent::clone () const
 {
        return shared_ptr<Content> (new FFmpegContent (*this));
 }
+
+double
+FFmpegContent::temporal_length () const
+{
+       return video_length() / video_frame_rate();
+}
index 8bf4d42a539aa07c2cd93793c9b27a4b5b9621cf..6d7151498a9e6b3c3b12a6b2875b559522fc09fc 100644 (file)
@@ -89,6 +89,7 @@ public:
        std::string information () const;
        void as_xml (xmlpp::Node *) const;
        boost::shared_ptr<Content> clone () const;
+       double temporal_length () const;
 
         /* AudioContent */
         int audio_channels () const;
index 2dc97c1b3fbd9d6e3579d3e71c4939e02e579de2..a385625e7cdc2098b7c355868af96ed3e3f47a64 100644 (file)
@@ -1075,6 +1075,13 @@ Film::player () const
        return shared_ptr<Player> (new Player (shared_from_this (), _playlist));
 }
 
+shared_ptr<Playlist>
+Film::playlist () const
+{
+       boost::mutex::scoped_lock lm (_state_mutex);
+       return _playlist;
+}
+
 ContentList
 Film::content () const
 {
index 8748e18f55aa9b3aff8a14fe38262773c9a96d94..e2f9b101a4671d2e24276d2d03d7c593736dd16d 100644 (file)
@@ -103,6 +103,7 @@ public:
        bool have_dcp () const;
 
        boost::shared_ptr<Player> player () const;
+       boost::shared_ptr<Playlist> playlist () const;
 
        /* Proxies for some Playlist methods */
 
index c2bb9d8531d2ee1f8c5761ebd2f1827119a5d372..f9e9b2f4b6668e62328014a19da6ba8eb9cf07f8 100644 (file)
@@ -29,6 +29,7 @@ class Content;
 typedef std::vector<boost::shared_ptr<Content> > ContentList;
 typedef int64_t ContentAudioFrame;
 typedef int ContentVideoFrame;
+typedef double Time;
 
 /** @struct Crop
  *  @brief A description of the crop of an image or video.
index 9fb2b9bce8f3a4c12860ee6bae4ab00e8b2ef59f..2af6ba908c68eb7366e99307f40dcf5cafb21406 100644 (file)
@@ -104,3 +104,9 @@ VideoContent::information () const
        
        return s.str ();
 }
+
+Time
+VideoContent::temporal_length () const
+{
+       return video_length() / video_frame_rate();
+}
index 75e507d4d799a7cfc24be6224bdaca26099c7eed..b2ec87e2b287f35cd40ec18504d4ca3f0a5e5c5a 100644 (file)
@@ -42,6 +42,7 @@ public:
 
        void as_xml (xmlpp::Node *) const;
        virtual std::string information () const;
+       Time temporal_length () const;
 
        ContentVideoFrame video_length () const {
                boost::mutex::scoped_lock lm (_mutex);
index db3e03d78f2f6ee72b3ec93cd88a4a445dfa53b1..c50782452ce0af3393e6e3dd9a7cf1e0280a572e 100644 (file)
@@ -52,6 +52,7 @@
 #include "imagemagick_content_dialog.h"
 #include "ffmpeg_content_dialog.h"
 #include "audio_mapping_view.h"
+#include "timeline_dialog.h"
 
 using std::string;
 using std::cout;
@@ -215,6 +216,7 @@ FilmEditor::connect_to_widgets ()
        _content_edit->Connect           (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED,       wxCommandEventHandler (FilmEditor::content_edit_clicked), 0, this);
        _content_earlier->Connect        (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED,       wxCommandEventHandler (FilmEditor::content_earlier_clicked), 0, this);
        _content_later->Connect          (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED,       wxCommandEventHandler (FilmEditor::content_later_clicked), 0, this);
+       _timeline_button->Connect        (wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED,       wxCommandEventHandler (FilmEditor::timeline_clicked), 0, this);
        _loop_content->Connect           (wxID_ANY, wxEVT_COMMAND_CHECKBOX_CLICKED,     wxCommandEventHandler (FilmEditor::loop_content_toggled), 0, this);
        _loop_count->Connect             (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED,     wxCommandEventHandler (FilmEditor::loop_count_changed), 0, this);
        _left_crop->Connect              (wxID_ANY, wxEVT_COMMAND_SPINCTRL_UPDATED,     wxCommandEventHandler (FilmEditor::left_crop_changed), 0, this);
@@ -388,6 +390,9 @@ FilmEditor::make_content_panel ()
        font.SetPointSize(font.GetPointSize() - 1);
        _playlist_description->SetFont(font);
 
+       _timeline_button = new wxButton (_content_panel, wxID_ANY, _("Timeline..."));
+       _content_sizer->Add (_timeline_button, 0, wxALL, 6);
+
        _loop_count->SetRange (2, 1024);
 }
 
@@ -1449,3 +1454,11 @@ FilmEditor::setup_playlist_description ()
 
        _playlist_description->SetLabel (std_to_wx (_film->playlist_description ()));
 }
+
+void
+FilmEditor::timeline_clicked (wxCommandEvent &)
+{
+       TimelineDialog* d = new TimelineDialog (this, _film->playlist ());
+       d->ShowModal ();
+       d->Destroy ();
+}
index baaeb46d7101ea5ebcf0bef41957a53d93a06958..fddd213b988e069db08f815ae81ec0d5b39bca4a 100644 (file)
@@ -93,6 +93,7 @@ private:
        void edit_filters_clicked (wxCommandEvent &);
        void loop_content_toggled (wxCommandEvent &);
        void loop_count_changed (wxCommandEvent &);
+       void timeline_clicked (wxCommandEvent &);
 
        /* Handle changes to the model */
        void film_changed (Film::Property);
@@ -145,6 +146,7 @@ private:
        wxTextCtrl* _content_information;
        wxCheckBox* _loop_content;
        wxSpinCtrl* _loop_count;
+       wxButton* _timeline_button;
        wxStaticText* _playlist_description;
        wxButton* _edit_dci_button;
        wxChoice* _format;
diff --git a/src/wx/timeline.cc b/src/wx/timeline.cc
new file mode 100644 (file)
index 0000000..8287563
--- /dev/null
@@ -0,0 +1,191 @@
+/*
+    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <list>
+#include <wx/graphics.h>
+#include "timeline.h"
+#include "wx_util.h"
+#include "playlist.h"
+
+using std::list;
+using std::cout;
+using std::max;
+using boost::shared_ptr;
+
+int const Timeline::_track_height = 64;
+
+Timeline::Timeline (wxWindow* parent, shared_ptr<Playlist> pl)
+       : wxPanel (parent)
+       , _playlist (pl)
+{
+       SetDoubleBuffered (true);
+       
+       Connect (wxID_ANY, wxEVT_PAINT, wxPaintEventHandler (Timeline::paint), 0, this);
+
+       if (pl->audio_from() == Playlist::AUDIO_FFMPEG) {
+               SetMinSize (wxSize (640, _track_height * 2 + 96));
+       } else {
+               SetMinSize (wxSize (640, _track_height * (max (1UL, pl->audio().size()) + 1) + 96));
+       }
+}
+
+template <class T>
+int
+plot_content_list (
+       list<shared_ptr<const T> > content, wxGraphicsContext* gc, int x, int y, double pixels_per_second, int track_height, wxString type, bool consecutive
+       )
+{
+       Time t = 0;
+       for (typename list<shared_ptr<const T> >::iterator i = content.begin(); i != content.end(); ++i) {
+               Time const len = (*i)->temporal_length ();
+               wxGraphicsPath path = gc->CreatePath ();
+               path.MoveToPoint (x + t * pixels_per_second, y);
+               path.AddLineToPoint (x + (t + len) * pixels_per_second, y);
+               path.AddLineToPoint (x + (t + len) * pixels_per_second, y + track_height);
+               path.AddLineToPoint (x + t * pixels_per_second, y + track_height);
+               path.AddLineToPoint (x + t * pixels_per_second, y);
+               gc->StrokePath (path);
+               gc->FillPath (path);
+
+               wxString name = wxString::Format ("%s [%s]", std_to_wx ((*i)->file().filename().string()), type);
+               wxDouble name_width;
+               wxDouble name_height;
+               wxDouble name_descent;
+               wxDouble name_leading;
+               gc->GetTextExtent (name, &name_width, &name_height, &name_descent, &name_leading);
+               
+               gc->Clip (wxRegion (x + t * pixels_per_second, y, len * pixels_per_second, track_height));
+               gc->DrawText (name, t * pixels_per_second + 12, y + track_height - name_height - 4);
+               gc->ResetClip ();
+
+               if (consecutive) {
+                       t += len;
+               } else {
+                       y += track_height;
+               }
+       }
+
+       if (consecutive) {
+               y += track_height;
+       }
+
+       return y;
+}
+
+void
+Timeline::paint (wxPaintEvent &)
+{
+       wxPaintDC dc (this);
+
+       shared_ptr<Playlist> pl = _playlist.lock ();
+       if (!pl) {
+               return;
+       }
+
+       wxGraphicsContext* gc = wxGraphicsContext::Create (dc);
+       if (!gc) {
+               return;
+       }
+
+       int const x_offset = 8;
+       int y = 8;
+       int const width = GetSize().GetWidth();
+       double const pixels_per_second = (width - x_offset * 2) / (pl->content_length() / pl->video_frame_rate());
+
+       gc->SetFont (gc->CreateFont (*wxNORMAL_FONT));
+
+       gc->SetPen (*wxBLACK_PEN);
+#if wxMAJOR_VERSION == 2 && wxMINOR_VERSION >= 9
+       gc->SetPen (*wxThePenList->FindOrCreatePen (wxColour (0, 0, 0), 4, wxPENSTYLE_SOLID));
+       gc->SetBrush (*wxTheBrushList->FindOrCreateBrush (wxColour (149, 121, 232, 255), wxBRUSHSTYLE_SOLID));
+#else                  
+       gc->SetPen (*wxThePenList->FindOrCreatePen (wxColour (0, 0, 0), 4, SOLID));
+       gc->SetBrush (*wxTheBrushList->FindOrCreateBrush (wxColour (149, 121, 232, 255), SOLID));
+#endif
+       y = plot_content_list (pl->video (), gc, x_offset, y, pixels_per_second, _track_height, _("video"), true);
+       
+#if wxMAJOR_VERSION == 2 && wxMINOR_VERSION >= 9
+       gc->SetBrush (*wxTheBrushList->FindOrCreateBrush (wxColour (242, 92, 120, 255), wxBRUSHSTYLE_SOLID));
+#else                  
+       gc->SetBrush (*wxTheBrushList->FindOrCreateBrush (wxColour (242, 92, 120, 255), SOLID));
+#endif
+       y = plot_content_list (pl->audio (), gc, x_offset, y, pixels_per_second, _track_height, _("audio"), pl->audio_from() == Playlist::AUDIO_FFMPEG);
+
+       /* Time axis */
+
+#if wxMAJOR_VERSION == 2 && wxMINOR_VERSION >= 9
+        gc->SetPen (*wxThePenList->FindOrCreatePen (wxColour (0, 0, 0), 1, wxPENSTYLE_SOLID));
+#else              
+       gc->SetPen (*wxThePenList->FindOrCreatePen (wxColour (0, 0, 0), 1, SOLID));
+#endif             
+                   
+       int mark_interval = rint (128 / pixels_per_second);
+        if (mark_interval > 5) {
+               mark_interval -= mark_interval % 5;
+       }
+        if (mark_interval > 10) {
+               mark_interval -= mark_interval % 10;
+       }
+       if (mark_interval > 60) {
+               mark_interval -= mark_interval % 60;
+       }
+       if (mark_interval > 3600) {
+               mark_interval -= mark_interval % 3600;
+       }
+
+        if (mark_interval < 1) {
+               mark_interval = 1;
+        }
+
+       wxGraphicsPath path = gc->CreatePath ();
+       path.MoveToPoint (x_offset, y + 40);
+       path.AddLineToPoint (width, y + 40);
+       gc->StrokePath (path);
+
+       double t = 0;
+       while ((t * pixels_per_second) < width) {
+               wxGraphicsPath path = gc->CreatePath ();
+               path.MoveToPoint (x_offset + t * pixels_per_second, y + 36);
+               path.AddLineToPoint (x_offset + t * pixels_per_second, y + 44);
+               gc->StrokePath (path);
+
+               int tc = t;
+               int const h = tc / 3600;
+               tc -= h * 3600;
+               int const m = tc / 60;
+               tc -= m * 60;
+               int const s = tc;
+
+               wxString str = wxString::Format ("%02d:%02d:%02d", h, m, s);
+               wxDouble str_width;
+               wxDouble str_height;
+               wxDouble str_descent;
+               wxDouble str_leading;
+               gc->GetTextExtent (str, &str_width, &str_height, &str_descent, &str_leading);
+
+               int const tx = x_offset + t * pixels_per_second;
+               if ((tx + str_width) < width) {
+                       gc->DrawText (str, x_offset + t * pixels_per_second, y + 60);
+               }
+               t += mark_interval;
+       }
+
+       delete gc;
+}
+
diff --git a/src/wx/timeline.h b/src/wx/timeline.h
new file mode 100644 (file)
index 0000000..48aebc6
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <boost/shared_ptr.hpp>
+#include <boost/weak_ptr.hpp>
+#include <wx/wx.h>
+
+class Playlist;
+
+class Timeline : public wxPanel
+{
+public:
+       Timeline (wxWindow *, boost::shared_ptr<Playlist>);
+
+private:
+       void paint (wxPaintEvent &);
+
+       static int const _track_height;
+       
+       boost::weak_ptr<Playlist> _playlist;
+};
diff --git a/src/wx/timeline_dialog.cc b/src/wx/timeline_dialog.cc
new file mode 100644 (file)
index 0000000..5633c29
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <list>
+#include <wx/graphics.h>
+#include "timeline_dialog.h"
+#include "wx_util.h"
+#include "playlist.h"
+
+using std::list;
+using std::cout;
+using boost::shared_ptr;
+
+TimelineDialog::TimelineDialog (wxWindow* parent, shared_ptr<Playlist> pl)
+       : wxDialog (parent, wxID_ANY, _("Timeline"), wxDefaultPosition, wxSize (640, 512), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
+       , _timeline (this, pl)
+{
+       wxBoxSizer* sizer = new wxBoxSizer (wxVERTICAL);
+       
+       sizer->Add (&_timeline, 1, wxEXPAND | wxALL, 12);
+
+       SetSizer (sizer);
+       sizer->Layout ();
+       sizer->SetSizeHints (this);
+}
diff --git a/src/wx/timeline_dialog.h b/src/wx/timeline_dialog.h
new file mode 100644 (file)
index 0000000..e58de55
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+    Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <boost/shared_ptr.hpp>
+#include <boost/weak_ptr.hpp>
+#include <wx/wx.h>
+#include "timeline.h"
+
+class Playlist;
+
+class TimelineDialog : public wxDialog
+{
+public:
+       TimelineDialog (wxWindow *, boost::shared_ptr<Playlist>);
+
+private:
+       Timeline _timeline;
+};
index 001e8469eeb24917f407e54281cdff96c0cf7839..c63ef128dba643967d62c5e5eab9f8290a4ce926 100644 (file)
@@ -22,6 +22,8 @@ sources = """
           new_film_dialog.cc
           properties_dialog.cc
           server_dialog.cc
+          timeline.cc
+          timeline_dialog.cc
           wx_util.cc
           wx_ui_signaller.cc
           """