Supporters update.
[dcpomatic.git] / src / wx / screens_panel.cc
1 /*
2     Copyright (C) 2015-2022 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
22 #include "check_box.h"
23 #include "cinema_dialog.h"
24 #include "dcpomatic_button.h"
25 #include "screen_dialog.h"
26 #include "screens_panel.h"
27 #include "wx_util.h"
28 #include "lib/cinema.h"
29 #include "lib/config.h"
30 #include "lib/screen.h"
31 #include "lib/timer.h"
32 #include <dcp/scope_guard.h>
33
34
35 using std::cout;
36 using std::list;
37 using std::make_pair;
38 using std::make_shared;
39 using std::map;
40 using std::pair;
41 using std::shared_ptr;
42 using std::string;
43 using std::vector;
44 using boost::optional;
45 #if BOOST_VERSION >= 106100
46 using namespace boost::placeholders;
47 #endif
48 using namespace dcpomatic;
49
50
51 ScreensPanel::ScreensPanel (wxWindow* parent)
52         : wxPanel (parent, wxID_ANY)
53 {
54         _overall_sizer = new wxBoxSizer(wxVERTICAL);
55
56         auto search_sizer = new wxBoxSizer(wxHORIZONTAL);
57
58         _search = new wxSearchCtrl (this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(200, search_ctrl_height()));
59 #ifndef __WXGTK3__
60         /* The cancel button seems to be strangely broken in GTK3; clicking on it twice sometimes works */
61         _search->ShowCancelButton (true);
62 #endif
63         search_sizer->Add(_search, 0, wxBOTTOM, DCPOMATIC_SIZER_GAP);
64
65         _show_only_checked = new CheckBox(this, _("Show only checked"));
66         search_sizer->Add(_show_only_checked, 1, wxEXPAND | wxLEFT | wxBOTTOM, DCPOMATIC_SIZER_GAP);
67
68         _overall_sizer->Add(search_sizer);
69
70         auto targets = new wxBoxSizer (wxHORIZONTAL);
71         _targets = new wxTreeListCtrl (this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTL_MULTIPLE | wxTL_3STATE | wxTL_NO_HEADER);
72         _targets->AppendColumn (wxT("foo"));
73
74         targets->Add (_targets, 1, wxEXPAND | wxRIGHT, DCPOMATIC_SIZER_GAP);
75
76         add_cinemas ();
77
78         auto side_buttons = new wxBoxSizer (wxVERTICAL);
79
80         auto target_buttons = new wxBoxSizer (wxVERTICAL);
81
82         _add_cinema = new Button (this, _("Add Cinema..."));
83         target_buttons->Add (_add_cinema, 1, wxEXPAND | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
84         _edit_cinema = new Button (this, _("Edit Cinema..."));
85         target_buttons->Add (_edit_cinema, 1, wxEXPAND | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
86         _remove_cinema = new Button (this, _("Remove Cinema"));
87         target_buttons->Add (_remove_cinema, 1, wxEXPAND | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
88         _add_screen = new Button (this, _("Add Screen..."));
89         target_buttons->Add (_add_screen, 1, wxEXPAND | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
90         _edit_screen = new Button (this, _("Edit Screen..."));
91         target_buttons->Add (_edit_screen, 1, wxEXPAND | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
92         _remove_screen = new Button (this, _("Remove Screen"));
93         target_buttons->Add (_remove_screen, 1, wxEXPAND | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
94
95         side_buttons->Add (target_buttons, 0, 0);
96
97         auto check_buttons = new wxBoxSizer (wxVERTICAL);
98
99         _check_all = new Button (this, _("Check all"));
100         check_buttons->Add (_check_all, 1, wxEXPAND | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
101         _uncheck_all = new Button (this, _("Uncheck all"));
102         check_buttons->Add (_uncheck_all, 1, wxEXPAND | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
103
104         side_buttons->Add (check_buttons, 1, wxEXPAND | wxTOP, DCPOMATIC_BUTTON_STACK_GAP * 8);
105
106         targets->Add (side_buttons, 0, 0);
107
108         _overall_sizer->Add (targets, 1, wxEXPAND);
109
110         _search->Bind        (wxEVT_TEXT, boost::bind (&ScreensPanel::display_filter_changed, this));
111         _show_only_checked->Bind(wxEVT_CHECKBOX, boost::bind(&ScreensPanel::display_filter_changed, this));
112         _targets->Bind       (wxEVT_TREELIST_SELECTION_CHANGED, &ScreensPanel::selection_changed_shim, this);
113         _targets->Bind       (wxEVT_TREELIST_ITEM_CHECKED, &ScreensPanel::checkbox_changed, this);
114         _targets->Bind       (wxEVT_TREELIST_ITEM_ACTIVATED, &ScreensPanel::item_activated, this);
115
116         _add_cinema->Bind    (wxEVT_BUTTON, boost::bind (&ScreensPanel::add_cinema_clicked, this));
117         _edit_cinema->Bind   (wxEVT_BUTTON, boost::bind (&ScreensPanel::edit_cinema_clicked, this));
118         _remove_cinema->Bind (wxEVT_BUTTON, boost::bind (&ScreensPanel::remove_cinema_clicked, this));
119
120         _add_screen->Bind    (wxEVT_BUTTON, boost::bind (&ScreensPanel::add_screen_clicked, this));
121         _edit_screen->Bind   (wxEVT_BUTTON, boost::bind (&ScreensPanel::edit_screen_clicked, this));
122         _remove_screen->Bind (wxEVT_BUTTON, boost::bind (&ScreensPanel::remove_screen_clicked, this));
123
124         _check_all->Bind     (wxEVT_BUTTON, boost::bind(&ScreensPanel::check_all, this));
125         _uncheck_all->Bind   (wxEVT_BUTTON, boost::bind(&ScreensPanel::uncheck_all, this));
126
127         SetSizer(_overall_sizer);
128
129         _config_connection = Config::instance()->Changed.connect(boost::bind(&ScreensPanel::config_changed, this, _1));
130 }
131
132
133 ScreensPanel::~ScreensPanel ()
134 {
135         _targets->Unbind (wxEVT_TREELIST_SELECTION_CHANGED, &ScreensPanel::selection_changed_shim, this);
136         _targets->Unbind (wxEVT_TREELIST_ITEM_CHECKED, &ScreensPanel::checkbox_changed, this);
137 }
138
139
140 void
141 ScreensPanel::check_all ()
142 {
143         for (auto cinema = _targets->GetFirstChild(_targets->GetRootItem()); cinema.IsOk();  cinema = _targets->GetNextSibling(cinema)) {
144                 _targets->CheckItem(cinema, wxCHK_CHECKED);
145                 for (auto screen = _targets->GetFirstChild(cinema); screen.IsOk(); screen = _targets->GetNextSibling(screen)) {
146                         _targets->CheckItem(screen, wxCHK_CHECKED);
147                         set_screen_checked(screen, true);
148                 }
149         }
150 }
151
152
153 void
154 ScreensPanel::uncheck_all ()
155 {
156         for (auto cinema = _targets->GetFirstChild(_targets->GetRootItem()); cinema.IsOk();  cinema = _targets->GetNextSibling(cinema)) {
157                 _targets->CheckItem(cinema, wxCHK_UNCHECKED);
158                 for (auto screen = _targets->GetFirstChild(cinema); screen.IsOk(); screen = _targets->GetNextSibling(screen)) {
159                         _targets->CheckItem(screen, wxCHK_UNCHECKED);
160                         set_screen_checked(screen, false);
161                 }
162         }
163 }
164
165
166 void
167 ScreensPanel::setup_sensitivity ()
168 {
169         bool const sc = _selected_cinemas.size() == 1;
170         bool const ss = _selected_screens.size() == 1;
171
172         _edit_cinema->Enable (sc || ss);
173         _remove_cinema->Enable (_selected_cinemas.size() >= 1);
174
175         _add_screen->Enable (sc || ss);
176         _edit_screen->Enable (ss);
177         _remove_screen->Enable (_selected_screens.size() >= 1);
178
179         _show_only_checked->Enable(!_checked_screens.empty());
180 }
181
182
183 void
184 ScreensPanel::convert_to_lower(string& s)
185 {
186         transform(s.begin(), s.end(), s.begin(), ::tolower);
187 }
188
189
190 bool
191 ScreensPanel::matches_search(shared_ptr<const Cinema> cinema, string search)
192 {
193         if (search.empty()) {
194                 return true;
195         }
196
197         return _collator.find(search, cinema->name);
198 }
199
200
201 optional<wxTreeListItem>
202 ScreensPanel::add_cinema (shared_ptr<Cinema> cinema, wxTreeListItem previous)
203 {
204         auto const search = wx_to_std(_search->GetValue());
205         if (!matches_search(cinema, search)) {
206                 return {};
207         }
208
209         if (_show_only_checked->get()) {
210                 auto screens = cinema->screens();
211                 auto iter = std::find_if(screens.begin(), screens.end(), [this](shared_ptr<dcpomatic::Screen> screen) {
212                         return _checked_screens.find(screen) != _checked_screens.end();
213                 });
214                 if (iter == screens.end()) {
215                         return {};
216                 }
217         }
218
219         auto id = _targets->InsertItem(_targets->GetRootItem(), previous, std_to_wx(cinema->name));
220
221         _item_to_cinema[id] = cinema;
222         _cinema_to_item[cinema] = id;
223
224         for (auto screen: cinema->screens()) {
225                 add_screen (cinema, screen);
226         }
227
228         return id;
229 }
230
231
232 optional<wxTreeListItem>
233 ScreensPanel::add_screen (shared_ptr<Cinema> cinema, shared_ptr<Screen> screen)
234 {
235         auto item = cinema_to_item(cinema);
236         if (!item) {
237                 return {};
238         }
239
240         auto id = _targets->AppendItem(*item, std_to_wx(screen->name));
241
242         _item_to_screen[id] = screen;
243         _screen_to_item[screen] = id;
244
245         return item;
246 }
247
248
249 void
250 ScreensPanel::add_cinema_clicked ()
251 {
252         CinemaDialog dialog(GetParent(), _("Add Cinema"));
253
254         if (dialog.ShowModal() == wxID_OK) {
255                 auto cinema = make_shared<Cinema>(dialog.name(), dialog.emails(), dialog.notes(), dialog.utc_offset_hour(), dialog.utc_offset_minute());
256
257                 auto cinemas = sorted_cinemas();
258
259                 try {
260                         _ignore_cinemas_changed = true;
261                         dcp::ScopeGuard sg = [this]() { _ignore_cinemas_changed = false; };
262                         Config::instance()->add_cinema(cinema);
263                 } catch (FileError& e) {
264                         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()));
265                         return;
266                 }
267
268                 wxTreeListItem previous = wxTLI_FIRST;
269                 bool found = false;
270                 auto const search = wx_to_std(_search->GetValue());
271                 for (auto existing_cinema: cinemas) {
272                         if (!matches_search(existing_cinema, search)) {
273                                 continue;
274                         }
275                         if (_collator.compare(dialog.name(), existing_cinema->name) < 0) {
276                                 /* existing_cinema should be after the one we're inserting */
277                                 found = true;
278                                 break;
279                         }
280                         auto item = cinema_to_item(existing_cinema);
281                         DCPOMATIC_ASSERT(item);
282                         previous = *item;
283                 }
284
285                 auto item = add_cinema(cinema, found ? previous : wxTLI_LAST);
286
287                 if (item) {
288                         _targets->UnselectAll ();
289                         _targets->Select (*item);
290                 }
291         }
292
293         selection_changed ();
294 }
295
296
297 shared_ptr<Cinema>
298 ScreensPanel::cinema_for_operation () const
299 {
300         if (_selected_cinemas.size() == 1) {
301                 return _selected_cinemas[0];
302         } else if (_selected_screens.size() == 1) {
303                 return _selected_screens[0]->cinema;
304         }
305
306         return {};
307 }
308
309
310 void
311 ScreensPanel::edit_cinema_clicked ()
312 {
313         auto cinema = cinema_for_operation ();
314         if (cinema) {
315                 edit_cinema(cinema);
316         }
317 }
318
319
320 void
321 ScreensPanel::edit_cinema(shared_ptr<Cinema> cinema)
322 {
323         CinemaDialog dialog(
324                 GetParent(), _("Edit cinema"), cinema->name, cinema->emails, cinema->notes, cinema->utc_offset_hour(), cinema->utc_offset_minute()
325                 );
326
327         if (dialog.ShowModal() == wxID_OK) {
328                 cinema->name = dialog.name();
329                 cinema->emails = dialog.emails();
330                 cinema->notes = dialog.notes();
331                 cinema->set_utc_offset_hour(dialog.utc_offset_hour());
332                 cinema->set_utc_offset_minute(dialog.utc_offset_minute());
333                 notify_cinemas_changed();
334                 auto item = cinema_to_item(cinema);
335                 DCPOMATIC_ASSERT(item);
336                 _targets->SetItemText (*item, std_to_wx(dialog.name()));
337         }
338 }
339
340
341 void
342 ScreensPanel::remove_cinema_clicked ()
343 {
344         if (_selected_cinemas.size() == 1) {
345                 if (!confirm_dialog(this, wxString::Format(_("Are you sure you want to remove the cinema '%s'?"), std_to_wx(_selected_cinemas[0]->name)))) {
346                         return;
347                 }
348         } else {
349                 if (!confirm_dialog(this, wxString::Format(_("Are you sure you want to remove %d cinemas?"), int(_selected_cinemas.size())))) {
350                         return;
351                 }
352         }
353
354         auto cinemas_to_remove = _selected_cinemas;
355
356         for (auto const& cinema: cinemas_to_remove) {
357                 _ignore_cinemas_changed = true;
358                 dcp::ScopeGuard sg = [this]() { _ignore_cinemas_changed = false; };
359                 for (auto screen: cinema->screens()) {
360                         _checked_screens.erase(screen);
361                 }
362                 Config::instance()->remove_cinema(cinema);
363                 auto item = cinema_to_item(cinema);
364                 DCPOMATIC_ASSERT(item);
365                 _targets->DeleteItem(*item);
366         }
367
368         selection_changed ();
369         setup_show_only_checked();
370 }
371
372
373 void
374 ScreensPanel::add_screen_clicked ()
375 {
376         auto cinema = cinema_for_operation ();
377         if (!cinema) {
378                 return;
379         }
380
381         ScreenDialog dialog(GetParent(), _("Add Screen"));
382
383         if (dialog.ShowModal () != wxID_OK) {
384                 return;
385         }
386
387         for (auto screen: cinema->screens()) {
388                 if (screen->name == dialog.name()) {
389                         error_dialog (
390                                 GetParent(),
391                                 wxString::Format (
392                                         _("You cannot add a screen called '%s' as the cinema already has a screen with this name."),
393                                         std_to_wx(dialog.name()).data()
394                                         )
395                                 );
396                         return;
397                 }
398         }
399
400         auto screen = std::make_shared<Screen>(dialog.name(), dialog.notes(), dialog.recipient(), dialog.recipient_file(), dialog.trusted_devices());
401         cinema->add_screen (screen);
402         notify_cinemas_changed();
403
404         auto id = add_screen (cinema, screen);
405         if (id) {
406                 _targets->Expand (id.get ());
407         }
408 }
409
410
411 void
412 ScreensPanel::edit_screen_clicked ()
413 {
414         if (_selected_screens.size() == 1) {
415                 edit_screen(_selected_screens[0]);
416         }
417 }
418
419
420 void
421 ScreensPanel::edit_screen(shared_ptr<Screen> edit_screen)
422 {
423         ScreenDialog dialog(
424                 GetParent(), _("Edit screen"),
425                 edit_screen->name,
426                 edit_screen->notes,
427                 edit_screen->recipient,
428                 edit_screen->recipient_file,
429                 edit_screen->trusted_devices
430                 );
431
432         if (dialog.ShowModal() != wxID_OK) {
433                 return;
434         }
435
436         auto cinema = edit_screen->cinema;
437         for (auto screen: cinema->screens()) {
438                 if (screen != edit_screen && screen->name == dialog.name()) {
439                         error_dialog (
440                                 GetParent(),
441                                 wxString::Format (
442                                         _("You cannot change this screen's name to '%s' as the cinema already has a screen with this name."),
443                                         std_to_wx(dialog.name()).data()
444                                         )
445                                 );
446                         return;
447                 }
448         }
449
450         edit_screen->name = dialog.name();
451         edit_screen->notes = dialog.notes();
452         edit_screen->recipient = dialog.recipient();
453         edit_screen->recipient_file = dialog.recipient_file();
454         edit_screen->trusted_devices = dialog.trusted_devices();
455         notify_cinemas_changed();
456
457         auto item = screen_to_item(edit_screen);
458         DCPOMATIC_ASSERT (item);
459         _targets->SetItemText(*item, std_to_wx(dialog.name()));
460 }
461
462
463 void
464 ScreensPanel::remove_screen_clicked ()
465 {
466         if (_selected_screens.size() == 1) {
467                 if (!confirm_dialog(this, wxString::Format(_("Are you sure you want to remove the screen '%s'?"), std_to_wx(_selected_screens[0]->name)))) {
468                         return;
469                 }
470         } else {
471                 if (!confirm_dialog(this, wxString::Format(_("Are you sure you want to remove %d screens?"), int(_selected_screens.size())))) {
472                         return;
473                 }
474         }
475
476         for (auto screen: _selected_screens) {
477                 _checked_screens.erase(screen);
478                 screen->cinema->remove_screen(screen);
479                 auto item = screen_to_item(screen);
480                 DCPOMATIC_ASSERT(item);
481                 _targets->DeleteItem(*item);
482         }
483
484         /* This is called by the signal on Linux, but not it seems on Windows, so we call it ourselves
485          * as well.
486          */
487         selection_changed();
488         notify_cinemas_changed();
489         setup_show_only_checked();
490 }
491
492
493 vector<shared_ptr<Screen>>
494 ScreensPanel::screens () const
495 {
496         vector<shared_ptr<Screen>> output;
497         std::copy (_checked_screens.begin(), _checked_screens.end(), std::back_inserter(output));
498         return output;
499 }
500
501
502 void
503 ScreensPanel::selection_changed_shim (wxTreeListEvent &)
504 {
505         selection_changed ();
506 }
507
508
509 void
510 ScreensPanel::selection_changed ()
511 {
512         if (_ignore_selection_change) {
513                 return;
514         }
515
516         wxTreeListItems selection;
517         _targets->GetSelections (selection);
518
519         _selected_cinemas.clear ();
520         _selected_screens.clear ();
521
522         for (size_t i = 0; i < selection.size(); ++i) {
523                 if (auto cinema = item_to_cinema(selection[i])) {
524                         _selected_cinemas.push_back(cinema);
525                 }
526                 if (auto screen = item_to_screen(selection[i])) {
527                         _selected_screens.push_back(screen);
528                 }
529         }
530
531         setup_sensitivity ();
532 }
533
534
535 list<shared_ptr<Cinema>>
536 ScreensPanel::sorted_cinemas() const
537 {
538         auto cinemas = Config::instance()->cinemas();
539
540         cinemas.sort(
541                 [this](shared_ptr<Cinema> a, shared_ptr<Cinema> b) { return _collator.compare(a->name, b->name) < 0; }
542                 );
543
544         return cinemas;
545 }
546
547
548 void
549 ScreensPanel::add_cinemas ()
550 {
551         for (auto cinema: sorted_cinemas()) {
552                 add_cinema (cinema, wxTLI_LAST);
553         }
554 }
555
556
557 void
558 ScreensPanel::clear_and_re_add()
559 {
560         _targets->DeleteAllItems ();
561
562         _item_to_cinema.clear ();
563         _cinema_to_item.clear ();
564         _item_to_screen.clear ();
565         _screen_to_item.clear ();
566
567         add_cinemas ();
568 }
569
570
571 /** Search and/or "show only checked" changed */
572 void
573 ScreensPanel::display_filter_changed()
574 {
575         clear_and_re_add();
576
577         _ignore_selection_change = true;
578
579         for (auto const& selection: _selected_cinemas) {
580                 if (auto item = cinema_to_item(selection)) {
581                         _targets->Select (*item);
582                 }
583         }
584
585         for (auto const& selection: _selected_screens) {
586                 if (auto item = screen_to_item(selection)) {
587                         _targets->Select (*item);
588                 }
589         }
590
591         _ignore_selection_change = false;
592
593         _ignore_check_change = true;
594
595         for (auto const& checked: _checked_screens) {
596                 if (auto item = screen_to_item(checked)) {
597                         _targets->CheckItem(*item, wxCHK_CHECKED);
598                         setup_cinema_checked_state(*item);
599                 }
600         }
601
602         _ignore_check_change = false;
603 }
604
605
606 void
607 ScreensPanel::set_screen_checked (wxTreeListItem item, bool checked)
608 {
609         auto screen = item_to_screen(item);
610         DCPOMATIC_ASSERT(screen);
611         if (checked) {
612                 _checked_screens.insert(screen);
613         } else {
614                 _checked_screens.erase(screen);
615         }
616
617         setup_show_only_checked();
618 }
619
620
621 void
622 ScreensPanel::setup_cinema_checked_state (wxTreeListItem screen)
623 {
624         auto cinema = _targets->GetItemParent(screen);
625         DCPOMATIC_ASSERT (cinema.IsOk());
626         int checked = 0;
627         int unchecked = 0;
628         for (auto child = _targets->GetFirstChild(cinema); child.IsOk(); child = _targets->GetNextSibling(child)) {
629                 if (_targets->GetCheckedState(child) == wxCHK_CHECKED) {
630                     ++checked;
631                 } else {
632                     ++unchecked;
633                 }
634         }
635         if (checked == 0) {
636                 _targets->CheckItem(cinema, wxCHK_UNCHECKED);
637         } else if (unchecked == 0) {
638                 _targets->CheckItem(cinema, wxCHK_CHECKED);
639         } else {
640                 _targets->CheckItem(cinema, wxCHK_UNDETERMINED);
641         }
642 }
643
644
645 void
646 ScreensPanel::checkbox_changed (wxTreeListEvent& ev)
647 {
648         if (_ignore_check_change) {
649                 return;
650         }
651
652         if (item_to_cinema(ev.GetItem())) {
653                 /* Cinema: check/uncheck all children */
654                 auto const checked = _targets->GetCheckedState(ev.GetItem());
655                 for (auto child = _targets->GetFirstChild(ev.GetItem()); child.IsOk(); child = _targets->GetNextSibling(child)) {
656                         _targets->CheckItem(child, checked);
657                         set_screen_checked(child, checked);
658                 }
659         } else {
660                 set_screen_checked(ev.GetItem(), _targets->GetCheckedState(ev.GetItem()));
661                 setup_cinema_checked_state(ev.GetItem());
662         }
663
664         ScreensChanged ();
665 }
666
667
668 shared_ptr<Cinema>
669 ScreensPanel::item_to_cinema (wxTreeListItem item) const
670 {
671         auto iter = _item_to_cinema.find (item);
672         if (iter == _item_to_cinema.end()) {
673                 return {};
674         }
675
676         return iter->second;
677 }
678
679
680 shared_ptr<Screen>
681 ScreensPanel::item_to_screen (wxTreeListItem item) const
682 {
683         auto iter = _item_to_screen.find (item);
684         if (iter == _item_to_screen.end()) {
685                 return {};
686         }
687
688         return iter->second;
689 }
690
691
692 optional<wxTreeListItem>
693 ScreensPanel::cinema_to_item (shared_ptr<Cinema> cinema) const
694 {
695         auto iter = _cinema_to_item.find (cinema);
696         if (iter == _cinema_to_item.end()) {
697                 return {};
698         }
699
700         return iter->second;
701 }
702
703
704 optional<wxTreeListItem>
705 ScreensPanel::screen_to_item (shared_ptr<Screen> screen) const
706 {
707         auto iter = _screen_to_item.find (screen);
708         if (iter == _screen_to_item.end()) {
709                 return {};
710         }
711
712         return iter->second;
713 }
714
715
716 bool
717 ScreensPanel::notify_cinemas_changed()
718 {
719         _ignore_cinemas_changed = true;
720         dcp::ScopeGuard sg = [this]() { _ignore_cinemas_changed = false; };
721
722         try {
723                 Config::instance()->changed(Config::CINEMAS);
724         } catch (FileError& e) {
725                 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()));
726                 return false;
727         }
728
729         return true;
730 }
731
732
733 void
734 ScreensPanel::config_changed(Config::Property property)
735 {
736         if (property == Config::Property::CINEMAS && !_ignore_cinemas_changed) {
737                 clear_and_re_add();
738         }
739 }
740
741
742 void
743 ScreensPanel::item_activated(wxTreeListEvent& ev)
744 {
745         auto iter = _item_to_cinema.find(ev.GetItem());
746         if (iter != _item_to_cinema.end()) {
747                 edit_cinema(iter->second);
748         } else {
749                 auto iter = _item_to_screen.find(ev.GetItem());
750                 if (iter != _item_to_screen.end()) {
751                         edit_screen(iter->second);
752                 }
753         }
754 }
755
756
757 void
758 ScreensPanel::setup_show_only_checked()
759 {
760         if (_checked_screens.empty()) {
761                 _show_only_checked->set_text(_("Show only checked"));
762         } else {
763                 _show_only_checked->set_text(wxString::Format(_("Show only %d checked"), static_cast<int>(_checked_screens.size())));
764         }
765
766         _overall_sizer->Layout();
767         setup_sensitivity();
768 }
769