show.rs 5.46 KB
Newer Older
1
use glib;
2
use gtk;
3
use gtk::prelude::*;
4

5
use crossbeam_channel::Sender;
6
use failure::Error;
7
use fragile::Fragile;
8
use html2text;
9
use rayon;
10

11 12
use podcasts_data::dbqueries;
use podcasts_data::Show;
13

14
use app::Action;
15
use utils::{self, lazy_load};
16
use widgets::{EpisodeWidget, ShowMenu};
17

18 19 20 21
use std::rc::Rc;
use std::sync::{Arc, Mutex};

lazy_static! {
22
    static ref SHOW_WIDGET_VALIGNMENT: Mutex<Option<(i32, Fragile<gtk::Adjustment>)>> =
23 24
        Mutex::new(None);
}
25

26
#[derive(Debug, Clone)]
Jordan Petridis's avatar
Jordan Petridis committed
27
pub struct ShowWidget {
Jordan Petridis's avatar
Jordan Petridis committed
28
    pub container: gtk::Box,
29
    scrolled_window: gtk::ScrolledWindow,
30
    cover: gtk::Image,
31
    description: gtk::Label,
32
    episodes: gtk::ListBox,
33
    show_id: Option<i32>,
34 35
}

36 37
impl Default for ShowWidget {
    fn default() -> Self {
38
        let builder = gtk::Builder::new_from_resource("/org/gnome/Podcasts/gtk/show_widget.ui");
39
        let container: gtk::Box = builder.get_object("container").unwrap();
40
        let scrolled_window: gtk::ScrolledWindow = builder.get_object("scrolled_window").unwrap();
41
        let episodes = builder.get_object("episodes").unwrap();
42 43

        let cover: gtk::Image = builder.get_object("cover").unwrap();
44 45
        let description: gtk::Label = builder.get_object("description").unwrap();

Jordan Petridis's avatar
Jordan Petridis committed
46
        ShowWidget {
47
            container,
48
            scrolled_window,
49 50
            cover,
            description,
51
            episodes,
52
            show_id: None,
53 54
        }
    }
55
}
56

57
impl ShowWidget {
58
    pub fn new(pd: Arc<Show>, sender: Sender<Action>) -> Rc<ShowWidget> {
59
        let mut pdw = ShowWidget::default();
60 61 62
        pdw.init(&pd);

        let menu = ShowMenu::new(&pd, &pdw.episodes, &sender);
63
        sender.send(Action::InitShowMenu(Fragile::new(menu)));
64

65
        let pdw = Rc::new(pdw);
66 67
        let res = populate_listbox(&pdw, pd.clone(), sender);
        debug_assert!(res.is_ok());
68

69 70 71
        pdw
    }

72
    pub fn init(&mut self, pd: &Arc<Show>) {
73
        self.set_description(pd.description());
74
        self.show_id = Some(pd.id());
75

76 77
        let res = self.set_cover(&pd);
        debug_assert!(res.is_ok());
78 79 80
    }

    /// Set the show cover.
81
    fn set_cover(&self, pd: &Arc<Show>) -> Result<(), Error> {
82
        utils::set_image_from_path(&self.cover, pd.id(), 256)
83
    }
84

85 86
    /// Set the descripton text.
    fn set_description(&self, text: &str) {
87
        self.description
88
            .set_markup(html2text::from_read(text.as_bytes(), 70).trim());
89
    }
90

91 92 93
    /// Save the scrollabar vajustment to the cache.
    pub fn save_vadjustment(&self, oldid: i32) -> Result<(), Error> {
        if let Ok(mut guard) = SHOW_WIDGET_VALIGNMENT.lock() {
Zander Brown's avatar
Zander Brown committed
94 95
            let adj = self
                .scrolled_window
96 97
                .get_vadjustment()
                .ok_or_else(|| format_err!("Could not get the adjustment"))?;
98
            *guard = Some((oldid, Fragile::new(adj)));
99 100 101 102
            debug!("Widget Alignment was saved with ID: {}.", oldid);
        }

        Ok(())
103
    }
104

105
    /// Set scrolled window vertical adjustment.
106
    fn set_vadjustment(&self, pd: &Arc<Show>) -> Result<(), Error> {
107 108 109 110
        let guard = SHOW_WIDGET_VALIGNMENT
            .lock()
            .map_err(|err| format_err!("Failed to lock widget align mutex: {}", err))?;

111
        if let Some((oldid, ref fragile)) = *guard {
112 113 114 115 116 117 118
            // Only copy the old scrollbar if both widget's represent the same podcast.
            debug!("PID: {}", pd.id());
            debug!("OLDID: {}", oldid);
            if pd.id() != oldid {
                debug!("Early return");
                return Ok(());
            };
119

120
            // Copy the vertical scrollbar adjustment from the old view into the new one.
121
            let res = fragile
122
                .try_get()
123 124 125 126 127
                .map(|x| utils::smooth_scroll_to(&self.scrolled_window, &x))
                .map_err(From::from);

            debug_assert!(res.is_ok());
            return res;
128 129
        }

130 131
        Ok(())
    }
132

133 134
    pub fn show_id(&self) -> Option<i32> {
        self.show_id
135
    }
136
}
137

138 139 140
/// Populate the listbox with the shows episodes.
fn populate_listbox(
    show: &Rc<ShowWidget>,
141
    pd: Arc<Show>,
142 143 144 145 146 147 148 149
    sender: Sender<Action>,
) -> Result<(), Error> {
    use crossbeam_channel::bounded;

    let count = dbqueries::get_pd_episodes_count(&pd)?;

    let (sender_, receiver) = bounded(1);
    rayon::spawn(clone!(pd => move || {
150 151 152 153 154
        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);
        }
155 156 157
    }));

    if count == 0 {
158
        let builder = gtk::Builder::new_from_resource("/org/gnome/Podcasts/gtk/empty_show.ui");
159 160 161
        let container: gtk::Box = builder
            .get_object("empty_show")
            .ok_or_else(|| format_err!("FOO"))?;
162 163 164
        show.episodes.add(&container);
        return Ok(());
    }
165

166 167 168
    let show_ = show.clone();
    gtk::idle_add(move || {
        let episodes = match receiver.try_recv() {
169 170
            Some(e) => e,
            None => return glib::Continue(true),
171
        };
172
        debug_assert!(episodes.len() as i64 == count);
173

174 175
        let list = show_.episodes.clone();
        let constructor = clone!(sender => move |ep| {
176
            EpisodeWidget::new(ep, &sender).container.clone()
177 178
        });

179
        let callback = clone!(pd, show_ => move || {
180 181
            let res = show_.set_vadjustment(&pd);
            debug_assert!(res.is_ok());
182 183 184 185 186 187 188 189
        });

        lazy_load(episodes, list.clone(), constructor, callback);

        glib::Continue(false)
    });

    Ok(())
190
}