#include "wx/wx_util.h"
#include "lib/analytics.h"
#include "lib/audio_content.h"
-#include "lib/check_content_change_job.h"
+#include "lib/check_content_job.h"
#include "lib/cinema.h"
#include "lib/compose.hpp"
#include "lib/config.h"
+#include "lib/constants.h"
#include "lib/content.h"
#include "lib/content_factory.h"
#include "lib/cross.h"
#include "lib/kdm_with_metadata.h"
#include "lib/log.h"
#include "lib/make_dcp.h"
+#include "lib/release_notes.h"
+#include "lib/scope_guard.h"
#include "lib/screen.h"
#include "lib/send_kdm_email_job.h"
#include "lib/signal_manager.h"
#include "lib/text_content.h"
#include "lib/transcode_job.h"
#include "lib/update_checker.h"
-#include "lib/util.h"
#include "lib/version.h"
#include "lib/video_content.h"
#include <dcp/exceptions.h>
};
+class LimitedFrameSplitter : public wxSplitterWindow
+{
+public:
+ LimitedFrameSplitter(wxWindow* parent)
+ : wxSplitterWindow(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxSP_NOBORDER | wxSP_3DSASH | wxSP_LIVE_UPDATE)
+ {
+ /* This value doesn't really mean much but we just want to stop double-click on the
+ divider from shrinking the left panel.
+ */
+ SetMinimumPaneSize(64);
+
+ Bind(wxEVT_SIZE, boost::bind(&LimitedFrameSplitter::sized, this, _1));
+ }
+
+ bool OnSashPositionChange(int new_position) override
+ {
+ /* Try to stop the left bit of the splitter getting too small */
+ auto const ok = new_position > _left_panel_minimum_size;
+ if (ok) {
+ Config::instance()->set_main_divider_sash_position(new_position);
+ }
+ return ok;
+ }
+
+private:
+ void sized(wxSizeEvent& ev)
+ {
+ if (GetSize().GetWidth() > _left_panel_minimum_size && GetSashPosition() < _left_panel_minimum_size) {
+ /* The window is now fairly big but the left panel is small; this happens when the DCP-o-matic window
+ * is shrunk and then made larger again. Try to set a sensible left panel size in this case.
+ */
+ SetSashPosition(Config::instance()->main_divider_sash_position().get_value_or(_left_panel_minimum_size));
+ }
+
+ ev.Skip();
+ }
+
+ int const _left_panel_minimum_size = 200;
+};
+
+
class DOMFrame : public wxFrame
{
public:
explicit DOMFrame (wxString const& title)
: wxFrame (nullptr, -1, title)
+ /* Use a panel as the only child of the Frame so that we avoid
+ the dark-grey background on Windows.
+ */
+ , _splitter(new LimitedFrameSplitter(this))
+ , _right_panel(new wxPanel(_splitter, wxID_ANY))
+ , _film_viewer(_right_panel)
{
#if defined(DCPOMATIC_WINDOWS)
if (Config::instance()->win32_console()) {
Bind (wxEVT_CLOSE_WINDOW, boost::bind (&DOMFrame::close, this, _1));
Bind (wxEVT_SHOW, boost::bind (&DOMFrame::show, this, _1));
- /* Use a panel as the only child of the Frame so that we avoid
- the dark-grey background on Windows.
- */
- auto overall_panel = new wxPanel (this, wxID_ANY);
+ auto left_panel = new wxPanel(_splitter, wxID_ANY);
+
+ _film_editor = new FilmEditor(left_panel, _film_viewer);
+
+ auto left_sizer = new wxBoxSizer(wxHORIZONTAL);
+ left_sizer->Add(_film_editor, 1, wxEXPAND);
+
+ left_panel->SetSizerAndFit(left_sizer);
- _film_viewer.reset (new FilmViewer (overall_panel));
- _controls = new StandardControls (overall_panel, _film_viewer, true);
- _film_editor = new FilmEditor (overall_panel, _film_viewer);
- auto job_manager_view = new JobManagerView (overall_panel, false);
+ _controls = new StandardControls(_right_panel, _film_viewer, true);
+ auto job_manager_view = new JobManagerView(_right_panel, false);
auto right_sizer = new wxBoxSizer (wxVERTICAL);
- right_sizer->Add (_film_viewer->panel(), 2, wxEXPAND | wxALL, 6);
+ right_sizer->Add(_film_viewer.panel(), 2, wxEXPAND | wxALL, 6);
right_sizer->Add (_controls, 0, wxEXPAND | wxALL, 6);
right_sizer->Add (job_manager_view, 1, wxEXPAND | wxALL, 6);
- wxBoxSizer* main_sizer = new wxBoxSizer (wxHORIZONTAL);
- main_sizer->Add (_film_editor, 0, wxEXPAND | wxALL, 6);
- main_sizer->Add (right_sizer, 1, wxEXPAND | wxALL, 6);
+ _right_panel->SetSizer(right_sizer);
+
+ _splitter->SplitVertically(left_panel, _right_panel, Config::instance()->main_divider_sash_position().get_value_or(left_panel->GetSize().GetWidth() + 8));
set_menu_sensitivity ();
_film_editor->content_panel()->SelectionChanged.connect (boost::bind (&DOMFrame::set_menu_sensitivity, this));
set_title ();
- JobManager::instance()->ActiveJobsChanged.connect (boost::bind (&DOMFrame::set_menu_sensitivity, this));
-
- overall_panel->SetSizer (main_sizer);
+ JobManager::instance()->ActiveJobsChanged.connect(boost::bind(&DOMFrame::active_jobs_changed, this));
UpdateChecker::instance()->StateChanged.connect(boost::bind(&DOMFrame::update_checker_state_changed, this));
set_film (film);
- JobManager::instance()->add(shared_ptr<Job>(new CheckContentChangeJob(film)));
+ JobManager::instance()->add(make_shared<CheckContentJob>(film));
}
catch (FileNotFoundError& e) {
auto const dir = e.file().parent_path();
void set_film (shared_ptr<Film> film)
{
_film = film;
- _film_viewer->set_film (_film);
- _film_editor->set_film (_film);
+ _film_viewer.set_film(_film);
+ _film_editor->set_film(_film);
_controls->set_film (_film);
if (_video_waveform_dialog) {
_video_waveform_dialog->Destroy ();
void file_new ()
{
auto d = new FilmNameLocationDialog (this, _("New Film"), true);
+ ScopeGuard sg = [d]() { d->Destroy(); };
int const r = d->ShowModal ();
- if (r == wxID_OK && d->check_path() && maybe_save_then_delete_film<FilmChangedClosingDialog>()) {
- try {
- new_film (d->path(), d->template_name());
- } catch (boost::filesystem::filesystem_error& e) {
+ if (r != wxID_OK || !d->check_path() || !maybe_save_then_delete_film<FilmChangedClosingDialog>()) {
+ return;
+ }
+
+ try {
+ new_film (d->path(), d->template_name());
+ } catch (boost::filesystem::filesystem_error& e) {
#ifdef DCPOMATIC_WINDOWS
- string bad_chars = "<>:\"/|?*";
- string const filename = d->path().filename().string();
- string found_bad_chars;
- for (size_t i = 0; i < bad_chars.length(); ++i) {
- if (filename.find(bad_chars[i]) != string::npos && found_bad_chars.find(bad_chars[i]) == string::npos) {
- found_bad_chars += bad_chars[i];
- }
- }
- wxString message = _("Could not create folder to store film.");
- message += " ";
- if (!found_bad_chars.empty()) {
- message += wxString::Format (_("Try removing the %s characters from your folder name."), std_to_wx(found_bad_chars).data());
- } else {
- message += _("Please check that you do not have Windows controlled folder access enabled for DCP-o-matic.");
+ string bad_chars = "<>:\"/|?*";
+ string const filename = d->path().filename().string();
+ string found_bad_chars;
+ for (size_t i = 0; i < bad_chars.length(); ++i) {
+ if (filename.find(bad_chars[i]) != string::npos && found_bad_chars.find(bad_chars[i]) == string::npos) {
+ found_bad_chars += bad_chars[i];
}
- error_dialog (this, message, std_to_wx(e.what()));
+ }
+ wxString message = _("Could not create folder to store film.");
+ message += " ";
+ if (!found_bad_chars.empty()) {
+ message += wxString::Format (_("Try removing the %s characters from your folder name."), std_to_wx(found_bad_chars).data());
+ } else {
+ message += _("Please check that you do not have Windows controlled folder access enabled for DCP-o-matic.");
+ }
+ error_dialog (this, message, std_to_wx(e.what()));
#else
- error_dialog (this, _("Could not create folder to store film."), std_to_wx(e.what()));
+ error_dialog (this, _("Could not create folder to store film."), std_to_wx(e.what()));
#endif
- }
}
-
- d->Destroy ();
}
void file_open ()
std_to_wx (Config::instance()->default_directory_or (wx_to_std (wxStandardPaths::Get().GetDocumentsDir())).string ()),
wxDEFAULT_DIALOG_STYLE | wxDD_DIR_MUST_EXIST
);
+ ScopeGuard sg = [c]() { c->Destroy(); };
int r;
while (true) {
if (r == wxID_OK && maybe_save_then_delete_film<FilmChangedClosingDialog>()) {
load_film (wx_to_std (c->GetPath ()));
}
-
- c->Destroy ();
}
void file_save ()
void file_save_as_template ()
{
auto d = new SaveTemplateDialog (this);
- int const r = d->ShowModal ();
- if (r == wxID_OK) {
+ ScopeGuard sg = [d]() { d->Destroy(); };
+ if (d->ShowModal() == wxID_OK) {
Config::instance()->save_template (_film, d->name ());
}
- d->Destroy ();
}
void file_duplicate ()
{
auto d = new FilmNameLocationDialog (this, _("Duplicate Film"), false);
- int const r = d->ShowModal ();
+ ScopeGuard sg = [d]() { d->Destroy(); };
- if (r == wxID_OK && d->check_path() && maybe_save_film<FilmChangedDuplicatingDialog>()) {
+ if (d->ShowModal() == wxID_OK && d->check_path() && maybe_save_film<FilmChangedDuplicatingDialog>()) {
shared_ptr<Film> film (new Film (d->path()));
film->copy_from (_film);
film->set_name (d->path().filename().generic_string());
film->write_metadata ();
}
-
- d->Destroy ();
}
void file_duplicate_and_open ()
{
auto d = new FilmNameLocationDialog (this, _("Duplicate Film"), false);
- int const r = d->ShowModal ();
+ ScopeGuard sg = [d]() { d->Destroy(); };
- if (r == wxID_OK && d->check_path() && maybe_save_film<FilmChangedDuplicatingDialog>()) {
+ if (d->ShowModal() == wxID_OK && d->check_path() && maybe_save_film<FilmChangedDuplicatingDialog>()) {
shared_ptr<Film> film (new Film (d->path()));
film->copy_from (_film);
film->set_name (d->path().filename().generic_string());
film->write_metadata ();
set_film (film);
}
-
- d->Destroy ();
}
void file_close ()
DCPOMATIC_ASSERT (_clipboard);
auto d = new PasteDialog (this, static_cast<bool>(_clipboard->video), static_cast<bool>(_clipboard->audio), !_clipboard->text.empty());
- if (d->ShowModal() == wxID_OK) {
- for (auto i: _film_editor->content_panel()->selected()) {
- if (d->video() && i->video) {
- DCPOMATIC_ASSERT (_clipboard->video);
- i->video->take_settings_from (_clipboard->video);
- }
- if (d->audio() && i->audio) {
- DCPOMATIC_ASSERT (_clipboard->audio);
- i->audio->take_settings_from (_clipboard->audio);
- }
+ ScopeGuard sg = [d]() { d->Destroy(); };
+ if (d->ShowModal() != wxID_OK) {
+ return;
+ }
- if (d->text()) {
- auto j = i->text.begin ();
- auto k = _clipboard->text.begin ();
- while (j != i->text.end() && k != _clipboard->text.end()) {
- (*j)->take_settings_from (*k);
- ++j;
- ++k;
- }
+ for (auto i: _film_editor->content_panel()->selected()) {
+ if (d->video() && i->video) {
+ DCPOMATIC_ASSERT (_clipboard->video);
+ i->video->take_settings_from (_clipboard->video);
+ }
+ if (d->audio() && i->audio) {
+ DCPOMATIC_ASSERT (_clipboard->audio);
+ i->audio->take_settings_from (_clipboard->audio);
+ }
+
+ if (d->text()) {
+ auto j = i->text.begin ();
+ auto k = _clipboard->text.begin ();
+ while (j != i->text.end() && k != _clipboard->text.end()) {
+ (*j)->take_settings_from (*k);
+ ++j;
+ ++k;
}
}
}
- d->Destroy ();
}
void edit_select_all ()
_("Restore default preferences"),
wxYES_NO | wxYES_DEFAULT | wxICON_QUESTION
);
+ ScopeGuard sg = [d]() { d->Destroy(); };
- int const r = d->ShowModal ();
- d->Destroy ();
-
- if (r == wxID_YES) {
+ if (d->ShowModal() == wxID_YES) {
Config::restore_defaults ();
}
}
this, _("Specify ZIP file"), wxEmptyString, wxT("dcpomatic_config.zip"), wxT("ZIP files (*.zip)|*.zip"),
wxFD_SAVE | wxFD_OVERWRITE_PROMPT
);
+ ScopeGuard sg = [dialog]() { dialog->Destroy(); };
- int const r = dialog->ShowModal ();
- dialog->Destroy ();
-
- if (r == wxID_OK) {
+ if (dialog->ShowModal() == wxID_OK) {
save_all_config_as_zip (wx_to_std(dialog->GetPath()));
}
}
if (Config::instance()->show_hints_before_make_dcp()) {
auto hints = new HintsDialog (this, _film, false);
- int const r = hints->ShowModal();
- hints->Destroy ();
- if (r == wxID_CANCEL) {
+ ScopeGuard sg = [hints]() { hints->Destroy(); };
+ if (hints->ShowModal() == wxID_CANCEL) {
return;
}
}
if (Config::instance()->show_hints_before_make_dcp()) {
auto hints = new HintsDialog (this, _film, false);
- int const r = hints->ShowModal();
- hints->Destroy ();
- if (r == wxID_CANCEL) {
+ ScopeGuard sg = [hints]() { hints->Destroy(); };
+ if (hints->ShowModal() == wxID_CANCEL) {
return;
}
}
}
auto d = new SelfDKDMDialog (this, _film);
+ ScopeGuard sg = [d]() { d->Destroy(); };
if (d->ShowModal () != wxID_OK) {
- d->Destroy ();
return;
}
kdm->as_xml (path);
}
}
-
- d->Destroy ();
}
void jobs_export_video_file ()
{
auto d = new ExportVideoFileDialog (this, _film->isdcf_name(true));
- if (d->ShowModal() == wxID_OK) {
- if (boost::filesystem::exists(d->path())) {
- bool ok = confirm_dialog(
- this,
- wxString::Format (_("File %s already exists. Do you want to overwrite it?"), std_to_wx(d->path().string()).data())
- );
-
- if (!ok) {
- d->Destroy ();
- return;
- }
- }
+ ScopeGuard sg = [d]() { d->Destroy(); };
+ if (d->ShowModal() != wxID_OK) {
+ return;
+ }
- auto job = make_shared<TranscodeJob>(_film, TranscodeJob::ChangedBehaviour::EXAMINE_THEN_STOP);
- job->set_encoder (
- make_shared<FFmpegEncoder> (
- _film, job, d->path(), d->format(), d->mixdown_to_stereo(), d->split_reels(), d->split_streams(), d->x264_crf())
- );
- JobManager::instance()->add (job);
+ if (boost::filesystem::exists(d->path())) {
+ bool ok = confirm_dialog(
+ this,
+ wxString::Format (_("File %s already exists. Do you want to overwrite it?"), std_to_wx(d->path().string()).data())
+ );
+
+ if (!ok) {
+ return;
+ }
}
- d->Destroy ();
+
+ auto job = make_shared<TranscodeJob>(_film, TranscodeJob::ChangedBehaviour::EXAMINE_THEN_STOP);
+ job->set_encoder (
+ make_shared<FFmpegEncoder> (
+ _film, job, d->path(), d->format(), d->mixdown_to_stereo(), d->split_reels(), d->split_streams(), d->x264_crf())
+ );
+ JobManager::instance()->add (job);
}
void jobs_export_subtitles ()
{
auto d = new ExportSubtitlesDialog (this, _film->reels().size(), _film->interop());
- if (d->ShowModal() == wxID_OK) {
- auto job = make_shared<TranscodeJob>(_film, TranscodeJob::ChangedBehaviour::EXAMINE_THEN_STOP);
- job->set_encoder (
- make_shared<SubtitleEncoder>(_film, job, d->path(), _film->isdcf_name(true), d->split_reels(), d->include_font())
- );
- JobManager::instance()->add (job);
+ ScopeGuard sg = [d]() { d->Destroy(); };
+ if (d->ShowModal() != wxID_OK) {
+ return;
}
- d->Destroy ();
+ auto job = make_shared<TranscodeJob>(_film, TranscodeJob::ChangedBehaviour::EXAMINE_THEN_STOP);
+ job->set_encoder(
+ make_shared<SubtitleEncoder>(_film, job, d->path(), _film->isdcf_name(true), d->split_reels(), d->include_font())
+ );
+ JobManager::instance()->add(job);
}
void view_closed_captions ()
{
- _film_viewer->show_closed_captions ();
+ _film_viewer.show_closed_captions ();
}
void view_video_waveform ()
void tools_send_translations ()
{
auto d = new SendI18NDialog (this);
+ ScopeGuard sg = [d]() { d->Destroy(); };
if (d->ShowModal() == wxID_OK) {
string body;
body += d->name() + "\n";
}
}
}
-
- d->Destroy ();
}
void help_about ()
{
auto d = new AboutDialog (this);
+ ScopeGuard sg = [d]() { d->Destroy(); };
d->ShowModal ();
- d->Destroy ();
}
void help_report_a_problem ()
{
auto d = new ReportProblemDialog (this, _film);
+ ScopeGuard sg = [d]() { d->Destroy(); };
if (d->ShowModal () == wxID_OK) {
d->report ();
}
- d->Destroy ();
}
bool should_close ()
_("Unfinished jobs"),
wxYES_NO | wxYES_DEFAULT | wxICON_QUESTION
);
+ ScopeGuard sg = [d]() { d->Destroy(); };
- bool const r = d->ShowModal() == wxID_YES;
- d->Destroy ();
- return r;
+ return d->ShowModal() == wxID_YES;
}
void close (wxCloseEvent& ev)
ev.Skip ();
}
+ void active_jobs_changed()
+ {
+ /* ActiveJobsChanged can be called while JobManager holds a lock on its mutex, making
+ * the call to JobManager::get() in set_menu_sensitivity() deadlock unless we work around
+ * it by using an idle callback. This feels quite unpleasant.
+ */
+ signal_manager->when_idle(boost::bind(&DOMFrame::set_menu_sensitivity, this));
+ }
+
void set_menu_sensitivity ()
{
auto jobs = JobManager::instance()->get ();
if (uc->state() == UpdateChecker::State::YES) {
auto dialog = new UpdateDialog (this, uc->stable(), uc->test());
+ ScopeGuard sg = [dialog]() { dialog->Destroy(); };
dialog->ShowModal ();
- dialog->Destroy ();
} else if (uc->state() == UpdateChecker::State::FAILED) {
error_dialog (this, _("The DCP-o-matic download server could not be contacted."));
} else {
void start_stop_pressed ()
{
- if (_film_viewer->playing()) {
- _film_viewer->stop();
+ if (_film_viewer.playing()) {
+ _film_viewer.stop();
} else {
- _film_viewer->start();
+ _film_viewer.start();
}
}
void back_frame ()
{
- _film_viewer->seek_by (-_film_viewer->one_video_frame(), true);
+ _film_viewer.seek_by(-_film_viewer.one_video_frame(), true);
}
void forward_frame ()
{
- _film_viewer->seek_by (_film_viewer->one_video_frame(), true);
+ _film_viewer.seek_by(_film_viewer.one_video_frame(), true);
}
void analytics_message (string title, string html)
{
auto d = new HTMLDialog(this, std_to_wx(title), std_to_wx(html));
+ ScopeGuard sg = [d]() { d->Destroy(); };
d->ShowModal();
- d->Destroy();
}
void set_title ()
}
FilmEditor* _film_editor;
- std::shared_ptr<FilmViewer> _film_viewer;
+ LimitedFrameSplitter* _splitter;
+ wxPanel* _right_panel;
+ FilmViewer _film_viewer;
StandardControls* _controls;
VideoWaveformDialog* _video_waveform_dialog = nullptr;
SystemInformationDialog* _system_information_dialog = nullptr;
try {
wxInitAllImageHandlers ();
- Config::FailedToLoad.connect (boost::bind (&App::config_failed_to_load, this));
+ Config::FailedToLoad.connect(boost::bind(&App::config_failed_to_load, this, _1));
Config::Warning.connect (boost::bind (&App::config_warning, this, _1));
_splash = maybe_show_splash ();
if (Config::instance()->check_for_updates ()) {
UpdateChecker::instance()->run ();
}
+
+ auto release_notes = find_release_notes(gui_is_dark());
+ if (release_notes) {
+ auto notes = new HTMLDialog(nullptr, _("Release notes"), std_to_wx(*release_notes), true);
+ ScopeGuard sg = [notes]() { notes->Destroy(); };
+ notes->Centre();
+ notes->ShowModal();
+ }
}
catch (exception& e)
{
{
if (_splash) {
_splash->Destroy ();
- _splash = 0;
+ _splash = nullptr;
}
}
- void config_failed_to_load ()
+ void config_failed_to_load (Config::LoadFailure what)
{
- message_dialog (_frame, _("The existing configuration failed to load. Default values will be used instead. These may take a short time to create."));
+ report_config_load_failure(_frame, what);
}
void config_warning (string m)
_("Do nothing"),
Config::NAG_BAD_SIGNER_CHAIN_UTF8
);
- int const r = d->ShowModal ();
- d->Destroy ();
- return r == wxID_OK;
+ ScopeGuard sg = [d]() { d->Destroy(); };
+ return d->ShowModal() == wxID_OK;
}
case Config::BAD_SIGNER_VALIDITY_TOO_LONG:
{
_("Do nothing"),
Config::NAG_BAD_SIGNER_CHAIN_VALIDITY
);
- int const r = d->ShowModal ();
- d->Destroy ();
- return r == wxID_OK;
+ ScopeGuard sg = [d]() { d->Destroy(); };
+ return d->ShowModal() == wxID_OK;
}
case Config::BAD_SIGNER_INCONSISTENT:
{
"the certificate chain for signing DCPs and KDMs?"),
_("Close DCP-o-matic")
);
- int const r = d->ShowModal ();
- d->Destroy ();
- if (r != wxID_OK) {
+ ScopeGuard sg = [d]() { d->Destroy(); };
+ if (d->ShowModal() != wxID_OK) {
exit (EXIT_FAILURE);
}
return true;
"configuration before continuing."),
_("Close DCP-o-matic")
);
- int const r = d->ShowModal ();
- d->Destroy ();
- if (r != wxID_OK) {
+ ScopeGuard sg = [d]() { d->Destroy(); };
+ if (d->ShowModal() != wxID_OK) {
exit (EXIT_FAILURE);
}
return true;