Extract save_playlist().
[dcpomatic.git] / src / tools / dcpomatic_playlist.cc
index 04950028af6dcfaec08a5ed663ebe4fb4f1391eb..636a50f8ab9d6a3daafd36e564ba8d2ef65c871e 100644 (file)
@@ -1,5 +1,5 @@
 /*
-    Copyright (C) 2018-2020 Carl Hetherington <cth@carlh.net>
+    Copyright (C) 2018-2021 Carl Hetherington <cth@carlh.net>
 
     This file is part of DCP-o-matic.
 
 
 */
 
-#include "../wx/wx_util.h"
-#include "../wx/wx_signal_manager.h"
-#include "../wx/content_view.h"
-#include "../wx/dcpomatic_button.h"
-#include "../wx/about_dialog.h"
-#include "../wx/playlist_editor_config_dialog.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 <wx/wx.h>
-#include <wx/listctrl.h>
+
+#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 <dcp/warnings.h>
+LIBDCP_DISABLE_WARNINGS
 #include <wx/imaglist.h>
-#include <wx/spinctrl.h>
+#include <wx/listctrl.h>
 #include <wx/preferences.h>
-#ifdef __WXOSX__
-#include <ApplicationServices/ApplicationServices.h>
-#endif
-#include <boost/foreach.hpp>
+#include <wx/spinctrl.h>
+#include <wx/wx.h>
+LIBDCP_ENABLE_WARNINGS
+
 
-using std::exception;
 using std::cout;
-using std::string;
-using std::map;
+using std::exception;
 using std::make_pair;
+using std::make_shared;
+using std::map;
+using std::shared_ptr;
+using std::string;
 using std::vector;
-using boost::optional;
-using boost::shared_ptr;
-using boost::weak_ptr;
+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<const SPL> playlist)
+{
+       if (auto dir = Config::instance()->player_playlist_directory()) {
+               playlist->write(*dir / (playlist->id() + ".xml"));
+       }
+}
+
 
 class ContentDialog : public wxDialog, public ContentStore
 {
@@ -62,17 +78,19 @@ public:
        {
                _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 ();
+
+               _config_changed_connection = Config::instance()->Changed.connect(boost::bind(&ContentView::update, _content_view));
        }
 
        shared_ptr<Content> selected () const
@@ -80,13 +98,14 @@ public:
                return _content_view->selected ();
        }
 
-       shared_ptr<Content> get (string digest) const
+       shared_ptr<Content> get (string digest) const override
        {
                return _content_view->get (digest);
        }
 
 private:
        ContentView* _content_view;
+       boost::signals2::scoped_connection _config_changed_connection;
 };
 
 
@@ -97,8 +116,9 @@ public:
        PlaylistList (wxPanel* parent, ContentStore* content_store)
                : _sizer (new wxBoxSizer(wxVERTICAL))
                , _content_store (content_store)
+               , _parent(parent)
        {
-               wxStaticText* label = new wxStaticText (parent, wxID_ANY, wxEmptyString);
+               auto label = new wxStaticText (parent, wxID_ANY, wxEmptyString);
                label->SetLabelMarkup (_("<b>Playlists</b>"));
                _sizer->Add (label, 0, wxTOP | wxLEFT, DCPOMATIC_SIZER_GAP * 2);
 
@@ -109,13 +129,13 @@ public:
                _list->AppendColumn (_("Name"), wxLIST_FORMAT_LEFT, 840);
                _list->AppendColumn (_("Length"), wxLIST_FORMAT_LEFT, 100);
 
-               wxBoxSizer* button_sizer = new wxBoxSizer (wxVERTICAL);
+               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);
 
-               wxSizer* list = new wxBoxSizer (wxHORIZONTAL);
+               auto list = new wxBoxSizer (wxHORIZONTAL);
                list->Add (_list, 1, wxEXPAND | wxALL, DCPOMATIC_SIZER_GAP);
                list->Add (button_sizer, 0, wxALL, DCPOMATIC_SIZER_GAP);
 
@@ -127,6 +147,8 @@ public:
                _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 ()
@@ -137,7 +159,7 @@ public:
        shared_ptr<SignalSPL> first_playlist () const
        {
                if (_playlists.empty()) {
-                       return shared_ptr<SignalSPL>();
+                       return {};
                }
 
                return _playlists.front ();
@@ -146,6 +168,11 @@ public:
        boost::signals2::signal<void (shared_ptr<SignalSPL>)> Edit;
 
 private:
+       void setup_sensitivity()
+       {
+               _delete->Enable(static_cast<bool>(selected()));
+       }
+
        void add_playlist_to_view (shared_ptr<const SignalSPL> playlist)
        {
                wxListItem item;
@@ -157,72 +184,97 @@ private:
        void add_playlist_to_model (shared_ptr<SignalSPL> playlist)
        {
                _playlists.push_back (playlist);
-               playlist->NameChanged.connect (bind(&PlaylistList::name_changed, this, weak_ptr<SignalSPL>(playlist)));
+               playlist->Changed.connect(bind(&PlaylistList::changed, this, weak_ptr<SignalSPL>(playlist), _1));
        }
 
-       void name_changed (weak_ptr<SignalSPL> wp)
+       void changed(weak_ptr<SignalSPL> wp, SignalSPL::Change change)
        {
-               shared_ptr<SignalSPL> playlist = wp.lock ();
+               auto playlist = wp.lock ();
                if (!playlist) {
                        return;
                }
 
-               int N = 0;
-               BOOST_FOREACH (shared_ptr<SignalSPL> i, _playlists) {
-                       if (i == playlist) {
-                               _list->SetItem (N, 0, std_to_wx(i->name()));
+               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;
                        }
-                       ++N;
+                       break;
+               }
+               case SignalSPL::Change::CONTENT:
+                       save_playlist(playlist);
+                       break;
                }
        }
 
        void load_playlists ()
        {
-               optional<boost::filesystem::path> path = Config::instance()->player_playlist_directory();
+               auto path = Config::instance()->player_playlist_directory();
                if (!path) {
                        return;
                }
 
                _list->DeleteAllItems ();
                _playlists.clear ();
-               for (boost::filesystem::directory_iterator i(*path); i != boost::filesystem::directory_iterator(); ++i) {
-                       shared_ptr<SignalSPL> spl(new SignalSPL);
+               for (auto i: boost::filesystem::directory_iterator(*path)) {
+                       auto spl = make_shared<SignalSPL>();
                        try {
-                               spl->read (*i, _content_store);
+                               spl->read (i, _content_store);
                                add_playlist_to_model (spl);
                        } catch (...) {}
                }
 
-               BOOST_FOREACH (shared_ptr<SignalSPL> i, _playlists) {
+               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<SignalSPL> 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);
        }
 
-       void delete_playlist ()
+       boost::optional<int> selected() const
        {
-               long int selected = _list->GetNextItem (-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
+               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;
                }
 
-               optional<boost::filesystem::path> dir = Config::instance()->player_playlist_directory();
+               auto dir = Config::instance()->player_playlist_directory();
                if (!dir) {
                        return;
                }
 
-               boost::filesystem::remove (*dir / (_playlists[selected]->id() + ".xml"));
-               _list->DeleteItem (selected);
-               _playlists.erase (_playlists.begin() + selected);
+               boost::filesystem::remove(*dir / (_playlists[*index]->id() + ".xml"));
+               _list->DeleteItem(*index);
+               _playlists.erase(_playlists.begin() + *index);
 
-               Edit (shared_ptr<SignalSPL>());
+               Edit(shared_ptr<SignalSPL>());
        }
 
        void selection_changed ()
@@ -233,14 +285,17 @@ private:
                } else {
                        Edit (_playlists[selected]);
                }
+
+               setup_sensitivity();
        }
 
        wxBoxSizer* _sizer;
        wxListCtrl* _list;
        wxButton* _new;
        wxButton* _delete;
-       vector<shared_ptr<SignalSPL> > _playlists;
+       vector<shared_ptr<SignalSPL>> _playlists;
        ContentStore* _content_store;
+       wxWindow* _parent;
 };
 
 
@@ -251,15 +306,15 @@ public:
                : _content_dialog (content_dialog)
                , _sizer (new wxBoxSizer(wxVERTICAL))
        {
-               wxBoxSizer* title = new wxBoxSizer (wxHORIZONTAL);
-               wxStaticText* label = new wxStaticText (parent, wxID_ANY, wxEmptyString);
+               auto title = new wxBoxSizer (wxHORIZONTAL);
+               auto label = new wxStaticText (parent, wxID_ANY, wxEmptyString);
                label->SetLabelMarkup (_("<b>Playlist:</b>"));
                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);
                _sizer->Add (title, 0, wxTOP | wxLEFT, DCPOMATIC_SIZER_GAP * 2);
 
-               wxBoxSizer* list = new wxBoxSizer (wxHORIZONTAL);
+               auto list = new wxBoxSizer (wxHORIZONTAL);
 
                _list = new wxListCtrl (
                        parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLC_REPORT | wxLC_SINGLE_SEL
@@ -270,18 +325,11 @@ public:
                _list->AppendColumn (_("Type"), wxLIST_FORMAT_LEFT, 100);
                _list->AppendColumn (_("Encrypted"), wxLIST_FORMAT_CENTRE, 90);
 
-               wxImageList* images = new wxImageList (16, 16);
+               auto images = new wxImageList (16, 16);
                wxIcon tick_icon;
                wxIcon no_tick_icon;
-#ifdef DCPOMATIC_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()), wxBITMAP_TYPE_PNG);
-               boost::filesystem::path no_tick_path = shared_path() / "no_tick.png";
-               no_tick_icon.LoadFile (std_to_wx(no_tick_path.string()), wxBITMAP_TYPE_PNG);
-#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);
 
@@ -289,7 +337,7 @@ public:
 
                list->Add (_list, 1, wxEXPAND | wxALL, DCPOMATIC_SIZER_GAP);
 
-               wxBoxSizer* button_sizer = new wxBoxSizer (wxVERTICAL);
+               auto button_sizer = new wxBoxSizer (wxVERTICAL);
                _up = new Button (parent, _("Up"));
                _down = new Button (parent, _("Down"));
                _add = new Button (parent, _("Add"));
@@ -310,6 +358,8 @@ public:
                _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 ()
@@ -322,7 +372,7 @@ public:
                _playlist = playlist;
                _list->DeleteAllItems ();
                if (_playlist) {
-                       BOOST_FOREACH (SPLEntry i, _playlist->get()) {
+                       for (auto i: _playlist->get()) {
                                add (i);
                        }
                        _name->SetValue (std_to_wx(_playlist->name()));
@@ -358,7 +408,7 @@ private:
        {
                _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, 2, std_to_wx(e.kind->name()));
                _list->SetItem (N, 3, e.encrypted ? S_("Question|Y") : S_("Question|N"));
        }
 
@@ -379,7 +429,7 @@ private:
        {
                int const r = _content_dialog->ShowModal ();
                if (r == wxID_OK) {
-                       shared_ptr<Content> content = _content_dialog->selected ();
+                       auto content = _content_dialog->selected ();
                        if (content) {
                                SPLEntry e (content);
                                add (e);
@@ -398,9 +448,7 @@ private:
 
                DCPOMATIC_ASSERT (_playlist);
 
-               SPLEntry tmp = (*_playlist)[s];
-               (*_playlist)[s] = (*_playlist)[s-1];
-               (*_playlist)[s-1] = tmp;
+               _playlist->swap(s, s - 1);
 
                set_item (s - 1, (*_playlist)[s-1]);
                set_item (s, (*_playlist)[s]);
@@ -415,9 +463,7 @@ private:
 
                DCPOMATIC_ASSERT (_playlist);
 
-               SPLEntry tmp = (*_playlist)[s];
-               (*_playlist)[s] = (*_playlist)[s+1];
-               (*_playlist)[s+1] = tmp;
+               _playlist->swap(s, s + 1);
 
                set_item (s + 1, (*_playlist)[s+1]);
                set_item (s, (*_playlist)[s]);
@@ -451,19 +497,19 @@ class DOMFrame : public wxFrame
 {
 public:
        explicit DOMFrame (wxString const & title)
-               : wxFrame (0, -1, title)
+               : wxFrame (nullptr, wxID_ANY, title)
                , _content_dialog (new ContentDialog(this))
-               , _config_dialog (0)
+               , _config_dialog (nullptr)
        {
-               wxMenuBar* bar = new wxMenuBar;
+               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.
                */
-               wxPanel* overall_panel = new wxPanel (this, wxID_ANY);
-               wxBoxSizer* sizer = new wxBoxSizer (wxVERTICAL);
+               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);
@@ -475,11 +521,11 @@ public:
 
                _playlist_list->Edit.connect (bind(&DOMFrame::change_playlist, this, _1));
 
-               _playlist_content->set (_playlist_list->first_playlist());
-
                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:
@@ -492,7 +538,7 @@ private:
 
        void help_about ()
        {
-               AboutDialog* d = new AboutDialog (this);
+               auto d = new AboutDialog (this);
                d->ShowModal ();
                d->Destroy ();
        }
@@ -507,26 +553,16 @@ private:
 
        void change_playlist (shared_ptr<SignalSPL> playlist)
        {
-               shared_ptr<SignalSPL> old = _playlist_content->playlist ();
+               auto old = _playlist_content->playlist ();
                if (old) {
                        save_playlist (old);
                }
                _playlist_content->set (playlist);
        }
 
-       void save_playlist (shared_ptr<SignalSPL> playlist)
-       {
-               optional<boost::filesystem::path> dir = Config::instance()->player_playlist_directory();
-               if (!dir) {
-                       error_dialog (this, _("No playlist folder is specified in preferences.  Please set on and then try again."));
-                       return;
-               }
-               playlist->write (*dir / (playlist->id() + ".xml"));
-       }
-
        void setup_menu (wxMenuBar* m)
        {
-               wxMenu* file = new wxMenu;
+               auto file = new wxMenu;
 #ifdef __WXOSX__
                file->Append (wxID_EXIT, _("&Exit"));
 #else
@@ -534,11 +570,11 @@ private:
 #endif
 
 #ifndef __WXOSX__
-               wxMenu* edit = new wxMenu;
+               auto edit = new wxMenu;
                edit->Append (wxID_PREFERENCES, _("&Preferences...\tCtrl-P"));
 #endif
 
-               wxMenu* help = new wxMenu;
+               auto help = new wxMenu;
 #ifdef __WXOSX__
                help->Append (wxID_ABOUT, _("About DCP-o-matic"));
 #else
@@ -552,12 +588,30 @@ private:
                m->Append (help, _("&Help"));
        }
 
+
+       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.
  */
@@ -566,12 +620,12 @@ class App : public wxApp
 public:
        App ()
                : wxApp ()
-               , _frame (0)
+               , _frame (nullptr)
        {}
 
 private:
 
-       bool OnInit ()
+       bool OnInit () override
        try
        {
                wxInitAllImageHandlers ();
@@ -585,10 +639,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 ();
@@ -628,7 +680,7 @@ private:
        }
 
        /* An unhandled exception has occurred inside the main event loop */
-       bool OnExceptionInMainLoop ()
+       bool OnExceptionInMainLoop () override
        {
                try {
                        throw;
@@ -657,7 +709,7 @@ private:
                return false;
        }
 
-       void OnUnhandledException ()
+       void OnUnhandledException () override
        {
                error_dialog (0, _("An unknown exception occurred.") + "  " + REPORT_PROBLEM);
        }