ShowWidget: Move controls into a headerbar menu.
This fits better the HIG and allows for more flexibility in the way the ShowWidget is implemented/designed.
This commit is contained in:
parent
5a6c73c4c1
commit
536805791e
@ -257,6 +257,29 @@ Tobias Bernard
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkMenuButton" id="secondary_menu">
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="no_show_all">True</property>
|
||||
<property name="valign">center</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">view-more-symbolic</property>
|
||||
<property name="icon_size">1</property>
|
||||
</object>
|
||||
</child>
|
||||
<style>
|
||||
<class name="image-button"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="pack_type">end</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkMenuButton" id="menu_button">
|
||||
<property name="visible">True</property>
|
||||
|
||||
20
hammond-gtk/resources/gtk/secondary_menu.ui
Normal file
20
hammond-gtk/resources/gtk/secondary_menu.ui
Normal file
@ -0,0 +1,20 @@
|
||||
<?xml version="1.0"?>
|
||||
<interface>
|
||||
<!-- interface-requires gtk+ 3.0 -->
|
||||
<menu id="menu">
|
||||
<section>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">_Mark all episodes as played</attribute>
|
||||
<!-- <attribute name="action">win.refresh</attribute> -->
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">_Website</attribute>
|
||||
<!-- <attribute name="action">win.import</attribute> -->
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">_Unsubscribe</attribute>
|
||||
<!-- <attribute name="action">win.export</attribute> -->
|
||||
</item>
|
||||
</section>
|
||||
</menu>
|
||||
</interface>
|
||||
70
hammond-gtk/resources/gtk/show_menu.ui
Normal file
70
hammond-gtk/resources/gtk/show_menu.ui
Normal file
@ -0,0 +1,70 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.22.0 -->
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.20"/>
|
||||
<object class="GtkPopoverMenu" id="menu">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="valign">center</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="margin_left">6</property>
|
||||
<property name="margin_right">6</property>
|
||||
<property name="margin_top">6</property>
|
||||
<property name="margin_bottom">6</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">6</property>
|
||||
<child>
|
||||
<object class="GtkModelButton" id="website">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="text" translatable="yes">Open Website</property>
|
||||
<property name="centered">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkModelButton" id="played">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="text" translatable="yes">Mark all as played</property>
|
||||
<property name="centered">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkModelButton" id="unsub">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="text" translatable="yes">Unsubscribe</property>
|
||||
<property name="centered">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="submenu">main</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</interface>
|
||||
@ -79,13 +79,12 @@ Tobias Bernard
|
||||
<property name="margin_bottom">32</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">24</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">6</property>
|
||||
<property name="spacing">12</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="cover">
|
||||
<property name="visible">True</property>
|
||||
@ -133,75 +132,6 @@ Sorry, we could not find a description for this Show.</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="spacing">6</property>
|
||||
<child>
|
||||
<object class="GtkMenuButton" id="settings_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="icon_name">emblem-system-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="link_button">
|
||||
<property name="label" translatable="yes">Website</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="valign">center</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="padding">5</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="unsub_button">
|
||||
<property name="label" translatable="yes">Unsubscribe</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="valign">center</property>
|
||||
<style>
|
||||
<class name="destructive-action"/>
|
||||
</style>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="padding">5</property>
|
||||
<property name="pack_type">end</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
@ -213,6 +143,7 @@ Sorry, we could not find a description for this Show.</property>
|
||||
<object class="GtkFrame">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_top">12</property>
|
||||
<property name="label_xalign">0</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<child>
|
||||
|
||||
@ -12,6 +12,7 @@
|
||||
<file compressed="true" preprocess="xml-stripblanks">gtk/headerbar.ui</file>
|
||||
<file compressed="true" preprocess="xml-stripblanks">gtk/inapp_notif.ui</file>
|
||||
<file compressed="true" preprocess="xml-stripblanks">gtk/hamburger.ui</file>
|
||||
<file compressed="true" preprocess="xml-stripblanks">gtk/show_menu.ui</file>
|
||||
<file compressed="true" preprocess="xml-stripblanks">gtk/help-overlay.ui</file>
|
||||
<file compressed="true" preprocess="xml-stripblanks">gtk/player_toolbar.ui</file>
|
||||
<file compressed="true">gtk/style.css</file>
|
||||
|
||||
@ -11,14 +11,16 @@ use gtk::SettingsExt as GtkSettingsExt;
|
||||
|
||||
use crossbeam_channel::{unbounded, Receiver, Sender};
|
||||
use hammond_data::Show;
|
||||
use send_cell::SendCell;
|
||||
|
||||
use headerbar::Header;
|
||||
use settings::{self, WindowGeometry};
|
||||
use stacks::{Content, PopulatedState};
|
||||
use utils;
|
||||
use widgets::about_dialog;
|
||||
use widgets::appnotif::{InAppNotification, UndoState};
|
||||
use widgets::player;
|
||||
use widgets::{about_dialog, mark_all_notif, remove_show_notif};
|
||||
use widgets::show_menu::{mark_all_notif, remove_show_notif, ShowMenu};
|
||||
|
||||
use std::env;
|
||||
use std::rc::Rc;
|
||||
@ -56,6 +58,7 @@ pub enum Action {
|
||||
RemoveShow(Arc<Show>),
|
||||
ErrorNotification(String),
|
||||
InitEpisode(i32),
|
||||
InitShowMenu(SendCell<ShowMenu>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@ -265,6 +268,10 @@ impl App {
|
||||
notif.show(&self.overlay);
|
||||
}
|
||||
Action::InitEpisode(rowid) => self.player.initialize_episode(rowid).unwrap(),
|
||||
Action::InitShowMenu(s) => {
|
||||
let menu = s.borrow();
|
||||
self.headerbar.set_secondary_menu(&menu.container);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -27,6 +27,7 @@ pub struct Header {
|
||||
app_menu: MenuModel,
|
||||
updater: UpdateIndicator,
|
||||
add: AddPopover,
|
||||
dots: gtk::MenuButton,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@ -141,6 +142,7 @@ impl Default for Header {
|
||||
let back = builder.get_object("back").unwrap();
|
||||
let show_title = builder.get_object("show_title").unwrap();
|
||||
let menu_button = builder.get_object("menu_button").unwrap();
|
||||
let dots = builder.get_object("secondary_menu").unwrap();
|
||||
let app_menu = menus.get_object("menu").unwrap();
|
||||
|
||||
let update_box = builder.get_object("update_notification").unwrap();
|
||||
@ -175,11 +177,11 @@ impl Default for Header {
|
||||
app_menu,
|
||||
updater,
|
||||
add,
|
||||
dots,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Factor out the hamburger menu
|
||||
// TODO: Make a proper state machine for the headerbar states
|
||||
impl Header {
|
||||
pub fn new(content: &Content, sender: &Sender<Action>) -> Rc<Self> {
|
||||
@ -209,13 +211,15 @@ impl Header {
|
||||
let add_toggle = &s.add.toggle;
|
||||
let show_title = &s.show_title;
|
||||
let menu = &s.menu_button;
|
||||
let dots = &s.dots;
|
||||
s.back.connect_clicked(
|
||||
clone!(switch, add_toggle, show_title, sender, menu => move |back| {
|
||||
clone!(switch, add_toggle, show_title, sender, menu, dots => move |back| {
|
||||
switch.show();
|
||||
add_toggle.show();
|
||||
back.hide();
|
||||
show_title.hide();
|
||||
menu.show();
|
||||
dots.hide();
|
||||
sender.send(Action::ShowShowsAnimated);
|
||||
}),
|
||||
);
|
||||
@ -230,6 +234,7 @@ impl Header {
|
||||
self.set_show_title(title);
|
||||
self.show_title.show();
|
||||
self.menu_button.hide();
|
||||
self.dots.show();
|
||||
}
|
||||
|
||||
pub fn switch_to_normal(&self) {
|
||||
@ -238,6 +243,7 @@ impl Header {
|
||||
self.back.hide();
|
||||
self.show_title.hide();
|
||||
self.menu_button.show();
|
||||
self.dots.hide();
|
||||
}
|
||||
|
||||
pub fn set_show_title(&self, title: &str) {
|
||||
@ -255,4 +261,8 @@ impl Header {
|
||||
pub fn open_menu(&self) {
|
||||
self.menu_button.clicked();
|
||||
}
|
||||
|
||||
pub fn set_secondary_menu(&self, pop: >k::PopoverMenu) {
|
||||
self.dots.set_popover(Some(pop));
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@ mod episode;
|
||||
mod home_view;
|
||||
pub mod player;
|
||||
mod show;
|
||||
pub mod show_menu;
|
||||
mod shows_view;
|
||||
|
||||
pub use self::aboutdialog::about_dialog;
|
||||
@ -12,5 +13,5 @@ pub use self::empty::EmptyView;
|
||||
pub use self::episode::EpisodeWidget;
|
||||
pub use self::home_view::HomeView;
|
||||
pub use self::show::ShowWidget;
|
||||
pub use self::show::{mark_all_notif, remove_show_notif};
|
||||
pub use self::show_menu::ShowMenu;
|
||||
pub use self::shows_view::ShowsView;
|
||||
|
||||
@ -5,18 +5,15 @@ use gtk::prelude::*;
|
||||
use crossbeam_channel::Sender;
|
||||
use failure::Error;
|
||||
use html2text;
|
||||
use open;
|
||||
use rayon;
|
||||
use send_cell::SendCell;
|
||||
|
||||
use hammond_data::dbqueries;
|
||||
use hammond_data::utils::delete_show;
|
||||
use hammond_data::Show;
|
||||
|
||||
use app::Action;
|
||||
use utils::{self, lazy_load};
|
||||
use widgets::appnotif::{InAppNotification, UndoState};
|
||||
use widgets::EpisodeWidget;
|
||||
use widgets::{EpisodeWidget, ShowMenu};
|
||||
|
||||
use std::rc::Rc;
|
||||
use std::sync::{Arc, Mutex};
|
||||
@ -32,9 +29,6 @@ pub struct ShowWidget {
|
||||
scrolled_window: gtk::ScrolledWindow,
|
||||
cover: gtk::Image,
|
||||
description: gtk::Label,
|
||||
link: gtk::Button,
|
||||
settings: gtk::MenuButton,
|
||||
unsub: gtk::Button,
|
||||
episodes: gtk::ListBox,
|
||||
show_id: Option<i32>,
|
||||
}
|
||||
@ -48,18 +42,12 @@ impl Default for ShowWidget {
|
||||
|
||||
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,
|
||||
show_id: None,
|
||||
}
|
||||
@ -69,52 +57,26 @@ impl Default for ShowWidget {
|
||||
impl ShowWidget {
|
||||
pub fn new(pd: Arc<Show>, sender: Sender<Action>) -> Rc<ShowWidget> {
|
||||
let mut pdw = ShowWidget::default();
|
||||
pdw.init(&pd, &sender);
|
||||
pdw.init(&pd);
|
||||
|
||||
let menu = ShowMenu::new(&pd, &pdw.episodes, &sender);
|
||||
sender.send(Action::InitShowMenu(SendCell::new(menu)));
|
||||
|
||||
let pdw = Rc::new(pdw);
|
||||
populate_listbox(&pdw, pd, sender)
|
||||
populate_listbox(&pdw, pd.clone(), sender)
|
||||
.map_err(|err| error!("Failed to populate the listbox: {}", err))
|
||||
.ok();
|
||||
|
||||
pdw
|
||||
}
|
||||
|
||||
pub fn init(&mut self, pd: &Arc<Show>, sender: &Sender<Action>) {
|
||||
let builder = gtk::Builder::new_from_resource("/org/gnome/Hammond/gtk/show_widget.ui");
|
||||
|
||||
self.unsub
|
||||
.connect_clicked(clone!(pd, sender => move |bttn| {
|
||||
on_unsub_button_clicked(pd.clone(), bttn, &sender);
|
||||
}));
|
||||
|
||||
pub fn init(&mut self, pd: &Arc<Show>) {
|
||||
self.set_description(pd.description());
|
||||
self.show_id = Some(pd.id());
|
||||
|
||||
self.set_cover(&pd)
|
||||
.map_err(|err| error!("Failed to set a cover: {}", err))
|
||||
.ok();
|
||||
|
||||
let link = pd.link().to_owned();
|
||||
self.link.set_tooltip_text(Some(link.as_str()));
|
||||
self.link.connect_clicked(move |_| {
|
||||
info!("Opening link: {}", &link);
|
||||
open::that(&link)
|
||||
.map_err(|err| error!("Error: {}", err))
|
||||
.map_err(|_| error!("Failed open link: {}", &link))
|
||||
.ok();
|
||||
});
|
||||
|
||||
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
|
||||
)
|
||||
}));
|
||||
self.settings.set_popover(&show_menu);
|
||||
}
|
||||
|
||||
/// Set the show cover.
|
||||
@ -173,6 +135,7 @@ impl ShowWidget {
|
||||
|
||||
/// Populate the listbox with the shows episodes.
|
||||
fn populate_listbox(
|
||||
// FIXME: Refference cycle
|
||||
show: &Rc<ShowWidget>,
|
||||
pd: Arc<Show>,
|
||||
sender: Sender<Action>,
|
||||
@ -222,111 +185,3 @@ fn populate_listbox(
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn on_unsub_button_clicked(pd: Arc<Show>, unsub_button: >k::Button, sender: &Sender<Action>) {
|
||||
// hack to get away without properly checking for none.
|
||||
// if pressed twice would panic.
|
||||
unsub_button.set_sensitive(false);
|
||||
|
||||
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);
|
||||
|
||||
unsub_button.set_sensitive(true);
|
||||
}
|
||||
|
||||
fn on_played_button_clicked(pd: Arc<Show>, episodes: >k::ListBox, sender: &Sender<Action>) {
|
||||
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))
|
||||
}
|
||||
|
||||
fn mark_all_watched(pd: &Show, sender: &Sender<Action>) -> 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);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn mark_all_notif(pd: Arc<Show>, sender: &Sender<Action>) -> InAppNotification {
|
||||
let id = pd.id();
|
||||
let callback = clone!(sender => move || {
|
||||
mark_all_watched(&pd, &sender)
|
||||
.map_err(|err| error!("Notif Callback Error: {}", err))
|
||||
.ok();
|
||||
glib::Continue(false)
|
||||
});
|
||||
|
||||
let undo_callback = clone!(sender => move || sender.send(Action::RefreshWidgetIfSame(id)));
|
||||
let text = "Marked all episodes as listened";
|
||||
InAppNotification::new(text, callback, undo_callback, UndoState::Shown)
|
||||
}
|
||||
|
||||
pub fn remove_show_notif(pd: Arc<Show>, sender: Sender<Action>) -> InAppNotification {
|
||||
let text = format!("Unsubscribed from {}", pd.title());
|
||||
|
||||
utils::ignore_show(pd.id())
|
||||
.map_err(|err| error!("Error: {}", err))
|
||||
.map_err(|_| error!("Could not insert {} to the ignore list.", pd.title()))
|
||||
.ok();
|
||||
|
||||
let callback = clone!(pd, sender => move || {
|
||||
utils::uningore_show(pd.id())
|
||||
.map_err(|err| error!("Error: {}", err))
|
||||
.map_err(|_| error!("Could not remove {} from the ignore list.", pd.title()))
|
||||
.ok();
|
||||
|
||||
// Spawn a thread so it won't block the ui.
|
||||
rayon::spawn(clone!(pd, sender => move || {
|
||||
delete_show(&pd)
|
||||
.map_err(|err| error!("Error: {}", err))
|
||||
.map_err(|_| error!("Failed to delete {}", pd.title()))
|
||||
.ok();
|
||||
|
||||
sender.send(Action::RefreshEpisodesView);
|
||||
}));
|
||||
glib::Continue(false)
|
||||
});
|
||||
|
||||
let undo_callback = move || {
|
||||
utils::uningore_show(pd.id())
|
||||
.map_err(|err| error!("{}", err))
|
||||
.ok();
|
||||
sender.send(Action::RefreshShowsView);
|
||||
sender.send(Action::RefreshEpisodesView);
|
||||
};
|
||||
|
||||
InAppNotification::new(&text, callback, undo_callback, UndoState::Shown)
|
||||
}
|
||||
|
||||
// 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::ListBox) -> Option<()> {
|
||||
let children = episodes.get_children();
|
||||
|
||||
for row in children {
|
||||
let row = row.downcast::<gtk::ListBoxRow>().ok()?;
|
||||
let container = row.get_children().remove(0).downcast::<gtk::Box>().ok()?;
|
||||
let foo = container
|
||||
.get_children()
|
||||
.remove(0)
|
||||
.downcast::<gtk::Box>()
|
||||
.ok()?;
|
||||
let bar = foo.get_children().remove(0).downcast::<gtk::Box>().ok()?;
|
||||
let title = bar.get_children().remove(0).downcast::<gtk::Label>().ok()?;
|
||||
|
||||
title.get_style_context().map(|c| c.add_class("dim-label"));
|
||||
}
|
||||
Some(())
|
||||
}
|
||||
|
||||
181
hammond-gtk/src/widgets/show_menu.rs
Normal file
181
hammond-gtk/src/widgets/show_menu.rs
Normal file
@ -0,0 +1,181 @@
|
||||
use glib;
|
||||
use gtk;
|
||||
use gtk::prelude::*;
|
||||
|
||||
use crossbeam_channel::Sender;
|
||||
use failure::Error;
|
||||
use open;
|
||||
use rayon;
|
||||
|
||||
use hammond_data::dbqueries;
|
||||
use hammond_data::utils::delete_show;
|
||||
use hammond_data::Show;
|
||||
|
||||
use app::Action;
|
||||
use utils;
|
||||
use widgets::appnotif::{InAppNotification, UndoState};
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ShowMenu {
|
||||
pub container: gtk::PopoverMenu,
|
||||
website: gtk::ModelButton,
|
||||
played: gtk::ModelButton,
|
||||
unsub: gtk::ModelButton,
|
||||
}
|
||||
|
||||
impl Default for ShowMenu {
|
||||
fn default() -> Self {
|
||||
let builder = gtk::Builder::new_from_resource("/org/gnome/Hammond/gtk/show_menu.ui");
|
||||
let container = builder.get_object("menu").unwrap();
|
||||
let website = builder.get_object("website").unwrap();
|
||||
let played = builder.get_object("played").unwrap();
|
||||
let unsub = builder.get_object("unsub").unwrap();
|
||||
|
||||
ShowMenu {
|
||||
container,
|
||||
website,
|
||||
played,
|
||||
unsub,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ShowMenu {
|
||||
pub fn new(pd: &Arc<Show>, episodes: >k::ListBox, sender: &Sender<Action>) -> Self {
|
||||
let s = Self::default();
|
||||
s.init(pd, episodes, sender);
|
||||
s
|
||||
}
|
||||
|
||||
fn init(&self, pd: &Arc<Show>, episodes: >k::ListBox, sender: &Sender<Action>) {
|
||||
self.connect_website(pd);
|
||||
self.connect_played(pd, episodes, sender);
|
||||
self.connect_unsub(pd, sender)
|
||||
}
|
||||
|
||||
fn connect_website(&self, pd: &Arc<Show>) {
|
||||
self.website.set_tooltip_text(Some(pd.link()));
|
||||
self.website.connect_clicked(clone!(pd => move |_| {
|
||||
let link = pd.link();
|
||||
info!("Opening link: {}", link);
|
||||
open::that(link)
|
||||
.map_err(|err| error!("Error: {}", err))
|
||||
.map_err(|_| error!("Failed open link: {}", link))
|
||||
.ok();
|
||||
}));
|
||||
}
|
||||
|
||||
fn connect_played(&self, pd: &Arc<Show>, episodes: >k::ListBox, sender: &Sender<Action>) {
|
||||
self.played
|
||||
.connect_clicked(clone!(pd, episodes, sender => move |_| {
|
||||
let res = dim_titles(&episodes);
|
||||
debug_assert!(res.is_some());
|
||||
|
||||
sender.send(Action::MarkAllPlayerNotification(pd.clone()))
|
||||
}));
|
||||
}
|
||||
|
||||
fn connect_unsub(&self, pd: &Arc<Show>, sender: &Sender<Action>) {
|
||||
self.unsub
|
||||
.connect_clicked(clone!(pd, sender => move |unsub| {
|
||||
// hack to get away without properly checking for none.
|
||||
// if pressed twice would panic.
|
||||
unsub.set_sensitive(false);
|
||||
|
||||
sender.send(Action::RemoveShow(pd.clone()));
|
||||
|
||||
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);
|
||||
|
||||
unsub.set_sensitive(true);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
// 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::ListBox) -> Option<()> {
|
||||
let children = episodes.get_children();
|
||||
|
||||
for row in children {
|
||||
let row = row.downcast::<gtk::ListBoxRow>().ok()?;
|
||||
let container = row.get_children().remove(0).downcast::<gtk::Box>().ok()?;
|
||||
let foo = container
|
||||
.get_children()
|
||||
.remove(0)
|
||||
.downcast::<gtk::Box>()
|
||||
.ok()?;
|
||||
let bar = foo.get_children().remove(0).downcast::<gtk::Box>().ok()?;
|
||||
let title = bar.get_children().remove(0).downcast::<gtk::Label>().ok()?;
|
||||
|
||||
title.get_style_context().map(|c| c.add_class("dim-label"));
|
||||
}
|
||||
Some(())
|
||||
}
|
||||
|
||||
fn mark_all_watched(pd: &Show, sender: &Sender<Action>) -> 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);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn mark_all_notif(pd: Arc<Show>, sender: &Sender<Action>) -> InAppNotification {
|
||||
let id = pd.id();
|
||||
let callback = clone!(sender => move || {
|
||||
mark_all_watched(&pd, &sender)
|
||||
.map_err(|err| error!("Notif Callback Error: {}", err))
|
||||
.ok();
|
||||
glib::Continue(false)
|
||||
});
|
||||
|
||||
let undo_callback = clone!(sender => move || sender.send(Action::RefreshWidgetIfSame(id)));
|
||||
let text = "Marked all episodes as listened";
|
||||
InAppNotification::new(text, callback, undo_callback, UndoState::Shown)
|
||||
}
|
||||
|
||||
pub fn remove_show_notif(pd: Arc<Show>, sender: Sender<Action>) -> InAppNotification {
|
||||
let text = format!("Unsubscribed from {}", pd.title());
|
||||
|
||||
utils::ignore_show(pd.id())
|
||||
.map_err(|err| error!("Error: {}", err))
|
||||
.map_err(|_| error!("Could not insert {} to the ignore list.", pd.title()))
|
||||
.ok();
|
||||
|
||||
let callback = clone!(pd, sender => move || {
|
||||
utils::uningore_show(pd.id())
|
||||
.map_err(|err| error!("Error: {}", err))
|
||||
.map_err(|_| error!("Could not remove {} from the ignore list.", pd.title()))
|
||||
.ok();
|
||||
|
||||
// Spawn a thread so it won't block the ui.
|
||||
rayon::spawn(clone!(pd, sender => move || {
|
||||
delete_show(&pd)
|
||||
.map_err(|err| error!("Error: {}", err))
|
||||
.map_err(|_| error!("Failed to delete {}", pd.title()))
|
||||
.ok();
|
||||
|
||||
sender.send(Action::RefreshEpisodesView);
|
||||
}));
|
||||
glib::Continue(false)
|
||||
});
|
||||
|
||||
let undo_callback = move || {
|
||||
utils::uningore_show(pd.id())
|
||||
.map_err(|err| error!("{}", err))
|
||||
.ok();
|
||||
sender.send(Action::RefreshShowsView);
|
||||
sender.send(Action::RefreshEpisodesView);
|
||||
};
|
||||
|
||||
InAppNotification::new(&text, callback, undo_callback, UndoState::Shown)
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user