summaryrefslogtreecommitdiff
path: root/src/wx
diff options
context:
space:
mode:
Diffstat (limited to 'src/wx')
-rw-r--r--src/wx/about_dialog.cc43
-rw-r--r--src/wx/audio_dialog.cc5
-rw-r--r--src/wx/audio_panel.cc57
-rw-r--r--src/wx/audio_panel.h3
-rw-r--r--src/wx/check_box.cc7
-rw-r--r--src/wx/check_box.h1
-rw-r--r--src/wx/cinema_dialog.cc45
-rw-r--r--src/wx/cinema_dialog.h11
-rw-r--r--src/wx/colours.h25
-rw-r--r--src/wx/config_dialog.cc6
-rw-r--r--src/wx/content_menu.cc12
-rw-r--r--src/wx/content_panel.cc15
-rw-r--r--src/wx/content_panel.h4
-rw-r--r--src/wx/content_sub_panel.cc23
-rw-r--r--src/wx/content_sub_panel.h1
-rw-r--r--src/wx/content_timeline.cc1021
-rw-r--r--src/wx/content_timeline.h149
-rw-r--r--src/wx/content_timeline_atmos_view.cc (renamed from src/wx/timeline_atmos_content_view.cc)15
-rw-r--r--src/wx/content_timeline_atmos_view.h (renamed from src/wx/timeline_atmos_content_view.h)8
-rw-r--r--src/wx/content_timeline_audio_view.cc (renamed from src/wx/timeline_audio_content_view.cc)23
-rw-r--r--src/wx/content_timeline_audio_view.h (renamed from src/wx/timeline_audio_content_view.h)8
-rw-r--r--src/wx/content_timeline_dialog.cc (renamed from src/wx/timeline_dialog.cc)38
-rw-r--r--src/wx/content_timeline_dialog.h (renamed from src/wx/timeline_dialog.h)8
-rw-r--r--src/wx/content_timeline_text_view.cc (renamed from src/wx/timeline_text_content_view.cc)13
-rw-r--r--src/wx/content_timeline_text_view.h (renamed from src/wx/timeline_text_content_view.h)12
-rw-r--r--src/wx/content_timeline_video_view.cc (renamed from src/wx/timeline_video_content_view.cc)18
-rw-r--r--src/wx/content_timeline_video_view.h (renamed from src/wx/timeline_video_content_view.h)8
-rw-r--r--src/wx/content_timeline_view.cc42
-rw-r--r--src/wx/content_timeline_view.h60
-rw-r--r--src/wx/content_view.cc3
-rw-r--r--src/wx/dcp_panel.cc221
-rw-r--r--src/wx/dcp_panel.h17
-rw-r--r--src/wx/dcp_referencing_dialog.cc231
-rw-r--r--src/wx/dcp_referencing_dialog.h70
-rw-r--r--src/wx/dcp_timeline.cc616
-rw-r--r--src/wx/dcp_timeline.h125
-rw-r--r--src/wx/dcp_timeline_dialog.cc78
-rw-r--r--src/wx/dcp_timeline_dialog.h39
-rw-r--r--src/wx/dcp_timeline_reel_marker_view.cc71
-rw-r--r--src/wx/dcp_timeline_reel_marker_view.h59
-rw-r--r--src/wx/dcp_timeline_view.h44
-rw-r--r--src/wx/dcpomatic_choice.cc22
-rw-r--r--src/wx/dcpomatic_choice.h10
-rw-r--r--src/wx/dir_picker_ctrl.cc11
-rw-r--r--src/wx/dir_picker_ctrl.h3
-rw-r--r--src/wx/disk_warning_dialog.cc7
-rw-r--r--src/wx/dkdm_dialog.cc11
-rw-r--r--src/wx/export_video_file_dialog.h2
-rw-r--r--src/wx/file_picker_ctrl.cc8
-rw-r--r--src/wx/film_editor.cc33
-rw-r--r--src/wx/film_editor.h8
-rw-r--r--src/wx/film_name_location_dialog.cc15
-rw-r--r--src/wx/film_viewer.h2
-rw-r--r--src/wx/full_config_dialog.cc134
-rw-r--r--src/wx/grok/gpu_config_panel.h227
-rw-r--r--src/wx/id.h1
-rw-r--r--src/wx/job_view.cc3
-rw-r--r--src/wx/kdm_cpl_panel.cc7
-rw-r--r--src/wx/kdm_dialog.cc36
-rw-r--r--src/wx/kdm_dialog.h1
-rw-r--r--src/wx/kdm_timing_panel.cc71
-rw-r--r--src/wx/kdm_timing_panel.h27
-rw-r--r--src/wx/load_config_from_zip_dialog.cc59
-rw-r--r--src/wx/load_config_from_zip_dialog.h41
-rw-r--r--src/wx/metadata_dialog.cc10
-rw-r--r--src/wx/player_config_dialog.cc5
-rw-r--r--src/wx/playlist_controls.cc5
-rw-r--r--src/wx/playlist_editor_config_dialog.cc7
-rw-r--r--src/wx/recipient_dialog.cc43
-rw-r--r--src/wx/recipient_dialog.h6
-rw-r--r--src/wx/recipients_panel.cc67
-rw-r--r--src/wx/recipients_panel.h7
-rw-r--r--src/wx/screens_panel.cc271
-rw-r--r--src/wx/screens_panel.h52
-rw-r--r--src/wx/system_information_dialog.cc5
-rw-r--r--src/wx/text_panel.cc53
-rw-r--r--src/wx/text_panel.h3
-rw-r--r--src/wx/timecode.cc2
-rw-r--r--src/wx/timecode.h13
-rw-r--r--src/wx/timeline.cc1001
-rw-r--r--src/wx/timeline.h131
-rw-r--r--src/wx/timeline_content_view.cc7
-rw-r--r--src/wx/timeline_content_view.h6
-rw-r--r--src/wx/timeline_labels_view.cc6
-rw-r--r--src/wx/timeline_labels_view.h6
-rw-r--r--src/wx/timeline_reels_view.cc6
-rw-r--r--src/wx/timeline_reels_view.h6
-rw-r--r--src/wx/timeline_time_axis_view.cc6
-rw-r--r--src/wx/timeline_time_axis_view.h8
-rw-r--r--src/wx/timeline_view.cc69
-rw-r--r--src/wx/timeline_view.h42
-rw-r--r--src/wx/try_unmount_dialog.cc8
-rw-r--r--src/wx/update_dialog.cc5
-rw-r--r--src/wx/verify_dcp_progress_dialog.cc52
-rw-r--r--src/wx/verify_dcp_progress_dialog.h9
-rw-r--r--src/wx/verify_dcp_progress_panel.cc87
-rw-r--r--src/wx/verify_dcp_progress_panel.h41
-rw-r--r--src/wx/verify_dcp_result_dialog.cc47
-rw-r--r--src/wx/verify_dcp_result_dialog.h (renamed from src/wx/verify_dcp_dialog.h)4
-rw-r--r--src/wx/verify_dcp_result_panel.cc (renamed from src/wx/verify_dcp_dialog.cc)157
-rw-r--r--src/wx/verify_dcp_result_panel.h50
-rw-r--r--src/wx/video_panel.cc56
-rw-r--r--src/wx/video_panel.h3
-rw-r--r--src/wx/wscript25
-rw-r--r--src/wx/wx_util.cc151
-rw-r--r--src/wx/wx_util.h20
-rw-r--r--src/wx/wx_variant.cc147
-rw-r--r--src/wx/wx_variant.h54
108 files changed, 4539 insertions, 2214 deletions
diff --git a/src/wx/about_dialog.cc b/src/wx/about_dialog.cc
index 2c742cce3..220092da6 100644
--- a/src/wx/about_dialog.cc
+++ b/src/wx/about_dialog.cc
@@ -26,7 +26,9 @@
#include "about_dialog.h"
#include "static_text.h"
#include "wx_util.h"
+#include "wx_variant.h"
#include "lib/compose.hpp"
+#include "lib/variant.h"
#include "lib/version.h"
#include <dcp/warnings.h>
LIBDCP_DISABLE_WARNINGS
@@ -38,7 +40,7 @@ using std::vector;
AboutDialog::AboutDialog (wxWindow* parent)
- : wxDialog (parent, wxID_ANY, _("About DCP-o-matic"))
+ : wxDialog(parent, wxID_ANY, variant::wx::insert_dcpomatic(_("About %s")))
{
auto overall_sizer = new wxBoxSizer (wxVERTICAL);
auto sizer = new wxBoxSizer (wxVERTICAL);
@@ -53,7 +55,7 @@ AboutDialog::AboutDialog (wxWindow* parent)
wxFont version_font (*wxNORMAL_FONT);
version_font.SetWeight (wxFONTWEIGHT_BOLD);
- auto t = new StaticText (this, _("DCP-o-matic"));
+ auto t = new StaticText(this, variant::wx::dcpomatic());
t->SetFont (title_font);
sizer->Add (t, wxSizerFlags().Centre().Border(wxALL, 16));
@@ -67,26 +69,30 @@ AboutDialog::AboutDialog (wxWindow* parent)
sizer->Add (t, wxSizerFlags().Centre().Border(wxALL, 2));
sizer->AddSpacer (12);
- t = new StaticText (
- this,
- _("Free, open-source DCP creation from almost anything."),
- wxDefaultPosition, wxDefaultSize, wxALIGN_CENTER
- );
- t->SetFont (subtitle_font);
-
- sizer->Add (t, wxSizerFlags().Centre().Border(wxALL, 8));
-
- auto h = new wxHyperlinkCtrl (
- this, wxID_ANY,
- wxT ("dcpomatic.com"),
- wxT ("https://dcpomatic.com")
- );
+ if (variant::show_tagline())
+ {
+ t = new StaticText(
+ this,
+ _("Free, open-source DCP creation from almost anything."),
+ wxDefaultPosition, wxDefaultSize, wxALIGN_CENTER
+ );
+ t->SetFont(subtitle_font);
+ sizer->Add(t, wxSizerFlags().Centre().Border(wxALL, 8));
+ }
- sizer->Add (h, wxSizerFlags().Centre().Border(wxALL, 8));
+ if (variant::show_dcpomatic_website())
+ {
+ auto h = new wxHyperlinkCtrl(
+ this, wxID_ANY,
+ wxT("dcpomatic.com"),
+ wxT("https://dcpomatic.com")
+ );
+ sizer->Add(h, wxSizerFlags().Centre().Border(wxALL, 8));
+ }
t = new StaticText (
this,
- _("(C) 2012-2024 Carl Hetherington, Terrence Meiczinger\n Ole Laursen"),
+ _("(C) 2012-2024 Carl Hetherington, Terrence Meiczinger\nOle Laursen, Aaron Boxer"),
wxDefaultPosition, wxDefaultSize, wxALIGN_CENTER
);
@@ -99,6 +105,7 @@ AboutDialog::AboutDialog (wxWindow* parent)
written_by.Add (wxT ("Terrence Meiczinger"));
written_by.Add (wxT ("Mart Jansink"));
written_by.Add (wxT ("Ole Laursen"));
+ written_by.Add (wxT ("Aaron Boxer"));
add_section (_("Written by"), written_by);
wxArrayString with_help_from;
diff --git a/src/wx/audio_dialog.cc b/src/wx/audio_dialog.cc
index 714bf78e4..1f7f3ad03 100644
--- a/src/wx/audio_dialog.cc
+++ b/src/wx/audio_dialog.cc
@@ -24,6 +24,7 @@
#include "check_box.h"
#include "static_text.h"
#include "wx_util.h"
+#include "wx_variant.h"
#include "lib/analyse_audio_job.h"
#include "lib/audio_analysis.h"
#include "lib/audio_content.h"
@@ -166,9 +167,9 @@ AudioDialog::AudioDialog (wxWindow* parent, shared_ptr<Film> film, FilmViewer& v
_film_content_connection = film->ContentChange.connect(boost::bind(&AudioDialog::content_change, this, _1, _3));
DCPOMATIC_ASSERT (film->directory());
if (content) {
- SetTitle(wxString::Format(_("DCP-o-matic audio - %s"), std_to_wx(content->path(0).string())));
+ SetTitle(wxString::Format(_("%s audio - %s"), variant::wx::dcpomatic(), std_to_wx(content->path(0).string())));
} else {
- SetTitle(wxString::Format(_("DCP-o-matic audio - %s"), std_to_wx(film->directory().get().string())));
+ SetTitle(wxString::Format(_("%s audio - %s"), variant::wx::dcpomatic(), std_to_wx(film->directory().get().string())));
}
if (content) {
diff --git a/src/wx/audio_panel.cc b/src/wx/audio_panel.cc
index fcadd0c68..34923fb02 100644
--- a/src/wx/audio_panel.cc
+++ b/src/wx/audio_panel.cc
@@ -71,14 +71,6 @@ AudioPanel::AudioPanel (ContentPanel* p)
void
AudioPanel::create ()
{
- _reference = new CheckBox (this, _("Use this DCP's audio as OV and make VF"));
- _reference_note = new StaticText (this, wxT(""));
- _reference_note->Wrap (200);
- auto font = _reference_note->GetFont();
- font.SetStyle(wxFONTSTYLE_ITALIC);
- font.SetPointSize(font.GetPointSize() - 1);
- _reference_note->SetFont(font);
-
_show = new Button (this, _("Show graph of audio levels..."));
_peak = new StaticText (this, wxT (""));
@@ -121,6 +113,9 @@ AudioPanel::create ()
_description = new StaticText (this, wxT(" \n"), wxDefaultPosition, wxDefaultSize);
_sizer->Add (_description, 0, wxALL, 12);
+ auto font = _description->GetFont();
+ font.SetStyle(wxFONTSTYLE_ITALIC);
+ font.SetPointSize(font.GetPointSize() - 1);
_description->SetFont (font);
_gain->wrapped()->SetRange (-60, 60);
@@ -133,7 +128,6 @@ AudioPanel::create ()
film_changed(FilmProperty::VIDEO_FRAME_RATE);
film_changed(FilmProperty::REEL_TYPE);
- _reference->bind(&AudioPanel::reference_clicked, this);
_show->Bind (wxEVT_BUTTON, boost::bind (&AudioPanel::show_clicked, this));
_gain_calculate_button->Bind (wxEVT_BUTTON, boost::bind (&AudioPanel::gain_calculate_button_clicked, this));
@@ -155,12 +149,6 @@ AudioPanel::add_to_grid ()
{
int r = 0;
- auto reference_sizer = new wxBoxSizer (wxVERTICAL);
- reference_sizer->Add (_reference, 0);
- reference_sizer->Add (_reference_note, 0);
- _grid->Add (reference_sizer, wxGBPosition(r, 0), wxGBSpan(1, 4));
- ++r;
-
_grid->Add (_show, wxGBPosition (r, 0), wxGBSpan (1, 2));
_grid->Add (_peak, wxGBPosition (r, 2), wxGBSpan (1, 2), wxALIGN_CENTER_VERTICAL);
++r;
@@ -258,15 +246,6 @@ AudioPanel::film_content_changed (int property)
/* This is a bit aggressive but probably not so bad */
_peak_cache.clear();
setup_peak ();
- } else if (property == DCPContentProperty::REFERENCE_AUDIO) {
- if (ac.size() == 1) {
- shared_ptr<DCPContent> dcp = dynamic_pointer_cast<DCPContent> (ac.front ());
- checked_set (_reference, dcp ? dcp->reference_audio () : false);
- } else {
- checked_set (_reference, false);
- }
-
- setup_sensitivity ();
} else if (property == ContentProperty::VIDEO_FRAME_RATE) {
setup_description ();
} else if (property == AudioContentProperty::FADE_IN) {
@@ -384,19 +363,8 @@ AudioPanel::setup_sensitivity ()
dcp = dynamic_pointer_cast<DCPContent> (sel.front ());
}
- string why_not;
- bool const can_reference = dcp && dcp->can_reference_audio (_parent->film(), why_not);
- wxString cannot;
- if (why_not.empty()) {
- cannot = _("Cannot reference this DCP's audio.");
- } else {
- cannot = _("Cannot reference this DCP's audio: ") + std_to_wx(why_not);
- }
- setup_refer_button (_reference, _reference_note, dcp, can_reference, cannot);
-
- auto const ref = _reference->GetValue();
+ auto const ref = dcp && dcp->reference_audio();
auto const single = sel.size() == 1;
-
auto const all_have_video = std::all_of(sel.begin(), sel.end(), [](shared_ptr<const Content> c) { return static_cast<bool>(c->video); });
_gain->wrapped()->Enable (!ref);
@@ -501,23 +469,6 @@ AudioPanel::active_jobs_changed (optional<string> old_active, optional<string> n
void
-AudioPanel::reference_clicked ()
-{
- auto c = _parent->selected ();
- if (c.size() != 1) {
- return;
- }
-
- auto d = dynamic_pointer_cast<DCPContent>(c.front());
- if (!d) {
- return;
- }
-
- d->set_reference_audio (_reference->GetValue ());
-}
-
-
-void
AudioPanel::set_film (shared_ptr<Film>)
{
/* We are changing film, so destroy any audio dialog for the old one */
diff --git a/src/wx/audio_panel.h b/src/wx/audio_panel.h
index 61dd2783a..d25f7e247 100644
--- a/src/wx/audio_panel.h
+++ b/src/wx/audio_panel.h
@@ -54,15 +54,12 @@ private:
void setup_peak ();
void active_jobs_changed (boost::optional<std::string>, boost::optional<std::string>);
void setup_sensitivity ();
- void reference_clicked ();
void add_to_grid () override;
boost::optional<float> peak () const;
void fade_in_changed ();
void fade_out_changed ();
void use_same_fades_as_video_changed ();
- CheckBox* _reference;
- wxStaticText* _reference_note;
wxButton* _show;
wxStaticText* _gain_label;
wxStaticText* _gain_db_label;
diff --git a/src/wx/check_box.cc b/src/wx/check_box.cc
index baec651c1..c18ceba47 100644
--- a/src/wx/check_box.cc
+++ b/src/wx/check_box.cc
@@ -53,3 +53,10 @@ CheckBox::get() const
return GetValue();
}
+
+void
+CheckBox::set(bool state)
+{
+ SetValue(state);
+}
+
diff --git a/src/wx/check_box.h b/src/wx/check_box.h
index 4b3fddb9f..d9feb00c9 100644
--- a/src/wx/check_box.h
+++ b/src/wx/check_box.h
@@ -39,6 +39,7 @@ public:
void set_text (wxString text) override;
wxString get_text () const override;
bool get() const;
+ void set(bool state);
template <typename... Args>
void bind(Args... args) {
diff --git a/src/wx/cinema_dialog.cc b/src/wx/cinema_dialog.cc
index 84fde5f41..3b8265e6f 100644
--- a/src/wx/cinema_dialog.cc
+++ b/src/wx/cinema_dialog.cc
@@ -20,6 +20,7 @@
#include "cinema_dialog.h"
+#include "dcpomatic_choice.h"
#include "wx_util.h"
#include "lib/dcpomatic_assert.h"
#include "lib/util.h"
@@ -36,7 +37,7 @@ using namespace boost::placeholders;
#endif
-CinemaDialog::CinemaDialog(wxWindow* parent, wxString title, string name, vector<string> emails, string notes, int utc_offset_hour, int utc_offset_minute)
+CinemaDialog::CinemaDialog(wxWindow* parent, wxString title, string name, vector<string> emails, string notes, dcp::UTCOffset utc_offset)
: wxDialog (parent, wxID_ANY, title)
{
auto overall_sizer = new wxBoxSizer (wxVERTICAL);
@@ -50,9 +51,9 @@ CinemaDialog::CinemaDialog(wxWindow* parent, wxString title, string name, vector
sizer->Add (_name, wxGBPosition(r, 1));
++r;
- add_label_to_sizer (sizer, this, _("UTC offset (time zone)"), true, wxGBPosition(r, 0));
- _utc_offset = new wxChoice (this, wxID_ANY);
- sizer->Add (_utc_offset, wxGBPosition(r, 1));
+ add_label_to_sizer(sizer, this, _("UTC offset (time zone)"), true, wxGBPosition(r, 0));
+ _utc_offset = new Choice(this);
+ sizer->Add(_utc_offset, wxGBPosition(r, 1));
++r;
add_label_to_sizer (sizer, this, _("Notes"), true, wxGBPosition(r, 0));
@@ -84,15 +85,15 @@ CinemaDialog::CinemaDialog(wxWindow* parent, wxString title, string name, vector
}
/* Default to UTC */
- size_t sel = get_offsets (_offsets);
+ size_t sel = get_offsets(_offsets);
for (size_t i = 0; i < _offsets.size(); ++i) {
- _utc_offset->Append (_offsets[i].name);
- if (_offsets[i].hour == utc_offset_hour && _offsets[i].minute == utc_offset_minute) {
+ _utc_offset->add_entry(_offsets[i].name);
+ if (_offsets[i].offset == utc_offset) {
sel = i;
}
}
- _utc_offset->SetSelection (sel);
+ _utc_offset->set(sel);
overall_sizer->Layout ();
overall_sizer->SetSizeHints (this);
@@ -122,32 +123,20 @@ CinemaDialog::emails() const
}
-int
-CinemaDialog::utc_offset_hour () const
+string
+CinemaDialog::notes () const
{
- int const sel = _utc_offset->GetSelection();
- if (sel < 0 || sel > int(_offsets.size())) {
- return 0;
- }
-
- return _offsets[sel].hour;
+ return wx_to_std (_notes->GetValue());
}
-int
-CinemaDialog::utc_offset_minute () const
+dcp::UTCOffset
+CinemaDialog::utc_offset() const
{
- int const sel = _utc_offset->GetSelection();
+ auto const sel = _utc_offset->GetSelection();
if (sel < 0 || sel > int(_offsets.size())) {
- return 0;
+ return {};
}
- return _offsets[sel].minute;
-}
-
-
-string
-CinemaDialog::notes () const
-{
- return wx_to_std (_notes->GetValue());
+ return _offsets[sel].offset;
}
diff --git a/src/wx/cinema_dialog.h b/src/wx/cinema_dialog.h
index 0b8362843..873657772 100644
--- a/src/wx/cinema_dialog.h
+++ b/src/wx/cinema_dialog.h
@@ -30,6 +30,9 @@ LIBDCP_ENABLE_WARNINGS
#include <vector>
+class Choice;
+
+
class CinemaDialog : public wxDialog
{
public:
@@ -39,15 +42,13 @@ public:
std::string name = "",
std::vector<std::string> emails = std::vector<std::string>(),
std::string notes = "",
- int utc_offset_hour = 0,
- int utc_offset_minute = 0
+ dcp::UTCOffset = {}
);
std::string name () const;
std::string notes () const;
std::vector<std::string> emails () const;
- int utc_offset_hour () const;
- int utc_offset_minute () const;
+ dcp::UTCOffset utc_offset() const;
private:
void set_emails (std::vector<std::string>);
@@ -56,6 +57,6 @@ private:
wxTextCtrl* _notes;
EditableList<std::string, EmailDialog>* _email_list;
std::vector<std::string> _emails;
- wxChoice* _utc_offset;
+ Choice* _utc_offset;
std::vector<Offset> _offsets;
};
diff --git a/src/wx/colours.h b/src/wx/colours.h
new file mode 100644
index 000000000..0d33208e0
--- /dev/null
+++ b/src/wx/colours.h
@@ -0,0 +1,25 @@
+/*
+ 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))
diff --git a/src/wx/config_dialog.cc b/src/wx/config_dialog.cc
index b4adc855e..05c3f281c 100644
--- a/src/wx/config_dialog.cc
+++ b/src/wx/config_dialog.cc
@@ -25,7 +25,9 @@
#include "dcpomatic_button.h"
#include "nag_dialog.h"
#include "static_text.h"
+#include "wx_variant.h"
#include "lib/constants.h"
+#include "lib/util.h"
#include <dcp/file.h>
#include <dcp/filesystem.h>
#include <dcp/raw_convert.h>
@@ -148,7 +150,7 @@ GeneralPage::add_language_controls (wxGridBagSizer* table, int& r)
++r;
auto restart = add_label_to_sizer (
- table, _panel, _("(restart DCP-o-matic to see language changes)"), false, wxGBPosition (r, 0), wxGBSpan (1, 2)
+ table, _panel, variant::wx::insert_dcpomatic(_("(restart %s to see language changes)")), false, wxGBPosition (r, 0), wxGBSpan (1, 2)
);
wxFont font = restart->GetFont();
font.SetStyle (wxFONTSTYLE_ITALIC);
@@ -792,7 +794,7 @@ KeysPage::import_decryption_chain_and_key ()
if (new_chain->chain_valid() && new_chain->private_key_valid()) {
Config::instance()->set_decryption_chain(new_chain);
} else {
- error_dialog(_panel, _("Invalid DCP-o-matic export file"));
+ error_dialog(_panel, variant::wx::insert_dcpomatic(_("Invalid %s export file")));
}
}
diff --git a/src/wx/content_menu.cc b/src/wx/content_menu.cc
index 642457d93..92a68e3b3 100644
--- a/src/wx/content_menu.cc
+++ b/src/wx/content_menu.cc
@@ -23,13 +23,13 @@
#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"
@@ -314,12 +314,12 @@ ContentMenu::remove ()
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) {
diff --git a/src/wx/content_panel.cc b/src/wx/content_panel.cc
index 9e73900fc..07a632b4a 100644
--- a/src/wx/content_panel.cc
+++ b/src/wx/content_panel.cc
@@ -21,16 +21,17 @@
#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"
+#include "wx_variant.h"
#include "lib/audio_content.h"
#include "lib/case_insensitive_sorter.h"
#include "lib/compose.hpp"
@@ -673,10 +674,14 @@ ContentPanel::add_dcp(boost::filesystem::path dcp)
} catch (ProjectFolderError &) {
error_dialog (
_parent,
- _(
- "This looks like a DCP-o-matic project folder, which cannot be added to a different project. "
- "Choose the DCP folder inside the DCP-o-matic project folder if that's what you want to import."
- )
+ wxString::Format(
+ _(
+ "This looks like a %s project folder, which cannot be added to a different project. "
+ "Choose the DCP folder inside the %s project folder if that's what you want to import."
+ ),
+ variant::wx::dcpomatic(),
+ variant::wx::dcpomatic()
+ )
);
} catch (exception& e) {
error_dialog(_parent, e.what());
diff --git a/src/wx/content_panel.h b/src/wx/content_panel.h
index ca0d49719..38b634a62 100644
--- a/src/wx/content_panel.h
+++ b/src/wx/content_panel.h
@@ -33,12 +33,12 @@ LIBDCP_ENABLE_WARNINGS
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;
@@ -132,7 +132,7 @@ private:
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;
diff --git a/src/wx/content_sub_panel.cc b/src/wx/content_sub_panel.cc
index 6bbc9a51f..10fdfa232 100644
--- a/src/wx/content_sub_panel.cc
+++ b/src/wx/content_sub_panel.cc
@@ -53,29 +53,6 @@ ContentSubPanel::ContentSubPanel (ContentPanel* p, wxString name)
}
void
-ContentSubPanel::setup_refer_button (wxCheckBox* button, wxStaticText* note, shared_ptr<DCPContent> dcp, bool can_reference, wxString cannot)
-{
- button->Enable (can_reference);
-
- if (dcp && !can_reference) {
- note->SetLabel (cannot);
- } else {
- note->SetLabel (wxT(""));
- }
-
- note->Wrap (400);
-
- if (cannot.IsEmpty()) {
- note->Hide ();
- } else {
- note->Show ();
- }
-
- layout ();
-}
-
-
-void
ContentSubPanel::layout ()
{
int x;
diff --git a/src/wx/content_sub_panel.h b/src/wx/content_sub_panel.h
index a3916817e..48970e6a9 100644
--- a/src/wx/content_sub_panel.h
+++ b/src/wx/content_sub_panel.h
@@ -55,7 +55,6 @@ public:
protected:
- void setup_refer_button (wxCheckBox* button, wxStaticText* note, std::shared_ptr<DCPContent> dcp, bool can_reference, wxString cannot);
void layout ();
virtual void add_to_grid () = 0;
diff --git a/src/wx/content_timeline.cc b/src/wx/content_timeline.cc
new file mode 100644
index 000000000..663f93030
--- /dev/null
+++ b/src/wx/content_timeline.cc
@@ -0,0 +1,1021 @@
+/*
+ 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;
+ }
+ }
+}
+
diff --git a/src/wx/content_timeline.h b/src/wx/content_timeline.h
new file mode 100644
index 000000000..10f880191
--- /dev/null
+++ b/src/wx/content_timeline.h
@@ -0,0 +1,149 @@
+/*
+ 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;
+};
diff --git a/src/wx/timeline_atmos_content_view.cc b/src/wx/content_timeline_atmos_view.cc
index ec0f06ec1..2352ba5dc 100644
--- a/src/wx/timeline_atmos_content_view.cc
+++ b/src/wx/content_timeline_atmos_view.cc
@@ -19,17 +19,18 @@
*/
-#include "timeline_atmos_content_view.h"
+#include "colours.h"
+#include "content_timeline_atmos_view.h"
using std::shared_ptr;
-/** @class TimelineAtmosContentView
- * @brief Timeline view for AtmosContent.
+/** @class ContentTimelineContentView
+ * @brief Content timeline view for AtmosContent.
*/
-TimelineAtmosContentView::TimelineAtmosContentView (Timeline& tl, shared_ptr<Content> c)
+ContentTimelineAtmosView::ContentTimelineAtmosView(ContentTimeline& tl, shared_ptr<Content> c)
: TimelineContentView (tl, c)
{
@@ -37,14 +38,14 @@ TimelineAtmosContentView::TimelineAtmosContentView (Timeline& tl, shared_ptr<Con
wxColour
-TimelineAtmosContentView::background_colour () const
+ContentTimelineAtmosView::background_colour() const
{
- return wxColour (149, 121, 232, 255);
+ return ATMOS_CONTENT_COLOUR;
}
wxColour
-TimelineAtmosContentView::foreground_colour () const
+ContentTimelineAtmosView::foreground_colour() const
{
return wxColour (0, 0, 0, 255);
}
diff --git a/src/wx/timeline_atmos_content_view.h b/src/wx/content_timeline_atmos_view.h
index 15da14edc..d5fea9248 100644
--- a/src/wx/timeline_atmos_content_view.h
+++ b/src/wx/content_timeline_atmos_view.h
@@ -22,13 +22,13 @@
#include "timeline_content_view.h"
-/** @class TimelineAtmosContentView
- * @brief Timeline view for AtmosContent.
+/** @class ContentTimelineAtmosContentView
+ * @brief Content timeline view for AtmosContent.
*/
-class TimelineAtmosContentView : public TimelineContentView
+class ContentTimelineAtmosView : public TimelineContentView
{
public:
- TimelineAtmosContentView (Timeline& tl, std::shared_ptr<Content> c);
+ ContentTimelineAtmosView(ContentTimeline& tl, std::shared_ptr<Content> c);
private:
bool active () const override {
diff --git a/src/wx/timeline_audio_content_view.cc b/src/wx/content_timeline_audio_view.cc
index 057ebb944..600e39057 100644
--- a/src/wx/timeline_audio_content_view.cc
+++ b/src/wx/content_timeline_audio_view.cc
@@ -18,39 +18,44 @@
*/
-#include "timeline_audio_content_view.h"
+
+#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;
-using std::dynamic_pointer_cast;
-/** @class TimelineAudioContentView
- * @brief Timeline view for AudioContent.
+
+/** @class ContentTimelineAudioView
+ * @brief Content timeline view for AudioContent.
*/
-TimelineAudioContentView::TimelineAudioContentView (Timeline& tl, shared_ptr<Content> c)
+
+ContentTimelineAudioView::ContentTimelineAudioView(ContentTimeline& tl, shared_ptr<Content> c)
: TimelineContentView (tl, c)
{
}
wxColour
-TimelineAudioContentView::background_colour () const
+ContentTimelineAudioView::background_colour () const
{
- return wxColour (149, 121, 232, 255);
+ return AUDIO_CONTENT_COLOUR;
}
wxColour
-TimelineAudioContentView::foreground_colour () const
+ContentTimelineAudioView::foreground_colour () const
{
return wxColour (0, 0, 0, 255);
}
wxString
-TimelineAudioContentView::label () const
+ContentTimelineAudioView::label () const
{
wxString s = TimelineContentView::label ();
shared_ptr<AudioContent> ac = content()->audio;
diff --git a/src/wx/timeline_audio_content_view.h b/src/wx/content_timeline_audio_view.h
index 5b8d6cdc2..719c2536d 100644
--- a/src/wx/timeline_audio_content_view.h
+++ b/src/wx/content_timeline_audio_view.h
@@ -22,13 +22,13 @@
#include "timeline_content_view.h"
-/** @class TimelineAudioContentView
- * @brief Timeline view for AudioContent.
+/** @class ContentTimelineAudioView
+ * @brief Content timeline view for AudioContent.
*/
-class TimelineAudioContentView : public TimelineContentView
+class ContentTimelineAudioView : public TimelineContentView
{
public:
- TimelineAudioContentView (Timeline& tl, std::shared_ptr<Content> c);
+ ContentTimelineAudioView(ContentTimeline& tl, std::shared_ptr<Content> c);
private:
bool active () const override {
diff --git a/src/wx/timeline_dialog.cc b/src/wx/content_timeline_dialog.cc
index e0e1689a8..697e6561f 100644
--- a/src/wx/timeline_dialog.cc
+++ b/src/wx/content_timeline_dialog.cc
@@ -20,8 +20,8 @@
#include "content_panel.h"
+#include "content_timeline_dialog.h"
#include "film_editor.h"
-#include "timeline_dialog.h"
#include "wx_util.h"
#include "lib/compose.hpp"
#include "lib/cross.h"
@@ -43,7 +43,7 @@ using namespace boost::placeholders;
#endif
-TimelineDialog::TimelineDialog(ContentPanel* cp, shared_ptr<Film> film, FilmViewer& viewer)
+ContentTimelineDialog::ContentTimelineDialog(ContentPanel* cp, shared_ptr<Film> film, FilmViewer& viewer)
: wxDialog (
cp->window(),
wxID_ANY,
@@ -73,14 +73,14 @@ TimelineDialog::TimelineDialog(ContentPanel* cp, shared_ptr<Film> film, FilmView
_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->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 (&TimelineDialog::tool_clicked, this, _1));
+ _toolbar->Bind(wxEVT_TOOL, bind(&ContentTimelineDialog::tool_clicked, this, _1));
sizer->Add (_toolbar, 0, wxALL, 12);
sizer->Add (&_timeline, 1, wxEXPAND | wxALL, 12);
@@ -96,17 +96,17 @@ TimelineDialog::TimelineDialog(ContentPanel* cp, shared_ptr<Film> film, FilmView
sizer->Layout ();
sizer->SetSizeHints (this);
- Bind(wxEVT_CHAR_HOOK, boost::bind(&TimelineDialog::keypress, this, _1));
+ Bind(wxEVT_CHAR_HOOK, boost::bind(&ContentTimelineDialog::keypress, this, _1));
- _toolbar->ToggleTool ((int) Timeline::SNAP, _timeline.snap ());
+ _toolbar->ToggleTool(static_cast<int>(ContentTimeline::SNAP), _timeline.snap ());
film_change(ChangeType::DONE, FilmProperty::SEQUENCE);
- _film_changed_connection = film->Change.connect (bind (&TimelineDialog::film_change, this, _1, _2));
+ _film_changed_connection = film->Change.connect(bind(&ContentTimelineDialog::film_change, this, _1, _2));
}
void
-TimelineDialog::film_change(ChangeType type, FilmProperty p)
+ContentTimelineDialog::film_change(ChangeType type, FilmProperty p)
{
if (type != ChangeType::DONE) {
return;
@@ -118,26 +118,26 @@ TimelineDialog::film_change(ChangeType type, FilmProperty p)
}
if (p == FilmProperty::SEQUENCE) {
- _toolbar->ToggleTool ((int) Timeline::SEQUENCE, film->sequence ());
+ _toolbar->ToggleTool(static_cast<int>(ContentTimeline::SEQUENCE), film->sequence());
}
}
void
-TimelineDialog::set_selection (ContentList selection)
+ContentTimelineDialog::set_selection(ContentList selection)
{
_timeline.set_selection (selection);
}
void
-TimelineDialog::tool_clicked (wxCommandEvent& ev)
+ContentTimelineDialog::tool_clicked(wxCommandEvent& ev)
{
- Timeline::Tool t = static_cast<Timeline::Tool>(ev.GetId());
+ auto t = static_cast<ContentTimeline::Tool>(ev.GetId());
_timeline.tool_clicked (t);
- if (t == Timeline::SNAP) {
+ if (t == ContentTimeline::SNAP) {
_timeline.set_snap (_toolbar->GetToolState(static_cast<int>(t)));
- } else if (t == Timeline::SEQUENCE) {
+ } else if (t == ContentTimeline::SEQUENCE) {
auto film = _film.lock ();
if (film) {
film->set_sequence (_toolbar->GetToolState(static_cast<int>(t)));
@@ -147,7 +147,7 @@ TimelineDialog::tool_clicked (wxCommandEvent& ev)
void
-TimelineDialog::keypress(wxKeyEvent const& event)
+ContentTimelineDialog::keypress(wxKeyEvent const& event)
{
_timeline.keypress(event);
}
diff --git a/src/wx/timeline_dialog.h b/src/wx/content_timeline_dialog.h
index 8134aa6db..2babf0437 100644
--- a/src/wx/timeline_dialog.h
+++ b/src/wx/content_timeline_dialog.h
@@ -19,7 +19,7 @@
*/
-#include "timeline.h"
+#include "content_timeline.h"
#include <dcp/warnings.h>
LIBDCP_DISABLE_WARNINGS
#include <wx/wx.h>
@@ -29,10 +29,10 @@ LIBDCP_ENABLE_WARNINGS
class Playlist;
-class TimelineDialog : public wxDialog
+class ContentTimelineDialog : public wxDialog
{
public:
- TimelineDialog(ContentPanel *, std::shared_ptr<Film>, FilmViewer& viewer);
+ ContentTimelineDialog(ContentPanel *, std::shared_ptr<Film>, FilmViewer& viewer);
void set_selection (ContentList selection);
@@ -42,7 +42,7 @@ private:
void keypress(wxKeyEvent const& event);
std::weak_ptr<Film> _film;
- Timeline _timeline;
+ ContentTimeline _timeline;
wxToolBar* _toolbar;
boost::signals2::scoped_connection _film_changed_connection;
};
diff --git a/src/wx/timeline_text_content_view.cc b/src/wx/content_timeline_text_view.cc
index a57398599..f22f47850 100644
--- a/src/wx/timeline_text_content_view.cc
+++ b/src/wx/content_timeline_text_view.cc
@@ -19,7 +19,8 @@
*/
-#include "timeline_text_content_view.h"
+#include "colours.h"
+#include "content_timeline_text_view.h"
#include "lib/text_content.h"
#include "lib/content.h"
@@ -27,7 +28,7 @@
using std::shared_ptr;
-TimelineTextContentView::TimelineTextContentView (Timeline& tl, shared_ptr<Content> c, shared_ptr<TextContent> caption)
+ContentTimelineTextView::ContentTimelineTextView(ContentTimeline& tl, shared_ptr<Content> c, shared_ptr<TextContent> caption)
: TimelineContentView (tl, c)
, _caption (caption)
{
@@ -35,17 +36,17 @@ TimelineTextContentView::TimelineTextContentView (Timeline& tl, shared_ptr<Conte
}
wxColour
-TimelineTextContentView::background_colour () const
+ContentTimelineTextView::background_colour() const
{
if (!active ()) {
return wxColour (210, 210, 210, 128);
}
- return wxColour (163, 255, 154, 255);
+ return TEXT_CONTENT_COLOUR;
}
wxColour
-TimelineTextContentView::foreground_colour () const
+ContentTimelineTextView::foreground_colour() const
{
if (!active ()) {
return wxColour (180, 180, 180, 128);
@@ -55,7 +56,7 @@ TimelineTextContentView::foreground_colour () const
}
bool
-TimelineTextContentView::active () const
+ContentTimelineTextView::active() const
{
return _caption->use();
}
diff --git a/src/wx/timeline_text_content_view.h b/src/wx/content_timeline_text_view.h
index 046f5b3e6..8d37ad489 100644
--- a/src/wx/timeline_text_content_view.h
+++ b/src/wx/content_timeline_text_view.h
@@ -18,18 +18,20 @@
*/
+
#include "timeline_content_view.h"
+
class TextContent;
-class TextContent;
-/** @class TimelineTextContentView
- * @brief Timeline view for TextContent.
+
+/** @class ContentTimelineTextView
+ * @brief Content timeline view for TextContent.
*/
-class TimelineTextContentView : public TimelineContentView
+class ContentTimelineTextView : public TimelineContentView
{
public:
- TimelineTextContentView (Timeline& tl, std::shared_ptr<Content>, std::shared_ptr<TextContent>);
+ ContentTimelineTextView(ContentTimeline& tl, std::shared_ptr<Content>, std::shared_ptr<TextContent>);
private:
bool active () const override;
diff --git a/src/wx/timeline_video_content_view.cc b/src/wx/content_timeline_video_view.cc
index b0f4b4f5d..d907627cd 100644
--- a/src/wx/timeline_video_content_view.cc
+++ b/src/wx/content_timeline_video_view.cc
@@ -18,31 +18,35 @@
*/
+
+#include "colours.h"
+#include "content_timeline_video_view.h"
#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)
+
+ContentTimelineVideoView::ContentTimelineVideoView(ContentTimeline& tl, shared_ptr<Content> c)
: TimelineContentView (tl, c)
{
}
wxColour
-TimelineVideoContentView::background_colour () const
+ContentTimelineVideoView::background_colour() const
{
if (!active()) {
return wxColour (210, 210, 210, 128);
}
- return wxColour (242, 92, 120, 255);
+ return VIDEO_CONTENT_COLOUR;
}
wxColour
-TimelineVideoContentView::foreground_colour () const
+ContentTimelineVideoView::foreground_colour() const
{
if (!active()) {
return wxColour (180, 180, 180, 128);
@@ -52,9 +56,9 @@ TimelineVideoContentView::foreground_colour () const
}
bool
-TimelineVideoContentView::active () const
+ContentTimelineVideoView::active() const
{
- shared_ptr<Content> c = _content.lock ();
+ auto c = _content.lock();
DCPOMATIC_ASSERT (c);
return c->video && c->video->use();
}
diff --git a/src/wx/timeline_video_content_view.h b/src/wx/content_timeline_video_view.h
index fa8ddf54c..940a1a305 100644
--- a/src/wx/timeline_video_content_view.h
+++ b/src/wx/content_timeline_video_view.h
@@ -22,13 +22,13 @@
#include "timeline_content_view.h"
-/** @class TimelineVideoContentView
- * @brief Timeline view for VideoContent.
+/** @class ContentTimelineVideoView
+ * @brief Content timeline view for VideoContent.
*/
-class TimelineVideoContentView : public TimelineContentView
+class ContentTimelineVideoView : public TimelineContentView
{
public:
- TimelineVideoContentView (Timeline& tl, std::shared_ptr<Content> c);
+ ContentTimelineVideoView(ContentTimeline& tl, std::shared_ptr<Content> c);
private:
bool active () const override;
diff --git a/src/wx/content_timeline_view.cc b/src/wx/content_timeline_view.cc
new file mode 100644
index 000000000..127e440fa
--- /dev/null
+++ b/src/wx/content_timeline_view.cc
@@ -0,0 +1,42 @@
+/*
+ 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();
+}
+
+
diff --git a/src/wx/content_timeline_view.h b/src/wx/content_timeline_view.h
new file mode 100644
index 000000000..450d19df4
--- /dev/null
+++ b/src/wx/content_timeline_view.h
@@ -0,0 +1,60 @@
+/*
+ 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
diff --git a/src/wx/content_view.cc b/src/wx/content_view.cc
index b9fe7ce00..b2d6f86d1 100644
--- a/src/wx/content_view.cc
+++ b/src/wx/content_view.cc
@@ -21,6 +21,7 @@
#include "content_view.h"
#include "wx_util.h"
+#include "wx_variant.h"
#include "lib/config.h"
#include "lib/content_factory.h"
#include "lib/cross.h"
@@ -86,7 +87,7 @@ ContentView::update ()
dir = home_directory ();
}
- wxProgressDialog progress (_("DCP-o-matic"), _("Reading content directory"));
+ wxProgressDialog progress(variant::wx::dcpomatic(), _("Reading content directory"));
auto jm = JobManager::instance ();
list<shared_ptr<ExamineContentJob>> jobs;
diff --git a/src/wx/dcp_panel.cc b/src/wx/dcp_panel.cc
index f4ba74cde..bc8ac859c 100644
--- a/src/wx/dcp_panel.cc
+++ b/src/wx/dcp_panel.cc
@@ -23,6 +23,7 @@
#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"
@@ -97,10 +98,6 @@ DCPPanel::DCPPanel(wxNotebook* n, shared_ptr<Film> film, FilmViewer& viewer)
wxALIGN_CENTRE_HORIZONTAL | wxST_NO_AUTORESIZE | wxST_ELLIPSIZE_MIDDLE
);
- _enable_audio_language = new CheckBox(_panel, _("Audio language"));
- _audio_language = new wxStaticText (_panel, wxID_ANY, wxT(""));
- _edit_audio_language = new Button (_panel, _("Edit..."));
-
_dcp_content_type_label = create_label (_panel, _("Content Type"), true);
_dcp_content_type = new Choice(_panel);
@@ -110,18 +107,12 @@ DCPPanel::DCPPanel(wxNotebook* n, shared_ptr<Film> film, FilmViewer& viewer)
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);
@@ -134,28 +125,17 @@ DCPPanel::DCPPanel(wxNotebook* n, shared_ptr<Film> film, FilmViewer& viewer)
_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));
- _enable_audio_language->bind(&DCPPanel::enable_audio_language_toggled, this);
- _edit_audio_language->Bind (wxEVT_BUTTON, boost::bind(&DCPPanel::edit_audio_language_clicked, this));
+ _reels->Bind(wxEVT_BUTTON, boost::bind(&DCPPanel::reels_clicked, this));
for (auto i: DCPContentType::all()) {
- _dcp_content_type->add(i->pretty_name());
+ _dcp_content_type->add_entry(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(_("Which standard the DCP should use. Interop is older and SMPTE is the modern standard. If in doubt, choose 'SMPTE'"));
+ _standard->SetToolTip(_("The standard that the DCP should use. Interop is older, and SMPTE is the newer (current) standard. If in doubt, choose 'SMPTE'"));
Config::instance()->Changed.connect (boost::bind(&DCPPanel::config_changed, this, _1));
@@ -166,11 +146,12 @@ DCPPanel::DCPPanel(wxNotebook* n, shared_ptr<Film> film, FilmViewer& viewer)
void
DCPPanel::add_standards()
{
- _standard->add(_("SMPTE"), N_("smpte"));
+ _standard->add_entry(_("SMPTE"), N_("smpte"));
if (Config::instance()->allow_smpte_bv20() || (_film && _film->limit_to_smpte_bv20())) {
- _standard->add(_("SMPTE (Bv2.0 only)"), N_("smpte-bv20"));
+ _standard->add_entry(_("SMPTE (Bv2.0 only)"), N_("smpte-bv20"));
}
- _standard->add(_("Interop"), N_("interop"));
+ _standard->add_entry(_("Interop"), N_("interop"));
+ _standard->add_entry(_("MPEG2 Interop"), N_("mpeg2-interop"));
_sizer->Layout();
}
@@ -182,7 +163,11 @@ DCPPanel::set_standard()
DCPOMATIC_ASSERT(!_film->limit_to_smpte_bv20() || _standard->GetCount() == 3);
if (_film->interop()) {
- checked_set(_standard, "interop");
+ if (_film->video_encoding() == VideoEncoding::JPEG2000) {
+ checked_set(_standard, "interop");
+ } else {
+ checked_set(_standard, "mpeg2-interop");
+ }
} else {
checked_set(_standard, _film->limit_to_smpte_bv20() ? "smpte-bv20" : "smpte");
}
@@ -204,12 +189,18 @@ DCPPanel::standard_changed ()
if (*data == N_("interop")) {
_film->set_interop(true);
_film->set_limit_to_smpte_bv20(false);
+ _film->set_video_encoding(VideoEncoding::JPEG2000);
} else if (*data == N_("smpte")) {
_film->set_interop(false);
_film->set_limit_to_smpte_bv20(false);
+ _film->set_video_encoding(VideoEncoding::JPEG2000);
} else if (*data == N_("smpte-bv20")) {
_film->set_interop(false);
_film->set_limit_to_smpte_bv20(true);
+ _film->set_video_encoding(VideoEncoding::JPEG2000);
+ } else if (*data == N_("mpeg2-interop")) {
+ _film->set_interop(true);
+ _film->set_video_encoding(VideoEncoding::MPEG2);
}
}
@@ -241,15 +232,6 @@ DCPPanel::add_to_grid ()
_grid->Add (_dcp_name, wxGBPosition(r, 0), wxGBSpan(1, 2), wxALIGN_CENTER_VERTICAL | wxEXPAND);
++r;
- {
- auto s = new wxBoxSizer (wxHORIZONTAL);
- s->Add (_enable_audio_language, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, DCPOMATIC_SIZER_GAP);
- s->Add (_audio_language, 1, wxALIGN_CENTER_VERTICAL | wxBOTTOM, DCPOMATIC_CHECKBOX_BOTTOM_PAD);
- s->Add (_edit_audio_language, 0, wxALIGN_CENTER_VERTICAL | wxBOTTOM, DCPOMATIC_CHECKBOX_BOTTOM_PAD);
- _grid->Add (s, wxGBPosition(r, 0), wxGBSpan(1, 2), wxEXPAND | wxALIGN_CENTER_VERTICAL);
- }
- ++r;
-
add_label_to_sizer (_grid, _dcp_content_type_label, true, wxGBPosition(r, 0));
_grid->Add (_dcp_content_type, wxGBPosition(r, 1));
++r;
@@ -257,19 +239,6 @@ DCPPanel::add_to_grid ()
_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;
@@ -277,6 +246,7 @@ DCPPanel::add_to_grid ()
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;
}
@@ -294,13 +264,13 @@ DCPPanel::name_changed ()
void
-DCPPanel::j2k_bandwidth_changed ()
+DCPPanel::video_bit_rate_changed()
{
if (!_film) {
return;
}
- _film->set_j2k_bandwidth (_j2k_bandwidth->GetValue() * 1000000);
+ _film->set_video_bit_rate(_film->video_encoding(), _video_bit_rate->GetValue() * 1000000);
}
@@ -390,6 +360,14 @@ DCPPanel::metadata_clicked ()
void
+DCPPanel::reels_clicked()
+{
+ _dcp_timeline.reset(_panel, _film);
+ _dcp_timeline->Show();
+}
+
+
+void
DCPPanel::film_changed(FilmProperty p)
{
switch (p) {
@@ -418,8 +396,8 @@ DCPPanel::film_changed(FilmProperty p)
setup_container ();
setup_dcp_name ();
break;
- case FilmProperty::J2K_BANDWIDTH:
- checked_set (_j2k_bandwidth, _film->j2k_bandwidth() / 1000000);
+ case FilmProperty::VIDEO_BIT_RATE:
+ checked_set(_video_bit_rate, _film->video_bit_rate(_film->video_encoding()) / 1000000);
break;
case FilmProperty::USE_ISDCF_NAME:
{
@@ -478,6 +456,12 @@ DCPPanel::film_changed(FilmProperty p)
setup_dcp_name ();
_markers->Enable (!_film->interop());
break;
+ case FilmProperty::VIDEO_ENCODING:
+ set_standard();
+ setup_container();
+ setup_sensitivity();
+ film_changed(FilmProperty::VIDEO_BIT_RATE);
+ break;
case FilmProperty::LIMIT_TO_SMPTE_BV20:
set_standard();
break;
@@ -490,13 +474,6 @@ 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 ();
@@ -511,6 +488,7 @@ DCPPanel::film_changed(FilmProperty p)
checked_set (_audio_language, al ? std_to_wx(al->to_string()) : wxT(""));
setup_dcp_name ();
setup_sensitivity ();
+ _audio_panel_sizer->Layout();
break;
}
case FilmProperty::AUDIO_FRAME_RATE:
@@ -565,24 +543,28 @@ DCPPanel::film_content_changed (int property)
void
DCPPanel::setup_container ()
{
- int n = 0;
- auto ratios = Ratio::containers ();
- auto i = ratios.begin ();
- while (i != ratios.end() && *i != _film->container()) {
- ++i;
- ++n;
+ auto ratios = Ratio::containers();
+ if (std::find(ratios.begin(), ratios.end(), _film->container()) == ratios.end()) {
+ ratios.push_back(_film->container());
}
- if (i == ratios.end()) {
- checked_set (_container, -1);
- checked_set (_container_size, wxT(""));
- } else {
- checked_set (_container, n);
- auto const size = fit_ratio_within (_film->container()->ratio(), _film->full_frame ());
- checked_set (_container_size, wxString::Format("%dx%d", size.width, size.height));
+ wxArrayString new_ratios;
+ for (auto ratio: ratios) {
+ new_ratios.Add(std_to_wx(ratio->container_nickname()));
}
+ _container->set_entries(new_ratios);
+
+ auto iter = std::find_if(ratios.begin(), ratios.end(), [this](Ratio const* ratio) { return ratio == _film->container(); });
+ DCPOMATIC_ASSERT(iter != ratios.end());
+
+ checked_set(_container, iter - ratios.begin());
+ auto const size = fit_ratio_within(_film->container()->ratio(), _film->full_frame ());
+ checked_set(_container_size, wxString::Format("%dx%d", size.width, size.height));
+
setup_dcp_name ();
+
+ _video_grid->Layout();
}
@@ -644,7 +626,7 @@ DCPPanel::set_film (shared_ptr<Film> film)
film_changed(FilmProperty::CONTAINER);
film_changed(FilmProperty::RESOLUTION);
film_changed(FilmProperty::ENCRYPTED);
- film_changed(FilmProperty::J2K_BANDWIDTH);
+ film_changed(FilmProperty::VIDEO_BIT_RATE);
film_changed(FilmProperty::VIDEO_FRAME_RATE);
film_changed(FilmProperty::AUDIO_CHANNELS);
film_changed(FilmProperty::SEQUENCE);
@@ -673,6 +655,8 @@ DCPPanel::set_general_sensitivity (bool s)
void
DCPPanel::setup_sensitivity ()
{
+ auto const mpeg2 = _film && _film->video_encoding() == VideoEncoding::MPEG2;
+
_name->Enable (_generally_sensitive);
_use_isdcf_name->Enable (_generally_sensitive);
_dcp_content_type->Enable (_generally_sensitive);
@@ -681,16 +665,15 @@ DCPPanel::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);
+ _reels->Enable (_generally_sensitive && _film);
_frame_rate_choice->Enable (_generally_sensitive && _film && !_film->references_dcp_video() && !_film->contains_atmos_content());
_frame_rate_spin->Enable (_generally_sensitive && _film && !_film->references_dcp_video() && !_film->contains_atmos_content());
_audio_channels->Enable (_generally_sensitive && _film && !_film->references_dcp_audio());
_audio_processor->Enable (_generally_sensitive && _film && !_film->references_dcp_audio());
- _j2k_bandwidth->Enable (_generally_sensitive && _film && !_film->references_dcp_video());
- _container->Enable (_generally_sensitive && _film && !_film->references_dcp_video());
+ _video_bit_rate->Enable (_generally_sensitive && _film && !_film->references_dcp_video());
+ _container->Enable (_generally_sensitive && _film && !_film->references_dcp_video() && !mpeg2);
_best_frame_rate->Enable (
_generally_sensitive &&
_film &&
@@ -698,8 +681,8 @@ DCPPanel::setup_sensitivity ()
!_film->references_dcp_video() &&
!_film->contains_atmos_content()
);
- _resolution->Enable (_generally_sensitive && _film && !_film->references_dcp_video());
- _three_d->Enable (_generally_sensitive && _film && !_film->references_dcp_video());
+ _resolution->Enable (_generally_sensitive && _film && !_film->references_dcp_video() && !mpeg2);
+ _three_d->Enable (_generally_sensitive && _film && !_film->references_dcp_video() && !mpeg2);
_standard->Enable (
_generally_sensitive &&
@@ -772,7 +755,8 @@ DCPPanel::reencode_j2k_changed ()
void
DCPPanel::config_changed (Config::Property p)
{
- _j2k_bandwidth->SetRange (1, Config::instance()->maximum_j2k_bandwidth() / 1000000);
+ VideoEncoding const encoding = _film ? _film->video_encoding() : VideoEncoding::JPEG2000;
+ _video_bit_rate->SetRange(1, Config::instance()->maximum_video_bit_rate(encoding) / 1000000);
setup_frame_rate_widget ();
if (p == Config::SHOW_EXPERIMENTAL_AUDIO_PROCESSORS) {
@@ -790,6 +774,8 @@ DCPPanel::config_changed (Config::Property p)
}
} else if (p == Config::ISDCF_NAME_PART_LENGTH) {
setup_dcp_name();
+ } else if (p == Config::ALLOW_ANY_CONTAINER) {
+ setup_container();
}
}
@@ -831,8 +817,8 @@ DCPPanel::make_video_panel ()
_three_d = new CheckBox (panel, _("3D"));
- _j2k_bandwidth_label = create_label (panel, _("JPEG2000 bandwidth\nfor newly-encoded data"), true);
- _j2k_bandwidth = new SpinCtrl (panel, DCPOMATIC_SPIN_CTRL_WIDTH);
+ _video_bit_rate_label = create_label(panel, _("Video bit rate\nfor newly-encoded data"), true);
+ _video_bit_rate = new SpinCtrl(panel, DCPOMATIC_SPIN_CTRL_WIDTH);
_mbits_label = create_label (panel, _("Mbit/s"), false);
_reencode_j2k = new CheckBox (panel, _("Re-encode JPEG2000 data from input"));
@@ -841,26 +827,23 @@ DCPPanel::make_video_panel ()
_frame_rate_choice->Bind (wxEVT_CHOICE, boost::bind(&DCPPanel::frame_rate_choice_changed, this));
_frame_rate_spin->Bind (wxEVT_SPINCTRL, boost::bind(&DCPPanel::frame_rate_spin_changed, this));
_best_frame_rate->Bind (wxEVT_BUTTON, boost::bind(&DCPPanel::best_frame_rate_clicked, this));
- _j2k_bandwidth->Bind (wxEVT_SPINCTRL, boost::bind(&DCPPanel::j2k_bandwidth_changed, this));
+ _video_bit_rate->Bind (wxEVT_SPINCTRL, boost::bind(&DCPPanel::video_bit_rate_changed, this));
/* Also listen to wxEVT_TEXT so that typing numbers directly in is always noticed */
- _j2k_bandwidth->Bind (wxEVT_TEXT, boost::bind(&DCPPanel::j2k_bandwidth_changed, this));
+ _video_bit_rate->Bind (wxEVT_TEXT, boost::bind(&DCPPanel::video_bit_rate_changed, this));
_resolution->Bind (wxEVT_CHOICE, boost::bind(&DCPPanel::resolution_changed, this));
_three_d->bind(&DCPPanel::three_d_changed, this);
_reencode_j2k->bind(&DCPPanel::reencode_j2k_changed, this);
- for (auto i: Ratio::containers()) {
- _container->add(i->container_nickname());
- }
-
for (auto i: Config::instance()->allowed_dcp_frame_rates()) {
- _frame_rate_choice->add(boost::lexical_cast<string>(i));
+ _frame_rate_choice->add_entry(boost::lexical_cast<string>(i));
}
- _j2k_bandwidth->SetRange (1, Config::instance()->maximum_j2k_bandwidth() / 1000000);
+ VideoEncoding const encoding = _film ? _film->video_encoding() : VideoEncoding::JPEG2000;
+ _video_bit_rate->SetRange(1, Config::instance()->maximum_video_bit_rate(encoding) / 1000000);
_frame_rate_spin->SetRange (1, 480);
- _resolution->add(_("2K"));
- _resolution->add(_("4K"));
+ _resolution->add_entry(_("2K"));
+ _resolution->add_entry(_("4K"));
add_video_panel_to_grid ();
setup_frame_rate_widget();
@@ -900,9 +883,9 @@ DCPPanel::add_video_panel_to_grid ()
_video_grid->Add (_three_d, wxGBPosition (r, 0), wxGBSpan (1, 2));
++r;
- add_label_to_sizer (_video_grid, _j2k_bandwidth_label, true, wxGBPosition (r, 0));
+ add_label_to_sizer(_video_grid, _video_bit_rate_label, true, wxGBPosition (r, 0));
auto s = new wxBoxSizer (wxHORIZONTAL);
- s->Add (_j2k_bandwidth, 0, wxALIGN_CENTER_VERTICAL);
+ s->Add(_video_bit_rate, 0, wxALIGN_CENTER_VERTICAL);
add_label_to_sizer (s, _mbits_label, false, 0, wxLEFT | wxALIGN_CENTER_VERTICAL);
_video_grid->Add (s, wxGBPosition(r, 1), wxDefaultSpan);
++r;
@@ -953,6 +936,10 @@ DCPPanel::make_audio_panel ()
_audio_processor = new Choice(panel);
add_audio_processors ();
+ _enable_audio_language = new CheckBox(panel, _("Language"));
+ _audio_language = new wxStaticText(panel, wxID_ANY, wxT(""));
+ _edit_audio_language = new Button(panel, _("Edit..."));
+
_show_audio = new Button (panel, _("Show graph of audio levels..."));
_audio_channels->Bind (wxEVT_CHOICE, boost::bind (&DCPPanel::audio_channels_changed, this));
@@ -960,6 +947,10 @@ DCPPanel::make_audio_panel ()
_audio_sample_rate->Bind (wxEVT_CHOICE, boost::bind(&DCPPanel::audio_sample_rate_changed, this));
}
_audio_processor->Bind (wxEVT_CHOICE, boost::bind (&DCPPanel::audio_processor_changed, this));
+
+ _enable_audio_language->bind(&DCPPanel::enable_audio_language_toggled, this);
+ _edit_audio_language->Bind(wxEVT_BUTTON, boost::bind(&DCPPanel::edit_audio_language_clicked, this));
+
_show_audio->Bind (wxEVT_BUTTON, boost::bind (&DCPPanel::show_audio_clicked, this));
if (_audio_sample_rate) {
@@ -992,6 +983,16 @@ DCPPanel::add_audio_panel_to_grid ()
_audio_grid->Add (_audio_processor, wxGBPosition (r, 1));
++r;
+ {
+ auto s = new wxBoxSizer (wxHORIZONTAL);
+ s->Add(_enable_audio_language, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, DCPOMATIC_SIZER_GAP);
+ s->Add(_audio_language, 1, wxALIGN_CENTER_VERTICAL | wxBOTTOM, DCPOMATIC_CHECKBOX_BOTTOM_PAD);
+ s->Add(DCPOMATIC_SIZER_X_GAP, 0);
+ s->Add(_edit_audio_language, 0, wxALIGN_CENTER_VERTICAL | wxBOTTOM, DCPOMATIC_CHECKBOX_BOTTOM_PAD);
+ _audio_grid->Add(s, wxGBPosition(r, 0), wxGBSpan(1, 2), wxEXPAND | wxALIGN_CENTER_VERTICAL);
+ }
+ ++r;
+
_audio_grid->Add (_show_audio, wxGBPosition (r, 0), wxGBSpan (1, 2));
++r;
}
@@ -1030,33 +1031,11 @@ DCPPanel::show_audio_clicked ()
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 ()
{
- _audio_processor->add(_("None"), new wxStringClientData(N_("none")));
+ _audio_processor->add_entry(_("None"), new wxStringClientData(N_("none")));
for (auto ap: AudioProcessor::visible()) {
- _audio_processor->add(std_to_wx(ap->name()), new wxStringClientData(std_to_wx(ap->id())));
+ _audio_processor->add_entry(std_to_wx(ap->name()), new wxStringClientData(std_to_wx(ap->id())));
}
_audio_panel_sizer->Layout();
}
diff --git a/src/wx/dcp_panel.h b/src/wx/dcp_panel.h
index 849fe185c..c686a9c55 100644
--- a/src/wx/dcp_panel.h
+++ b/src/wx/dcp_panel.h
@@ -39,6 +39,7 @@ class wxGridBagSizer;
class AudioDialog;
class Choice;
+class DCPTimelineDialog;
class Film;
class FilmViewer;
class InteropMetadataDialog;
@@ -70,7 +71,7 @@ private:
void copy_isdcf_name_button_clicked ();
void container_changed ();
void dcp_content_type_changed ();
- void j2k_bandwidth_changed ();
+ void video_bit_rate_changed();
void frame_rate_choice_changed ();
void frame_rate_spin_changed ();
void best_frame_rate_clicked ();
@@ -82,10 +83,9 @@ private:
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 ();
@@ -129,9 +129,9 @@ private:
Choice* _container;
wxStaticText* _container_size;
wxButton* _copy_isdcf_name_button;
- wxStaticText* _j2k_bandwidth_label;
+ wxStaticText* _video_bit_rate_label;
wxStaticText* _mbits_label;
- wxSpinCtrl* _j2k_bandwidth;
+ wxSpinCtrl* _video_bit_rate;
wxStaticText* _dcp_content_type_label;
Choice* _dcp_content_type;
wxStaticText* _frame_rate_label;
@@ -153,19 +153,16 @@ private:
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;
diff --git a/src/wx/dcp_referencing_dialog.cc b/src/wx/dcp_referencing_dialog.cc
new file mode 100644
index 000000000..853391bd3
--- /dev/null
+++ b/src/wx/dcp_referencing_dialog.cc
@@ -0,0 +1,231 @@
+/*
+ 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 "dcp_referencing_dialog.h"
+#include "static_text.h"
+#include "wx_util.h"
+#include "lib/dcp_content.h"
+#include "lib/film.h"
+#include "lib/types.h"
+#include <wx/gbsizer.h>
+#include <wx/wx.h>
+
+
+using std::dynamic_pointer_cast;
+using std::shared_ptr;
+using std::string;
+using std::vector;
+using std::weak_ptr;
+#if BOOST_VERSION >= 106100
+using namespace boost::placeholders;
+#endif
+
+
+DCPReferencingDialog::DCPReferencingDialog(wxWindow* parent, shared_ptr<const Film> film)
+ : wxDialog(parent, wxID_ANY, _("Version file (VF) setup"))
+ , _film(film)
+ , _dcp_grid(new wxGridBagSizer(DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP))
+ , _overall_sizer(new wxBoxSizer(wxVERTICAL))
+{
+ _film_connection = film->Change.connect(boost::bind(&DCPReferencingDialog::film_changed, this, _1, _2));
+ _film_content_connection = film->ContentChange.connect(boost::bind(&DCPReferencingDialog::film_content_changed, this, _1, _3));
+
+ _overall_sizer->Add(_dcp_grid, 1, wxALL, DCPOMATIC_DIALOG_BORDER);
+ SetSizer(_overall_sizer);
+
+ auto buttons = CreateSeparatedButtonSizer(wxOK);
+ if (buttons) {
+ _overall_sizer->Add(buttons, wxSizerFlags().Expand().DoubleBorder());
+ }
+
+ setup();
+}
+
+
+void
+DCPReferencingDialog::film_changed(ChangeType type, FilmProperty property)
+{
+ if (type != ChangeType::DONE) {
+ return;
+ }
+
+ if (
+ property == FilmProperty::INTEROP ||
+ property == FilmProperty::RESOLUTION ||
+ property == FilmProperty::CONTAINER ||
+ property == FilmProperty::REEL_TYPE ||
+ property == FilmProperty::VIDEO_FRAME_RATE ||
+ property == FilmProperty::CONTENT) {
+ setup();
+ }
+}
+
+
+void
+DCPReferencingDialog::film_content_changed(ChangeType type, int property)
+{
+ if (type != ChangeType::DONE) {
+ return;
+ }
+
+ if (
+ property != DCPContentProperty::REFERENCE_VIDEO &&
+ property != DCPContentProperty::REFERENCE_AUDIO &&
+ property != DCPContentProperty::REFERENCE_TEXT) {
+ setup();
+ }
+}
+
+
+void
+DCPReferencingDialog::setup()
+{
+ _dcps.clear();
+ _dcp_grid->Clear(true);
+
+ int column = 0;
+ for (auto const& heading: { _("DCP"), _("Picture"), _("Sound"), _("Subtitles"), _("Closed captions") }) {
+ auto text = new StaticText(this, heading);
+ wxFont font(*wxNORMAL_FONT);
+ font.SetWeight(wxFONTWEIGHT_BOLD);
+ text->SetFont(font);
+ _dcp_grid->Add(text, wxGBPosition(0, column), wxDefaultSpan, wxALL, DCPOMATIC_SIZER_GAP);
+ ++column;
+ };
+
+ auto const all_parts = { Part::VIDEO, Part::AUDIO, Part::SUBTITLES, Part::CLOSED_CAPTIONS };
+
+ int row = 1;
+ for (auto& content: _film->content()) {
+ auto dcp_content = dynamic_pointer_cast<DCPContent>(content);
+ if (!dcp_content) {
+ continue;
+ }
+
+ DCP record;
+ record.content = dcp_content;
+ _dcp_grid->Add(new StaticText(this, std_to_wx(dcp_content->name())), wxGBPosition(row, 0));
+ column = 1;
+ for (auto const& part: all_parts) {
+ record.check_box[part] = new CheckBox(this, wxT(""));
+ switch (part) {
+ case Part::VIDEO:
+ record.check_box[part]->set(dcp_content->reference_video());
+ break;
+ case Part::AUDIO:
+ record.check_box[part]->set(dcp_content->reference_audio());
+ break;
+ case Part::SUBTITLES:
+ record.check_box[part]->set(dcp_content->reference_text(TextType::OPEN_SUBTITLE));
+ break;
+ case Part::CLOSED_CAPTIONS:
+ record.check_box[part]->set(dcp_content->reference_text(TextType::CLOSED_CAPTION));
+ break;
+ default:
+ DCPOMATIC_ASSERT(false);
+ }
+ record.check_box[part]->bind(&DCPReferencingDialog::checkbox_changed, this, weak_ptr<DCPContent>(dcp_content), record.check_box[part], part);
+ _dcp_grid->Add(record.check_box[part], wxGBPosition(row, column++), wxDefaultSpan, wxALIGN_CENTER);
+ }
+ ++row;
+
+ auto add_problem = [this, &row](wxString const& cannot, string why_not) {
+ auto reason = new StaticText(this, cannot + wxT(": ") + std_to_wx(why_not));
+ wxFont font(*wxNORMAL_FONT);
+ font.SetStyle(wxFONTSTYLE_ITALIC);
+ reason->SetFont(font);
+ _dcp_grid->Add(reason, wxGBPosition(row, 0), wxGBSpan(1, 5), wxLEFT, DCPOMATIC_SIZER_X_GAP * 4);
+ ++row;
+ };
+
+ string why_not;
+
+ if (!dcp_content->can_reference_anything(_film, why_not)) {
+ for (auto const& part: all_parts) {
+ record.check_box[part]->Enable(false);
+ }
+ add_problem(_("Cannot reference this DCP"), why_not);
+ } else {
+ if (!dcp_content->can_reference_video(_film, why_not)) {
+ record.check_box[Part::VIDEO]->Enable(false);
+ if (dcp_content->video) {
+ add_problem(_("Cannot reference this DCP's video"), why_not);
+ }
+ }
+
+ if (!dcp_content->can_reference_audio(_film, why_not)) {
+ record.check_box[Part::AUDIO]->Enable(false);
+ if (dcp_content->audio) {
+ add_problem(_("Cannot reference this DCP's audio"), why_not);
+ }
+ }
+
+ if (!dcp_content->can_reference_text(_film, TextType::OPEN_SUBTITLE, why_not)) {
+ record.check_box[Part::SUBTITLES]->Enable(false);
+ if (dcp_content->text_of_original_type(TextType::OPEN_SUBTITLE)) {
+ add_problem(_("Cannot reference this DCP's subtitles"), why_not);
+ }
+ }
+
+ if (!dcp_content->can_reference_text(_film, TextType::CLOSED_CAPTION, why_not)) {
+ record.check_box[Part::CLOSED_CAPTIONS]->Enable(false);
+ if (dcp_content->text_of_original_type(TextType::CLOSED_CAPTION)) {
+ add_problem(_("Cannot reference this DCP's closed captions"), why_not);
+ }
+ }
+ }
+
+ _dcps.push_back(record);
+ }
+
+ _dcp_grid->Layout();
+ _overall_sizer->Layout();
+ _overall_sizer->SetSizeHints(this);
+}
+
+
+void
+DCPReferencingDialog::checkbox_changed(weak_ptr<DCPContent> weak_content, CheckBox* check_box, Part part)
+{
+ auto content = weak_content.lock();
+ if (!content) {
+ return;
+ }
+
+ switch (part) {
+ case Part::VIDEO:
+ content->set_reference_video(check_box->get());
+ break;
+ case Part::AUDIO:
+ content->set_reference_audio(check_box->get());
+ break;
+ case Part::SUBTITLES:
+ content->set_reference_text(TextType::OPEN_SUBTITLE, check_box->get());
+ break;
+ case Part::CLOSED_CAPTIONS:
+ content->set_reference_text(TextType::CLOSED_CAPTION, check_box->get());
+ break;
+ default:
+ DCPOMATIC_ASSERT(false);
+ }
+}
+
diff --git a/src/wx/dcp_referencing_dialog.h b/src/wx/dcp_referencing_dialog.h
new file mode 100644
index 000000000..e49292c22
--- /dev/null
+++ b/src/wx/dcp_referencing_dialog.h
@@ -0,0 +1,70 @@
+/*
+ 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 "lib/enum_indexed_vector.h"
+#include "lib/film.h"
+#include <wx/wx.h>
+#include <memory>
+#include <vector>
+
+
+class CheckBox;
+class DCPContent;
+class Film;
+class wxBoxSizer;
+class wxGridBagSizer;
+
+
+class DCPReferencingDialog : public wxDialog
+{
+public:
+ DCPReferencingDialog(wxWindow* parent, std::shared_ptr<const Film> film);
+
+private:
+ enum class Part {
+ VIDEO,
+ AUDIO,
+ SUBTITLES,
+ CLOSED_CAPTIONS,
+ COUNT
+ };
+
+ void setup();
+ void checkbox_changed(std::weak_ptr<DCPContent> content, CheckBox* check_box, Part part);
+ void film_changed(ChangeType type, FilmProperty property);
+ void film_content_changed(ChangeType type, int property);
+
+ std::shared_ptr<const Film> _film;
+
+ wxGridBagSizer* _dcp_grid;
+ wxBoxSizer* _overall_sizer;
+
+ struct DCP {
+ std::shared_ptr<DCPContent> content;
+ EnumIndexedVector<CheckBox*, Part> check_box;
+ };
+
+ std::vector<DCP> _dcps;
+
+ boost::signals2::scoped_connection _film_connection;
+ boost::signals2::scoped_connection _film_content_connection;
+};
+
diff --git a/src/wx/dcp_timeline.cc b/src/wx/dcp_timeline.cc
new file mode 100644
index 000000000..7b8e93325
--- /dev/null
+++ b/src/wx/dcp_timeline.cc
@@ -0,0 +1,616 @@
+/*
+ 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 "id.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 = DCPOMATIC_DCP_TIMELINE_MENU
+};
+
+
+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_entry(_("Single reel"));
+ _reel_type->add_entry(_("Split by video content"));
+ _reel_type->add_entry(_("Split by maximum reel size"));
+ _reel_type->add_entry(_("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](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();
+}
+
diff --git a/src/wx/dcp_timeline.h b/src/wx/dcp_timeline.h
new file mode 100644
index 000000000..23644c03f
--- /dev/null
+++ b/src/wx/dcp_timeline.h
@@ -0,0 +1,125 @@
+/*
+ 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/change_signaller.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 <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
+
diff --git a/src/wx/dcp_timeline_dialog.cc b/src/wx/dcp_timeline_dialog.cc
new file mode 100644
index 000000000..2cf6a74f1
--- /dev/null
+++ b/src/wx/dcp_timeline_dialog.cc
@@ -0,0 +1,78 @@
+/*
+ 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);
+}
+
diff --git a/src/wx/dcp_timeline_dialog.h b/src/wx/dcp_timeline_dialog.h
new file mode 100644
index 000000000..d1293ca26
--- /dev/null
+++ b/src/wx/dcp_timeline_dialog.h
@@ -0,0 +1,39 @@
+/*
+ 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;
+};
+
diff --git a/src/wx/dcp_timeline_reel_marker_view.cc b/src/wx/dcp_timeline_reel_marker_view.cc
new file mode 100644
index 000000000..1c97ca175
--- /dev/null
+++ b/src/wx/dcp_timeline_reel_marker_view.cc
@@ -0,0 +1,71 @@
+/*
+ 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 };
+}
+
diff --git a/src/wx/dcp_timeline_reel_marker_view.h b/src/wx/dcp_timeline_reel_marker_view.h
new file mode 100644
index 000000000..273d98259
--- /dev/null
+++ b/src/wx/dcp_timeline_reel_marker_view.h
@@ -0,0 +1,59 @@
+/*
+ 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;
+};
+
diff --git a/src/wx/dcp_timeline_view.h b/src/wx/dcp_timeline_view.h
new file mode 100644
index 000000000..24a75cad2
--- /dev/null
+++ b/src/wx/dcp_timeline_view.h
@@ -0,0 +1,44 @@
+/*
+ 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;
+};
+
+
+
diff --git a/src/wx/dcpomatic_choice.cc b/src/wx/dcpomatic_choice.cc
index 2f1a6f0e9..f2e215439 100644
--- a/src/wx/dcpomatic_choice.cc
+++ b/src/wx/dcpomatic_choice.cc
@@ -40,14 +40,14 @@ Choice::Choice(wxWindow* parent)
void
-Choice::add(string const& entry)
+Choice::add_entry(string const& entry)
{
- add(std_to_wx(entry));
+ add_entry(std_to_wx(entry));
}
void
-Choice::add(wxString const& entry)
+Choice::add_entry(wxString const& entry)
{
if (_needs_clearing) {
Clear();
@@ -59,7 +59,7 @@ Choice::add(wxString const& entry)
void
-Choice::add(wxString const& entry, wxClientData* data)
+Choice::add_entry(wxString const& entry, wxClientData* data)
{
if (_needs_clearing) {
Clear();
@@ -71,7 +71,7 @@ Choice::add(wxString const& entry, wxClientData* data)
void
-Choice::add(wxString const& entry, wxString const& data)
+Choice::add_entry(wxString const& entry, wxString const& data)
{
if (_needs_clearing) {
Clear();
@@ -83,6 +83,18 @@ Choice::add(wxString const& entry, wxString const& data)
void
+Choice::set_entries(wxArrayString const& entries)
+{
+ if (GetStrings() == entries) {
+ return;
+ }
+
+ Clear();
+ Set(entries);
+}
+
+
+void
Choice::set(int index)
{
SetSelection(index);
diff --git a/src/wx/dcpomatic_choice.h b/src/wx/dcpomatic_choice.h
index dec0a3701..cc8115d20 100644
--- a/src/wx/dcpomatic_choice.h
+++ b/src/wx/dcpomatic_choice.h
@@ -32,10 +32,12 @@ class Choice : public wxChoice
public:
Choice(wxWindow* parent);
- void add(wxString const& entry);
- void add(wxString const& entry, wxClientData* data);
- void add(wxString const& entry, wxString const& data);
- void add(std::string const& entry);
+ void add_entry(wxString const& entry);
+ void add_entry(wxString const& entry, wxClientData* data);
+ void add_entry(wxString const& entry, wxString const& data);
+ void add_entry(std::string const& entry);
+ void set_entries(wxArrayString const& entries);
+
void set(int index);
void set_by_data(wxString const& data);
boost::optional<int> get() const;
diff --git a/src/wx/dir_picker_ctrl.cc b/src/wx/dir_picker_ctrl.cc
index 4c19da42a..a8c09e334 100644
--- a/src/wx/dir_picker_ctrl.cc
+++ b/src/wx/dir_picker_ctrl.cc
@@ -36,12 +36,13 @@ using namespace std;
using namespace boost;
-DirPickerCtrl::DirPickerCtrl (wxWindow* parent)
+DirPickerCtrl::DirPickerCtrl(wxWindow* parent, bool leaf)
: wxPanel (parent)
+ , _leaf(leaf)
{
_sizer = new wxBoxSizer (wxHORIZONTAL);
- _folder = new StaticText (this, wxT(""));
+ _folder = new StaticText(this, wxT(""), wxDefaultPosition, wxDefaultSize, wxST_ELLIPSIZE_END);
wxFont font = _folder->GetFont ();
font.SetStyle (wxFONTSTYLE_ITALIC);
_folder->SetFont (font);
@@ -62,7 +63,11 @@ DirPickerCtrl::SetPath (wxString p)
if (_path == wxStandardPaths::Get().GetDocumentsDir()) {
_folder->SetLabel (_("My Documents"));
} else {
- _folder->SetLabel (_path);
+ if (_leaf) {
+ _folder->SetLabel(std_to_wx(boost::filesystem::path(wx_to_std(_path)).filename().string()));
+ } else {
+ _folder->SetLabel(_path);
+ }
}
wxCommandEvent ev (wxEVT_DIRPICKER_CHANGED, wxID_ANY);
diff --git a/src/wx/dir_picker_ctrl.h b/src/wx/dir_picker_ctrl.h
index f70168db4..31df9518c 100644
--- a/src/wx/dir_picker_ctrl.h
+++ b/src/wx/dir_picker_ctrl.h
@@ -33,7 +33,7 @@ LIBDCP_ENABLE_WARNINGS
class DirPickerCtrl : public wxPanel
{
public:
- explicit DirPickerCtrl (wxWindow *);
+ DirPickerCtrl(wxWindow *, bool leaf = false);
wxString GetPath () const;
void SetPath (wxString);
@@ -47,6 +47,7 @@ private:
wxButton* _browse;
wxString _path;
wxSizer* _sizer;
+ bool _leaf = false;
};
#endif
diff --git a/src/wx/disk_warning_dialog.cc b/src/wx/disk_warning_dialog.cc
index 531b7f4f9..9af4a2aca 100644
--- a/src/wx/disk_warning_dialog.cc
+++ b/src/wx/disk_warning_dialog.cc
@@ -18,9 +18,12 @@
*/
+
#include "disk_warning_dialog.h"
#include "static_text.h"
#include "wx_util.h"
+#include "wx_variant.h"
+
DiskWarningDialog::DiskWarningDialog ()
: wxDialog(nullptr, wxID_ANY, _("Important notice"))
@@ -45,10 +48,10 @@ DiskWarningDialog::DiskWarningDialog ()
auto const confirmation = _("I am sure");
text->SetLabelMarkup(wxString::Format(
- _("The <b>DCP-o-matic Disk Writer</b> is\n\n<span weight=\"bold\" size=\"20480\" foreground=\"red\">BETA-GRADE TEST SOFTWARE</span>\n\n"
+ _("The <b>%s</b> is\n\n<span weight=\"bold\" size=\"20480\" foreground=\"red\">BETA-GRADE TEST SOFTWARE</span>\n\n"
"and may\n\n<span weight=\"bold\" size=\"20480\" foreground=\"red\">DESTROY DATA!</span>\n\n"
"If you are sure you want to continue please type\n\n<tt>%s</tt>\n\ninto the box below, then click OK."),
- confirmation));
+ variant::wx::dcpomatic_disk_writer(), confirmation));
}
bool
diff --git a/src/wx/dkdm_dialog.cc b/src/wx/dkdm_dialog.cc
index 34a8d6284..a36a9ceae 100644
--- a/src/wx/dkdm_dialog.cc
+++ b/src/wx/dkdm_dialog.cc
@@ -28,6 +28,7 @@
#include "recipients_panel.h"
#include "static_text.h"
#include "wx_util.h"
+#include "wx_variant.h"
#include "lib/config.h"
#include "lib/film.h"
#include "lib/job_manager.h"
@@ -161,7 +162,7 @@ DKDMDialog::make_clicked ()
list<KDMWithMetadataPtr> kdms;
try {
for (auto i: _recipients->recipients()) {
- auto p = kdm_for_dkdm_recipient (film, _cpl->cpl(), i, _timing->from(), _timing->until());
+ auto p = kdm_for_dkdm_recipient(film, _cpl->cpl(), i, _timing->from(), _timing->until());
if (p) {
kdms.push_back (p);
}
@@ -170,7 +171,13 @@ DKDMDialog::make_clicked ()
if (e.starts_too_early()) {
error_dialog (this, _("The KDM start period is before (or close to) the start of the signing certificate's validity period. Use a later start time for this KDM."));
} else {
- error_dialog (this, _("The KDM end period is after (or close to) the end of the signing certificates' validity period. Either use an earlier end time for this KDM or re-create your signing certificates in the DCP-o-matic preferences window."));
+ error_dialog(
+ this,
+ variant::wx::insert_dcpomatic(
+ _("The KDM end period is after (or close to) the end of the signing certificates' validity period. "
+ "Either use an earlier end time for this KDM or re-create your signing certificates in the %s preferences window.")
+ )
+ );
}
return;
} catch (runtime_error& e) {
diff --git a/src/wx/export_video_file_dialog.h b/src/wx/export_video_file_dialog.h
index beb33610b..4e626be6b 100644
--- a/src/wx/export_video_file_dialog.h
+++ b/src/wx/export_video_file_dialog.h
@@ -20,7 +20,7 @@
#include "table_dialog.h"
-#include "lib/ffmpeg_encoder.h"
+#include "lib/ffmpeg_file_encoder.h"
#include <dcp/warnings.h>
LIBDCP_DISABLE_WARNINGS
#include <wx/wx.h>
diff --git a/src/wx/file_picker_ctrl.cc b/src/wx/file_picker_ctrl.cc
index 7aa0bfb40..c6aba45b7 100644
--- a/src/wx/file_picker_ctrl.cc
+++ b/src/wx/file_picker_ctrl.cc
@@ -32,8 +32,8 @@ LIBDCP_ENABLE_WARNINGS
#include <boost/filesystem.hpp>
-using namespace std;
-using namespace boost;
+using std::string;
+using boost::optional;
FilePickerCtrl::FilePickerCtrl(
@@ -42,9 +42,9 @@ FilePickerCtrl::FilePickerCtrl(
wxString wildcard,
bool open,
bool warn_overwrite,
- std::string initial_path_key,
+ string initial_path_key,
optional<std::string> initial_filename,
- optional<filesystem::path> override_path
+ optional<boost::filesystem::path> override_path
)
: wxPanel (parent)
, _prompt (prompt)
diff --git a/src/wx/film_editor.cc b/src/wx/film_editor.cc
index 9f54db58e..fae02787a 100644
--- a/src/wx/film_editor.cc
+++ b/src/wx/film_editor.cc
@@ -54,13 +54,15 @@ FilmEditor::FilmEditor(wxWindow* parent, FilmViewer& viewer)
{
auto s = new wxBoxSizer (wxVERTICAL);
- auto notebook = new wxNotebook(this, wxID_ANY);
- s->Add(notebook, 1, wxEXPAND);
+ _notebook = new wxNotebook(this, wxID_ANY);
+ s->Add(_notebook, 1, wxEXPAND);
- _content_panel = new ContentPanel(notebook, _film, viewer);
- notebook->AddPage(_content_panel->window(), _("Content"), true);
- _dcp_panel = new DCPPanel(notebook, _film, viewer);
- notebook->AddPage(_dcp_panel->panel (), _("DCP"), false);
+ _content_panel = new ContentPanel(_notebook, _film, viewer);
+ _notebook->AddPage(_content_panel->window(), _("Content"), true);
+ _dcp_panel = new DCPPanel(_notebook, _film, viewer);
+ _notebook->AddPage(_dcp_panel->panel (), _("DCP"), false);
+
+ _notebook->Bind(wxEVT_NOTEBOOK_PAGE_CHANGED, boost::bind(&FilmEditor::page_changed, this, _1));
JobManager::instance()->ActiveJobsChanged.connect (
bind(&FilmEditor::active_jobs_changed, this, _2)
@@ -71,6 +73,18 @@ FilmEditor::FilmEditor(wxWindow* parent, FilmViewer& viewer)
}
+void
+FilmEditor::page_changed(wxBookCtrlEvent& ev)
+{
+ /* One of these events arrives early on with GetOldSelection() being a non-existent tab,
+ * and we want to ignore that.
+ */
+ if (_film && ev.GetOldSelection() < 2) {
+ _film->set_ui_state("FilmEditorTab", ev.GetSelection() == 0 ? "content" : "dcp");
+ }
+}
+
+
/** Called when the metadata stored in the Film object has changed;
* so that we can update the GUI.
* @param p Property of the Film that has changed.
@@ -144,6 +158,13 @@ FilmEditor::set_film (shared_ptr<Film> film)
if (!_film->content().empty()) {
_content_panel->set_selection (_film->content().front());
}
+
+ auto tab = _film->ui_state("FilmEditorTab").get_value_or("content");
+ if (tab == "content") {
+ _notebook->SetSelection(0);
+ } else if (tab == "dcp") {
+ _notebook->SetSelection(1);
+ }
}
diff --git a/src/wx/film_editor.h b/src/wx/film_editor.h
index a3df266ad..54d639ef5 100644
--- a/src/wx/film_editor.h
+++ b/src/wx/film_editor.h
@@ -32,11 +32,12 @@ LIBDCP_ENABLE_WARNINGS
#include <boost/signals2.hpp>
-class wxNotebook;
-class Film;
class ContentPanel;
class DCPPanel;
+class Film;
class FilmViewer;
+class wxBookCtrlEvent;
+class wxNotebook;
/** @class FilmEditor
@@ -71,6 +72,9 @@ private:
void set_general_sensitivity (bool);
void active_jobs_changed (boost::optional<std::string>);
+ void page_changed(wxBookCtrlEvent& ev);
+
+ wxNotebook* _notebook;
ContentPanel* _content_panel;
DCPPanel* _dcp_panel;
diff --git a/src/wx/film_name_location_dialog.cc b/src/wx/film_name_location_dialog.cc
index 05ffa7a68..9d309810e 100644
--- a/src/wx/film_name_location_dialog.cc
+++ b/src/wx/film_name_location_dialog.cc
@@ -35,11 +35,12 @@ LIBDCP_ENABLE_WARNINGS
#include <boost/filesystem.hpp>
-using namespace std;
-using namespace boost;
+using std::string;
+using boost::bind;
+using boost::optional;
-optional<filesystem::path> FilmNameLocationDialog::_directory;
+optional<boost::filesystem::path> FilmNameLocationDialog::_directory;
FilmNameLocationDialog::FilmNameLocationDialog (wxWindow* parent, wxString title, bool offer_templates)
@@ -112,10 +113,10 @@ FilmNameLocationDialog::folder_changed ()
}
-filesystem::path
+boost::filesystem::path
FilmNameLocationDialog::path () const
{
- filesystem::path p;
+ boost::filesystem::path p;
p /= wx_to_std (_folder->GetPath());
p /= wx_to_std (_name->GetValue());
return p;
@@ -139,7 +140,7 @@ FilmNameLocationDialog::template_name () const
bool
FilmNameLocationDialog::check_path ()
{
- if (filesystem::is_directory(path()) && !filesystem::is_empty(path())) {
+ if (boost::filesystem::is_directory(path()) && !boost::filesystem::is_empty(path())) {
if (!confirm_dialog (
this,
std_to_wx (
@@ -150,7 +151,7 @@ FilmNameLocationDialog::check_path ()
)) {
return false;
}
- } else if (filesystem::is_regular_file(path())) {
+ } else if (boost::filesystem::is_regular_file(path())) {
error_dialog (
this,
String::compose (wx_to_std(_("%1 already exists as a file, so you cannot use it for a film.")), path().c_str())
diff --git a/src/wx/film_viewer.h b/src/wx/film_viewer.h
index 5824f8baa..63aa113d1 100644
--- a/src/wx/film_viewer.h
+++ b/src/wx/film_viewer.h
@@ -25,8 +25,10 @@
#include "video_view.h"
+#include "lib/change_signaller.h"
#include "lib/config.h"
#include "lib/film_property.h"
+#include "lib/player.h"
#include "lib/player_text.h"
#include "lib/signaller.h"
#include "lib/timer.h"
diff --git a/src/wx/full_config_dialog.cc b/src/wx/full_config_dialog.cc
index c1c36c4a4..00e5575b1 100644
--- a/src/wx/full_config_dialog.cc
+++ b/src/wx/full_config_dialog.cc
@@ -45,7 +45,11 @@
#include "send_test_email_dialog.h"
#include "server_dialog.h"
#include "static_text.h"
+#ifdef DCPOMATIC_GROK
+#include "grok/gpu_config_panel.h"
+#endif
#include "wx_util.h"
+#include "wx_variant.h"
#include "lib/config.h"
#include "lib/cross.h"
#include "lib/dcp_content_type.h"
@@ -104,12 +108,12 @@ private:
int r = 0;
add_language_controls (table, r);
- add_label_to_sizer (table, _panel, _("Number of threads DCP-o-matic should use"), true, wxGBPosition (r, 0));
+ add_label_to_sizer(table, _panel, variant::wx::insert_dcpomatic(_("Number of threads %s should use")), true, wxGBPosition(r, 0));
_master_encoding_threads = new wxSpinCtrl (_panel);
table->Add (_master_encoding_threads, wxGBPosition (r, 1));
++r;
- add_label_to_sizer (table, _panel, _("Number of threads DCP-o-matic encode server should use"), true, wxGBPosition (r, 0));
+ add_label_to_sizer(table, _panel, variant::wx::insert_dcpomatic_encode_server(_("Number of threads %s should use")), true, wxGBPosition(r, 0));
_server_encoding_threads = new wxSpinCtrl (_panel);
table->Add (_server_encoding_threads, wxGBPosition (r, 1));
++r;
@@ -143,8 +147,8 @@ private:
add_update_controls (table, r);
- _default_add_file_location->add(_("Same place as last time"));
- _default_add_file_location->add(_("Same place as project"));
+ _default_add_file_location->add_entry(_("Same place as last time"));
+ _default_add_file_location->add_entry(_("Same place as project"));
_default_add_file_location->bind(&FullGeneralPage::default_add_file_location_changed, this);
_config_file->Bind (wxEVT_FILEPICKER_CHANGED, boost::bind(&FullGeneralPage::config_file_changed, this));
@@ -316,10 +320,19 @@ private:
table->Add (_dcp_audio_channels);
{
- add_label_to_sizer (table, _panel, _("Default JPEG2000 bandwidth"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
+ add_label_to_sizer(table, _panel, _("Default JPEG2000 bit rate"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
auto s = new wxBoxSizer (wxHORIZONTAL);
- _j2k_bandwidth = new wxSpinCtrl (_panel);
- s->Add (_j2k_bandwidth);
+ _j2k_video_bit_rate = new wxSpinCtrl(_panel);
+ s->Add(_j2k_video_bit_rate);
+ add_label_to_sizer (s, _panel, _("Mbit/s"), false, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
+ table->Add (s, 1);
+ }
+
+ {
+ add_label_to_sizer(table, _panel, _("Default MPEG2 bit rate"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
+ auto s = new wxBoxSizer (wxHORIZONTAL);
+ _mpeg2_video_bit_rate = new wxSpinCtrl(_panel);
+ s->Add(_mpeg2_video_bit_rate);
add_label_to_sizer (s, _panel, _("Mbit/s"), false, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
table->Add (s, 1);
}
@@ -406,8 +419,10 @@ private:
_dcp_content_type->Bind (wxEVT_CHOICE, boost::bind (&DefaultsPage::dcp_content_type_changed, this));
_dcp_audio_channels->Bind (wxEVT_CHOICE, boost::bind (&DefaultsPage::dcp_audio_channels_changed, this));
- _j2k_bandwidth->SetRange (50, 250);
- _j2k_bandwidth->Bind (wxEVT_SPINCTRL, boost::bind (&DefaultsPage::j2k_bandwidth_changed, this));
+ _j2k_video_bit_rate->SetRange(50, 250);
+ _j2k_video_bit_rate->Bind(wxEVT_SPINCTRL, boost::bind(&DefaultsPage::j2k_video_bit_rate_changed, this));
+ _mpeg2_video_bit_rate->SetRange(1, 50);
+ _mpeg2_video_bit_rate->Bind(wxEVT_SPINCTRL, boost::bind(&DefaultsPage::mpeg2_video_bit_rate_changed, this));
_audio_delay->SetRange (-1000, 1000);
_audio_delay->Bind (wxEVT_SPINCTRL, boost::bind (&DefaultsPage::audio_delay_changed, this));
@@ -447,8 +462,10 @@ private:
_kdm_directory->SetPath (std_to_wx (config->default_kdm_directory_or (wx_to_std (wxStandardPaths::Get().GetDocumentsDir())).string ()));
_kdm_type->set (config->default_kdm_type());
checked_set (_use_isdcf_name_by_default, config->use_isdcf_name_by_default());
- checked_set (_j2k_bandwidth, config->default_j2k_bandwidth() / 1000000);
- _j2k_bandwidth->SetRange (50, config->maximum_j2k_bandwidth() / 1000000);
+ checked_set(_j2k_video_bit_rate, config->default_video_bit_rate(VideoEncoding::JPEG2000) / 1000000);
+ _j2k_video_bit_rate->SetRange(50, config->maximum_video_bit_rate(VideoEncoding::JPEG2000) / 1000000);
+ checked_set(_mpeg2_video_bit_rate, config->default_video_bit_rate(VideoEncoding::MPEG2) / 1000000);
+ _mpeg2_video_bit_rate->SetRange(1, config->maximum_video_bit_rate(VideoEncoding::MPEG2) / 1000000);
checked_set (_dcp_audio_channels, locale_convert<string> (config->default_dcp_audio_channels()));
checked_set (_audio_delay, config->default_audio_delay ());
checked_set (_standard, config->default_interop() ? 1 : 0);
@@ -523,9 +540,14 @@ private:
config->set_default_kdm_duration (RoughDuration(duration, unit));
}
- void j2k_bandwidth_changed ()
+ void j2k_video_bit_rate_changed()
{
- Config::instance()->set_default_j2k_bandwidth (_j2k_bandwidth->GetValue() * 1000000);
+ Config::instance()->set_default_video_bit_rate(VideoEncoding::JPEG2000, _j2k_video_bit_rate->GetValue() * 1000000);
+ }
+
+ void mpeg2_video_bit_rate_changed()
+ {
+ Config::instance()->set_default_video_bit_rate(VideoEncoding::MPEG2, _mpeg2_video_bit_rate->GetValue() * 1000000);
}
void audio_delay_changed ()
@@ -630,7 +652,8 @@ private:
}
}
- wxSpinCtrl* _j2k_bandwidth;
+ wxSpinCtrl* _j2k_video_bit_rate;
+ wxSpinCtrl* _mpeg2_video_bit_rate;
wxSpinCtrl* _audio_delay;
wxSpinCtrl* _still_length;
#ifdef DCPOMATIC_USE_OWN_PICKER
@@ -983,8 +1006,8 @@ private:
Email email(
wx_to_std(dialog.from()),
{ wx_to_std(dialog.to()) },
- wx_to_std(_("DCP-o-matic test email")),
- wx_to_std(_("This is a test email from DCP-o-matic."))
+ wx_to_std(variant::wx::insert_dcpomatic(_("%s test email"))),
+ wx_to_std(variant::wx::insert_dcpomatic(_("This is a test email from %s.")))
);
auto config = Config::instance();
try {
@@ -1404,32 +1427,50 @@ private:
add_label_to_sizer (table, _panel, _("Issuer"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
_issuer = new wxTextCtrl (_panel, wxID_ANY);
- _issuer->SetToolTip (_("This will be written to the DCP's XML files as the <Issuer>. If it is blank, a default value mentioning DCP-o-matic will be used."));
+ _issuer->SetToolTip(
+ variant::wx::insert_dcpomatic(
+ _("This will be written to the DCP's XML files as the <Issuer>. If it is blank, a default value mentioning %s will be used.")
+ ));
table->Add (_issuer, 1, wxALL | wxEXPAND);
add_label_to_sizer (table, _panel, _("Creator"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
_creator = new wxTextCtrl (_panel, wxID_ANY);
- _creator->SetToolTip (_("This will be written to the DCP's XML files as the <Creator>. If it is blank, a default value mentioning DCP-o-matic will be used."));
+ _creator->SetToolTip(
+ variant::wx::insert_dcpomatic(
+ _("This will be written to the DCP's XML files as the <Creator>. If it is blank, a default value mentioning %s will be used.")
+ ));
table->Add (_creator, 1, wxALL | wxEXPAND);
add_label_to_sizer (table, _panel, _("Company name"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
_company_name = new wxTextCtrl (_panel, wxID_ANY);
- _company_name->SetToolTip (_("This will be written to the DCP's MXF files as the 'company name'. If it is blank, a default value mentioning libdcp (an internal DCP-o-matic library) will be used."));
+ _company_name->SetToolTip(
+ variant::wx::insert_dcpomatic(
+ _("This will be written to the DCP's MXF files as the 'company name'. If it is blank, a default value mentioning libdcp (an internal %s library) will be used.")
+ ));
table->Add (_company_name, 1, wxALL | wxEXPAND);
add_label_to_sizer (table, _panel, _("Product name"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
_product_name = new wxTextCtrl (_panel, wxID_ANY);
- _product_name->SetToolTip (_("This will be written to the DCP's MXF files as the 'product name'. If it is blank, a default value mentioning libdcp (an internal DCP-o-matic library) will be used."));
+ _product_name->SetToolTip(
+ variant::wx::insert_dcpomatic(
+ _("This will be written to the DCP's MXF files as the 'product name'. If it is blank, a default value mentioning libdcp (an internal %s library) will be used.")
+ ));
table->Add (_product_name, 1, wxALL | wxEXPAND);
add_label_to_sizer (table, _panel, _("Product version"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
_product_version = new wxTextCtrl (_panel, wxID_ANY);
- _product_version->SetToolTip (_("This will be written to the DCP's MXF files as the 'product version'. If it is blank, a default value mentioning libdcp (an internal DCP-o-matic library) will be used."));
+ _product_version->SetToolTip(
+ variant::wx::insert_dcpomatic(
+ _("This will be written to the DCP's MXF files as the 'product version'. If it is blank, a default value mentioning libdcp (an internal %s library) will be used.")
+ ));
table->Add (_product_version, 1, wxALL | wxEXPAND);
add_label_to_sizer (table, _panel, _("JPEG2000 comment"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
_j2k_comment = new wxTextCtrl (_panel, wxID_ANY);
- _j2k_comment->SetToolTip (_("This will be written to the DCP's JPEG2000 data as a comment. If it is blank, a default value mentioning libdcp (an internal DCP-o-matic library) will be used."));
+ _j2k_comment->SetToolTip(
+ variant::wx::insert_dcpomatic(
+ _("This will be written to the DCP's JPEG2000 data as a comment. If it is blank, a default value mentioning libdcp (an internal %s library) will be used.")
+ ));
table->Add (_j2k_comment, 1, wxALL | wxEXPAND);
_panel->GetSizer()->Add (table, 0, wxEXPAND | wxALL, _border);
@@ -1519,10 +1560,19 @@ private:
_panel->GetSizer()->Add(table, 1, wxALL | wxEXPAND, _border);
{
- add_label_to_sizer(table, _panel, _("Maximum JPEG2000 bandwidth"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
+ add_label_to_sizer(table, _panel, _("Maximum JPEG2000 bit rate"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
+ auto s = new wxBoxSizer(wxHORIZONTAL);
+ _maximum_j2k_video_bit_rate = new wxSpinCtrl(_panel);
+ s->Add(_maximum_j2k_video_bit_rate, 1);
+ add_label_to_sizer(s, _panel, _("Mbit/s"), false, 0, wxLEFT | wxALIGN_CENTRE_VERTICAL);
+ table->Add(s, 1);
+ }
+
+ {
+ add_label_to_sizer(table, _panel, _("Maximum MPEG2 bit rate"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
auto s = new wxBoxSizer(wxHORIZONTAL);
- _maximum_j2k_bandwidth = new wxSpinCtrl(_panel);
- s->Add(_maximum_j2k_bandwidth, 1);
+ _maximum_mpeg2_video_bit_rate = new wxSpinCtrl(_panel);
+ s->Add(_maximum_mpeg2_video_bit_rate, 1);
add_label_to_sizer(s, _panel, _("Mbit/s"), false, 0, wxLEFT | wxALIGN_CENTRE_VERTICAL);
table->Add(s, 1);
}
@@ -1537,13 +1587,7 @@ private:
_allow_any_container = new CheckBox(_panel, _("Allow full-frame and non-standard container ratios"));
table->Add(_allow_any_container, 1, wxEXPAND | wxLEFT, DCPOMATIC_SIZER_GAP);
- auto restart = new StaticText(_panel, _("(restart DCP-o-matic to see all ratios)"));
- auto font = restart->GetFont();
- font.SetStyle(wxFONTSTYLE_ITALIC);
- font.SetPointSize(font.GetPointSize() - 1);
- restart->SetFont(font);
- table->Add(restart, 1, wxALIGN_CENTRE_VERTICAL | wxBOTTOM, DCPOMATIC_CHECKBOX_BOTTOM_PAD);
- restart->SetFont(font);
+ table->AddSpacer(0);
checkbox(_("Allow creation of DCPs with 96kHz audio"), _allow_96khz_audio);
checkbox(_("Allow mapping to all audio channels"), _use_all_audio_channels);
@@ -1557,8 +1601,10 @@ private:
table->Add(s, 1);
}
- _maximum_j2k_bandwidth->SetRange(1, 1000);
- _maximum_j2k_bandwidth->Bind(wxEVT_SPINCTRL, boost::bind(&NonStandardPage::maximum_j2k_bandwidth_changed, this));
+ _maximum_j2k_video_bit_rate->SetRange(1, 1000);
+ _maximum_j2k_video_bit_rate->Bind(wxEVT_SPINCTRL, boost::bind(&NonStandardPage::maximum_j2k_video_bit_rate_changed, this));
+ _maximum_mpeg2_video_bit_rate->SetRange(1, 100);
+ _maximum_mpeg2_video_bit_rate->Bind(wxEVT_SPINCTRL, boost::bind(&NonStandardPage::maximum_mpeg2_video_bit_rate_changed, this));
_allow_any_dcp_frame_rate->bind(&NonStandardPage::allow_any_dcp_frame_rate_changed, this);
_allow_any_container->bind(&NonStandardPage::allow_any_container_changed, this);
_allow_96khz_audio->bind(&NonStandardPage::allow_96khz_audio_changed, this);
@@ -1572,7 +1618,8 @@ private:
{
auto config = Config::instance();
- checked_set(_maximum_j2k_bandwidth, config->maximum_j2k_bandwidth() / 1000000);
+ checked_set(_maximum_j2k_video_bit_rate, config->maximum_video_bit_rate(VideoEncoding::JPEG2000) / 1000000);
+ checked_set(_maximum_mpeg2_video_bit_rate, config->maximum_video_bit_rate(VideoEncoding::MPEG2) / 1000000);
checked_set(_allow_any_dcp_frame_rate, config->allow_any_dcp_frame_rate());
checked_set(_allow_any_container, config->allow_any_container());
checked_set(_allow_96khz_audio, config->allow_96khz_audio());
@@ -1581,9 +1628,14 @@ private:
checked_set(_isdcf_name_part_length, config->isdcf_name_part_length());
}
- void maximum_j2k_bandwidth_changed()
+ void maximum_j2k_video_bit_rate_changed()
{
- Config::instance()->set_maximum_j2k_bandwidth(_maximum_j2k_bandwidth->GetValue() * 1000000);
+ Config::instance()->set_maximum_video_bit_rate(VideoEncoding::JPEG2000, _maximum_j2k_video_bit_rate->GetValue() * 1000000);
+ }
+
+ void maximum_mpeg2_video_bit_rate_changed()
+ {
+ Config::instance()->set_maximum_video_bit_rate(VideoEncoding::MPEG2, _maximum_mpeg2_video_bit_rate->GetValue() * 1000000);
}
void allow_any_dcp_frame_rate_changed()
@@ -1616,7 +1668,8 @@ private:
Config::instance()->set_isdcf_name_part_length(_isdcf_name_part_length->GetValue());
}
- wxSpinCtrl* _maximum_j2k_bandwidth = nullptr;
+ wxSpinCtrl* _maximum_j2k_video_bit_rate = nullptr;
+ wxSpinCtrl* _maximum_mpeg2_video_bit_rate = nullptr;
CheckBox* _allow_any_dcp_frame_rate = nullptr;
CheckBox* _allow_any_container = nullptr;
CheckBox* _allow_96khz_audio = nullptr;
@@ -1671,7 +1724,7 @@ private:
_video_display_mode = new wxChoice (_panel, wxID_ANY);
table->Add (_video_display_mode);
- auto restart = add_label_to_sizer (table, _panel, _("(restart DCP-o-matic to change display mode)"), false);
+ auto restart = add_label_to_sizer(table, _panel, variant::wx::insert_dcpomatic(_("(restart %s to change display mode)")), false);
auto font = restart->GetFont();
font.SetStyle (wxFONTSTYLE_ITALIC);
font.SetPointSize (font.GetPointSize() - 1);
@@ -1946,6 +1999,9 @@ create_full_config_dialog ()
e->AddPage (new SoundPage (ps, border));
e->AddPage (new DefaultsPage (ps, border));
e->AddPage (new EncodingServersPage(ps, border));
+#ifdef DCPOMATIC_GROK
+ e->AddPage (new GPUPage (ps, border));
+#endif
e->AddPage (new KeysPage (ps, border));
e->AddPage (new TMSPage (ps, border));
e->AddPage (new EmailPage (ps, border));
diff --git a/src/wx/grok/gpu_config_panel.h b/src/wx/grok/gpu_config_panel.h
new file mode 100644
index 000000000..cbf037592
--- /dev/null
+++ b/src/wx/grok/gpu_config_panel.h
@@ -0,0 +1,227 @@
+/*
+ Copyright (C) 2023 Grok Image Compression Inc.
+
+ 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/>.
+
+*/
+
+
+#pragma once
+
+static std::vector<std::string> get_gpu_names(boost::filesystem::path binary, boost::filesystem::path filename)
+{
+ // Execute the GPU listing program and redirect its output to a file
+ if (std::system((binary.string() + " > " + filename.string()).c_str()) < 0) {
+ return {};
+ }
+
+ std::vector<std::string> gpu_names;
+ std::ifstream file(filename.c_str());
+ if (file.is_open())
+ {
+ std::string line;
+ while (std::getline(file, line))
+ gpu_names.push_back(line);
+ file.close();
+ }
+
+ return gpu_names;
+}
+
+
+class GpuList : public wxPanel
+{
+public:
+ GpuList(wxPanel* parent)
+ : wxPanel(parent, wxID_ANY)
+ {
+ _combo_box = new wxComboBox(this, wxID_ANY, "", wxDefaultPosition, wxSize(400, -1));
+ _combo_box->Bind(wxEVT_COMBOBOX, &GpuList::OnComboBox, this);
+ update();
+
+ auto sizer = new wxBoxSizer(wxHORIZONTAL);
+ sizer->Add(_combo_box, 0, wxALIGN_CENTER_VERTICAL);
+ SetSizerAndFit(sizer);
+ }
+
+ void update()
+ {
+ auto grok = Config::instance()->grok().get_value_or({});
+ auto lister_binary = grok.binary_location / "gpu_lister";
+ auto lister_file = grok.binary_location / "gpus.txt";
+ if (boost::filesystem::exists(lister_binary)) {
+ auto gpu_names = get_gpu_names(lister_binary, lister_file);
+
+ _combo_box->Clear();
+ for (auto const& name: gpu_names) {
+ _combo_box->Append(name);
+ }
+ }
+ }
+
+ void set_selection(int sel)
+ {
+ if (sel < static_cast<int>(_combo_box->GetCount())) {
+ _combo_box->SetSelection(sel);
+ }
+ }
+
+private:
+ void OnComboBox(wxCommandEvent&)
+ {
+ auto selection = _combo_box->GetSelection();
+ if (selection != wxNOT_FOUND) {
+ auto grok = Config::instance()->grok().get_value_or({});
+ grok.selected = selection;
+ Config::instance()->set_grok(grok);
+ }
+ }
+
+ wxComboBox* _combo_box;
+ int _selection = 0;
+};
+
+
+class GPUPage : public Page
+{
+public:
+ GPUPage(wxSize panel_size, int border)
+ : Page(panel_size, border)
+ {}
+
+ wxString GetName() const override
+ {
+ return _("GPU");
+ }
+
+#ifdef DCPOMATIC_OSX
+ /* XXX: this icon does not exist */
+ wxBitmap GetLargeIcon() const override
+ {
+ return wxBitmap(icon_path("gpu"), wxBITMAP_TYPE_PNG);
+ }
+#endif
+
+private:
+ void setup() override
+ {
+ _enable_gpu = new CheckBox(_panel, _("Enable GPU acceleration"));
+ _panel->GetSizer()->Add(_enable_gpu, 0, wxALL | wxEXPAND, _border);
+
+ wxFlexGridSizer* table = new wxFlexGridSizer(2, DCPOMATIC_SIZER_X_GAP, DCPOMATIC_SIZER_Y_GAP);
+ table->AddGrowableCol(1, 1);
+ _panel->GetSizer()->Add(table, 1, wxALL | wxEXPAND, _border);
+
+ add_label_to_sizer(table, _panel, _("Acceleration binary folder"), true, 0, wxLEFT | wxLEFT | wxALIGN_CENTRE_VERTICAL);
+ _binary_location = new wxDirPickerCtrl(_panel, wxDD_DIR_MUST_EXIST);
+ table->Add(_binary_location, 1, wxEXPAND);
+
+ add_label_to_sizer(table, _panel, _("GPU selection"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
+ _gpu_list_control = new GpuList(_panel);
+ table->Add(_gpu_list_control, 1, wxEXPAND);
+
+ add_label_to_sizer(table, _panel, _("License server"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
+ _server = new wxTextCtrl(_panel, wxID_ANY);
+ table->Add(_server, 1, wxEXPAND | wxALL);
+
+ add_label_to_sizer(table, _panel, _("Port"), false, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
+ _port = new wxSpinCtrl(_panel, wxID_ANY);
+ _port->SetRange(0, 65535);
+ table->Add(_port);
+
+ add_label_to_sizer(table, _panel, _("License"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
+ _licence = new PasswordEntry(_panel);
+ table->Add(_licence->get_panel(), 1, wxEXPAND | wxALL);
+
+ _enable_gpu->bind(&GPUPage::enable_gpu_changed, this);
+ _binary_location->Bind(wxEVT_DIRPICKER_CHANGED, boost::bind (&GPUPage::binary_location_changed, this));
+ _server->Bind(wxEVT_TEXT, boost::bind(&GPUPage::server_changed, this));
+ _port->Bind(wxEVT_SPINCTRL, boost::bind(&GPUPage::port_changed, this));
+ _licence->Changed.connect(boost::bind(&GPUPage::licence_changed, this));
+
+ setup_sensitivity();
+ }
+
+ void setup_sensitivity()
+ {
+ auto grok = Config::instance()->grok().get_value_or({});
+
+ _binary_location->Enable(grok.enable);
+ _gpu_list_control->Enable(grok.enable);
+ _server->Enable(grok.enable);
+ _port->Enable(grok.enable);
+ _licence->get_panel()->Enable(grok.enable);
+ }
+
+ void config_changed() override
+ {
+ auto grok = Config::instance()->grok().get_value_or({});
+
+ checked_set(_enable_gpu, grok.enable);
+ _binary_location->SetPath(std_to_wx(grok.binary_location.string()));
+ _gpu_list_control->update();
+ _gpu_list_control->set_selection(grok.selected);
+ checked_set(_server, grok.licence_server);
+ checked_set(_port, grok.licence_port);
+ checked_set(_licence, grok.licence);
+ }
+
+ void enable_gpu_changed()
+ {
+ auto grok = Config::instance()->grok().get_value_or({});
+ grok.enable = _enable_gpu->GetValue();
+ Config::instance()->set_grok(grok);
+
+ setup_sensitivity();
+ }
+
+ void binary_location_changed()
+ {
+ auto grok = Config::instance()->grok().get_value_or({});
+ grok.binary_location = wx_to_std(_binary_location->GetPath());
+ Config::instance()->set_grok(grok);
+
+ _gpu_list_control->update();
+ }
+
+ void server_changed()
+ {
+ auto grok = Config::instance()->grok().get_value_or({});
+ grok.licence_server = wx_to_std(_server->GetValue());
+ Config::instance()->set_grok(grok);
+ }
+
+ void port_changed()
+ {
+ auto grok = Config::instance()->grok().get_value_or({});
+ grok.licence_port = _port->GetValue();
+ Config::instance()->set_grok(grok);
+ }
+
+ void licence_changed()
+ {
+ auto grok = Config::instance()->grok().get_value_or({});
+ grok.licence = wx_to_std(_licence->get());
+ Config::instance()->set_grok(grok);
+ }
+
+ CheckBox* _enable_gpu = nullptr;
+ wxDirPickerCtrl* _binary_location = nullptr;
+ GpuList* _gpu_list_control = nullptr;
+ wxTextCtrl* _server = nullptr;
+ wxSpinCtrl* _port = nullptr;
+ PasswordEntry* _licence = nullptr;
+};
diff --git a/src/wx/id.h b/src/wx/id.h
index 99aa25855..4839a0868 100644
--- a/src/wx/id.h
+++ b/src/wx/id.h
@@ -24,4 +24,5 @@
#define DCPOMATIC_CPL_MENU (wxID_HIGHEST + 1000)
#define DCPOMATIC_CONTENT_MENU (wxID_HIGHEST + 1500)
#define DCPOMATIC_AUDIO_GAIN_MENU (wxID_HIGHEST + 2000)
+#define DCPOMATIC_DCP_TIMELINE_MENU (wxID_HIGHEST + 2500)
diff --git a/src/wx/job_view.cc b/src/wx/job_view.cc
index efe17f4de..d5b6c4632 100644
--- a/src/wx/job_view.cc
+++ b/src/wx/job_view.cc
@@ -25,6 +25,7 @@
#include "message_dialog.h"
#include "static_text.h"
#include "wx_util.h"
+#include "wx_variant.h"
#include "lib/analyse_audio_job.h"
#include "lib/compose.hpp"
#include "lib/config.h"
@@ -165,7 +166,7 @@ JobView::finished ()
if (_job->enable_notify() && _notify->GetValue()) {
if (Config::instance()->notification(Config::MESSAGE_BOX)) {
- wxMessageBox (std_to_wx(_job->name() + ": " + _job->status()), _("DCP-o-matic"), wxICON_INFORMATION);
+ wxMessageBox(std_to_wx(_job->name() + ": " + _job->status()), variant::wx::dcpomatic(), wxICON_INFORMATION);
}
if (Config::instance()->notification(Config::EMAIL)) {
string body = Config::instance()->notification_email();
diff --git a/src/wx/kdm_cpl_panel.cc b/src/wx/kdm_cpl_panel.cc
index 4699582aa..523d0c369 100644
--- a/src/wx/kdm_cpl_panel.cc
+++ b/src/wx/kdm_cpl_panel.cc
@@ -23,6 +23,7 @@
#include "kdm_cpl_panel.h"
#include "static_text.h"
#include "wx_util.h"
+#include <dcp/filesystem.h>
#include <dcp/warnings.h>
LIBDCP_DISABLE_WARNINGS
#include <libxml++/libxml++.h>
@@ -41,11 +42,11 @@ KDMCPLPanel::KDMCPLPanel (wxWindow* parent, vector<CPLSummary> cpls)
/* CPL choice */
auto s = new wxBoxSizer (wxHORIZONTAL);
- add_label_to_sizer (s, this, _("CPL"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTER_VERTICAL);
+ add_label_to_sizer (s, this, _("CPL"), true, 0, wxLEFT | wxRIGHT | wxALIGN_CENTRE_VERTICAL);
_cpl = new wxChoice (this, wxID_ANY);
- s->Add (_cpl, 1, wxEXPAND);
+ s->Add (_cpl, 1, wxTOP | wxEXPAND, DCPOMATIC_CHOICE_TOP_PAD);
_cpl_browse = new Button (this, _("Browse..."));
- s->Add (_cpl_browse, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, DCPOMATIC_SIZER_X_GAP);
+ s->Add (_cpl_browse, 0, wxLEFT, DCPOMATIC_SIZER_X_GAP);
vertical->Add (s, 0, wxEXPAND | wxTOP, DCPOMATIC_SIZER_GAP + 2);
/* CPL details */
diff --git a/src/wx/kdm_dialog.cc b/src/wx/kdm_dialog.cc
index 5ab13b4ce..1a9c564d4 100644
--- a/src/wx/kdm_dialog.cc
+++ b/src/wx/kdm_dialog.cc
@@ -29,6 +29,7 @@
#include "screens_panel.h"
#include "static_text.h"
#include "wx_util.h"
+#include "wx_variant.h"
#include "lib/cinema.h"
#include "lib/config.h"
#include "lib/film.h"
@@ -125,7 +126,7 @@ KDMDialog::KDMDialog (wxWindow* parent, shared_ptr<const Film> film)
/* Bind */
- _screens->ScreensChanged.connect (boost::bind (&KDMDialog::setup_sensitivity, this));
+ _screens->ScreensChanged.connect(boost::bind(&KDMDialog::screens_changed, this));
_timing->TimingChanged.connect (boost::bind (&KDMDialog::setup_sensitivity, this));
_make->Bind (wxEVT_BUTTON, boost::bind (&KDMDialog::make_clicked, this));
_cpl->Changed.connect(boost::bind(&KDMDialog::cpl_changed, this));
@@ -140,6 +141,14 @@ KDMDialog::KDMDialog (wxWindow* parent, shared_ptr<const Film> film)
void
+KDMDialog::screens_changed()
+{
+ _timing->suggest_utc_offset(_screens->best_utc_offset());
+ setup_sensitivity();
+}
+
+
+void
KDMDialog::cpl_changed()
{
try {
@@ -196,8 +205,21 @@ KDMDialog::make_clicked ()
return film->make_kdm(_cpl->cpl(), begin, end);
};
- for (auto i: _screens->screens()) {
- auto p = kdm_for_screen(make_kdm, i, _timing->from(), _timing->until(), _output->formulation(), !_output->forensic_mark_video(), for_audio, period_checks);
+ CinemaList cinemas;
+
+ for (auto screen: _screens->screens()) {
+ auto p = kdm_for_screen(
+ make_kdm,
+ screen.first,
+ *cinemas.cinema(screen.first),
+ *cinemas.screen(screen.second),
+ _timing->from(),
+ _timing->until(),
+ _output->formulation(),
+ !_output->forensic_mark_video(),
+ for_audio,
+ period_checks
+ );
if (p) {
kdms.push_back (p);
}
@@ -219,7 +241,13 @@ KDMDialog::make_clicked ()
if (e.starts_too_early()) {
error_dialog (this, _("The KDM start period is before (or close to) the start of the signing certificate's validity period. Use a later start time for this KDM."));
} else {
- error_dialog (this, _("The KDM end period is after (or close to) the end of the signing certificates' validity period. Either use an earlier end time for this KDM or re-create your signing certificates in the DCP-o-matic preferences window."));
+ error_dialog(
+ this,
+ variant::wx::insert_dcpomatic(
+ _("The KDM end period is after (or close to) the end of the signing certificates' validity "
+ "period. Either use an earlier end time for this KDM or re-create your signing certificates "
+ "in the %s preferences window."))
+ );
}
return;
} catch (runtime_error& e) {
diff --git a/src/wx/kdm_dialog.h b/src/wx/kdm_dialog.h
index c1e9588fe..ec92db616 100644
--- a/src/wx/kdm_dialog.h
+++ b/src/wx/kdm_dialog.h
@@ -52,6 +52,7 @@ private:
void make_clicked ();
bool confirm_overwrite (boost::filesystem::path path);
void cpl_changed();
+ void screens_changed();
std::weak_ptr<const Film> _film;
ScreensPanel* _screens;
diff --git a/src/wx/kdm_timing_panel.cc b/src/wx/kdm_timing_panel.cc
index 0fd00de93..c892c9d48 100644
--- a/src/wx/kdm_timing_panel.cc
+++ b/src/wx/kdm_timing_panel.cc
@@ -19,11 +19,13 @@
*/
+#include "dcpomatic_choice.h"
#include "kdm_timing_panel.h"
#include "static_text.h"
#include "time_picker.h"
#include "wx_util.h"
#include "lib/config.h"
+#include <dcp/utc_offset.h>
#include <dcp/warnings.h>
LIBDCP_DISABLE_WARNINGS
#include <wx/datectrl.h>
@@ -105,6 +107,10 @@ KDMTimingPanel::KDMTimingPanel (wxWindow* parent)
table->Add (_until_time, 0, wxALIGN_CENTER_VERTICAL);
+ add_label_to_sizer(table, this, _("UTC offset (time zone)"), true, 1, wxALIGN_CENTRE_VERTICAL);
+ _utc_offset = new Choice(this);
+ table->Add(_utc_offset, 0, wxALIGN_CENTRE_VERTICAL | wxLEFT, DCPOMATIC_SIZER_X_GAP);
+
overall_sizer->Add (table, 0, wxTOP, DCPOMATIC_SIZER_GAP);
_warning = new StaticText (this, wxT(""));
@@ -115,6 +121,13 @@ KDMTimingPanel::KDMTimingPanel (wxWindow* parent)
_warning->SetForegroundColour (wxColour (255, 0, 0));
_warning->SetFont(font);
+ /* Default to UTC */
+ auto const sel = get_offsets(_offsets);
+ for (auto const& offset: _offsets) {
+ _utc_offset->add_entry(offset.name);
+ }
+ _utc_offset->set(sel);
+
/* I said I've been to the year 3000. Not much has changed but they live underwater. And your In-in-in-interop DCP
is pretty fine.
*/
@@ -125,33 +138,38 @@ KDMTimingPanel::KDMTimingPanel (wxWindow* parent)
_until_date->Bind (wxEVT_DATE_CHANGED, bind (&KDMTimingPanel::changed, this));
_from_time->Changed.connect (bind (&KDMTimingPanel::changed, this));
_until_time->Changed.connect (bind (&KDMTimingPanel::changed, this));
+ _utc_offset->bind(&KDMTimingPanel::utc_offset_changed, this);
SetSizer (overall_sizer);
}
-boost::posix_time::ptime
+dcp::LocalTime
KDMTimingPanel::from () const
{
- return posix_time (_from_date, _from_time);
+ return local_time(_from_date, _from_time, utc_offset());
}
-boost::posix_time::ptime
-KDMTimingPanel::posix_time (wxDatePickerCtrl* date_picker, TimePicker* time_picker)
+dcp::LocalTime
+KDMTimingPanel::local_time(wxDatePickerCtrl* date_picker, TimePicker* time_picker, dcp::UTCOffset offset)
{
auto const date = date_picker->GetValue ();
- return boost::posix_time::ptime (
- boost::gregorian::date (date.GetYear(), date.GetMonth() + 1, date.GetDay()),
- boost::posix_time::time_duration (time_picker->hours(), time_picker->minutes(), 0)
+ return dcp::LocalTime(
+ date.GetYear(),
+ date.GetMonth() + 1,
+ date.GetDay(),
+ time_picker->hours(),
+ time_picker->minutes(),
+ offset
);
}
-boost::posix_time::ptime
+dcp::LocalTime
KDMTimingPanel::until () const
{
- return posix_time (_until_date, _until_time);
+ return local_time(_until_date, _until_time, utc_offset());
}
@@ -173,3 +191,38 @@ KDMTimingPanel::changed () const
TimingChanged ();
}
+
+
+dcp::UTCOffset
+KDMTimingPanel::utc_offset() const
+{
+ auto const sel = _utc_offset->get();
+ if (!sel || *sel >= int(_offsets.size())) {
+ return {};
+ }
+
+ return _offsets[*sel].offset;
+}
+
+
+void
+KDMTimingPanel::utc_offset_changed()
+{
+ _utc_offset_changed_once = true;
+ changed();
+}
+
+
+void
+KDMTimingPanel::suggest_utc_offset(dcp::UTCOffset offset)
+{
+ if (!_utc_offset_changed_once) {
+ for (size_t i = 0; i < _offsets.size(); ++i) {
+ if (_offsets[i].offset == offset) {
+ _utc_offset->set(i);
+ break;
+ }
+ }
+ }
+}
+
diff --git a/src/wx/kdm_timing_panel.h b/src/wx/kdm_timing_panel.h
index 7221ba722..a6199534a 100644
--- a/src/wx/kdm_timing_panel.h
+++ b/src/wx/kdm_timing_panel.h
@@ -18,6 +18,9 @@
*/
+
+#include "wx_util.h"
+#include <dcp/utc_offset.h>
#include <dcp/warnings.h>
LIBDCP_DISABLE_WARNINGS
#include <wx/wx.h>
@@ -25,30 +28,42 @@ LIBDCP_ENABLE_WARNINGS
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/signals2.hpp>
-class wxDatePickerCtrl;
+
+class Choice;
class TimePicker;
+class wxDatePickerCtrl;
+
class KDMTimingPanel : public wxPanel
{
public:
explicit KDMTimingPanel (wxWindow* parent);
- /** @return KDM from time in local time */
- boost::posix_time::ptime from () const;
- /** @return KDM until time in local time */
- boost::posix_time::ptime until () const;
+ dcp::LocalTime from() const;
+ dcp::LocalTime until() const;
bool valid () const;
+ /** Give a UTC offset from a cinema that the user just selected. If the user
+ * never changed the UTC offset in the panel, the suggested UTC will be set.
+ */
+ void suggest_utc_offset(dcp::UTCOffset offset);
+
boost::signals2::signal<void ()> TimingChanged;
private:
void changed () const;
- static boost::posix_time::ptime posix_time (wxDatePickerCtrl *, TimePicker *);
+ void utc_offset_changed();
+ dcp::UTCOffset utc_offset() const;
+
+ static dcp::LocalTime local_time(wxDatePickerCtrl *, TimePicker *, dcp::UTCOffset offset);
wxDatePickerCtrl* _from_date;
wxDatePickerCtrl* _until_date;
TimePicker* _from_time;
TimePicker* _until_time;
+ Choice* _utc_offset;
+ bool _utc_offset_changed_once = false;
wxStaticText* _warning;
+ std::vector<Offset> _offsets;
};
diff --git a/src/wx/load_config_from_zip_dialog.cc b/src/wx/load_config_from_zip_dialog.cc
new file mode 100644
index 000000000..a7d573ded
--- /dev/null
+++ b/src/wx/load_config_from_zip_dialog.cc
@@ -0,0 +1,59 @@
+/*
+ Copyright (C) 2024 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 "load_config_from_zip_dialog.h"
+#include "wx_util.h"
+#include "lib/config.h"
+#include "lib/unzipper.h"
+
+
+LoadConfigFromZIPDialog::LoadConfigFromZIPDialog(wxWindow* parent, boost::filesystem::path zip_file)
+ : TableDialog(parent, _("Load configuration from ZIP file"), 1, 0, true)
+{
+ _use_current = add(new wxRadioButton(this, wxID_ANY, _("Copy the cinemas in the ZIP file over the current list at")));
+ auto current_path = add(new wxStaticText(this, wxID_ANY, std_to_wx(Config::instance()->cinemas_file().string())));
+ auto current_path_font = current_path->GetFont();
+ current_path_font.SetFamily(wxFONTFAMILY_TELETYPE);
+ current_path->SetFont(current_path_font);
+
+ _use_zip = add(new wxRadioButton(this, wxID_ANY, _("Copy the cinemas in the ZIP file to the original location at")));
+ auto zip_path = add(new wxStaticText(this, wxID_ANY, std_to_wx(Config::cinemas_file_from_zip(zip_file).string())));
+ auto zip_path_font = zip_path->GetFont();
+ zip_path_font.SetFamily(wxFONTFAMILY_TELETYPE);
+ zip_path->SetFont(zip_path_font);
+
+ _ignore = add(new wxRadioButton(this, wxID_ANY, _("Do not use the cinemas in the ZIP file")));
+
+ layout();
+}
+
+
+Config::CinemasAction
+LoadConfigFromZIPDialog::action() const
+{
+ if (_use_current->GetValue()) {
+ return Config::CinemasAction::WRITE_TO_CURRENT_PATH;
+ } else if (_use_zip->GetValue()) {
+ return Config::CinemasAction::WRITE_TO_PATH_IN_ZIPPED_CONFIG;
+ }
+
+ return Config::CinemasAction::IGNORE;
+}
diff --git a/src/wx/load_config_from_zip_dialog.h b/src/wx/load_config_from_zip_dialog.h
new file mode 100644
index 000000000..f5f4ec6ea
--- /dev/null
+++ b/src/wx/load_config_from_zip_dialog.h
@@ -0,0 +1,41 @@
+/*
+ Copyright (C) 2024 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 "table_dialog.h"
+#include "lib/config.h"
+#include <boost/filesystem.hpp>
+
+
+class LoadConfigFromZIPDialog : public TableDialog
+{
+public:
+ LoadConfigFromZIPDialog(wxWindow* parent, boost::filesystem::path zip_file);
+
+ Config::CinemasAction action() const;
+
+private:
+ wxRadioButton* _use_current;
+ wxRadioButton* _use_zip;
+ wxRadioButton* _ignore;
+};
+
+
+
diff --git a/src/wx/metadata_dialog.cc b/src/wx/metadata_dialog.cc
index 347f2fffd..1d0758175 100644
--- a/src/wx/metadata_dialog.cc
+++ b/src/wx/metadata_dialog.cc
@@ -204,9 +204,9 @@ MetadataDialog::setup_standard (wxPanel* panel, wxSizer* sizer)
{
add_label_to_sizer(sizer, panel, _("Territory type"), true, 0, wxALIGN_CENTER_VERTICAL);
_territory_type = new Choice(panel);
- _territory_type->add(_("Specific"), wx_to_std(territory_type_to_string(TerritoryType::SPECIFIC)));
- _territory_type->add(_("International texted"), wx_to_std(territory_type_to_string(TerritoryType::INTERNATIONAL_TEXTED)));
- _territory_type->add(_("International textless"), wx_to_std(territory_type_to_string(TerritoryType::INTERNATIONAL_TEXTLESS)));
+ _territory_type->add_entry(_("Specific"), wx_to_std(territory_type_to_string(TerritoryType::SPECIFIC)));
+ _territory_type->add_entry(_("International texted"), wx_to_std(territory_type_to_string(TerritoryType::INTERNATIONAL_TEXTED)));
+ _territory_type->add_entry(_("International textless"), wx_to_std(territory_type_to_string(TerritoryType::INTERNATIONAL_TEXTLESS)));
sizer->Add(_territory_type);
_enable_release_territory = new CheckBox(panel, _("Release territory"));
@@ -330,8 +330,8 @@ MetadataDialog::setup_advanced (wxPanel* panel, wxSizer* sizer)
sizer->Add (s, 1, wxEXPAND);
}
- _luminance_unit->add(_("candela per m²"));
- _luminance_unit->add(_("foot lambert"));
+ _luminance_unit->add_entry(_("candela per m²"));
+ _luminance_unit->add_entry(_("foot lambert"));
}
diff --git a/src/wx/player_config_dialog.cc b/src/wx/player_config_dialog.cc
index 58874e50b..d7bba4dcc 100644
--- a/src/wx/player_config_dialog.cc
+++ b/src/wx/player_config_dialog.cc
@@ -37,6 +37,7 @@
#include "server_dialog.h"
#include "static_text.h"
#include "wx_util.h"
+#include "wx_variant.h"
#include "lib/config.h"
#include "lib/cross.h"
#include "lib/dcp_content_type.h"
@@ -114,7 +115,7 @@ private:
table->Add (_video_display_mode, wxGBPosition(r, 1));
++r;
- wxStaticText* restart = add_label_to_sizer (table, _panel, _("(restart DCP-o-matic to change display mode)"), false, wxGBPosition(r, 0));
+ wxStaticText* restart = add_label_to_sizer(table, _panel, variant::wx::insert_dcpomatic_player(_("(restart %s to change display mode)")), false, wxGBPosition(r, 0));
wxFont font = restart->GetFont();
font.SetStyle (wxFONTSTYLE_ITALIC);
font.SetPointSize (font.GetPointSize() - 1);
@@ -353,7 +354,7 @@ private:
wxPreferencesEditor*
create_player_config_dialog ()
{
- auto e = new wxPreferencesEditor (_("DCP-o-matic Player Preferences"));
+ auto e = new wxPreferencesEditor(variant::wx::insert_dcpomatic_player(_("%s Preferences")));
#ifdef DCPOMATIC_OSX
/* Width that we force some of the config panels to be on OSX so that
diff --git a/src/wx/playlist_controls.cc b/src/wx/playlist_controls.cc
index 3a1bba362..f51bcc4e2 100644
--- a/src/wx/playlist_controls.cc
+++ b/src/wx/playlist_controls.cc
@@ -25,6 +25,7 @@
#include "playlist_controls.h"
#include "static_text.h"
#include "wx_util.h"
+#include "wx_variant.h"
#include "lib/compose.hpp"
#include "lib/constants.h"
#include "lib/cross.h"
@@ -331,7 +332,7 @@ PlaylistControls::spl_selection_changed ()
void
PlaylistControls::select_playlist (int selected, int position)
{
- wxProgressDialog dialog (_("DCP-o-matic"), "Loading playlist and KDMs");
+ wxProgressDialog dialog(variant::wx::dcpomatic(), "Loading playlist and KDMs");
for (auto const& i: _playlists[selected].get()) {
dialog.Pulse ();
@@ -403,7 +404,7 @@ PlaylistControls::update_current_content ()
{
DCPOMATIC_ASSERT (_selected_playlist);
- wxProgressDialog dialog (_("DCP-o-matic"), "Loading content");
+ wxProgressDialog dialog(variant::wx::dcpomatic(), "Loading content");
setup_sensitivity ();
dialog.Pulse ();
diff --git a/src/wx/playlist_editor_config_dialog.cc b/src/wx/playlist_editor_config_dialog.cc
index d5c4106ea..67bedc05e 100644
--- a/src/wx/playlist_editor_config_dialog.cc
+++ b/src/wx/playlist_editor_config_dialog.cc
@@ -18,8 +18,11 @@
*/
-#include "playlist_editor_config_dialog.h"
+
#include "config_dialog.h"
+#include "playlist_editor_config_dialog.h"
+#include "wx_variant.h"
+
/** @file src/playlist_editor_config_dialog.cc
* @brief A dialogue to edit DCP-o-matic Playlist Editor configuration.
@@ -28,7 +31,7 @@
wxPreferencesEditor*
create_playlist_editor_config_dialog ()
{
- auto e = new wxPreferencesEditor (_("DCP-o-matic Playlist Editor Preferences"));
+ auto e = new wxPreferencesEditor(variant::wx::insert_dcpomatic_playlist_editor(_("%s Preferences")));
#ifdef DCPOMATIC_OSX
/* Width that we force some of the config panels to be on OSX so that
diff --git a/src/wx/recipient_dialog.cc b/src/wx/recipient_dialog.cc
index a69adb169..992c88362 100644
--- a/src/wx/recipient_dialog.cc
+++ b/src/wx/recipient_dialog.cc
@@ -55,7 +55,7 @@ column (string s)
RecipientDialog::RecipientDialog (
- wxWindow* parent, wxString title, string name, string notes, vector<string> emails, int utc_offset_hour, int utc_offset_minute, optional<dcp::Certificate> recipient
+ wxWindow* parent, wxString title, string name, string notes, vector<string> emails, optional<dcp::Certificate> recipient
)
: wxDialog (parent, wxID_ANY, title)
, _recipient (recipient)
@@ -76,11 +76,6 @@ RecipientDialog::RecipientDialog (
_sizer->Add (_notes, wxGBPosition (r, 1));
++r;
- add_label_to_sizer (_sizer, this, _("UTC offset (time zone)"), true, wxGBPosition (r, 0));
- _utc_offset = new wxChoice (this, wxID_ANY);
- _sizer->Add (_utc_offset, wxGBPosition (r, 1));
- ++r;
-
add_label_to_sizer (_sizer, this, _("Email addresses for KDM delivery"), false, wxGBPosition (r, 0), wxGBSpan (1, 2));
++r;
@@ -125,17 +120,6 @@ RecipientDialog::RecipientDialog (
overall_sizer->Add (buttons, wxSizerFlags().Expand().DoubleBorder());
}
- /* Default to UTC */
- size_t sel = get_offsets (_offsets);
- for (size_t i = 0; i < _offsets.size(); ++i) {
- _utc_offset->Append (_offsets[i].name);
- if (_offsets[i].hour == utc_offset_hour && _offsets[i].minute == utc_offset_minute) {
- sel = i;
- }
- }
-
- _utc_offset->SetSelection (sel);
-
overall_sizer->Layout ();
overall_sizer->SetSizeHints (this);
@@ -234,28 +218,3 @@ RecipientDialog::emails () const
{
return _emails;
}
-
-
-int
-RecipientDialog::utc_offset_hour () const
-{
- int const sel = _utc_offset->GetSelection();
- if (sel < 0 || sel > int (_offsets.size())) {
- return 0;
- }
-
- return _offsets[sel].hour;
-}
-
-int
-RecipientDialog::utc_offset_minute () const
-{
- int const sel = _utc_offset->GetSelection();
- if (sel < 0 || sel > int (_offsets.size())) {
- return 0;
- }
-
- return _offsets[sel].minute;
-}
-
-
diff --git a/src/wx/recipient_dialog.h b/src/wx/recipient_dialog.h
index a46e67af5..61f6b3031 100644
--- a/src/wx/recipient_dialog.h
+++ b/src/wx/recipient_dialog.h
@@ -44,8 +44,6 @@ public:
std::string name = "",
std::string notes = "",
std::vector<std::string> emails = std::vector<std::string>(),
- int utc_offset_hour = 0,
- int utc_offset_minute = 0,
boost::optional<dcp::Certificate> c = boost::optional<dcp::Certificate>()
);
@@ -53,8 +51,6 @@ public:
std::string notes () const;
boost::optional<dcp::Certificate> recipient () const;
std::vector<std::string> emails () const;
- int utc_offset_hour () const;
- int utc_offset_minute () const;
private:
void get_recipient_from_file ();
@@ -71,8 +67,6 @@ private:
wxButton* _get_recipient_from_file;
EditableList<std::string, EmailDialog>* _email_list;
std::vector<std::string> _emails;
- wxChoice* _utc_offset;
- std::vector<Offset> _offsets;
boost::optional<dcp::Certificate> _recipient;
};
diff --git a/src/wx/recipients_panel.cc b/src/wx/recipients_panel.cc
index 485a0f94e..9596c3cfd 100644
--- a/src/wx/recipients_panel.cc
+++ b/src/wx/recipients_panel.cc
@@ -23,7 +23,7 @@
#include "wx_util.h"
#include "recipient_dialog.h"
#include "dcpomatic_button.h"
-#include "lib/config.h"
+#include "lib/dkdm_recipient_list.h"
#include <list>
#include <iostream>
@@ -103,15 +103,15 @@ RecipientsPanel::setup_sensitivity ()
void
-RecipientsPanel::add_recipient (shared_ptr<DKDMRecipient> r)
+RecipientsPanel::add_recipient(DKDMRecipientID id, DKDMRecipient const& recipient)
{
string const search = wx_to_std(_search->GetValue());
- if (!search.empty() && !_collator.find(search, r->name)) {
+ if (!search.empty() && !_collator.find(search, recipient.name)) {
return;
}
- _recipients[_targets->AppendItem(_root, std_to_wx(r->name))] = r;
+ _recipients.emplace(make_pair(_targets->AppendItem(_root, std_to_wx(recipient.name)), id));
_targets->SortChildren (_root);
}
@@ -122,9 +122,10 @@ RecipientsPanel::add_recipient_clicked ()
{
RecipientDialog dialog(GetParent(), _("Add recipient"));
if (dialog.ShowModal() == wxID_OK) {
- auto r = std::make_shared<DKDMRecipient>(dialog.name(), dialog.notes(), dialog.recipient(), dialog.emails(), dialog.utc_offset_hour(), dialog.utc_offset_minute());
- Config::instance()->add_dkdm_recipient (r);
- add_recipient (r);
+ auto recipient = DKDMRecipient(dialog.name(), dialog.notes(), dialog.recipient(), dialog.emails());
+ DKDMRecipientList recipient_list;
+ auto const id = recipient_list.add_dkdm_recipient(recipient);
+ add_recipient(id, recipient);
}
}
@@ -136,20 +137,28 @@ RecipientsPanel::edit_recipient_clicked ()
return;
}
- auto c = *_selected.begin();
+ DKDMRecipientList recipients;
+ auto selection = *_selected.begin();
+ auto const recipient_id = selection.second;
+ auto recipient = recipients.dkdm_recipient(recipient_id);
+ DCPOMATIC_ASSERT(recipient);
RecipientDialog dialog(
- GetParent(), _("Edit recipient"), c.second->name, c.second->notes, c.second->emails, c.second->utc_offset_hour, c.second->utc_offset_minute, c.second->recipient
+ GetParent(),
+ _("Edit recipient"),
+ recipient->name,
+ recipient->notes,
+ recipient->emails,
+ recipient->recipient
);
if (dialog.ShowModal() == wxID_OK) {
- c.second->name = dialog.name();
- c.second->emails = dialog.emails();
- c.second->notes = dialog.notes();
- c.second->utc_offset_hour = dialog.utc_offset_hour();
- c.second->utc_offset_minute = dialog.utc_offset_minute();
- _targets->SetItemText(c.first, std_to_wx(dialog.name()));
- Config::instance()->changed (Config::DKDM_RECIPIENTS);
+ recipient->name = dialog.name();
+ recipient->emails = dialog.emails();
+ recipient->notes = dialog.notes();
+ recipient->recipient = dialog.recipient();
+ recipients.update_dkdm_recipient(recipient_id, *recipient);
+ _targets->SetItemText(selection.first, std_to_wx(dialog.name()));
}
}
@@ -158,7 +167,8 @@ void
RecipientsPanel::remove_recipient_clicked ()
{
for (auto const& i: _selected) {
- Config::instance()->remove_dkdm_recipient (i.second);
+ DKDMRecipientList recipient_list;
+ recipient_list.remove_dkdm_recipient(i.second);
_targets->Delete (i.first);
}
@@ -166,19 +176,15 @@ RecipientsPanel::remove_recipient_clicked ()
}
-list<shared_ptr<DKDMRecipient>>
+list<DKDMRecipient>
RecipientsPanel::recipients () const
{
- list<shared_ptr<DKDMRecipient>> r;
-
- for (auto const& i: _selected) {
- r.push_back (i.second);
+ list<DKDMRecipient> all;
+ DKDMRecipientList recipients;
+ for (auto const& recipient: recipients.dkdm_recipients()) {
+ all.push_back(recipient.second);
}
-
- r.sort ();
- r.unique ();
-
- return r;
+ return all;
}
@@ -204,7 +210,7 @@ RecipientsPanel::selection_changed ()
for (size_t i = 0; i < s.GetCount(); ++i) {
RecipientMap::const_iterator j = _recipients.find (s[i]);
if (j != _recipients.end ()) {
- _selected[j->first] = j->second;
+ _selected.emplace(*j);
}
}
@@ -218,8 +224,9 @@ RecipientsPanel::add_recipients ()
{
_root = _targets->AddRoot ("Foo");
- for (auto i: Config::instance()->dkdm_recipients()) {
- add_recipient (i);
+ DKDMRecipientList recipients;
+ for (auto const& recipient: recipients.dkdm_recipients()) {
+ add_recipient(recipient.first, recipient.second);
}
}
diff --git a/src/wx/recipients_panel.h b/src/wx/recipients_panel.h
index 6e1f1408f..d252b8d06 100644
--- a/src/wx/recipients_panel.h
+++ b/src/wx/recipients_panel.h
@@ -21,6 +21,7 @@
#include "lib/collator.h"
#include "lib/dkdm_recipient.h"
+#include "lib/dkdm_recipient_list.h"
#include <dcp/warnings.h>
LIBDCP_DISABLE_WARNINGS
#include <wx/srchctrl.h>
@@ -43,12 +44,12 @@ public:
void setup_sensitivity ();
- std::list<std::shared_ptr<DKDMRecipient>> recipients () const;
+ std::list<DKDMRecipient> recipients() const;
boost::signals2::signal<void ()> RecipientsChanged;
private:
void add_recipients ();
- void add_recipient (std::shared_ptr<DKDMRecipient>);
+ void add_recipient(DKDMRecipientID id, DKDMRecipient const& recipient);
void add_recipient_clicked ();
void edit_recipient_clicked ();
void remove_recipient_clicked ();
@@ -63,7 +64,7 @@ private:
wxButton* _remove_recipient;
wxTreeItemId _root;
- typedef std::map<wxTreeItemId, std::shared_ptr<DKDMRecipient>> RecipientMap;
+ typedef std::map<wxTreeItemId, DKDMRecipientID> RecipientMap;
RecipientMap _recipients;
RecipientMap _selected;
diff --git a/src/wx/screens_panel.cc b/src/wx/screens_panel.cc
index d3b1db77d..fdc4dacc3 100644
--- a/src/wx/screens_panel.cc
+++ b/src/wx/screens_panel.cc
@@ -25,6 +25,7 @@
#include "screen_dialog.h"
#include "screens_panel.h"
#include "wx_util.h"
+#include "wx_variant.h"
#include "lib/cinema.h"
#include "lib/config.h"
#include "lib/screen.h"
@@ -125,8 +126,6 @@ ScreensPanel::ScreensPanel (wxWindow* parent)
_uncheck_all->Bind (wxEVT_BUTTON, boost::bind(&ScreensPanel::uncheck_all, this));
SetSizer(_overall_sizer);
-
- _config_connection = Config::instance()->Changed.connect(boost::bind(&ScreensPanel::config_changed, this, _1));
}
@@ -188,28 +187,37 @@ ScreensPanel::convert_to_lower(string& s)
bool
-ScreensPanel::matches_search(shared_ptr<const Cinema> cinema, string search)
+ScreensPanel::matches_search(Cinema const& cinema, string search)
{
if (search.empty()) {
return true;
}
- return _collator.find(search, cinema->name);
+ return _collator.find(search, cinema.name);
}
+/** Add an existing cinema to the GUI */
optional<wxTreeListItem>
-ScreensPanel::add_cinema (shared_ptr<Cinema> cinema, wxTreeListItem previous)
+ScreensPanel::add_cinema(CinemaID cinema_id, wxTreeListItem previous)
{
+ CinemaList cinemas;
+ auto cinema = cinemas.cinema(cinema_id);
+ DCPOMATIC_ASSERT(cinema);
+
auto const search = wx_to_std(_search->GetValue());
- if (!matches_search(cinema, search)) {
+ if (!matches_search(*cinema, search)) {
return {};
}
+ auto screens = cinemas.screens(cinema_id);
+
if (_show_only_checked->get()) {
- auto screens = cinema->screens();
- auto iter = std::find_if(screens.begin(), screens.end(), [this](shared_ptr<dcpomatic::Screen> screen) {
- return _checked_screens.find(screen) != _checked_screens.end();
+ auto iter = std::find_if(screens.begin(), screens.end(), [this](pair<ScreenID, dcpomatic::Screen> const& screen) {
+ auto iter2 = std::find_if(_checked_screens.begin(), _checked_screens.end(), [screen](pair<CinemaID, ScreenID> const& checked) {
+ return checked.second == screen.first;
+ });
+ return iter2 != _checked_screens.end();
});
if (iter == screens.end()) {
return {};
@@ -218,29 +226,34 @@ ScreensPanel::add_cinema (shared_ptr<Cinema> cinema, wxTreeListItem previous)
auto id = _targets->InsertItem(_targets->GetRootItem(), previous, std_to_wx(cinema->name));
- _item_to_cinema[id] = cinema;
- _cinema_to_item[cinema] = id;
+ _item_to_cinema.emplace(make_pair(id, cinema_id));
+ _cinema_to_item[cinema_id] = id;
- for (auto screen: cinema->screens()) {
- add_screen (cinema, screen);
+ for (auto screen: screens) {
+ add_screen(cinema_id, screen.first);
}
return id;
}
+/** Add an existing screen to the GUI */
optional<wxTreeListItem>
-ScreensPanel::add_screen (shared_ptr<Cinema> cinema, shared_ptr<Screen> screen)
+ScreensPanel::add_screen(CinemaID cinema_id, ScreenID screen_id)
{
- auto item = cinema_to_item(cinema);
+ auto item = cinema_to_item(cinema_id);
if (!item) {
return {};
}
+ CinemaList cinemas;
+ auto screen = cinemas.screen(screen_id);
+ DCPOMATIC_ASSERT(screen);
+
auto id = _targets->AppendItem(*item, std_to_wx(screen->name));
- _item_to_screen[id] = screen;
- _screen_to_item[screen] = id;
+ _item_to_screen.emplace(make_pair(id, make_pair(cinema_id, screen_id)));
+ _screen_to_item[screen_id] = id;
return item;
}
@@ -252,37 +265,31 @@ ScreensPanel::add_cinema_clicked ()
CinemaDialog dialog(GetParent(), _("Add Cinema"));
if (dialog.ShowModal() == wxID_OK) {
- auto cinema = make_shared<Cinema>(dialog.name(), dialog.emails(), dialog.notes(), dialog.utc_offset_hour(), dialog.utc_offset_minute());
+ auto cinema = Cinema(dialog.name(), dialog.emails(), dialog.notes(), dialog.utc_offset());
- auto cinemas = sorted_cinemas();
+ CinemaList cinemas;
+ auto existing_cinemas = cinemas.cinemas();
- try {
- _ignore_cinemas_changed = true;
- dcp::ScopeGuard sg = [this]() { _ignore_cinemas_changed = false; };
- Config::instance()->add_cinema(cinema);
- } catch (FileError& e) {
- error_dialog(GetParent(), _("Could not write cinema details to the cinemas.xml file. Check that the location of cinemas.xml is valid in DCP-o-matic's preferences."), std_to_wx(e.what()));
- return;
- }
+ auto const cinema_id = cinemas.add_cinema(cinema);
wxTreeListItem previous = wxTLI_FIRST;
bool found = false;
auto const search = wx_to_std(_search->GetValue());
- for (auto existing_cinema: cinemas) {
- if (!matches_search(existing_cinema, search)) {
+ for (auto existing_cinema: existing_cinemas) {
+ if (!matches_search(existing_cinema.second, search)) {
continue;
}
- if (_collator.compare(dialog.name(), existing_cinema->name) < 0) {
+ if (_collator.compare(dialog.name(), existing_cinema.second.name) < 0) {
/* existing_cinema should be after the one we're inserting */
found = true;
break;
}
- auto item = cinema_to_item(existing_cinema);
+ auto item = cinema_to_item(existing_cinema.first);
DCPOMATIC_ASSERT(item);
previous = *item;
}
- auto item = add_cinema(cinema, found ? previous : wxTLI_LAST);
+ auto item = add_cinema(cinema_id, found ? previous : wxTLI_LAST);
if (item) {
_targets->UnselectAll ();
@@ -294,13 +301,13 @@ ScreensPanel::add_cinema_clicked ()
}
-shared_ptr<Cinema>
+optional<CinemaID>
ScreensPanel::cinema_for_operation () const
{
if (_selected_cinemas.size() == 1) {
return _selected_cinemas[0];
} else if (_selected_screens.size() == 1) {
- return _selected_screens[0]->cinema;
+ return _selected_screens[0].first;
}
return {};
@@ -310,28 +317,29 @@ ScreensPanel::cinema_for_operation () const
void
ScreensPanel::edit_cinema_clicked ()
{
- auto cinema = cinema_for_operation ();
- if (cinema) {
- edit_cinema(cinema);
+ auto cinema_id = cinema_for_operation();
+ if (cinema_id) {
+ edit_cinema(*cinema_id);
}
}
void
-ScreensPanel::edit_cinema(shared_ptr<Cinema> cinema)
+ScreensPanel::edit_cinema(CinemaID cinema_id)
{
- CinemaDialog dialog(
- GetParent(), _("Edit cinema"), cinema->name, cinema->emails, cinema->notes, cinema->utc_offset_hour(), cinema->utc_offset_minute()
- );
+ CinemaList cinemas;
+ auto cinema = cinemas.cinema(cinema_id);
+ DCPOMATIC_ASSERT(cinema);
+
+ CinemaDialog dialog(GetParent(), _("Edit cinema"), cinema->name, cinema->emails, cinema->notes, cinema->utc_offset);
if (dialog.ShowModal() == wxID_OK) {
- cinema->name = dialog.name();
cinema->emails = dialog.emails();
+ cinema->name = dialog.name();
cinema->notes = dialog.notes();
- cinema->set_utc_offset_hour(dialog.utc_offset_hour());
- cinema->set_utc_offset_minute(dialog.utc_offset_minute());
- notify_cinemas_changed();
- auto item = cinema_to_item(cinema);
+ cinema->utc_offset = dialog.utc_offset();
+ cinemas.update_cinema(cinema_id, *cinema);
+ auto item = cinema_to_item(cinema_id);
DCPOMATIC_ASSERT(item);
_targets->SetItemText (*item, std_to_wx(dialog.name()));
}
@@ -341,8 +349,11 @@ ScreensPanel::edit_cinema(shared_ptr<Cinema> cinema)
void
ScreensPanel::remove_cinema_clicked ()
{
+ CinemaList cinemas;
+
if (_selected_cinemas.size() == 1) {
- if (!confirm_dialog(this, wxString::Format(_("Are you sure you want to remove the cinema '%s'?"), std_to_wx(_selected_cinemas[0]->name)))) {
+ auto cinema = cinemas.cinema(_selected_cinemas[0]);
+ if (!confirm_dialog(this, wxString::Format(_("Are you sure you want to remove the cinema '%s'?"), std_to_wx(cinema->name)))) {
return;
}
} else {
@@ -353,14 +364,12 @@ ScreensPanel::remove_cinema_clicked ()
auto cinemas_to_remove = _selected_cinemas;
- for (auto const& cinema: cinemas_to_remove) {
- _ignore_cinemas_changed = true;
- dcp::ScopeGuard sg = [this]() { _ignore_cinemas_changed = false; };
- for (auto screen: cinema->screens()) {
- _checked_screens.erase(screen);
+ for (auto const& cinema_id: cinemas_to_remove) {
+ for (auto screen: cinemas.screens(cinema_id)) {
+ _checked_screens.erase({cinema_id, screen.first});
}
- Config::instance()->remove_cinema(cinema);
- auto item = cinema_to_item(cinema);
+ cinemas.remove_cinema(cinema_id);
+ auto item = cinema_to_item(cinema_id);
DCPOMATIC_ASSERT(item);
_targets->DeleteItem(*item);
}
@@ -373,8 +382,8 @@ ScreensPanel::remove_cinema_clicked ()
void
ScreensPanel::add_screen_clicked ()
{
- auto cinema = cinema_for_operation ();
- if (!cinema) {
+ auto cinema_id = cinema_for_operation();
+ if (!cinema_id) {
return;
}
@@ -384,8 +393,10 @@ ScreensPanel::add_screen_clicked ()
return;
}
- for (auto screen: cinema->screens()) {
- if (screen->name == dialog.name()) {
+ CinemaList cinemas;
+
+ for (auto screen: cinemas.screens(*cinema_id)) {
+ if (screen.second.name == dialog.name()) {
error_dialog (
GetParent(),
wxString::Format (
@@ -397,11 +408,10 @@ ScreensPanel::add_screen_clicked ()
}
}
- auto screen = std::make_shared<Screen>(dialog.name(), dialog.notes(), dialog.recipient(), dialog.recipient_file(), dialog.trusted_devices());
- cinema->add_screen (screen);
- notify_cinemas_changed();
+ auto screen = Screen(dialog.name(), dialog.notes(), dialog.recipient(), dialog.recipient_file(), dialog.trusted_devices());
+ auto const screen_id = cinemas.add_screen(*cinema_id, screen);
- auto id = add_screen (cinema, screen);
+ auto id = add_screen(*cinema_id, screen_id);
if (id) {
_targets->Expand (id.get ());
}
@@ -412,30 +422,33 @@ void
ScreensPanel::edit_screen_clicked ()
{
if (_selected_screens.size() == 1) {
- edit_screen(_selected_screens[0]);
+ edit_screen(_selected_screens[0].first, _selected_screens[0].second);
}
}
void
-ScreensPanel::edit_screen(shared_ptr<Screen> edit_screen)
+ScreensPanel::edit_screen(CinemaID cinema_id, ScreenID screen_id)
{
+ CinemaList cinemas;
+ auto screen = cinemas.screen(screen_id);
+ DCPOMATIC_ASSERT(screen);
+
ScreenDialog dialog(
GetParent(), _("Edit screen"),
- edit_screen->name,
- edit_screen->notes,
- edit_screen->recipient,
- edit_screen->recipient_file,
- edit_screen->trusted_devices
+ screen->name,
+ screen->notes,
+ screen->recipient,
+ screen->recipient_file,
+ screen->trusted_devices
);
if (dialog.ShowModal() != wxID_OK) {
return;
}
- auto cinema = edit_screen->cinema;
- for (auto screen: cinema->screens()) {
- if (screen != edit_screen && screen->name == dialog.name()) {
+ for (auto screen: cinemas.screens(cinema_id)) {
+ if (screen.first != screen_id && screen.second.name == dialog.name()) {
error_dialog (
GetParent(),
wxString::Format (
@@ -447,14 +460,14 @@ ScreensPanel::edit_screen(shared_ptr<Screen> edit_screen)
}
}
- edit_screen->name = dialog.name();
- edit_screen->notes = dialog.notes();
- edit_screen->recipient = dialog.recipient();
- edit_screen->recipient_file = dialog.recipient_file();
- edit_screen->trusted_devices = dialog.trusted_devices();
- notify_cinemas_changed();
+ screen->name = dialog.name();
+ screen->notes = dialog.notes();
+ screen->recipient = dialog.recipient();
+ screen->recipient_file = dialog.recipient_file();
+ screen->trusted_devices = dialog.trusted_devices();
+ cinemas.update_screen(screen_id, *screen);
- auto item = screen_to_item(edit_screen);
+ auto item = screen_to_item(screen_id);
DCPOMATIC_ASSERT (item);
_targets->SetItemText(*item, std_to_wx(dialog.name()));
}
@@ -463,8 +476,12 @@ ScreensPanel::edit_screen(shared_ptr<Screen> edit_screen)
void
ScreensPanel::remove_screen_clicked ()
{
+ CinemaList cinemas;
+
if (_selected_screens.size() == 1) {
- if (!confirm_dialog(this, wxString::Format(_("Are you sure you want to remove the screen '%s'?"), std_to_wx(_selected_screens[0]->name)))) {
+ auto screen = cinemas.screen(_selected_screens[0].second);
+ DCPOMATIC_ASSERT(screen);
+ if (!confirm_dialog(this, wxString::Format(_("Are you sure you want to remove the screen '%s'?"), std_to_wx(screen->name)))) {
return;
}
} else {
@@ -473,10 +490,10 @@ ScreensPanel::remove_screen_clicked ()
}
}
- for (auto screen: _selected_screens) {
- _checked_screens.erase(screen);
- screen->cinema->remove_screen(screen);
- auto item = screen_to_item(screen);
+ for (auto screen_id: _selected_screens) {
+ _checked_screens.erase(screen_id);
+ cinemas.remove_screen(screen_id.second);
+ auto item = screen_to_item(screen_id.second);
DCPOMATIC_ASSERT(item);
_targets->DeleteItem(*item);
}
@@ -485,17 +502,14 @@ ScreensPanel::remove_screen_clicked ()
* as well.
*/
selection_changed();
- notify_cinemas_changed();
setup_show_only_checked();
}
-vector<shared_ptr<Screen>>
+std::set<pair<CinemaID, ScreenID>>
ScreensPanel::screens () const
{
- vector<shared_ptr<Screen>> output;
- std::copy (_checked_screens.begin(), _checked_screens.end(), std::back_inserter(output));
- return output;
+ return _checked_screens;
}
@@ -521,10 +535,10 @@ ScreensPanel::selection_changed ()
for (size_t i = 0; i < selection.size(); ++i) {
if (auto cinema = item_to_cinema(selection[i])) {
- _selected_cinemas.push_back(cinema);
+ _selected_cinemas.push_back(*cinema);
}
if (auto screen = item_to_screen(selection[i])) {
- _selected_screens.push_back(screen);
+ _selected_screens.push_back(*screen);
}
}
@@ -532,24 +546,12 @@ ScreensPanel::selection_changed ()
}
-list<shared_ptr<Cinema>>
-ScreensPanel::sorted_cinemas() const
-{
- auto cinemas = Config::instance()->cinemas();
-
- cinemas.sort(
- [this](shared_ptr<Cinema> a, shared_ptr<Cinema> b) { return _collator.compare(a->name, b->name) < 0; }
- );
-
- return cinemas;
-}
-
-
void
ScreensPanel::add_cinemas ()
{
- for (auto cinema: sorted_cinemas()) {
- add_cinema (cinema, wxTLI_LAST);
+ CinemaList cinemas;
+ for (auto cinema: cinemas.cinemas()) {
+ add_cinema (cinema.first, wxTLI_LAST);
}
}
@@ -583,7 +585,7 @@ ScreensPanel::display_filter_changed()
}
for (auto const& selection: _selected_screens) {
- if (auto item = screen_to_item(selection)) {
+ if (auto item = screen_to_item(selection.second)) {
_targets->Select (*item);
}
}
@@ -593,7 +595,7 @@ ScreensPanel::display_filter_changed()
_ignore_check_change = true;
for (auto const& checked: _checked_screens) {
- if (auto item = screen_to_item(checked)) {
+ if (auto item = screen_to_item(checked.second)) {
_targets->CheckItem(*item, wxCHK_CHECKED);
setup_cinema_checked_state(*item);
}
@@ -609,9 +611,9 @@ ScreensPanel::set_screen_checked (wxTreeListItem item, bool checked)
auto screen = item_to_screen(item);
DCPOMATIC_ASSERT(screen);
if (checked) {
- _checked_screens.insert(screen);
+ _checked_screens.insert({screen->first, screen->second});
} else {
- _checked_screens.erase(screen);
+ _checked_screens.erase({screen->first, screen->second});
}
setup_show_only_checked();
@@ -665,7 +667,7 @@ ScreensPanel::checkbox_changed (wxTreeListEvent& ev)
}
-shared_ptr<Cinema>
+optional<CinemaID>
ScreensPanel::item_to_cinema (wxTreeListItem item) const
{
auto iter = _item_to_cinema.find (item);
@@ -677,7 +679,7 @@ ScreensPanel::item_to_cinema (wxTreeListItem item) const
}
-shared_ptr<Screen>
+optional<pair<CinemaID, ScreenID>>
ScreensPanel::item_to_screen (wxTreeListItem item) const
{
auto iter = _item_to_screen.find (item);
@@ -690,7 +692,7 @@ ScreensPanel::item_to_screen (wxTreeListItem item) const
optional<wxTreeListItem>
-ScreensPanel::cinema_to_item (shared_ptr<Cinema> cinema) const
+ScreensPanel::cinema_to_item(CinemaID cinema) const
{
auto iter = _cinema_to_item.find (cinema);
if (iter == _cinema_to_item.end()) {
@@ -702,7 +704,7 @@ ScreensPanel::cinema_to_item (shared_ptr<Cinema> cinema) const
optional<wxTreeListItem>
-ScreensPanel::screen_to_item (shared_ptr<Screen> screen) const
+ScreensPanel::screen_to_item(ScreenID screen) const
{
auto iter = _screen_to_item.find (screen);
if (iter == _screen_to_item.end()) {
@@ -713,32 +715,6 @@ ScreensPanel::screen_to_item (shared_ptr<Screen> screen) const
}
-bool
-ScreensPanel::notify_cinemas_changed()
-{
- _ignore_cinemas_changed = true;
- dcp::ScopeGuard sg = [this]() { _ignore_cinemas_changed = false; };
-
- try {
- Config::instance()->changed(Config::CINEMAS);
- } catch (FileError& e) {
- error_dialog(GetParent(), _("Could not write cinema details to the cinemas.xml file. Check that the location of cinemas.xml is valid in DCP-o-matic's preferences."), std_to_wx(e.what()));
- return false;
- }
-
- return true;
-}
-
-
-void
-ScreensPanel::config_changed(Config::Property property)
-{
- if (property == Config::Property::CINEMAS && !_ignore_cinemas_changed) {
- clear_and_re_add();
- }
-}
-
-
void
ScreensPanel::item_activated(wxTreeListEvent& ev)
{
@@ -748,7 +724,7 @@ ScreensPanel::item_activated(wxTreeListEvent& ev)
} else {
auto iter = _item_to_screen.find(ev.GetItem());
if (iter != _item_to_screen.end()) {
- edit_screen(iter->second);
+ edit_screen(iter->second.first, iter->second.second);
}
}
}
@@ -767,3 +743,16 @@ ScreensPanel::setup_show_only_checked()
setup_sensitivity();
}
+
+dcp::UTCOffset
+ScreensPanel::best_utc_offset() const
+{
+ std::set<CinemaID> unique_cinema_ids;
+ for (auto const& screen: screens()) {
+ unique_cinema_ids.insert(screen.first);
+ }
+
+ CinemaList cinema_list;
+ return cinema_list.unique_utc_offset(unique_cinema_ids).get_value_or(dcp::UTCOffset());
+}
+
diff --git a/src/wx/screens_panel.h b/src/wx/screens_panel.h
index 80a7b3843..98ec2c631 100644
--- a/src/wx/screens_panel.h
+++ b/src/wx/screens_panel.h
@@ -19,6 +19,7 @@
*/
+#include "lib/cinema_list.h"
#include "lib/collator.h"
#include "lib/config.h"
#include <dcp/warnings.h>
@@ -38,7 +39,6 @@ namespace dcpomatic {
}
-class Cinema;
class CheckBox;
@@ -48,45 +48,44 @@ public:
explicit ScreensPanel (wxWindow* parent);
~ScreensPanel ();
- std::vector<std::shared_ptr<dcpomatic::Screen>> screens () const;
+ std::set<std::pair<CinemaID, ScreenID>> screens() const;
void setup_sensitivity ();
+ dcp::UTCOffset best_utc_offset() const;
+
boost::signals2::signal<void ()> ScreensChanged;
private:
void add_cinemas ();
- boost::optional<wxTreeListItem> add_cinema (std::shared_ptr<Cinema>, wxTreeListItem previous);
- boost::optional<wxTreeListItem> add_screen (std::shared_ptr<Cinema>, std::shared_ptr<dcpomatic::Screen>);
+ boost::optional<wxTreeListItem> add_cinema(CinemaID cinema, wxTreeListItem previous);
+ boost::optional<wxTreeListItem> add_screen(CinemaID cinema, ScreenID screen);
void add_cinema_clicked ();
void edit_cinema_clicked ();
- void edit_cinema(std::shared_ptr<Cinema> cinema);
+ void edit_cinema(CinemaID cinema_id);
void remove_cinema_clicked ();
void add_screen_clicked ();
void edit_screen_clicked ();
- void edit_screen(std::shared_ptr<dcpomatic::Screen> screen);
+ void edit_screen(CinemaID cinema_id, ScreenID screen_id);
void remove_screen_clicked ();
void selection_changed_shim (wxTreeListEvent &);
void selection_changed ();
void display_filter_changed();
void checkbox_changed (wxTreeListEvent& ev);
void item_activated(wxTreeListEvent& ev);
- std::shared_ptr<Cinema> cinema_for_operation () const;
+ boost::optional<CinemaID> cinema_for_operation() const;
void set_screen_checked (wxTreeListItem item, bool checked);
void setup_cinema_checked_state (wxTreeListItem screen);
void check_all ();
void uncheck_all ();
- bool notify_cinemas_changed();
void clear_and_re_add();
- void config_changed(Config::Property);
void convert_to_lower(std::string& s);
- bool matches_search(std::shared_ptr<const Cinema> cinema, std::string search);
- std::list<std::shared_ptr<Cinema>> sorted_cinemas() const;
+ bool matches_search(Cinema const& cinema, std::string search);
void setup_show_only_checked();
- std::shared_ptr<Cinema> item_to_cinema (wxTreeListItem item) const;
- std::shared_ptr<dcpomatic::Screen> item_to_screen (wxTreeListItem item) const;
- boost::optional<wxTreeListItem> cinema_to_item (std::shared_ptr<Cinema> cinema) const;
- boost::optional<wxTreeListItem> screen_to_item (std::shared_ptr<dcpomatic::Screen> screen) const;
+ boost::optional<CinemaID> item_to_cinema(wxTreeListItem item) const;
+ boost::optional<std::pair<CinemaID, ScreenID>> item_to_screen(wxTreeListItem item) const;
+ boost::optional<wxTreeListItem> cinema_to_item(CinemaID cinema) const;
+ boost::optional<wxTreeListItem> screen_to_item(ScreenID screen) const;
wxBoxSizer* _overall_sizer;
wxSearchCtrl* _search;
@@ -104,24 +103,19 @@ private:
/* We want to be able to search (and so remove selected things from the view)
* but not deselect them, so we maintain lists of selected cinemas and screens.
*/
- std::vector<std::shared_ptr<Cinema>> _selected_cinemas;
- std::vector<std::shared_ptr<dcpomatic::Screen>> _selected_screens;
- /* Likewise with checked screens, except that we can work out which cinemas
- * are checked from which screens are checked, so we don't need to store the
- * cinemas.
- */
- std::set<std::shared_ptr<dcpomatic::Screen>> _checked_screens;
+ std::vector<CinemaID> _selected_cinemas;
+ /* List of cinema_id, screen_id */
+ std::vector<std::pair<CinemaID, ScreenID>> _selected_screens;
+ /* Likewise with checked screens */
+ std::set<std::pair<CinemaID, ScreenID>> _checked_screens;
- std::map<wxTreeListItem, std::shared_ptr<Cinema>> _item_to_cinema;
- std::map<wxTreeListItem, std::shared_ptr<dcpomatic::Screen>> _item_to_screen;
- std::map<std::shared_ptr<Cinema>, wxTreeListItem> _cinema_to_item;
- std::map<std::shared_ptr<dcpomatic::Screen>, wxTreeListItem> _screen_to_item;
+ std::map<wxTreeListItem, CinemaID> _item_to_cinema;
+ std::map<wxTreeListItem, std::pair<CinemaID, ScreenID>> _item_to_screen;
+ std::map<CinemaID, wxTreeListItem> _cinema_to_item;
+ std::map<ScreenID, wxTreeListItem> _screen_to_item;
bool _ignore_selection_change = false;
bool _ignore_check_change = false;
Collator _collator;
-
- boost::signals2::scoped_connection _config_connection;
- bool _ignore_cinemas_changed = false;
};
diff --git a/src/wx/system_information_dialog.cc b/src/wx/system_information_dialog.cc
index fbae2e010..26138900a 100644
--- a/src/wx/system_information_dialog.cc
+++ b/src/wx/system_information_dialog.cc
@@ -23,6 +23,7 @@
#include "gl_video_view.h"
#include "system_information_dialog.h"
#include "wx_util.h"
+#include "wx_variant.h"
#ifdef DCPOMATIC_OSX
@@ -48,7 +49,7 @@ SystemInformationDialog::SystemInformationDialog(wxWindow* parent, FilmViewer co
if (!gl) {
add (_("OpenGL version"), true);
- add (_("unknown (OpenGL not enabled in DCP-o-matic)"), false);
+ add(variant::wx::insert_dcpomatic(_("unknown (OpenGL not enabled in %s)")), false);
} else {
auto information = gl->information();
auto add_string = [this, &information](GLenum name, wxString label) {
@@ -79,7 +80,7 @@ SystemInformationDialog::SystemInformationDialog(wxWindow* parent, FilmViewer co
: TableDialog (parent, _("System information"), 2, 1, false)
{
add (_("OpenGL version"), true);
- add (_("OpenGL renderer not supported by this DCP-o-matic version"), false);
+ add(variant::wx::insert_dcpomatic(_("OpenGL renderer not supported by this %s version")), false);
}
#endif
diff --git a/src/wx/text_panel.cc b/src/wx/text_panel.cc
index 78c024565..c3a5706b0 100644
--- a/src/wx/text_panel.cc
+++ b/src/wx/text_panel.cc
@@ -84,14 +84,6 @@ TextPanel::create ()
refer = _("Use this DCP's closed caption as OV and make VF");
}
- _reference = new CheckBox (this, refer);
- _reference_note = new StaticText (this, wxT(""));
- _reference_note->Wrap (200);
- auto font = _reference_note->GetFont();
- font.SetStyle(wxFONTSTYLE_ITALIC);
- font.SetPointSize(font.GetPointSize() - 1);
- _reference_note->SetFont(font);
-
_use = new CheckBox (this, _("Use as"));
_type = new wxChoice (this, wxID_ANY);
_type->Append (_("open subtitles"));
@@ -132,7 +124,6 @@ TextPanel::create ()
_y_scale->SetRange (0, 1000);
_line_spacing->SetRange (0, 1000);
- _reference->bind(&TextPanel::reference_clicked, this);
_use->bind(&TextPanel::use_toggled, this);
_type->Bind (wxEVT_CHOICE, boost::bind (&TextPanel::type_changed, this));
_burn->bind(&TextPanel::burn_toggled, this);
@@ -232,12 +223,6 @@ TextPanel::add_to_grid ()
{
int r = 0;
- auto reference_sizer = new wxBoxSizer (wxVERTICAL);
- reference_sizer->Add (_reference, 0);
- reference_sizer->Add (_reference_note, 0);
- _grid->Add (reference_sizer, wxGBPosition(r, 0), wxGBSpan(1, 4));
- ++r;
-
auto use = new wxBoxSizer (wxHORIZONTAL);
use->Add (_use, 0, wxEXPAND | wxRIGHT, DCPOMATIC_SIZER_GAP);
use->Add (_type, 1, wxEXPAND, 0);
@@ -496,15 +481,6 @@ TextPanel::film_content_changed (int property)
if (_language_type) {
_language_type->SetSelection (text ? (text->language_is_additional() ? 1 : 0) : 0);
}
- } else if (property == DCPContentProperty::REFERENCE_TEXT) {
- if (scs) {
- auto dcp = dynamic_pointer_cast<DCPContent> (scs);
- checked_set (_reference, dcp ? dcp->reference_text(_original_type) : false);
- } else {
- checked_set (_reference, false);
- }
-
- setup_sensitivity ();
} else if (property == DCPContentProperty::TEXTS) {
setup_sensitivity ();
} else if (property == ContentProperty::TRIM_START) {
@@ -593,17 +569,7 @@ TextPanel::setup_sensitivity ()
dcp = dynamic_pointer_cast<DCPContent>(sel.front());
}
- string why_not;
- bool const can_reference = dcp && dcp->can_reference_text (_parent->film(), _original_type, why_not);
- wxString cannot;
- if (why_not.empty()) {
- cannot = _("Cannot reference this DCP's subtitles or captions.");
- } else {
- cannot = _("Cannot reference this DCP's subtitles or captions: ") + std_to_wx(why_not);
- }
- setup_refer_button (_reference, _reference_note, dcp, can_reference, cannot);
-
- bool const reference = _reference->GetValue ();
+ auto const reference = dcp && dcp->reference_text(_original_type);
auto const type = current_type ();
@@ -763,23 +729,6 @@ TextPanel::fonts_dialog_clicked ()
void
-TextPanel::reference_clicked ()
-{
- auto c = _parent->selected ();
- if (c.size() != 1) {
- return;
- }
-
- auto d = dynamic_pointer_cast<DCPContent> (c.front ());
- if (!d) {
- return;
- }
-
- d->set_reference_text (_original_type, _reference->GetValue ());
-}
-
-
-void
TextPanel::appearance_dialog_clicked ()
{
auto c = _parent->selected_text ();
diff --git a/src/wx/text_panel.h b/src/wx/text_panel.h
index 5adad5a3e..a2afba439 100644
--- a/src/wx/text_panel.h
+++ b/src/wx/text_panel.h
@@ -56,7 +56,6 @@ private:
void stream_changed ();
void text_view_clicked ();
void fonts_dialog_clicked ();
- void reference_clicked ();
void appearance_dialog_clicked ();
void outline_subtitles_changed ();
TextType current_type () const;
@@ -74,8 +73,6 @@ private:
void update_outline_subtitles_in_viewer ();
void clear_outline_subtitles ();
- CheckBox* _reference;
- wxStaticText* _reference_note;
CheckBox* _outline_subtitles = nullptr;
CheckBox* _use;
wxChoice* _type;
diff --git a/src/wx/timecode.cc b/src/wx/timecode.cc
index 1e6a1930d..64fe87190 100644
--- a/src/wx/timecode.cc
+++ b/src/wx/timecode.cc
@@ -114,7 +114,7 @@ void
TimecodeBase::changed ()
{
if (_set_button && !_ignore_changed) {
- _set_button->Enable (true);
+ _set_button->Enable(valid());
}
}
diff --git a/src/wx/timecode.h b/src/wx/timecode.h
index 6c5d8ae23..22899ddc9 100644
--- a/src/wx/timecode.h
+++ b/src/wx/timecode.h
@@ -50,6 +50,7 @@ public:
protected:
void changed ();
void set_clicked ();
+ virtual bool valid() const = 0;
wxSizer* _sizer;
wxPanel* _editable;
@@ -96,6 +97,11 @@ public:
_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) {
@@ -116,6 +122,13 @@ public:
{
return T(get(), fps);
}
+
+private:
+ bool valid() const override {
+ return !_maximum || get() <= *_maximum;
+ }
+
+ boost::optional<dcpomatic::HMSF> _maximum;
};
#endif
diff --git a/src/wx/timeline.cc b/src/wx/timeline.cc
index 38e9de4ee..329f4ef00 100644
--- a/src/wx/timeline.cc
+++ b/src/wx/timeline.cc
@@ -1,5 +1,5 @@
/*
- 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.
@@ -18,1012 +18,25 @@
*/
-#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;
- }
- }
-}
-
diff --git a/src/wx/timeline.h b/src/wx/timeline.h
index 621609fa7..cc35913b9 100644
--- a/src/wx/timeline.h
+++ b/src/wx/timeline.h
@@ -1,5 +1,5 @@
/*
- 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.
@@ -19,137 +19,32 @@
*/
-#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
diff --git a/src/wx/timeline_content_view.cc b/src/wx/timeline_content_view.cc
index 5d039d0d3..69a675c42 100644
--- a/src/wx/timeline_content_view.cc
+++ b/src/wx/timeline_content_view.cc
@@ -19,10 +19,11 @@
*/
-#include "timeline.h"
+#include "content_timeline.h"
#include "timeline_content_view.h"
#include "wx_util.h"
#include "lib/content.h"
+#include "lib/util.h"
#include <dcp/warnings.h>
LIBDCP_DISABLE_WARNINGS
#include <wx/graphics.h>
@@ -37,8 +38,8 @@ using namespace boost::placeholders;
#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));
diff --git a/src/wx/timeline_content_view.h b/src/wx/timeline_content_view.h
index 7794120cd..7b206d30f 100644
--- a/src/wx/timeline_content_view.h
+++ b/src/wx/timeline_content_view.h
@@ -23,7 +23,7 @@
#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
@@ -37,10 +37,10 @@ class Content;
/** @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;
diff --git a/src/wx/timeline_labels_view.cc b/src/wx/timeline_labels_view.cc
index 181adc5ca..c869d7ec5 100644
--- a/src/wx/timeline_labels_view.cc
+++ b/src/wx/timeline_labels_view.cc
@@ -19,7 +19,7 @@
*/
-#include "timeline.h"
+#include "content_timeline.h"
#include "timeline_labels_view.h"
#include "wx_util.h"
#include <dcp/warnings.h>
@@ -34,8 +34,8 @@ using std::max;
using std::min;
-TimelineLabelsView::TimelineLabelsView (Timeline& tl)
- : TimelineView (tl)
+TimelineLabelsView::TimelineLabelsView(ContentTimeline& tl)
+ : ContentTimelineView(tl)
{
wxString labels[] = {
_("Video"),
diff --git a/src/wx/timeline_labels_view.h b/src/wx/timeline_labels_view.h
index fb80b1bf3..324531c39 100644
--- a/src/wx/timeline_labels_view.h
+++ b/src/wx/timeline_labels_view.h
@@ -19,16 +19,16 @@
*/
-#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;
diff --git a/src/wx/timeline_reels_view.cc b/src/wx/timeline_reels_view.cc
index 0601a1196..5f2a78079 100644
--- a/src/wx/timeline_reels_view.cc
+++ b/src/wx/timeline_reels_view.cc
@@ -19,7 +19,7 @@
*/
-#include "timeline.h"
+#include "content_timeline.h"
#include "timeline_reels_view.h"
#include "wx_util.h"
#include "lib/film.h"
@@ -35,8 +35,8 @@ using std::list;
using namespace dcpomatic;
-TimelineReelsView::TimelineReelsView (Timeline& tl, int y)
- : TimelineView (tl)
+TimelineReelsView::TimelineReelsView(ContentTimeline& tl, int y)
+ : ContentTimelineView(tl)
, _y (y)
{
diff --git a/src/wx/timeline_reels_view.h b/src/wx/timeline_reels_view.h
index 357fe2ce4..7dbc34308 100644
--- a/src/wx/timeline_reels_view.h
+++ b/src/wx/timeline_reels_view.h
@@ -19,13 +19,13 @@
*/
-#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);
diff --git a/src/wx/timeline_time_axis_view.cc b/src/wx/timeline_time_axis_view.cc
index d055bda7d..d9b7710c6 100644
--- a/src/wx/timeline_time_axis_view.cc
+++ b/src/wx/timeline_time_axis_view.cc
@@ -19,7 +19,7 @@
*/
-#include "timeline.h"
+#include "content_timeline.h"
#include "timeline_time_axis_view.h"
#include "wx_util.h"
#include <dcp/warnings.h>
@@ -34,8 +34,8 @@ using std::list;
using namespace dcpomatic;
-TimelineTimeAxisView::TimelineTimeAxisView (Timeline& tl, int y)
- : TimelineView (tl)
+TimelineTimeAxisView::TimelineTimeAxisView(ContentTimeline& tl, int y)
+ : ContentTimelineView(tl)
, _y (y)
{
diff --git a/src/wx/timeline_time_axis_view.h b/src/wx/timeline_time_axis_view.h
index 4c8e090fe..f89121776 100644
--- a/src/wx/timeline_time_axis_view.h
+++ b/src/wx/timeline_time_axis_view.h
@@ -18,12 +18,14 @@
*/
-#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);
diff --git a/src/wx/timeline_view.cc b/src/wx/timeline_view.cc
deleted file mode 100644
index 2897c98e3..000000000
--- a/src/wx/timeline_view.cc
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- 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();
-}
-
-
diff --git a/src/wx/timeline_view.h b/src/wx/timeline_view.h
index 166a1121a..32eedde09 100644
--- a/src/wx/timeline_view.h
+++ b/src/wx/timeline_view.h
@@ -1,5 +1,5 @@
/*
- 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.
@@ -28,40 +28,42 @@
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
+
diff --git a/src/wx/try_unmount_dialog.cc b/src/wx/try_unmount_dialog.cc
index ddfaec5c0..37d6e75fa 100644
--- a/src/wx/try_unmount_dialog.cc
+++ b/src/wx/try_unmount_dialog.cc
@@ -22,6 +22,7 @@
#include "static_text.h"
#include "try_unmount_dialog.h"
#include "wx_util.h"
+#include "wx_variant.h"
#include <dcp/warnings.h>
LIBDCP_DISABLE_WARNINGS
#include <wx/wx.h>
@@ -32,14 +33,17 @@ static int constexpr width = 300;
TryUnmountDialog::TryUnmountDialog (wxWindow* parent, wxString description)
- : wxDialog (parent, wxID_ANY, _("DCP-o-matic Disk Writer"))
+ : wxDialog(parent, wxID_ANY, variant::wx::dcpomatic_disk_writer())
{
auto sizer = new wxBoxSizer (wxVERTICAL);
auto text = new StaticText (this, wxEmptyString, wxDefaultPosition, wxSize(width, -1));
sizer->Add (text, 1, wxEXPAND | wxALL, DCPOMATIC_DIALOG_BORDER);
text->SetLabelMarkup (
- wxString::Format(_("The drive <b>%s</b> is mounted.\n\nIt must be unmounted before DCP-o-matic can write to it.\n\nShould DCP-o-matic try to unmount it now?"), description)
+ wxString::Format(
+ _("The drive <b>%s</b> is mounted.\n\nIt must be unmounted before %s can write to it.\n\nShould DCP-o-matic try to unmount it now?"),
+ description, variant::wx::dcpomatic()
+ )
);
text->Wrap(width);
diff --git a/src/wx/update_dialog.cc b/src/wx/update_dialog.cc
index 0a7319704..7c687aa60 100644
--- a/src/wx/update_dialog.cc
+++ b/src/wx/update_dialog.cc
@@ -22,6 +22,7 @@
#include "static_text.h"
#include "update_dialog.h"
#include "wx_util.h"
+#include "wx_variant.h"
#include <dcp/warnings.h>
LIBDCP_DISABLE_WARNINGS
#include <wx/hyperlink.h>
@@ -40,9 +41,9 @@ UpdateDialog::UpdateDialog (wxWindow* parent, optional<string> stable, optional<
wxStaticText* message;
if ((stable || test) && !(stable && test)) {
- message = new StaticText (this, _("A new version of DCP-o-matic is available."));
+ message = new StaticText(this, variant::wx::insert_dcpomatic(_("A new version of %s is available.")));
} else {
- message = new StaticText (this, _("New versions of DCP-o-matic are available."));
+ message = new StaticText(this, variant::wx::insert_dcpomatic(_("New versions of %s are available.")));
}
overall_sizer->Add (message, 0, wxTOP | wxLEFT | wxRIGHT, DCPOMATIC_DIALOG_BORDER);
diff --git a/src/wx/verify_dcp_progress_dialog.cc b/src/wx/verify_dcp_progress_dialog.cc
index 6c941c98d..5e3a7be62 100644
--- a/src/wx/verify_dcp_progress_dialog.cc
+++ b/src/wx/verify_dcp_progress_dialog.cc
@@ -20,6 +20,7 @@
#include "verify_dcp_progress_dialog.h"
+#include "verify_dcp_progress_panel.h"
#include "wx_util.h"
#include "lib/cross.h"
#include "lib/job.h"
@@ -36,36 +37,17 @@ using std::shared_ptr;
using boost::optional;
-static int const max_file_name_length = 80;
-
-
VerifyDCPProgressDialog::VerifyDCPProgressDialog (wxWindow* parent, wxString title)
: wxDialog (parent, wxID_ANY, title)
+ , _panel(new VerifyDCPProgressPanel(this))
, _cancel (false)
{
- wxBoxSizer* overall_sizer = new wxBoxSizer (wxVERTICAL);
-
- _job_name = new wxStaticText (this, wxID_ANY, wxT(""));
- overall_sizer->Add (_job_name, 0, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, DCPOMATIC_SIZER_GAP);
-
- _file_name = new wxStaticText (this, wxID_ANY, wxT(""));
- wxFont file_name_font (*wxNORMAL_FONT);
- file_name_font.SetFamily (wxFONTFAMILY_MODERN);
- file_name_font.SetPointSize (file_name_font.GetPointSize() - 2);
- _file_name->SetFont (file_name_font);
-
- int w;
- int h;
- _file_name->GetTextExtent (std_to_wx(string(max_file_name_length, 'X')), &w, &h);
- _file_name->SetMinSize (wxSize(w, -1));
+ auto overall_sizer = new wxBoxSizer (wxVERTICAL);
- overall_sizer->Add (_file_name, 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, DCPOMATIC_SIZER_GAP);
+ overall_sizer->Add(_panel, 0, wxEXPAND | wxALL, DCPOMATIC_SIZER_GAP);
- _progress = new wxGauge (this, wxID_ANY, 100);
- overall_sizer->Add (_progress, 0, wxEXPAND | wxALL, DCPOMATIC_SIZER_GAP);
-
- wxButton* cancel = new wxButton (this, wxID_ANY, _("Cancel"));
- wxSizer* buttons = new wxBoxSizer (wxHORIZONTAL);
+ auto cancel = new wxButton(this, wxID_ANY, _("Cancel"));
+ auto buttons = new wxBoxSizer(wxHORIZONTAL);
buttons->AddStretchSpacer ();
buttons->Add (cancel, 0);
overall_sizer->Add (buttons, 0, wxEXPAND | wxALL, DCPOMATIC_SIZER_GAP);
@@ -94,26 +76,8 @@ VerifyDCPProgressDialog::run (shared_ptr<Job> job)
while (jm->work_to_do()) {
wxEventLoopBase::GetActive()->YieldFor(wxEVT_CATEGORY_UI | wxEVT_CATEGORY_USER_INPUT);
dcpomatic_sleep_seconds (1);
- optional<float> const progress = job->progress ();
- if (progress) {
- _progress->SetValue (*progress * 100);
- } else {
- _progress->Pulse ();
- }
- string const sub = job->sub_name ();
- size_t colon = sub.find (":");
- if (colon != string::npos) {
- _job_name->SetLabel (std_to_wx(sub.substr(0, colon)));
- string file_name;
- if ((sub.length() - colon - 1) > max_file_name_length) {
- file_name = "..." + sub.substr(sub.length() - max_file_name_length + 3);
- } else {
- file_name = sub.substr(colon + 1);
- }
- _file_name->SetLabel (std_to_wx(file_name));
- } else {
- _job_name->SetLabel (std_to_wx(sub));
- }
+
+ _panel->update(job);
if (_cancel) {
break;
diff --git a/src/wx/verify_dcp_progress_dialog.h b/src/wx/verify_dcp_progress_dialog.h
index 190c302ea..1ae3336e8 100644
--- a/src/wx/verify_dcp_progress_dialog.h
+++ b/src/wx/verify_dcp_progress_dialog.h
@@ -25,10 +25,9 @@ LIBDCP_DISABLE_WARNINGS
LIBDCP_ENABLE_WARNINGS
#include <memory>
-class Job;
-class wxGauge;
-class wxStaticText;
+class Job;
+class VerifyDCPProgressPanel;
class VerifyDCPProgressDialog : public wxDialog
@@ -41,9 +40,7 @@ public:
private:
void cancel ();
- wxStaticText* _job_name;
- wxStaticText* _file_name;
- wxGauge* _progress;
+ VerifyDCPProgressPanel* _panel;
bool _cancel;
};
diff --git a/src/wx/verify_dcp_progress_panel.cc b/src/wx/verify_dcp_progress_panel.cc
new file mode 100644
index 000000000..4c4ce6883
--- /dev/null
+++ b/src/wx/verify_dcp_progress_panel.cc
@@ -0,0 +1,87 @@
+/*
+ Copyright (C) 2020 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/job.h"
+#include "verify_dcp_progress_panel.h"
+#include "wx_util.h"
+
+
+using std::shared_ptr;
+using std::string;
+
+
+auto constexpr max_file_name_length = 80;
+
+
+
+VerifyDCPProgressPanel::VerifyDCPProgressPanel(wxWindow* parent)
+ : wxPanel(parent, wxID_ANY)
+{
+ auto overall_sizer = new wxBoxSizer(wxVERTICAL);
+
+ _job_name = new wxStaticText(this, wxID_ANY, wxT(""));
+ overall_sizer->Add(_job_name, 0, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, DCPOMATIC_SIZER_GAP);
+
+ _file_name = new wxStaticText(this, wxID_ANY, wxT(""));
+ wxFont file_name_font(*wxNORMAL_FONT);
+ file_name_font.SetFamily(wxFONTFAMILY_MODERN);
+ file_name_font.SetPointSize(file_name_font.GetPointSize() - 2);
+ _file_name->SetFont(file_name_font);
+
+ int w;
+ int h;
+ _file_name->GetTextExtent(std_to_wx(string(max_file_name_length, 'X')), &w, &h);
+ _file_name->SetMinSize(wxSize(w, -1));
+
+ overall_sizer->Add(_file_name, 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, DCPOMATIC_SIZER_GAP);
+
+ _progress = new wxGauge(this, wxID_ANY, 100);
+ overall_sizer->Add(_progress, 0, wxEXPAND | wxALL, DCPOMATIC_SIZER_GAP);
+
+ SetSizerAndFit (overall_sizer);
+}
+
+
+void
+VerifyDCPProgressPanel::update(shared_ptr<Job> job)
+{
+ auto const progress = job->progress();
+ if (progress) {
+ _progress->SetValue(*progress * 100);
+ } else {
+ _progress->Pulse();
+ }
+ string const sub = job->sub_name();
+ size_t colon = sub.find(":");
+ if (colon != string::npos) {
+ _job_name->SetLabel(std_to_wx(sub.substr(0, colon)));
+ string file_name;
+ if ((sub.length() - colon - 1) > max_file_name_length) {
+ file_name = "..." + sub.substr(sub.length() - max_file_name_length + 3);
+ } else {
+ file_name = sub.substr(colon + 1);
+ }
+ _file_name->SetLabel(std_to_wx(file_name));
+ } else {
+ _job_name->SetLabel(std_to_wx(sub));
+ }
+}
+
diff --git a/src/wx/verify_dcp_progress_panel.h b/src/wx/verify_dcp_progress_panel.h
new file mode 100644
index 000000000..e39713d37
--- /dev/null
+++ b/src/wx/verify_dcp_progress_panel.h
@@ -0,0 +1,41 @@
+/*
+ Copyright (C) 2020 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 <wx/wx.h>
+#include <memory>
+
+
+class Job;
+
+
+class VerifyDCPProgressPanel : public wxPanel
+{
+public:
+ VerifyDCPProgressPanel(wxWindow* parent);
+
+ void update(std::shared_ptr<Job> job);
+
+private:
+ wxStaticText* _job_name;
+ wxStaticText* _file_name;
+ wxGauge* _progress;
+};
+
diff --git a/src/wx/verify_dcp_result_dialog.cc b/src/wx/verify_dcp_result_dialog.cc
new file mode 100644
index 000000000..806eac85a
--- /dev/null
+++ b/src/wx/verify_dcp_result_dialog.cc
@@ -0,0 +1,47 @@
+/*
+ Copyright (C) 2018-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 "verify_dcp_result_dialog.h"
+#include "verify_dcp_result_panel.h"
+#include "wx_util.h"
+
+
+using std::shared_ptr;
+
+
+VerifyDCPResultDialog::VerifyDCPResultDialog(wxWindow* parent, shared_ptr<VerifyDCPJob> job)
+ : wxDialog (parent, wxID_ANY, _("DCP verification"), wxDefaultPosition, {600, 400})
+{
+ auto sizer = new wxBoxSizer (wxVERTICAL);
+
+ auto panel = new VerifyDCPResultPanel(this);
+ panel->fill(job);
+ sizer->Add(panel, 1, wxEXPAND);
+
+ auto buttons = CreateStdDialogButtonSizer(0);
+ sizer->Add (CreateSeparatedSizer(buttons), wxSizerFlags().Expand().DoubleBorder());
+ buttons->SetAffirmativeButton (new wxButton (this, wxID_OK));
+ buttons->Realize ();
+
+ SetSizer (sizer);
+ sizer->Layout ();
+ sizer->SetSizeHints (this);
+}
diff --git a/src/wx/verify_dcp_dialog.h b/src/wx/verify_dcp_result_dialog.h
index 076217b1f..8953ff8c5 100644
--- a/src/wx/verify_dcp_dialog.h
+++ b/src/wx/verify_dcp_result_dialog.h
@@ -30,8 +30,8 @@ class wxRichTextCtrl;
class VerifyDCPJob;
-class VerifyDCPDialog : public wxDialog
+class VerifyDCPResultDialog : public wxDialog
{
public:
- VerifyDCPDialog (wxWindow* parent, std::shared_ptr<VerifyDCPJob> job);
+ VerifyDCPResultDialog(wxWindow* parent, std::shared_ptr<VerifyDCPJob> job);
};
diff --git a/src/wx/verify_dcp_dialog.cc b/src/wx/verify_dcp_result_panel.cc
index c7a32e5dd..1e7e3395f 100644
--- a/src/wx/verify_dcp_dialog.cc
+++ b/src/wx/verify_dcp_result_panel.cc
@@ -1,5 +1,5 @@
/*
- Copyright (C) 2018-2021 Carl Hetherington <cth@carlh.net>
+ Copyright (C) 2018 Carl Hetherington <cth@carlh.net>
This file is part of DCP-o-matic.
@@ -19,11 +19,14 @@
*/
-#include "verify_dcp_dialog.h"
+#include "dcpomatic_button.h"
+#include "file_dialog.h"
+#include "verify_dcp_result_panel.h"
#include "wx_util.h"
#include "lib/verify_dcp_job.h"
#include <dcp/raw_convert.h>
#include <dcp/verify.h>
+#include <dcp/verify_report.h>
#include <dcp/warnings.h>
LIBDCP_DISABLE_WARNINGS
#include <wx/richtext/richtextctrl.h>
@@ -39,39 +42,51 @@ using std::string;
using std::vector;
-VerifyDCPDialog::VerifyDCPDialog (wxWindow* parent, shared_ptr<VerifyDCPJob> job)
- : wxDialog (parent, wxID_ANY, _("DCP verification"), wxDefaultPosition, {600, 400})
+VerifyDCPResultPanel::VerifyDCPResultPanel(wxWindow* parent)
+ : wxPanel(parent, wxID_ANY)
{
- auto sizer = new wxBoxSizer (wxVERTICAL);
- auto notebook = new wxNotebook (this, wxID_ANY);
- sizer->Add (notebook, 1, wxEXPAND | wxALL, DCPOMATIC_DIALOG_BORDER);
-
- map<dcp::VerificationNote::Type, wxRichTextCtrl*> pages;
- pages[dcp::VerificationNote::Type::ERROR] = new wxRichTextCtrl (notebook, wxID_ANY, wxEmptyString, wxDefaultPosition, {400, 300}, wxRE_READONLY);
- notebook->AddPage (pages[dcp::VerificationNote::Type::ERROR], _("Errors"));
- pages[dcp::VerificationNote::Type::BV21_ERROR] = new wxRichTextCtrl (notebook, wxID_ANY, wxEmptyString, wxDefaultPosition, {400, 300}, wxRE_READONLY);
- notebook->AddPage (pages[dcp::VerificationNote::Type::BV21_ERROR], _("SMPTE Bv2.1 errors"));
- pages[dcp::VerificationNote::Type::WARNING] = new wxRichTextCtrl (notebook, wxID_ANY, wxEmptyString, wxDefaultPosition, {400, 300}, wxRE_READONLY);
- notebook->AddPage (pages[dcp::VerificationNote::Type::WARNING], _("Warnings"));
-
- auto summary = new wxStaticText (this, wxID_ANY, wxT(""));
- sizer->Add (summary, 0, wxALL, DCPOMATIC_DIALOG_BORDER);
-
- auto buttons = CreateStdDialogButtonSizer (0);
- sizer->Add (CreateSeparatedSizer(buttons), wxSizerFlags().Expand().DoubleBorder());
- buttons->SetAffirmativeButton (new wxButton (this, wxID_OK));
- buttons->Realize ();
-
- SetSizer (sizer);
- sizer->Layout ();
- sizer->SetSizeHints (this);
-
- for (auto const& i: pages) {
+ auto sizer = new wxBoxSizer(wxVERTICAL);
+ auto notebook = new wxNotebook(this, wxID_ANY);
+ sizer->Add(notebook, 1, wxEXPAND | wxALL, DCPOMATIC_DIALOG_BORDER);
+
+ _pages[dcp::VerificationNote::Type::ERROR] = new wxRichTextCtrl(notebook, wxID_ANY, wxEmptyString, wxDefaultPosition, {400, 300}, wxRE_READONLY);
+ notebook->AddPage(_pages[dcp::VerificationNote::Type::ERROR], _("Errors"));
+ _pages[dcp::VerificationNote::Type::BV21_ERROR] = new wxRichTextCtrl(notebook, wxID_ANY, wxEmptyString, wxDefaultPosition, {400, 300}, wxRE_READONLY);
+ notebook->AddPage(_pages[dcp::VerificationNote::Type::BV21_ERROR], _("SMPTE Bv2.1 errors"));
+ _pages[dcp::VerificationNote::Type::WARNING] = new wxRichTextCtrl(notebook, wxID_ANY, wxEmptyString, wxDefaultPosition, {400, 300}, wxRE_READONLY);
+ notebook->AddPage(_pages[dcp::VerificationNote::Type::WARNING], _("Warnings"));
+
+ _summary = new wxStaticText(this, wxID_ANY, wxT(""));
+ sizer->Add(_summary, 0, wxALL, DCPOMATIC_DIALOG_BORDER);
+
+ auto save_sizer = new wxBoxSizer(wxHORIZONTAL);
+ _save_text_report = new Button(this, _("Save report as text..."));
+ save_sizer->Add(_save_text_report, 0, wxALL, DCPOMATIC_SIZER_GAP);
+ _save_html_report = new Button(this, _("Save report as HTML..."));
+ save_sizer->Add(_save_html_report, 0, wxALL, DCPOMATIC_SIZER_GAP);
+ sizer->Add(save_sizer);
+
+ SetSizer(sizer);
+ sizer->Layout();
+ sizer->SetSizeHints(this);
+
+ for (auto const& i: _pages) {
i.second->GetCaret()->Hide();
}
- if (job->finished_ok() && job->notes().empty()) {
- summary->SetLabel (_("DCP validates OK."));
+ _save_text_report->bind(&VerifyDCPResultPanel::save_text_report, this);
+ _save_html_report->bind(&VerifyDCPResultPanel::save_html_report, this);
+
+ _save_text_report->Enable(false);
+ _save_html_report->Enable(false);
+}
+
+
+void
+VerifyDCPResultPanel::fill(shared_ptr<VerifyDCPJob> job)
+{
+ if (job->finished_ok() && job->result().notes.empty()) {
+ _summary->SetLabel(_("DCP validates OK."));
return;
}
@@ -80,11 +95,11 @@ VerifyDCPDialog::VerifyDCPDialog (wxWindow* parent, shared_ptr<VerifyDCPJob> job
counts[dcp::VerificationNote::Type::BV21_ERROR] = 0;
counts[dcp::VerificationNote::Type::ERROR] = 0;
- auto add_bullet = [&pages](dcp::VerificationNote::Type type, wxString message) {
- pages[type]->BeginStandardBullet(N_("standard/diamond"), 1, 50);
- pages[type]->WriteText (message);
- pages[type]->Newline ();
- pages[type]->EndStandardBullet ();
+ auto add_bullet = [this](dcp::VerificationNote::Type type, wxString message) {
+ _pages[type]->BeginStandardBullet(N_("standard/diamond"), 1, 50);
+ _pages[type]->WriteText(message);
+ _pages[type]->Newline();
+ _pages[type]->EndStandardBullet();
};
auto add = [&counts, &add_bullet](dcp::VerificationNote note, wxString message) {
@@ -123,20 +138,20 @@ VerifyDCPDialog::VerifyDCPDialog (wxWindow* parent, shared_ptr<VerifyDCPJob> job
if (note.other_id()) {
message.Replace("%other_id", std_to_wx(note.other_id().get()));
}
- add_bullet (note.type(), message);
+ add_bullet(note.type(), message);
counts[note.type()]++;
};
if (job->finished_in_error() && job->error_summary() != "") {
/* We have an error that did not come from dcp::verify */
- add_bullet (dcp::VerificationNote::Type::ERROR, std_to_wx(job->error_summary()));
+ add_bullet(dcp::VerificationNote::Type::ERROR, std_to_wx(job->error_summary()));
++counts[dcp::VerificationNote::Type::ERROR];
}
- for (auto i: job->notes()) {
+ for (auto i: job->result().notes) {
switch (i.code()) {
case dcp::VerificationNote::Code::FAILED_READ:
- add (i, _("Could not read DCP (%n)"));
+ add(i, _("Could not read DCP (%n)"));
break;
case dcp::VerificationNote::Code::MISMATCHED_CPL_HASHES:
add(i, _("The hash (%reference_hash) of the CPL %n in the PKL does not agree with the CPL file (%calculated_hash). This probably means that the CPL file is corrupt."));
@@ -382,7 +397,7 @@ VerifyDCPDialog::VerifyDCPDialog (wxWindow* parent, shared_ptr<VerifyDCPJob> job
case dcp::VerificationNote::Code::MISMATCHED_TIMED_TEXT_DURATION:
{
vector<string> parts;
- boost::split (parts, i.note().get(), boost::is_any_of(" "));
+ boost::split(parts, i.note().get(), boost::is_any_of(" "));
add(i, wxString::Format(_("The reel duration (%s) of some timed text is not the same as the ContainerDuration (%s) of its MXF."), std_to_wx(parts[0]), std_to_wx(parts[1])));
break;
}
@@ -450,10 +465,23 @@ VerifyDCPDialog::VerifyDCPDialog (wxWindow* parent, shared_ptr<VerifyDCPJob> job
add(i, _("The <LabelText> in a <ContentVersion> in CPL %id is empty"));
break;
case dcp::VerificationNote::Code::INVALID_CPL_NAMESPACE:
- add(i, _("The CPL %f has an invalid namespace %n"));
+ add(i, _("The CPL %id has an invalid namespace %n"));
break;
case dcp::VerificationNote::Code::MISSING_CPL_CONTENT_VERSION:
- add(i, _("The CPL %n has no <ContentVersion> tag"));
+ add(i, _("The CPL %id has no <ContentVersion> tag"));
+ break;
+ case dcp::VerificationNote::Code::MATCHING_CPL_HASHES:
+ case dcp::VerificationNote::Code::CORRECT_PICTURE_HASH:
+ case dcp::VerificationNote::Code::VALID_PICTURE_FRAME_SIZES_IN_BYTES:
+ case dcp::VerificationNote::Code::VALID_RELEASE_TERRITORY:
+ case dcp::VerificationNote::Code::VALID_CPL_ANNOTATION_TEXT:
+ case dcp::VerificationNote::Code::MATCHING_PKL_ANNOTATION_TEXT_WITH_CPL:
+ case dcp::VerificationNote::Code::ALL_ENCRYPTED:
+ case dcp::VerificationNote::Code::NONE_ENCRYPTED:
+ case dcp::VerificationNote::Code::VALID_CONTENT_KIND:
+ case dcp::VerificationNote::Code::VALID_MAIN_PICTURE_ACTIVE_AREA:
+ case dcp::VerificationNote::Code::VALID_CONTENT_VERSION_LABEL_TEXT:
+ /* These are all "OK" messages which we don't report here */
break;
}
}
@@ -484,17 +512,52 @@ VerifyDCPDialog::VerifyDCPDialog (wxWindow* parent, shared_ptr<VerifyDCPJob> job
summary_text += wxString::Format("and %d warnings.", counts[dcp::VerificationNote::Type::WARNING]);
}
- summary->SetLabel(summary_text);
+ _summary->SetLabel(summary_text);
if (counts[dcp::VerificationNote::Type::ERROR] == 0) {
- add_bullet (dcp::VerificationNote::Type::ERROR, _("No errors found."));
+ add_bullet(dcp::VerificationNote::Type::ERROR, _("No errors found."));
}
if (counts[dcp::VerificationNote::Type::BV21_ERROR] == 0) {
- add_bullet (dcp::VerificationNote::Type::BV21_ERROR, _("No SMPTE Bv2.1 errors found."));
+ add_bullet(dcp::VerificationNote::Type::BV21_ERROR, _("No SMPTE Bv2.1 errors found."));
}
if (counts[dcp::VerificationNote::Type::WARNING] == 0) {
- add_bullet (dcp::VerificationNote::Type::WARNING, _("No warnings found."));
+ add_bullet(dcp::VerificationNote::Type::WARNING, _("No warnings found."));
+ }
+
+ _job = job;
+ _save_text_report->Enable(true);
+ _save_html_report->Enable(true);
+}
+
+
+template <class T>
+void save(wxWindow* parent, wxString filter, dcp::VerificationResult const& result)
+{
+ FileDialog dialog(parent, _("Verification report"), filter, wxFD_SAVE | wxFD_OVERWRITE_PROMPT, "SaveVerificationReport");
+ if (!dialog.show()) {
+ return;
+ }
+
+ T formatter(dialog.path());
+ dcp::verify_report(result, formatter);
+}
+
+
+void
+VerifyDCPResultPanel::save_text_report()
+{
+ if (_job) {
+ save<dcp::TextFormatter>(this, wxT("Text files (*.txt)|*.txt"), _job->result());
+ }
+}
+
+
+void
+VerifyDCPResultPanel::save_html_report()
+{
+ if (_job) {
+ save<dcp::HTMLFormatter>(this, wxT("HTML files (*.htm;*html)|*.htm;*.html"), _job->result());
}
}
diff --git a/src/wx/verify_dcp_result_panel.h b/src/wx/verify_dcp_result_panel.h
new file mode 100644
index 000000000..8cf92118b
--- /dev/null
+++ b/src/wx/verify_dcp_result_panel.h
@@ -0,0 +1,50 @@
+/*
+ Copyright (C) 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 <dcp/verify.h>
+#include <wx/wx.h>
+#include <map>
+#include <memory>
+
+
+class Button;
+class VerifyDCPJob;
+class wxRichTextCtrl;
+
+
+class VerifyDCPResultPanel : public wxPanel
+{
+public:
+ VerifyDCPResultPanel(wxWindow* parent);
+
+ void fill(std::shared_ptr<VerifyDCPJob> job);
+
+private:
+ void save_text_report();
+ void save_html_report();
+
+ wxStaticText* _summary;
+ std::map<dcp::VerificationNote::Type, wxRichTextCtrl*> _pages;
+ Button* _save_text_report;
+ Button* _save_html_report;
+
+ std::shared_ptr<VerifyDCPJob> _job;
+};
diff --git a/src/wx/video_panel.cc b/src/wx/video_panel.cc
index 859c0a886..5a91df8fe 100644
--- a/src/wx/video_panel.cc
+++ b/src/wx/video_panel.cc
@@ -76,14 +76,6 @@ VideoPanel::VideoPanel (ContentPanel* p)
void
VideoPanel::create ()
{
- _reference = new CheckBox (this, _("Use this DCP's video as OV and make VF"));
- _reference_note = new StaticText (this, wxT(""));
- _reference_note->Wrap (200);
- auto font = _reference_note->GetFont();
- font.SetStyle(wxFONTSTYLE_ITALIC);
- font.SetPointSize(font.GetPointSize() - 1);
- _reference_note->SetFont(font);
-
_type_label = create_label (this, _("Type"), true);
_frame_type = new ContentChoice<VideoContent, VideoFrameType> (
this,
@@ -203,6 +195,9 @@ VideoPanel::create ()
_range->Append (_("Video (MPEG, 16-235)"));
_description = new StaticText (this, wxT ("\n \n \n \n \n"), wxDefaultPosition, wxDefaultSize);
+ auto font = _description->GetFont();
+ font.SetStyle(wxFONTSTYLE_ITALIC);
+ font.SetPointSize(font.GetPointSize() - 1);
_description->SetFont(font);
_left_crop->wrapped()->SetRange (0, 4096);
@@ -223,7 +218,6 @@ VideoPanel::create ()
_fade_in->Changed.connect (boost::bind (&VideoPanel::fade_in_changed, this));
_fade_out->Changed.connect (boost::bind (&VideoPanel::fade_out_changed, this));
- _reference->bind(&VideoPanel::reference_clicked, this);
_scale_fit->Bind (wxEVT_RADIOBUTTON, boost::bind (&VideoPanel::scale_fit_clicked, this));
_scale_custom->Bind (wxEVT_RADIOBUTTON, boost::bind (&VideoPanel::scale_custom_clicked, this));
_scale_custom_edit->Bind (wxEVT_BUTTON, boost::bind (&VideoPanel::scale_custom_edit_clicked, this));
@@ -244,12 +238,6 @@ VideoPanel::add_to_grid ()
{
int r = 0;
- auto reference_sizer = new wxBoxSizer (wxVERTICAL);
- reference_sizer->Add (_reference, 0);
- reference_sizer->Add (_reference_note, 0);
- _grid->Add (reference_sizer, wxGBPosition(r, 0), wxGBSpan(1, 3));
- ++r;
-
add_label_to_sizer (_grid, _type_label, true, wxGBPosition(r, 0));
_frame_type->add (_grid, wxGBPosition(r, 1), wxGBSpan(1, 2));
++r;
@@ -458,15 +446,6 @@ VideoPanel::film_content_changed (int property)
} else {
_fade_out->clear ();
}
- } else if (property == DCPContentProperty::REFERENCE_VIDEO) {
- if (vc.size() == 1) {
- shared_ptr<DCPContent> dcp = dynamic_pointer_cast<DCPContent> (vc.front ());
- checked_set (_reference, dcp ? dcp->reference_video () : false);
- } else {
- checked_set (_reference, false);
- }
-
- setup_sensitivity ();
} else if (property == VideoContentProperty::RANGE) {
if (vcs) {
checked_set (_range, vcs->video->range() == VideoRange::FULL ? 0 : 1);
@@ -594,15 +573,7 @@ VideoPanel::setup_sensitivity ()
dcp = dynamic_pointer_cast<DCPContent> (sel.front ());
}
- string why_not;
- bool const can_reference = dcp && dcp->can_reference_video (_parent->film(), why_not);
- wxString cannot;
- if (why_not.empty()) {
- cannot = _("Cannot reference this DCP's video.");
- } else {
- cannot = _("Cannot reference this DCP's video: ") + std_to_wx(why_not);
- }
- setup_refer_button (_reference, _reference_note, dcp, can_reference, cannot);
+ bool const reference = dcp && dcp->reference_video();
bool any_use = false;
for (auto i: _parent->selected_video()) {
@@ -611,7 +582,7 @@ VideoPanel::setup_sensitivity ()
}
}
- bool const enable = !_reference->GetValue() && any_use;
+ bool const enable = !reference && any_use;
if (!enable) {
_frame_type->wrapped()->Enable (false);
@@ -684,23 +655,6 @@ VideoPanel::fade_out_changed ()
void
-VideoPanel::reference_clicked ()
-{
- auto c = _parent->selected ();
- if (c.size() != 1) {
- return;
- }
-
- auto d = dynamic_pointer_cast<DCPContent> (c.front ());
- if (!d) {
- return;
- }
-
- d->set_reference_video (_reference->GetValue ());
-}
-
-
-void
VideoPanel::scale_fit_clicked ()
{
for (auto i: _parent->selected_video()) {
diff --git a/src/wx/video_panel.h b/src/wx/video_panel.h
index 686d1b99b..e6b689b03 100644
--- a/src/wx/video_panel.h
+++ b/src/wx/video_panel.h
@@ -53,7 +53,6 @@ public:
void content_selection_changed () override;
private:
- void reference_clicked ();
void colour_conversion_changed ();
void edit_colour_conversion_clicked ();
void range_changed ();
@@ -73,8 +72,6 @@ private:
void setup_description ();
void setup_sensitivity ();
- CheckBox* _reference;
- wxStaticText* _reference_note;
wxStaticText* _type_label;
ContentChoice<VideoContent, VideoFrameType>* _frame_type;
wxStaticText* _crop_label;
diff --git a/src/wx/wscript b/src/wx/wscript
index a6eefa69f..bdae9708e 100644
--- a/src/wx/wscript
+++ b/src/wx/wscript
@@ -48,12 +48,23 @@ sources = """
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
credentials_download_certificate_panel.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
@@ -102,6 +113,7 @@ sources = """
language_subtag_panel.cc
language_tag_dialog.cc
language_tag_widget.cc
+ load_config_from_zip_dialog.cc
kdm_choice.cc
make_chain_dialog.cc
markers.cc
@@ -159,27 +171,24 @@ sources = """
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
- verify_dcp_dialog.cc
+ verify_dcp_result_dialog.cc
+ verify_dcp_result_panel.cc
verify_dcp_progress_dialog.cc
+ verify_dcp_progress_panel.cc
video_panel.cc
video_view.cc
video_waveform_dialog.cc
video_waveform_plot.cc
- wx_util.cc
wx_signal_manager.cc
+ wx_util.cc
+ wx_variant.cc
"""
def configure(conf):
diff --git a/src/wx/wx_util.cc b/src/wx/wx_util.cc
index 28ac6ab5c..1c04a4755 100644
--- a/src/wx/wx_util.cc
+++ b/src/wx/wx_util.cc
@@ -31,20 +31,22 @@
#include "static_text.h"
#include "wx_ptr.h"
#include "wx_util.h"
+#include "wx_variant.h"
#include "lib/config.h"
#include "lib/cross.h"
#include "lib/job.h"
#include "lib/job_manager.h"
#include "lib/util.h"
+#include "lib/variant.h"
#include "lib/version.h"
#include <dcp/locale_convert.h>
#include <dcp/warnings.h>
LIBDCP_DISABLE_WARNINGS
-#include <wx/spinctrl.h>
-#include <wx/splash.h>
-#include <wx/progdlg.h>
#include <wx/filepicker.h>
+#include <wx/progdlg.h>
#include <wx/sizer.h>
+#include <wx/spinctrl.h>
+#include <wx/splash.h>
LIBDCP_ENABLE_WARNINGS
#include <boost/thread.hpp>
@@ -161,7 +163,7 @@ add_label_to_sizer (wxGridBagSizer* s, wxStaticText* t, bool, wxGBPosition pos,
void
error_dialog (wxWindow* parent, wxString m, optional<wxString> e)
{
- auto d = make_wx<wxMessageDialog>(parent, m, _("DCP-o-matic"), wxOK | wxICON_ERROR);
+ auto d = make_wx<wxMessageDialog>(parent, m, variant::wx::dcpomatic(), wxOK | wxICON_ERROR);
if (e) {
wxString em = *e;
em[0] = wxToupper (em[0]);
@@ -178,7 +180,7 @@ error_dialog (wxWindow* parent, wxString m, optional<wxString> e)
void
message_dialog (wxWindow* parent, wxString m)
{
- auto d = make_wx<wxMessageDialog>(parent, m, _("DCP-o-matic"), wxOK | wxICON_INFORMATION);
+ auto d = make_wx<wxMessageDialog>(parent, m, variant::wx::dcpomatic(), wxOK | wxICON_INFORMATION);
d->ShowModal ();
}
@@ -187,7 +189,7 @@ message_dialog (wxWindow* parent, wxString m)
bool
confirm_dialog (wxWindow* parent, wxString m)
{
- auto d = make_wx<wxMessageDialog>(parent, m, _("DCP-o-matic"), wxYES_NO | wxICON_QUESTION);
+ auto d = make_wx<wxMessageDialog>(parent, m, variant::wx::dcpomatic(), wxYES_NO | wxICON_QUESTION);
return d->ShowModal() == wxID_YES;
}
@@ -409,6 +411,65 @@ checked_set(RegionSubtagWidget* widget, optional<dcp::LanguageTag::RegionSubtag>
}
+#ifdef DCPOMATIC_OSX
+
+void
+dcpomatic_setup_i18n()
+{
+ wxLog::EnableLogging();
+
+ auto get_locale_value = [](CFLocaleKey key) {
+ CFLocaleRef cflocale = CFLocaleCopyCurrent();
+ auto value = (CFStringRef) CFLocaleGetValue(cflocale, key);
+ char buffer[64];
+ CFStringGetCString(value, buffer, sizeof(buffer), kCFStringEncodingUTF8);
+ CFRelease(cflocale);
+ return string(buffer);
+ };
+
+ auto translations = new wxTranslations();
+
+ auto config_lang = Config::instance()->language();
+ if (config_lang && !config_lang->empty()) {
+ translations->SetLanguage(std_to_wx(*config_lang));
+ } else {
+ /* We want to use the user's preferred language. It seems that if we use the wxWidgets default we will get the
+ * language for the locale, which may not be what we want (e.g. for a machine in Germany, configured for DE locale,
+ * but with the preferred language set to English).
+ *
+ * Instead, the the language code from macOS then get the corresponding canonical language string with region,
+ * which wxTranslations::SetLanguage will accept.
+ */
+ auto const language_code = get_locale_value(kCFLocaleLanguageCode);
+ /* Ideally this would be wxUILocale (as wxLocale is deprecated) but we want to keep this building
+ * with the old wxWidgets we use for the older macOS builds.
+ */
+ auto const info = wxLocale::FindLanguageInfo(std_to_wx(language_code));
+ if (info) {
+#if wxCHECK_VERSION(3, 1, 6)
+ translations->SetLanguage(info->GetCanonicalWithRegion());
+#else
+ translations->SetLanguage(info->CanonicalName);
+#endif
+ }
+ }
+
+#ifdef DCPOMATIC_DEBUG
+ wxFileTranslationsLoader::AddCatalogLookupPathPrefix(wxT("build/src/wx/mo"));
+ wxFileTranslationsLoader::AddCatalogLookupPathPrefix(wxT("build/src/tools/mo"));
+#endif
+
+ translations->AddStdCatalog();
+ translations->AddCatalog(wxT("libdcpomatic2-wx"));
+ translations->AddCatalog(wxT("dcpomatic2"));
+
+ wxTranslations::Set(translations);
+
+ dcpomatic_setup_gettext_i18n(config_lang.get_value_or(""));
+}
+
+#else
+
void
dcpomatic_setup_i18n ()
{
@@ -458,6 +519,8 @@ dcpomatic_setup_i18n ()
}
}
+#endif
+
int
wx_get (wxSpinCtrl* w)
@@ -623,35 +686,35 @@ display_progress (wxString title, wxString task)
int
get_offsets (vector<Offset>& offsets)
{
- offsets.push_back (Offset(_("UTC-11"), -11, 0));
- offsets.push_back (Offset(_("UTC-10"), -10, 0));
- offsets.push_back (Offset(_("UTC-9"), -9, 0));
- offsets.push_back (Offset(_("UTC-8"), -8, 0));
- offsets.push_back (Offset(_("UTC-7"), -7, 0));
- offsets.push_back (Offset(_("UTC-6"), -6, 0));
- offsets.push_back (Offset(_("UTC-5"), -5, 0));
- offsets.push_back (Offset(_("UTC-4:30"), -4, 30));
- offsets.push_back (Offset(_("UTC-4"), -4, 0));
- offsets.push_back (Offset(_("UTC-3:30"), -3, 30));
- offsets.push_back (Offset(_("UTC-3"), -3, 0));
- offsets.push_back (Offset(_("UTC-2"), -2, 0));
- offsets.push_back (Offset(_("UTC-1"), -1, 0));
+ offsets.push_back({_("UTC-11"), dcp::UTCOffset(-11, 0)});
+ offsets.push_back({_("UTC-10"), dcp::UTCOffset(-10, 0)});
+ offsets.push_back({_("UTC-9"), dcp::UTCOffset( -9, 0)});
+ offsets.push_back({_("UTC-8"), dcp::UTCOffset( -8, 0)});
+ offsets.push_back({_("UTC-7"), dcp::UTCOffset( -7, 0)});
+ offsets.push_back({_("UTC-6"), dcp::UTCOffset( -6, 0)});
+ offsets.push_back({_("UTC-5"), dcp::UTCOffset( -5, 0)});
+ offsets.push_back({_("UTC-4:30"),dcp::UTCOffset( -4, -30)});
+ offsets.push_back({_("UTC-4"), dcp::UTCOffset( -4, 0)});
+ offsets.push_back({_("UTC-3:30"),dcp::UTCOffset( -3, -30)});
+ offsets.push_back({_("UTC-3"), dcp::UTCOffset( -3, 0)});
+ offsets.push_back({_("UTC-2"), dcp::UTCOffset( -2, 0)});
+ offsets.push_back({_("UTC-1"), dcp::UTCOffset( -1, 0)});
int utc = offsets.size();
- offsets.push_back (Offset(_("UTC") , 0, 0));
- offsets.push_back (Offset(_("UTC+1"), 1, 0));
- offsets.push_back (Offset(_("UTC+2"), 2, 0));
- offsets.push_back (Offset(_("UTC+3"), 3, 0));
- offsets.push_back (Offset(_("UTC+4"), 4, 0));
- offsets.push_back (Offset(_("UTC+5"), 5, 0));
- offsets.push_back (Offset(_("UTC+5:30"), 5, 30));
- offsets.push_back (Offset(_("UTC+6"), 6, 0));
- offsets.push_back (Offset(_("UTC+7"), 7, 0));
- offsets.push_back (Offset(_("UTC+8"), 8, 0));
- offsets.push_back (Offset(_("UTC+9"), 9, 0));
- offsets.push_back (Offset(_("UTC+9:30"), 9, 30));
- offsets.push_back (Offset(_("UTC+10"), 10, 0));
- offsets.push_back (Offset(_("UTC+11"), 11, 0));
- offsets.push_back (Offset(_("UTC+12"), 12, 0));
+ offsets.push_back({_("UTC") , dcp::UTCOffset( 0, 0)});
+ offsets.push_back({_("UTC+1"), dcp::UTCOffset( 1, 0)});
+ offsets.push_back({_("UTC+2"), dcp::UTCOffset( 2, 0)});
+ offsets.push_back({_("UTC+3"), dcp::UTCOffset( 3, 0)});
+ offsets.push_back({_("UTC+4"), dcp::UTCOffset( 4, 0)});
+ offsets.push_back({_("UTC+5"), dcp::UTCOffset( 5, 0)});
+ offsets.push_back({_("UTC+5:30"),dcp::UTCOffset( 5, 30)});
+ offsets.push_back({_("UTC+6"), dcp::UTCOffset( 6, 0)});
+ offsets.push_back({_("UTC+7"), dcp::UTCOffset( 7, 0)});
+ offsets.push_back({_("UTC+8"), dcp::UTCOffset( 8, 0)});
+ offsets.push_back({_("UTC+9"), dcp::UTCOffset( 9, 0)});
+ offsets.push_back({_("UTC+9:30"),dcp::UTCOffset( 9, 30)});
+ offsets.push_back({_("UTC+10"), dcp::UTCOffset( 10, 0)});
+ offsets.push_back({_("UTC+11"), dcp::UTCOffset( 11, 0)});
+ offsets.push_back({_("UTC+12"), dcp::UTCOffset( 12, 0)});
return utc;
}
@@ -747,20 +810,12 @@ report_config_load_failure(wxWindow* parent, Config::LoadFailure what)
case Config::LoadFailure::CONFIG:
message_dialog(parent, _("The existing configuration failed to load. Default values will be used instead. These may take a short time to create."));
break;
- case Config::LoadFailure::CINEMAS:
- message_dialog(
- parent,
- _(wxString::Format("The cinemas list for creating KDMs (cinemas.xml) failed to load. Please check the numbered backup files in %s",
- std_to_wx(Config::instance()->cinemas_file().parent_path().string())))
- );
- break;
- case Config::LoadFailure::DKDM_RECIPIENTS:
- message_dialog(
- parent,
- _(wxString::Format("The recipients list for creating DKDMs (dkdm_recipients.xml) failed to load. Please check the numbered backup files in %s",
- std_to_wx(Config::instance()->dkdm_recipients_file().parent_path().string())))
- );
- break;
}
}
+
+wxString
+wx::report_problem()
+{
+ return std_to_wx(::report_problem());
+}
diff --git a/src/wx/wx_util.h b/src/wx/wx_util.h
index 66b01640c..db1b3ae0c 100644
--- a/src/wx/wx_util.h
+++ b/src/wx/wx_util.h
@@ -31,6 +31,7 @@
#include "wx_ptr.h"
#include "lib/config.h"
#include "lib/dcpomatic_time.h"
+#include <dcp/utc_offset.h>
#include <dcp/warnings.h>
LIBDCP_DISABLE_WARNINGS
#include <wx/gbsizer.h>
@@ -56,7 +57,7 @@ class PasswordEntry;
#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
@@ -74,7 +75,7 @@ class PasswordEntry;
/** Amount by which you need to top-pad a choice to make it line up, in some cases */
#ifdef DCPOMATIC_OSX
-#define DCPOMATIC_CHOICE_TOP_PAD 1
+#define DCPOMATIC_CHOICE_TOP_PAD 2
#else
#define DCPOMATIC_CHOICE_TOP_PAD 0
#endif
@@ -131,21 +132,24 @@ extern double dpi_scale_factor (wxWindow* window);
extern int search_ctrl_height ();
extern void report_config_load_failure(wxWindow* parent, Config::LoadFailure what);
+
struct Offset
{
- Offset (wxString n, int h, int m)
- : name (n)
- , hour (h)
- , minute (m)
+ Offset(wxString name_, dcp::UTCOffset offset_)
+ : name(name_)
+ , offset(offset_)
{}
wxString name;
- int hour;
- int minute;
+ dcp::UTCOffset offset;
};
extern int get_offsets (std::vector<Offset>& offsets);
+namespace wx {
+ extern wxString report_problem();
+}
+
extern void checked_set (FilePickerCtrl* widget, boost::filesystem::path value);
extern void checked_set (wxDirPickerCtrl* widget, boost::filesystem::path value);
diff --git a/src/wx/wx_variant.cc b/src/wx/wx_variant.cc
new file mode 100644
index 000000000..96b00bba4
--- /dev/null
+++ b/src/wx/wx_variant.cc
@@ -0,0 +1,147 @@
+/*
+ Copyright (C) 2024 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 "wx_util.h"
+#include "wx_variant.h"
+#include "lib/variant.h"
+
+
+
+wxString
+variant::wx::dcpomatic()
+{
+ return std_to_wx(variant::dcpomatic());
+}
+
+wxString
+variant::wx::dcpomatic_batch_converter()
+{
+ return std_to_wx(variant::dcpomatic_batch_converter());
+}
+
+wxString
+variant::wx::dcpomatic_combiner()
+{
+ return std_to_wx(variant::dcpomatic_combiner());
+}
+
+wxString
+variant::wx::dcpomatic_disk_writer()
+{
+ return std_to_wx(variant::dcpomatic_disk_writer());
+}
+
+wxString
+variant::wx::dcpomatic_editor()
+{
+ return std_to_wx(variant::dcpomatic_editor());
+}
+
+wxString
+variant::wx::dcpomatic_encode_server()
+{
+ return std_to_wx(variant::dcpomatic_encode_server());
+}
+
+wxString
+variant::wx::dcpomatic_kdm_creator()
+{
+ return std_to_wx(variant::dcpomatic_kdm_creator());
+}
+
+wxString
+variant::wx::dcpomatic_player()
+{
+ return std_to_wx(variant::dcpomatic_player());
+}
+
+wxString
+variant::wx::dcpomatic_playlist_editor()
+{
+ return std_to_wx(variant::dcpomatic_playlist_editor());
+}
+
+wxString
+variant::wx::dcpomatic_verifier()
+{
+ return std_to_wx(variant::dcpomatic_verifier());
+}
+
+wxString
+variant::wx::insert_dcpomatic(wxString const& s)
+{
+ return wxString::Format(s, dcpomatic());
+}
+
+wxString
+variant::wx::insert_dcpomatic_batch_converter(wxString const& s)
+{
+ return wxString::Format(s, dcpomatic_batch_converter());
+}
+
+wxString
+variant::wx::insert_dcpomatic_disk_writer(wxString const& s)
+{
+ return wxString::Format(s, dcpomatic_disk_writer());
+}
+
+wxString
+variant::wx::insert_dcpomatic_editor(wxString const& s)
+{
+ return wxString::Format(s, dcpomatic_editor());
+}
+
+wxString
+variant::wx::insert_dcpomatic_encode_server(wxString const& s)
+{
+ return wxString::Format(s, dcpomatic_encode_server());
+}
+
+wxString
+variant::wx::insert_dcpomatic_kdm_creator(wxString const& s)
+{
+ return wxString::Format(s, dcpomatic_kdm_creator());
+}
+
+wxString
+variant::wx::insert_dcpomatic_player(wxString const& s)
+{
+ return wxString::Format(s, dcpomatic_player());
+}
+
+wxString
+variant::wx::insert_dcpomatic_playlist_editor(wxString const& s)
+{
+ return wxString::Format(s, dcpomatic_playlist_editor());
+}
+
+wxString
+variant::wx::insert_dcpomatic_verifier(wxString const& s)
+{
+ return wxString::Format(s, dcpomatic_verifier());
+}
+
+wxString
+variant::wx::report_problem_email()
+{
+ return std_to_wx(variant::report_problem_email());
+}
+
diff --git a/src/wx/wx_variant.h b/src/wx/wx_variant.h
new file mode 100644
index 000000000..bf2bcaee0
--- /dev/null
+++ b/src/wx/wx_variant.h
@@ -0,0 +1,54 @@
+/*
+ Copyright (C) 2024 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 <wx/wx.h>
+
+
+namespace variant {
+namespace wx {
+
+
+wxString dcpomatic();
+wxString dcpomatic_batch_converter();
+wxString dcpomatic_combiner();
+wxString dcpomatic_disk_writer();
+wxString dcpomatic_editor();
+wxString dcpomatic_encode_server();
+wxString dcpomatic_kdm_creator();
+wxString dcpomatic_player();
+wxString dcpomatic_playlist_editor();
+wxString dcpomatic_verifier();
+
+wxString insert_dcpomatic(wxString const& s);
+wxString insert_dcpomatic_batch_converter(wxString const& s);
+wxString insert_dcpomatic_disk_writer(wxString const& s);
+wxString insert_dcpomatic_editor(wxString const& s);
+wxString insert_dcpomatic_encode_server(wxString const& s);
+wxString insert_dcpomatic_kdm_creator(wxString const& s);
+wxString insert_dcpomatic_player(wxString const& s);
+wxString insert_dcpomatic_playlist_editor(wxString const& s);
+wxString insert_dcpomatic_verifier(wxString const& s);
+
+wxString report_problem_email();
+
+
+}
+}