62f8688a9192150328c4fc7dac5724925f633dd9
[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());
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(GetParent(), _("Edit cinema"), cinema->name, cinema->emails, cinema->notes);
324
325         if (dialog.ShowModal() == wxID_OK) {
326                 cinema->name = dialog.name();
327                 cinema->emails = dialog.emails();
328                 cinema->notes = dialog.notes();
329                 notify_cinemas_changed();
330                 auto item = cinema_to_item(cinema);
331                 DCPOMATIC_ASSERT(item);
332                 _targets->SetItemText (*item, std_to_wx(dialog.name()));
333         }
334 }
335
336
337 void
338 ScreensPanel::remove_cinema_clicked ()
339 {
340         if (_selected_cinemas.size() == 1) {
341                 if (!confirm_dialog(this, wxString::Format(_("Are you sure you want to remove the cinema '%s'?"), std_to_wx(_selected_cinemas[0]->name)))) {
342                         return;
343                 }
344         } else {
345                 if (!confirm_dialog(this, wxString::Format(_("Are you sure you want to remove %d cinemas?"), int(_selected_cinemas.size())))) {
346                         return;
347                 }
348         }
349
350         auto cinemas_to_remove = _selected_cinemas;
351
352         for (auto const& cinema: cinemas_to_remove) {
353                 _ignore_cinemas_changed = true;
354                 dcp::ScopeGuard sg = [this]() { _ignore_cinemas_changed = false; };
355                 for (auto screen: cinema->screens()) {
356                         _checked_screens.erase(screen);
357                 }
358                 Config::instance()->remove_cinema(cinema);
359                 auto item = cinema_to_item(cinema);
360                 DCPOMATIC_ASSERT(item);
361                 _targets->DeleteItem(*item);
362         }
363
364         selection_changed ();
365         setup_show_only_checked();
366 }
367
368
369 void
370 ScreensPanel::add_screen_clicked ()
371 {
372         auto cinema = cinema_for_operation ();
373         if (!cinema) {
374                 return;
375         }
376
377         ScreenDialog dialog(GetParent(), _("Add Screen"));
378
379         if (dialog.ShowModal () != wxID_OK) {
380                 return;
381         }
382
383         for (auto screen: cinema->screens()) {
384                 if (screen->name == dialog.name()) {
385                         error_dialog (
386                                 GetParent(),
387                                 wxString::Format (
388                                         _("You cannot add a screen called '%s' as the cinema already has a screen with this name."),
389                                         std_to_wx(dialog.name()).data()
390                                         )
391                                 );
392                         return;
393                 }
394         }
395
396         auto screen = std::make_shared<Screen>(dialog.name(), dialog.notes(), dialog.recipient(), dialog.recipient_file(), dialog.trusted_devices());
397         cinema->add_screen (screen);
398         notify_cinemas_changed();
399
400         auto id = add_screen (cinema, screen);
401         if (id) {
402                 _targets->Expand (id.get ());
403         }
404 }
405
406
407 void
408 ScreensPanel::edit_screen_clicked ()
409 {
410         if (_selected_screens.size() == 1) {
411                 edit_screen(_selected_screens[0]);
412         }
413 }
414
415
416 void
417 ScreensPanel::edit_screen(shared_ptr<Screen> edit_screen)
418 {
419         ScreenDialog dialog(
420                 GetParent(), _("Edit screen"),
421                 edit_screen->name,
422                 edit_screen->notes,
423                 edit_screen->recipient,
424                 edit_screen->recipient_file,
425                 edit_screen->trusted_devices
426                 );
427
428         if (dialog.ShowModal() != wxID_OK) {
429                 return;
430         }
431
432         auto cinema = edit_screen->cinema;
433         for (auto screen: cinema->screens()) {
434                 if (screen != edit_screen && screen->name == dialog.name()) {
435                         error_dialog (
436                                 GetParent(),
437                                 wxString::Format (
438                                         _("You cannot change this screen's name to '%s' as the cinema already has a screen with this name."),
439                                         std_to_wx(dialog.name()).data()
440                                         )
441                                 );
442                         return;
443                 }
444         }
445
446         edit_screen->name = dialog.name();
447         edit_screen->notes = dialog.notes();
448         edit_screen->recipient = dialog.recipient();
449         edit_screen->recipient_file = dialog.recipient_file();
450         edit_screen->trusted_devices = dialog.trusted_devices();
451         notify_cinemas_changed();
452
453         auto item = screen_to_item(edit_screen);
454         DCPOMATIC_ASSERT (item);
455         _targets->SetItemText(*item, std_to_wx(dialog.name()));
456 }
457
458
459 void
460 ScreensPanel::remove_screen_clicked ()
461 {
462         if (_selected_screens.size() == 1) {
463                 if (!confirm_dialog(this, wxString::Format(_("Are you sure you want to remove the screen '%s'?"), std_to_wx(_selected_screens[0]->name)))) {
464                         return;
465                 }
466         } else {
467                 if (!confirm_dialog(this, wxString::Format(_("Are you sure you want to remove %d screens?"), int(_selected_screens.size())))) {
468                         return;
469                 }
470         }
471
472         for (auto screen: _selected_screens) {
473                 _checked_screens.erase(screen);
474                 screen->cinema->remove_screen(screen);
475                 auto item = screen_to_item(screen);
476                 DCPOMATIC_ASSERT(item);
477                 _targets->DeleteItem(*item);
478         }
479
480         /* This is called by the signal on Linux, but not it seems on Windows, so we call it ourselves
481          * as well.
482          */
483         selection_changed();
484         notify_cinemas_changed();
485         setup_show_only_checked();
486 }
487
488
489 vector<shared_ptr<Screen>>
490 ScreensPanel::screens () const
491 {
492         vector<shared_ptr<Screen>> output;
493         std::copy (_checked_screens.begin(), _checked_screens.end(), std::back_inserter(output));
494         return output;
495 }
496
497
498 void
499 ScreensPanel::selection_changed_shim (wxTreeListEvent &)
500 {
501         selection_changed ();
502 }
503
504
505 void
506 ScreensPanel::selection_changed ()
507 {
508         if (_ignore_selection_change) {
509                 return;
510         }
511
512         wxTreeListItems selection;
513         _targets->GetSelections (selection);
514
515         _selected_cinemas.clear ();
516         _selected_screens.clear ();
517
518         for (size_t i = 0; i < selection.size(); ++i) {
519                 if (auto cinema = item_to_cinema(selection[i])) {
520                         _selected_cinemas.push_back(cinema);
521                 }
522                 if (auto screen = item_to_screen(selection[i])) {
523                         _selected_screens.push_back(screen);
524                 }
525         }
526
527         setup_sensitivity ();
528 }
529
530
531 list<shared_ptr<Cinema>>
532 ScreensPanel::sorted_cinemas() const
533 {
534         auto cinemas = Config::instance()->cinemas();
535
536         cinemas.sort(
537                 [this](shared_ptr<Cinema> a, shared_ptr<Cinema> b) { return _collator.compare(a->name, b->name) < 0; }
538                 );
539
540         return cinemas;
541 }
542
543
544 void
545 ScreensPanel::add_cinemas ()
546 {
547         for (auto cinema: sorted_cinemas()) {
548                 add_cinema (cinema, wxTLI_LAST);
549         }
550 }
551
552
553 void
554 ScreensPanel::clear_and_re_add()
555 {
556         _targets->DeleteAllItems ();
557
558         _item_to_cinema.clear ();
559         _cinema_to_item.clear ();
560         _item_to_screen.clear ();
561         _screen_to_item.clear ();
562
563         add_cinemas ();
564 }
565
566
567 /** Search and/or "show only checked" changed */
568 void
569 ScreensPanel::display_filter_changed()
570 {
571         clear_and_re_add();
572
573         _ignore_selection_change = true;
574
575         for (auto const& selection: _selected_cinemas) {
576                 if (auto item = cinema_to_item(selection)) {
577                         _targets->Select (*item);
578                 }
579         }
580
581         for (auto const& selection: _selected_screens) {
582                 if (auto item = screen_to_item(selection)) {
583                         _targets->Select (*item);
584                 }
585         }
586
587         _ignore_selection_change = false;
588
589         _ignore_check_change = true;
590
591         for (auto const& checked: _checked_screens) {
592                 if (auto item = screen_to_item(checked)) {
593                         _targets->CheckItem(*item, wxCHK_CHECKED);
594                         setup_cinema_checked_state(*item);
595                 }
596         }
597
598         _ignore_check_change = false;
599 }
600
601
602 void
603 ScreensPanel::set_screen_checked (wxTreeListItem item, bool checked)
604 {
605         auto screen = item_to_screen(item);
606         DCPOMATIC_ASSERT(screen);
607         if (checked) {
608                 _checked_screens.insert(screen);
609         } else {
610                 _checked_screens.erase(screen);
611         }
612
613         setup_show_only_checked();
614 }
615
616
617 void
618 ScreensPanel::setup_cinema_checked_state (wxTreeListItem screen)
619 {
620         auto cinema = _targets->GetItemParent(screen);
621         DCPOMATIC_ASSERT (cinema.IsOk());
622         int checked = 0;
623         int unchecked = 0;
624         for (auto child = _targets->GetFirstChild(cinema); child.IsOk(); child = _targets->GetNextSibling(child)) {
625                 if (_targets->GetCheckedState(child) == wxCHK_CHECKED) {
626                     ++checked;
627                 } else {
628                     ++unchecked;
629                 }
630         }
631         if (checked == 0) {
632                 _targets->CheckItem(cinema, wxCHK_UNCHECKED);
633         } else if (unchecked == 0) {
634                 _targets->CheckItem(cinema, wxCHK_CHECKED);
635         } else {
636                 _targets->CheckItem(cinema, wxCHK_UNDETERMINED);
637         }
638 }
639
640
641 void
642 ScreensPanel::checkbox_changed (wxTreeListEvent& ev)
643 {
644         if (_ignore_check_change) {
645                 return;
646         }
647
648         if (item_to_cinema(ev.GetItem())) {
649                 /* Cinema: check/uncheck all children */
650                 auto const checked = _targets->GetCheckedState(ev.GetItem());
651                 for (auto child = _targets->GetFirstChild(ev.GetItem()); child.IsOk(); child = _targets->GetNextSibling(child)) {
652                         _targets->CheckItem(child, checked);
653                         set_screen_checked(child, checked);
654                 }
655         } else {
656                 set_screen_checked(ev.GetItem(), _targets->GetCheckedState(ev.GetItem()));
657                 setup_cinema_checked_state(ev.GetItem());
658         }
659
660         ScreensChanged ();
661 }
662
663
664 shared_ptr<Cinema>
665 ScreensPanel::item_to_cinema (wxTreeListItem item) const
666 {
667         auto iter = _item_to_cinema.find (item);
668         if (iter == _item_to_cinema.end()) {
669                 return {};
670         }
671
672         return iter->second;
673 }
674
675
676 shared_ptr<Screen>
677 ScreensPanel::item_to_screen (wxTreeListItem item) const
678 {
679         auto iter = _item_to_screen.find (item);
680         if (iter == _item_to_screen.end()) {
681                 return {};
682         }
683
684         return iter->second;
685 }
686
687
688 optional<wxTreeListItem>
689 ScreensPanel::cinema_to_item (shared_ptr<Cinema> cinema) const
690 {
691         auto iter = _cinema_to_item.find (cinema);
692         if (iter == _cinema_to_item.end()) {
693                 return {};
694         }
695
696         return iter->second;
697 }
698
699
700 optional<wxTreeListItem>
701 ScreensPanel::screen_to_item (shared_ptr<Screen> screen) const
702 {
703         auto iter = _screen_to_item.find (screen);
704         if (iter == _screen_to_item.end()) {
705                 return {};
706         }
707
708         return iter->second;
709 }
710
711
712 bool
713 ScreensPanel::notify_cinemas_changed()
714 {
715         _ignore_cinemas_changed = true;
716         dcp::ScopeGuard sg = [this]() { _ignore_cinemas_changed = false; };
717
718         try {
719                 Config::instance()->changed(Config::CINEMAS);
720         } catch (FileError& e) {
721                 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()));
722                 return false;
723         }
724
725         return true;
726 }
727
728
729 void
730 ScreensPanel::config_changed(Config::Property property)
731 {
732         if (property == Config::Property::CINEMAS && !_ignore_cinemas_changed) {
733                 clear_and_re_add();
734         }
735 }
736
737
738 void
739 ScreensPanel::item_activated(wxTreeListEvent& ev)
740 {
741         auto iter = _item_to_cinema.find(ev.GetItem());
742         if (iter != _item_to_cinema.end()) {
743                 edit_cinema(iter->second);
744         } else {
745                 auto iter = _item_to_screen.find(ev.GetItem());
746                 if (iter != _item_to_screen.end()) {
747                         edit_screen(iter->second);
748                 }
749         }
750 }
751
752
753 void
754 ScreensPanel::setup_show_only_checked()
755 {
756         if (_checked_screens.empty()) {
757                 _show_only_checked->set_text(_("Show only checked"));
758         } else {
759                 _show_only_checked->set_text(wxString::Format(_("Show only %d checked"), static_cast<int>(_checked_screens.size())));
760         }
761
762         _overall_sizer->Layout();
763         setup_sensitivity();
764 }
765