+class LimitedContentPanelSplitter : public wxSplitterWindow
+{
+public:
+ LimitedContentPanelSplitter(wxWindow* parent)
+ : wxSplitterWindow(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxSP_NOBORDER | wxSP_3DSASH | wxSP_LIVE_UPDATE)
+ {
+ /* This value doesn't really mean much but we just want to stop double-click on the
+ divider from shrinking the bottom panel (#1601).
+ */
+ SetMinimumPaneSize(64);
+
+ Bind(wxEVT_SIZE, boost::bind(&LimitedContentPanelSplitter::sized, this, _1));
+ }
+
+ bool OnSashPositionChange(int new_position) override
+ {
+ /* Try to stop the top bit of the splitter getting so small that buttons disappear */
+ auto const ok = new_position > 220;
+ if (ok) {
+ Config::instance()->set_main_content_divider_sash_position(new_position);
+ }
+ return ok;
+ }
+
+ void first_shown(wxWindow* top, wxWindow* bottom)
+ {
+ int const sn = wxDisplay::GetFromWindow(this);
+ /* Fallback for when GetFromWindow fails for reasons that aren't clear */
+ int pos = -600;
+ if (sn >= 0) {
+ wxRect const screen = wxDisplay(sn).GetClientArea();
+ /* This is a hack to try and make the content notebook a sensible size; large on big displays but small
+ enough on small displays to leave space for the content area.
+ */
+ pos = screen.height > 800 ? -600 : -_top_panel_minimum_size;
+ }
+ SplitHorizontally(top, bottom, Config::instance()->main_content_divider_sash_position().get_value_or(pos));
+ _first_shown = true;
+ }
+
+private:
+ void sized(wxSizeEvent& ev)
+ {
+ auto const height = GetSize().GetHeight();
+ if (_first_shown && (!_last_height || *_last_height != height) && height > _top_panel_minimum_size && GetSashPosition() < _top_panel_minimum_size) {
+ /* The window is now fairly big but the top panel is small; this happens when the DCP-o-matic window
+ * is shrunk and then made larger again. Try to set a sensible top panel size in this case (#1839).
+ */
+ SetSashPosition(Config::instance()->main_content_divider_sash_position().get_value_or(_top_panel_minimum_size));
+ }
+
+ ev.Skip ();
+ _last_height = height;
+ }
+
+ bool _first_shown = false;
+ int const _top_panel_minimum_size = 350;
+ boost::optional<int> _last_height;
+};
+
+
+class ContentDropTarget : public wxFileDropTarget
+{
+public:
+ ContentDropTarget(ContentPanel* owner)
+ : _panel(owner)
+ {}
+
+ bool OnDropFiles(wxCoord, wxCoord, wxArrayString const& filenames) override
+ {
+ vector<boost::filesystem::path> files;
+ vector<boost::filesystem::path> dcps;
+ vector<boost::filesystem::path> folders;
+ for (size_t i = 0; i < filenames.GetCount(); ++i) {
+ auto path = boost::filesystem::path(wx_to_std(filenames[i]));
+ if (boost::filesystem::is_regular_file(path)) {
+ files.push_back(path);
+ } else if (boost::filesystem::is_directory(path)) {
+ if (contains_assetmap(path)) {
+ dcps.push_back(path);
+ } else {
+ folders.push_back(path);
+ }
+ }
+ }
+
+ if (!filenames.empty()) {
+ _panel->add_files(files);
+ }
+
+ for (auto dcp: dcps) {
+ _panel->add_dcp(dcp);
+ }
+
+ for (auto dir: folders) {
+ _panel->add_folder(dir);
+ }
+
+ return true;
+ };
+
+private:
+ ContentPanel* _panel;
+};
+
+
+/** A wxListCtrl that can middle-ellipsize its text */
+class ContentListCtrl : public wxListCtrl
+{
+public:
+ ContentListCtrl(wxWindow* parent)
+ : wxListCtrl(parent, wxID_ANY, wxDefaultPosition, wxSize(320, 160), wxLC_REPORT | wxLC_NO_HEADER | wxLC_VIRTUAL)
+ {
+ _red.SetTextColour(*wxRED);
+ }
+
+ struct Item
+ {
+ wxString text;
+ weak_ptr<Content> content;
+ bool error;
+ };
+
+ void set(vector<Item> const& items)
+ {
+ _items = items;
+ SetItemCount(items.size());
+ }
+
+ wxString OnGetItemText(long item, long) const override
+ {
+ DCPOMATIC_ASSERT(item >= 0 && item < static_cast<long>(_items.size()));
+ wxClientDC dc(const_cast<wxWindow*>(static_cast<wxWindow const*>(this)));
+ return wxControl::Ellipsize(_items[item].text, dc, wxELLIPSIZE_MIDDLE, GetSize().GetWidth());
+ }
+
+ wxListItemAttr* OnGetItemAttr(long item) const override
+ {
+ DCPOMATIC_ASSERT(item >= 0 && item < static_cast<long>(_items.size()));
+ return _items[item].error ? const_cast<wxListItemAttr*>(&_red) : nullptr;
+ }
+
+ weak_ptr<Content> content_at_index(long index)
+ {
+ if (index < 0 || index >= static_cast<long>(_items.size())) {
+ return {};
+ }
+ return _items[index].content;
+ }
+
+private:
+ std::vector<Item> _items;
+ wxListItemAttr _red;
+};
+
+
+ContentPanel::ContentPanel(wxNotebook* n, shared_ptr<Film> film, FilmViewer& viewer)