#include "timeline_labels_view.h"
#include "timeline_video_content_view.h"
#include "timeline_audio_content_view.h"
-#include "timeline_subtitle_content_view.h"
+#include "timeline_text_content_view.h"
#include "timeline_atmos_content_view.h"
#include "content_panel.h"
#include "wx_util.h"
+#include "film_viewer.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/subtitle_content.h"
+#include "lib/text_content.h"
#include "lib/video_content.h"
#include "lib/atmos_mxf_content.h"
#include <wx/graphics.h>
#include <boost/weak_ptr.hpp>
#include <boost/foreach.hpp>
#include <list>
+#include <iterator>
#include <iostream>
using std::list;
using std::cout;
using std::min;
using std::max;
+using std::abs;
using boost::shared_ptr;
using boost::weak_ptr;
using boost::dynamic_pointer_cast;
using boost::bind;
using boost::optional;
+using namespace dcpomatic;
/* 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)
+Timeline::Timeline (wxWindow* parent, ContentPanel* cp, shared_ptr<Film> film, weak_ptr<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))
, _content_panel (cp)
, _film (film)
+ , _viewer (viewer)
, _time_axis_view (new TimelineTimeAxisView (*this, 64))
, _reels_view (new TimelineReelsView (*this, 32))
, _labels_view (new TimelineLabelsView (*this))
, _y_scroll_rate (16)
, _pixels_per_track (48)
, _first_resize (true)
+ , _timer (this)
{
#ifndef __WXOSX__
_labels_canvas->SetDoubleBuffered (true);
_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_SCROLLWIN_THUMBTRACK, boost::bind (&Timeline::scrolled, 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));
+ _main_canvas->Bind (wxEVT_SCROLLWIN_LINEDOWN, boost::bind (&Timeline::scrolled, this, _1));
+ _main_canvas->Bind (wxEVT_SCROLLWIN_PAGEUP, boost::bind (&Timeline::scrolled, this, _1));
+ _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_changed (Film::CONTENT);
+ film_change (CHANGE_TYPE_DONE, Film::CONTENT);
SetMinSize (wxSize (640, 4 * pixels_per_track() + 96));
- _film_changed_connection = film->Changed.connect (bind (&Timeline::film_changed, this, _1));
- _film_content_changed_connection = film->ContentChanged.connect (bind (&Timeline::film_content_changed, this, _2, _3));
+ _film_changed_connection = film->Change.connect (bind (&Timeline::film_change, this, _1, _2));
+ _film_content_change_connection = film->ContentChange.connect (bind (&Timeline::film_content_change, this, _1, _3, _4));
+
+ Bind (wxEVT_TIMER, boost::bind(&Timeline::update_playhead, this));
+ _timer.Start (200, wxTIMER_CONTINUOUS);
setup_scrollbars ();
_labels_canvas->ShowScrollbars (wxSHOW_SB_NEVER, wxSHOW_SB_NEVER);
}
+void
+Timeline::update_playhead ()
+{
+ Refresh ();
+}
+
void
Timeline::set_pixels_per_second (double pps)
{
gc->DrawRectangle (
min (_down_point.x, _zoom_point->x),
min (_down_point.y, _zoom_point->y),
- fabs (_down_point.x - _zoom_point->x),
- fabs (_down_point.y - _zoom_point->y)
+ abs (_down_point.x - _zoom_point->x),
+ abs (_down_point.y - _zoom_point->y)
);
}
+ /* Playhead */
+
+ shared_ptr<FilmViewer> vp = _viewer.lock ();
+ DCPOMATIC_ASSERT (vp);
+
+ gc->SetPen (*wxRED_PEN);
+ wxGraphicsPath path = gc->CreatePath ();
+ double const ph = vp->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_changed (Film::Property p)
+Timeline::film_change (ChangeType type, Film::Property p)
{
+ if (type != CHANGE_TYPE_DONE) {
+ return;
+ }
+
if (p == Film::CONTENT || p == Film::REEL_TYPE || p == Film::REEL_LENGTH) {
ensure_ui_thread ();
recreate_views ();
_views.push_back (shared_ptr<TimelineView> (new TimelineAudioContentView (*this, i)));
}
- if (i->subtitle) {
- _views.push_back (shared_ptr<TimelineView> (new TimelineSubtitleContentView (*this, i)));
+ BOOST_FOREACH (shared_ptr<TextContent> j, i->text) {
+ _views.push_back (shared_ptr<TimelineView> (new TimelineTextContentView (*this, i, j)));
}
- if (dynamic_pointer_cast<AtmosMXFContent> (i)) {
- _views.push_back (shared_ptr<TimelineView> (new TimelineAtmosContentView (*this, i)));
+ if (i->atmos) {
+ _views.push_back (shared_ptr<TimelineView>(new TimelineAtmosContentView(*this, i)));
}
}
}
void
-Timeline::film_content_changed (int property, bool frequent)
+Timeline::film_content_change (ChangeType type, int property, bool frequent)
{
+ if (type != CHANGE_TYPE_DONE) {
+ return;
+ }
+
ensure_ui_thread ();
- if (property == AudioContentProperty::STREAMS) {
+ if (property == AudioContentProperty::STREAMS || property == VideoContentProperty::FRAME_TYPE) {
recreate_views ();
+ } else if (property == ContentProperty::POSITION || property == ContentProperty::LENGTH) {
+ _reels_view->force_redraw ();
} else if (!frequent) {
setup_scrollbars ();
Refresh ();
template <class T>
int
-place (TimelineViewList& views, int& tracks)
+place (shared_ptr<const Film> film, TimelineViewList& views, int& tracks)
{
int const base = tracks;
int t = base;
shared_ptr<Content> content = cv->content();
- DCPTimePeriod const content_period (content->position(), content->end());
+ DCPTimePeriod const content_period (content->position(), content->end(film));
while (true) {
TimelineViewList::iterator j = views.begin();
shared_ptr<Content> test_content = test->content();
if (
test->track() && test->track().get() == t &&
- content_period.overlap(DCPTimePeriod(test_content->position(), test_content->end()))) {
+ content_period.overlap(DCPTimePeriod(test_content->position(), test_content->end(film)))) {
/* we have an overlap on track `t' */
++t;
break;
return tracks - base;
}
+/** Compare the mapped output channels of two TimelineViews, so we can into
+ * order of first mapped DCP channel.
+ */
+struct AudioMappingComparator {
+ bool operator()(shared_ptr<TimelineView> a, shared_ptr<TimelineView> b) {
+ int la = -1;
+ shared_ptr<TimelineAudioContentView> cva = dynamic_pointer_cast<TimelineAudioContentView>(a);
+ if (cva) {
+ list<int> oc = cva->content()->audio->mapping().mapped_output_channels();
+ la = *min_element(boost::begin(oc), boost::end(oc));
+ }
+ int lb = -1;
+ shared_ptr<TimelineAudioContentView> cvb = dynamic_pointer_cast<TimelineAudioContentView>(b);
+ if (cvb) {
+ list<int> oc = cvb->content()->audio->mapping().mapped_output_channels();
+ lb = *min_element(boost::begin(oc), boost::end(oc));
+ }
+ return la < lb;
+ }
+};
+
void
Timeline::assign_tracks ()
{
/* Tracks are:
Video (mono or left-eye)
Video (right-eye)
- Subtitle 1
- Subtitle 2
- Subtitle N
+ Text 1
+ Text 2
+ Text N
Atmos
Audio 1
Audio 2
Audio N
*/
+ shared_ptr<const Film> film = _film.lock ();
+ DCPOMATIC_ASSERT (film);
+
_tracks = 0;
for (TimelineViewList::iterator i = _views.begin(); i != _views.end(); ++i) {
_tracks = max (_tracks, 1);
- /* Subtitle */
+ /* Texts */
- int const subtitle_tracks = place<TimelineSubtitleContentView> (_views, _tracks);
+ int const text_tracks = place<TimelineTextContentView> (film, _views, _tracks);
/* Atmos */
bool have_atmos = false;
BOOST_FOREACH (shared_ptr<TimelineView> i, _views) {
- shared_ptr<TimelineVideoContentView> cv = dynamic_pointer_cast<TimelineVideoContentView> (i);
- if (!cv) {
- continue;
- }
- if (dynamic_pointer_cast<TimelineAtmosContentView> (i)) {
- cv->set_track (_tracks - 1);
+ shared_ptr<TimelineAtmosContentView> cv = dynamic_pointer_cast<TimelineAtmosContentView>(i);
+ if (cv) {
+ cv->set_track (_tracks);
have_atmos = true;
}
}
++_tracks;
}
- /* Audio */
+ /* Audio. We're sorting the views so that we get the audio views in order of increasing
+ DCP channel index.
+ */
- place<TimelineAudioContentView> (_views, _tracks);
+ TimelineViewList views = _views;
+ sort(views.begin(), views.end(), AudioMappingComparator());
+ int const audio_tracks = place<TimelineAudioContentView> (film, views, _tracks);
_labels_view->set_3d (have_3d);
- _labels_view->set_subtitle_tracks (subtitle_tracks);
+ _labels_view->set_audio_tracks (audio_tracks);
+ _labels_view->set_text_tracks (text_tracks);
_labels_view->set_atmos (have_atmos);
_time_axis_view->set_y (tracks());
if (!film || !_pixels_per_second) {
return;
}
- _labels_canvas->SetVirtualSize (_labels_view->bbox().width, tracks() * pixels_per_track() + 96);
+
+ int const h = tracks() * pixels_per_track() + tracks_y_offset() + _time_axis_view->bbox().height;
+
+ _labels_canvas->SetVirtualSize (_labels_view->bbox().width, h);
_labels_canvas->SetScrollRate (_x_scroll_rate, _y_scroll_rate);
- _main_canvas->SetVirtualSize (*_pixels_per_second * film->length().seconds(), tracks() * pixels_per_track() + tracks_y_offset() + _time_axis_view->bbox().height);
+ _main_canvas->SetVirtualSize (*_pixels_per_second * film->length().seconds(), h);
_main_canvas->SetScrollRate (_x_scroll_rate, _y_scroll_rate);
}
{
/* Search backwards through views so that we find the uppermost one first */
TimelineViewList::reverse_iterator i = _views.rbegin();
- Position<int> const p (ev.GetX(), ev.GetY());
+
+ int vsx, vsy;
+ _main_canvas->GetViewStart (&vsx, &vsy);
+ Position<int> const p (ev.GetX() + vsx * _x_scroll_rate, ev.GetY() + vsy * _y_scroll_rate);
+
while (i != _views.rend() && !(*i)->bbox().contains (p)) {
shared_ptr<TimelineContentView> cv = dynamic_pointer_cast<TimelineContentView> (*i);
++i;
break;
case ZOOM:
case ZOOM_ALL:
+ case SNAP:
+ case SEQUENCE:
/* Nothing to do */
break;
}
continue;
}
+ shared_ptr<Film> film = _film.lock ();
+ DCPOMATIC_ASSERT (film);
+
_start_snaps.push_back (cv->content()->position());
_end_snaps.push_back (cv->content()->position());
- _start_snaps.push_back (cv->content()->end());
- _end_snaps.push_back (cv->content()->end());
+ _start_snaps.push_back (cv->content()->end(film));
+ _end_snaps.push_back (cv->content()->end(film));
- BOOST_FOREACH (DCPTime i, cv->content()->reel_split_points()) {
+ BOOST_FOREACH (DCPTime i, cv->content()->reel_split_points(film)) {
_start_snaps.push_back (i);
}
}
left_up_zoom (ev);
break;
case ZOOM_ALL:
+ case SNAP:
+ case SEQUENCE:
break;
}
}
}
_content_panel->set_selection (selected_content ());
- set_position_from_event (ev);
+ /* Since we may have just set change signals back to `not-frequent', we have to
+ make sure this position change is signalled, even if the position value has
+ not changed since the last time it was set (with frequent=true). This is
+ a bit of a hack.
+ */
+ set_position_from_event (ev, true);
/* Clear up up the stuff we don't do during drag */
assign_tracks ();
DCPTime const time_right = DCPTime::from_seconds((bottom_right.x + vsx) / *_pixels_per_second);
set_pixels_per_second (double(GetSize().GetWidth()) / (time_right.seconds() - time_left.seconds()));
- double const tracks_top = double(top_left.y) / _pixels_per_track;
- double const tracks_bottom = double(bottom_right.y) / _pixels_per_track;
+ double const tracks_top = double(top_left.y - tracks_y_offset()) / _pixels_per_track;
+ double const tracks_bottom = double(bottom_right.y - tracks_y_offset()) / _pixels_per_track;
set_pixels_per_track (lrint(GetSize().GetHeight() / (tracks_bottom - tracks_top)));
setup_scrollbars ();
- _main_canvas->Scroll (time_left.seconds() * *_pixels_per_second / _x_scroll_rate, tracks_top * _pixels_per_track / _y_scroll_rate);
- _labels_canvas->Scroll (0, tracks_top * _pixels_per_track / _y_scroll_rate);
+ int const y = (tracks_top * _pixels_per_track + tracks_y_offset()) / _y_scroll_rate;
+ _main_canvas->Scroll (time_left.seconds() * *_pixels_per_second / _x_scroll_rate, y);
+ _labels_canvas->Scroll (0, y);
_zoom_point = optional<wxPoint> ();
Refresh ();
mouse_moved_zoom (ev);
break;
case ZOOM_ALL:
+ case SNAP:
+ case SEQUENCE:
break;
}
}
Refresh ();
break;
case ZOOM_ALL:
+ case SNAP:
+ case SEQUENCE:
break;
}
}
}
void
-Timeline::set_position_from_event (wxMouseEvent& ev)
+Timeline::set_position_from_event (wxMouseEvent& ev, bool force_emit)
{
if (!_pixels_per_second) {
return;
DCPTime new_position = _down_view_position + DCPTime::from_seconds ((p.x - _down_point.x) / pps);
- if (_snap) {
+ shared_ptr<Film> film = _film.lock ();
+ DCPOMATIC_ASSERT (film);
- DCPTime const new_end = new_position + _down_view->content()->length_after_trim();
+ if (_snap) {
+ DCPTime const new_end = new_position + _down_view->content()->length_after_trim(film);
/* Signed `distance' to nearest thing (i.e. negative is left on the timeline,
positive is right).
*/
new_position = DCPTime ();
}
- _down_view->content()->set_position (new_position);
+ _down_view->content()->set_position (film, new_position, force_emit);
- shared_ptr<Film> film = _film.lock ();
- DCPOMATIC_ASSERT (film);
film->set_sequence (false);
}
void
Timeline::force_redraw (dcpomatic::Rect<int> const & r)
{
- RefreshRect (wxRect (r.x, r.y, r.width, r.height), false);
+ _main_canvas->RefreshRect (wxRect (r.x, r.y, r.width, r.height), false);
}
shared_ptr<const Film>
Timeline::scrolled (wxScrollWinEvent& ev)
{
if (ev.GetOrientation() == wxVERTICAL) {
- _labels_canvas->Scroll (0, ev.GetPosition ());
+ int x, y;
+ _main_canvas->GetViewStart (&x, &y);
+ _labels_canvas->Scroll (0, y);
}
ev.Skip ();
}
case ZOOM_ALL:
zoom_all ();
break;
+ case SNAP:
+ case SEQUENCE:
+ break;
}
}