/* Copyright (C) 2013-2016 Carl Hetherington This file is part of DCP-o-matic. DCP-o-matic is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. DCP-o-matic is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with DCP-o-matic. If not, see . */ /** @file src/wx/content_widget.h * @brief ContentWidget class. */ #ifndef DCPOMATIC_CONTENT_WIDGET_H #define DCPOMATIC_CONTENT_WIDGET_H #include "wx_util.h" #include "lib/content.h" #include LIBDCP_DISABLE_WARNINGS #include #include #include LIBDCP_ENABLE_WARNINGS #include /** @class ContentWidget * @brief A widget which represents some Content state and which can be used * when multiple pieces of content are selected. * * @param S Type of ContentPart being manipulated (e.g. VideoContent) * @param T Type of the widget (e.g. wxSpinCtrl) * @param U Data type of state as used by the model. * @param V Data type of state as used by the view. */ template class ContentWidget { public: /** @param parent Parent window. * @param wrapped Control widget that we are wrapping. * @param property ContentProperty that the widget is handling. * @param part Part of Content that the property is in (e.g. &Content::video) * @param model_getter Function on the Content to get the value. * @param model_setter Function on the Content to set the value. * @param view_changed Function called when the view has changed; useful for linking controls. * @param view_to_model Function to convert a view value to a model value. * @param model_to_view Function to convert a model value to a view value. */ ContentWidget ( wxWindow* parent, T* wrapped, int property, std::function (Content*)> part, std::function model_getter, std::function model_setter, std::function view_changed, std::function view_to_model, std::function model_to_view ) : _wrapped (wrapped) , _sizer (0) , _button (new wxButton (parent, wxID_ANY, _("Multiple values"))) , _property (property) , _part(std::move(part)) , _model_getter(std::move(model_getter)) , _model_setter(std::move(model_setter)) , _view_changed(std::move(view_changed)) , _view_to_model(std::move(view_to_model)) , _model_to_view(std::move(model_to_view)) , _ignore_model_changes (false) { _button->SetToolTip (_("Click the button to set all selected content to the same value.")); _button->Hide (); _button->Bind (wxEVT_COMMAND_BUTTON_CLICKED, boost::bind (&ContentWidget::button_clicked, this)); } ContentWidget (ContentWidget const&) = delete; ContentWidget& operator= (ContentWidget const&) = delete; /** @return the widget that we are wrapping */ T* wrapped () const { return _wrapped; } typedef std::vector > List; /** Set the content that this control is working on (i.e. the selected content) */ void set_content (List content) { for (typename std::list::iterator i = _connections.begin(); i != _connections.end(); ++i) { i->disconnect (); } _connections.clear (); _content = content; _wrapped->Enable (!_content.empty ()); update_from_model (); for (typename List::iterator i = _content.begin(); i != _content.end(); ++i) { #if BOOST_VERSION >= 106100 _connections.push_back((*i)->Change.connect(boost::bind(&ContentWidget::model_changed, this, boost::placeholders::_1, boost::placeholders::_2))); #else _connections.push_back((*i)->Change.connect(boost::bind(&ContentWidget::model_changed, this, _1, _2))); #endif } } /** Add this widget to a wxGridBagSizer */ void add (wxGridBagSizer* sizer, wxGBPosition position, wxGBSpan span = wxDefaultSpan, int flag = 0) { _sizer = sizer; _position = position; _span = span; _sizer->Add (_wrapped, _position, _span, flag); } /** Update the view from the model */ void update_from_model () { if (_content.empty ()) { set_single (); return; } typename List::iterator i = _content.begin (); U const v = boost::bind (_model_getter, _part(_content.front().get()).get())(); while (i != _content.end() && boost::bind (_model_getter, _part(i->get()).get())() == v) { ++i; } if (i == _content.end ()) { set_single (); checked_set (_wrapped, _model_to_view (v)); } else { set_multiple (); } } void view_changed () { _ignore_model_changes = true; for (size_t i = 0; i < _content.size(); ++i) { boost::bind (_model_setter, _part (_content[i].get()).get(), _view_to_model (wx_get (_wrapped))) (); } if (_view_changed) { _view_changed (); } _ignore_model_changes = false; } void show (bool s) { _wrapped->Show (s); } private: void set_single () { if (_wrapped->IsShown() || !_sizer) { return; } _sizer->Detach (_button); _button->Hide (); _sizer->Add (_wrapped, _position, _span); _wrapped->Show (); _sizer->Layout (); } void set_multiple () { if (_button->IsShown() || !_sizer) { return; } _wrapped->Hide (); _sizer->Detach (_wrapped); _button->Show (); _sizer->Add(_button, _position, _span, wxALIGN_CENTER_VERTICAL); _sizer->Layout (); } void button_clicked () { U const v = boost::bind (_model_getter, _part(_content.front().get()).get())(); for (auto const& i: _content) { boost::bind (_model_setter, _part(i.get()).get(), v)(); } } void model_changed (ChangeType type, int property) { if (type == ChangeType::DONE && property == _property && !_ignore_model_changes) { update_from_model (); } } T* _wrapped; wxGridBagSizer* _sizer; wxGBPosition _position; wxGBSpan _span; wxButton* _button; List _content; int _property; std::function (Content *)> _part; std::function _model_getter; std::function _model_setter; std::function _view_changed; std::function _view_to_model; std::function _model_to_view; std::list _connections; bool _ignore_model_changes; }; template V caster (U x) { return static_cast (x); } template class ContentSpinCtrl : public ContentWidget { public: ContentSpinCtrl ( wxWindow* parent, wxSpinCtrl* wrapped, int property, std::function (Content *)> part, std::function getter, std::function setter, std::function view_changed = std::function() ) : ContentWidget ( parent, wrapped, property, part, getter, setter, view_changed, &caster, &caster ) { wrapped->Bind (wxEVT_COMMAND_SPINCTRL_UPDATED, boost::bind (&ContentWidget::view_changed, this)); } }; template class ContentSpinCtrlDouble : public ContentWidget { public: ContentSpinCtrlDouble ( wxWindow* parent, wxSpinCtrlDouble* wrapped, int property, std::function (Content *)> part, std::function getter, std::function setter, std::function view_changed = std::function() ) : ContentWidget ( parent, wrapped, property, part, getter, setter, view_changed, &caster, &caster ) { wrapped->Bind (wxEVT_COMMAND_SPINCTRLDOUBLE_UPDATED, boost::bind (&ContentWidget::view_changed, this)); } }; template class ContentChoice : public ContentWidget { public: ContentChoice ( wxWindow* parent, wxChoice* wrapped, int property, std::function (Content *)> part, std::function getter, std::function setter, std::function view_to_model, std::function model_to_view, std::function view_changed = std::function() ) : ContentWidget ( parent, wrapped, property, part, getter, setter, view_changed, view_to_model, model_to_view ) { wrapped->Bind (wxEVT_COMMAND_CHOICE_SELECTED, boost::bind (&ContentWidget::view_changed, this)); } }; #endif