Emit no audio from DCPs if none is mapped
[dcpomatic.git] / src / wx / timeline.cc
index 89f2239569edea9cf9e8b79f325381e119ec460b..38e9de4ee46318ca73bfff98a29023c64bac5047 100644 (file)
 
 */
 
+#include "content_panel.h"
 #include "film_editor.h"
+#include "film_viewer.h"
 #include "timeline.h"
-#include "timeline_time_axis_view.h"
-#include "timeline_reels_view.h"
-#include "timeline_labels_view.h"
-#include "timeline_video_content_view.h"
+#include "timeline_atmos_content_view.h"
 #include "timeline_audio_content_view.h"
+#include "timeline_labels_view.h"
+#include "timeline_reels_view.h"
 #include "timeline_text_content_view.h"
-#include "timeline_atmos_content_view.h"
-#include "content_panel.h"
+#include "timeline_time_axis_view.h"
+#include "timeline_video_content_view.h"
 #include "wx_util.h"
-#include "film_viewer.h"
+#include "lib/atmos_mxf_content.h"
+#include "lib/audio_content.h"
 #include "lib/film.h"
-#include "lib/playlist.h"
 #include "lib/image_content.h"
-#include "lib/timer.h"
-#include "lib/audio_content.h"
+#include "lib/playlist.h"
 #include "lib/text_content.h"
+#include "lib/timer.h"
 #include "lib/video_content.h"
-#include "lib/atmos_mxf_content.h"
+#include <dcp/scope_guard.h>
+#include <dcp/warnings.h>
+LIBDCP_DISABLE_WARNINGS
 #include <wx/graphics.h>
-#include <list>
+LIBDCP_ENABLE_WARNINGS
 #include <iterator>
-#include <iostream>
+#include <list>
 
+
+using std::abs;
+using std::dynamic_pointer_cast;
 using std::list;
-using std::cout;
-using std::min;
+using std::make_shared;
 using std::max;
-using std::abs;
+using std::min;
 using std::shared_ptr;
 using std::weak_ptr;
-using std::dynamic_pointer_cast;
-using std::make_shared;
 using boost::bind;
 using boost::optional;
 using namespace dcpomatic;
@@ -59,11 +62,13 @@ using namespace dcpomatic;
 using namespace boost::placeholders;
 #endif
 
+
 /* 3 hours in 640 pixels */
 double const Timeline::_minimum_pixels_per_second = 640.0 / (60 * 60 * 3);
 int const Timeline::_minimum_pixels_per_track = 16;
 
-Timeline::Timeline (wxWindow* parent, ContentPanel* cp, shared_ptr<Film> film, weak_ptr<FilmViewer> viewer)
+
+Timeline::Timeline(wxWindow* parent, ContentPanel* cp, shared_ptr<Film> film, FilmViewer& viewer)
        : wxPanel (parent, wxID_ANY)
        , _labels_canvas (new wxScrolledCanvas (this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxFULL_REPAINT_ON_RESIZE))
        , _main_canvas (new wxScrolledCanvas (this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxFULL_REPAINT_ON_RESIZE))
@@ -77,7 +82,7 @@ Timeline::Timeline (wxWindow* parent, ContentPanel* cp, shared_ptr<Film> film, w
        , _left_down (false)
        , _down_view_position (0)
        , _first_move (false)
-       , _menu (this)
+       , _menu (this, viewer)
        , _snap (true)
        , _tool (SELECT)
        , _x_scroll_rate (16)
@@ -104,6 +109,7 @@ Timeline::Timeline (wxWindow* parent, ContentPanel* cp, shared_ptr<Film> film, w
        _main_canvas->Bind   (wxEVT_RIGHT_DOWN, boost::bind (&Timeline::right_down,   this, _1));
        _main_canvas->Bind   (wxEVT_MOTION,     boost::bind (&Timeline::mouse_moved,  this, _1));
        _main_canvas->Bind   (wxEVT_SIZE,       boost::bind (&Timeline::resized,      this));
+       _main_canvas->Bind   (wxEVT_MOUSEWHEEL, boost::bind(&Timeline::mouse_wheel_turned, this, _1));
        _main_canvas->Bind   (wxEVT_SCROLLWIN_TOP,        boost::bind (&Timeline::scrolled,     this, _1));
        _main_canvas->Bind   (wxEVT_SCROLLWIN_BOTTOM,     boost::bind (&Timeline::scrolled,     this, _1));
        _main_canvas->Bind   (wxEVT_SCROLLWIN_LINEUP,     boost::bind (&Timeline::scrolled,     this, _1));
@@ -112,7 +118,7 @@ Timeline::Timeline (wxWindow* parent, ContentPanel* cp, shared_ptr<Film> film, w
        _main_canvas->Bind   (wxEVT_SCROLLWIN_PAGEDOWN,   boost::bind (&Timeline::scrolled,     this, _1));
        _main_canvas->Bind   (wxEVT_SCROLLWIN_THUMBTRACK, boost::bind (&Timeline::scrolled,     this, _1));
 
-       film_change (ChangeType::DONE, Film::CONTENT);
+       film_change(ChangeType::DONE, FilmProperty::CONTENT);
 
        SetMinSize (wxSize (640, 4 * pixels_per_track() + 96));
 
@@ -126,41 +132,101 @@ Timeline::Timeline (wxWindow* parent, ContentPanel* cp, shared_ptr<Film> film, w
        _labels_canvas->ShowScrollbars (wxSHOW_SB_NEVER, wxSHOW_SB_NEVER);
 }
 
+
+void
+Timeline::mouse_wheel_turned(wxMouseEvent& event)
+{
+       auto const rotation = event.GetWheelRotation();
+
+       if (event.ControlDown()) {
+               /* On my mouse one click of the scroll wheel is 120, and it's -ve when
+                * scrolling the wheel towards me.
+                */
+               auto const scale = rotation > 0 ?
+                       (1.0 / (rotation / 90.0)) :
+                       (-rotation / 90.0);
+
+               int before_start_x;
+               int before_start_y;
+               _main_canvas->GetViewStart(&before_start_x, &before_start_y);
+
+               auto const before_pps = _pixels_per_second.get_value_or(1);
+               auto const before_pos = _last_mouse_wheel_x && *_last_mouse_wheel_x == event.GetX() ?
+                       *_last_mouse_wheel_time :
+                       (before_start_x * _x_scroll_rate + event.GetX()) / before_pps;
+
+               set_pixels_per_second(before_pps * scale);
+               setup_scrollbars();
+
+               auto after_left = std::max(0.0, before_pos * _pixels_per_second.get_value_or(1) - event.GetX());
+               _main_canvas->Scroll(after_left / _x_scroll_rate, before_start_y);
+               _labels_canvas->Scroll(0, before_start_y);
+               Refresh();
+
+               if (!_last_mouse_wheel_x || *_last_mouse_wheel_x != event.GetX()) {
+                       _last_mouse_wheel_x = event.GetX();
+                       _last_mouse_wheel_time = before_pos;
+               }
+       } else if (event.ShiftDown()) {
+               int before_start_x;
+               int before_start_y;
+               _main_canvas->GetViewStart(&before_start_x, &before_start_y);
+               auto const width = _main_canvas->GetSize().GetWidth();
+               _main_canvas->Scroll(std::max(0.0, before_start_x - rotation * 100.0 / width), before_start_y);
+       }
+}
+
+
 void
 Timeline::update_playhead ()
 {
        Refresh ();
 }
 
+
 void
 Timeline::set_pixels_per_second (double pps)
 {
        _pixels_per_second = max (_minimum_pixels_per_second, pps);
 }
 
+
 void
 Timeline::paint_labels ()
 {
        wxPaintDC dc (_labels_canvas);
 
+       auto film = _film.lock();
+       if (film->content().empty()) {
+               return;
+       }
+
        auto gc = wxGraphicsContext::Create (dc);
        if (!gc) {
                return;
        }
 
+       dcp::ScopeGuard sg = [gc]() { delete gc; };
+
        int vsx, vsy;
        _labels_canvas->GetViewStart (&vsx, &vsy);
        gc->Translate (-vsx * _x_scroll_rate, -vsy * _y_scroll_rate + tracks_y_offset());
 
-       _labels_view->paint (gc, list<dcpomatic::Rect<int> >());
-
-       delete gc;
+       _labels_view->paint (gc, {});
 }
 
+
 void
 Timeline::paint_main ()
 {
        wxPaintDC dc (_main_canvas);
+       dc.Clear();
+
+       auto film = _film.lock();
+       if (film->content().empty()) {
+               return;
+       }
+
        _main_canvas->DoPrepareDC (dc);
 
        auto gc = wxGraphicsContext::Create (dc);
@@ -168,9 +234,7 @@ Timeline::paint_main ()
                return;
        }
 
-       int vsx, vsy;
-       _main_canvas->GetViewStart (&vsx, &vsy);
-       gc->Translate (-vsx * _x_scroll_rate, -vsy * _y_scroll_rate);
+       dcp::ScopeGuard sg = [gc]() { delete gc; };
 
        gc->SetAntialiasMode (wxANTIALIAS_DEFAULT);
 
@@ -182,7 +246,7 @@ Timeline::paint_main ()
                list<dcpomatic::Rect<int>> overlaps;
                for (auto j: _views) {
                        auto jc = dynamic_pointer_cast<TimelineContentView> (j);
-                       /* No overlap with non-content views, views no different tracks, audio views or non-active views */
+                       /* No overlap with non-content views, views on different tracks, audio views or non-active views */
                        if (!ic || !jc || i == j || ic->track() != jc->track() || ic->track().get_value_or(2) >= 2 || !ic->active() || !jc->active()) {
                                continue;
                        }
@@ -197,9 +261,7 @@ Timeline::paint_main ()
        }
 
        if (_zoom_point) {
-               /* Translate back as _down_point and _zoom_point do not take scroll into account */
-               gc->Translate (vsx * _x_scroll_rate, vsy * _y_scroll_rate);
-               gc->SetPen (*wxBLACK_PEN);
+               gc->SetPen(gui_is_dark() ? *wxWHITE_PEN : *wxBLACK_PEN);
                gc->SetBrush (*wxTRANSPARENT_BRUSH);
                gc->DrawRectangle (
                        min (_down_point.x, _zoom_point->x),
@@ -211,34 +273,31 @@ Timeline::paint_main ()
 
        /* Playhead */
 
-       auto vp = _viewer.lock ();
-       DCPOMATIC_ASSERT (vp);
-
        gc->SetPen (*wxRED_PEN);
        auto path = gc->CreatePath ();
-       double const ph = vp->position().seconds() * pixels_per_second().get_value_or(0);
+       double const ph = _viewer.position().seconds() * pixels_per_second().get_value_or(0);
        path.MoveToPoint (ph, 0);
        path.AddLineToPoint (ph, pixels_per_track() * _tracks + 32);
        gc->StrokePath (path);
-
-       delete gc;
 }
 
+
 void
-Timeline::film_change (ChangeType type, Film::Property p)
+Timeline::film_change(ChangeType type, FilmProperty p)
 {
        if (type != ChangeType::DONE) {
                return;
        }
 
-       if (p == Film::CONTENT || p == Film::REEL_TYPE || p == Film::REEL_LENGTH) {
+       if (p == FilmProperty::CONTENT || p == FilmProperty::REEL_TYPE || p == FilmProperty::REEL_LENGTH) {
                ensure_ui_thread ();
                recreate_views ();
-       } else if (p == Film::CONTENT_ORDER) {
+       } else if (p == FilmProperty::CONTENT_ORDER) {
                Refresh ();
        }
 }
 
+
 void
 Timeline::recreate_views ()
 {
@@ -274,6 +333,7 @@ Timeline::recreate_views ()
        Refresh ();
 }
 
+
 void
 Timeline::film_content_change (ChangeType type, int property, bool frequent)
 {
@@ -293,6 +353,7 @@ Timeline::film_content_change (ChangeType type, int property, bool frequent)
        }
 }
 
+
 template <class T>
 int
 place (shared_ptr<const Film> film, TimelineViewList& views, int& tracks)
@@ -305,11 +366,12 @@ place (shared_ptr<const Film> film, TimelineViewList& views, int& tracks)
                }
 
                auto cv = dynamic_pointer_cast<TimelineContentView> (i);
+               DCPOMATIC_ASSERT(cv);
 
                int t = base;
 
                auto content = cv->content();
-               DCPTimePeriod const content_period (content->position(), content->end(film));
+               DCPTimePeriod const content_period = content->period(film);
 
                while (true) {
                        auto j = views.begin();
@@ -323,7 +385,8 @@ place (shared_ptr<const Film> film, TimelineViewList& views, int& tracks)
                                auto test_content = test->content();
                                if (
                                        test->track() && test->track().get() == t &&
-                                       content_period.overlap(DCPTimePeriod(test_content->position(), test_content->end(film)))) {
+                                       content_period.overlap(test_content->period(film))
+                                  ) {
                                        /* we have an overlap on track `t' */
                                        ++t;
                                        break;
@@ -345,6 +408,7 @@ place (shared_ptr<const Film> film, TimelineViewList& views, int& tracks)
        return tracks - base;
 }
 
+
 /** Compare the mapped output channels of two TimelineViews, so we can into
  *  order of first mapped DCP channel.
  */
@@ -366,12 +430,14 @@ struct AudioMappingComparator {
        }
 };
 
+
 void
 Timeline::assign_tracks ()
 {
        /* Tracks are:
-          Video (mono or left-eye)
-          Video (right-eye)
+          Video 1
+          Video 2
+          Video N
           Text 1
           Text 2
           Text N
@@ -393,29 +459,7 @@ Timeline::assign_tracks ()
                }
        }
 
-       /* Video */
-
-       bool have_3d = false;
-       for (auto i: _views) {
-               auto cv = dynamic_pointer_cast<TimelineVideoContentView>(i);
-               if (!cv) {
-                       continue;
-               }
-
-               /* Video on tracks 0 and maybe 1 (left and right eye) */
-               if (cv->content()->video->frame_type() == VideoFrameType::THREE_D_RIGHT) {
-                       cv->set_track (1);
-                       _tracks = max (_tracks, 2);
-                       have_3d = true;
-               } else {
-                       cv->set_track (0);
-               }
-       }
-
-       _tracks = max (_tracks, 1);
-
-       /* Texts */
-
+       int const video_tracks = place<TimelineVideoContentView> (film, _views, _tracks);
        int const text_tracks = place<TimelineTextContentView> (film, _views, _tracks);
 
        /* Atmos */
@@ -441,7 +485,7 @@ Timeline::assign_tracks ()
        sort(views.begin(), views.end(), AudioMappingComparator());
        int const audio_tracks = place<TimelineAudioContentView> (film, views, _tracks);
 
-       _labels_view->set_3d (have_3d);
+       _labels_view->set_video_tracks (video_tracks);
        _labels_view->set_audio_tracks (audio_tracks);
        _labels_view->set_text_tracks (text_tracks);
        _labels_view->set_atmos (have_atmos);
@@ -450,12 +494,14 @@ Timeline::assign_tracks ()
        _reels_view->set_y (8);
 }
 
+
 int
 Timeline::tracks () const
 {
        return _tracks;
 }
 
+
 void
 Timeline::setup_scrollbars ()
 {
@@ -472,6 +518,7 @@ Timeline::setup_scrollbars ()
        _main_canvas->SetScrollRate (_x_scroll_rate, _y_scroll_rate);
 }
 
+
 shared_ptr<TimelineView>
 Timeline::event_to_view (wxMouseEvent& ev)
 {
@@ -483,7 +530,6 @@ Timeline::event_to_view (wxMouseEvent& ev)
        Position<int> const p (ev.GetX() + vsx * _x_scroll_rate, ev.GetY() + vsy * _y_scroll_rate);
 
        while (i != _views.rend() && !(*i)->bbox().contains (p)) {
-               auto cv = dynamic_pointer_cast<TimelineContentView>(*i);
                ++i;
        }
 
@@ -494,6 +540,7 @@ Timeline::event_to_view (wxMouseEvent& ev)
        return *i;
 }
 
+
 void
 Timeline::left_down (wxMouseEvent& ev)
 {
@@ -513,6 +560,7 @@ Timeline::left_down (wxMouseEvent& ev)
        }
 }
 
+
 void
 Timeline::left_down_select (wxMouseEvent& ev)
 {
@@ -526,6 +574,12 @@ Timeline::left_down_select (wxMouseEvent& ev)
                _down_view_position = content_view->content()->position ();
        }
 
+       if (dynamic_pointer_cast<TimelineTimeAxisView>(view)) {
+               int vsx, vsy;
+               _main_canvas->GetViewStart(&vsx, &vsy);
+               _viewer.seek(DCPTime::from_seconds((ev.GetPosition().x + vsx * _x_scroll_rate) / _pixels_per_second.get_value_or(1)), true);
+       }
+
        for (auto i: _views) {
                auto cv = dynamic_pointer_cast<TimelineContentView>(i);
                if (!cv) {
@@ -569,6 +623,7 @@ Timeline::left_down_select (wxMouseEvent& ev)
        }
 }
 
+
 void
 Timeline::left_up (wxMouseEvent& ev)
 {
@@ -588,6 +643,7 @@ Timeline::left_up (wxMouseEvent& ev)
        }
 }
 
+
 void
 Timeline::left_up_select (wxMouseEvent& ev)
 {
@@ -612,6 +668,7 @@ Timeline::left_up_select (wxMouseEvent& ev)
        _end_snaps.clear ();
 }
 
+
 void
 Timeline::left_up_zoom (wxMouseEvent& ev)
 {
@@ -625,6 +682,8 @@ Timeline::left_up_zoom (wxMouseEvent& ev)
 
        if ((bottom_right.x - top_left.x) < 8 || (bottom_right.y - top_left.y) < 8) {
                /* Very small zoom rectangle: we assume it wasn't intentional */
+               _zoom_point = optional<wxPoint> ();
+               Refresh ();
                return;
        }
 
@@ -645,12 +704,14 @@ Timeline::left_up_zoom (wxMouseEvent& ev)
        Refresh ();
 }
 
+
 void
 Timeline::set_pixels_per_track (int h)
 {
        _pixels_per_track = max(_minimum_pixels_per_track, h);
 }
 
+
 void
 Timeline::mouse_moved (wxMouseEvent& ev)
 {
@@ -668,6 +729,7 @@ Timeline::mouse_moved (wxMouseEvent& ev)
        }
 }
 
+
 void
 Timeline::mouse_moved_select (wxMouseEvent& ev)
 {
@@ -678,6 +740,7 @@ Timeline::mouse_moved_select (wxMouseEvent& ev)
        set_position_from_event (ev);
 }
 
+
 void
 Timeline::mouse_moved_zoom (wxMouseEvent& ev)
 {
@@ -686,9 +749,11 @@ Timeline::mouse_moved_zoom (wxMouseEvent& ev)
        }
 
        _zoom_point = ev.GetPosition ();
+       setup_scrollbars();
        Refresh ();
 }
 
+
 void
 Timeline::right_down (wxMouseEvent& ev)
 {
@@ -710,6 +775,7 @@ Timeline::right_down (wxMouseEvent& ev)
        }
 }
 
+
 void
 Timeline::right_down_select (wxMouseEvent& ev)
 {
@@ -727,6 +793,7 @@ Timeline::right_down_select (wxMouseEvent& ev)
        _menu.popup (_film, selected_content (), selected_views (), ev.GetPosition ());
 }
 
+
 void
 Timeline::maybe_snap (DCPTime a, DCPTime b, optional<DCPTime>& nearest_distance) const
 {
@@ -736,6 +803,7 @@ Timeline::maybe_snap (DCPTime a, DCPTime b, optional<DCPTime>& nearest_distance)
        }
 }
 
+
 void
 Timeline::set_position_from_event (wxMouseEvent& ev, bool force_emit)
 {
@@ -801,18 +869,21 @@ Timeline::set_position_from_event (wxMouseEvent& ev, bool force_emit)
        film->set_sequence (false);
 }
 
+
 void
 Timeline::force_redraw (dcpomatic::Rect<int> const & r)
 {
        _main_canvas->RefreshRect (wxRect (r.x, r.y, r.width, r.height), false);
 }
 
+
 shared_ptr<const Film>
 Timeline::film () const
 {
        return _film.lock ();
 }
 
+
 void
 Timeline::resized ()
 {
@@ -823,6 +894,7 @@ Timeline::resized ()
        setup_scrollbars ();
 }
 
+
 void
 Timeline::clear_selection ()
 {
@@ -834,6 +906,7 @@ Timeline::clear_selection ()
        }
 }
 
+
 TimelineContentViewList
 Timeline::selected_views () const
 {
@@ -849,6 +922,7 @@ Timeline::selected_views () const
        return sel;
 }
 
+
 ContentList
 Timeline::selected_content () const
 {
@@ -861,6 +935,7 @@ Timeline::selected_content () const
        return sel;
 }
 
+
 void
 Timeline::set_selection (ContentList selection)
 {
@@ -872,18 +947,21 @@ Timeline::set_selection (ContentList selection)
        }
 }
 
+
 int
 Timeline::tracks_y_offset () const
 {
        return _reels_view->bbox().height + 4;
 }
 
+
 int
 Timeline::width () const
 {
        return _main_canvas->GetVirtualSize().GetWidth();
 }
 
+
 void
 Timeline::scrolled (wxScrollWinEvent& ev)
 {
@@ -895,6 +973,7 @@ Timeline::scrolled (wxScrollWinEvent& ev)
        ev.Skip ();
 }
 
+
 void
 Timeline::tool_clicked (Tool t)
 {
@@ -912,15 +991,39 @@ Timeline::tool_clicked (Tool t)
        }
 }
 
+
 void
 Timeline::zoom_all ()
 {
        auto film = _film.lock ();
        DCPOMATIC_ASSERT (film);
-       set_pixels_per_second ((_main_canvas->GetSize().GetWidth() - 32) / film->length().seconds());
-       set_pixels_per_track ((_main_canvas->GetSize().GetHeight() - tracks_y_offset() - _time_axis_view->bbox().height - 32) / _tracks);
+       set_pixels_per_second((_main_canvas->GetSize().GetWidth() - 32) / std::max(1.0, film->length().seconds()));
+       set_pixels_per_track((_main_canvas->GetSize().GetHeight() - tracks_y_offset() - _time_axis_view->bbox().height - 32) / std::max(1, _tracks));
        setup_scrollbars ();
        _main_canvas->Scroll (0, 0);
        _labels_canvas->Scroll (0, 0);
        Refresh ();
 }
+
+
+void
+Timeline::keypress(wxKeyEvent const& event)
+{
+       if (event.GetKeyCode() == WXK_DELETE) {
+               auto film = _film.lock();
+               DCPOMATIC_ASSERT(film);
+               film->remove_content(selected_content());
+       } else {
+               switch (event.GetRawKeyCode()) {
+               case '+':
+                       set_pixels_per_second(_pixels_per_second.get_value_or(1) * 2);
+                       setup_scrollbars();
+                       break;
+               case '-':
+                       set_pixels_per_second(_pixels_per_second.get_value_or(1) / 2);
+                       setup_scrollbars();
+                       break;
+               }
+       }
+}
+