*/
+#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;
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))
, _left_down (false)
, _down_view_position (0)
, _first_move (false)
- , _menu (this)
+ , _menu (this, viewer)
, _snap (true)
, _tool (SELECT)
, _x_scroll_rate (16)
_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));
_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::Property::CONTENT);
+ film_change(ChangeType::DONE, FilmProperty::CONTENT);
SetMinSize (wxSize (640, 4 * pixels_per_track() + 96));
_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);
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);
}
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),
/* 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::Property::CONTENT || p == Film::Property::REEL_TYPE || p == Film::Property::REEL_LENGTH) {
+ if (p == FilmProperty::CONTENT || p == FilmProperty::REEL_TYPE || p == FilmProperty::REEL_LENGTH) {
ensure_ui_thread ();
recreate_views ();
- } else if (p == Film::Property::CONTENT_ORDER) {
+ } else if (p == FilmProperty::CONTENT_ORDER) {
Refresh ();
}
}
+
void
Timeline::recreate_views ()
{
_views.push_back (make_shared<TimelineVideoContentView>(*this, i));
}
- if (i->audio && !i->audio->mapping().mapped_output_channels().empty ()) {
+ if (i->has_mapped_audio()) {
_views.push_back (make_shared<TimelineAudioContentView>(*this, i));
}
Refresh ();
}
+
void
Timeline::film_content_change (ChangeType type, int property, bool frequent)
{
}
}
+
template <class T>
int
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();
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;
return tracks - base;
}
+
/** Compare the mapped output channels of two TimelineViews, so we can into
* order of first mapped DCP channel.
*/
}
};
+
void
Timeline::assign_tracks ()
{
_reels_view->set_y (8);
}
+
int
Timeline::tracks () const
{
return _tracks;
}
+
void
Timeline::setup_scrollbars ()
{
_main_canvas->SetScrollRate (_x_scroll_rate, _y_scroll_rate);
}
+
shared_ptr<TimelineView>
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;
}
return *i;
}
+
void
Timeline::left_down (wxMouseEvent& ev)
{
}
}
+
void
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) {
}
}
+
void
Timeline::left_up (wxMouseEvent& ev)
{
}
}
+
void
Timeline::left_up_select (wxMouseEvent& ev)
{
_end_snaps.clear ();
}
+
void
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)
{
}
}
+
void
Timeline::mouse_moved_select (wxMouseEvent& ev)
{
set_position_from_event (ev);
}
+
void
Timeline::mouse_moved_zoom (wxMouseEvent& ev)
{
}
_zoom_point = ev.GetPosition ();
+ setup_scrollbars();
Refresh ();
}
+
void
Timeline::right_down (wxMouseEvent& ev)
{
}
}
+
void
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
{
}
}
+
void
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 ()
{
setup_scrollbars ();
}
+
void
Timeline::clear_selection ()
{
}
}
+
TimelineContentViewList
Timeline::selected_views () const
{
return sel;
}
+
ContentList
Timeline::selected_content () const
{
return sel;
}
+
void
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)
{
ev.Skip ();
}
+
void
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;
+ }
+ }
+}
+