2 Copyright (C) 2015-2022 Carl Hetherington <cth@carlh.net>
4 This file is part of DCP-o-matic.
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.
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.
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/>.
22 #include "cinema_dialog.h"
23 #include "dcpomatic_button.h"
24 #include "screen_dialog.h"
25 #include "screens_panel.h"
27 #include "lib/cinema.h"
28 #include "lib/config.h"
29 #include "lib/screen.h"
34 using std::make_shared;
37 using std::shared_ptr;
40 using boost::optional;
41 using namespace dcpomatic;
44 ScreensPanel::ScreensPanel (wxWindow* parent)
45 : wxPanel (parent, wxID_ANY)
46 , _ignore_selection_change (false)
48 auto sizer = new wxBoxSizer (wxVERTICAL);
50 _search = new wxSearchCtrl (this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(200, search_ctrl_height()));
52 /* The cancel button seems to be strangely broken in GTK3; clicking on it twice sometimes works */
53 _search->ShowCancelButton (true);
55 sizer->Add (_search, 0, wxBOTTOM, DCPOMATIC_SIZER_GAP);
57 auto targets = new wxBoxSizer (wxHORIZONTAL);
58 _targets = new wxTreeListCtrl (this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTL_MULTIPLE | wxTL_3STATE | wxTL_NO_HEADER);
59 _targets->AppendColumn (wxT("foo"));
60 _targets->SetSortColumn (0);
61 _targets->SetItemComparator (&_comparator);
63 targets->Add (_targets, 1, wxEXPAND | wxRIGHT, DCPOMATIC_SIZER_GAP);
67 auto target_buttons = new wxBoxSizer (wxVERTICAL);
69 _add_cinema = new Button (this, _("Add Cinema..."));
70 target_buttons->Add (_add_cinema, 1, wxEXPAND | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
71 _edit_cinema = new Button (this, _("Edit Cinema..."));
72 target_buttons->Add (_edit_cinema, 1, wxEXPAND | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
73 _remove_cinema = new Button (this, _("Remove Cinema"));
74 target_buttons->Add (_remove_cinema, 1, wxEXPAND | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
75 _add_screen = new Button (this, _("Add Screen..."));
76 target_buttons->Add (_add_screen, 1, wxEXPAND | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
77 _edit_screen = new Button (this, _("Edit Screen..."));
78 target_buttons->Add (_edit_screen, 1, wxEXPAND | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
79 _remove_screen = new Button (this, _("Remove Screen"));
80 target_buttons->Add (_remove_screen, 1, wxEXPAND | wxBOTTOM, DCPOMATIC_BUTTON_STACK_GAP);
82 targets->Add (target_buttons, 0, 0);
84 sizer->Add (targets, 1, wxEXPAND);
86 _search->Bind (wxEVT_TEXT, boost::bind (&ScreensPanel::search_changed, this));
87 _targets->Bind (wxEVT_TREELIST_SELECTION_CHANGED, &ScreensPanel::selection_changed_shim, this);
88 _targets->Bind (wxEVT_TREELIST_ITEM_CHECKED, &ScreensPanel::checkbox_changed, this);
90 _add_cinema->Bind (wxEVT_BUTTON, boost::bind (&ScreensPanel::add_cinema_clicked, this));
91 _edit_cinema->Bind (wxEVT_BUTTON, boost::bind (&ScreensPanel::edit_cinema_clicked, this));
92 _remove_cinema->Bind (wxEVT_BUTTON, boost::bind (&ScreensPanel::remove_cinema_clicked, this));
94 _add_screen->Bind (wxEVT_BUTTON, boost::bind (&ScreensPanel::add_screen_clicked, this));
95 _edit_screen->Bind (wxEVT_BUTTON, boost::bind (&ScreensPanel::edit_screen_clicked, this));
96 _remove_screen->Bind (wxEVT_BUTTON, boost::bind (&ScreensPanel::remove_screen_clicked, this));
102 ScreensPanel::~ScreensPanel ()
104 _targets->Unbind (wxEVT_TREELIST_SELECTION_CHANGED, &ScreensPanel::selection_changed_shim, this);
105 _targets->Unbind (wxEVT_TREELIST_ITEM_CHECKED, &ScreensPanel::checkbox_changed, this);
110 ScreensPanel::setup_sensitivity ()
112 bool const sc = _selected_cinemas.size() == 1;
113 bool const ss = _selected_screens.size() == 1;
115 _edit_cinema->Enable (sc || ss);
116 _remove_cinema->Enable (_selected_cinemas.size() >= 1);
118 _add_screen->Enable (sc || ss);
119 _edit_screen->Enable (ss);
120 _remove_screen->Enable (_selected_screens.size() >= 1);
124 optional<wxTreeListItem>
125 ScreensPanel::add_cinema (shared_ptr<Cinema> cinema)
127 auto search = wx_to_std (_search->GetValue ());
128 transform (search.begin(), search.end(), search.begin(), ::tolower);
130 if (!search.empty ()) {
131 auto name = cinema->name;
132 transform (name.begin(), name.end(), name.begin(), ::tolower);
133 if (name.find (search) == string::npos) {
138 auto id = _targets->AppendItem(_targets->GetRootItem(), std_to_wx(cinema->name));
140 _cinemas[id] = cinema;
142 for (auto screen: cinema->screens()) {
143 add_screen (cinema, screen);
150 optional<wxTreeListItem>
151 ScreensPanel::add_screen (shared_ptr<Cinema> cinema, shared_ptr<Screen> screen)
153 auto cinema_iter = _cinemas.begin();
154 while (cinema_iter != _cinemas.end() && cinema_iter->second != cinema) {
158 if (cinema_iter == _cinemas.end()) {
162 _screens[_targets->AppendItem(cinema_iter->first, std_to_wx(screen->name))] = screen;
163 return cinema_iter->first;
168 ScreensPanel::add_cinema_clicked ()
170 auto d = new CinemaDialog (GetParent(), _("Add Cinema"));
171 if (d->ShowModal () == wxID_OK) {
172 auto cinema = make_shared<Cinema>(d->name(), d->emails(), d->notes(), d->utc_offset_hour(), d->utc_offset_minute());
173 Config::instance()->add_cinema (cinema);
174 auto id = add_cinema (cinema);
176 _targets->UnselectAll ();
177 _targets->Select (*id);
185 optional<pair<wxTreeListItem, shared_ptr<Cinema>>>
186 ScreensPanel::cinema_for_operation () const
188 if (_selected_cinemas.size() == 1) {
189 return make_pair(_selected_cinemas.begin()->first, _selected_cinemas.begin()->second);
190 } else if (_selected_screens.size() == 1) {
191 return make_pair(_targets->GetItemParent(_selected_screens.begin()->first), _selected_screens.begin()->second->cinema);
199 ScreensPanel::edit_cinema_clicked ()
201 auto cinema = cinema_for_operation ();
206 auto d = new CinemaDialog (
207 GetParent(), _("Edit cinema"), cinema->second->name, cinema->second->emails, cinema->second->notes, cinema->second->utc_offset_hour(), cinema->second->utc_offset_minute()
210 if (d->ShowModal() == wxID_OK) {
211 cinema->second->name = d->name ();
212 cinema->second->emails = d->emails ();
213 cinema->second->notes = d->notes ();
214 cinema->second->set_utc_offset_hour (d->utc_offset_hour ());
215 cinema->second->set_utc_offset_minute (d->utc_offset_minute ());
216 _targets->SetItemText (cinema->first, std_to_wx(d->name()));
217 Config::instance()->changed (Config::CINEMAS);
225 ScreensPanel::remove_cinema_clicked ()
227 if (_selected_cinemas.size() == 1) {
228 if (!confirm_dialog(this, wxString::Format(_("Are you sure you want to remove the cinema '%s'?"), std_to_wx(_selected_cinemas.begin()->second->name)))) {
232 if (!confirm_dialog(this, wxString::Format(_("Are you sure you want to remove %d cinemas?"), int(_selected_cinemas.size())))) {
237 for (auto const& i: _selected_cinemas) {
238 Config::instance()->remove_cinema (i.second);
239 _targets->DeleteItem (i.first);
242 selection_changed ();
247 ScreensPanel::add_screen_clicked ()
249 auto cinema = cinema_for_operation ();
254 auto d = new ScreenDialog (GetParent(), _("Add Screen"));
255 if (d->ShowModal () != wxID_OK) {
260 for (auto screen: cinema->second->screens()) {
261 if (screen->name == d->name()) {
265 _("You cannot add a screen called '%s' as the cinema already has a screen with this name."),
266 std_to_wx(d->name()).data()
273 auto screen = std::make_shared<Screen>(d->name(), d->notes(), d->recipient(), d->recipient_file(), d->trusted_devices());
274 cinema->second->add_screen (screen);
275 auto id = add_screen (cinema->second, screen);
277 _targets->Expand (id.get ());
280 Config::instance()->changed (Config::CINEMAS);
287 ScreensPanel::edit_screen_clicked ()
289 if (_selected_screens.size() != 1) {
293 auto edit_screen = *_selected_screens.begin();
295 auto d = new ScreenDialog (
296 GetParent(), _("Edit screen"),
297 edit_screen.second->name,
298 edit_screen.second->notes,
299 edit_screen.second->recipient,
300 edit_screen.second->recipient_file,
301 edit_screen.second->trusted_devices
304 if (d->ShowModal() != wxID_OK) {
309 auto cinema = edit_screen.second->cinema;
310 for (auto screen: cinema->screens()) {
311 if (screen != edit_screen.second && screen->name == d->name()) {
315 _("You cannot change this screen's name to '%s' as the cinema already has a screen with this name."),
316 std_to_wx(d->name()).data()
323 edit_screen.second->name = d->name ();
324 edit_screen.second->notes = d->notes ();
325 edit_screen.second->recipient = d->recipient ();
326 edit_screen.second->recipient_file = d->recipient_file ();
327 edit_screen.second->trusted_devices = d->trusted_devices ();
328 _targets->SetItemText (edit_screen.first, std_to_wx(d->name()));
329 Config::instance()->changed (Config::CINEMAS);
336 ScreensPanel::remove_screen_clicked ()
338 if (_selected_screens.size() == 1) {
339 if (!confirm_dialog(this, wxString::Format(_("Are you sure you want to remove the screen '%s'?"), std_to_wx(_selected_screens.begin()->second->name)))) {
343 if (!confirm_dialog(this, wxString::Format(_("Are you sure you want to remove %d screens?"), int(_selected_screens.size())))) {
348 for (auto const& i: _selected_screens) {
349 auto j = _cinemas.begin ();
350 while (j != _cinemas.end ()) {
351 auto sc = j->second->screens ();
352 if (find (sc.begin(), sc.end(), i.second) != sc.end ()) {
359 if (j == _cinemas.end()) {
363 j->second->remove_screen (i.second);
364 _targets->DeleteItem (i.first);
367 Config::instance()->changed (Config::CINEMAS);
371 vector<shared_ptr<Screen>>
372 ScreensPanel::screens () const
374 vector<shared_ptr<Screen>> output;
376 for (auto item = _targets->GetFirstItem(); item.IsOk(); item = _targets->GetNextItem(item)) {
377 if (_targets->GetCheckedState(item) == wxCHK_CHECKED) {
378 auto screen_iter = _screens.find(item);
379 if (screen_iter != _screens.end()) {
380 output.push_back (screen_iter->second);
390 ScreensPanel::selection_changed_shim (wxTreeListEvent &)
392 selection_changed ();
397 ScreensPanel::selection_changed ()
399 if (_ignore_selection_change) {
404 _targets->GetSelections (s);
406 _selected_cinemas.clear ();
407 _selected_screens.clear ();
409 for (size_t i = 0; i < s.size(); ++i) {
410 auto cinema = _cinemas.find (s[i]);
411 if (cinema != _cinemas.end ()) {
412 _selected_cinemas[cinema->first] = cinema->second;
414 auto screen = _screens.find (s[i]);
415 if (screen != _screens.end ()) {
416 _selected_screens[screen->first] = screen->second;
420 setup_sensitivity ();
425 ScreensPanel::add_cinemas ()
427 for (auto cinema: Config::instance()->cinemas()) {
434 ScreensPanel::search_changed ()
436 _targets->DeleteAllItems ();
442 _ignore_selection_change = true;
444 for (auto const& selection: _selected_cinemas) {
445 /* The wxTreeListItems will now be different, so we must search by cinema */
446 auto cinema = _cinemas.begin ();
447 while (cinema != _cinemas.end() && cinema->second != selection.second) {
451 if (cinema != _cinemas.end()) {
452 _targets->Select (cinema->first);
456 for (auto const& selection: _selected_screens) {
457 auto screen = _screens.begin ();
458 while (screen != _screens.end() && screen->second != selection.second) {
462 if (screen != _screens.end()) {
463 _targets->Select (screen->first);
467 _ignore_selection_change = false;
472 ScreensPanel::checkbox_changed (wxTreeListEvent& ev)
474 if (_cinemas.find(ev.GetItem()) != _cinemas.end()) {
475 /* Cinema: check/uncheck all children */
476 auto const checked = _targets->GetCheckedState(ev.GetItem());
477 for (auto child = _targets->GetFirstChild(ev.GetItem()); child.IsOk(); child = _targets->GetNextSibling(child)) {
478 _targets->CheckItem(child, checked);
481 /* Screen: set cinema to checked/unchecked/3state */
482 auto parent = _targets->GetItemParent(ev.GetItem());
483 DCPOMATIC_ASSERT (parent.IsOk());
486 for (auto child = _targets->GetFirstChild(parent); child.IsOk(); child = _targets->GetNextSibling(child)) {
487 if (_targets->GetCheckedState(child) == wxCHK_CHECKED) {
494 _targets->CheckItem(parent, wxCHK_UNCHECKED);
495 } else if (unchecked == 0) {
496 _targets->CheckItem(parent, wxCHK_CHECKED);
498 _targets->CheckItem(parent, wxCHK_UNDETERMINED);