Add unfinished timing tab; fix crash on reconstruction of timeline; fix lack of black...
authorCarl Hetherington <cth@carlh.net>
Sat, 25 May 2013 23:21:53 +0000 (00:21 +0100)
committerCarl Hetherington <cth@carlh.net>
Sat, 25 May 2013 23:21:53 +0000 (00:21 +0100)
src/lib/content.cc
src/lib/player.cc
src/lib/player.h
src/lib/sndfile_content.cc
src/wx/film_editor.cc
src/wx/film_editor.h
src/wx/timecode.cc [new file with mode: 0644]
src/wx/timecode.h [new file with mode: 0644]
src/wx/timeline.cc
src/wx/timeline.h
src/wx/wscript

index ad61f4d6c04fa84c8de2afbd087f2d70579ccb06..aaf2e4f9c3341424581582eaad4921a8a4e06a8e 100644 (file)
@@ -1,5 +1,3 @@
-/* -*- c-basic-offset: 8; default-tab-width: 8; -*- */
-
 /*
     Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
 
index ff13f95dbd2197be7be681ff520b29ab1ef4422b..786180a6a5f27d6e50331a1530a43b70e253f22f 100644 (file)
@@ -1,5 +1,3 @@
-/* -*- c-basic-offset: 8; default-tab-width: 8; -*- */
-
 /*
     Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
 
@@ -103,18 +101,18 @@ Player::pass ()
         shared_ptr<Piece> earliest;
 
        for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
-               cout << "check " << (*i)->content->file() << " start=" << (*i)->content->start() << ", next=" << (*i)->decoder->next() << ", end=" << (*i)->content->end() << "\n";
+//             cout << "check " << (*i)->content->file() << " start=" << (*i)->content->start() << ", next=" << (*i)->decoder->next() << ", end=" << (*i)->content->end() << "\n";
                if (((*i)->decoder->next() + (*i)->content->start()) >= (*i)->content->end()) {
                        continue;
                }
 
-               if (!_audio && dynamic_pointer_cast<SndfileContent> ((*i)->content)) {
+               if (!_audio && dynamic_pointer_cast<AudioDecoder> ((*i)->decoder) && !dynamic_pointer_cast<VideoDecoder> ((*i)->decoder)) {
                        continue;
                }
                
                Time const t = (*i)->content->start() + (*i)->decoder->next();
                if (t < earliest_t) {
-                       cout << "\t candidate; " << t << " " << (t / TIME_HZ) << ".\n";
+//                     cout << "\t candidate; " << t << " " << (t / TIME_HZ) << ".\n";
                        earliest_t = t;
                        earliest = *i;
                }
@@ -148,8 +146,6 @@ Player::pass ()
 void
 Player::process_video (weak_ptr<Content> weak_content, shared_ptr<const Image> image, bool same, shared_ptr<Subtitle> sub, Time time)
 {
-       cout << "[V]\n";
-       
        shared_ptr<Content> content = weak_content.lock ();
        if (!content) {
                return;
@@ -208,13 +204,13 @@ Player::seek (Time t)
                return;
        }
 
-       cout << "seek to " << t << " " << (t / TIME_HZ) << "\n";
+//     cout << "seek to " << t << " " << (t / TIME_HZ) << "\n";
 
        for (list<shared_ptr<Piece> >::iterator i = _pieces.begin(); i != _pieces.end(); ++i) {
                Time s = t - (*i)->content->start ();
                s = max (static_cast<Time> (0), s);
                s = min ((*i)->content->length(), s);
-               cout << "seek [" << (*i)->content->file() << "," << (*i)->content->start() << "," << (*i)->content->end() << "] to " << s << "\n";
+//             cout << "seek [" << (*i)->content->file() << "," << (*i)->content->start() << "," << (*i)->content->end() << "] to " << s << "\n";
                (*i)->decoder->seek (s);
        }
 
@@ -234,6 +230,27 @@ Player::seek_forward ()
 
 }
 
+void
+Player::add_black_piece (Time s, Time len)
+{
+       shared_ptr<NullContent> nc (new NullContent (_film, s, len));
+       shared_ptr<BlackDecoder> bd (new BlackDecoder (_film, nc));
+       bd->Video.connect (bind (&Player::process_video, this, nc, _1, _2, _3, _4));
+       _pieces.push_back (shared_ptr<Piece> (new Piece (nc, bd)));
+       cout << "\tblack @ " << s << " -- " << (s + len) << "\n";
+}
+
+void
+Player::add_silent_piece (Time s, Time len)
+{
+       shared_ptr<NullContent> nc (new NullContent (_film, s, len));
+       shared_ptr<SilenceDecoder> sd (new SilenceDecoder (_film, nc));
+       sd->Audio.connect (bind (&Player::process_audio, this, nc, _1, _2));
+       _pieces.push_back (shared_ptr<Piece> (new Piece (nc, sd)));
+       cout << "\tsilence @ " << s << " -- " << (s + len) << "\n";
+}
+
+
 struct ContentSorter
 {
        bool operator() (shared_ptr<Content> a, shared_ptr<Content> b)
@@ -245,7 +262,7 @@ struct ContentSorter
 void
 Player::setup_pieces ()
 {
-//     cout << "----- Player SETUP PIECES.\n";
+       cout << "----- Player SETUP PIECES.\n";
 
        list<shared_ptr<Piece> > old_pieces = _pieces;
 
@@ -268,7 +285,7 @@ Player::setup_pieces ()
                        fd->Audio.connect (bind (&Player::process_audio, this, *i, _1, _2));
 
                        decoder = fd;
-//                     cout << "\tFFmpeg @ " << fc->start() << " -- " << fc->end() << "\n";
+                       cout << "\tFFmpeg @ " << fc->start() << " -- " << fc->end() << "\n";
                }
                
                shared_ptr<const ImageMagickContent> ic = dynamic_pointer_cast<const ImageMagickContent> (*i);
@@ -289,7 +306,7 @@ Player::setup_pieces ()
                        }
 
                        decoder = id;
-//                     cout << "\tImageMagick @ " << ic->start() << " -- " << ic->end() << "\n";
+                       cout << "\tImageMagick @ " << ic->start() << " -- " << ic->end() << "\n";
                }
 
                shared_ptr<const SndfileContent> sc = dynamic_pointer_cast<const SndfileContent> (*i);
@@ -298,7 +315,7 @@ Player::setup_pieces ()
                        sd->Audio.connect (bind (&Player::process_audio, this, *i, _1, _2));
 
                        decoder = sd;
-//                     cout << "\tSndfile @ " << sc->start() << " -- " << sc->end() << "\n";
+                       cout << "\tSndfile @ " << sc->start() << " -- " << sc->end() << "\n";
                }
 
                _pieces.push_back (shared_ptr<Piece> (new Piece (*i, decoder)));
@@ -313,26 +330,24 @@ Player::setup_pieces ()
                if (dynamic_pointer_cast<VideoContent> ((*i)->content)) {
                        Time const diff = (*i)->content->start() - video_pos;
                        if (diff > 0) {
-                               shared_ptr<NullContent> nc (new NullContent (_film, video_pos, diff));
-                               shared_ptr<BlackDecoder> bd (new BlackDecoder (_film, nc));
-                               bd->Video.connect (bind (&Player::process_video, this, nc, _1, _2, _3, _4));
-                               _pieces.push_back (shared_ptr<Piece> (new Piece (nc, bd)));
-//                             cout << "\tblack @ " << video_pos << " -- " << (video_pos + diff) << "\n";
+                               add_black_piece (video_pos, diff);
                        }
                                                
                        video_pos = (*i)->content->end();
                } else {
                        Time const diff = (*i)->content->start() - audio_pos;
                        if (diff > 0) {
-                               shared_ptr<NullContent> nc (new NullContent (_film, audio_pos, diff));
-                               shared_ptr<SilenceDecoder> sd (new SilenceDecoder (_film, nc));
-                               sd->Audio.connect (bind (&Player::process_audio, this, nc, _1, _2));
-                               _pieces.push_back (shared_ptr<Piece> (new Piece (nc, sd)));
-//                             cout << "\tsilence @ " << audio_pos << " -- " << (audio_pos + diff) << "\n";
+                               add_silent_piece (video_pos, diff);
                        }
                        audio_pos = (*i)->content->end();
                }
        }
+
+       if (video_pos < audio_pos) {
+               add_black_piece (video_pos, audio_pos - video_pos);
+       } else if (audio_pos < video_pos) {
+               add_silent_piece (audio_pos, video_pos - audio_pos);
+       }
 }
 
 void
index 4d23d1951ab53b9256b4e471bc9080f93229ba07..cdedf1676eca8d46be8745e29a5aac332d4a8d33 100644 (file)
@@ -68,6 +68,8 @@ private:
        void playlist_changed ();
        void content_changed (boost::weak_ptr<Content>, int);
        void do_seek (Time, bool);
+       void add_black_piece (Time, Time);
+       void add_silent_piece (Time, Time);
 
        boost::shared_ptr<const Film> _film;
        boost::shared_ptr<const Playlist> _playlist;
@@ -79,8 +81,6 @@ private:
        /** Our pieces are ready to go; if this is false the pieces must be (re-)created before they are used */
        bool _have_valid_pieces;
        std::list<boost::shared_ptr<Piece> > _pieces;
-
-       /** Time of the earliest thing not yet to have been emitted */
        Time _position;
        AudioBuffers _audio_buffers;
        Time _next_audio;
index 0f895c95e109b28ddd57b7007e3d6264fd33cd32..a80c7dbe511cb50de9032b813ef3444324eb4dc7 100644 (file)
@@ -120,6 +120,7 @@ SndfileContent::as_xml (xmlpp::Node* node) const
 {
        node->add_child("Type")->add_child_text ("Sndfile");
        Content::as_xml (node);
+       AudioContent::as_xml (node);
        node->add_child("AudioChannels")->add_child_text (lexical_cast<string> (_audio_channels));
        node->add_child("AudioLength")->add_child_text (lexical_cast<string> (_audio_length));
        node->add_child("AudioFrameRate")->add_child_text (lexical_cast<string> (_audio_frame_rate));
index 36d63b8055a4b7de144c8facdc1ac40f5d37cc8a..b67b47b4e10edfd9287e54c3db4f0bb65600ad87 100644 (file)
@@ -1,5 +1,3 @@
-/* -*- c-basic-offset: 8; default-tab-width: 8; -*- */
-
 /*
     Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
 
@@ -55,6 +53,7 @@
 #include "timeline_dialog.h"
 #include "audio_mapping_view.h"
 #include "container.h"
+#include "timecode.h"
 
 using std::string;
 using std::cout;
@@ -230,11 +229,11 @@ void
 FilmEditor::make_video_panel ()
 {
        _video_panel = new wxPanel (_content_notebook);
-       _video_sizer = new wxBoxSizer (wxVERTICAL);
-       _video_panel->SetSizer (_video_sizer);
+       wxBoxSizer* video_sizer = new wxBoxSizer (wxVERTICAL);
+       _video_panel->SetSizer (video_sizer);
        
        wxGridBagSizer* grid = new wxGridBagSizer (4, 4);
-       _video_sizer->Add (grid, 0, wxALL, 8);
+       video_sizer->Add (grid, 0, wxALL, 8);
 
        int r = 0;
        add_label_to_grid_bag_sizer (grid, _video_panel, _("Left crop"), wxGBPosition (r, 0));
@@ -336,13 +335,15 @@ FilmEditor::make_content_panel ()
 
        _content_notebook = new wxNotebook (_content_panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_LEFT);
        _content_sizer->Add (_content_notebook, 1, wxEXPAND | wxTOP, 6);
-       
+
        make_video_panel ();
        _content_notebook->AddPage (_video_panel, _("Video"), false);
        make_audio_panel ();
        _content_notebook->AddPage (_audio_panel, _("Audio"), false);
        make_subtitle_panel ();
        _content_notebook->AddPage (_subtitle_panel, _("Subtitles"), false);
+       make_timing_panel ();
+       _content_notebook->AddPage (_timing_panel, _("Timing"), false);
 
        _loop_count->SetRange (2, 1024);
 }
@@ -351,11 +352,11 @@ void
 FilmEditor::make_audio_panel ()
 {
        _audio_panel = new wxPanel (_content_notebook);
-       _audio_sizer = new wxBoxSizer (wxVERTICAL);
-       _audio_panel->SetSizer (_audio_sizer);
+       wxBoxSizer* audio_sizer = new wxBoxSizer (wxVERTICAL);
+       _audio_panel->SetSizer (audio_sizer);
        
        wxFlexGridSizer* grid = new wxFlexGridSizer (3, 4, 4);
-       _audio_sizer->Add (grid, 0, wxALL, 8);
+       audio_sizer->Add (grid, 0, wxALL, 8);
 
        _show_audio = new wxButton (_audio_panel, wxID_ANY, _("Show Audio..."));
        grid->Add (_show_audio, 1);
@@ -397,7 +398,7 @@ FilmEditor::make_audio_panel ()
        grid->AddSpacer (0);
        
        _audio_mapping = new AudioMappingView (_audio_panel);
-       _audio_sizer->Add (_audio_mapping, 1, wxEXPAND | wxALL, 6);
+       audio_sizer->Add (_audio_mapping, 1, wxEXPAND | wxALL, 6);
 
        _audio_gain->SetRange (-60, 60);
        _audio_delay->SetRange (-1000, 1000);
@@ -407,10 +408,10 @@ void
 FilmEditor::make_subtitle_panel ()
 {
        _subtitle_panel = new wxPanel (_content_notebook);
-       _subtitle_sizer = new wxBoxSizer (wxVERTICAL);
-       _subtitle_panel->SetSizer (_subtitle_sizer);
+       wxBoxSizer* subtitle_sizer = new wxBoxSizer (wxVERTICAL);
+       _subtitle_panel->SetSizer (subtitle_sizer);
        wxFlexGridSizer* grid = new wxFlexGridSizer (2, 4, 4);
-       _subtitle_sizer->Add (grid, 0, wxALL, 8);
+       subtitle_sizer->Add (grid, 0, wxALL, 8);
 
        _with_subtitles = new wxCheckBox (_subtitle_panel, wxID_ANY, _("With Subtitles"));
        grid->Add (_with_subtitles, 1);
@@ -443,6 +444,24 @@ FilmEditor::make_subtitle_panel ()
        _subtitle_scale->SetRange (1, 1000);
 }
 
+void
+FilmEditor::make_timing_panel ()
+{
+       _timing_panel = new wxPanel (_content_notebook);
+       wxBoxSizer* timing_sizer = new wxBoxSizer (wxVERTICAL);
+       _timing_panel->SetSizer (timing_sizer);
+       wxFlexGridSizer* grid = new wxFlexGridSizer (2, 4, 4);
+       timing_sizer->Add (grid, 0, wxALL, 8);
+
+       add_label_to_sizer (grid, _timing_panel, _("Start time"));
+       _start = new Timecode (_timing_panel);
+       grid->Add (_start);
+       add_label_to_sizer (grid, _timing_panel, _("Length"));
+       _length = new Timecode (_timing_panel);
+       grid->Add (_length);
+}
+
+
 /** Called when the left crop widget has been changed */
 void
 FilmEditor::left_crop_changed (wxCommandEvent &)
@@ -666,7 +685,13 @@ FilmEditor::film_content_changed (weak_ptr<Content> weak_content, int property)
                ffmpeg_content = dynamic_pointer_cast<FFmpegContent> (content);
        }
 
-       if (property == VideoContentProperty::VIDEO_CROP) {
+       if (property == ContentProperty::START) {
+               if (content) {
+                       _start->set (content->start (), _film->dcp_video_frame_rate ());
+               } else {
+                       _start->set (0, 24);
+               }
+       } else if (property == VideoContentProperty::VIDEO_CROP) {
                checked_set (_left_crop,   video_content ? video_content->crop().left :   0);
                checked_set (_right_crop,  video_content ? video_content->crop().right :  0);
                checked_set (_top_crop,    video_content ? video_content->crop().top :    0);
@@ -817,6 +842,7 @@ FilmEditor::set_film (shared_ptr<Film> f)
        film_changed (Film::DCI_METADATA);
        film_changed (Film::DCP_VIDEO_FRAME_RATE);
 
+       film_content_changed (boost::shared_ptr<Content> (), ContentProperty::START);
        film_content_changed (boost::shared_ptr<Content> (), VideoContentProperty::VIDEO_CROP);
        film_content_changed (boost::shared_ptr<Content> (), AudioContentProperty::AUDIO_GAIN);
        film_content_changed (boost::shared_ptr<Content> (), AudioContentProperty::AUDIO_DELAY);
index 89065c97d9cc5b4401d48c2393fef1b4a098fe2d..b169aee7f47456ed6210e786abaaa89e538510d7 100644 (file)
@@ -36,6 +36,7 @@ class AudioDialog;
 class TimelineDialog;
 class AudioMappingView;
 class Format;
+class Timecode;
 
 /** @class FilmEditor
  *  @brief A wx widget to edit a film's metadata, and perform various functions.
@@ -55,6 +56,7 @@ private:
        void make_video_panel ();
        void make_audio_panel ();
        void make_subtitle_panel ();
+       void make_timing_panel ();
        void connect_to_widgets ();
        
        /* Handle changes to the view */
@@ -120,11 +122,9 @@ private:
        wxPanel* _content_panel;
        wxSizer* _content_sizer;
        wxPanel* _video_panel;
-       wxSizer* _video_sizer;
        wxPanel* _audio_panel;
-       wxSizer* _audio_sizer;
        wxPanel* _subtitle_panel;
-       wxSizer* _subtitle_sizer;
+       wxPanel* _timing_panel;
 
        /** The film we are editing */
        boost::shared_ptr<Film> _film;
@@ -167,6 +167,8 @@ private:
        wxStaticText* _audio_description;
        wxChoice* _subtitle_stream;
        AudioMappingView* _audio_mapping;
+       Timecode* _start;
+       Timecode* _length;
 
        std::vector<Format const *> _formats;
 
diff --git a/src/wx/timecode.cc b/src/wx/timecode.cc
new file mode 100644 (file)
index 0000000..460f742
--- /dev/null
@@ -0,0 +1,90 @@
+/*
+    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/lexical_cast.hpp>
+#include "timecode.h"
+#include "wx_util.h"
+
+using std::string;
+using boost::lexical_cast;
+
+Timecode::Timecode (wxWindow* parent)
+       : wxPanel (parent)
+{
+       wxClientDC dc (parent);
+       wxSize size = dc.GetTextExtent (wxT ("9999"));
+       size.SetHeight (-1);
+
+       wxTextValidator validator (wxFILTER_INCLUDE_CHAR_LIST);
+       wxArrayString list;
+
+       string n = "0123456789";
+       for (size_t i = 0; i < n.length(); ++i) {
+               list.Add (n[i]);
+       }
+
+       validator.SetIncludes (list);
+       
+       wxBoxSizer* sizer = new wxBoxSizer (wxHORIZONTAL);
+       _hours = new wxTextCtrl (this, wxID_ANY, wxT(""), wxDefaultPosition, size, 0, validator);
+       _hours->SetMaxLength (2);
+       sizer->Add (_hours);
+       add_label_to_sizer (sizer, this, ":");
+       _minutes = new wxTextCtrl (this, wxID_ANY, wxT(""), wxDefaultPosition, size);
+       _minutes->SetMaxLength (2);
+       sizer->Add (_minutes);
+       add_label_to_sizer (sizer, this, ":");
+       _seconds = new wxTextCtrl (this, wxID_ANY, wxT(""), wxDefaultPosition, size);
+       _seconds->SetMaxLength (2);
+       sizer->Add (_seconds);
+       add_label_to_sizer (sizer, this, ".");
+       _frames = new wxTextCtrl (this, wxID_ANY, wxT(""), wxDefaultPosition, size);
+       _frames->SetMaxLength (2);
+       sizer->Add (_frames);
+
+       SetSizerAndFit (sizer);
+}
+
+void
+Timecode::set (Time t, int fps)
+{
+       int const h = t / (3600 * TIME_HZ);
+       t -= h * 3600 * TIME_HZ;
+       int const m = t / (60 * TIME_HZ);
+       t -= m * 60 * TIME_HZ;
+       int const s = t / TIME_HZ;
+       t -= s * TIME_HZ;
+       int const f = t * fps / TIME_HZ;
+
+       _hours->SetValue (wxString::Format ("%02d", h));
+       _minutes->SetValue (wxString::Format ("%02d", m));
+       _seconds->SetValue (wxString::Format ("%02d", s));
+       _frames->SetValue (wxString::Format ("%02d", f));
+}
+
+Time
+Timecode::get (int fps) const
+{
+       Time t = 0;
+       t += lexical_cast<int> (wx_to_std (_hours->GetValue())) * 3600 * TIME_HZ;
+       t += lexical_cast<int> (wx_to_std (_minutes->GetValue())) * 60 * TIME_HZ;
+       t += lexical_cast<int> (wx_to_std (_seconds->GetValue())) * TIME_HZ;
+       t += lexical_cast<int> (wx_to_std (_frames->GetValue())) * TIME_HZ / fps;
+       return t;
+}
diff --git a/src/wx/timecode.h b/src/wx/timecode.h
new file mode 100644 (file)
index 0000000..f243ee0
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+    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 <wx/wx.h>
+#include "lib/types.h"
+
+class Timecode : public wxPanel
+{
+public:
+       Timecode (wxWindow *);
+
+       void set (Time, int);
+       Time get (int) const;
+
+private:
+       wxTextCtrl* _hours;
+       wxTextCtrl* _minutes;
+       wxTextCtrl* _seconds;
+       wxTextCtrl* _frames;
+};
index 96bd2ccd4866a9585875b7cf8b232d7e5eefacff..8c90c3d8c1aa33768a70a93449f507c4f3efd079 100644 (file)
@@ -1,5 +1,3 @@
-/* -*- c-basic-offset: 8; default-tab-width: 8; -*- */
-
 /*
     Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
 
@@ -81,7 +79,7 @@ public:
                , _track (t)
                , _selected (false)
        {
-               c->Changed.connect (bind (&ContentView::content_changed, this, _2));
+               _content_connection = c->Changed.connect (bind (&ContentView::content_changed, this, _2));
        }
 
        Rect bbox () const
@@ -150,11 +148,11 @@ private:
 #endif
                
                wxGraphicsPath path = gc->CreatePath ();
-               path.MoveToPoint    (time_x (start),       y_pos (_track));
-               path.AddLineToPoint (time_x (start + len), y_pos (_track));
-               path.AddLineToPoint (time_x (start + len), y_pos (_track + 1));
-               path.AddLineToPoint (time_x (start),       y_pos (_track + 1));
-               path.AddLineToPoint (time_x (start),       y_pos (_track));
+               path.MoveToPoint    (time_x (start),       y_pos (_track) + 4);
+               path.AddLineToPoint (time_x (start + len), y_pos (_track) + 4);
+               path.AddLineToPoint (time_x (start + len), y_pos (_track + 1) - 4);
+               path.AddLineToPoint (time_x (start),       y_pos (_track + 1) - 4);
+               path.AddLineToPoint (time_x (start),       y_pos (_track) + 4);
                gc->StrokePath (path);
                gc->FillPath (path);
 
@@ -185,6 +183,8 @@ private:
        boost::weak_ptr<Content> _content;
        int _track;
        bool _selected;
+
+       boost::signals2::scoped_connection _content_connection;
 };
 
 class AudioContentView : public ContentView
@@ -326,7 +326,7 @@ Timeline::Timeline (wxWindow* parent, shared_ptr<const Film> film)
 
        playlist_changed ();
 
-       film->playlist()->Changed.connect (bind (&Timeline::playlist_changed, this));
+       _playlist_connection = film->playlist()->Changed.connect (bind (&Timeline::playlist_changed, this));
 }
 
 void
@@ -452,7 +452,7 @@ Timeline::mouse_moved (wxMouseEvent& ev)
        if (_down_view) {
                shared_ptr<Content> c = _down_view->content().lock();
                if (c) {
-                       c->set_start (_down_view_start + time_diff);
+                       c->set_start (max (static_cast<Time> (0), _down_view_start + time_diff));
                }
        }
 }
index 79ceceaa0c7fde4208c60ebe4fb1836524b4b75b..52b29de8653d466e1ba94d2079041ca84f0b9b66 100644 (file)
@@ -1,5 +1,3 @@
-/* -*- c-basic-offset: 8; default-tab-width: 8; -*- */
-
 /*
     Copyright (C) 2013 Carl Hetherington <cth@carlh.net>
 
@@ -21,6 +19,7 @@
 
 #include <boost/shared_ptr.hpp>
 #include <boost/weak_ptr.hpp>
+#include <boost/signals2.hpp>
 #include <wx/wx.h>
 #include "util.h"
 
@@ -76,4 +75,6 @@ private:
        boost::shared_ptr<ContentView> _down_view;
        Time _down_view_start;
        bool _first_move;
+
+       boost::signals2::scoped_connection _playlist_connection;
 };
index 884eabc3a85a0c05f2409b12ca39478398000d60..d915f5899a5065ff8e66cff0ff040fabe11b606e 100644 (file)
@@ -21,6 +21,7 @@ sources = """
           new_film_dialog.cc
           properties_dialog.cc
           server_dialog.cc
+          timecode.cc
           timeline.cc
           timeline_dialog.cc
           wx_util.cc