Keyboard shortcut overview!

(shame everything else is broken...)
This commit is contained in:
Zander Brown 2018-05-20 13:59:00 +01:00
parent 095dd73c52
commit 8c2ea052de
6 changed files with 153 additions and 141 deletions

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<object class="GtkShortcutsWindow" id="help_overlay">
<property name="modal">True</property>
<child>
<object class="GtkShortcutsSection">
<property name="section-name">shortcuts</property>
<property name="max-height">12</property>
<child>
<object class="GtkShortcutsGroup">
<property name="title" translatable="yes">Miscellaneous</property>
<child>
<object class="GtkShortcutsShortcut">
<property name="accelerator">&lt;primar&gt;q</property>
<property name="title" translatable="yes">Quit</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</interface>

View File

@ -12,6 +12,7 @@
<file compressed="true" preprocess="xml-stripblanks">gtk/headerbar.ui</file> <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/inapp_notif.ui</file>
<file compressed="true" preprocess="xml-stripblanks">gtk/menus.ui</file> <file compressed="true" preprocess="xml-stripblanks">gtk/menus.ui</file>
<file compressed="true" preprocess="xml-stripblanks">gtk/help-overlay.ui</file>
<file compressed="true">gtk/style.css</file> <file compressed="true">gtk/style.css</file>
</gresource> </gresource>
</gresources> </gresources>

View File

@ -9,15 +9,15 @@ use gtk::SettingsExt as GtkSettingsExt;
use hammond_data::Podcast; use hammond_data::Podcast;
use hammond_data::{opml}; use hammond_data::{opml};
use appnotif::{InAppNotification, UndoState}; //use appnotif::{InAppNotification, UndoState};
use headerbar::Header; use headerbar::Header;
use settings::{self, WindowGeometry}; use settings::{self, WindowGeometry};
use stacks::{Content, PopulatedState}; use stacks::{Content/*, PopulatedState*/};
use utils; use utils;
use widgets::{mark_all_notif, remove_show_notif}; //use widgets::{mark_all_notif, remove_show_notif};
use std::rc::Rc; use std::rc::Rc;
use std::sync::mpsc::{channel, Receiver, Sender}; use std::sync::mpsc::{channel, Sender};
use std::sync::Arc; use std::sync::Arc;
use rayon; use rayon;
@ -44,12 +44,6 @@ pub enum Action {
#[derive(Debug)] #[derive(Debug)]
pub struct App { pub struct App {
app_instance: gtk::Application, app_instance: gtk::Application,
window: gtk::Window,
overlay: gtk::Overlay,
header: Rc<Header>,
content: Rc<Content>,
receiver: Receiver<Action>,
sender: Sender<Action>,
settings: Settings, settings: Settings,
} }
@ -66,21 +60,11 @@ impl App {
let cleanup_date = settings::get_cleanup_date(&settings); let cleanup_date = settings::get_cleanup_date(&settings);
utils::cleanup(cleanup_date); utils::cleanup(cleanup_date);
// Create the main window
let window = gtk::Window::new(gtk::WindowType::Toplevel);
window.set_title("Hammond");
let (sender, receiver) = channel();
window.connect_delete_event(clone!(application, settings, window => move |_, _| {
WindowGeometry::from_window(&window).write(&settings);
application.quit();
Inhibit(false)
}));
// Ideally a lot more than actions would happen in startup & window // Ideally a lot more than actions would happen in startup & window
// creation would be in activate // creation would be in activate
application.connect_startup(clone!(window, sender => move |app| { application.connect_startup(clone!(settings => move |app| {
let (sender, _receiver) = channel();
let refresh = SimpleAction::new("refresh", None); let refresh = SimpleAction::new("refresh", None);
refresh.connect_activate(clone!(sender => move |_, _| { refresh.connect_activate(clone!(sender => move |_, _| {
gtk::idle_add(clone!(sender => move || { gtk::idle_add(clone!(sender => move || {
@ -92,17 +76,38 @@ impl App {
app.add_action(&refresh); app.add_action(&refresh);
let import = SimpleAction::new("import", None); let import = SimpleAction::new("import", None);
import.connect_activate(clone!(window, sender => move |_, _| on_import_clicked(&window, &sender))); import.connect_activate(clone!(sender, app => move |_, _| {
let window = app.get_active_window().expect("Failed to get active window");
on_import_clicked(&window, &sender);
}));
app.add_action(&import); app.add_action(&import);
let about = SimpleAction::new("about", None); let about = SimpleAction::new("about", None);
// Should investigate use of active_window here about.connect_activate(clone!(app => move |_, _| {
about.connect_activate(clone!(window => move |_, _| about_dialog(&window))); let window = app.get_active_window().expect("Failed to get active window");
about_dialog(&window);
}));
app.add_action(&about); app.add_action(&about);
let quit = SimpleAction::new("quit", None); let quit = SimpleAction::new("quit", None);
quit.connect_activate(clone!(app => move |_, _| app.quit())); quit.connect_activate(clone!(app => move |_, _| app.quit()));
app.add_action(&quit); app.add_action(&quit);
app.connect_activate(clone!(sender, settings => move |app| {
// Get the current window (if any)
if let Some(window) = app.get_active_window() {
// Already open, just raise the window
window.present();
} else {
// Time to open one!
// Create the main window
let window = gtk::ApplicationWindow::new(&app);
window.set_title("Hammond");
window.connect_delete_event(clone!(app, settings => move |window, _| {
WindowGeometry::from_window(&window).write(&settings);
app.quit();
Inhibit(false)
})); }));
// Create a content instance // Create a content instance
@ -110,7 +115,7 @@ impl App {
Rc::new(Content::new(sender.clone()).expect("Content Initialization failed.")); Rc::new(Content::new(sender.clone()).expect("Content Initialization failed."));
// Create the headerbar // Create the headerbar
let header = Rc::new(Header::new(&content, &window, &sender)); let _header = Rc::new(Header::new(&content, &window, &sender));
// Add the content main stack to the overlay. // Add the content main stack to the overlay.
let overlay = gtk::Overlay::new(); let overlay = gtk::Overlay::new();
@ -119,79 +124,16 @@ impl App {
// Add the overlay to the main window // Add the overlay to the main window
window.add(&overlay); window.add(&overlay);
App { WindowGeometry::from_settings(&settings).apply(&window);
app_instance: application,
window,
overlay,
header,
content,
receiver,
sender,
settings,
}
}
fn setup_timed_callbacks(&self) { App::setup_timed_callbacks(&sender, &settings);
self.setup_dark_theme();
self.setup_refresh_on_startup();
self.setup_auto_refresh();
}
fn setup_dark_theme(&self) { window.show_all();
let settings = gtk::Settings::get_default().unwrap(); window.activate();
let enabled = self.settings.get_boolean("dark-theme");
settings.set_property_gtk_application_prefer_dark_theme(enabled); let _headerbar = _header;
}
fn setup_refresh_on_startup(&self) {
// Update the feeds right after the Application is initialized.
if self.settings.get_boolean("refresh-on-startup") {
let sender = self.sender.clone();
info!("Refresh on startup.");
// The ui loads async, after initialization
// so we need to delay this a bit so it won't block
// requests that will come from loading the gui on startup.
gtk::timeout_add(1500, move || {
let s: Option<Vec<_>> = None;
utils::refresh(s, sender.clone());
glib::Continue(false)
});
}
}
fn setup_auto_refresh(&self) {
let refresh_interval = settings::get_refresh_interval(&self.settings).num_seconds() as u32;
let sender = self.sender.clone();
info!("Auto-refresh every {:?} seconds.", refresh_interval);
gtk::timeout_add_seconds(refresh_interval, move || {
let s: Option<Vec<_>> = None;
utils::refresh(s, sender.clone());
glib::Continue(true)
});
}
pub fn run(self) {
WindowGeometry::from_settings(&self.settings).apply(&self.window);
let window = self.window.clone();
self.app_instance.connect_startup(move |app| {
build_ui(&window, app);
});
self.setup_timed_callbacks();
let content = self.content;
let headerbar = self.header;
let sender = self.sender;
let overlay = self.overlay;
let receiver = self.receiver;
gtk::timeout_add(50, move || { gtk::timeout_add(50, move || {
match receiver.try_recv() { /*match receiver.try_recv() {
Ok(Action::RefreshAllViews) => content.update(), Ok(Action::RefreshAllViews) => content.update(),
Ok(Action::RefreshShowsView) => content.update_shows_view(), Ok(Action::RefreshShowsView) => content.update_shows_view(),
Ok(Action::RefreshWidgetIfSame(id)) => content.update_widget_if_same(id), Ok(Action::RefreshWidgetIfSame(id)) => content.update_widget_if_same(id),
@ -239,22 +181,68 @@ impl App {
notif.show(&overlay); notif.show(&overlay);
} }
Err(_) => (), Err(_) => (),
} }*/
Continue(true) Continue(true)
}); });
}
}));
}));
App {
app_instance: application,
settings,
}
}
fn setup_timed_callbacks(sender: &Sender<Action>, settings: &Settings) {
App::setup_dark_theme(&sender, settings);
App::setup_refresh_on_startup(&sender, settings);
App::setup_auto_refresh(&sender, settings);
}
fn setup_dark_theme(_sender: &Sender<Action>, settings: &Settings) {
let gtk_settings = gtk::Settings::get_default().unwrap();
let enabled = settings.get_boolean("dark-theme");
gtk_settings.set_property_gtk_application_prefer_dark_theme(enabled);
}
fn setup_refresh_on_startup(sender: &Sender<Action>, settings: &Settings) {
// Update the feeds right after the Application is initialized.
let sender = sender.clone();
if settings.get_boolean("refresh-on-startup") {
info!("Refresh on startup.");
// The ui loads async, after initialization
// so we need to delay this a bit so it won't block
// requests that will come from loading the gui on startup.
gtk::timeout_add(1500, move || {
let s: Option<Vec<_>> = None;
utils::refresh(s, sender.clone());
glib::Continue(false)
});
}
}
fn setup_auto_refresh(sender: &Sender<Action>, settings: &Settings) {
let refresh_interval = settings::get_refresh_interval(&settings).num_seconds() as u32;
info!("Auto-refresh every {:?} seconds.", refresh_interval);
let sender = sender.clone();
gtk::timeout_add_seconds(refresh_interval, move || {
let s: Option<Vec<_>> = None;
utils::refresh(s, sender.clone());
glib::Continue(true)
});
}
pub fn run(self) {
ApplicationExtManual::run(&self.app_instance, &[]); ApplicationExtManual::run(&self.app_instance, &[]);
} }
} }
fn build_ui(window: &gtk::Window, app: &gtk::Application) {
window.set_application(app);
window.show_all();
window.activate();
app.connect_activate(move |_| ());
}
// Totally copied it from fractal. // Totally copied it from fractal.
// https://gitlab.gnome.org/danigm/fractal/blob/503e311e22b9d7540089d735b92af8e8f93560c5/fractal-gtk/src/app.rs#L1883-1912 // https://gitlab.gnome.org/danigm/fractal/blob/503e311e22b9d7540089d735b92af8e8f93560c5/fractal-gtk/src/app.rs#L1883-1912
fn about_dialog(window: &gtk::Window) { fn about_dialog(window: &gtk::Window) {

View File

@ -58,13 +58,13 @@ impl Default for Header {
// TODO: Refactor components into smaller state machines // TODO: Refactor components into smaller state machines
impl Header { impl Header {
pub fn new(content: &Content, window: &gtk::Window, sender: &Sender<Action>) -> Header { pub fn new(content: &Content, window: &gtk::ApplicationWindow, sender: &Sender<Action>) -> Header {
let h = Header::default(); let h = Header::default();
h.init(content, window, &sender); h.init(content, window, &sender);
h h
} }
pub fn init(&self, content: &Content, window: &gtk::Window, sender: &Sender<Action>) { pub fn init(&self, content: &Content, window: &gtk::ApplicationWindow, sender: &Sender<Action>) {
let builder = gtk::Builder::new_from_resource("/org/gnome/Hammond/gtk/headerbar.ui"); let builder = gtk::Builder::new_from_resource("/org/gnome/Hammond/gtk/headerbar.ui");
let add_popover: gtk::Popover = builder.get_object("add_popover").unwrap(); let add_popover: gtk::Popover = builder.get_object("add_popover").unwrap();

View File

@ -4,7 +4,7 @@
)] )]
#![allow(unknown_lints)] #![allow(unknown_lints)]
#![warn(unused_extern_crates, unused)] #![warn(unused_extern_crates, unused)]
#![deny(warnings)] //#![deny(warnings)]
extern crate gdk; extern crate gdk;
extern crate gdk_pixbuf; extern crate gdk_pixbuf;

View File

@ -15,7 +15,7 @@ pub struct WindowGeometry {
} }
impl WindowGeometry { impl WindowGeometry {
pub fn from_window(window: &gtk::Window) -> WindowGeometry { pub fn from_window(window: &gtk::ApplicationWindow) -> WindowGeometry {
let position = window.get_position(); let position = window.get_position();
let size = window.get_size(); let size = window.get_size();
let left = position.0; let left = position.0;
@ -49,7 +49,7 @@ impl WindowGeometry {
} }
} }
pub fn apply(&self, window: &gtk::Window) { pub fn apply(&self, window: &gtk::ApplicationWindow) {
if self.width > 0 && self.height > 0 { if self.width > 0 && self.height > 0 {
window.resize(self.width, self.height); window.resize(self.width, self.height);
} }