#define MAX_CLOSED_CAPTION_XML_SIZE (256 * 1024)
#define MAX_CLOSED_CAPTION_XML_SIZE_TEXT "256KB"
#define CERTIFICATE_VALIDITY_PERIOD (10 * 365)
+#define SNAP_SUBDIVISION 64
#endif
using namespace dcpomatic;
+bool
+dcpomatic::operator<=(HMSF const& a, HMSF const& b)
+{
+ if (a.h != b.h) {
+ return a.h <= b.h;
+ }
+
+ if (a.m != b.m) {
+ return a.m <= b.m;
+ }
+
+ if (a.s != b.s) {
+ return a.s <= b.s;
+ }
+
+ return a.f <= b.f;
+}
+
+
template <>
Time<ContentTimeDifferentiator, DCPTimeDifferentiator>::Time (DCPTime d, FrameRateChange f)
: _t (llrint(d.get() * f.speed_up))
};
+bool operator<=(HMSF const& a, HMSF const& b);
+
+
/** A time in seconds, expressed as a number scaled up by Time::HZ. We want two different
* versions of this class, dcpomatic::ContentTime and dcpomatic::DCPTime, and we want it to be impossible to
* convert implicitly between the two. Hence there's this template hack. I'm not
}
root->add_child("ReelType")->add_child_text (raw_convert<string> (static_cast<int> (_reel_type)));
root->add_child("ReelLength")->add_child_text (raw_convert<string> (_reel_length));
+ for (auto boundary: _custom_reel_boundaries) {
+ root->add_child("CustomReelBoundary")->add_child_text(raw_convert<string>(boundary.get()));
+ }
root->add_child("ReencodeJ2K")->add_child_text (_reencode_j2k ? "1" : "0");
root->add_child("UserExplicitVideoFrameRate")->add_child_text(_user_explicit_video_frame_rate ? "1" : "0");
for (auto const& marker: _markers) {
_reel_type = static_cast<ReelType> (f.optional_number_child<int>("ReelType").get_value_or (static_cast<int>(ReelType::SINGLE)));
_reel_length = f.optional_number_child<int64_t>("ReelLength").get_value_or (2000000000);
+ for (auto boundary: f.node_children("CustomReelBoundary")) {
+ _custom_reel_boundaries.push_back(DCPTime(raw_convert<int64_t>(boundary->content())));
+ }
_reencode_j2k = f.optional_bool_child("ReencodeJ2K").get_value_or(false);
_user_explicit_video_frame_rate = f.optional_bool_child("UserExplicitVideoFrameRate").get_value_or(false);
_reel_length = r;
}
+
+void
+Film::set_custom_reel_boundaries(vector<DCPTime> boundaries)
+{
+ FilmChangeSignaller ch(this, FilmProperty::CUSTOM_REEL_BOUNDARIES);
+ std::sort(boundaries.begin(), boundaries.end());
+ _custom_reel_boundaries = std::move(boundaries);
+}
+
+
void
Film::set_reencode_j2k (bool r)
{
if (change_made) {
Message (_("DCP-o-matic had to change your settings for referring to DCPs as OV. Please review those settings to make sure they are what you want."));
}
+
+ if (reel_type() == ReelType::CUSTOM) {
+ auto boundaries = custom_reel_boundaries();
+ auto too_late = std::find_if(boundaries.begin(), boundaries.end(), [this](dcpomatic::DCPTime const& time) {
+ return time >= length();
+ });
+
+ if (too_late != boundaries.end()) {
+ if (std::distance(too_late, boundaries.end()) > 1) {
+ Message(_("DCP-o-matic had to remove some of your custom reel boundaries as they no longer lie within the film."));
+ } else {
+ Message(_("DCP-o-matic had to remove one of your custom reel boundaries as it no longer lies within the film."));
+ }
+ boundaries.erase(too_late, boundaries.end());
+ set_custom_reel_boundaries(boundaries);
+ }
+ }
}
void
/* XXX */
}
-list<DCPTimePeriod>
+
+vector<DCPTimePeriod>
Film::reels () const
{
- list<DCPTimePeriod> p;
+ vector<DCPTimePeriod> periods;
auto const len = length();
switch (reel_type ()) {
case ReelType::SINGLE:
- p.push_back (DCPTimePeriod (DCPTime (), len));
+ periods.emplace_back(DCPTime(), len);
break;
case ReelType::BY_VIDEO_CONTENT:
{
for (auto t: split_points) {
if (last && (t - *last) >= DCPTime::from_seconds(1)) {
/* Period from *last to t is long enough; use it and start a new one */
- p.push_back (DCPTimePeriod(*last, t));
+ periods.emplace_back(*last, t);
last = t;
} else if (!last) {
/* That was the first time, so start a new period */
}
}
- if (!p.empty()) {
- p.back().to = split_points.back();
+ if (!periods.empty()) {
+ periods.back().to = split_points.back();
}
break;
}
Frame const reel_in_frames = max(_reel_length / ((j2k_bandwidth() / video_frame_rate()) / 8), static_cast<Frame>(video_frame_rate()));
while (current < len) {
DCPTime end = min (len, current + DCPTime::from_frames (reel_in_frames, video_frame_rate ()));
- p.push_back (DCPTimePeriod (current, end));
+ periods.emplace_back(current, end);
current = end;
}
break;
}
+ case ReelType::CUSTOM:
+ {
+ DCPTimePeriod current;
+ for (auto boundary: _custom_reel_boundaries) {
+ current.to = boundary;
+ periods.push_back(current);
+ current.from = boundary;
+ }
+ current.to = len;
+ periods.push_back(current);
+ break;
+ }
}
- return p;
+ return periods;
}
+
/** @param period A period within the DCP
* @return Name of the content which most contributes to the given period.
*/
return _playlist;
}
- std::list<dcpomatic::DCPTimePeriod> reels () const;
+ std::vector<dcpomatic::DCPTimePeriod> reels() const;
std::list<int> mapped_audio_channels () const;
boost::optional<dcp::LanguageTag> audio_language () const {
return _reel_length;
}
+ std::vector<dcpomatic::DCPTime> custom_reel_boundaries() const {
+ return _custom_reel_boundaries;
+ }
+
std::string context_id () const {
return _context_id;
}
void set_audio_processor (AudioProcessor const * processor);
void set_reel_type (ReelType);
void set_reel_length (int64_t);
+ void set_custom_reel_boundaries(std::vector<dcpomatic::DCPTime> boundaries);
void set_reencode_j2k (bool);
void set_marker (dcp::Marker type, dcpomatic::DCPTime time);
void unset_marker (dcp::Marker type);
bool _limit_to_smpte_bv20;
AudioProcessor const * _audio_processor;
ReelType _reel_type;
- /** Desired reel length in bytes, if _reel_type == REELTYPE_BY_LENGTH */
+ /** Desired reel length in bytes, if _reel_type == BY_LENGTH */
int64_t _reel_length;
+ /** Reel boundaries (excluding those at the start and end, sorted in ascending order) if _reel_type == CUSTOM */
+ std::vector<dcpomatic::DCPTime> _custom_reel_boundaries;
bool _reencode_j2k;
/** true if the user has ever explicitly set the video frame rate of this film */
bool _user_explicit_video_frame_rate;
AUDIO_PROCESSOR,
REEL_TYPE,
REEL_LENGTH,
+ CUSTOM_REEL_BOUNDARIES,
REENCODE_J2K,
MARKERS,
RATINGS,
{
SINGLE,
BY_VIDEO_CONTENT,
- BY_LENGTH
+ BY_LENGTH,
+ CUSTOM
};
--- /dev/null
+/*
+ Copyright (C) 2023 Carl Hetherington <cth@carlh.net>
+
+ This file is part of DCP-o-matic.
+
+ DCP-o-matic 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.
+
+ DCP-o-matic 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 DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#define VIDEO_CONTENT_COLOUR (wxColour(242, 92, 120, 255))
+#define AUDIO_CONTENT_COLOUR (wxColour(149, 121, 232, 255))
+#define TEXT_CONTENT_COLOUR (wxColour(163, 255, 154, 255))
+#define ATMOS_CONTENT_COLOUR (wxColour(149, 121, 232, 255))
#include "content_advanced_dialog.h"
#include "content_menu.h"
#include "content_properties_dialog.h"
+#include "content_timeline_audio_view.h"
+#include "content_timeline_video_view.h"
#include "dir_dialog.h"
#include "file_dialog.h"
#include "film_viewer.h"
#include "id.h"
#include "repeat_dialog.h"
-#include "timeline_video_content_view.h"
-#include "timeline_audio_content_view.h"
#include "wx_util.h"
#include "lib/audio_content.h"
#include "lib/config.h"
continue;
}
- shared_ptr<TimelineVideoContentView> video;
- shared_ptr<TimelineAudioContentView> audio;
+ shared_ptr<ContentTimelineVideoView> video;
+ shared_ptr<ContentTimelineAudioView> audio;
for (auto j: _views) {
- auto v = dynamic_pointer_cast<TimelineVideoContentView>(j);
- auto a = dynamic_pointer_cast<TimelineAudioContentView>(j);
+ auto v = dynamic_pointer_cast<ContentTimelineVideoView>(j);
+ auto a = dynamic_pointer_cast<ContentTimelineAudioView>(j);
if (v && v->content() == fc) {
video = v;
} else if (a && a->content() == fc) {
#include "audio_panel.h"
#include "content_panel.h"
+#include "content_timeline_dialog.h"
#include "dcpomatic_button.h"
#include "dir_dialog.h"
#include "file_dialog.h"
#include "film_viewer.h"
#include "image_sequence_dialog.h"
#include "text_panel.h"
-#include "timeline_dialog.h"
#include "timing_panel.h"
#include "video_panel.h"
#include "wx_util.h"
class AudioPanel;
class ContentListCtrl;
class ContentSubPanel;
+class ContentTimelineDialog;
class Film;
class FilmEditor;
class FilmViewer;
class LimitedContentPanelSplitter;
class TextPanel;
-class TimelineDialog;
class TimingPanel;
class VideoPanel;
class wxListCtrl;
EnumIndexedVector<TextPanel*, TextType> _text_panel;
TimingPanel* _timing_panel;
ContentMenu* _menu;
- wx_ptr<TimelineDialog> _timeline_dialog;
+ wx_ptr<ContentTimelineDialog> _timeline_dialog;
wxNotebook* _parent;
wxWindow* _last_selected_tab = nullptr;
--- /dev/null
+/*
+ Copyright (C) 2013-2021 Carl Hetherington <cth@carlh.net>
+
+ This file is part of DCP-o-matic.
+
+ DCP-o-matic 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.
+
+ DCP-o-matic 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 DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "content_panel.h"
+#include "content_timeline.h"
+#include "film_editor.h"
+#include "film_viewer.h"
+#include "content_timeline_atmos_view.h"
+#include "content_timeline_audio_view.h"
+#include "content_timeline_text_view.h"
+#include "content_timeline_video_view.h"
+#include "timeline_labels_view.h"
+#include "timeline_reels_view.h"
+#include "timeline_time_axis_view.h"
+#include "wx_util.h"
+#include "lib/atmos_mxf_content.h"
+#include "lib/audio_content.h"
+#include "lib/constants.h"
+#include "lib/film.h"
+#include "lib/image_content.h"
+#include "lib/playlist.h"
+#include "lib/text_content.h"
+#include "lib/timer.h"
+#include "lib/video_content.h"
+#include <dcp/scope_guard.h>
+#include <dcp/warnings.h>
+LIBDCP_DISABLE_WARNINGS
+#include <wx/graphics.h>
+LIBDCP_ENABLE_WARNINGS
+#include <iterator>
+#include <list>
+
+
+using std::abs;
+using std::dynamic_pointer_cast;
+using std::list;
+using std::make_shared;
+using std::max;
+using std::min;
+using std::shared_ptr;
+using std::weak_ptr;
+using boost::bind;
+using boost::optional;
+using namespace dcpomatic;
+#if BOOST_VERSION >= 106100
+using namespace boost::placeholders;
+#endif
+
+
+int const ContentTimeline::_minimum_pixels_per_track = 16;
+
+
+ContentTimeline::ContentTimeline(wxWindow* parent, ContentPanel* cp, shared_ptr<Film> film, FilmViewer& viewer)
+ : Timeline(parent)
+ , _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))
+ , _tracks (0)
+ , _left_down (false)
+ , _down_view_position (0)
+ , _first_move (false)
+ , _menu (this, viewer)
+ , _snap (true)
+ , _tool (SELECT)
+ , _x_scroll_rate (16)
+ , _y_scroll_rate (16)
+ , _pixels_per_track (48)
+ , _first_resize (true)
+ , _timer (this)
+{
+#ifndef __WXOSX__
+ _labels_canvas->SetDoubleBuffered (true);
+ _main_canvas->SetDoubleBuffered (true);
+#endif
+
+ auto sizer = new wxBoxSizer (wxHORIZONTAL);
+ sizer->Add (_labels_canvas, 0, wxEXPAND);
+ _labels_canvas->SetMinSize (wxSize (_labels_view->bbox().width, -1));
+ sizer->Add (_main_canvas, 1, wxEXPAND);
+ SetSizer (sizer);
+
+ _labels_canvas->Bind(wxEVT_PAINT, boost::bind(&ContentTimeline::paint_labels, this));
+ _main_canvas->Bind (wxEVT_PAINT, boost::bind(&ContentTimeline::paint_main, this));
+ _main_canvas->Bind (wxEVT_LEFT_DOWN, boost::bind(&ContentTimeline::left_down, this, _1));
+ _main_canvas->Bind (wxEVT_LEFT_UP, boost::bind(&ContentTimeline::left_up, this, _1));
+ _main_canvas->Bind (wxEVT_RIGHT_DOWN, boost::bind(&ContentTimeline::right_down, this, _1));
+ _main_canvas->Bind (wxEVT_MOTION, boost::bind(&ContentTimeline::mouse_moved, this, _1));
+ _main_canvas->Bind (wxEVT_SIZE, boost::bind(&ContentTimeline::resized, this));
+ _main_canvas->Bind (wxEVT_MOUSEWHEEL, boost::bind(&ContentTimeline::mouse_wheel_turned, this, _1));
+ _main_canvas->Bind (wxEVT_SCROLLWIN_TOP, boost::bind(&ContentTimeline::scrolled, this, _1));
+ _main_canvas->Bind (wxEVT_SCROLLWIN_BOTTOM, boost::bind(&ContentTimeline::scrolled, this, _1));
+ _main_canvas->Bind (wxEVT_SCROLLWIN_LINEUP, boost::bind(&ContentTimeline::scrolled, this, _1));
+ _main_canvas->Bind (wxEVT_SCROLLWIN_LINEDOWN, boost::bind(&ContentTimeline::scrolled, this, _1));
+ _main_canvas->Bind (wxEVT_SCROLLWIN_PAGEUP, boost::bind(&ContentTimeline::scrolled, this, _1));
+ _main_canvas->Bind (wxEVT_SCROLLWIN_PAGEDOWN, boost::bind(&ContentTimeline::scrolled, this, _1));
+ _main_canvas->Bind (wxEVT_SCROLLWIN_THUMBTRACK, boost::bind(&ContentTimeline::scrolled, this, _1));
+
+ film_change(ChangeType::DONE, FilmProperty::CONTENT);
+
+ SetMinSize (wxSize (640, 4 * pixels_per_track() + 96));
+
+ _film_changed_connection = film->Change.connect(bind(&ContentTimeline::film_change, this, _1, _2));
+ _film_content_change_connection = film->ContentChange.connect(bind(&ContentTimeline::film_content_change, this, _1, _3, _4));
+
+ Bind(wxEVT_TIMER, boost::bind(&ContentTimeline::update_playhead, this));
+ _timer.Start (200, wxTIMER_CONTINUOUS);
+
+ setup_scrollbars ();
+ _labels_canvas->ShowScrollbars (wxSHOW_SB_NEVER, wxSHOW_SB_NEVER);
+}
+
+
+void
+ContentTimeline::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
+ContentTimeline::update_playhead()
+{
+ Refresh ();
+}
+
+
+void
+ContentTimeline::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, {});
+}
+
+
+void
+ContentTimeline::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);
+ if (!gc) {
+ return;
+ }
+
+ dcp::ScopeGuard sg = [gc]() { delete gc; };
+
+ gc->SetAntialiasMode (wxANTIALIAS_DEFAULT);
+
+ for (auto i: _views) {
+
+ auto ic = dynamic_pointer_cast<TimelineContentView> (i);
+
+ /* Find areas of overlap with other content views, so that we can plot them */
+ list<dcpomatic::Rect<int>> overlaps;
+ for (auto j: _views) {
+ auto jc = dynamic_pointer_cast<TimelineContentView> (j);
+ /* 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;
+ }
+
+ auto r = j->bbox().intersection(i->bbox());
+ if (r) {
+ overlaps.push_back (r.get ());
+ }
+ }
+
+ i->paint (gc, overlaps);
+ }
+
+ if (_zoom_point) {
+ gc->SetPen(gui_is_dark() ? *wxWHITE_PEN : *wxBLACK_PEN);
+ gc->SetBrush (*wxTRANSPARENT_BRUSH);
+ gc->DrawRectangle (
+ min (_down_point.x, _zoom_point->x),
+ min (_down_point.y, _zoom_point->y),
+ abs (_down_point.x - _zoom_point->x),
+ abs (_down_point.y - _zoom_point->y)
+ );
+ }
+
+ /* Playhead */
+
+ gc->SetPen (*wxRED_PEN);
+ auto path = gc->CreatePath ();
+ 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);
+}
+
+
+void
+ContentTimeline::film_change(ChangeType type, FilmProperty p)
+{
+ if (type != ChangeType::DONE) {
+ return;
+ }
+
+ if (p == FilmProperty::CONTENT || p == FilmProperty::REEL_TYPE || p == FilmProperty::REEL_LENGTH) {
+ ensure_ui_thread ();
+ recreate_views ();
+ } else if (p == FilmProperty::CONTENT_ORDER) {
+ Refresh ();
+ }
+}
+
+
+void
+ContentTimeline::recreate_views()
+{
+ auto film = _film.lock ();
+ if (!film) {
+ return;
+ }
+
+ _views.clear ();
+ _views.push_back (_time_axis_view);
+ _views.push_back (_reels_view);
+
+ for (auto i: film->content ()) {
+ if (i->video) {
+ _views.push_back(make_shared<ContentTimelineVideoView>(*this, i));
+ }
+
+ if (i->audio && !i->audio->mapping().mapped_output_channels().empty ()) {
+ _views.push_back(make_shared<ContentTimelineAudioView>(*this, i));
+ }
+
+ for (auto j: i->text) {
+ _views.push_back(make_shared<ContentTimelineTextView>(*this, i, j));
+ }
+
+ if (i->atmos) {
+ _views.push_back(make_shared<ContentTimelineAtmosView>(*this, i));
+ }
+ }
+
+ assign_tracks ();
+ setup_scrollbars ();
+ Refresh ();
+}
+
+
+void
+ContentTimeline::film_content_change(ChangeType type, int property, bool frequent)
+{
+ if (type != ChangeType::DONE) {
+ return;
+ }
+
+ ensure_ui_thread ();
+
+ 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(shared_ptr<const Film> film, ContentTimelineViewList& views, int& tracks)
+{
+ int const base = tracks;
+
+ for (auto i: views) {
+ if (!dynamic_pointer_cast<T>(i)) {
+ continue;
+ }
+
+ auto cv = dynamic_pointer_cast<TimelineContentView> (i);
+ DCPOMATIC_ASSERT(cv);
+
+ int t = base;
+
+ auto content = cv->content();
+ DCPTimePeriod const content_period = content->period(film);
+
+ while (true) {
+ auto j = views.begin();
+ while (j != views.end()) {
+ auto test = dynamic_pointer_cast<T> (*j);
+ if (!test) {
+ ++j;
+ continue;
+ }
+
+ auto test_content = test->content();
+ if (
+ test->track() && test->track().get() == t &&
+ content_period.overlap(test_content->period(film))
+ ) {
+ /* we have an overlap on track `t' */
+ ++t;
+ break;
+ }
+
+ ++j;
+ }
+
+ if (j == views.end ()) {
+ /* no overlap on `t' */
+ break;
+ }
+ }
+
+ cv->set_track (t);
+ tracks = max (tracks, t + 1);
+ }
+
+ 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<ContentTimelineView> a, shared_ptr<ContentTimelineView> b) {
+ int la = -1;
+ auto cva = dynamic_pointer_cast<ContentTimelineAudioView>(a);
+ if (cva) {
+ auto oc = cva->content()->audio->mapping().mapped_output_channels();
+ la = *min_element(boost::begin(oc), boost::end(oc));
+ }
+ int lb = -1;
+ auto cvb = dynamic_pointer_cast<ContentTimelineAudioView>(b);
+ if (cvb) {
+ auto oc = cvb->content()->audio->mapping().mapped_output_channels();
+ lb = *min_element(boost::begin(oc), boost::end(oc));
+ }
+ return la < lb;
+ }
+};
+
+
+void
+ContentTimeline::assign_tracks()
+{
+ /* Tracks are:
+ Video 1
+ Video 2
+ Video N
+ Text 1
+ Text 2
+ Text N
+ Atmos
+ Audio 1
+ Audio 2
+ Audio N
+ */
+
+ auto film = _film.lock ();
+ DCPOMATIC_ASSERT (film);
+
+ _tracks = 0;
+
+ for (auto i: _views) {
+ auto c = dynamic_pointer_cast<TimelineContentView>(i);
+ if (c) {
+ c->unset_track ();
+ }
+ }
+
+ int const video_tracks = place<ContentTimelineVideoView>(film, _views, _tracks);
+ int const text_tracks = place<ContentTimelineTextView>(film, _views, _tracks);
+
+ /* Atmos */
+
+ bool have_atmos = false;
+ for (auto i: _views) {
+ auto cv = dynamic_pointer_cast<ContentTimelineAtmosView>(i);
+ if (cv) {
+ cv->set_track (_tracks);
+ have_atmos = true;
+ }
+ }
+
+ if (have_atmos) {
+ ++_tracks;
+ }
+
+ /* Audio. We're sorting the views so that we get the audio views in order of increasing
+ DCP channel index.
+ */
+
+ auto views = _views;
+ sort(views.begin(), views.end(), AudioMappingComparator());
+ int const audio_tracks = place<ContentTimelineAudioView>(film, views, _tracks);
+
+ _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);
+
+ _time_axis_view->set_y (tracks());
+ _reels_view->set_y (8);
+}
+
+
+int
+ContentTimeline::tracks() const
+{
+ return _tracks;
+}
+
+
+void
+ContentTimeline::setup_scrollbars()
+{
+ auto film = _film.lock ();
+ if (!film || !_pixels_per_second) {
+ return;
+ }
+
+ 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(), h);
+ _main_canvas->SetScrollRate (_x_scroll_rate, _y_scroll_rate);
+}
+
+
+shared_ptr<ContentTimelineView>
+ContentTimeline::event_to_view(wxMouseEvent& ev)
+{
+ /* Search backwards through views so that we find the uppermost one first */
+ auto i = _views.rbegin();
+
+ 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)) {
+ ++i;
+ }
+
+ if (i == _views.rend ()) {
+ return {};
+ }
+
+ return *i;
+}
+
+
+void
+ContentTimeline::left_down(wxMouseEvent& ev)
+{
+ _left_down = true;
+ _down_point = ev.GetPosition ();
+
+ switch (_tool) {
+ case SELECT:
+ left_down_select (ev);
+ break;
+ case ZOOM:
+ case ZOOM_ALL:
+ case SNAP:
+ case SEQUENCE:
+ /* Nothing to do */
+ break;
+ }
+}
+
+
+void
+ContentTimeline::left_down_select(wxMouseEvent& ev)
+{
+ auto view = event_to_view (ev);
+ auto content_view = dynamic_pointer_cast<TimelineContentView>(view);
+
+ _down_view.reset ();
+
+ if (content_view) {
+ _down_view = content_view;
+ _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) {
+ continue;
+ }
+
+ if (!ev.ShiftDown ()) {
+ cv->set_selected (view == i);
+ }
+ }
+
+ if (content_view && ev.ShiftDown ()) {
+ content_view->set_selected (!content_view->selected ());
+ }
+
+ _first_move = false;
+
+ if (_down_view) {
+ /* Pre-compute the points that we might snap to */
+ for (auto i: _views) {
+ auto cv = dynamic_pointer_cast<TimelineContentView>(i);
+ if (!cv || cv == _down_view || cv->content() == _down_view->content()) {
+ continue;
+ }
+
+ auto 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(film));
+ _end_snaps.push_back (cv->content()->end(film));
+
+ for (auto i: cv->content()->reel_split_points(film)) {
+ _start_snaps.push_back (i);
+ }
+ }
+
+ /* Tell everyone that things might change frequently during the drag */
+ _down_view->content()->set_change_signals_frequent (true);
+ }
+}
+
+
+void
+ContentTimeline::left_up(wxMouseEvent& ev)
+{
+ _left_down = false;
+
+ switch (_tool) {
+ case SELECT:
+ left_up_select (ev);
+ break;
+ case ZOOM:
+ left_up_zoom (ev);
+ break;
+ case ZOOM_ALL:
+ case SNAP:
+ case SEQUENCE:
+ break;
+ }
+}
+
+
+void
+ContentTimeline::left_up_select(wxMouseEvent& ev)
+{
+ if (_down_view) {
+ _down_view->content()->set_change_signals_frequent (false);
+ }
+
+ _content_panel->set_selection (selected_content ());
+ /* 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 ();
+ setup_scrollbars ();
+ Refresh ();
+
+ _start_snaps.clear ();
+ _end_snaps.clear ();
+}
+
+
+void
+ContentTimeline::left_up_zoom(wxMouseEvent& ev)
+{
+ _zoom_point = ev.GetPosition ();
+
+ int vsx, vsy;
+ _main_canvas->GetViewStart (&vsx, &vsy);
+
+ wxPoint top_left(min(_down_point.x, _zoom_point->x), min(_down_point.y, _zoom_point->y));
+ wxPoint bottom_right(max(_down_point.x, _zoom_point->x), max(_down_point.y, _zoom_point->y));
+
+ 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;
+ }
+
+ auto const time_left = DCPTime::from_seconds((top_left.x + vsx) / *_pixels_per_second);
+ auto 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 - 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 ();
+ 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 ();
+}
+
+
+void
+ContentTimeline::set_pixels_per_track(int h)
+{
+ _pixels_per_track = max(_minimum_pixels_per_track, h);
+}
+
+
+void
+ContentTimeline::mouse_moved(wxMouseEvent& ev)
+{
+ switch (_tool) {
+ case SELECT:
+ mouse_moved_select (ev);
+ break;
+ case ZOOM:
+ mouse_moved_zoom (ev);
+ break;
+ case ZOOM_ALL:
+ case SNAP:
+ case SEQUENCE:
+ break;
+ }
+}
+
+
+void
+ContentTimeline::mouse_moved_select(wxMouseEvent& ev)
+{
+ if (!_left_down) {
+ return;
+ }
+
+ set_position_from_event (ev);
+}
+
+
+void
+ContentTimeline::mouse_moved_zoom(wxMouseEvent& ev)
+{
+ if (!_left_down) {
+ return;
+ }
+
+ _zoom_point = ev.GetPosition ();
+ setup_scrollbars();
+ Refresh ();
+}
+
+
+void
+ContentTimeline::right_down(wxMouseEvent& ev)
+{
+ switch (_tool) {
+ case SELECT:
+ right_down_select (ev);
+ break;
+ case ZOOM:
+ /* Zoom out */
+ set_pixels_per_second (*_pixels_per_second / 2);
+ set_pixels_per_track (_pixels_per_track / 2);
+ setup_scrollbars ();
+ Refresh ();
+ break;
+ case ZOOM_ALL:
+ case SNAP:
+ case SEQUENCE:
+ break;
+ }
+}
+
+
+void
+ContentTimeline::right_down_select(wxMouseEvent& ev)
+{
+ auto view = event_to_view (ev);
+ auto cv = dynamic_pointer_cast<TimelineContentView> (view);
+ if (!cv) {
+ return;
+ }
+
+ if (!cv->selected ()) {
+ clear_selection ();
+ cv->set_selected (true);
+ }
+
+ _menu.popup (_film, selected_content (), selected_views (), ev.GetPosition ());
+}
+
+
+void
+ContentTimeline::maybe_snap(DCPTime a, DCPTime b, optional<DCPTime>& nearest_distance) const
+{
+ auto const d = a - b;
+ if (!nearest_distance || d.abs() < nearest_distance.get().abs()) {
+ nearest_distance = d;
+ }
+}
+
+
+void
+ContentTimeline::set_position_from_event(wxMouseEvent& ev, bool force_emit)
+{
+ if (!_pixels_per_second) {
+ return;
+ }
+
+ double const pps = _pixels_per_second.get ();
+
+ auto const p = ev.GetPosition();
+
+ if (!_first_move) {
+ /* We haven't moved yet; in that case, we must move the mouse some reasonable distance
+ before the drag is considered to have started.
+ */
+ int const dist = sqrt (pow (p.x - _down_point.x, 2) + pow (p.y - _down_point.y, 2));
+ if (dist < 8) {
+ return;
+ }
+ _first_move = true;
+ }
+
+ if (!_down_view) {
+ return;
+ }
+
+ auto new_position = _down_view_position + DCPTime::from_seconds ((p.x - _down_point.x) / pps);
+
+ auto film = _film.lock ();
+ DCPOMATIC_ASSERT (film);
+
+ if (_snap) {
+ auto 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).
+ */
+ optional<DCPTime> nearest_distance;
+
+ /* Find the nearest snap point */
+
+ for (auto i: _start_snaps) {
+ maybe_snap (i, new_position, nearest_distance);
+ }
+
+ for (auto i: _end_snaps) {
+ maybe_snap (i, new_end, nearest_distance);
+ }
+
+ if (nearest_distance) {
+ /* Snap if it's close; `close' means within a proportion of the time on the timeline */
+ if (nearest_distance.get().abs() < DCPTime::from_seconds ((width() / pps) / SNAP_SUBDIVISION)) {
+ new_position += nearest_distance.get ();
+ }
+ }
+ }
+
+ if (new_position < DCPTime ()) {
+ new_position = DCPTime ();
+ }
+
+ _down_view->content()->set_position (film, new_position, force_emit);
+
+ film->set_sequence (false);
+}
+
+
+void
+ContentTimeline::force_redraw(dcpomatic::Rect<int> const & r)
+{
+ _main_canvas->RefreshRect (wxRect (r.x, r.y, r.width, r.height), false);
+}
+
+
+shared_ptr<const Film>
+ContentTimeline::film() const
+{
+ return _film.lock ();
+}
+
+
+void
+ContentTimeline::resized()
+{
+ if (_main_canvas->GetSize().GetWidth() > 0 && _first_resize) {
+ zoom_all ();
+ _first_resize = false;
+ }
+ setup_scrollbars ();
+}
+
+
+void
+ContentTimeline::clear_selection()
+{
+ for (auto i: _views) {
+ shared_ptr<TimelineContentView> cv = dynamic_pointer_cast<TimelineContentView>(i);
+ if (cv) {
+ cv->set_selected (false);
+ }
+ }
+}
+
+
+TimelineContentViewList
+ContentTimeline::selected_views() const
+{
+ TimelineContentViewList sel;
+
+ for (auto i: _views) {
+ auto cv = dynamic_pointer_cast<TimelineContentView>(i);
+ if (cv && cv->selected()) {
+ sel.push_back (cv);
+ }
+ }
+
+ return sel;
+}
+
+
+ContentList
+ContentTimeline::selected_content() const
+{
+ ContentList sel;
+
+ for (auto i: selected_views()) {
+ sel.push_back(i->content());
+ }
+
+ return sel;
+}
+
+
+void
+ContentTimeline::set_selection(ContentList selection)
+{
+ for (auto i: _views) {
+ auto cv = dynamic_pointer_cast<TimelineContentView> (i);
+ if (cv) {
+ cv->set_selected (find (selection.begin(), selection.end(), cv->content ()) != selection.end ());
+ }
+ }
+}
+
+
+int
+ContentTimeline::tracks_y_offset() const
+{
+ return _reels_view->bbox().height + 4;
+}
+
+
+int
+ContentTimeline::width() const
+{
+ return _main_canvas->GetVirtualSize().GetWidth();
+}
+
+
+void
+ContentTimeline::scrolled(wxScrollWinEvent& ev)
+{
+ if (ev.GetOrientation() == wxVERTICAL) {
+ int x, y;
+ _main_canvas->GetViewStart (&x, &y);
+ _labels_canvas->Scroll (0, y);
+ }
+ ev.Skip ();
+}
+
+
+void
+ContentTimeline::tool_clicked(Tool t)
+{
+ switch (t) {
+ case ZOOM:
+ case SELECT:
+ _tool = t;
+ break;
+ case ZOOM_ALL:
+ zoom_all ();
+ break;
+ case SNAP:
+ case SEQUENCE:
+ break;
+ }
+}
+
+
+void
+ContentTimeline::zoom_all()
+{
+ auto film = _film.lock ();
+ DCPOMATIC_ASSERT (film);
+ 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
+ContentTimeline::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;
+ }
+ }
+}
+
--- /dev/null
+/*
+ Copyright (C) 2013-2019 Carl Hetherington <cth@carlh.net>
+
+ This file is part of DCP-o-matic.
+
+ DCP-o-matic 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.
+
+ DCP-o-matic 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 DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "content_menu.h"
+#include "timeline.h"
+#include "timeline_content_view.h"
+#include "lib/film_property.h"
+#include "lib/rect.h"
+#include <dcp/warnings.h>
+LIBDCP_DISABLE_WARNINGS
+#include <wx/wx.h>
+LIBDCP_ENABLE_WARNINGS
+#include <boost/signals2.hpp>
+
+
+class ContentPanel;
+class ContentTimelineView;
+class Film;
+class FilmViewer;
+class TimelineLabelsView;
+class TimelineReelsView;
+class TimelineTimeAxisView;
+
+
+class ContentTimeline : public Timeline
+{
+public:
+ ContentTimeline(wxWindow *, ContentPanel *, std::shared_ptr<Film>, FilmViewer& viewer);
+
+ std::shared_ptr<const Film> film () const;
+
+ void force_redraw (dcpomatic::Rect<int> const &);
+
+ int width () const;
+
+ int pixels_per_track () const {
+ return _pixels_per_track;
+ }
+
+ int tracks () const;
+
+ void set_snap (bool s) {
+ _snap = s;
+ }
+
+ bool snap () const {
+ return _snap;
+ }
+
+ void set_selection (ContentList selection);
+
+ enum Tool {
+ SELECT,
+ ZOOM,
+ ZOOM_ALL,
+ SNAP,
+ SEQUENCE
+ };
+
+ void tool_clicked (Tool t);
+
+ int tracks_y_offset () const;
+
+ void keypress(wxKeyEvent const &);
+
+private:
+ void paint_labels ();
+ void paint_main ();
+ void left_down (wxMouseEvent &);
+ void left_down_select (wxMouseEvent &);
+ void left_up (wxMouseEvent &);
+ void left_up_select (wxMouseEvent &);
+ void left_up_zoom (wxMouseEvent &);
+ void right_down (wxMouseEvent &);
+ void right_down_select (wxMouseEvent &);
+ void mouse_moved (wxMouseEvent &);
+ void mouse_moved_select (wxMouseEvent &);
+ void mouse_moved_zoom (wxMouseEvent &);
+ void film_change(ChangeType type, FilmProperty);
+ void film_content_change (ChangeType type, int, bool frequent);
+ void resized ();
+ void assign_tracks ();
+ void set_position_from_event (wxMouseEvent& ev, bool force_emit = false);
+ void clear_selection ();
+ void recreate_views ();
+ void setup_scrollbars ();
+ void scrolled (wxScrollWinEvent& ev);
+ void set_pixels_per_track (int h);
+ void zoom_all ();
+ void update_playhead ();
+ void mouse_wheel_turned(wxMouseEvent& event);
+
+ std::shared_ptr<ContentTimelineView> event_to_view(wxMouseEvent &);
+ TimelineContentViewList selected_views () const;
+ ContentList selected_content () const;
+ void maybe_snap (dcpomatic::DCPTime a, dcpomatic::DCPTime b, boost::optional<dcpomatic::DCPTime>& nearest_distance) const;
+
+ wxScrolledCanvas* _labels_canvas;
+ wxScrolledCanvas* _main_canvas;
+ ContentPanel* _content_panel;
+ std::weak_ptr<Film> _film;
+ FilmViewer& _viewer;
+ ContentTimelineViewList _views;
+ std::shared_ptr<TimelineTimeAxisView> _time_axis_view;
+ std::shared_ptr<TimelineReelsView> _reels_view;
+ std::shared_ptr<TimelineLabelsView> _labels_view;
+ int _tracks;
+ bool _left_down;
+ wxPoint _down_point;
+ boost::optional<wxPoint> _zoom_point;
+ std::shared_ptr<TimelineContentView> _down_view;
+ dcpomatic::DCPTime _down_view_position;
+ bool _first_move;
+ ContentMenu _menu;
+ bool _snap;
+ std::list<dcpomatic::DCPTime> _start_snaps;
+ std::list<dcpomatic::DCPTime> _end_snaps;
+ Tool _tool;
+ int _x_scroll_rate;
+ int _y_scroll_rate;
+ int _pixels_per_track;
+ bool _first_resize;
+ wxTimer _timer;
+ boost::optional<int> _last_mouse_wheel_x;
+ boost::optional<double> _last_mouse_wheel_time;
+
+ static int const _minimum_pixels_per_track;
+
+ boost::signals2::scoped_connection _film_changed_connection;
+ boost::signals2::scoped_connection _film_content_change_connection;
+};
--- /dev/null
+/*
+ Copyright (C) 2016-2021 Carl Hetherington <cth@carlh.net>
+
+ This file is part of DCP-o-matic.
+
+ DCP-o-matic 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.
+
+ DCP-o-matic 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 DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "colours.h"
+#include "content_timeline_atmos_view.h"
+
+
+using std::shared_ptr;
+
+
+/** @class ContentTimelineContentView
+ * @brief Content timeline view for AtmosContent.
+ */
+
+ContentTimelineAtmosView::ContentTimelineAtmosView(ContentTimeline& tl, shared_ptr<Content> c)
+ : TimelineContentView (tl, c)
+{
+
+}
+
+
+wxColour
+ContentTimelineAtmosView::background_colour() const
+{
+ return ATMOS_CONTENT_COLOUR;
+}
+
+
+wxColour
+ContentTimelineAtmosView::foreground_colour() const
+{
+ return wxColour (0, 0, 0, 255);
+}
--- /dev/null
+/*
+ Copyright (C) 2016-2021 Carl Hetherington <cth@carlh.net>
+
+ This file is part of DCP-o-matic.
+
+ DCP-o-matic 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.
+
+ DCP-o-matic 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 DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "timeline_content_view.h"
+
+
+/** @class ContentTimelineAtmosContentView
+ * @brief Content timeline view for AtmosContent.
+ */
+class ContentTimelineAtmosView : public TimelineContentView
+{
+public:
+ ContentTimelineAtmosView(ContentTimeline& tl, std::shared_ptr<Content> c);
+
+private:
+ bool active () const override {
+ return true;
+ }
+
+ wxColour background_colour () const override;
+ wxColour foreground_colour () const override;
+};
--- /dev/null
+/*
+ Copyright (C) 2013-2018 Carl Hetherington <cth@carlh.net>
+
+ This file is part of DCP-o-matic.
+
+ DCP-o-matic 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.
+
+ DCP-o-matic 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 DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "colours.h"
+#include "content_timeline_audio_view.h"
+#include "wx_util.h"
+#include "lib/audio_content.h"
+#include "lib/util.h"
+
+
+using std::dynamic_pointer_cast;
+using std::list;
+using std::shared_ptr;
+
+
+/** @class ContentTimelineAudioView
+ * @brief Content timeline view for AudioContent.
+ */
+
+
+ContentTimelineAudioView::ContentTimelineAudioView(ContentTimeline& tl, shared_ptr<Content> c)
+ : TimelineContentView (tl, c)
+{
+
+}
+
+wxColour
+ContentTimelineAudioView::background_colour () const
+{
+ return AUDIO_CONTENT_COLOUR;
+}
+
+wxColour
+ContentTimelineAudioView::foreground_colour () const
+{
+ return wxColour (0, 0, 0, 255);
+}
+
+wxString
+ContentTimelineAudioView::label () const
+{
+ wxString s = TimelineContentView::label ();
+ shared_ptr<AudioContent> ac = content()->audio;
+ DCPOMATIC_ASSERT (ac);
+
+ if (ac->gain() > 0.01) {
+ s += wxString::Format (" +%.1fdB", ac->gain());
+ } else if (ac->gain() < -0.01) {
+ s += wxString::Format (" %.1fdB", ac->gain());
+ }
+
+ if (ac->delay() > 0) {
+ s += wxString::Format (_(" delayed by %dms"), ac->delay());
+ } else if (ac->delay() < 0) {
+ s += wxString::Format (_(" advanced by %dms"), -ac->delay());
+ }
+
+ list<int> mapped = ac->mapping().mapped_output_channels();
+ if (!mapped.empty ()) {
+ s += wxString::FromUTF8(" → ");
+ for (auto i: mapped) {
+ s += std_to_wx(short_audio_channel_name(i)) + ", ";
+ }
+ s = s.Left(s.Length() - 2);
+ }
+
+ return s;
+}
--- /dev/null
+/*
+ Copyright (C) 2013-2015 Carl Hetherington <cth@carlh.net>
+
+ This file is part of DCP-o-matic.
+
+ DCP-o-matic 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.
+
+ DCP-o-matic 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 DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "timeline_content_view.h"
+
+
+/** @class ContentTimelineAudioView
+ * @brief Content timeline view for AudioContent.
+ */
+class ContentTimelineAudioView : public TimelineContentView
+{
+public:
+ ContentTimelineAudioView(ContentTimeline& tl, std::shared_ptr<Content> c);
+
+private:
+ bool active () const override {
+ return true;
+ }
+ wxColour background_colour () const override;
+ wxColour foreground_colour () const override;
+ wxString label () const override;
+};
--- /dev/null
+/*
+ Copyright (C) 2013-2021 Carl Hetherington <cth@carlh.net>
+
+ This file is part of DCP-o-matic.
+
+ DCP-o-matic 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.
+
+ DCP-o-matic 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 DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "content_panel.h"
+#include "content_timeline_dialog.h"
+#include "film_editor.h"
+#include "wx_util.h"
+#include "lib/compose.hpp"
+#include "lib/cross.h"
+#include "lib/film.h"
+#include "lib/playlist.h"
+#include <dcp/warnings.h>
+LIBDCP_DISABLE_WARNINGS
+#include <wx/graphics.h>
+LIBDCP_ENABLE_WARNINGS
+#include <list>
+
+
+using std::list;
+using std::shared_ptr;
+using std::string;
+using std::weak_ptr;
+#if BOOST_VERSION >= 106100
+using namespace boost::placeholders;
+#endif
+
+
+ContentTimelineDialog::ContentTimelineDialog(ContentPanel* cp, shared_ptr<Film> film, FilmViewer& viewer)
+ : wxDialog (
+ cp->window(),
+ wxID_ANY,
+ _("Timeline"),
+ wxDefaultPosition,
+ wxSize (640, 512),
+#ifdef DCPOMATIC_OSX
+ /* I can't get wxFRAME_FLOAT_ON_PARENT to work on OS X, and although wxSTAY_ON_TOP keeps
+ the window above all others (and not just our own) it's better than nothing for now.
+ */
+ wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxFULL_REPAINT_ON_RESIZE | wxSTAY_ON_TOP
+#else
+ wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxFULL_REPAINT_ON_RESIZE | wxFRAME_FLOAT_ON_PARENT
+#endif
+ )
+ , _film (film)
+ , _timeline (this, cp, film, viewer)
+{
+ auto sizer = new wxBoxSizer (wxVERTICAL);
+
+ wxBitmap select(icon_path("select"), wxBITMAP_TYPE_PNG);
+ wxBitmap zoom(icon_path("zoom"), wxBITMAP_TYPE_PNG);
+ wxBitmap zoom_all(icon_path("zoom_all"), wxBITMAP_TYPE_PNG);
+ wxBitmap snap(icon_path("snap"), wxBITMAP_TYPE_PNG);
+ wxBitmap sequence(icon_path("sequence"), wxBITMAP_TYPE_PNG);
+
+ _toolbar = new wxToolBar (this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTB_HORIZONTAL);
+ _toolbar->SetMargins (4, 4);
+ _toolbar->SetToolBitmapSize (wxSize(32, 32));
+ _toolbar->AddRadioTool(static_cast<int>(ContentTimeline::SELECT), _("Select"), select, wxNullBitmap, _("Select and move content"));
+ _toolbar->AddRadioTool(static_cast<int>(ContentTimeline::ZOOM), _("Zoom"), zoom, wxNullBitmap, _("Zoom in / out"));
+ _toolbar->AddTool(static_cast<int>(ContentTimeline::ZOOM_ALL), _("Zoom all"), zoom_all, _("Zoom out to whole film"));
+ _toolbar->AddCheckTool(static_cast<int>(ContentTimeline::SNAP), _("Snap"), snap, wxNullBitmap, _("Snap"));
+ _toolbar->AddCheckTool(static_cast<int>(ContentTimeline::SEQUENCE), _("Sequence"), sequence, wxNullBitmap, _("Keep video and subtitles in sequence"));
+ _toolbar->Realize ();
+
+ _toolbar->Bind(wxEVT_TOOL, bind(&ContentTimelineDialog::tool_clicked, this, _1));
+
+ sizer->Add (_toolbar, 0, wxALL, 12);
+ sizer->Add (&_timeline, 1, wxEXPAND | wxALL, 12);
+
+#ifdef DCPOMATIC_LINUX
+ auto buttons = CreateSeparatedButtonSizer (wxCLOSE);
+ if (buttons) {
+ sizer->Add (buttons, wxSizerFlags().Expand().DoubleBorder());
+ }
+#endif
+
+ SetSizer (sizer);
+ sizer->Layout ();
+ sizer->SetSizeHints (this);
+
+ Bind(wxEVT_CHAR_HOOK, boost::bind(&ContentTimelineDialog::keypress, this, _1));
+
+ _toolbar->ToggleTool(static_cast<int>(ContentTimeline::SNAP), _timeline.snap ());
+ film_change(ChangeType::DONE, FilmProperty::SEQUENCE);
+
+ _film_changed_connection = film->Change.connect(bind(&ContentTimelineDialog::film_change, this, _1, _2));
+}
+
+
+void
+ContentTimelineDialog::film_change(ChangeType type, FilmProperty p)
+{
+ if (type != ChangeType::DONE) {
+ return;
+ }
+
+ auto film = _film.lock ();
+ if (!film) {
+ return;
+ }
+
+ if (p == FilmProperty::SEQUENCE) {
+ _toolbar->ToggleTool(static_cast<int>(ContentTimeline::SEQUENCE), film->sequence());
+ }
+}
+
+
+void
+ContentTimelineDialog::set_selection(ContentList selection)
+{
+ _timeline.set_selection (selection);
+}
+
+
+void
+ContentTimelineDialog::tool_clicked(wxCommandEvent& ev)
+{
+ auto t = static_cast<ContentTimeline::Tool>(ev.GetId());
+ _timeline.tool_clicked (t);
+ if (t == ContentTimeline::SNAP) {
+ _timeline.set_snap (_toolbar->GetToolState(static_cast<int>(t)));
+ } else if (t == ContentTimeline::SEQUENCE) {
+ auto film = _film.lock ();
+ if (film) {
+ film->set_sequence (_toolbar->GetToolState(static_cast<int>(t)));
+ }
+ }
+}
+
+
+void
+ContentTimelineDialog::keypress(wxKeyEvent const& event)
+{
+ _timeline.keypress(event);
+}
--- /dev/null
+/*
+ Copyright (C) 2013-2021 Carl Hetherington <cth@carlh.net>
+
+ This file is part of DCP-o-matic.
+
+ DCP-o-matic 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.
+
+ DCP-o-matic 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 DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "content_timeline.h"
+#include <dcp/warnings.h>
+LIBDCP_DISABLE_WARNINGS
+#include <wx/wx.h>
+LIBDCP_ENABLE_WARNINGS
+
+
+class Playlist;
+
+
+class ContentTimelineDialog : public wxDialog
+{
+public:
+ ContentTimelineDialog(ContentPanel *, std::shared_ptr<Film>, FilmViewer& viewer);
+
+ void set_selection (ContentList selection);
+
+private:
+ void film_change(ChangeType type, FilmProperty);
+ void tool_clicked (wxCommandEvent& id);
+ void keypress(wxKeyEvent const& event);
+
+ std::weak_ptr<Film> _film;
+ ContentTimeline _timeline;
+ wxToolBar* _toolbar;
+ boost::signals2::scoped_connection _film_changed_connection;
+};
--- /dev/null
+/*
+ Copyright (C) 2013-2018 Carl Hetherington <cth@carlh.net>
+
+ This file is part of DCP-o-matic.
+
+ DCP-o-matic 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.
+
+ DCP-o-matic 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 DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "colours.h"
+#include "content_timeline_text_view.h"
+#include "lib/text_content.h"
+#include "lib/content.h"
+
+
+using std::shared_ptr;
+
+
+ContentTimelineTextView::ContentTimelineTextView(ContentTimeline& tl, shared_ptr<Content> c, shared_ptr<TextContent> caption)
+ : TimelineContentView (tl, c)
+ , _caption (caption)
+{
+
+}
+
+wxColour
+ContentTimelineTextView::background_colour() const
+{
+ if (!active ()) {
+ return wxColour (210, 210, 210, 128);
+ }
+
+ return TEXT_CONTENT_COLOUR;
+}
+
+wxColour
+ContentTimelineTextView::foreground_colour() const
+{
+ if (!active ()) {
+ return wxColour (180, 180, 180, 128);
+ }
+
+ return wxColour (0, 0, 0, 255);
+}
+
+bool
+ContentTimelineTextView::active() const
+{
+ return _caption->use();
+}
--- /dev/null
+/*
+ Copyright (C) 2013-2016 Carl Hetherington <cth@carlh.net>
+
+ This file is part of DCP-o-matic.
+
+ DCP-o-matic 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.
+
+ DCP-o-matic 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 DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "timeline_content_view.h"
+
+
+class TextContent;
+
+
+/** @class ContentTimelineTextView
+ * @brief Content timeline view for TextContent.
+ */
+class ContentTimelineTextView : public TimelineContentView
+{
+public:
+ ContentTimelineTextView(ContentTimeline& tl, std::shared_ptr<Content>, std::shared_ptr<TextContent>);
+
+private:
+ bool active () const override;
+ wxColour background_colour () const override;
+ wxColour foreground_colour () const override;
+
+ std::shared_ptr<TextContent> _caption;
+};
--- /dev/null
+/*
+ Copyright (C) 2013-2019 Carl Hetherington <cth@carlh.net>
+
+ This file is part of DCP-o-matic.
+
+ DCP-o-matic 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.
+
+ DCP-o-matic 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 DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "colours.h"
+#include "content_timeline_video_view.h"
+#include "lib/image_content.h"
+#include "lib/video_content.h"
+
+
+using std::dynamic_pointer_cast;
+using std::shared_ptr;
+
+
+ContentTimelineVideoView::ContentTimelineVideoView(ContentTimeline& tl, shared_ptr<Content> c)
+ : TimelineContentView (tl, c)
+{
+
+}
+
+wxColour
+ContentTimelineVideoView::background_colour() const
+{
+ if (!active()) {
+ return wxColour (210, 210, 210, 128);
+ }
+
+ return VIDEO_CONTENT_COLOUR;
+}
+
+wxColour
+ContentTimelineVideoView::foreground_colour() const
+{
+ if (!active()) {
+ return wxColour (180, 180, 180, 128);
+ }
+
+ return wxColour (0, 0, 0, 255);
+}
+
+bool
+ContentTimelineVideoView::active() const
+{
+ auto c = _content.lock();
+ DCPOMATIC_ASSERT (c);
+ return c->video && c->video->use();
+}
--- /dev/null
+/*
+ Copyright (C) 2013-2015 Carl Hetherington <cth@carlh.net>
+
+ This file is part of DCP-o-matic.
+
+ DCP-o-matic 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.
+
+ DCP-o-matic 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 DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "timeline_content_view.h"
+
+
+/** @class ContentTimelineVideoView
+ * @brief Content timeline view for VideoContent.
+ */
+class ContentTimelineVideoView : public TimelineContentView
+{
+public:
+ ContentTimelineVideoView(ContentTimeline& tl, std::shared_ptr<Content> c);
+
+private:
+ bool active () const override;
+ wxColour background_colour () const override;
+ wxColour foreground_colour () const override;
+};
--- /dev/null
+/*
+ Copyright (C) 2013-2021 Carl Hetherington <cth@carlh.net>
+
+ This file is part of DCP-o-matic.
+
+ DCP-o-matic 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.
+
+ DCP-o-matic 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 DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "content_timeline.h"
+#include "content_timeline_view.h"
+
+
+using std::list;
+using namespace dcpomatic;
+
+
+ContentTimelineView::ContentTimelineView(ContentTimeline& timeline)
+ : TimelineView(timeline)
+{
+
+}
+
+int
+ContentTimelineView::y_pos(int t) const
+{
+ return t * _timeline.pixels_per_track() + _timeline.tracks_y_offset();
+}
+
+
--- /dev/null
+/*
+ Copyright (C) 2013-2021 Carl Hetherington <cth@carlh.net>
+
+ This file is part of DCP-o-matic.
+
+ DCP-o-matic 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.
+
+ DCP-o-matic 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 DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#ifndef DCPOMATIC_CONTENT_TIMELINE_VIEW_H
+#define DCPOMATIC_CONTENT_TIMELINE_VIEW_H
+
+
+#include "timeline_view.h"
+#include "lib/rect.h"
+#include "lib/dcpomatic_time.h"
+
+
+class wxGraphicsContext;
+class ContentTimeline;
+
+
+/** @class ContentTimelineView
+ * @brief Parent class for components of the content timeline (e.g. a piece of content or an axis).
+ */
+class ContentTimelineView : public TimelineView<ContentTimeline>
+{
+public:
+ explicit ContentTimelineView(ContentTimeline& t);
+ virtual ~ContentTimelineView () = default;
+
+ void paint(wxGraphicsContext* gc, std::list<dcpomatic::Rect<int>> overlaps)
+ {
+ _last_paint_bbox = bbox();
+ do_paint(gc, overlaps);
+ }
+
+protected:
+ virtual void do_paint(wxGraphicsContext *, std::list<dcpomatic::Rect<int>> overlaps) = 0;
+
+ int y_pos(int t) const;
+};
+
+
+typedef std::vector<std::shared_ptr<ContentTimelineView>> ContentTimelineViewList;
+
+
+#endif
#include "check_box.h"
#include "check_box.h"
#include "dcp_panel.h"
+#include "dcp_timeline_dialog.h"
#include "dcpomatic_button.h"
#include "dcpomatic_choice.h"
#include "dcpomatic_spin_ctrl.h"
auto size = dc.GetTextExtent (wxT ("GGGGGGGG..."));
size.SetHeight (-1);
- _reels_label = create_label (_panel, _("Reels"), true);
- _reel_type = new Choice(_panel);
-
- _reel_length_label = create_label (_panel, _("Reel length"), true);
- _reel_length = new SpinCtrl (_panel, DCPOMATIC_SPIN_CTRL_WIDTH);
- _reel_length_gb_label = create_label (_panel, _("GB"), false);
-
_standard_label = create_label (_panel, _("Standard"), true);
_standard = new Choice(_panel);
_markers = new Button (_panel, _("Markers..."));
_metadata = new Button (_panel, _("Metadata..."));
+ _reels = new Button(_panel, _("Reels..."));
_notebook = new wxNotebook (_panel, wxID_ANY);
_sizer->Add (_notebook, 1, wxEXPAND | wxTOP, 6);
_copy_isdcf_name_button->Bind(wxEVT_BUTTON, boost::bind(&DCPPanel::copy_isdcf_name_button_clicked, this));
_dcp_content_type->Bind (wxEVT_CHOICE, boost::bind(&DCPPanel::dcp_content_type_changed, this));
_encrypted->bind(&DCPPanel::encrypted_toggled, this);
- _reel_type->Bind (wxEVT_CHOICE, boost::bind(&DCPPanel::reel_type_changed, this));
- _reel_length->Bind (wxEVT_SPINCTRL, boost::bind(&DCPPanel::reel_length_changed, this));
_standard->Bind (wxEVT_CHOICE, boost::bind(&DCPPanel::standard_changed, this));
_markers->Bind (wxEVT_BUTTON, boost::bind(&DCPPanel::markers_clicked, this));
_metadata->Bind (wxEVT_BUTTON, boost::bind(&DCPPanel::metadata_clicked, this));
+ _reels->Bind(wxEVT_BUTTON, boost::bind(&DCPPanel::reels_clicked, this));
+
for (auto i: DCPContentType::all()) {
_dcp_content_type->add(i->pretty_name());
}
- _reel_type->add(_("Single reel"));
- _reel_type->add(_("Split by video content"));
- /// TRANSLATORS: translate the word "Custom" here; do not include the "Reel|" prefix
- _reel_type->add(S_("Reel|Custom"));
- _reel_type->SetToolTip(_("How the DCP should be split into parts internally. If in doubt, choose 'Single reel'"));
-
- _reel_length->SetRange (1, 64);
-
add_standards();
_standard->SetToolTip(_("The standard that the DCP should use. Interop is older, and SMPTE is the newer (current) standard. If in doubt, choose 'SMPTE'"));
_grid->Add (_encrypted, wxGBPosition(r, 0), wxGBSpan(1, 2));
++r;
- add_label_to_sizer (_grid, _reels_label, true, wxGBPosition(r, 0));
- _grid->Add (_reel_type, wxGBPosition(r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
- ++r;
-
- add_label_to_sizer (_grid, _reel_length_label, true, wxGBPosition(r, 0));
- {
- auto s = new wxBoxSizer (wxHORIZONTAL);
- s->Add (_reel_length);
- add_label_to_sizer (s, _reel_length_gb_label, false, 0, wxLEFT | wxALIGN_CENTER_VERTICAL);
- _grid->Add (s, wxGBPosition(r, 1));
- }
- ++r;
-
add_label_to_sizer (_grid, _standard_label, true, wxGBPosition(r, 0));
_grid->Add (_standard, wxGBPosition(r, 1), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
++r;
auto extra = new wxBoxSizer (wxHORIZONTAL);
extra->Add (_markers, 1, wxRIGHT, DCPOMATIC_SIZER_X_GAP);
extra->Add (_metadata, 1, wxRIGHT, DCPOMATIC_SIZER_X_GAP);
+ extra->Add(_reels, 1, wxRIGHT, DCPOMATIC_SIZER_X_GAP);
_grid->Add (extra, wxGBPosition(r, 0), wxGBSpan(1, 2));
++r;
}
}
+void
+DCPPanel::reels_clicked()
+{
+ _dcp_timeline.reset(_panel, _film);
+ _dcp_timeline->Show();
+}
+
+
void
DCPPanel::film_changed(FilmProperty p)
{
setup_audio_channels_choice (_audio_channels, minimum_allowed_audio_channels());
film_changed (FilmProperty::AUDIO_CHANNELS);
break;
- case FilmProperty::REEL_TYPE:
- checked_set (_reel_type, static_cast<int>(_film->reel_type()));
- _reel_length->Enable (_film->reel_type() == ReelType::BY_LENGTH);
- break;
- case FilmProperty::REEL_LENGTH:
- checked_set (_reel_length, _film->reel_length() / 1000000000LL);
- break;
case FilmProperty::CONTENT:
setup_dcp_name ();
setup_sensitivity ();
_audio_language->Enable (_enable_audio_language->GetValue());
_edit_audio_language->Enable (_enable_audio_language->GetValue());
_encrypted->Enable (_generally_sensitive);
- _reel_type->Enable (_generally_sensitive && _film && !_film->references_dcp_video() && !_film->references_dcp_audio());
- _reel_length->Enable (_generally_sensitive && _film && _film->reel_type() == ReelType::BY_LENGTH);
_markers->Enable (_generally_sensitive && _film && !_film->interop());
_metadata->Enable (_generally_sensitive);
_frame_rate_choice->Enable (_generally_sensitive && _film && !_film->references_dcp_video() && !_film->contains_atmos_content());
}
-void
-DCPPanel::reel_type_changed ()
-{
- if (!_film || !_reel_type->get()) {
- return;
- }
-
- _film->set_reel_type(static_cast<ReelType>(*_reel_type->get()));
-}
-
-
-void
-DCPPanel::reel_length_changed ()
-{
- if (!_film) {
- return;
- }
-
- _film->set_reel_length (_reel_length->GetValue() * 1000000000LL);
-}
-
-
void
DCPPanel::add_audio_processors ()
{
class AudioDialog;
class Choice;
+class DCPTimelineDialog;
class Film;
class FilmViewer;
class InteropMetadataDialog;
void encrypted_toggled ();
void audio_processor_changed ();
void show_audio_clicked ();
- void reel_type_changed ();
- void reel_length_changed ();
void markers_clicked ();
void metadata_clicked ();
+ void reels_clicked();
void reencode_j2k_changed ();
void enable_audio_language_toggled ();
void edit_audio_language_clicked ();
wxStaticText* _standard_label;
Choice* _standard;
CheckBox* _encrypted;
- wxStaticText* _reels_label;
- Choice* _reel_type;
- wxStaticText* _reel_length_label;
- wxStaticText* _reel_length_gb_label;
- wxSpinCtrl* _reel_length;
wxButton* _markers;
wxButton* _metadata;
+ Button* _reels;
wxSizer* _audio_panel_sizer;
wx_ptr<AudioDialog> _audio_dialog;
wx_ptr<MarkersDialog> _markers_dialog;
wx_ptr<InteropMetadataDialog> _interop_metadata_dialog;
wx_ptr<SMPTEMetadataDialog> _smpte_metadata_dialog;
+ wx_ptr<DCPTimelineDialog> _dcp_timeline;
std::shared_ptr<Film> _film;
FilmViewer& _viewer;
--- /dev/null
+/*
+ Copyright (C) 2023 Carl Hetherington <cth@carlh.net>
+
+ This file is part of DCP-o-matic.
+
+ DCP-o-matic 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.
+
+ DCP-o-matic 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 DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "check_box.h"
+#include "colours.h"
+#include "dcp_timeline.h"
+#include "dcp_timeline_reel_marker_view.h"
+#include "dcpomatic_choice.h"
+#include "dcpomatic_spin_ctrl.h"
+#include "timecode.h"
+#include "wx_util.h"
+#include "lib/atmos_content.h"
+#include "lib/audio_content.h"
+#include "lib/constants.h"
+#include "lib/film.h"
+#include "lib/text_content.h"
+#include "lib/video_content.h"
+#include <dcp/scope_guard.h>
+LIBDCP_DISABLE_WARNINGS
+#include <wx/graphics.h>
+LIBDCP_ENABLE_WARNINGS
+
+
+using std::dynamic_pointer_cast;
+using std::make_shared;
+using std::shared_ptr;
+using std::vector;
+using boost::optional;
+#if BOOST_VERSION >= 106100
+using namespace boost::placeholders;
+#endif
+using namespace dcpomatic;
+
+
+auto constexpr reel_marker_y_pos = 48;
+auto constexpr content_y_pos = 112;
+auto constexpr content_type_height = 12;
+
+enum {
+ ID_add_reel_boundary,
+};
+
+
+class ReelBoundary
+{
+public:
+ ReelBoundary(wxWindow* parent, wxGridBagSizer* sizer, int index, DCPTime maximum, int fps, DCPTimeline& timeline, bool editable)
+ : _label(new wxStaticText(parent, wxID_ANY, wxString::Format(_("Reel %d to reel %d"), index + 1, index + 2)))
+ , _timecode(new Timecode<DCPTime>(parent, true))
+ , _index(index)
+ , _view(timeline, reel_marker_y_pos)
+ , _fps(fps)
+ {
+ sizer->Add(_label, wxGBPosition(index, 0), wxDefaultSpan, wxALIGN_CENTER_VERTICAL);
+ sizer->Add(_timecode, wxGBPosition(index, 1));
+
+ _timecode->set_maximum(maximum.split(fps));
+ _timecode->set_editable(editable);
+ _timecode->Changed.connect(boost::bind(&ReelBoundary::timecode_changed, this));
+ }
+
+ ~ReelBoundary()
+ {
+ if (_label) {
+ _label->Destroy();
+ }
+
+ if (_timecode) {
+ _timecode->Destroy();
+ }
+ }
+
+ ReelBoundary(ReelBoundary const&) = delete;
+ ReelBoundary& operator=(ReelBoundary const&) = delete;
+
+ ReelBoundary(ReelBoundary&& other) = delete;
+ ReelBoundary& operator=(ReelBoundary&& other) = delete;
+
+ void set_time(DCPTime time)
+ {
+ if (_timecode) {
+ _timecode->set(time, _fps);
+ }
+ _view.set_time(time);
+ }
+
+ dcpomatic::DCPTime time() const {
+ return _view.time();
+ }
+
+ int index() const {
+ return _index;
+ }
+
+ DCPTimelineReelMarkerView& view() {
+ return _view;
+ }
+
+ DCPTimelineReelMarkerView const& view() const {
+ return _view;
+ }
+
+ boost::signals2::signal<void (int, dcpomatic::DCPTime)> Changed;
+
+private:
+ void timecode_changed() {
+ set_time(_timecode->get(_fps));
+ Changed(_index, time());
+ }
+
+ wxStaticText* _label = nullptr;
+ Timecode<dcpomatic::DCPTime>* _timecode = nullptr;
+ int _index = 0;
+ DCPTimelineReelMarkerView _view;
+ int _fps;
+};
+
+
+DCPTimeline::DCPTimeline(wxWindow* parent, shared_ptr<Film> film)
+ : Timeline(parent)
+ , _film(film)
+ , _canvas(new wxScrolledCanvas(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxFULL_REPAINT_ON_RESIZE))
+ , _reel_settings(new wxPanel(this, wxID_ANY))
+ , _reel_detail(new wxPanel(this, wxID_ANY))
+ , _reel_detail_sizer(new wxGridBagSizer(DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP))
+{
+#ifndef __WXOSX__
+ _canvas->SetDoubleBuffered(true);
+#endif
+ _reel_detail->SetSizer(_reel_detail_sizer);
+
+ auto sizer = new wxBoxSizer(wxVERTICAL);
+ sizer->Add(_reel_settings, 0);
+ sizer->Add(_canvas, 0, wxEXPAND);
+ sizer->Add(_reel_detail, 1, wxEXPAND | wxALL, DCPOMATIC_DIALOG_BORDER);
+ SetSizer(sizer);
+
+ SetMinSize(wxSize(640, 480));
+ _canvas->SetMinSize({-1, content_y_pos + content_type_height * 4});
+
+ _canvas->Bind(wxEVT_PAINT, boost::bind(&DCPTimeline::paint, this));
+ _canvas->Bind(wxEVT_SIZE, boost::bind(&DCPTimeline::setup_pixels_per_second, this));
+ _canvas->Bind(wxEVT_LEFT_DOWN, boost::bind(&DCPTimeline::left_down, this, _1));
+ _canvas->Bind(wxEVT_RIGHT_DOWN, boost::bind(&DCPTimeline::right_down, this, _1));
+ _canvas->Bind(wxEVT_LEFT_UP, boost::bind(&DCPTimeline::left_up, this, _1));
+ _canvas->Bind(wxEVT_MOTION, boost::bind(&DCPTimeline::mouse_moved, this, _1));
+
+ _film_connection = film->Change.connect(boost::bind(&DCPTimeline::film_changed, this, _1, _2));
+
+ _menu = new wxMenu;
+ _add_reel_boundary = _menu->Append(ID_add_reel_boundary, _("Add reel"));
+ _canvas->Bind(wxEVT_MENU, boost::bind(&DCPTimeline::add_reel_boundary, this));
+
+ setup_reel_settings();
+ setup_reel_boundaries();
+
+ sizer->Layout();
+ setup_pixels_per_second();
+ setup_sensitivity();
+}
+
+
+void
+DCPTimeline::add_reel_boundary()
+{
+ auto boundaries = film()->custom_reel_boundaries();
+ boundaries.push_back(DCPTime::from_seconds(_right_down_position.x / _pixels_per_second.get_value_or(1)));
+ film()->set_custom_reel_boundaries(boundaries);
+}
+
+
+void
+DCPTimeline::film_changed(ChangeType type, FilmProperty property)
+{
+ if (type != ChangeType::DONE) {
+ return;
+ }
+
+ switch (property) {
+ case FilmProperty::REEL_TYPE:
+ case FilmProperty::REEL_LENGTH:
+ case FilmProperty::CUSTOM_REEL_BOUNDARIES:
+ setup_sensitivity();
+ setup_reel_boundaries();
+ break;
+ case FilmProperty::CONTENT:
+ case FilmProperty::CONTENT_ORDER:
+ setup_pixels_per_second();
+ Refresh();
+ break;
+ default:
+ break;
+ }
+}
+
+
+void
+DCPTimeline::setup_sensitivity()
+{
+ _snap->Enable(editable());
+ _maximum_reel_size->Enable(film()->reel_type() == ReelType::BY_LENGTH);
+}
+
+
+void
+DCPTimeline::setup_reel_settings()
+{
+ auto sizer = new wxGridBagSizer(DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
+ _reel_settings->SetSizer(sizer);
+
+ int r = 0;
+ add_label_to_sizer(sizer, _reel_settings, _("Reel mode"), true, wxGBPosition(r, 0));
+ _reel_type = new Choice(_reel_settings);
+ _reel_type->add(_("Single reel"));
+ _reel_type->add(_("Split by video content"));
+ _reel_type->add(_("Split by maximum reel size"));
+ _reel_type->add(_("Custom"));
+ sizer->Add(_reel_type, wxGBPosition(r, 1));
+ ++r;
+
+ add_label_to_sizer(sizer, _reel_settings, _("Maximum reel size"), true, wxGBPosition(r, 0));
+ _maximum_reel_size = new SpinCtrl(_reel_settings, DCPOMATIC_SPIN_CTRL_WIDTH);
+ _maximum_reel_size->SetRange(1, 1000);
+ {
+ auto s = new wxBoxSizer(wxHORIZONTAL);
+ s->Add(_maximum_reel_size, 0);
+ add_label_to_sizer(s, _reel_settings, _("GB"), false, 0, wxALIGN_CENTER_VERTICAL | wxLEFT);
+ sizer->Add(s, wxGBPosition(r, 1));
+ }
+ ++r;
+
+ _snap = new CheckBox(_reel_settings, _("Snap when dragging"));
+ sizer->Add(_snap, wxGBPosition(r, 1));
+ ++r;
+
+ _reel_type->set(static_cast<int>(film()->reel_type()));
+ _maximum_reel_size->SetValue(film()->reel_length() / 1000000000LL);
+
+ _reel_type->bind(&DCPTimeline::reel_mode_changed, this);
+ _maximum_reel_size->Bind(wxEVT_SPINCTRL, boost::bind(&DCPTimeline::maximum_reel_size_changed, this));
+}
+
+
+void
+DCPTimeline::reel_mode_changed()
+{
+ film()->set_reel_type(static_cast<ReelType>(*_reel_type->get()));
+}
+
+
+void
+DCPTimeline::maximum_reel_size_changed()
+{
+ film()->set_reel_length(_maximum_reel_size->GetValue() * 1000000000LL);
+}
+
+
+void
+DCPTimeline::set_reel_boundary(int index, DCPTime time)
+{
+ auto boundaries = film()->custom_reel_boundaries();
+ DCPOMATIC_ASSERT(index >= 0 && index < static_cast<int>(boundaries.size()));
+ boundaries[index] = time.round(film()->video_frame_rate());
+ film()->set_custom_reel_boundaries(boundaries);
+}
+
+
+void
+DCPTimeline::setup_reel_boundaries()
+{
+ auto const reels = film()->reels();
+ if (reels.empty()) {
+ _reel_boundaries.clear();
+ return;
+ }
+
+ size_t const boundaries = reels.size() - 1;
+ auto const maximum = film()->length();
+ for (size_t i = _reel_boundaries.size(); i < boundaries; ++i) {
+ auto boundary = std::make_shared<ReelBoundary>(
+ _reel_detail, _reel_detail_sizer, i, maximum, film()->video_frame_rate(), *this, editable()
+ );
+
+ boundary->Changed.connect(boost::bind(&DCPTimeline::set_reel_boundary, this, _1, _2));
+ _reel_boundaries.push_back(boundary);
+ }
+
+ _reel_boundaries.resize(boundaries);
+
+ auto const active = editable();
+ for (size_t i = 0; i < boundaries; ++i) {
+ _reel_boundaries[i]->set_time(reels[i].to);
+ _reel_boundaries[i]->view().set_active(active);
+ }
+
+ _reel_detail_sizer->Layout();
+ _canvas->Refresh();
+}
+
+
+void
+DCPTimeline::paint()
+{
+ wxPaintDC dc(_canvas);
+ dc.Clear();
+
+ if (film()->content().empty()) {
+ return;
+ }
+
+ _canvas->DoPrepareDC(dc);
+
+ auto gc = wxGraphicsContext::Create(dc);
+ if (!gc) {
+ return;
+ }
+
+ dcp::ScopeGuard sg = [gc]() { delete gc; };
+
+ gc->SetAntialiasMode(wxANTIALIAS_DEFAULT);
+
+ paint_reels(gc);
+ paint_content(gc);
+}
+
+
+void
+DCPTimeline::paint_reels(wxGraphicsContext* gc)
+{
+ constexpr int x_offset = 2;
+
+ for (auto const& boundary: _reel_boundaries) {
+ boundary->view().paint(gc);
+ }
+
+ gc->SetFont(gc->CreateFont(*wxNORMAL_FONT, wxColour(0, 0, 0)));
+ gc->SetPen(*wxThePenList->FindOrCreatePen(wxColour(0, 0, 0), 2, wxPENSTYLE_SOLID));
+
+ auto const pps = pixels_per_second().get_value_or(1);
+
+ auto start = gc->CreatePath();
+ start.MoveToPoint(x_offset, reel_marker_y_pos);
+ start.AddLineToPoint(x_offset, reel_marker_y_pos + DCPTimelineReelMarkerView::HEIGHT);
+ gc->StrokePath(start);
+
+ auto const length = film()->length().seconds() * pps;
+ auto end = gc->CreatePath();
+ end.MoveToPoint(x_offset + length, reel_marker_y_pos);
+ end.AddLineToPoint(x_offset + length, reel_marker_y_pos + DCPTimelineReelMarkerView::HEIGHT);
+ gc->StrokePath(end);
+
+ auto const y = reel_marker_y_pos + DCPTimelineReelMarkerView::HEIGHT * 3 / 4;
+
+ auto paint_reel = [gc, y](double from, double to, int index) {
+ auto path = gc->CreatePath();
+ path.MoveToPoint(from, y);
+ path.AddLineToPoint(to, y);
+ gc->StrokePath(path);
+
+ auto str = wxString::Format(wxT("#%d"), index + 1);
+ wxDouble str_width;
+ wxDouble str_height;
+ wxDouble str_descent;
+ wxDouble str_leading;
+ gc->GetTextExtent(str, &str_width, &str_height, &str_descent, &str_leading);
+
+ if (str_width < (to - from)) {
+ gc->DrawText(str, (from + to - str_width) / 2, y - str_height - 2);
+ }
+ };
+
+ gc->SetPen(*wxThePenList->FindOrCreatePen(wxColour(0, 0, 255), 2, wxPENSTYLE_DOT));
+ int index = 0;
+ DCPTime last;
+ for (auto const& boundary: _reel_boundaries) {
+ paint_reel(last.seconds() * pps + 2, boundary->time().seconds() * pps, index++);
+ last = boundary->time();
+ }
+
+ paint_reel(last.seconds() * pps + 2, film()->length().seconds() * pps, index);
+}
+
+
+void
+DCPTimeline::paint_content(wxGraphicsContext* gc)
+{
+ auto const pps = pixels_per_second().get_value_or(1);
+ auto const film = this->film();
+
+ auto const& solid_pen = *wxThePenList->FindOrCreatePen(wxColour(0, 0, 0), 1, wxPENSTYLE_SOLID);
+ auto const& dotted_pen = *wxThePenList->FindOrCreatePen(wxColour(0, 0, 0), 1, wxPENSTYLE_DOT);
+
+ auto const& video_brush = *wxTheBrushList->FindOrCreateBrush(VIDEO_CONTENT_COLOUR, wxBRUSHSTYLE_SOLID);
+ auto const& audio_brush = *wxTheBrushList->FindOrCreateBrush(AUDIO_CONTENT_COLOUR, wxBRUSHSTYLE_SOLID);
+ auto const& text_brush = *wxTheBrushList->FindOrCreateBrush(TEXT_CONTENT_COLOUR, wxBRUSHSTYLE_SOLID);
+ auto const& atmos_brush = *wxTheBrushList->FindOrCreateBrush(ATMOS_CONTENT_COLOUR, wxBRUSHSTYLE_SOLID);
+
+ auto maybe_draw =
+ [gc, film, pps, solid_pen, dotted_pen]
+ (shared_ptr<Content> content, shared_ptr<ContentPart> part, wxBrush brush, int offset) {
+ if (part) {
+ auto const y = content_y_pos + offset * content_type_height + 1;
+ gc->SetPen(solid_pen);
+ gc->SetBrush(brush);
+ gc->DrawRectangle(
+ content->position().seconds() * pps,
+ y,
+ content->length_after_trim(film).seconds() * pps,
+ content_type_height - 2
+ );
+
+ gc->SetPen(dotted_pen);
+ for (auto split: content->reel_split_points(film)) {
+ if (split != content->position()) {
+ auto path = gc->CreatePath();
+ path.MoveToPoint(split.seconds() * pps, y);
+ path.AddLineToPoint(split.seconds() * pps, y + content_type_height - 2);
+ gc->StrokePath(path);
+ }
+ }
+ }
+ };
+
+ for (auto content: film->content()) {
+ maybe_draw(content, dynamic_pointer_cast<ContentPart>(content->video), video_brush, 0);
+ maybe_draw(content, dynamic_pointer_cast<ContentPart>(content->audio), audio_brush, 1);
+ for (auto text: content->text) {
+ maybe_draw(content, dynamic_pointer_cast<ContentPart>(text), text_brush, 2);
+ }
+ maybe_draw(content, dynamic_pointer_cast<ContentPart>(content->atmos), atmos_brush, 3);
+ }
+}
+
+
+void
+DCPTimeline::setup_pixels_per_second()
+{
+ set_pixels_per_second((_canvas->GetSize().GetWidth() - 4) / std::max(1.0, film()->length().seconds()));
+}
+
+
+shared_ptr<ReelBoundary>
+DCPTimeline::event_to_reel_boundary(wxMouseEvent& ev) const
+{
+ Position<int> const position(ev.GetX(), ev.GetY());
+ auto iter = std::find_if(_reel_boundaries.begin(), _reel_boundaries.end(), [position](shared_ptr<const ReelBoundary> boundary) {
+ return boundary->view().bbox().contains(position);
+ });
+
+ if (iter == _reel_boundaries.end()) {
+ return {};
+ }
+
+ return *iter;
+}
+
+
+void
+DCPTimeline::left_down(wxMouseEvent& ev)
+{
+ if (!editable()) {
+ return;
+ }
+
+ if (auto boundary = event_to_reel_boundary(ev)) {
+ auto const snap_distance = DCPTime::from_seconds((_canvas->GetSize().GetWidth() / _pixels_per_second.get_value_or(1)) / SNAP_SUBDIVISION);
+ _drag = DCPTimeline::Drag(
+ boundary,
+ _reel_boundaries,
+ film(),
+ static_cast<int>(ev.GetX() - boundary->time().seconds() * _pixels_per_second.get_value_or(0)),
+ _snap->get(),
+ snap_distance
+ );
+ } else {
+ _drag = boost::none;
+ }
+}
+
+
+void
+DCPTimeline::right_down(wxMouseEvent& ev)
+{
+ _right_down_position = ev.GetPosition();
+ _canvas->PopupMenu(_menu, _right_down_position);
+}
+
+
+void
+DCPTimeline::left_up(wxMouseEvent&)
+{
+ if (!_drag) {
+ return;
+ }
+
+ set_reel_boundary(_drag->reel_boundary->index(), _drag->time());
+ _drag = boost::none;
+}
+
+
+void
+DCPTimeline::mouse_moved(wxMouseEvent& ev)
+{
+ if (!_drag) {
+ return;
+ }
+
+ auto time = DCPTime::from_seconds((ev.GetPosition().x - _drag->offset) / _pixels_per_second.get_value_or(1));
+ time = std::max(_drag->previous ? _drag->previous->time() : DCPTime(), time);
+ time = std::min(_drag->next ? _drag->next->time() : film()->length(), time);
+ _drag->set_time(time);
+ _canvas->RefreshRect({0, reel_marker_y_pos - 2, _canvas->GetSize().GetWidth(), DCPTimelineReelMarkerView::HEIGHT + 4}, true);
+}
+
+
+void
+DCPTimeline::force_redraw(dcpomatic::Rect<int> const & r)
+{
+ _canvas->RefreshRect(wxRect(r.x, r.y, r.width, r.height), false);
+}
+
+
+shared_ptr<Film>
+DCPTimeline::film() const
+{
+ auto film = _film.lock();
+ DCPOMATIC_ASSERT(film);
+ return film;
+}
+
+
+bool
+DCPTimeline::editable() const
+{
+ return film()->reel_type() == ReelType::CUSTOM;
+}
+
+
+DCPTimeline::Drag::Drag(
+ shared_ptr<ReelBoundary> reel_boundary_,
+ vector<shared_ptr<ReelBoundary>> const& reel_boundaries,
+ shared_ptr<const Film> film,
+ int offset_,
+ bool snap,
+ DCPTime snap_distance
+ )
+ : reel_boundary(reel_boundary_)
+ , offset(offset_)
+ , _snap_distance(snap_distance)
+{
+ auto iter = std::find(reel_boundaries.begin(), reel_boundaries.end(), reel_boundary);
+ auto index = std::distance(reel_boundaries.begin(), iter);
+
+ if (index > 0) {
+ previous = reel_boundaries[index - 1];
+ }
+ if (index < static_cast<int>(reel_boundaries.size() - 1)) {
+ next = reel_boundaries[index + 1];
+ }
+
+ if (snap) {
+ for (auto content: film->content()) {
+ for (auto split: content->reel_split_points(film)) {
+ _snaps.push_back(split);
+ }
+ }
+ }
+}
+
+
+void
+DCPTimeline::Drag::set_time(DCPTime time)
+{
+ optional<DCPTime> nearest_distance;
+ optional<DCPTime> nearest_time;
+ for (auto snap: _snaps) {
+ auto const distance = time - snap;
+ if (!nearest_distance || distance.abs() < nearest_distance->abs()) {
+ nearest_distance = distance.abs();
+ nearest_time = snap;
+ }
+ }
+
+ if (nearest_distance && *nearest_distance < _snap_distance) {
+ reel_boundary->set_time(*nearest_time);
+ } else {
+ reel_boundary->set_time(time);
+ }
+}
+
+
+DCPTime
+DCPTimeline::Drag::time() const
+{
+ return reel_boundary->time();
+}
+
--- /dev/null
+/*
+ Copyright (C) 2023 Carl Hetherington <cth@carlh.net>
+
+ This file is part of DCP-o-matic.
+
+ DCP-o-matic 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.
+
+ DCP-o-matic 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 DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#ifndef DCPOMATIC_DCP_TIMELINE_H
+#define DCPOMATIC_DCP_TIMELINE_H
+
+
+#include "timecode.h"
+#include "timeline.h"
+#include "lib/rect.h"
+#include <dcp/warnings.h>
+LIBDCP_DISABLE_WARNINGS
+#include <wx/wx.h>
+LIBDCP_ENABLE_WARNINGS
+#include <memory>
+
+
+class CheckBox;
+class Choice;
+class Film;
+class ReelBoundary;
+class SpinCtrl;
+class wxGridBagSizer;
+
+
+class DCPTimeline : public Timeline
+{
+public:
+ DCPTimeline(wxWindow* parent, std::shared_ptr<Film> film);
+
+ void force_redraw(dcpomatic::Rect<int> const &);
+
+private:
+ void paint();
+ void paint_reels(wxGraphicsContext* gc);
+ void paint_content(wxGraphicsContext* gc);
+ void setup_pixels_per_second();
+ void left_down(wxMouseEvent& ev);
+ void right_down(wxMouseEvent& ev);
+ void left_up(wxMouseEvent& ev);
+ void mouse_moved(wxMouseEvent& ev);
+ void reel_mode_changed();
+ void maximum_reel_size_changed();
+ void film_changed(ChangeType type, FilmProperty property);
+ std::shared_ptr<Film> film() const;
+ void setup_sensitivity();
+
+ void add_reel_boundary();
+ void setup_reel_settings();
+ void setup_reel_boundaries();
+ std::shared_ptr<ReelBoundary> event_to_reel_boundary(wxMouseEvent& ev) const;
+ void set_reel_boundary(int index, dcpomatic::DCPTime time);
+ bool editable() const;
+
+ std::weak_ptr<Film> _film;
+
+ wxScrolledCanvas* _canvas;
+
+ class Drag
+ {
+ public:
+ Drag(
+ std::shared_ptr<ReelBoundary> reel_boundary_,
+ std::vector<std::shared_ptr<ReelBoundary>> const& reel_boundaries,
+ std::shared_ptr<const Film> film,
+ int offset_,
+ bool snap,
+ dcpomatic::DCPTime snap_distance
+ );
+
+ std::shared_ptr<ReelBoundary> reel_boundary;
+ std::shared_ptr<ReelBoundary> previous;
+ std::shared_ptr<ReelBoundary> next;
+ int offset = 0;
+
+ void set_time(dcpomatic::DCPTime time);
+ dcpomatic::DCPTime time() const;
+
+ private:
+ std::vector<dcpomatic::DCPTime> _snaps;
+ dcpomatic::DCPTime _snap_distance;
+ };
+
+ boost::optional<Drag> _drag;
+
+ wxPoint _right_down_position;
+
+ wxPanel* _reel_settings;
+ Choice* _reel_type;
+ SpinCtrl* _maximum_reel_size;
+ CheckBox* _snap;
+ wxPanel* _reel_detail;
+ wxGridBagSizer* _reel_detail_sizer;
+
+ wxMenu* _menu;
+ wxMenuItem* _add_reel_boundary;
+
+ boost::signals2::scoped_connection _film_connection;
+
+ std::vector<std::shared_ptr<ReelBoundary>> _reel_boundaries;
+};
+
+
+#endif
+
--- /dev/null
+/*
+ Copyright (C) 2013-2021 Carl Hetherington <cth@carlh.net>
+
+ This file is part of DCP-o-matic.
+
+ DCP-o-matic 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.
+
+ DCP-o-matic 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 DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "dcp_panel.h"
+#include "dcp_timeline_dialog.h"
+#include "film_editor.h"
+#include "wx_util.h"
+#include "lib/compose.hpp"
+#include "lib/cross.h"
+#include "lib/film.h"
+#include "lib/playlist.h"
+#include <dcp/warnings.h>
+LIBDCP_DISABLE_WARNINGS
+#include <wx/graphics.h>
+LIBDCP_ENABLE_WARNINGS
+#include <list>
+
+
+using std::list;
+using std::shared_ptr;
+using std::string;
+using std::weak_ptr;
+#if BOOST_VERSION >= 106100
+using namespace boost::placeholders;
+#endif
+
+
+DCPTimelineDialog::DCPTimelineDialog(wxWindow* parent, shared_ptr<Film> film)
+ : wxDialog(
+ parent,
+ wxID_ANY,
+ _("Reels"),
+ wxDefaultPosition,
+ wxSize(640, 512),
+#ifdef DCPOMATIC_OSX
+ /* I can't get wxFRAME_FLOAT_ON_PARENT to work on OS X, and although wxSTAY_ON_TOP keeps
+ the window above all others (and not just our own) it's better than nothing for now.
+ */
+ wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxFULL_REPAINT_ON_RESIZE | wxSTAY_ON_TOP
+#else
+ wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxFULL_REPAINT_ON_RESIZE | wxFRAME_FLOAT_ON_PARENT
+#endif
+ )
+ , _timeline(this, film)
+{
+ auto sizer = new wxBoxSizer(wxVERTICAL);
+ sizer->Add (&_timeline, 1, wxEXPAND | wxALL, 12);
+
+#ifdef DCPOMATIC_LINUX
+ auto buttons = CreateSeparatedButtonSizer (wxCLOSE);
+ if (buttons) {
+ sizer->Add (buttons, wxSizerFlags().Expand().DoubleBorder());
+ }
+#endif
+
+ SetSizer(sizer);
+ sizer->Layout();
+ sizer->SetSizeHints(this);
+}
+
--- /dev/null
+/*
+ Copyright (C) 2023 Carl Hetherington <cth@carlh.net>
+
+ This file is part of DCP-o-matic.
+
+ DCP-o-matic 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.
+
+ DCP-o-matic 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 DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "dcp_timeline.h"
+#include <dcp/warnings.h>
+LIBDCP_DISABLE_WARNINGS
+#include <wx/wx.h>
+LIBDCP_ENABLE_WARNINGS
+#include <memory>
+
+
+class DCPTimelineDialog : public wxDialog
+{
+public:
+ DCPTimelineDialog(wxWindow* parent, std::shared_ptr<Film> film);
+
+private:
+ std::weak_ptr<Film> _film;
+ DCPTimeline _timeline;
+};
+
--- /dev/null
+/*
+ Copyright (C) 2023 Carl Hetherington <cth@carlh.net>
+
+ This file is part of DCP-o-matic.
+
+ DCP-o-matic 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.
+
+ DCP-o-matic 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 DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "dcp_timeline_reel_marker_view.h"
+LIBDCP_DISABLE_WARNINGS
+#include <wx/graphics.h>
+LIBDCP_ENABLE_WARNINGS
+
+
+using namespace std;
+using namespace dcpomatic;
+
+
+DCPTimelineReelMarkerView::DCPTimelineReelMarkerView(DCPTimeline& timeline, int y_pos)
+ : DCPTimelineView(timeline)
+ , _y_pos(y_pos)
+{
+
+}
+
+
+int
+DCPTimelineReelMarkerView::x_pos() const
+{
+ /* Nudge it over slightly so that the full line width is drawn on the left hand side */
+ return time_x(_time) + 2;
+}
+
+
+void
+DCPTimelineReelMarkerView::do_paint(wxGraphicsContext* gc)
+{
+ wxColour const outline = _active ? wxColour(0, 0, 0) : wxColour(128, 128, 128);
+ wxColour const fill = _active ? wxColour(255, 0, 0) : wxColour(192, 192, 192);
+ gc->SetPen(*wxThePenList->FindOrCreatePen(outline, 2, wxPENSTYLE_SOLID));
+ gc->SetBrush(*wxTheBrushList->FindOrCreateBrush(fill, wxBRUSHSTYLE_SOLID));
+
+ gc->DrawRectangle(x_pos(), _y_pos, HEAD_SIZE, HEAD_SIZE);
+
+ auto path = gc->CreatePath();
+ path.MoveToPoint(x_pos(), _y_pos + HEAD_SIZE + TAIL_LENGTH);
+ path.AddLineToPoint(x_pos(), _y_pos);
+ gc->StrokePath(path);
+ gc->FillPath(path);
+}
+
+
+dcpomatic::Rect<int>
+DCPTimelineReelMarkerView::bbox() const
+{
+ return { x_pos(), _y_pos, HEAD_SIZE, HEAD_SIZE + TAIL_LENGTH };
+}
+
--- /dev/null
+/*
+ Copyright (C) 2023 Carl Hetherington <cth@carlh.net>
+
+ This file is part of DCP-o-matic.
+
+ DCP-o-matic 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.
+
+ DCP-o-matic 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 DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "dcp_timeline_view.h"
+
+
+class DCPTimeline;
+
+
+class DCPTimelineReelMarkerView : public DCPTimelineView
+{
+public:
+ DCPTimelineReelMarkerView(DCPTimeline& timeline, int y_pos);
+
+ dcpomatic::Rect<int> bbox() const override;
+
+ dcpomatic::DCPTime time() const {
+ return _time;
+ }
+
+ void set_time(dcpomatic::DCPTime time) {
+ _time = time;
+ }
+
+ void set_active(bool active) {
+ _active = active;
+ }
+
+ static auto constexpr HEAD_SIZE = 16;
+ static auto constexpr TAIL_LENGTH = 28;
+ static auto constexpr HEIGHT = HEAD_SIZE + TAIL_LENGTH;
+
+private:
+ void do_paint(wxGraphicsContext* gc) override;
+ int x_pos() const;
+
+ dcpomatic::DCPTime _time;
+ int _y_pos;
+ bool _active = false;
+};
+
--- /dev/null
+/*
+ Copyright (C) 2013-2021 Carl Hetherington <cth@carlh.net>
+
+ This file is part of DCP-o-matic.
+
+ DCP-o-matic 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.
+
+ DCP-o-matic 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 DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+
+#include "dcp_timeline.h"
+#include "timeline_view.h"
+
+
+class DCPTimelineView : public TimelineView<DCPTimeline>
+{
+public:
+ explicit DCPTimelineView(DCPTimeline& timeline)
+ : TimelineView(timeline)
+ {}
+
+ void paint(wxGraphicsContext* gc)
+ {
+ _last_paint_bbox = bbox();
+ do_paint(gc);
+ }
+
+protected:
+ virtual void do_paint(wxGraphicsContext* context) = 0;
+};
+
+
+
TimecodeBase::changed ()
{
if (_set_button && !_ignore_changed) {
- _set_button->Enable (true);
+ _set_button->Enable(valid());
}
}
protected:
void changed ();
void set_clicked ();
+ virtual bool valid() const = 0;
wxSizer* _sizer;
wxPanel* _editable;
_frames->SetHint (std_to_wx(dcp::raw_convert<std::string>(hmsf.f)));
}
+ void set_maximum(dcpomatic::HMSF maximum)
+ {
+ _maximum = std::move(maximum);
+ }
+
dcpomatic::HMSF get () const
{
auto value_or_hint = [](wxTextCtrl const * t) {
{
return T(get(), fps);
}
+
+private:
+ bool valid() const override {
+ return !_maximum || get() <= *_maximum;
+ }
+
+ boost::optional<dcpomatic::HMSF> _maximum;
};
#endif
/*
- Copyright (C) 2013-2021 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2023 Carl Hetherington <cth@carlh.net>
This file is part of DCP-o-matic.
*/
-#include "content_panel.h"
-#include "film_editor.h"
-#include "film_viewer.h"
-#include "timeline.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_time_axis_view.h"
-#include "timeline_video_content_view.h"
-#include "wx_util.h"
-#include "lib/atmos_mxf_content.h"
-#include "lib/audio_content.h"
-#include "lib/film.h"
-#include "lib/image_content.h"
-#include "lib/playlist.h"
-#include "lib/text_content.h"
-#include "lib/timer.h"
-#include "lib/video_content.h"
-#include <dcp/scope_guard.h>
-#include <dcp/warnings.h>
-LIBDCP_DISABLE_WARNINGS
-#include <wx/graphics.h>
-LIBDCP_ENABLE_WARNINGS
-#include <iterator>
-#include <list>
-
-using std::abs;
-using std::dynamic_pointer_cast;
-using std::list;
-using std::make_shared;
-using std::max;
-using std::min;
-using std::shared_ptr;
-using std::weak_ptr;
-using boost::bind;
-using boost::optional;
-using namespace dcpomatic;
-#if BOOST_VERSION >= 106100
-using namespace boost::placeholders;
-#endif
+#include "timeline.h"
/* 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, 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))
- , _tracks (0)
- , _left_down (false)
- , _down_view_position (0)
- , _first_move (false)
- , _menu (this, viewer)
- , _snap (true)
- , _tool (SELECT)
- , _x_scroll_rate (16)
- , _y_scroll_rate (16)
- , _pixels_per_track (48)
- , _first_resize (true)
- , _timer (this)
-{
-#ifndef __WXOSX__
- _labels_canvas->SetDoubleBuffered (true);
- _main_canvas->SetDoubleBuffered (true);
-#endif
-
- auto sizer = new wxBoxSizer (wxHORIZONTAL);
- sizer->Add (_labels_canvas, 0, wxEXPAND);
- _labels_canvas->SetMinSize (wxSize (_labels_view->bbox().width, -1));
- sizer->Add (_main_canvas, 1, wxEXPAND);
- SetSizer (sizer);
-
- _labels_canvas->Bind (wxEVT_PAINT, boost::bind (&Timeline::paint_labels, this));
- _main_canvas->Bind (wxEVT_PAINT, boost::bind (&Timeline::paint_main, this));
- _main_canvas->Bind (wxEVT_LEFT_DOWN, boost::bind (&Timeline::left_down, this, _1));
- _main_canvas->Bind (wxEVT_LEFT_UP, boost::bind (&Timeline::left_up, this, _1));
- _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_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_change(ChangeType::DONE, FilmProperty::CONTENT);
-
- SetMinSize (wxSize (640, 4 * pixels_per_track() + 96));
-
- _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::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, {});
-}
-
-
-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);
- if (!gc) {
- return;
- }
-
- dcp::ScopeGuard sg = [gc]() { delete gc; };
-
- gc->SetAntialiasMode (wxANTIALIAS_DEFAULT);
-
- for (auto i: _views) {
-
- auto ic = dynamic_pointer_cast<TimelineContentView> (i);
-
- /* Find areas of overlap with other content views, so that we can plot them */
- list<dcpomatic::Rect<int>> overlaps;
- for (auto j: _views) {
- auto jc = dynamic_pointer_cast<TimelineContentView> (j);
- /* 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;
- }
-
- auto r = j->bbox().intersection(i->bbox());
- if (r) {
- overlaps.push_back (r.get ());
- }
- }
-
- i->paint (gc, overlaps);
- }
-
- if (_zoom_point) {
- gc->SetPen(gui_is_dark() ? *wxWHITE_PEN : *wxBLACK_PEN);
- gc->SetBrush (*wxTRANSPARENT_BRUSH);
- gc->DrawRectangle (
- min (_down_point.x, _zoom_point->x),
- min (_down_point.y, _zoom_point->y),
- abs (_down_point.x - _zoom_point->x),
- abs (_down_point.y - _zoom_point->y)
- );
- }
-
- /* Playhead */
-
- gc->SetPen (*wxRED_PEN);
- auto path = gc->CreatePath ();
- 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);
-}
-
-
-void
-Timeline::film_change(ChangeType type, FilmProperty p)
-{
- if (type != ChangeType::DONE) {
- return;
- }
-
- if (p == FilmProperty::CONTENT || p == FilmProperty::REEL_TYPE || p == FilmProperty::REEL_LENGTH) {
- ensure_ui_thread ();
- recreate_views ();
- } else if (p == FilmProperty::CONTENT_ORDER) {
- Refresh ();
- }
-}
-
-
-void
-Timeline::recreate_views ()
-{
- auto film = _film.lock ();
- if (!film) {
- return;
- }
-
- _views.clear ();
- _views.push_back (_time_axis_view);
- _views.push_back (_reels_view);
-
- for (auto i: film->content ()) {
- if (i->video) {
- _views.push_back (make_shared<TimelineVideoContentView>(*this, i));
- }
-
- if (i->audio && !i->audio->mapping().mapped_output_channels().empty ()) {
- _views.push_back (make_shared<TimelineAudioContentView>(*this, i));
- }
-
- for (auto j: i->text) {
- _views.push_back (make_shared<TimelineTextContentView>(*this, i, j));
- }
-
- if (i->atmos) {
- _views.push_back (make_shared<TimelineAtmosContentView>(*this, i));
- }
- }
-
- assign_tracks ();
- setup_scrollbars ();
- Refresh ();
-}
-
-
-void
-Timeline::film_content_change (ChangeType type, int property, bool frequent)
-{
- if (type != ChangeType::DONE) {
- return;
- }
-
- ensure_ui_thread ();
-
- 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 (shared_ptr<const Film> film, TimelineViewList& views, int& tracks)
-{
- int const base = tracks;
-
- for (auto i: views) {
- if (!dynamic_pointer_cast<T>(i)) {
- continue;
- }
-
- auto cv = dynamic_pointer_cast<TimelineContentView> (i);
- DCPOMATIC_ASSERT(cv);
-
- int t = base;
-
- auto content = cv->content();
- DCPTimePeriod const content_period = content->period(film);
-
- while (true) {
- auto j = views.begin();
- while (j != views.end()) {
- auto test = dynamic_pointer_cast<T> (*j);
- if (!test) {
- ++j;
- continue;
- }
-
- auto test_content = test->content();
- if (
- test->track() && test->track().get() == t &&
- content_period.overlap(test_content->period(film))
- ) {
- /* we have an overlap on track `t' */
- ++t;
- break;
- }
-
- ++j;
- }
-
- if (j == views.end ()) {
- /* no overlap on `t' */
- break;
- }
- }
-
- cv->set_track (t);
- tracks = max (tracks, t + 1);
- }
-
- 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;
- auto cva = dynamic_pointer_cast<TimelineAudioContentView>(a);
- if (cva) {
- auto oc = cva->content()->audio->mapping().mapped_output_channels();
- la = *min_element(boost::begin(oc), boost::end(oc));
- }
- int lb = -1;
- auto cvb = dynamic_pointer_cast<TimelineAudioContentView>(b);
- if (cvb) {
- auto 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 1
- Video 2
- Video N
- Text 1
- Text 2
- Text N
- Atmos
- Audio 1
- Audio 2
- Audio N
- */
-
- auto film = _film.lock ();
- DCPOMATIC_ASSERT (film);
-
- _tracks = 0;
-
- for (auto i: _views) {
- auto c = dynamic_pointer_cast<TimelineContentView>(i);
- if (c) {
- c->unset_track ();
- }
- }
-
- int const video_tracks = place<TimelineVideoContentView> (film, _views, _tracks);
- int const text_tracks = place<TimelineTextContentView> (film, _views, _tracks);
-
- /* Atmos */
-
- bool have_atmos = false;
- for (auto i: _views) {
- auto cv = dynamic_pointer_cast<TimelineAtmosContentView>(i);
- if (cv) {
- cv->set_track (_tracks);
- have_atmos = true;
- }
- }
-
- if (have_atmos) {
- ++_tracks;
- }
-
- /* Audio. We're sorting the views so that we get the audio views in order of increasing
- DCP channel index.
- */
-
- auto views = _views;
- sort(views.begin(), views.end(), AudioMappingComparator());
- int const audio_tracks = place<TimelineAudioContentView> (film, views, _tracks);
-
- _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);
-
- _time_axis_view->set_y (tracks());
- _reels_view->set_y (8);
-}
-
-
-int
-Timeline::tracks () const
-{
- return _tracks;
-}
-
-
-void
-Timeline::setup_scrollbars ()
-{
- auto film = _film.lock ();
- if (!film || !_pixels_per_second) {
- return;
- }
-
- 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(), h);
- _main_canvas->SetScrollRate (_x_scroll_rate, _y_scroll_rate);
-}
-
-
-shared_ptr<TimelineView>
-Timeline::event_to_view (wxMouseEvent& ev)
-{
- /* Search backwards through views so that we find the uppermost one first */
- auto i = _views.rbegin();
-
- 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)) {
- ++i;
- }
-
- if (i == _views.rend ()) {
- return {};
- }
-
- return *i;
-}
-
-
-void
-Timeline::left_down (wxMouseEvent& ev)
-{
- _left_down = true;
- _down_point = ev.GetPosition ();
-
- switch (_tool) {
- case SELECT:
- left_down_select (ev);
- break;
- case ZOOM:
- case ZOOM_ALL:
- case SNAP:
- case SEQUENCE:
- /* Nothing to do */
- break;
- }
-}
-
-
-void
-Timeline::left_down_select (wxMouseEvent& ev)
-{
- auto view = event_to_view (ev);
- auto content_view = dynamic_pointer_cast<TimelineContentView>(view);
-
- _down_view.reset ();
+double constexpr minimum_pixels_per_second = 640.0 / (60 * 60 * 3);
- if (content_view) {
- _down_view = content_view;
- _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) {
- continue;
- }
-
- if (!ev.ShiftDown ()) {
- cv->set_selected (view == i);
- }
- }
-
- if (content_view && ev.ShiftDown ()) {
- content_view->set_selected (!content_view->selected ());
- }
-
- _first_move = false;
-
- if (_down_view) {
- /* Pre-compute the points that we might snap to */
- for (auto i: _views) {
- auto cv = dynamic_pointer_cast<TimelineContentView>(i);
- if (!cv || cv == _down_view || cv->content() == _down_view->content()) {
- continue;
- }
-
- auto 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(film));
- _end_snaps.push_back (cv->content()->end(film));
-
- for (auto i: cv->content()->reel_split_points(film)) {
- _start_snaps.push_back (i);
- }
- }
-
- /* Tell everyone that things might change frequently during the drag */
- _down_view->content()->set_change_signals_frequent (true);
- }
-}
-
-
-void
-Timeline::left_up (wxMouseEvent& ev)
-{
- _left_down = false;
-
- switch (_tool) {
- case SELECT:
- left_up_select (ev);
- break;
- case ZOOM:
- left_up_zoom (ev);
- break;
- case ZOOM_ALL:
- case SNAP:
- case SEQUENCE:
- break;
- }
-}
-
-
-void
-Timeline::left_up_select (wxMouseEvent& ev)
+Timeline::Timeline(wxWindow* parent)
+ : wxPanel(parent, wxID_ANY)
{
- if (_down_view) {
- _down_view->content()->set_change_signals_frequent (false);
- }
- _content_panel->set_selection (selected_content ());
- /* 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 ();
- setup_scrollbars ();
- Refresh ();
-
- _start_snaps.clear ();
- _end_snaps.clear ();
-}
-
-
-void
-Timeline::left_up_zoom (wxMouseEvent& ev)
-{
- _zoom_point = ev.GetPosition ();
-
- int vsx, vsy;
- _main_canvas->GetViewStart (&vsx, &vsy);
-
- wxPoint top_left(min(_down_point.x, _zoom_point->x), min(_down_point.y, _zoom_point->y));
- wxPoint bottom_right(max(_down_point.x, _zoom_point->x), max(_down_point.y, _zoom_point->y));
-
- 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;
- }
-
- auto const time_left = DCPTime::from_seconds((top_left.x + vsx) / *_pixels_per_second);
- auto 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 - 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 ();
- 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 ();
-}
-
-
-void
-Timeline::set_pixels_per_track (int h)
-{
- _pixels_per_track = max(_minimum_pixels_per_track, h);
}
void
-Timeline::mouse_moved (wxMouseEvent& ev)
+Timeline::set_pixels_per_second(double pps)
{
- switch (_tool) {
- case SELECT:
- mouse_moved_select (ev);
- break;
- case ZOOM:
- mouse_moved_zoom (ev);
- break;
- case ZOOM_ALL:
- case SNAP:
- case SEQUENCE:
- break;
- }
+ _pixels_per_second = std::max(minimum_pixels_per_second, pps);
}
-void
-Timeline::mouse_moved_select (wxMouseEvent& ev)
-{
- if (!_left_down) {
- return;
- }
-
- set_position_from_event (ev);
-}
-
-
-void
-Timeline::mouse_moved_zoom (wxMouseEvent& ev)
-{
- if (!_left_down) {
- return;
- }
-
- _zoom_point = ev.GetPosition ();
- setup_scrollbars();
- Refresh ();
-}
-
-
-void
-Timeline::right_down (wxMouseEvent& ev)
-{
- switch (_tool) {
- case SELECT:
- right_down_select (ev);
- break;
- case ZOOM:
- /* Zoom out */
- set_pixels_per_second (*_pixels_per_second / 2);
- set_pixels_per_track (_pixels_per_track / 2);
- setup_scrollbars ();
- Refresh ();
- break;
- case ZOOM_ALL:
- case SNAP:
- case SEQUENCE:
- break;
- }
-}
-
-
-void
-Timeline::right_down_select (wxMouseEvent& ev)
-{
- auto view = event_to_view (ev);
- auto cv = dynamic_pointer_cast<TimelineContentView> (view);
- if (!cv) {
- return;
- }
-
- if (!cv->selected ()) {
- clear_selection ();
- cv->set_selected (true);
- }
-
- _menu.popup (_film, selected_content (), selected_views (), ev.GetPosition ());
-}
-
-
-void
-Timeline::maybe_snap (DCPTime a, DCPTime b, optional<DCPTime>& nearest_distance) const
-{
- auto const d = a - b;
- if (!nearest_distance || d.abs() < nearest_distance.get().abs()) {
- nearest_distance = d;
- }
-}
-
-
-void
-Timeline::set_position_from_event (wxMouseEvent& ev, bool force_emit)
-{
- if (!_pixels_per_second) {
- return;
- }
-
- double const pps = _pixels_per_second.get ();
-
- auto const p = ev.GetPosition();
-
- if (!_first_move) {
- /* We haven't moved yet; in that case, we must move the mouse some reasonable distance
- before the drag is considered to have started.
- */
- int const dist = sqrt (pow (p.x - _down_point.x, 2) + pow (p.y - _down_point.y, 2));
- if (dist < 8) {
- return;
- }
- _first_move = true;
- }
-
- if (!_down_view) {
- return;
- }
-
- auto new_position = _down_view_position + DCPTime::from_seconds ((p.x - _down_point.x) / pps);
-
- auto film = _film.lock ();
- DCPOMATIC_ASSERT (film);
-
- if (_snap) {
- auto 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).
- */
- optional<DCPTime> nearest_distance;
-
- /* Find the nearest snap point */
-
- for (auto i: _start_snaps) {
- maybe_snap (i, new_position, nearest_distance);
- }
-
- for (auto i: _end_snaps) {
- maybe_snap (i, new_end, nearest_distance);
- }
-
- if (nearest_distance) {
- /* Snap if it's close; `close' means within a proportion of the time on the timeline */
- if (nearest_distance.get().abs() < DCPTime::from_seconds ((width() / pps) / 64)) {
- new_position += nearest_distance.get ();
- }
- }
- }
-
- if (new_position < DCPTime ()) {
- new_position = DCPTime ();
- }
-
- _down_view->content()->set_position (film, new_position, 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 ()
-{
- if (_main_canvas->GetSize().GetWidth() > 0 && _first_resize) {
- zoom_all ();
- _first_resize = false;
- }
- setup_scrollbars ();
-}
-
-
-void
-Timeline::clear_selection ()
-{
- for (auto i: _views) {
- shared_ptr<TimelineContentView> cv = dynamic_pointer_cast<TimelineContentView>(i);
- if (cv) {
- cv->set_selected (false);
- }
- }
-}
-
-
-TimelineContentViewList
-Timeline::selected_views () const
-{
- TimelineContentViewList sel;
-
- for (auto i: _views) {
- auto cv = dynamic_pointer_cast<TimelineContentView>(i);
- if (cv && cv->selected()) {
- sel.push_back (cv);
- }
- }
-
- return sel;
-}
-
-
-ContentList
-Timeline::selected_content () const
-{
- ContentList sel;
-
- for (auto i: selected_views()) {
- sel.push_back(i->content());
- }
-
- return sel;
-}
-
-
-void
-Timeline::set_selection (ContentList selection)
-{
- for (auto i: _views) {
- auto cv = dynamic_pointer_cast<TimelineContentView> (i);
- if (cv) {
- cv->set_selected (find (selection.begin(), selection.end(), cv->content ()) != selection.end ());
- }
- }
-}
-
-
-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)
-{
- if (ev.GetOrientation() == wxVERTICAL) {
- int x, y;
- _main_canvas->GetViewStart (&x, &y);
- _labels_canvas->Scroll (0, y);
- }
- ev.Skip ();
-}
-
-
-void
-Timeline::tool_clicked (Tool t)
-{
- switch (t) {
- case ZOOM:
- case SELECT:
- _tool = t;
- break;
- case ZOOM_ALL:
- zoom_all ();
- break;
- case SNAP:
- case SEQUENCE:
- break;
- }
-}
-
-
-void
-Timeline::zoom_all ()
-{
- auto film = _film.lock ();
- DCPOMATIC_ASSERT (film);
- 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;
- }
- }
-}
-
/*
- Copyright (C) 2013-2019 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2023 Carl Hetherington <cth@carlh.net>
This file is part of DCP-o-matic.
*/
-#include "content_menu.h"
-#include "timeline_content_view.h"
-#include "lib/film_property.h"
-#include "lib/rect.h"
+#ifndef DCPOMATIC_TIMELINE_H
+#define DCPOMATIC_TIMELINE_H
+
+
#include <dcp/warnings.h>
LIBDCP_DISABLE_WARNINGS
#include <wx/wx.h>
LIBDCP_ENABLE_WARNINGS
-#include <boost/signals2.hpp>
-
-
-class ContentPanel;
-class Film;
-class FilmViewer;
-class TimelineLabelsView;
-class TimelineReelsView;
-class TimelineTimeAxisView;
-class TimelineView;
+#include <boost/optional.hpp>
class Timeline : public wxPanel
{
public:
- Timeline (wxWindow *, ContentPanel *, std::shared_ptr<Film>, FilmViewer& viewer);
-
- std::shared_ptr<const Film> film () const;
-
- void force_redraw (dcpomatic::Rect<int> const &);
-
- int width () const;
+ explicit Timeline(wxWindow* parent);
- int pixels_per_track () const {
- return _pixels_per_track;
- }
-
- boost::optional<double> pixels_per_second () const {
+ boost::optional<double> pixels_per_second() const {
return _pixels_per_second;
}
- int tracks () const;
- void set_snap (bool s) {
- _snap = s;
- }
+protected:
+ void set_pixels_per_second(double pps);
- bool snap () const {
- return _snap;
- }
-
- void set_selection (ContentList selection);
-
- enum Tool {
- SELECT,
- ZOOM,
- ZOOM_ALL,
- SNAP,
- SEQUENCE
- };
-
- void tool_clicked (Tool t);
-
- int tracks_y_offset () const;
-
- void keypress(wxKeyEvent const &);
-
-private:
- void paint_labels ();
- void paint_main ();
- void left_down (wxMouseEvent &);
- void left_down_select (wxMouseEvent &);
- void left_up (wxMouseEvent &);
- void left_up_select (wxMouseEvent &);
- void left_up_zoom (wxMouseEvent &);
- void right_down (wxMouseEvent &);
- void right_down_select (wxMouseEvent &);
- void mouse_moved (wxMouseEvent &);
- void mouse_moved_select (wxMouseEvent &);
- void mouse_moved_zoom (wxMouseEvent &);
- void film_change(ChangeType type, FilmProperty);
- void film_content_change (ChangeType type, int, bool frequent);
- void resized ();
- void assign_tracks ();
- void set_position_from_event (wxMouseEvent& ev, bool force_emit = false);
- void clear_selection ();
- void recreate_views ();
- void setup_scrollbars ();
- void scrolled (wxScrollWinEvent& ev);
- void set_pixels_per_second (double pps);
- void set_pixels_per_track (int h);
- void zoom_all ();
- void update_playhead ();
- void mouse_wheel_turned(wxMouseEvent& event);
-
- std::shared_ptr<TimelineView> event_to_view (wxMouseEvent &);
- TimelineContentViewList selected_views () const;
- ContentList selected_content () const;
- void maybe_snap (dcpomatic::DCPTime a, dcpomatic::DCPTime b, boost::optional<dcpomatic::DCPTime>& nearest_distance) const;
-
- wxScrolledCanvas* _labels_canvas;
- wxScrolledCanvas* _main_canvas;
- ContentPanel* _content_panel;
- std::weak_ptr<Film> _film;
- FilmViewer& _viewer;
- TimelineViewList _views;
- std::shared_ptr<TimelineTimeAxisView> _time_axis_view;
- std::shared_ptr<TimelineReelsView> _reels_view;
- std::shared_ptr<TimelineLabelsView> _labels_view;
- int _tracks;
boost::optional<double> _pixels_per_second;
- bool _left_down;
- wxPoint _down_point;
- boost::optional<wxPoint> _zoom_point;
- std::shared_ptr<TimelineContentView> _down_view;
- dcpomatic::DCPTime _down_view_position;
- bool _first_move;
- ContentMenu _menu;
- bool _snap;
- std::list<dcpomatic::DCPTime> _start_snaps;
- std::list<dcpomatic::DCPTime> _end_snaps;
- Tool _tool;
- int _x_scroll_rate;
- int _y_scroll_rate;
- int _pixels_per_track;
- bool _first_resize;
- wxTimer _timer;
- boost::optional<int> _last_mouse_wheel_x;
- boost::optional<double> _last_mouse_wheel_time;
-
- static double const _minimum_pixels_per_second;
- static int const _minimum_pixels_per_track;
-
- boost::signals2::scoped_connection _film_changed_connection;
- boost::signals2::scoped_connection _film_content_change_connection;
};
+
+
+#endif
+++ /dev/null
-/*
- Copyright (C) 2016-2021 Carl Hetherington <cth@carlh.net>
-
- This file is part of DCP-o-matic.
-
- DCP-o-matic 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.
-
- DCP-o-matic 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 DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
-
-*/
-
-
-#include "timeline_atmos_content_view.h"
-
-
-using std::shared_ptr;
-
-
-/** @class TimelineAtmosContentView
- * @brief Timeline view for AtmosContent.
- */
-
-TimelineAtmosContentView::TimelineAtmosContentView (Timeline& tl, shared_ptr<Content> c)
- : TimelineContentView (tl, c)
-{
-
-}
-
-
-wxColour
-TimelineAtmosContentView::background_colour () const
-{
- return wxColour (149, 121, 232, 255);
-}
-
-
-wxColour
-TimelineAtmosContentView::foreground_colour () const
-{
- return wxColour (0, 0, 0, 255);
-}
+++ /dev/null
-/*
- Copyright (C) 2016-2021 Carl Hetherington <cth@carlh.net>
-
- This file is part of DCP-o-matic.
-
- DCP-o-matic 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.
-
- DCP-o-matic 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 DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
-
-*/
-
-
-#include "timeline_content_view.h"
-
-
-/** @class TimelineAtmosContentView
- * @brief Timeline view for AtmosContent.
- */
-class TimelineAtmosContentView : public TimelineContentView
-{
-public:
- TimelineAtmosContentView (Timeline& tl, std::shared_ptr<Content> c);
-
-private:
- bool active () const override {
- return true;
- }
-
- wxColour background_colour () const override;
- wxColour foreground_colour () const override;
-};
+++ /dev/null
-/*
- Copyright (C) 2013-2018 Carl Hetherington <cth@carlh.net>
-
- This file is part of DCP-o-matic.
-
- DCP-o-matic 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.
-
- DCP-o-matic 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 DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
-
-*/
-
-#include "timeline_audio_content_view.h"
-#include "wx_util.h"
-#include "lib/audio_content.h"
-#include "lib/util.h"
-
-using std::list;
-using std::shared_ptr;
-using std::dynamic_pointer_cast;
-
-/** @class TimelineAudioContentView
- * @brief Timeline view for AudioContent.
- */
-
-TimelineAudioContentView::TimelineAudioContentView (Timeline& tl, shared_ptr<Content> c)
- : TimelineContentView (tl, c)
-{
-
-}
-
-wxColour
-TimelineAudioContentView::background_colour () const
-{
- return wxColour (149, 121, 232, 255);
-}
-
-wxColour
-TimelineAudioContentView::foreground_colour () const
-{
- return wxColour (0, 0, 0, 255);
-}
-
-wxString
-TimelineAudioContentView::label () const
-{
- wxString s = TimelineContentView::label ();
- shared_ptr<AudioContent> ac = content()->audio;
- DCPOMATIC_ASSERT (ac);
-
- if (ac->gain() > 0.01) {
- s += wxString::Format (" +%.1fdB", ac->gain());
- } else if (ac->gain() < -0.01) {
- s += wxString::Format (" %.1fdB", ac->gain());
- }
-
- if (ac->delay() > 0) {
- s += wxString::Format (_(" delayed by %dms"), ac->delay());
- } else if (ac->delay() < 0) {
- s += wxString::Format (_(" advanced by %dms"), -ac->delay());
- }
-
- list<int> mapped = ac->mapping().mapped_output_channels();
- if (!mapped.empty ()) {
- s += wxString::FromUTF8(" → ");
- for (auto i: mapped) {
- s += std_to_wx(short_audio_channel_name(i)) + ", ";
- }
- s = s.Left(s.Length() - 2);
- }
-
- return s;
-}
+++ /dev/null
-/*
- Copyright (C) 2013-2015 Carl Hetherington <cth@carlh.net>
-
- This file is part of DCP-o-matic.
-
- DCP-o-matic 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.
-
- DCP-o-matic 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 DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
-
-*/
-
-
-#include "timeline_content_view.h"
-
-
-/** @class TimelineAudioContentView
- * @brief Timeline view for AudioContent.
- */
-class TimelineAudioContentView : public TimelineContentView
-{
-public:
- TimelineAudioContentView (Timeline& tl, std::shared_ptr<Content> c);
-
-private:
- bool active () const override {
- return true;
- }
- wxColour background_colour () const override;
- wxColour foreground_colour () const override;
- wxString label () const override;
-};
*/
-#include "timeline.h"
+#include "content_timeline.h"
#include "timeline_content_view.h"
#include "wx_util.h"
#include "lib/content.h"
#endif
-TimelineContentView::TimelineContentView (Timeline& tl, shared_ptr<Content> c)
- : TimelineView (tl)
+TimelineContentView::TimelineContentView(ContentTimeline& tl, shared_ptr<Content> c)
+ : ContentTimelineView(tl)
, _content (c)
{
_content_connection = c->Change.connect (bind (&TimelineContentView::content_change, this, _1, _3));
#define DCPOMATIC_TIMELINE_CONTENT_VIEW_H
-#include "timeline_view.h"
+#include "content_timeline_view.h"
#include "lib/change_signaller.h"
#include <dcp/warnings.h>
LIBDCP_DISABLE_WARNINGS
/** @class TimelineContentView
* @brief Parent class for views of pieces of content.
*/
-class TimelineContentView : public TimelineView
+class TimelineContentView : public ContentTimelineView
{
public:
- TimelineContentView (Timeline& tl, std::shared_ptr<Content> c);
+ TimelineContentView(ContentTimeline& tl, std::shared_ptr<Content> c);
dcpomatic::Rect<int> bbox () const override;
+++ /dev/null
-/*
- Copyright (C) 2013-2021 Carl Hetherington <cth@carlh.net>
-
- This file is part of DCP-o-matic.
-
- DCP-o-matic 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.
-
- DCP-o-matic 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 DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
-
-*/
-
-
-#include "content_panel.h"
-#include "film_editor.h"
-#include "timeline_dialog.h"
-#include "wx_util.h"
-#include "lib/compose.hpp"
-#include "lib/cross.h"
-#include "lib/film.h"
-#include "lib/playlist.h"
-#include <dcp/warnings.h>
-LIBDCP_DISABLE_WARNINGS
-#include <wx/graphics.h>
-LIBDCP_ENABLE_WARNINGS
-#include <list>
-
-
-using std::list;
-using std::shared_ptr;
-using std::string;
-using std::weak_ptr;
-#if BOOST_VERSION >= 106100
-using namespace boost::placeholders;
-#endif
-
-
-TimelineDialog::TimelineDialog(ContentPanel* cp, shared_ptr<Film> film, FilmViewer& viewer)
- : wxDialog (
- cp->window(),
- wxID_ANY,
- _("Timeline"),
- wxDefaultPosition,
- wxSize (640, 512),
-#ifdef DCPOMATIC_OSX
- /* I can't get wxFRAME_FLOAT_ON_PARENT to work on OS X, and although wxSTAY_ON_TOP keeps
- the window above all others (and not just our own) it's better than nothing for now.
- */
- wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxFULL_REPAINT_ON_RESIZE | wxSTAY_ON_TOP
-#else
- wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxFULL_REPAINT_ON_RESIZE | wxFRAME_FLOAT_ON_PARENT
-#endif
- )
- , _film (film)
- , _timeline (this, cp, film, viewer)
-{
- auto sizer = new wxBoxSizer (wxVERTICAL);
-
- wxBitmap select(icon_path("select"), wxBITMAP_TYPE_PNG);
- wxBitmap zoom(icon_path("zoom"), wxBITMAP_TYPE_PNG);
- wxBitmap zoom_all(icon_path("zoom_all"), wxBITMAP_TYPE_PNG);
- wxBitmap snap(icon_path("snap"), wxBITMAP_TYPE_PNG);
- wxBitmap sequence(icon_path("sequence"), wxBITMAP_TYPE_PNG);
-
- _toolbar = new wxToolBar (this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTB_HORIZONTAL);
- _toolbar->SetMargins (4, 4);
- _toolbar->SetToolBitmapSize (wxSize(32, 32));
- _toolbar->AddRadioTool ((int) Timeline::SELECT, _("Select"), select, wxNullBitmap, _("Select and move content"));
- _toolbar->AddRadioTool ((int) Timeline::ZOOM, _("Zoom"), zoom, wxNullBitmap, _("Zoom in / out"));
- _toolbar->AddTool ((int) Timeline::ZOOM_ALL, _("Zoom all"), zoom_all, _("Zoom out to whole film"));
- _toolbar->AddCheckTool ((int) Timeline::SNAP, _("Snap"), snap, wxNullBitmap, _("Snap"));
- _toolbar->AddCheckTool ((int) Timeline::SEQUENCE, _("Sequence"), sequence, wxNullBitmap, _("Keep video and subtitles in sequence"));
- _toolbar->Realize ();
-
- _toolbar->Bind (wxEVT_TOOL, bind (&TimelineDialog::tool_clicked, this, _1));
-
- sizer->Add (_toolbar, 0, wxALL, 12);
- sizer->Add (&_timeline, 1, wxEXPAND | wxALL, 12);
-
-#ifdef DCPOMATIC_LINUX
- auto buttons = CreateSeparatedButtonSizer (wxCLOSE);
- if (buttons) {
- sizer->Add (buttons, wxSizerFlags().Expand().DoubleBorder());
- }
-#endif
-
- SetSizer (sizer);
- sizer->Layout ();
- sizer->SetSizeHints (this);
-
- Bind(wxEVT_CHAR_HOOK, boost::bind(&TimelineDialog::keypress, this, _1));
-
- _toolbar->ToggleTool ((int) Timeline::SNAP, _timeline.snap ());
- film_change(ChangeType::DONE, FilmProperty::SEQUENCE);
-
- _film_changed_connection = film->Change.connect (bind (&TimelineDialog::film_change, this, _1, _2));
-}
-
-
-void
-TimelineDialog::film_change(ChangeType type, FilmProperty p)
-{
- if (type != ChangeType::DONE) {
- return;
- }
-
- auto film = _film.lock ();
- if (!film) {
- return;
- }
-
- if (p == FilmProperty::SEQUENCE) {
- _toolbar->ToggleTool ((int) Timeline::SEQUENCE, film->sequence ());
- }
-}
-
-
-void
-TimelineDialog::set_selection (ContentList selection)
-{
- _timeline.set_selection (selection);
-}
-
-
-void
-TimelineDialog::tool_clicked (wxCommandEvent& ev)
-{
- Timeline::Tool t = static_cast<Timeline::Tool>(ev.GetId());
- _timeline.tool_clicked (t);
- if (t == Timeline::SNAP) {
- _timeline.set_snap (_toolbar->GetToolState(static_cast<int>(t)));
- } else if (t == Timeline::SEQUENCE) {
- auto film = _film.lock ();
- if (film) {
- film->set_sequence (_toolbar->GetToolState(static_cast<int>(t)));
- }
- }
-}
-
-
-void
-TimelineDialog::keypress(wxKeyEvent const& event)
-{
- _timeline.keypress(event);
-}
+++ /dev/null
-/*
- Copyright (C) 2013-2021 Carl Hetherington <cth@carlh.net>
-
- This file is part of DCP-o-matic.
-
- DCP-o-matic 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.
-
- DCP-o-matic 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 DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
-
-*/
-
-
-#include "timeline.h"
-#include <dcp/warnings.h>
-LIBDCP_DISABLE_WARNINGS
-#include <wx/wx.h>
-LIBDCP_ENABLE_WARNINGS
-
-
-class Playlist;
-
-
-class TimelineDialog : public wxDialog
-{
-public:
- TimelineDialog(ContentPanel *, std::shared_ptr<Film>, FilmViewer& viewer);
-
- void set_selection (ContentList selection);
-
-private:
- void film_change(ChangeType type, FilmProperty);
- void tool_clicked (wxCommandEvent& id);
- void keypress(wxKeyEvent const& event);
-
- std::weak_ptr<Film> _film;
- Timeline _timeline;
- wxToolBar* _toolbar;
- boost::signals2::scoped_connection _film_changed_connection;
-};
*/
-#include "timeline.h"
+#include "content_timeline.h"
#include "timeline_labels_view.h"
#include "wx_util.h"
#include <dcp/warnings.h>
using std::min;
-TimelineLabelsView::TimelineLabelsView (Timeline& tl)
- : TimelineView (tl)
+TimelineLabelsView::TimelineLabelsView(ContentTimeline& tl)
+ : ContentTimelineView(tl)
{
wxString labels[] = {
_("Video"),
*/
-#include "timeline_view.h"
+#include "content_timeline_view.h"
class wxWindow;
-class TimelineLabelsView : public TimelineView
+class TimelineLabelsView : public ContentTimelineView
{
public:
- explicit TimelineLabelsView (Timeline& tl);
+ explicit TimelineLabelsView(ContentTimeline& tl);
dcpomatic::Rect<int> bbox () const override;
*/
-#include "timeline.h"
+#include "content_timeline.h"
#include "timeline_reels_view.h"
#include "wx_util.h"
#include "lib/film.h"
using namespace dcpomatic;
-TimelineReelsView::TimelineReelsView (Timeline& tl, int y)
- : TimelineView (tl)
+TimelineReelsView::TimelineReelsView(ContentTimeline& tl, int y)
+ : ContentTimelineView(tl)
, _y (y)
{
*/
-#include "timeline_view.h"
+#include "content_timeline_view.h"
-class TimelineReelsView : public TimelineView
+class TimelineReelsView : public ContentTimelineView
{
public:
- TimelineReelsView (Timeline& tl, int y);
+ TimelineReelsView(ContentTimeline& tl, int y);
dcpomatic::Rect<int> bbox () const override;
void set_y (int y);
+++ /dev/null
-/*
- Copyright (C) 2013-2018 Carl Hetherington <cth@carlh.net>
-
- This file is part of DCP-o-matic.
-
- DCP-o-matic 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.
-
- DCP-o-matic 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 DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
-
-*/
-
-
-#include "timeline_text_content_view.h"
-#include "lib/text_content.h"
-#include "lib/content.h"
-
-
-using std::shared_ptr;
-
-
-TimelineTextContentView::TimelineTextContentView (Timeline& tl, shared_ptr<Content> c, shared_ptr<TextContent> caption)
- : TimelineContentView (tl, c)
- , _caption (caption)
-{
-
-}
-
-wxColour
-TimelineTextContentView::background_colour () const
-{
- if (!active ()) {
- return wxColour (210, 210, 210, 128);
- }
-
- return wxColour (163, 255, 154, 255);
-}
-
-wxColour
-TimelineTextContentView::foreground_colour () const
-{
- if (!active ()) {
- return wxColour (180, 180, 180, 128);
- }
-
- return wxColour (0, 0, 0, 255);
-}
-
-bool
-TimelineTextContentView::active () const
-{
- return _caption->use();
-}
+++ /dev/null
-/*
- Copyright (C) 2013-2016 Carl Hetherington <cth@carlh.net>
-
- This file is part of DCP-o-matic.
-
- DCP-o-matic 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.
-
- DCP-o-matic 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 DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
-
-*/
-
-#include "timeline_content_view.h"
-
-class TextContent;
-class TextContent;
-
-/** @class TimelineTextContentView
- * @brief Timeline view for TextContent.
- */
-class TimelineTextContentView : public TimelineContentView
-{
-public:
- TimelineTextContentView (Timeline& tl, std::shared_ptr<Content>, std::shared_ptr<TextContent>);
-
-private:
- bool active () const override;
- wxColour background_colour () const override;
- wxColour foreground_colour () const override;
-
- std::shared_ptr<TextContent> _caption;
-};
*/
-#include "timeline.h"
+#include "content_timeline.h"
#include "timeline_time_axis_view.h"
#include "wx_util.h"
#include <dcp/warnings.h>
using namespace dcpomatic;
-TimelineTimeAxisView::TimelineTimeAxisView (Timeline& tl, int y)
- : TimelineView (tl)
+TimelineTimeAxisView::TimelineTimeAxisView(ContentTimeline& tl, int y)
+ : ContentTimelineView(tl)
, _y (y)
{
*/
-#include "timeline_view.h"
-class TimelineTimeAxisView : public TimelineView
+#include "content_timeline_view.h"
+
+
+class TimelineTimeAxisView : public ContentTimelineView
{
public:
- TimelineTimeAxisView (Timeline& tl, int y);
+ TimelineTimeAxisView(ContentTimeline& tl, int y);
dcpomatic::Rect<int> bbox () const override;
void set_y (int y);
+++ /dev/null
-/*
- Copyright (C) 2013-2019 Carl Hetherington <cth@carlh.net>
-
- This file is part of DCP-o-matic.
-
- DCP-o-matic 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.
-
- DCP-o-matic 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 DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
-
-*/
-
-#include "lib/image_content.h"
-#include "lib/video_content.h"
-#include "timeline_video_content_view.h"
-
-using std::dynamic_pointer_cast;
-using std::shared_ptr;
-
-TimelineVideoContentView::TimelineVideoContentView (Timeline& tl, shared_ptr<Content> c)
- : TimelineContentView (tl, c)
-{
-
-}
-
-wxColour
-TimelineVideoContentView::background_colour () const
-{
- if (!active()) {
- return wxColour (210, 210, 210, 128);
- }
-
- return wxColour (242, 92, 120, 255);
-}
-
-wxColour
-TimelineVideoContentView::foreground_colour () const
-{
- if (!active()) {
- return wxColour (180, 180, 180, 128);
- }
-
- return wxColour (0, 0, 0, 255);
-}
-
-bool
-TimelineVideoContentView::active () const
-{
- shared_ptr<Content> c = _content.lock ();
- DCPOMATIC_ASSERT (c);
- return c->video && c->video->use();
-}
+++ /dev/null
-/*
- Copyright (C) 2013-2015 Carl Hetherington <cth@carlh.net>
-
- This file is part of DCP-o-matic.
-
- DCP-o-matic 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.
-
- DCP-o-matic 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 DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
-
-*/
-
-
-#include "timeline_content_view.h"
-
-
-/** @class TimelineVideoContentView
- * @brief Timeline view for VideoContent.
- */
-class TimelineVideoContentView : public TimelineContentView
-{
-public:
- TimelineVideoContentView (Timeline& tl, std::shared_ptr<Content> c);
-
-private:
- bool active () const override;
- wxColour background_colour () const override;
- wxColour foreground_colour () const override;
-};
+++ /dev/null
-/*
- Copyright (C) 2013-2021 Carl Hetherington <cth@carlh.net>
-
- This file is part of DCP-o-matic.
-
- DCP-o-matic 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.
-
- DCP-o-matic 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 DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
-
-*/
-
-
-#include "timeline_view.h"
-#include "timeline.h"
-
-
-using std::list;
-using namespace dcpomatic;
-
-
-/** @class TimelineView
- * @brief Parent class for components of the timeline (e.g. a piece of content or an axis).
- */
-TimelineView::TimelineView (Timeline& t)
- : _timeline (t)
-{
-
-}
-
-
-void
-TimelineView::paint (wxGraphicsContext* g, list<dcpomatic::Rect<int>> overlaps)
-{
- _last_paint_bbox = bbox ();
- do_paint (g, overlaps);
-}
-
-
-void
-TimelineView::force_redraw ()
-{
- _timeline.force_redraw (_last_paint_bbox.extended(4));
- _timeline.force_redraw (bbox().extended(4));
-}
-
-
-int
-TimelineView::time_x (DCPTime t) const
-{
- return t.seconds() * _timeline.pixels_per_second().get_value_or(0);
-}
-
-
-int
-TimelineView::y_pos(int t) const
-{
- return t * _timeline.pixels_per_track() + _timeline.tracks_y_offset();
-}
-
-
/*
- Copyright (C) 2013-2021 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2023 Carl Hetherington <cth@carlh.net>
This file is part of DCP-o-matic.
class wxGraphicsContext;
-class Timeline;
-/** @class TimelineView
- * @brief Parent class for components of the timeline (e.g. a piece of content or an axis).
+/** @class ContentTimelineView
+ * @brief Parent class for components of the content timeline (e.g. a piece of content or an axis).
*/
+template <class Timeline>
class TimelineView
{
public:
- explicit TimelineView (Timeline& t);
- virtual ~TimelineView () {}
+ explicit TimelineView(Timeline& timeline)
+ : _timeline(timeline)
+ {}
- TimelineView (TimelineView const&) = delete;
- TimelineView& operator= (TimelineView const&) = delete;
+ virtual ~TimelineView () = default;
- void paint (wxGraphicsContext* g, std::list<dcpomatic::Rect<int>> overlaps);
- void force_redraw ();
+ TimelineView(TimelineView const&) = delete;
+ TimelineView& operator=(TimelineView const&) = delete;
- virtual dcpomatic::Rect<int> bbox () const = 0;
+ void force_redraw()
+ {
+ _timeline.force_redraw(_last_paint_bbox.extended(4));
+ _timeline.force_redraw(bbox().extended(4));
+ }
-protected:
- virtual void do_paint (wxGraphicsContext *, std::list<dcpomatic::Rect<int>> overlaps) = 0;
+ virtual dcpomatic::Rect<int> bbox() const = 0;
- int time_x (dcpomatic::DCPTime t) const;
- int y_pos(int t) const;
+protected:
+ int time_x(dcpomatic::DCPTime t) const
+ {
+ return t.seconds() * _timeline.pixels_per_second().get_value_or(0);
+ }
Timeline& _timeline;
-
-private:
dcpomatic::Rect<int> _last_paint_bbox;
};
-typedef std::vector<std::shared_ptr<TimelineView>> TimelineViewList;
-
-
#endif
+
content_panel.cc
content_properties_dialog.cc
content_sub_panel.cc
+ content_timeline.cc
+ content_timeline_atmos_view.cc
+ content_timeline_audio_view.cc
+ content_timeline_dialog.cc
+ content_timeline_text_view.cc
+ content_timeline_video_view.cc
+ content_timeline_view.cc
content_version_dialog.cc
content_view.cc
controls.cc
custom_scale_dialog.cc
dcp_referencing_dialog.cc
dcp_panel.cc
+ dcp_timeline.cc
+ dcp_timeline_dialog.cc
+ dcp_timeline_reel_marker_view.cc
dcp_text_track_dialog.cc
dcpomatic_button.cc
dcpomatic_choice.cc
timer_display.cc
timecode.cc
timeline.cc
- timeline_atmos_content_view.cc
timeline_content_view.cc
- timeline_dialog.cc
- timeline_audio_content_view.cc
timeline_labels_view.cc
- timeline_text_content_view.cc
timeline_reels_view.cc
timeline_time_axis_view.cc
- timeline_video_content_view.cc
- timeline_view.cc
timing_panel.cc
try_unmount_dialog.cc
update_dialog.cc
#define DCPOMATIC_SIZER_GAP 8
#define DCPOMATIC_DIALOG_BORDER 12
#ifdef __WXGTK3__
-#define DCPOMATIC_SPIN_CTRL_WIDTH 118
+#define DCPOMATIC_SPIN_CTRL_WIDTH 124
#else
#define DCPOMATIC_SPIN_CTRL_WIDTH 56
#endif