Various work on playlist editor.
[dcpomatic.git] / src / tools / dcpomatic_playlist.cc
1 /*
2     Copyright (C) 2018 Carl Hetherington <cth@carlh.net>
3
4     This file is part of DCP-o-matic.
5
6     DCP-o-matic is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10
11     DCP-o-matic is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15
16     You should have received a copy of the GNU General Public License
17     along with DCP-o-matic.  If not, see <http://www.gnu.org/licenses/>.
18
19 */
20
21 #include "../wx/wx_util.h"
22 #include "../wx/wx_signal_manager.h"
23 #include "../lib/util.h"
24 #include "../lib/config.h"
25 #include "../lib/cross.h"
26 #include <wx/wx.h>
27 #include <wx/listctrl.h>
28 #include <wx/imaglist.h>
29
30 using std::exception;
31 using std::cout;
32 using boost::optional;
33
34 class PlaylistEntry
35 {
36 public:
37         std::string name;
38         std::string cpl_id;
39         dcp::ContentKind kind;
40         enum Type {
41                 DCP,
42                 ECINEMA
43         };
44         Type type;
45         bool encrypted;
46         bool skippable;
47         bool disable_timeline;
48         bool stop_after_play;
49 };
50
51
52 class DOMFrame : public wxFrame
53 {
54 public:
55         explicit DOMFrame (wxString const & title)
56                 : wxFrame (0, -1, title)
57         {
58                 /* Use a panel as the only child of the Frame so that we avoid
59                    the dark-grey background on Windows.
60                 */
61                 wxPanel* overall_panel = new wxPanel (this, wxID_ANY);
62                 wxBoxSizer* main_sizer = new wxBoxSizer (wxHORIZONTAL);
63
64                 _list = new wxListCtrl (
65                         overall_panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLC_REPORT | wxLC_SINGLE_SEL
66                         );
67
68                 _list->AppendColumn (_("Name"), wxLIST_FORMAT_LEFT, 400);
69                 _list->AppendColumn (_("CPL"), wxLIST_FORMAT_LEFT, 400);
70                 _list->AppendColumn (_("Type"), wxLIST_FORMAT_CENTRE, 75);
71                 _list->AppendColumn (_("Format"), wxLIST_FORMAT_CENTRE, 75);
72                 _list->AppendColumn (_("Encrypted"), wxLIST_FORMAT_CENTRE, 90);
73                 _list->AppendColumn (_("Skippable"), wxLIST_FORMAT_CENTRE, 90);
74                 _list->AppendColumn (_("Disable timeline"), wxLIST_FORMAT_CENTRE, 125);
75                 _list->AppendColumn (_("Stop after play"), wxLIST_FORMAT_CENTRE, 125);
76
77                 wxImageList* images = new wxImageList (16, 16);
78                 wxIcon tick_icon;
79                 wxIcon no_tick_icon;
80 #ifdef DCPOMATIX_OSX
81                 tick_icon.LoadFile ("tick.png", wxBITMAP_TYPE_PNG_RESOURCE);
82                 no_tick_icon.LoadFile ("no_tick.png", wxBITMAP_TYPE_PNG_RESOURCE);
83 #else
84                 boost::filesystem::path tick_path = shared_path() / "tick.png";
85                 tick_icon.LoadFile (std_to_wx(tick_path.string()));
86                 boost::filesystem::path no_tick_path = shared_path() / "no_tick.png";
87                 no_tick_icon.LoadFile (std_to_wx(no_tick_path.string()));
88 #endif
89                 images->Add (tick_icon);
90                 images->Add (no_tick_icon);
91
92                 _list->SetImageList (images, wxIMAGE_LIST_SMALL);
93
94                 main_sizer->Add (_list, 1, wxEXPAND | wxALL, DCPOMATIC_SIZER_GAP);
95
96                 wxBoxSizer* button_sizer = new wxBoxSizer (wxVERTICAL);
97                 _up = new wxButton (overall_panel, wxID_ANY, _("Up"));
98                 _down = new wxButton (overall_panel, wxID_ANY, _("Down"));
99                 _add = new wxButton (overall_panel, wxID_ANY, _("Add"));
100                 _remove = new wxButton (overall_panel, wxID_ANY, _("Remove"));
101                 _save = new wxButton (overall_panel, wxID_ANY, _("Save playlist"));
102                 _load = new wxButton (overall_panel, wxID_ANY, _("Load playlist"));
103                 button_sizer->Add (_up, 0, wxEXPAND | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
104                 button_sizer->Add (_down, 0, wxEXPAND | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
105                 button_sizer->Add (_add, 0, wxEXPAND | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
106                 button_sizer->Add (_remove, 0, wxEXPAND | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
107                 button_sizer->Add (_save, 0, wxEXPAND | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
108                 button_sizer->Add (_load, 0, wxEXPAND | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
109
110                 main_sizer->Add (button_sizer, 0, wxALL, DCPOMATIC_SIZER_GAP);
111                 overall_panel->SetSizer (main_sizer);
112
113                 _list->Bind (wxEVT_LEFT_DOWN, bind(&DOMFrame::list_left_click, this, _1));
114
115                 PlaylistEntry pe;
116                 pe.name = "Shit";
117                 pe.cpl_id = "sh-1t";
118                 pe.kind = dcp::FEATURE;
119                 pe.type = PlaylistEntry::ECINEMA;
120                 pe.encrypted = true;
121                 pe.disable_timeline = false;
122                 pe.stop_after_play = true;
123                 add (pe);
124
125                 setup_sensitivity ();
126         }
127
128 private:
129
130         void add (PlaylistEntry e)
131         {
132                 wxListItem item;
133                 item.SetId (0);
134                 long const N = _list->InsertItem (item);
135                 set_item (N, e);
136                 _playlist.push_back (e);
137         }
138
139         void set_item (long N, PlaylistEntry e)
140         {
141                 _list->SetItem (N, 0, std_to_wx(e.name));
142                 _list->SetItem (N, 1, std_to_wx(e.cpl_id));
143                 _list->SetItem (N, 2, std_to_wx(dcp::content_kind_to_string(e.kind)));
144                 _list->SetItem (N, 3, e.type == PlaylistEntry::DCP ? _("DCP") : _("E-cinema"));
145                 _list->SetItem (N, 4, e.encrypted ? _("Y") : _("N"));
146                 _list->SetItem (N, COLUMN_SKIPPABLE, wxEmptyString, e.skippable ? 0 : 1);
147                 _list->SetItem (N, COLUMN_DISABLE_TIMELINE, wxEmptyString, e.disable_timeline ? 0 : 1);
148                 _list->SetItem (N, COLUMN_STOP_AFTER_PLAY, wxEmptyString, e.stop_after_play ? 0 : 1);
149         }
150
151         void setup_sensitivity ()
152         {
153                 int const selected = _list->GetSelectedItemCount ();
154                 _up->Enable (selected > 0);
155                 _down->Enable (selected > 0);
156                 _remove->Enable (selected > 0);
157         }
158
159         void list_left_click (wxMouseEvent& ev)
160         {
161                 int flags;
162                 long item = _list->HitTest (ev.GetPosition(), flags, 0);
163                 int x = ev.GetPosition().x;
164                 optional<int> column;
165                 for (int i = 0; i < _list->GetColumnCount(); ++i) {
166                         x -= _list->GetColumnWidth (i);
167                         if (x < 0) {
168                                 column = i;
169                                 break;
170                         }
171                 }
172
173                 if (item != -1 && column) {
174                         switch (*column) {
175                         case COLUMN_SKIPPABLE:
176                                 _playlist[item].skippable = !_playlist[item].skippable;
177                                 break;
178                         case COLUMN_DISABLE_TIMELINE:
179                                 _playlist[item].disable_timeline = !_playlist[item].disable_timeline;
180                                 break;
181                         case COLUMN_STOP_AFTER_PLAY:
182                                 _playlist[item].stop_after_play = !_playlist[item].stop_after_play;
183                                 break;
184                         default:
185                                 ev.Skip ();
186                         }
187                         set_item (item, _playlist[item]);
188                 } else {
189                         ev.Skip ();
190                 }
191         }
192
193         wxListCtrl* _list;
194         wxButton* _up;
195         wxButton* _down;
196         wxButton* _add;
197         wxButton* _remove;
198         wxButton* _save;
199         wxButton* _load;
200         std::vector<PlaylistEntry> _playlist;
201
202         enum {
203                 COLUMN_SKIPPABLE = 5,
204                 COLUMN_DISABLE_TIMELINE = 6,
205                 COLUMN_STOP_AFTER_PLAY = 7
206         };
207 };
208
209 /** @class App
210  *  @brief The magic App class for wxWidgets.
211  */
212 class App : public wxApp
213 {
214 public:
215         App ()
216                 : wxApp ()
217                 , _frame (0)
218         {}
219
220 private:
221
222         bool OnInit ()
223         try
224         {
225                 SetAppName (_("DCP-o-matic KDM Creator"));
226
227                 if (!wxApp::OnInit()) {
228                         return false;
229                 }
230
231 #ifdef DCPOMATIC_LINUX
232                 unsetenv ("UBUNTU_MENUPROXY");
233 #endif
234
235                 #ifdef __WXOSX__
236                 ProcessSerialNumber serial;
237                 GetCurrentProcess (&serial);
238                 TransformProcessType (&serial, kProcessTransformToForegroundApplication);
239 #endif
240
241                 dcpomatic_setup_path_encoding ();
242
243                 /* Enable i18n; this will create a Config object
244                    to look for a force-configured language.  This Config
245                    object will be wrong, however, because dcpomatic_setup
246                    hasn't yet been called and there aren't any filters etc.
247                    set up yet.
248                 */
249                 dcpomatic_setup_i18n ();
250
251                 /* Set things up, including filters etc.
252                    which will now be internationalised correctly.
253                 */
254                 dcpomatic_setup ();
255
256                 /* Force the configuration to be re-loaded correctly next
257                    time it is needed.
258                 */
259                 Config::drop ();
260
261                 _frame = new DOMFrame (_("DCP-o-matic KDM Creator"));
262                 SetTopWindow (_frame);
263                 _frame->Maximize ();
264                 _frame->Show ();
265
266                 signal_manager = new wxSignalManager (this);
267                 Bind (wxEVT_IDLE, boost::bind (&App::idle, this));
268
269                 return true;
270         }
271         catch (exception& e)
272         {
273                 error_dialog (0, _("DCP-o-matic could not start"), std_to_wx(e.what()));
274                 return true;
275         }
276
277         /* An unhandled exception has occurred inside the main event loop */
278         bool OnExceptionInMainLoop ()
279         {
280                 try {
281                         throw;
282                 } catch (FileError& e) {
283                         error_dialog (
284                                 0,
285                                 wxString::Format (
286                                         _("An exception occurred: %s (%s)\n\n") + REPORT_PROBLEM,
287                                         std_to_wx (e.what()),
288                                         std_to_wx (e.file().string().c_str ())
289                                         )
290                                 );
291                 } catch (exception& e) {
292                         error_dialog (
293                                 0,
294                                 wxString::Format (
295                                         _("An exception occurred: %s.\n\n") + " " + REPORT_PROBLEM,
296                                         std_to_wx (e.what ())
297                                         )
298                                 );
299                 } catch (...) {
300                         error_dialog (0, _("An unknown exception occurred.") + "  " + REPORT_PROBLEM);
301                 }
302
303                 /* This will terminate the program */
304                 return false;
305         }
306
307         void OnUnhandledException ()
308         {
309                 error_dialog (0, _("An unknown exception occurred.") + "  " + REPORT_PROBLEM);
310         }
311
312         void idle ()
313         {
314                 signal_manager->ui_idle ();
315         }
316
317         DOMFrame* _frame;
318 };
319
320 IMPLEMENT_APP (App)