use failure::Error; // use glib; use gtk; use gtk::prelude::*; use html2pango::markup_from_raw; use open; use hammond_data::Podcast; use hammond_data::dbqueries; use app::Action; use utils::set_image_from_path; use widgets::episode::episodes_listbox; use std::sync::Arc; use std::sync::mpsc::Sender; #[derive(Debug, Clone)] pub struct ShowWidget { pub container: gtk::Box, scrolled_window: gtk::ScrolledWindow, cover: gtk::Image, description: gtk::Label, link: gtk::Button, settings: gtk::MenuButton, unsub: gtk::Button, episodes: gtk::Frame, } impl Default for ShowWidget { fn default() -> Self { let builder = gtk::Builder::new_from_resource("/org/gnome/hammond/gtk/show_widget.ui"); let container: gtk::Box = builder.get_object("container").unwrap(); let scrolled_window: gtk::ScrolledWindow = builder.get_object("scrolled_window").unwrap(); let episodes: gtk::Frame = builder.get_object("episodes").unwrap(); let cover: gtk::Image = builder.get_object("cover").unwrap(); let description: gtk::Label = builder.get_object("description").unwrap(); let unsub: gtk::Button = builder.get_object("unsub_button").unwrap(); let link: gtk::Button = builder.get_object("link_button").unwrap(); let settings: gtk::MenuButton = builder.get_object("settings_button").unwrap(); ShowWidget { container, scrolled_window, cover, description, unsub, link, settings, episodes, } } } impl ShowWidget { pub fn new(pd: Arc, sender: Sender) -> ShowWidget { let pdw = ShowWidget::default(); pdw.init(pd, sender); pdw } pub fn init(&self, pd: Arc, sender: Sender) { let builder = gtk::Builder::new_from_resource("/org/gnome/hammond/gtk/show_widget.ui"); // Hacky workaround so the pd.id() can be retrieved from the `ShowStack`. WidgetExt::set_name(&self.container, &pd.id().to_string()); self.unsub .connect_clicked(clone!(pd, sender => move |bttn| { if let Err(err) = on_unsub_button_clicked(pd.clone(), bttn, sender.clone()) { error!("Error: {}", err); } })); self.setup_listbox(pd.clone(), sender.clone()); self.set_description(pd.description()); if let Err(err) = self.set_cover(pd.clone()) { error!("Failed to set a cover: {}", err) } let link = pd.link().to_owned(); self.link.set_tooltip_text(Some(link.as_str())); self.link.connect_clicked(move |_| { info!("Opening link: {}", &link); if let Err(err) = open::that(&link) { error!("Failed to open link: {}", &link); error!("Error: {}", err); } }); let show_menu: gtk::Popover = builder.get_object("show_menu").unwrap(); let mark_all: gtk::ModelButton = builder.get_object("mark_all_watched").unwrap(); let episodes = self.episodes.clone(); mark_all.connect_clicked(clone!(pd, sender => move |_| { on_played_button_clicked( pd.clone(), &episodes, sender.clone() ) })); self.settings.set_popover(&show_menu); } /// Populate the listbox with the shows episodes. fn setup_listbox(&self, pd: Arc, sender: Sender) { let listbox = episodes_listbox(pd, sender.clone()); listbox.ok().map(|l| self.episodes.add(&l)); } /// Set the show cover. fn set_cover(&self, pd: Arc) -> Result<(), Error> { set_image_from_path(&self.cover, Arc::new(pd.into()), 128) } /// Set the descripton text. fn set_description(&self, text: &str) { self.description.set_markup(&markup_from_raw(text)); } /// Set scrolled window vertical adjustment. pub fn set_vadjustment(&self, vadjustment: >k::Adjustment) { self.scrolled_window.set_vadjustment(vadjustment) } } fn on_unsub_button_clicked( pd: Arc, unsub_button: >k::Button, sender: Sender, ) -> Result<(), Error> { // hack to get away without properly checking for none. // if pressed twice would panic. unsub_button.hide(); sender.send(Action::RemoveShow(pd))?; sender.send(Action::HeaderBarNormal)?; sender.send(Action::ShowShowsAnimated)?; // Queue a refresh after the switch to avoid blocking the db. sender.send(Action::RefreshShowsView)?; sender.send(Action::RefreshEpisodesView)?; Ok(()) } fn on_played_button_clicked(pd: Arc, episodes: >k::Frame, sender: Sender) { if dim_titles(episodes).is_none() { error!("Something went horribly wrong when dimming the titles."); warn!("RUN WHILE YOU STILL CAN!"); } sender.send(Action::MarkAllPlayerNotification(pd)).unwrap(); } pub fn mark_all_watched(pd: &Podcast, sender: Sender) -> Result<(), Error> { dbqueries::update_none_to_played_now(pd)?; // Not all widgets migth have been loaded when the mark_all is hit // So we will need to refresh again after it's done. sender.send(Action::RefreshWidgetIfSame(pd.id()))?; sender.send(Action::RefreshEpisodesView).map_err(From::from) } // Ideally if we had a custom widget this would have been as simple as: // `for row in listbox { ep = row.get_episode(); ep.dim_title(); }` // But now I can't think of a better way to do it than hardcoding the title // position relative to the EpisodeWidget container gtk::Box. fn dim_titles(episodes: >k::Frame) -> Option<()> { let listbox = episodes .get_children() .remove(0) .downcast::() .ok()?; let children = listbox.get_children(); for row in children { let row = row.downcast::().ok()?; let container = row.get_children().remove(0).downcast::().ok()?; let foo = container .get_children() .remove(0) .downcast::() .ok()?; let bar = foo.get_children().remove(0).downcast::().ok()?; let baz = bar.get_children().remove(0).downcast::().ok()?; let title = baz.get_children().remove(0).downcast::().ok()?; title.get_style_context().map(|c| c.add_class("dim-label")); } Some(()) }