X-Git-Url: https://git.carlh.net/gitweb/?p=dcpomatic.git;a=blobdiff_plain;f=src%2Ftools%2Fdcpomatic_playlist.cc;h=96dff56ed21ec169264eb6d822ae7f3a8ac5366e;hp=a81c0b730ef68873e22586e539a307d26223f14e;hb=ff3e116f48298bbee68a3f5679bffd509930c19b;hpb=1d08c7b46a8d8ef99e4fbd14225dcdf9f6cb4667 diff --git a/src/tools/dcpomatic_playlist.cc b/src/tools/dcpomatic_playlist.cc index a81c0b730..96dff56ed 100644 --- a/src/tools/dcpomatic_playlist.cc +++ b/src/tools/dcpomatic_playlist.cc @@ -1,5 +1,5 @@ /* - Copyright (C) 2018 Carl Hetherington + Copyright (C) 2018-2021 Carl Hetherington This file is part of DCP-o-matic. @@ -18,49 +18,80 @@ */ -#include "../wx/wx_util.h" -#include "../wx/wx_signal_manager.h" -#include "../wx/content_view.h" -#include "../lib/util.h" -#include "../lib/config.h" -#include "../lib/cross.h" -#include "../lib/film.h" -#include "../lib/dcp_content.h" -#include "../lib/spl_entry.h" -#include "../lib/spl.h" -#include -#include + +#include "wx/about_dialog.h" +#include "wx/content_view.h" +#include "wx/dcpomatic_button.h" +#include "wx/playlist_editor_config_dialog.h" +#include "wx/wx_signal_manager.h" +#include "wx/wx_util.h" +#include "lib/config.h" +#include "lib/cross.h" +#include "lib/dcp_content.h" +#include "lib/film.h" +#include "lib/spl.h" +#include "lib/spl_entry.h" +#include "lib/util.h" +#include +LIBDCP_DISABLE_WARNINGS #include +#include +#include +#include +#include +LIBDCP_ENABLE_WARNINGS + -using std::exception; using std::cout; +using std::exception; +using std::make_pair; +using std::make_shared; +using std::map; +using std::shared_ptr; using std::string; -using boost::optional; -using boost::shared_ptr; -using boost::weak_ptr; +using std::vector; +using std::weak_ptr; using boost::bind; -using boost::dynamic_pointer_cast; +using boost::optional; +using std::dynamic_pointer_cast; +#if BOOST_VERSION >= 106100 +using namespace boost::placeholders; +#endif + + +static +void +save_playlist(shared_ptr playlist) +{ + if (auto dir = Config::instance()->player_playlist_directory()) { + playlist->write(*dir / (playlist->id() + ".xml")); + } +} + class ContentDialog : public wxDialog, public ContentStore { public: - ContentDialog (wxWindow* parent, weak_ptr film) + ContentDialog (wxWindow* parent) : wxDialog (parent, wxID_ANY, _("Add content"), wxDefaultPosition, wxSize(800, 640)) , _content_view (new ContentView(this)) { _content_view->update (); - wxBoxSizer* overall_sizer = new wxBoxSizer (wxVERTICAL); + auto overall_sizer = new wxBoxSizer (wxVERTICAL); SetSizer (overall_sizer); overall_sizer->Add (_content_view, 1, wxEXPAND | wxALL, DCPOMATIC_DIALOG_BORDER); - wxSizer* buttons = CreateSeparatedButtonSizer (wxOK | wxCANCEL); + auto buttons = CreateSeparatedButtonSizer (wxOK | wxCANCEL); if (buttons) { overall_sizer->Add (buttons, wxSizerFlags().Expand().DoubleBorder()); } overall_sizer->Layout (); + + _content_view->Bind(wxEVT_LIST_ITEM_ACTIVATED, boost::bind(&ContentDialog::EndModal, this, wxID_OK)); + _config_changed_connection = Config::instance()->Changed.connect(boost::bind(&ContentView::update, _content_view)); } shared_ptr selected () const @@ -68,93 +99,313 @@ public: return _content_view->selected (); } - shared_ptr get (string digest) const + shared_ptr get (string digest) const override { return _content_view->get (digest); } private: ContentView* _content_view; + boost::signals2::scoped_connection _config_changed_connection; }; -class DOMFrame : public wxFrame + + +class PlaylistList { public: - explicit DOMFrame (wxString const & title) - : wxFrame (0, -1, title) - /* XXX: this is a bit of a hack, but we need it to be able to use the Content class hierarchy */ - , _film (new Film(optional())) - , _content_dialog (new ContentDialog(this, _film)) + PlaylistList (wxPanel* parent, ContentStore* content_store) + : _sizer (new wxBoxSizer(wxVERTICAL)) + , _content_store (content_store) + , _parent(parent) { - /* Use a panel as the only child of the Frame so that we avoid - the dark-grey background on Windows. - */ - wxPanel* overall_panel = new wxPanel (this, wxID_ANY); - wxBoxSizer* main_sizer = new wxBoxSizer (wxHORIZONTAL); + auto label = new wxStaticText (parent, wxID_ANY, wxEmptyString); + label->SetLabelMarkup (_("Playlists")); + _sizer->Add (label, 0, wxTOP | wxLEFT, DCPOMATIC_SIZER_GAP * 2); + + _list = new wxListCtrl ( + parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLC_REPORT | wxLC_SINGLE_SEL + ); + + _list->AppendColumn (_("Name"), wxLIST_FORMAT_LEFT, 840); + _list->AppendColumn (_("Length"), wxLIST_FORMAT_LEFT, 100); + + auto button_sizer = new wxBoxSizer (wxVERTICAL); + _new = new Button (parent, _("New")); + button_sizer->Add (_new, 0, wxEXPAND | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP); + _delete = new Button (parent, _("Delete")); + button_sizer->Add (_delete, 0, wxEXPAND | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP); + + auto list = new wxBoxSizer (wxHORIZONTAL); + list->Add (_list, 1, wxEXPAND | wxALL, DCPOMATIC_SIZER_GAP); + list->Add (button_sizer, 0, wxALL, DCPOMATIC_SIZER_GAP); + + _sizer->Add (list); + + load_playlists (); + + _list->Bind (wxEVT_COMMAND_LIST_ITEM_SELECTED, bind(&PlaylistList::selection_changed, this)); + _list->Bind (wxEVT_COMMAND_LIST_ITEM_DESELECTED, bind(&PlaylistList::selection_changed, this)); + _new->Bind (wxEVT_BUTTON, bind(&PlaylistList::new_playlist, this)); + _delete->Bind (wxEVT_BUTTON, bind(&PlaylistList::delete_playlist, this)); + + setup_sensitivity(); + } + + wxSizer* sizer () + { + return _sizer; + } + + shared_ptr first_playlist () const + { + if (_playlists.empty()) { + return {}; + } + + return _playlists.front (); + } + + boost::signals2::signal)> Edit; + +private: + void setup_sensitivity() + { + _delete->Enable(static_cast(selected())); + } + + void add_playlist_to_view (shared_ptr playlist) + { + wxListItem item; + item.SetId (_list->GetItemCount()); + long const N = _list->InsertItem (item); + _list->SetItem (N, 0, std_to_wx(playlist->name())); + } + + void add_playlist_to_model (shared_ptr playlist) + { + _playlists.push_back (playlist); + playlist->Changed.connect(bind(&PlaylistList::changed, this, weak_ptr(playlist), _1)); + } + + void changed(weak_ptr wp, SignalSPL::Change change) + { + auto playlist = wp.lock (); + if (!playlist) { + return; + } + + switch (change) { + case SignalSPL::Change::NAME: + { + int N = 0; + for (auto i: _playlists) { + if (i == playlist) { + _list->SetItem (N, 0, std_to_wx(i->name())); + } + ++N; + } + break; + } + case SignalSPL::Change::CONTENT: + save_playlist(playlist); + break; + } + } + + void load_playlists () + { + auto path = Config::instance()->player_playlist_directory(); + if (!path) { + return; + } + + _list->DeleteAllItems (); + _playlists.clear (); + for (auto i: boost::filesystem::directory_iterator(*path)) { + auto spl = make_shared(); + try { + spl->read (i, _content_store); + add_playlist_to_model (spl); + } catch (...) {} + } + + for (auto i: _playlists) { + add_playlist_to_view (i); + } + } + + void new_playlist () + { + auto dir = Config::instance()->player_playlist_directory(); + if (!dir) { + error_dialog(_parent, _("No playlist folder is specified in preferences. Please set one and then try again.")); + return; + } + + shared_ptr spl (new SignalSPL(wx_to_std(_("New Playlist")))); + add_playlist_to_model (spl); + add_playlist_to_view (spl); + _list->SetItemState (_list->GetItemCount() - 1, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED); + } + + boost::optional selected() const + { + long int selected = _list->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); + if (selected < 0 || selected >= int(_playlists.size())) { + return {}; + } + + return selected; + } + + void delete_playlist () + { + auto index = selected(); + if (!index) { + return; + } + + auto dir = Config::instance()->player_playlist_directory(); + if (!dir) { + return; + } + + boost::filesystem::remove(*dir / (_playlists[*index]->id() + ".xml")); + _list->DeleteItem(*index); + _playlists.erase(_playlists.begin() + *index); + + Edit(shared_ptr()); + } + + void selection_changed () + { + long int selected = _list->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); + if (selected < 0 || selected >= int(_playlists.size())) { + Edit (shared_ptr()); + } else { + Edit (_playlists[selected]); + } + + setup_sensitivity(); + } + + wxBoxSizer* _sizer; + wxListCtrl* _list; + wxButton* _new; + wxButton* _delete; + vector> _playlists; + ContentStore* _content_store; + wxWindow* _parent; +}; + + +class PlaylistContent +{ +public: + PlaylistContent (wxPanel* parent, ContentDialog* content_dialog) + : _content_dialog (content_dialog) + , _sizer (new wxBoxSizer(wxVERTICAL)) + { + auto title = new wxBoxSizer (wxHORIZONTAL); + auto label = new wxStaticText (parent, wxID_ANY, wxEmptyString); + label->SetLabelMarkup (_("Playlist:")); + title->Add (label, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, DCPOMATIC_SIZER_GAP); + _name = new wxTextCtrl (parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(400, -1)); + title->Add (_name, 0, wxRIGHT, DCPOMATIC_SIZER_GAP); + _save_name = new Button(parent, _("Save")); + title->Add(_save_name); + _sizer->Add (title, 0, wxTOP | wxLEFT, DCPOMATIC_SIZER_GAP * 2); + + auto list = new wxBoxSizer (wxHORIZONTAL); _list = new wxListCtrl ( - overall_panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLC_REPORT | wxLC_SINGLE_SEL + parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLC_REPORT | wxLC_SINGLE_SEL ); _list->AppendColumn (_("Name"), wxLIST_FORMAT_LEFT, 400); _list->AppendColumn (_("CPL"), wxLIST_FORMAT_LEFT, 350); - _list->AppendColumn (_("Type"), wxLIST_FORMAT_CENTRE, 100); - _list->AppendColumn (_("Format"), wxLIST_FORMAT_CENTRE, 75); + _list->AppendColumn (_("Type"), wxLIST_FORMAT_LEFT, 100); _list->AppendColumn (_("Encrypted"), wxLIST_FORMAT_CENTRE, 90); - _list->AppendColumn (_("Skippable"), wxLIST_FORMAT_CENTRE, 90); - _list->AppendColumn (_("Disable timeline"), wxLIST_FORMAT_CENTRE, 125); - _list->AppendColumn (_("Stop after play"), wxLIST_FORMAT_CENTRE, 125); - wxImageList* images = new wxImageList (16, 16); + auto images = new wxImageList (16, 16); wxIcon tick_icon; wxIcon no_tick_icon; -#ifdef DCPOMATIX_OSX - tick_icon.LoadFile ("tick.png", wxBITMAP_TYPE_PNG_RESOURCE); - no_tick_icon.LoadFile ("no_tick.png", wxBITMAP_TYPE_PNG_RESOURCE); -#else - boost::filesystem::path tick_path = shared_path() / "tick.png"; - tick_icon.LoadFile (std_to_wx(tick_path.string())); - boost::filesystem::path no_tick_path = shared_path() / "no_tick.png"; - no_tick_icon.LoadFile (std_to_wx(no_tick_path.string())); -#endif + tick_icon.LoadFile (bitmap_path("tick.png"), wxBITMAP_TYPE_PNG); + no_tick_icon.LoadFile (bitmap_path("no_tick.png"), wxBITMAP_TYPE_PNG); images->Add (tick_icon); images->Add (no_tick_icon); _list->SetImageList (images, wxIMAGE_LIST_SMALL); - main_sizer->Add (_list, 1, wxEXPAND | wxALL, DCPOMATIC_SIZER_GAP); + list->Add (_list, 1, wxEXPAND | wxALL, DCPOMATIC_SIZER_GAP); - wxBoxSizer* button_sizer = new wxBoxSizer (wxVERTICAL); - _up = new wxButton (overall_panel, wxID_ANY, _("Up")); - _down = new wxButton (overall_panel, wxID_ANY, _("Down")); - _add = new wxButton (overall_panel, wxID_ANY, _("Add")); - _remove = new wxButton (overall_panel, wxID_ANY, _("Remove")); - _save = new wxButton (overall_panel, wxID_ANY, _("Save playlist")); - _load = new wxButton (overall_panel, wxID_ANY, _("Load playlist")); + auto button_sizer = new wxBoxSizer (wxVERTICAL); + _up = new Button (parent, _("Up")); + _down = new Button (parent, _("Down")); + _add = new Button (parent, _("Add")); + _remove = new Button (parent, _("Remove")); button_sizer->Add (_up, 0, wxEXPAND | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP); button_sizer->Add (_down, 0, wxEXPAND | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP); button_sizer->Add (_add, 0, wxEXPAND | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP); button_sizer->Add (_remove, 0, wxEXPAND | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP); - button_sizer->Add (_save, 0, wxEXPAND | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP); - button_sizer->Add (_load, 0, wxEXPAND | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP); - - main_sizer->Add (button_sizer, 0, wxALL, DCPOMATIC_SIZER_GAP); - overall_panel->SetSizer (main_sizer); - - _list->Bind (wxEVT_LEFT_DOWN, bind(&DOMFrame::list_left_click, this, _1)); - _list->Bind (wxEVT_COMMAND_LIST_ITEM_SELECTED, boost::bind (&DOMFrame::selection_changed, this)); - _list->Bind (wxEVT_COMMAND_LIST_ITEM_DESELECTED, boost::bind (&DOMFrame::selection_changed, this)); - _up->Bind (wxEVT_BUTTON, bind(&DOMFrame::up_clicked, this)); - _down->Bind (wxEVT_BUTTON, bind(&DOMFrame::down_clicked, this)); - _add->Bind (wxEVT_BUTTON, bind(&DOMFrame::add_clicked, this)); - _remove->Bind (wxEVT_BUTTON, bind(&DOMFrame::remove_clicked, this)); - _save->Bind (wxEVT_BUTTON, bind(&DOMFrame::save_clicked, this)); - _load->Bind (wxEVT_BUTTON, bind(&DOMFrame::load_clicked, this)); + list->Add (button_sizer, 0, wxALL, DCPOMATIC_SIZER_GAP); + + _sizer->Add (list); + + _list->Bind (wxEVT_COMMAND_LIST_ITEM_SELECTED, bind(&PlaylistContent::setup_sensitivity, this)); + _list->Bind (wxEVT_COMMAND_LIST_ITEM_DESELECTED, bind(&PlaylistContent::setup_sensitivity, this)); + _name->Bind (wxEVT_TEXT, bind(&PlaylistContent::name_changed, this)); + _save_name->bind(&PlaylistContent::save_name_clicked, this); + _up->Bind (wxEVT_BUTTON, bind(&PlaylistContent::up_clicked, this)); + _down->Bind (wxEVT_BUTTON, bind(&PlaylistContent::down_clicked, this)); + _add->Bind (wxEVT_BUTTON, bind(&PlaylistContent::add_clicked, this)); + _remove->Bind (wxEVT_BUTTON, bind(&PlaylistContent::remove_clicked, this)); + + setup_sensitivity(); + } + + wxSizer* sizer () + { + return _sizer; + } + + void set (shared_ptr playlist) + { + _playlist = playlist; + _list->DeleteAllItems (); + if (_playlist) { + for (auto i: _playlist->get()) { + add (i); + } + _name->SetValue (std_to_wx(_playlist->name())); + } else { + _name->SetValue (wxT("")); + } setup_sensitivity (); } + shared_ptr playlist () const + { + return _playlist; + } + + private: + void save_name_clicked() + { + if (_playlist) { + _playlist->set_name(wx_to_std(_name->GetValue())); + save_playlist(_playlist); + } + setup_sensitivity(); + } + + void name_changed () + { + setup_sensitivity(); + } void add (SPLEntry e) { @@ -162,76 +413,40 @@ private: item.SetId (_list->GetItemCount()); long const N = _list->InsertItem (item); set_item (N, e); - _playlist.add (e); - } - - void selection_changed () - { - setup_sensitivity (); } void set_item (long N, SPLEntry e) { _list->SetItem (N, 0, std_to_wx(e.name)); _list->SetItem (N, 1, std_to_wx(e.id)); - _list->SetItem (N, 2, std_to_wx(dcp::content_kind_to_string(e.kind))); - _list->SetItem (N, 3, e.type == SPLEntry::DCP ? _("DCP") : _("E-cinema")); - _list->SetItem (N, 4, e.encrypted ? _("Y") : _("N")); - _list->SetItem (N, COLUMN_SKIPPABLE, wxEmptyString, e.skippable ? 0 : 1); - _list->SetItem (N, COLUMN_DISABLE_TIMELINE, wxEmptyString, e.disable_timeline ? 0 : 1); - _list->SetItem (N, COLUMN_STOP_AFTER_PLAY, wxEmptyString, e.stop_after_play ? 0 : 1); + _list->SetItem (N, 2, std_to_wx(e.kind->name())); + _list->SetItem (N, 3, e.encrypted ? S_("Question|Y") : S_("Question|N")); } void setup_sensitivity () { + bool const have_list = static_cast(_playlist); int const num_selected = _list->GetSelectedItemCount (); long int selected = _list->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED); - _up->Enable (selected > 0); - _down->Enable (selected != -1 && selected < (_list->GetItemCount() - 1)); - _remove->Enable (num_selected > 0); - } - - void list_left_click (wxMouseEvent& ev) - { - int flags; - long item = _list->HitTest (ev.GetPosition(), flags, 0); - int x = ev.GetPosition().x; - optional column; - for (int i = 0; i < _list->GetColumnCount(); ++i) { - x -= _list->GetColumnWidth (i); - if (x < 0) { - column = i; - break; - } - } - - if (item != -1 && column) { - switch (*column) { - case COLUMN_SKIPPABLE: - _playlist[item].skippable = !_playlist[item].skippable; - break; - case COLUMN_DISABLE_TIMELINE: - _playlist[item].disable_timeline = !_playlist[item].disable_timeline; - break; - case COLUMN_STOP_AFTER_PLAY: - _playlist[item].stop_after_play = !_playlist[item].stop_after_play; - break; - default: - ev.Skip (); - } - set_item (item, _playlist[item]); - } else { - ev.Skip (); - } + _name->Enable (have_list); + _save_name->Enable(_playlist && _playlist->name() != wx_to_std(_name->GetValue())); + _list->Enable (have_list); + _up->Enable (have_list && selected > 0); + _down->Enable (have_list && selected != -1 && selected < (_list->GetItemCount() - 1)); + _add->Enable (have_list); + _remove->Enable (have_list && num_selected > 0); } void add_clicked () { int const r = _content_dialog->ShowModal (); if (r == wxID_OK) { - shared_ptr content = _content_dialog->selected (); + auto content = _content_dialog->selected (); if (content) { - add (SPLEntry(content)); + SPLEntry e (content); + add (e); + DCPOMATIC_ASSERT (_playlist); + _playlist->add (e); } } } @@ -243,12 +458,12 @@ private: return; } - SPLEntry tmp = _playlist[s]; - _playlist[s] = _playlist[s-1]; - _playlist[s-1] = tmp; + DCPOMATIC_ASSERT (_playlist); + + _playlist->swap(s, s - 1); - set_item (s - 1, _playlist[s-1]); - set_item (s, _playlist[s]); + set_item (s - 1, (*_playlist)[s-1]); + set_item (s, (*_playlist)[s]); } void down_clicked () @@ -258,12 +473,12 @@ private: return; } - SPLEntry tmp = _playlist[s]; - _playlist[s] = _playlist[s+1]; - _playlist[s+1] = tmp; + DCPOMATIC_ASSERT (_playlist); - set_item (s + 1, _playlist[s+1]); - set_item (s, _playlist[s]); + _playlist->swap(s, s + 1); + + set_item (s + 1, (*_playlist)[s+1]); + set_item (s, (*_playlist)[s]); } void remove_clicked () @@ -273,51 +488,143 @@ private: return; } - _playlist.remove (s); + DCPOMATIC_ASSERT (_playlist); + _playlist->remove (s); _list->DeleteItem (s); } - void save_clicked () + ContentDialog* _content_dialog; + wxBoxSizer* _sizer; + wxTextCtrl* _name; + Button* _save_name; + wxListCtrl* _list; + wxButton* _up; + wxButton* _down; + wxButton* _add; + wxButton* _remove; + shared_ptr _playlist; +}; + + +class DOMFrame : public wxFrame +{ +public: + explicit DOMFrame (wxString const & title) + : wxFrame (nullptr, wxID_ANY, title) + , _content_dialog (new ContentDialog(this)) + , _config_dialog (nullptr) + { + auto bar = new wxMenuBar; + setup_menu (bar); + SetMenuBar (bar); + + /* 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 sizer = new wxBoxSizer (wxVERTICAL); + + _playlist_list = new PlaylistList (overall_panel, _content_dialog); + _playlist_content = new PlaylistContent (overall_panel, _content_dialog); + + sizer->Add (_playlist_list->sizer()); + sizer->Add (_playlist_content->sizer()); + + overall_panel->SetSizer (sizer); + + _playlist_list->Edit.connect (bind(&DOMFrame::change_playlist, this, _1)); + + Bind (wxEVT_MENU, boost::bind (&DOMFrame::file_exit, this), wxID_EXIT); + Bind (wxEVT_MENU, boost::bind (&DOMFrame::help_about, this), wxID_ABOUT); + Bind (wxEVT_MENU, boost::bind (&DOMFrame::edit_preferences, this), wxID_PREFERENCES); + + _config_changed_connection = Config::instance()->Changed.connect(boost::bind(&DOMFrame::config_changed, this)); + } + +private: + + void file_exit () { - wxFileDialog* d = new wxFileDialog (this, _("Select playlist file"), wxEmptyString, wxEmptyString, wxT("XML files (*.xml)|*.xml"), wxFD_SAVE | wxFD_OVERWRITE_PROMPT); - if (d->ShowModal() == wxID_OK) { - _playlist.write (wx_to_std(d->GetPath())); + /* false here allows the close handler to veto the close request */ + Close (false); + } + + void help_about () + { + auto d = new AboutDialog (this); + d->ShowModal (); + d->Destroy (); + } + + void edit_preferences () + { + if (!_config_dialog) { + _config_dialog = create_playlist_editor_config_dialog (); } + _config_dialog->Show (this); } - void load_clicked () + void change_playlist (shared_ptr playlist) { - wxFileDialog* d = new wxFileDialog (this, _("Select playlist file"), wxEmptyString, wxEmptyString, wxT("XML files (*.xml)|*.xml")); - if (d->ShowModal() == wxID_OK) { - _list->DeleteAllItems (); - if (!_playlist.read (wx_to_std(d->GetPath()), _content_dialog)) { - BOOST_FOREACH (SPLEntry i, _playlist.get()) { - add (i); - } - } else { - error_dialog (this, _("Some content in this playlist was not found.")); - } + auto old = _playlist_content->playlist (); + if (old) { + save_playlist (old); } + _playlist_content->set (playlist); } - wxListCtrl* _list; - wxButton* _up; - wxButton* _down; - wxButton* _add; - wxButton* _remove; - wxButton* _save; - wxButton* _load; - boost::shared_ptr _film; - SPL _playlist; - ContentDialog* _content_dialog; + void setup_menu (wxMenuBar* m) + { + auto file = new wxMenu; +#ifdef __WXOSX__ + file->Append (wxID_EXIT, _("&Exit")); +#else + file->Append (wxID_EXIT, _("&Quit")); +#endif + +#ifndef __WXOSX__ + auto edit = new wxMenu; + edit->Append (wxID_PREFERENCES, _("&Preferences...\tCtrl-P")); +#endif + + auto help = new wxMenu; +#ifdef __WXOSX__ + help->Append (wxID_ABOUT, _("About DCP-o-matic")); +#else + help->Append (wxID_ABOUT, _("About")); +#endif + + m->Append (file, _("&File")); +#ifndef __WXOSX__ + m->Append (edit, _("&Edit")); +#endif + m->Append (help, _("&Help")); + } - enum { - COLUMN_SKIPPABLE = 5, - COLUMN_DISABLE_TIMELINE = 6, - COLUMN_STOP_AFTER_PLAY = 7 - }; + + void config_changed () + { + try { + Config::instance()->write_config(); + } catch (exception& e) { + error_dialog ( + this, + wxString::Format ( + _("Could not write to config file at %s. Your changes have not been saved."), + std_to_wx (Config::instance()->cinemas_file().string()).data() + ) + ); + } + } + + ContentDialog* _content_dialog; + PlaylistList* _playlist_list; + PlaylistContent* _playlist_content; + wxPreferencesEditor* _config_dialog; + boost::signals2::scoped_connection _config_changed_connection; }; + /** @class App * @brief The magic App class for wxWidgets. */ @@ -326,15 +633,16 @@ class App : public wxApp public: App () : wxApp () - , _frame (0) + , _frame (nullptr) {} private: - bool OnInit () + bool OnInit () override try { - SetAppName (_("DCP-o-matic KDM Creator")); + wxInitAllImageHandlers (); + SetAppName (_("DCP-o-matic Playlist Editor")); if (!wxApp::OnInit()) { return false; @@ -344,10 +652,8 @@ private: unsetenv ("UBUNTU_MENUPROXY"); #endif - #ifdef __WXOSX__ - ProcessSerialNumber serial; - GetCurrentProcess (&serial); - TransformProcessType (&serial, kProcessTransformToForegroundApplication); +#ifdef DCPOMATIC_OSX + make_foreground_application (); #endif dcpomatic_setup_path_encoding (); @@ -387,7 +693,7 @@ private: } /* An unhandled exception has occurred inside the main event loop */ - bool OnExceptionInMainLoop () + bool OnExceptionInMainLoop () override { try { throw; @@ -416,7 +722,7 @@ private: return false; } - void OnUnhandledException () + void OnUnhandledException () override { error_dialog (0, _("An unknown exception occurred.") + " " + REPORT_PROBLEM); }