use glib; use gtk::{self, prelude::*, Adjustment, SelectionMode}; use crossbeam_channel::Sender; use failure::Error; use fragile::Fragile; use html2text; use libhandy::{Column, ColumnExt}; use rayon; use podcasts_data::dbqueries; use podcasts_data::Show; use app::Action; use utils::{self, lazy_load}; use widgets::{BaseView, EpisodeWidget, ShowMenu}; use std::rc::Rc; use std::sync::Arc; #[derive(Debug, Clone)] pub(crate) struct ShowWidget { view: BaseView, cover: gtk::Image, description: gtk::Label, episodes: gtk::ListBox, show_id: Option, } impl Default for ShowWidget { fn default() -> Self { let builder = gtk::Builder::new_from_resource("/org/gnome/Podcasts/gtk/show_widget.ui"); let sub_cont: gtk::Box = builder.get_object("sub_container").unwrap(); let cover: gtk::Image = builder.get_object("cover").unwrap(); let description: gtk::Label = builder.get_object("description").unwrap(); let view = BaseView::default(); let frame = gtk::Frame::new(None); let episodes = gtk::ListBox::new(); episodes.set_selection_mode(SelectionMode::None); let column = Column::new(); column.set_maximum_width(700); // For some reason the Column is not seen as a gtk::container // and therefore we can't call add() without the cast let column = column.upcast::(); let column = column.downcast::().unwrap(); frame.add(&episodes); sub_cont.add(&frame); column.add(&sub_cont); view.add(&column); column.show_all(); ShowWidget { view, cover, description, episodes, show_id: None, } } } impl ShowWidget { pub(crate) fn new( pd: Arc, sender: Sender, vadj: Option, ) -> Rc { let mut pdw = ShowWidget::default(); pdw.init(&pd); let menu = ShowMenu::new(&pd, &pdw.episodes, &sender); sender.send(Action::InitShowMenu(Fragile::new(menu))); let pdw = Rc::new(pdw); let res = populate_listbox(&pdw, pd.clone(), sender, vadj); debug_assert!(res.is_ok()); pdw } pub(crate) fn init(&mut self, pd: &Arc) { self.set_description(pd.description()); self.show_id = Some(pd.id()); let res = self.set_cover(&pd); debug_assert!(res.is_ok()); } pub(crate) fn container(&self) -> >k::Box { self.view.container() } pub(crate) fn get_vadjustment(&self) -> Option { self.view.get_vadjustment() } /// Set the show cover. fn set_cover(&self, pd: &Arc) -> Result<(), Error> { utils::set_image_from_path(&self.cover, pd.id(), 256) } /// Set the descripton text. fn set_description(&self, text: &str) { self.description .set_markup(html2text::from_read(text.as_bytes(), 70).trim()); } pub(crate) fn show_id(&self) -> Option { self.show_id } } /// Populate the listbox with the shows episodes. fn populate_listbox( // FIXME: we are leaking strong refs here show: &Rc, pd: Arc, sender: Sender, vadj: Option, ) -> Result<(), Error> { use crossbeam_channel::bounded; let count = dbqueries::get_pd_episodes_count(&pd)?; let (sender_, receiver) = bounded(1); rayon::spawn(clone!(pd => move || { if let Ok(episodes) = dbqueries::get_pd_episodeswidgets(&pd) { // The receiver can be dropped if there's an early return // like on show without episodes for example. sender_.send(episodes); } })); if count == 0 { let builder = gtk::Builder::new_from_resource("/org/gnome/Podcasts/gtk/empty_show.ui"); let container: gtk::Box = builder .get_object("empty_show") .ok_or_else(|| format_err!("FOO"))?; show.episodes.add(&container); return Ok(()); } let show_ = show.clone(); gtk::idle_add(move || { let episodes = match receiver.try_recv() { Some(e) => e, None => return glib::Continue(true), }; debug_assert!(episodes.len() as i64 == count); let list = show_.episodes.clone(); let constructor = clone!(sender => move |ep| { EpisodeWidget::new(ep, &sender).container.clone() }); let callback = clone!(show_, vadj => move || { if let Some(ref v) = vadj { show_.view.set_adjutments(None, Some(v)) }; }); lazy_load(episodes, list.clone(), constructor, callback); glib::Continue(false) }); Ok(()) }