subclassing: use composite templates everywhere

This commit is contained in:
Bilal Elmoussaoui 2020-12-03 15:10:24 +01:00
parent c581c12ab3
commit ddddb97c5f
25 changed files with 2226 additions and 1857 deletions

2452
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -20,6 +20,7 @@ surf = "2.1"
url = "2.1" url = "2.1"
zbar-rust = "0.0" zbar-rust = "0.0"
secret-service = "1.1" secret-service = "1.1"
once_cell = "1.5"
[dependencies.gtk] [dependencies.gtk]
git = "https://github.com/gtk-rs/gtk4-rs" git = "https://github.com/gtk-rs/gtk4-rs"

View file

@ -15,7 +15,7 @@
<!-- UI Files --> <!-- UI Files -->
<file compressed="true" preprocess="xml-stripblanks" alias="shortcuts.ui">resources/ui/shortcuts.ui</file> <file compressed="true" preprocess="xml-stripblanks" alias="shortcuts.ui">resources/ui/shortcuts.ui</file>
<file compressed="true" preprocess="xml-stripblanks">about_dialog.ui</file> <file compressed="true" preprocess="xml-stripblanks">about_dialog.ui</file>
<file compressed="true" preprocess="xml-stripblanks">window.ui</file> <file>window.ui</file>
<file compressed="true" preprocess="xml-stripblanks">preferences.ui</file> <file compressed="true" preprocess="xml-stripblanks">preferences.ui</file>
<file compressed="true" preprocess="xml-stripblanks">preferences_password_page.ui</file> <file compressed="true" preprocess="xml-stripblanks">preferences_password_page.ui</file>
<file compressed="true" preprocess="xml-stripblanks" alias="preferences_provider_page.ui">resources/ui/preferences_provider_page.ui</file> <file compressed="true" preprocess="xml-stripblanks" alias="preferences_provider_page.ui">resources/ui/preferences_provider_page.ui</file>

View file

@ -1,11 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<interface> <interface>
<object class="HdyWindow" id="add_dialog"> <template parent="HdyWindow" class="AccountAddDialog">
<property name="modal">True</property> <property name="modal">True</property>
<property name="default_width">360</property> <property name="default_width">360</property>
<property name="default_height">600</property> <property name="default_height">600</property>
<property name="title">Add a new account</property> <property name="title">Add a new account</property>
<property name="destroy_with_parent">True</property>
<child> <child>
<object class="HdyLeaflet"> <object class="HdyLeaflet">
<child> <child>
@ -143,8 +142,7 @@
</child> </child>
<child> <child>
<object class="GtkListBox" id="more_list"> <object class="GtkListBox" id="more_list">
<property name="visible">False</property> <property name="visible">False</property>
<property name="selection_mode">none</property> <property name="selection_mode">none</property>
<child> <child>
<object class="HdyActionRow" id="provider_website_row"> <object class="HdyActionRow" id="provider_website_row">
@ -211,7 +209,7 @@
</child> </child>
</object> </object>
</child> </child>
</object> </template>
<object class="GtkEntryCompletion" id="provider_completion"> <object class="GtkEntryCompletion" id="provider_completion">
<property name="minimum_key_length">2</property> <property name="minimum_key_length">2</property>
<property name="text_column">1</property> <property name="text_column">1</property>

View file

@ -10,7 +10,7 @@
<attribute name="action">account.delete</attribute> <attribute name="action">account.delete</attribute>
</item> </item>
</menu> </menu>
<object class="GtkListBoxRow" id="account_row"> <template parent="GtkListBoxRow" class="AccountRow">
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="valign">center</property> <property name="valign">center</property>
<property name="selectable">False</property> <property name="selectable">False</property>
@ -118,5 +118,5 @@
<style> <style>
<class name="account-row"/> <class name="account-row"/>
</style> </style>
</object> </template>
</interface> </interface>

View file

@ -6,7 +6,7 @@
<property name="step_increment">1</property> <property name="step_increment">1</property>
<property name="page_increment">10</property> <property name="page_increment">10</property>
</object> </object>
<object class="HdyPreferencesWindow" id="preferences_window"> <template class="PreferencesWindow" parent="HdyPreferencesWindow">
<property name="default_width">360</property> <property name="default_width">360</property>
<property name="default_height">600</property> <property name="default_height">600</property>
<property name="search-enabled">False</property> <property name="search-enabled">False</property>
@ -126,6 +126,6 @@
</child> </child>
</object> </object>
</child> </child>
</object> </template>
</interface> </interface>

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<interface> <interface>
<object class="GtkBox" id="password_page"> <template parent="GtkBox" class="PasswordPage">
<property name="orientation">vertical</property> <property name="orientation">vertical</property>
<child> <child>
<object class="GtkHeaderBar"> <object class="GtkHeaderBar">
@ -123,7 +123,7 @@
</child> </child>
</object> </object>
</child> </child>
</object> </template>
<object class="GtkSizeGroup"> <object class="GtkSizeGroup">
<property name="mode">GTK_SIZE_GROUP_HORIZONTAL</property> <property name="mode">GTK_SIZE_GROUP_HORIZONTAL</property>
<widgets> <widgets>

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<interface> <interface>
<object class="GtkListBoxRow" id="provider_row"> <template parent="GtkListBoxRow" class="ProviderRow">
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="valign">center</property> <property name="valign">center</property>
<property name="selectable">False</property> <property name="selectable">False</property>
@ -9,7 +9,7 @@
<property name="orientation">vertical</property> <property name="orientation">vertical</property>
<property name="vexpand">True</property> <property name="vexpand">True</property>
<child> <child>
<object class="GtkLabel" id="name"> <object class="GtkLabel" id="name_label">
<property name="halign">start</property> <property name="halign">start</property>
<style> <style>
<class name="title-2" /> <class name="title-2" />
@ -29,5 +29,5 @@
<style> <style>
<class name="provider-row"/> <class name="provider-row"/>
</style> </style>
</object> </template>
</interface> </interface>

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<interface> <interface>
<object class="GtkBox" id="providers_box"> <template parent="GtkBox" class="ProvidersList">
<property name="hexpand">True</property> <property name="hexpand">True</property>
<property name="vexpand">True</property> <property name="vexpand">True</property>
<property name="orientation">vertical</property> <property name="orientation">vertical</property>
@ -123,5 +123,5 @@
</child> </child>
</object> </object>
</child> </child>
</object> </template>
</interface> </interface>

View file

@ -18,157 +18,161 @@
<attribute name="action">app.about</attribute> <attribute name="action">app.about</attribute>
</item> </item>
</menu> </menu>
<object class="HdyLeaflet" id="deck"> <template class="Window" parent="HdyApplicationWindow">
<property name="can-unfold">False</property> <property name="icon_name">@APP_ID@</property>
<child> <child>
<object class="HdyLeafletPage"> <object class="HdyLeaflet" id="deck">
<property name="name">accounts</property> <property name="can-unfold">False</property>
<property name="child"> <child>
<object class="GtkBox"> <object class="HdyLeafletPage">
<property name="orientation">vertical</property> <property name="name">accounts</property>
<child> <property name="child">
<object class="GtkHeaderBar">
<property name="show-title-buttons">True</property>
<child type="start">
<object class="GtkButton" id="add_btn">
<property name="receives_default">True</property>
<property name="action_name">win.add-account</property>
<property name="icon-name">list-add-symbolic</property>
</object>
</child>
<child type="end">
<object class="GtkMenuButton">
<property name="receives_default">True</property>
<property name="menu-model">menu</property>
<property name="icon-name">open-menu-symbolic</property>
</object>
</child>
<child type="end">
<object class="GtkToggleButton" id="search_btn">
<property name="receives_default">True</property>
<property name="icon-name">system-search-symbolic</property>
</object>
</child>
<style>
<class name="titlebar"/>
</style>
</object>
</child>
<child>
<object class="GtkBox" id="container">
<property name="vexpand">True</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkSearchBar" id="search_bar">
<child>
<object class="GtkSearchEntry" id="search_entry">
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</property>
</object>
</child>
<child>
<object class="HdyLeafletPage">
<property name="name">login</property>
<property name="child">
<object class="GtkWindowHandle">
<child>
<object class="GtkBox"> <object class="GtkBox">
<property name="orientation">vertical</property> <property name="orientation">vertical</property>
<child> <child>
<object class="GtkBox"> <object class="GtkHeaderBar">
<child> <property name="show-title-buttons">True</property>
<object class="GtkWindowControls"> <child type="start">
<property name="side">start</property> <object class="GtkButton" id="add_btn">
<property name="halign">start</property> <property name="receives_default">True</property>
<property name="hexpand">true</property> <property name="action_name">win.add-account</property>
<property name="margin-top">6</property> <property name="icon-name">list-add-symbolic</property>
<property name="margin-bottom">6</property>
<property name="margin-start">6</property>
</object> </object>
</child> </child>
<child type="end">
<object class="GtkMenuButton">
<property name="receives_default">True</property>
<property name="menu-model">menu</property>
<property name="icon-name">open-menu-symbolic</property>
</object>
</child>
<child type="end">
<object class="GtkToggleButton" id="search_btn">
<property name="receives_default">True</property>
<property name="icon-name">system-search-symbolic</property>
</object>
</child>
<style>
<class name="titlebar"/>
</style>
</object>
</child>
<child>
<object class="GtkBox" id="container">
<property name="vexpand">True</property>
<property name="orientation">vertical</property>
<child> <child>
<object class="GtkWindowControls"> <object class="GtkSearchBar" id="search_bar">
<property name="side">end</property> <child>
<property name="halign">end</property> <object class="GtkSearchEntry" id="search_entry">
<property name="hexpand">true</property> </object>
<property name="margin_top">6</property> </child>
<property name="margin_bottom">6</property>
<property name="margin_end">6</property>
</object> </object>
</child> </child>
</object> </object>
</child> </child>
</object>
</property>
</object>
</child>
<child>
<object class="HdyLeafletPage">
<property name="name">login</property>
<property name="child">
<object class="GtkWindowHandle">
<child> <child>
<object class="GtkBox"> <object class="GtkBox">
<property name="halign">center</property>
<property name="valign">center</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="spacing">24</property>
<property name="orientation">vertical</property> <property name="orientation">vertical</property>
<child> <child>
<object class="GtkImage"> <object class="GtkBox">
<property name="halign">center</property> <child>
<property name="hexpand">True</property> <object class="GtkWindowControls">
<property name="icon-name">@app-id@</property> <property name="side">start</property>
<property name="pixel-size">128</property> <property name="halign">start</property>
</object> <property name="hexpand">true</property>
</child> <property name="margin-top">6</property>
<child> <property name="margin-bottom">6</property>
<object class="GtkLabel"> <property name="margin-start">6</property>
<property name="halign">center</property> </object>
<property name="margin-top">12</property> </child>
<property name="label" translatable="yes">Authenticator is locked</property> <child>
<style> <object class="GtkWindowControls">
<class name="title-2"/> <property name="side">end</property>
</style> <property name="halign">end</property>
<property name="hexpand">true</property>
<property name="margin_top">6</property>
<property name="margin_bottom">6</property>
<property name="margin_end">6</property>
</object>
</child>
</object> </object>
</child> </child>
<child> <child>
<object class="GtkBox"> <object class="GtkBox">
<property name="halign">center</property> <property name="halign">center</property>
<property name="valign">start</property> <property name="valign">center</property>
<property name="margin-top">24</property>
<property name="hexpand">True</property> <property name="hexpand">True</property>
<property name="vexpand">True</property> <property name="vexpand">True</property>
<property name="spacing">24</property>
<property name="orientation">vertical</property> <property name="orientation">vertical</property>
<child> <child>
<object class="GtkPasswordEntry" id="password_entry"> <object class="GtkImage">
<property name="halign">center</property> <property name="halign">center</property>
<property name="activates-default">True</property> <property name="hexpand">True</property>
<property name="show-peek-icon">True</property> <property name="icon-name">@app-id@</property>
<property name="pixel-size">128</property>
</object> </object>
</child> </child>
<child> <child>
<object class="GtkButton"> <object class="GtkLabel">
<property name="label" translatable="yes">Unlock</property>
<property name="action-name">win.unlock</property>
<property name="receives-default">True</property>
<property name="halign">center</property> <property name="halign">center</property>
<property name="margin-top">16</property> <property name="margin-top">12</property>
<property name="label" translatable="yes">Authenticator is locked</property>
<style> <style>
<class name="suggested-action"/> <class name="title-2"/>
<class name="pill-button" />
<class name="large-button" />
</style> </style>
</object> </object>
</child> </child>
<child>
<object class="GtkBox">
<property name="halign">center</property>
<property name="valign">start</property>
<property name="margin-top">24</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkPasswordEntry" id="password_entry">
<property name="halign">center</property>
<property name="activates-default">True</property>
<property name="show-peek-icon">True</property>
</object>
</child>
<child>
<object class="GtkButton">
<property name="label" translatable="yes">Unlock</property>
<property name="action-name">win.unlock</property>
<property name="receives-default">True</property>
<property name="halign">center</property>
<property name="margin-top">16</property>
<style>
<class name="suggested-action"/>
<class name="pill-button" />
<class name="large-button" />
</style>
</object>
</child>
</object>
</child>
</object> </object>
</child> </child>
</object> </object>
</child> </child>
</object> </object>
</property>
</child>
</object> </object>
</property> </child>
</object> </object>
</child> </child>
</object> </template>
</interface> </interface>

View file

@ -1,7 +1,7 @@
use crate::config; use crate::config;
use crate::helpers::Keyring; use crate::helpers::Keyring;
use crate::models::{Account, Provider, ProvidersModel}; use crate::models::{Account, Provider, ProvidersModel};
use crate::widgets::{AddAccountDialog, PreferencesWindow, View, Window, WindowPrivate}; use crate::widgets::{AccountAddDialog, PreferencesWindow, View, Window};
use gio::prelude::*; use gio::prelude::*;
use glib::subclass::prelude::*; use glib::subclass::prelude::*;
use glib::{subclass, WeakRef}; use glib::{subclass, WeakRef};
@ -95,12 +95,12 @@ impl ObjectImpl for ApplicationPrivate {
} }
} }
fn get_property(&self, _obj: &Self::Type, id: usize) -> Result<glib::Value, ()> { fn get_property(&self, _obj: &Self::Type, id: usize) -> glib::Value {
let prop = &PROPERTIES[id]; let prop = &PROPERTIES[id];
match *prop { match *prop {
subclass::Property("locked", ..) => Ok(self.locked.get().to_value()), subclass::Property("locked", ..) => self.locked.get().to_value(),
subclass::Property("can-be-locked", ..) => Ok(self.can_be_locked.get().to_value()), subclass::Property("can-be-locked", ..) => self.can_be_locked.get().to_value(),
_ => unimplemented!(), _ => unimplemented!(),
} }
} }
@ -127,11 +127,11 @@ impl ApplicationImpl for ApplicationPrivate {
action!( action!(
app, app,
"preferences", "preferences",
clone!(@strong app, @weak self.model as model => move |_,_| { clone!(@strong app => move |_,_| {
let window = app.get_active_window().unwrap(); let window = app.get_active_window().unwrap();
let preferences = PreferencesWindow::new(model); let preferences = PreferencesWindow::new();
preferences.widget.set_transient_for(Some(&window)); preferences.set_transient_for(Some(&window));
preferences.widget.show(); preferences.show();
}) })
); );
@ -249,23 +249,21 @@ impl Application {
match action { match action {
Action::OpenAddAccountDialog => { Action::OpenAddAccountDialog => {
let dialog = AddAccountDialog::new(self_.model.clone(), self_.sender.clone()); let dialog = AccountAddDialog::new(self_.model.clone(), self_.sender.clone());
dialog.widget.set_transient_for(Some(&active_window)); dialog.set_transient_for(Some(&active_window));
dialog.widget.show(); dialog.show();
} }
Action::AccountCreated(account, provider) => { Action::AccountCreated(account, provider) => {
let win_ = active_window.downcast_ref::<Window>().unwrap(); let win = active_window.downcast_ref::<Window>().unwrap();
let priv_ = WindowPrivate::from_instance(win_);
self_.model.add_account(&account, &provider); self_.model.add_account(&account, &provider);
priv_.providers.refilter(); win.providers().refilter();
} }
Action::AccountRemoved(account) => { Action::AccountRemoved(account) => {
let win_ = active_window.downcast_ref::<Window>().unwrap(); let win = active_window.downcast_ref::<Window>().unwrap();
let priv_ = WindowPrivate::from_instance(win_);
self_.model.remove_account(&account); self_.model.remove_account(&account);
priv_.providers.refilter(); win.providers().refilter();
} }
Action::SetView(view) => { Action::SetView(view) => {
let win_ = active_window.downcast_ref::<Window>().unwrap(); let win_ = active_window.downcast_ref::<Window>().unwrap();

View file

@ -46,7 +46,7 @@ pub(crate) fn scan(screenshot: &gio::File) -> Result<OtpAuth> {
let img = image::load_from_memory(&data)?; let img = image::load_from_memory(&data)?;
let (width, height) = img.dimensions(); let (width, height) = img.dimensions();
let img_data: Vec<u8> = img.to_luma().to_vec(); let img_data: Vec<u8> = img.to_luma8().to_vec();
let mut scanner = ZBarImageScanner::new(); let mut scanner = ZBarImageScanner::new();

View file

@ -122,14 +122,14 @@ impl ObjectImpl for AccountPriv {
} }
} }
fn get_property(&self, _obj: &Self::Type, id: usize) -> Result<glib::Value, ()> { fn get_property(&self, _obj: &Self::Type, id: usize) -> glib::Value {
let prop = &PROPERTIES[id]; let prop = &PROPERTIES[id];
match *prop { match *prop {
subclass::Property("id", ..) => Ok(self.id.get().to_value()), subclass::Property("id", ..) => self.id.get().to_value(),
subclass::Property("name", ..) => Ok(self.name.borrow().to_value()), subclass::Property("name", ..) => self.name.borrow().to_value(),
subclass::Property("token-id", ..) => Ok(self.token_id.borrow().to_value()), subclass::Property("token-id", ..) => self.token_id.borrow().to_value(),
subclass::Property("provider-id", ..) => Ok(self.provider_id.get().to_value()), subclass::Property("provider-id", ..) => self.provider_id.get().to_value(),
_ => unimplemented!(), _ => unimplemented!(),
} }
} }

View file

@ -197,17 +197,17 @@ impl ObjectImpl for ProviderPriv {
} }
} }
fn get_property(&self, _obj: &Self::Type, id: usize) -> Result<glib::Value, ()> { fn get_property(&self, _obj: &Self::Type, id: usize) -> glib::Value {
let prop = &PROPERTIES[id]; let prop = &PROPERTIES[id];
match *prop { match *prop {
subclass::Property("id", ..) => Ok(self.id.get().to_value()), subclass::Property("id", ..) => self.id.get().to_value(),
subclass::Property("name", ..) => Ok(self.name.borrow().to_value()), subclass::Property("name", ..) => self.name.borrow().to_value(),
subclass::Property("period", ..) => Ok(self.period.get().to_value()), subclass::Property("period", ..) => self.period.get().to_value(),
subclass::Property("algorithm", ..) => Ok(self.algorithm.borrow().to_value()), subclass::Property("algorithm", ..) => self.algorithm.borrow().to_value(),
subclass::Property("website", ..) => Ok(self.website.borrow().to_value()), subclass::Property("website", ..) => self.website.borrow().to_value(),
subclass::Property("help-url", ..) => Ok(self.help_url.borrow().to_value()), subclass::Property("help-url", ..) => self.help_url.borrow().to_value(),
subclass::Property("image-uri", ..) => Ok(self.image_uri.borrow().to_value()), subclass::Property("image-uri", ..) => self.image_uri.borrow().to_value(),
_ => unimplemented!(), _ => unimplemented!(),
} }
} }

View file

@ -3,61 +3,155 @@ use crate::helpers::qrcode;
use crate::models::{Account, Provider, ProvidersModel}; use crate::models::{Account, Provider, ProvidersModel};
use anyhow::Result; use anyhow::Result;
use gio::prelude::*; use gio::prelude::*;
use gio::{subclass::ObjectSubclass, ActionMapExt};
use glib::subclass::prelude::*;
use glib::{glib_object_subclass, glib_wrapper};
use glib::{signal::Inhibit, Receiver, Sender}; use glib::{signal::Inhibit, Receiver, Sender};
use gtk::prelude::*; use gtk::{prelude::*, CompositeTemplate};
use libhandy::ActionRowExt; use libhandy::ActionRowExt;
use once_cell::sync::OnceCell;
use std::cell::RefCell; use std::cell::RefCell;
use std::rc::Rc; use std::rc::Rc;
pub enum AddAccountAction { pub enum AccountAddAction {
SetIcon(gio::File), SetIcon(gio::File),
SetProvider(Provider), SetProvider(Provider),
Save, Save,
ScanQR, ScanQR,
} }
pub struct AddAccountDialog { mod imp {
pub widget: libhandy::Window, use super::*;
builder: gtk::Builder, use glib::subclass;
global_sender: Sender<Action>, use gtk::subclass::prelude::*;
sender: Sender<AddAccountAction>,
receiver: RefCell<Option<Receiver<AddAccountAction>>>, #[derive(CompositeTemplate)]
model: Rc<ProvidersModel>, pub struct AccountAddDialog {
selected_provider: Rc<RefCell<Option<Provider>>>, pub global_sender: OnceCell<Sender<Action>>,
actions: gio::SimpleActionGroup, pub sender: Sender<AccountAddAction>,
pub receiver: RefCell<Option<Receiver<AccountAddAction>>>,
pub model: OnceCell<Rc<ProvidersModel>>,
pub selected_provider: OnceCell<Provider>,
pub actions: gio::SimpleActionGroup,
#[template_child(id = "username_entry")]
pub username_entry: TemplateChild<gtk::Entry>,
#[template_child(id = "token_entry")]
pub token_entry: TemplateChild<gtk::Entry>,
#[template_child(id = "more_list")]
pub more_list: TemplateChild<gtk::ListBox>,
#[template_child(id = "period_label")]
pub period_label: TemplateChild<gtk::Label>,
#[template_child(id = "provider_entry")]
pub provider_entry: TemplateChild<gtk::Entry>,
#[template_child(id = "algorithm_label")]
pub algorithm_label: TemplateChild<gtk::Label>,
#[template_child(id = "provider_website_row")]
pub provider_website_row: TemplateChild<libhandy::ActionRow>,
#[template_child(id = "provider_help_row")]
pub provider_help_row: TemplateChild<libhandy::ActionRow>,
#[template_child(id = "provider_completion")]
pub provider_completion: TemplateChild<gtk::EntryCompletion>,
#[template_child(id = "image")]
pub image: TemplateChild<gtk::Image>,
#[template_child(id = "spinner")]
pub spinner: TemplateChild<gtk::Spinner>,
#[template_child(id = "image_stack")]
pub image_stack: TemplateChild<gtk::Stack>,
}
impl ObjectSubclass for AccountAddDialog {
const NAME: &'static str = "AccountAddDialog";
type Type = super::AccountAddDialog;
type ParentType = libhandy::Window;
type Instance = subclass::simple::InstanceStruct<Self>;
type Class = subclass::simple::ClassStruct<Self>;
glib_object_subclass!();
fn new() -> Self {
let (sender, r) = glib::MainContext::channel(glib::PRIORITY_DEFAULT);
let receiver = RefCell::new(Some(r));
let actions = gio::SimpleActionGroup::new();
Self {
global_sender: OnceCell::new(),
sender,
receiver,
actions,
model: OnceCell::new(),
selected_provider: OnceCell::new(),
token_entry: TemplateChild::default(),
username_entry: TemplateChild::default(),
more_list: TemplateChild::default(),
period_label: TemplateChild::default(),
provider_entry: TemplateChild::default(),
algorithm_label: TemplateChild::default(),
provider_website_row: TemplateChild::default(),
provider_help_row: TemplateChild::default(),
provider_completion: TemplateChild::default(),
image: TemplateChild::default(),
spinner: TemplateChild::default(),
image_stack: TemplateChild::default(),
}
}
fn class_init(klass: &mut Self::Class) {
klass.set_template_from_resource("/com/belmoussaoui/Authenticator/account_add.ui");
Self::bind_template_children(klass);
}
}
impl ObjectImpl for AccountAddDialog {
fn constructed(&self, obj: &Self::Type) {
obj.init_template();
self.parent_constructed(obj);
}
}
impl WidgetImpl for AccountAddDialog {}
impl WindowImpl for AccountAddDialog {}
impl libhandy::subclass::window::WindowImpl for AccountAddDialog {}
}
glib_wrapper! {
pub struct AccountAddDialog(ObjectSubclass<imp::AccountAddDialog>) @extends gtk::Widget, gtk::Window, libhandy::Window;
} }
impl AddAccountDialog { impl AccountAddDialog {
pub fn new(model: Rc<ProvidersModel>, global_sender: Sender<Action>) -> Rc<Self> { pub fn new(model: Rc<ProvidersModel>, global_sender: Sender<Action>) -> Self {
let builder = gtk::Builder::from_resource("/com/belmoussaoui/Authenticator/account_add.ui"); let dialog = glib::Object::new(Self::static_type(), &[])
get_widget!(builder, libhandy::Window, add_dialog); .expect("Failed to create AccountAddDialog")
.downcast::<AccountAddDialog>()
.expect("Created object is of wrong type");
let (sender, r) = glib::MainContext::channel(glib::PRIORITY_DEFAULT); let self_ = imp::AccountAddDialog::from_instance(&dialog);
let receiver = RefCell::new(Some(r)); self_.model.set(model);
let actions = gio::SimpleActionGroup::new(); self_.global_sender.set(global_sender);
let add_account_dialog = Rc::new(Self { dialog.setup_actions();
widget: add_dialog, dialog.setup_signals();
builder, dialog.setup_widgets();
global_sender, dialog
sender,
receiver,
actions,
model,
selected_provider: Rc::new(RefCell::new(None)),
});
add_account_dialog.setup_actions();
add_account_dialog.setup_signals();
add_account_dialog.setup_widgets(add_account_dialog.clone());
add_account_dialog
} }
fn setup_signals(&self) { fn setup_signals(&self) {
get_widget!(self.builder, gtk::Entry, username_entry); let self_ = imp::AccountAddDialog::from_instance(self);
get_widget!(self.builder, gtk::Entry, token_entry);
let validate_entries = clone!(@weak username_entry, @weak token_entry, @strong self.actions as actions => move |_: &gtk::Entry| { let username_entry = self_.username_entry.get();
let token_entry = self_.token_entry.get();
let validate_entries = clone!(@weak username_entry, @weak token_entry, @strong self_.actions as actions => move |_: &gtk::Entry| {
let username = username_entry.get_text().unwrap(); let username = username_entry.get_text().unwrap();
let token = token_entry.get_text().unwrap(); let token = token_entry.get_text().unwrap();
@ -70,27 +164,33 @@ impl AddAccountDialog {
token_entry.connect_changed(validate_entries); token_entry.connect_changed(validate_entries);
let event_controller = gtk::EventControllerKey::new(); let event_controller = gtk::EventControllerKey::new();
event_controller.connect_key_pressed(clone!(@weak self.widget as widget => @default-return Inhibit(false), move |_, k, _, _| { event_controller.connect_key_pressed(
if k == 65307 { clone!(@weak self as widget => @default-return Inhibit(false), move |_, k, _, _| {
widget.close(); if k == 65307 {
} widget.close();
Inhibit(false) }
})); Inhibit(false)
self.widget.add_controller(&event_controller); }),
);
self.add_controller(&event_controller);
} }
fn scan_qr(&self) -> Result<()> { fn scan_qr(&self) -> Result<()> {
let self_ = imp::AccountAddDialog::from_instance(self);
let token_entry = self_.token_entry.get();
let username_entry = self_.username_entry.get();
qrcode::screenshot_area( qrcode::screenshot_area(
clone!(@strong self.builder as builder, @strong self.model as model, clone!(@weak token_entry, @weak username_entry, @strong self_.model as model,
@strong self.sender as sender => move |screenshot| { @strong self_.sender as sender => move |screenshot| {
if let Ok(otpauth) = qrcode::scan(&gio::File::new_for_uri(&screenshot)) { if let Ok(otpauth) = qrcode::scan(&gio::File::new_for_uri(&screenshot)) {
get_widget!(builder, gtk::Entry, @token_entry).set_text(&otpauth.token); token_entry.set_text(&otpauth.token);
if let Some(ref username) = otpauth.account { if let Some(ref username) = otpauth.account {
get_widget!(builder, gtk::Entry, @username_entry).set_text(&username); username_entry.set_text(&username);
} }
if let Some(ref provider) = otpauth.issuer { if let Some(ref provider) = otpauth.issuer {
let provider = model.find_by_name(provider).unwrap(); let provider = model.get().unwrap().find_by_name(provider).unwrap();
send!(sender, AddAccountAction::SetProvider(provider)); send!(sender, AccountAddAction::SetProvider(provider));
} }
} }
}), }),
@ -99,98 +199,100 @@ impl AddAccountDialog {
} }
fn save(&self) -> Result<()> { fn save(&self) -> Result<()> {
if let Some(provider) = self.selected_provider.borrow().clone() { let self_ = imp::AccountAddDialog::from_instance(self);
let username = get_widget!(self.builder, gtk::Entry, @username_entry)
.get_text() if let Some(provider) = self_.selected_provider.get().clone() {
.unwrap(); let username = self_.username_entry.get().get_text().unwrap();
let token = get_widget!(self.builder, gtk::Entry, @token_entry) let token = self_.token_entry.get().get_text().unwrap();
.get_text()
.unwrap();
let account = Account::create(&username, &token, provider.id())?; let account = Account::create(&username, &token, provider.id())?;
send!( send!(
self.global_sender, self_.global_sender.get().unwrap(),
Action::AccountCreated(account, provider) Action::AccountCreated(account, provider.clone())
); );
} }
Ok(()) Ok(())
} }
fn set_provider(&self, provider: Provider) { fn set_provider(&self, provider: Provider) {
get_widget!(self.builder, gtk::ListBox, @more_list).show(); let self_ = imp::AccountAddDialog::from_instance(self);
get_widget!(self.builder, gtk::Entry, @provider_entry).set_text(&provider.name());
get_widget!(self.builder, gtk::Label, @period_label) self_.more_list.get().show();
.set_text(&format!("{} seconds", provider.period()));
get_widget!(self.builder, gtk::Label, @algorithm_label) self_.provider_entry.get().set_text(&provider.name());
self_
.period_label
.get()
.set_text(&provider.period().to_string());
self_
.algorithm_label
.get()
.set_text(&provider.algorithm().to_locale_string()); .set_text(&provider.algorithm().to_locale_string());
if let Some(ref website) = provider.website() { if let Some(ref website) = provider.website() {
get_widget!(self.builder, libhandy::ActionRow, provider_website_row); self_.provider_website_row.get().set_subtitle(Some(website));
provider_website_row.set_subtitle(Some(website));
} }
if let Some(ref help_url) = provider.help_url() { if let Some(ref help_url) = provider.help_url() {
get_widget!(self.builder, libhandy::ActionRow, provider_help_row); self_.provider_help_row.get().set_subtitle(Some(help_url));
provider_help_row.set_subtitle(Some(help_url));
} }
get_widget!(self.builder, gtk::Stack, @image_stack).set_visible_child_name("loading"); self_.image_stack.get().set_visible_child_name("loading");
get_widget!(self.builder, gtk::Spinner, @spinner).start(); self_.spinner.get().start();
let p = provider.clone(); let p = provider.clone();
let sender = self.sender.clone(); let sender = self_.sender.clone();
spawn!(async move { spawn!(async move {
if let Ok(file) = p.favicon().await { if let Ok(file) = p.favicon().await {
send!(sender, AddAccountAction::SetIcon(file)); send!(sender, AccountAddAction::SetIcon(file));
} }
}); });
self.selected_provider.replace(Some(provider)); self_.selected_provider.set(provider);
} }
fn setup_actions(&self) { fn setup_actions(&self) {
let self_ = imp::AccountAddDialog::from_instance(self);
action!( action!(
self.actions, self_.actions,
"back", "back",
clone!(@weak self.widget as dialog => move |_, _| { clone!(@weak self as dialog => move |_, _| {
dialog.destroy(); dialog.destroy();
}) })
); );
action!( action!(
self.actions, self_.actions,
"save", "save",
clone!(@strong self.sender as sender => move |_, _| { clone!(@strong self_.sender as sender => move |_, _| {
send!(sender, AddAccountAction::Save); send!(sender, AccountAddAction::Save);
}) })
); );
action!( action!(
self.actions, self_.actions,
"scan-qr", "scan-qr",
clone!(@strong self.sender as sender => move |_, _| { clone!(@strong self_.sender as sender => move |_, _| {
send!(sender, AddAccountAction::ScanQR); send!(sender, AccountAddAction::ScanQR);
}) })
); );
self.widget.insert_action_group("add", Some(&self.actions)); self.insert_action_group("add", Some(&self_.actions));
get_action!(self.actions, @save).set_enabled(false); get_action!(self_.actions, @save).set_enabled(false);
} }
fn setup_widgets(&self, dialog: Rc<Self>) { fn setup_widgets(&self) {
let receiver = self.receiver.borrow_mut().take().unwrap(); let self_ = imp::AccountAddDialog::from_instance(self);
let receiver = self_.receiver.borrow_mut().take().unwrap();
receiver.attach( receiver.attach(
None, None,
clone!(@strong dialog => move |action| dialog.do_action(action)), clone!(@weak self as dialog => move |action| dialog.do_action(action)),
); );
self_
get_widget!(self.builder, gtk::EntryCompletion, provider_completion); .provider_completion
provider_completion.set_model(Some(&self.model.completion_model())); .get()
.set_model(Some(&self_.model.get().unwrap().completion_model()));
get_widget!(self.builder, gtk::Entry, @token_entry); self_.provider_completion.get().connect_match_selected(
clone!(@strong self as dialog, @strong self_.model as model => move |_, store, iter| {
provider_completion.connect_match_selected(
clone!(@strong dialog, @strong self.model as model => move |_, store, iter| {
let provider_id = store.get_value(iter, 0). get_some::<i32>().unwrap(); let provider_id = store.get_value(iter, 0). get_some::<i32>().unwrap();
let provider = model.find_by_id(provider_id).unwrap(); let provider = model.get().unwrap().find_by_id(provider_id).unwrap();
dialog.set_provider(provider); dialog.set_provider(provider);
Inhibit(false) Inhibit(false)
@ -198,21 +300,21 @@ impl AddAccountDialog {
); );
} }
fn do_action(&self, action: AddAccountAction) -> glib::Continue { fn do_action(&self, action: AccountAddAction) -> glib::Continue {
match action { match action {
AddAccountAction::SetIcon(file) => { AccountAddAction::SetIcon(file) => {
get_widget!(self.builder, gtk::Image, @image) let self_ = imp::AccountAddDialog::from_instance(self);
.set_from_file(file.get_path().unwrap()); self_.image.get().set_from_file(file.get_path().unwrap());
get_widget!(self.builder, gtk::Spinner, @spinner).stop(); self_.spinner.get().stop();
get_widget!(self.builder, gtk::Stack, @image_stack).set_visible_child_name("image"); self_.image_stack.get().set_visible_child_name("image");
} }
AddAccountAction::SetProvider(p) => self.set_provider(p), AccountAddAction::SetProvider(p) => self.set_provider(p),
AddAccountAction::Save => { AccountAddAction::Save => {
if self.save().is_ok() { if self.save().is_ok() {
self.widget.close(); self.close();
} }
} }
AddAccountAction::ScanQR => { AccountAddAction::ScanQR => {
self.scan_qr(); self.scan_qr();
} }
}; };

View file

@ -1,5 +1,5 @@
mod add; mod add;
mod row; mod row;
pub use self::add::AddAccountDialog; pub use self::add::AccountAddDialog;
pub use self::row::AccountRow; pub use self::row::AccountRow;

View file

@ -1,84 +1,161 @@
use crate::application::Action;
use crate::models::Account; use crate::models::Account;
use gio::ActionMapExt; use gio::prelude::*;
use glib::Sender; use gio::{subclass::ObjectSubclass, ActionMapExt};
use gtk::prelude::*; use glib::subclass::prelude::*;
use glib::{glib_object_subclass, glib_wrapper};
use gtk::{prelude::*, CompositeTemplate};
use std::cell::RefCell;
mod imp {
use super::*;
use glib::subclass;
use gtk::subclass::prelude::*;
pub struct AccountRow<'a> { static PROPERTIES: [subclass::Property; 1] = [subclass::Property("account", |name| {
pub widget: gtk::ListBoxRow, glib::ParamSpec::object(
builder: gtk::Builder, name,
sender: Sender<Action>, "Account",
account: &'a Account, "The account",
actions: gio::SimpleActionGroup, Account::static_type(),
} glib::ParamFlags::READWRITE | glib::ParamFlags::CONSTRUCT_ONLY,
)
})];
impl<'a> AccountRow<'a> { #[derive(CompositeTemplate)]
pub fn new(account: &'a Account, sender: Sender<Action>) -> Self { pub struct AccountRow {
let builder = gtk::Builder::from_resource("/com/belmoussaoui/Authenticator/account_row.ui"); pub account: RefCell<Option<Account>>,
get_widget!(builder, gtk::ListBoxRow, account_row); pub actions: gio::SimpleActionGroup,
let actions = gio::SimpleActionGroup::new(); #[template_child(id = "name_label")]
let row = Self { pub name_label: TemplateChild<gtk::Label>,
widget: account_row, #[template_child(id = "name_entry")]
builder, pub name_entry: TemplateChild<gtk::Entry>,
sender, #[template_child(id = "edit_stack")]
account, pub edit_stack: TemplateChild<gtk::Stack>,
actions,
};
row.init();
row
} }
fn init(&self) { impl ObjectSubclass for AccountRow {
self.widget const NAME: &'static str = "AccountRow";
.insert_action_group("account", Some(&self.actions)); type Type = super::AccountRow;
type ParentType = gtk::ListBoxRow;
type Instance = subclass::simple::InstanceStruct<Self>;
type Class = subclass::simple::ClassStruct<Self>;
get_widget!(self.builder, gtk::Label, name_label); glib_object_subclass!();
get_widget!(self.builder, gtk::Entry, name_entry);
self.account fn new() -> Self {
.bind_property("name", &name_label, "label") let actions = gio::SimpleActionGroup::new();
Self {
actions,
name_label: TemplateChild::default(),
name_entry: TemplateChild::default(),
edit_stack: TemplateChild::default(),
account: RefCell::new(None),
}
}
fn class_init(klass: &mut Self::Class) {
klass.set_template_from_resource("/com/belmoussaoui/Authenticator/account_row.ui");
Self::bind_template_children(klass);
klass.install_properties(&PROPERTIES);
}
}
impl ObjectImpl for AccountRow {
fn set_property(&self, _obj: &Self::Type, id: usize, value: &glib::Value) {
let prop = &PROPERTIES[id];
match *prop {
subclass::Property("account", ..) => {
let account = value
.get()
.expect("type conformity checked by `Object::set_property`");
self.account.replace(account);
}
_ => unimplemented!(),
}
}
fn get_property(&self, _obj: &Self::Type, id: usize) -> glib::Value {
let prop = &PROPERTIES[id];
match *prop {
subclass::Property("account", ..) => self.account.borrow().to_value(),
_ => unimplemented!(),
}
}
fn constructed(&self, obj: &Self::Type) {
obj.init_template();
obj.setup_actions();
obj.setup_widgets();
self.parent_constructed(obj);
}
}
impl WidgetImpl for AccountRow {}
impl ListBoxRowImpl for AccountRow {}
}
glib_wrapper! {
pub struct AccountRow(ObjectSubclass<imp::AccountRow>) @extends gtk::Widget, gtk::ListBoxRow;
}
impl AccountRow {
pub fn new(account: Account) -> Self {
glib::Object::new(Self::static_type(), &[("account", &account)])
.expect("Failed to create AccountRow")
.downcast::<AccountRow>()
.expect("Created object is of wrong type")
}
fn account(&self) -> Account {
let account = self.get_property("account").unwrap();
account.get::<Account>().unwrap().unwrap()
}
fn setup_widgets(&self) {
let self_ = imp::AccountRow::from_instance(self);
self.account()
.bind_property("name", &self_.name_label.get(), "label")
.flags(glib::BindingFlags::DEFAULT | glib::BindingFlags::SYNC_CREATE) .flags(glib::BindingFlags::DEFAULT | glib::BindingFlags::SYNC_CREATE)
.build(); .build();
self.account self.account()
.bind_property("name", &name_entry, "text") .bind_property("name", &self_.name_entry.get(), "text")
.flags(glib::BindingFlags::DEFAULT | glib::BindingFlags::SYNC_CREATE) .flags(glib::BindingFlags::DEFAULT | glib::BindingFlags::SYNC_CREATE)
.build(); .build();
action!( self_.name_entry.get().connect_changed(
self.actions, clone!(@strong self_.actions as actions => move |entry| {
"delete", let name = entry.get_text().unwrap();
clone!(@strong self.sender as sender, @strong self.account as account => move |_, _| { get_action!(actions, @save).set_enabled(!name.is_empty());
send!(sender, Action::AccountRemoved(account.clone())); }),
})
); );
}
fn setup_actions(&self) {
let self_ = imp::AccountRow::from_instance(self);
self.insert_action_group("account", Some(&self_.actions));
action!(self_.actions, "delete", move |_, _| {
//send!(sender, Action::AccountRemoved(account.clone()));
});
let edit_stack = self_.edit_stack.get();
action!( action!(
self.actions, self_.actions,
"edit", "edit",
clone!(@strong self.builder as builder => move |_, _| { clone!(@weak edit_stack => move |_, _| {
get_widget!(builder, gtk::Stack, edit_stack);
edit_stack.set_visible_child_name("edit"); edit_stack.set_visible_child_name("edit");
}) })
); );
let name_entry = self_.name_entry.get();
action!( action!(
self.actions, self_.actions,
"save", "save",
clone!(@weak name_entry, clone!(@weak edit_stack, @weak name_entry => move |_, _| {
@strong self.account as account,
@strong self.builder as builder => move |_, _| {
let new_name = name_entry.get_text().unwrap(); let new_name = name_entry.get_text().unwrap();
account.set_name(&new_name);
get_widget!(builder, gtk::Stack, edit_stack);
edit_stack.set_visible_child_name("display"); edit_stack.set_visible_child_name("display");
}) })
); );
name_entry.connect_changed(clone!(@strong self.actions as actions => move |entry| {
let name = entry.get_text().unwrap();
get_action!(actions, @save).set_enabled(!name.is_empty());
}));
} }
} }

View file

@ -3,7 +3,7 @@ mod preferences;
mod providers; mod providers;
mod window; mod window;
pub use self::accounts::AddAccountDialog; pub use self::accounts::AccountAddDialog;
pub use self::preferences::PreferencesWindow; pub use self::preferences::PreferencesWindow;
pub use self::providers::ProvidersList; pub use self::providers::ProvidersList;
pub use self::window::{View, Window, WindowPrivate}; pub use self::window::{View, Window};

View file

@ -1,70 +1,131 @@
use crate::helpers::Keyring; use crate::helpers::Keyring;
use gio::{ActionExt, ActionMapExt};
use gtk::prelude::*;
use std::cell::Cell; use std::cell::Cell;
use std::rc::Rc;
pub struct PasswordPage { use gio::prelude::*;
pub widget: gtk::Box, use gio::subclass::ObjectSubclass;
builder: gtk::Builder, use glib::subclass::prelude::*;
actions: gio::SimpleActionGroup, use glib::{glib_object_subclass, glib_wrapper};
has_set_password: Cell<bool>, use gtk::{prelude::*, CompositeTemplate};
mod imp {
use super::*;
use glib::subclass;
use gtk::subclass::prelude::*;
#[derive(CompositeTemplate)]
pub struct PasswordPage {
// actions: gio::SimpleActionGroup,
pub has_set_password: Cell<bool>,
#[template_child(id = "current_password_entry")]
pub current_password_entry: TemplateChild<gtk::PasswordEntry>,
#[template_child(id = "password_entry")]
pub password_entry: TemplateChild<gtk::PasswordEntry>,
#[template_child(id = "confirm_password_entry")]
pub confirm_password_entry: TemplateChild<gtk::PasswordEntry>,
#[template_child(id = "current_password_row")]
pub current_password_row: TemplateChild<libhandy::ActionRow>,
}
impl ObjectSubclass for PasswordPage {
const NAME: &'static str = "PasswordPage";
type Type = super::PasswordPage;
type ParentType = gtk::Box;
type Instance = subclass::simple::InstanceStruct<Self>;
type Class = subclass::simple::ClassStruct<Self>;
glib_object_subclass!();
fn new() -> Self {
let has_set_password = Keyring::has_set_password().unwrap_or_else(|_| false);
Self {
has_set_password: Cell::new(has_set_password),
current_password_entry: TemplateChild::default(),
password_entry: TemplateChild::default(),
confirm_password_entry: TemplateChild::default(),
current_password_row: TemplateChild::default(),
}
}
fn class_init(klass: &mut Self::Class) {
klass.set_template_from_resource(
"/com/belmoussaoui/Authenticator/preferences_password_page.ui",
);
Self::bind_template_children(klass);
}
}
impl ObjectImpl for PasswordPage {
fn constructed(&self, obj: &Self::Type) {
obj.init_template();
obj.setup_widgets();
obj.setup_actions();
self.parent_constructed(obj);
}
}
impl WidgetImpl for PasswordPage {}
impl BoxImpl for PasswordPage {}
}
glib_wrapper! {
pub struct PasswordPage(ObjectSubclass<imp::PasswordPage>) @extends gtk::Widget, gtk::Box;
} }
impl PasswordPage { impl PasswordPage {
pub fn new(actions: gio::SimpleActionGroup) -> Rc<Self> { pub fn new() -> Self {
let builder = gtk::Builder::from_resource( glib::Object::new(Self::static_type(), &[])
"/com/belmoussaoui/Authenticator/preferences_password_page.ui", .expect("Failed to create PasswordPage")
); .downcast::<PasswordPage>()
get_widget!(builder, gtk::Box, password_page); .expect("Created object is of wrong type")
let has_set_password = Keyring::has_set_password().unwrap_or_else(|_| false);
let page = Rc::new(Self {
widget: password_page,
builder,
actions,
has_set_password: Cell::new(has_set_password),
});
page.init(page.clone());
page
} }
fn validate(&self) { fn validate(&self) {
get_widget!(self.builder, gtk::PasswordEntry, current_password_entry); let self_ = imp::PasswordPage::from_instance(self);
get_widget!(self.builder, gtk::PasswordEntry, password_entry);
get_widget!(self.builder, gtk::PasswordEntry, confirm_password_entry);
let current_password = current_password_entry.get_text().unwrap(); let current_password = self_.current_password_entry.get().get_text().unwrap();
let password = password_entry.get_text().unwrap(); let password = self_.password_entry.get().get_text().unwrap();
let password_repeat = confirm_password_entry.get_text().unwrap(); let password_repeat = self_.confirm_password_entry.get().get_text().unwrap();
let is_valid = if self.has_set_password.get() { let is_valid = if self_.has_set_password.get() {
password_repeat == password && current_password != password && password != "" password_repeat == password && current_password != password && password != ""
} else { } else {
password_repeat == password && password != "" password_repeat == password && password != ""
}; };
get_action!(self.actions, @save_password).set_enabled(is_valid); // get_action!(self.actions, @save_password).set_enabled(is_valid);
} }
fn init(&self, page: Rc<Self>) { fn setup_widgets(&self) {
get_widget!(self.builder, gtk::PasswordEntry, current_password_entry); let self_ = imp::PasswordPage::from_instance(self);
get_widget!(self.builder, gtk::PasswordEntry, password_entry);
get_widget!(self.builder, gtk::PasswordEntry, confirm_password_entry);
get_widget!(self.builder, libhandy::ActionRow, current_password_row);
password_entry.connect_changed(clone!(@strong page => move |_| page.validate())); self_
confirm_password_entry.connect_changed(clone!(@strong page => move |_| page.validate())); .password_entry
.get()
.connect_changed(clone!(@weak self as page=> move |_| page.validate()));
self_
.confirm_password_entry
.get()
.connect_changed(clone!(@weak self as page => move |_| page.validate()));
if !self.has_set_password.get() { if !self_.has_set_password.get() {
current_password_row.hide(); self_.current_password_row.get().hide();
} else { } else {
current_password_entry self_
.connect_changed(clone!(@strong page => move |_| page.validate())); .current_password_entry
.get()
.connect_changed(clone!(@weak self as page => move |_| page.validate()));
} }
}
action!( fn setup_actions(&self) {
let self_ = imp::PasswordPage::from_instance(self);
/*action!(
self.actions, self.actions,
"save_password", "save_password",
clone!(@strong page => move |_, _| { clone!(@strong page => move |_, _| {
@ -79,28 +140,27 @@ impl PasswordPage {
page.reset(); page.reset();
}) })
); );
get_action!(self.actions, @save_password).set_enabled(false); get_action!(self.actions, @save_password).set_enabled(false);
get_action!(self.actions, @reset_password).set_enabled(self.has_set_password.get()); get_action!(self.actions, @reset_password).set_enabled(self.has_set_password.get());*/
} }
fn reset(&self) { fn reset(&self) {
if Keyring::reset_password().is_ok() { if Keyring::reset_password().is_ok() {
get_action!(self.actions, @close_page).activate(None); let self_ = imp::PasswordPage::from_instance(self);
get_action!(self.actions, @save_password).set_enabled(false);
get_action!(self.actions, @reset_password).set_enabled(false); // get_action!(self.actions, @close_page).activate(None);
get_widget!(self.builder, libhandy::ActionRow, @current_password_row).hide(); // get_action!(self.actions, @save_password).set_enabled(false);
self.has_set_password.set(false); // get_action!(self.actions, @reset_password).set_enabled(false);
self_.current_password_row.get().hide();
self_.has_set_password.set(false);
} }
} }
fn save(&self) { fn save(&self) {
get_widget!(self.builder, gtk::PasswordEntry, current_password_entry); let self_ = imp::PasswordPage::from_instance(self);
get_widget!(self.builder, gtk::PasswordEntry, password_entry);
get_widget!(self.builder, gtk::PasswordEntry, confirm_password_entry);
let current_password = current_password_entry.get_text().unwrap(); let current_password = self_.current_password_entry.get().get_text().unwrap();
let password = password_entry.get_text().unwrap(); let password = self_.password_entry.get().get_text().unwrap();
if Keyring::has_set_password().unwrap_or(false) { if Keyring::has_set_password().unwrap_or(false) {
if !Keyring::is_current_password(&current_password).unwrap_or(false) { if !Keyring::is_current_password(&current_password).unwrap_or(false) {
@ -109,14 +169,14 @@ impl PasswordPage {
} }
if Keyring::set_password(&password).is_ok() { if Keyring::set_password(&password).is_ok() {
get_widget!(self.builder, libhandy::ActionRow, @current_password_row).show(); self_.current_password_row.get().show();
current_password_entry.set_text(""); self_.current_password_entry.get().set_text("");
password_entry.set_text(""); self_.password_entry.get().set_text("");
confirm_password_entry.set_text(""); self_.confirm_password_entry.get().set_text("");
get_action!(self.actions, @save_password).set_enabled(false); // get_action!(self.actions, @save_password).set_enabled(false);
get_action!(self.actions, @reset_password).set_enabled(true); // get_action!(self.actions, @reset_password).set_enabled(true);
get_action!(self.actions, @close_page).activate(None); // get_action!(self.actions, @close_page).activate(None);
self.has_set_password.set(true); self_.has_set_password.set(true);
} }
} }
} }

View file

@ -1,20 +1,17 @@
use super::window::PreferencesAction;
use crate::models::{Algorithm, Provider}; use crate::models::{Algorithm, Provider};
use glib::translate::ToGlib; use glib::translate::ToGlib;
use glib::Sender;
use gtk::prelude::*; use gtk::prelude::*;
use libhandy::ComboRowExt; use libhandy::{ComboRowExt, EnumListModelExt};
use std::rc::Rc; use std::rc::Rc;
pub struct ProviderPage { pub struct ProviderPage {
pub widget: gtk::Box, pub widget: gtk::Box,
builder: gtk::Builder, builder: gtk::Builder,
sender: Sender<PreferencesAction>,
algorithms_model: libhandy::EnumListModel, algorithms_model: libhandy::EnumListModel,
} }
impl ProviderPage { impl ProviderPage {
pub fn new(sender: Sender<PreferencesAction>) -> Rc<Self> { pub fn new() -> Rc<Self> {
let builder = gtk::Builder::from_resource( let builder = gtk::Builder::from_resource(
"/com/belmoussaoui/Authenticator/preferences_provider_page.ui", "/com/belmoussaoui/Authenticator/preferences_provider_page.ui",
); );
@ -23,7 +20,6 @@ impl ProviderPage {
let page = Rc::new(Self { let page = Rc::new(Self {
widget: provider_page, widget: provider_page,
sender,
builder, builder,
algorithms_model, algorithms_model,
}); });
@ -49,7 +45,6 @@ impl ProviderPage {
.find_position(provider.algorithm().to_glib()), .find_position(provider.algorithm().to_glib()),
); );
let p = provider.clone();
/*let sender = self.sender.clone(); /*let sender = self.sender.clone();
spawn!(async move { spawn!(async move {
if let Ok(file) = p.favicon().await { if let Ok(file) = p.favicon().await {

View file

@ -1,7 +1,5 @@
use super::window::PreferencesAction;
use crate::models::{Provider, ProvidersModel}; use crate::models::{Provider, ProvidersModel};
use gio::ListModelExt; use gio::ListModelExt;
use glib::Sender;
use gtk::prelude::*; use gtk::prelude::*;
use std::rc::Rc; use std::rc::Rc;
@ -9,11 +7,10 @@ pub struct ProvidersPage {
pub widget: libhandy::PreferencesPage, pub widget: libhandy::PreferencesPage,
builder: gtk::Builder, builder: gtk::Builder,
model: Rc<ProvidersModel>, model: Rc<ProvidersModel>,
sender: Sender<PreferencesAction>,
} }
impl ProvidersPage { impl ProvidersPage {
pub fn new(model: Rc<ProvidersModel>, sender: Sender<PreferencesAction>) -> Rc<Self> { pub fn new(model: Rc<ProvidersModel>) -> Rc<Self> {
let builder = gtk::Builder::from_resource( let builder = gtk::Builder::from_resource(
"/com/belmoussaoui/Authenticator/preferences_providers_page.ui", "/com/belmoussaoui/Authenticator/preferences_providers_page.ui",
); );
@ -23,7 +20,6 @@ impl ProvidersPage {
widget: providers_page, widget: providers_page,
builder, builder,
model, model,
sender,
}); });
page.init(); page.init();
page page
@ -44,13 +40,15 @@ impl ProvidersPage {
let selection_model = gtk::NoSelection::new(Some(&self.model.model)); let selection_model = gtk::NoSelection::new(Some(&self.model.model));
providers_list.set_model(Some(&selection_model)); providers_list.set_model(Some(&selection_model));
providers_list.connect_activate( providers_list.connect_activate(move |listview, pos| {
clone!(@strong self.sender as sender => move|listview, pos|{ let model = listview.get_model().unwrap();
let model = listview.get_model().unwrap(); let provider = model
let provider = model.get_object(pos).unwrap().downcast::<Provider>().unwrap(); .get_object(pos)
send!(sender, PreferencesAction::EditProvider(provider)); .unwrap()
}), .downcast::<Provider>()
); .unwrap();
// send!(sender, PreferencesAction::EditProvider(provider));
});
} }
} }

View file

@ -1,118 +1,131 @@
use super::password_page::PasswordPage; use super::password_page::PasswordPage;
use super::provider_page::ProviderPage;
use super::providers_page::ProvidersPage;
use crate::config; use crate::config;
use crate::models::{Provider, ProvidersModel}; use gio::prelude::*;
use gio::ActionMapExt; use gio::ActionMapExt;
use gio::SettingsExt; use gio::{subclass::ObjectSubclass, SettingsExt};
use glib::{Receiver, Sender}; use glib::subclass::prelude::*;
use gtk::prelude::*; use glib::{glib_object_subclass, glib_wrapper};
use gtk::{prelude::*, CompositeTemplate};
use libhandy::PreferencesWindowExt; use libhandy::PreferencesWindowExt;
use std::cell::RefCell;
use std::rc::Rc;
pub enum PreferencesAction { mod imp {
EditProvider(Provider), use super::*;
use glib::subclass;
use gtk::subclass::prelude::*;
use libhandy::subclass::{
preferences_window::PreferencesWindowImpl, window::WindowImpl as HdyWindowImpl,
};
#[derive(CompositeTemplate)]
pub struct PreferencesWindow {
pub settings: gio::Settings,
pub actions: gio::SimpleActionGroup,
pub password_page: PasswordPage,
#[template_child(id = "auto_lock_switch")]
pub auto_lock: TemplateChild<gtk::Switch>,
#[template_child(id = "dark_theme_switch")]
pub dark_theme: TemplateChild<gtk::Switch>,
#[template_child(id = "lock_timeout_spin_btn")]
pub lock_timeout: TemplateChild<gtk::SpinButton>,
}
impl ObjectSubclass for PreferencesWindow {
const NAME: &'static str = "PreferencesWindow";
type Type = super::PreferencesWindow;
type ParentType = libhandy::PreferencesWindow;
type Instance = subclass::simple::InstanceStruct<Self>;
type Class = subclass::simple::ClassStruct<Self>;
glib_object_subclass!();
fn new() -> Self {
let settings = gio::Settings::new(config::APP_ID);
let actions = gio::SimpleActionGroup::new();
Self {
settings,
actions,
password_page: PasswordPage::new(),
auto_lock: TemplateChild::default(),
dark_theme: TemplateChild::default(),
lock_timeout: TemplateChild::default(),
}
}
fn class_init(klass: &mut Self::Class) {
klass.set_template_from_resource("/com/belmoussaoui/Authenticator/preferences.ui");
Self::bind_template_children(klass);
}
}
impl ObjectImpl for PreferencesWindow {
fn constructed(&self, obj: &Self::Type) {
obj.init_template();
obj.setup_widgets();
obj.setup_actions();
self.parent_constructed(obj);
}
}
impl WidgetImpl for PreferencesWindow {}
impl WindowImpl for PreferencesWindow {}
impl HdyWindowImpl for PreferencesWindow {}
impl PreferencesWindowImpl for PreferencesWindow {}
} }
pub struct PreferencesWindow { glib_wrapper! {
pub widget: libhandy::PreferencesWindow, pub struct PreferencesWindow(ObjectSubclass<imp::PreferencesWindow>)
builder: gtk::Builder, @extends gtk::Widget, gtk::Window, libhandy::Window, libhandy::PreferencesWindow;
settings: gio::Settings,
providers_model: Rc<ProvidersModel>,
password_page: Rc<PasswordPage>,
providers_page: Rc<ProvidersPage>,
provider_page: Rc<ProviderPage>,
actions: gio::SimpleActionGroup,
sender: Sender<PreferencesAction>,
receiver: RefCell<Option<Receiver<PreferencesAction>>>,
} }
impl PreferencesWindow { impl PreferencesWindow {
pub fn new(providers_model: Rc<ProvidersModel>) -> Rc<Self> { pub fn new() -> Self {
let builder = gtk::Builder::from_resource("/com/belmoussaoui/Authenticator/preferences.ui"); glib::Object::new(Self::static_type(), &[])
get_widget!(builder, libhandy::PreferencesWindow, preferences_window); .expect("Failed to create PreferencesWindow")
let settings = gio::Settings::new(config::APP_ID); .downcast::<PreferencesWindow>()
let actions = gio::SimpleActionGroup::new(); .expect("Created object is of wrong type")
let (sender, r) = glib::MainContext::channel(glib::PRIORITY_DEFAULT);
let receiver = RefCell::new(Some(r));
let preferences = Rc::new(Self {
widget: preferences_window,
builder,
settings,
providers_page: ProvidersPage::new(providers_model.clone(), sender.clone()),
provider_page: ProviderPage::new(sender.clone()),
password_page: PasswordPage::new(actions.clone()),
providers_model,
actions,
sender,
receiver,
});
preferences.init(preferences.clone());
preferences.setup_actions();
preferences
} }
fn init(&self, preferences: Rc<Self>) { fn setup_widgets(&self) {
get_widget!(self.builder, gtk::Switch, dark_theme_switch); let self_ = imp::PreferencesWindow::from_instance(self);
self.settings.bind(
self_.settings.bind(
"dark-theme", "dark-theme",
&dark_theme_switch, &self_.dark_theme.get(),
"active", "active",
gio::SettingsBindFlags::DEFAULT, gio::SettingsBindFlags::DEFAULT,
); );
self_.settings.bind(
get_widget!(self.builder, gtk::Switch, auto_lock_switch);
self.settings.bind(
"auto-lock", "auto-lock",
&auto_lock_switch, &self_.auto_lock.get(),
"active", "active",
gio::SettingsBindFlags::DEFAULT, gio::SettingsBindFlags::DEFAULT,
); );
get_widget!(self.builder, gtk::SpinButton, lock_timeout_spin_btn); self_
auto_lock_switch .auto_lock
.bind_property("active", &lock_timeout_spin_btn, "sensitive") .get()
.bind_property("active", &self_.lock_timeout.get(), "sensitive")
.flags(glib::BindingFlags::DEFAULT | glib::BindingFlags::SYNC_CREATE) .flags(glib::BindingFlags::DEFAULT | glib::BindingFlags::SYNC_CREATE)
.build(); .build();
self.widget.add(&self.providers_page.widget);
let receiver = self.receiver.borrow_mut().take().unwrap();
receiver.attach(None, move |action| preferences.do_action(action));
}
fn do_action(&self, action: PreferencesAction) -> glib::Continue {
match action {
PreferencesAction::EditProvider(provider) => {
self.provider_page.set_provider(provider);
self.widget.present_subpage(&self.provider_page.widget)
}
}
glib::Continue(true)
} }
fn setup_actions(&self) { fn setup_actions(&self) {
let self_ = imp::PreferencesWindow::from_instance(self);
action!( action!(
self.actions, self_.actions,
"show_password_page", "show_password_page",
clone!(@strong self.builder as builder, clone!(@weak self as win, @weak self_.password_page as password_page => move |_, _| {
@strong self.password_page.widget as password_page, win.present_subpage(&password_page);
@strong self.widget as widget => move |_, _| {
widget.present_subpage(&password_page);
}) })
); );
action!( action!(
self.actions, self_.actions,
"close_page", "close_page",
clone!(@strong self.builder as builder, clone!(@weak self as win => move |_, _| {
@strong self.widget as widget => move |_, _| { win.close_subpage();
widget.close_subpage();
}) })
); );
self.widget self.insert_action_group("preferences", Some(&self_.actions));
.insert_action_group("preferences", Some(&self.actions));
} }
} }

View file

@ -1,69 +1,107 @@
use crate::application::Action;
use crate::models::{Provider, ProvidersModel}; use crate::models::{Provider, ProvidersModel};
use crate::widgets::providers::ProviderRow; use crate::widgets::providers::ProviderRow;
use gio::ListModelExt; use gio::{subclass::ObjectSubclass, ListModelExt};
use glib::Sender; use glib::subclass::prelude::*;
use gtk::prelude::*; use glib::{glib_object_subclass, glib_wrapper};
use gtk::{prelude::*, CompositeTemplate};
use std::rc::Rc; use std::rc::Rc;
pub struct ProvidersList { mod imp {
pub widget: gtk::Box, use super::*;
builder: gtk::Builder, use glib::subclass;
pub filter_model: gtk::FilterListModel, use gtk::subclass::prelude::*;
#[derive(Debug, CompositeTemplate)]
pub struct ProvidersList {
#[template_child(id = "providers_list")]
pub providers_list: TemplateChild<gtk::ListBox>,
pub filter_model: gtk::FilterListModel,
}
impl ObjectSubclass for ProvidersList {
const NAME: &'static str = "ProvidersList";
type Type = super::ProvidersList;
type ParentType = gtk::Box;
type Instance = subclass::simple::InstanceStruct<Self>;
type Class = subclass::simple::ClassStruct<Self>;
glib_object_subclass!();
fn new() -> Self {
let filter_model =
gtk::FilterListModel::new(gtk::NONE_FILTER_LIST_MODEL, gtk::NONE_FILTER);
Self {
providers_list: TemplateChild::default(),
filter_model,
}
}
fn class_init(klass: &mut Self::Class) {
klass.set_template_from_resource("/com/belmoussaoui/Authenticator/providers_list.ui");
Self::bind_template_children(klass);
}
}
impl ObjectImpl for ProvidersList {
fn constructed(&self, obj: &Self::Type) {
obj.init_template();
obj.setup_widgets();
self.parent_constructed(obj);
}
}
impl WidgetImpl for ProvidersList {}
impl BoxImpl for ProvidersList {}
} }
glib_wrapper! {
pub struct ProvidersList(ObjectSubclass<imp::ProvidersList>) @extends gtk::Widget, gtk::Box;
}
impl ProvidersList { impl ProvidersList {
pub fn new() -> Self { pub fn new() -> Self {
let builder = glib::Object::new(Self::static_type(), &[])
gtk::Builder::from_resource("/com/belmoussaoui/Authenticator/providers_list.ui"); .expect("Failed to create ProvidersList")
get_widget!(builder, gtk::Box, providers_box); .downcast::<ProvidersList>()
.expect("Created object is of wrong type")
let filter_model = gtk::FilterListModel::new(gtk::NONE_FILTER_LIST_MODEL, gtk::NONE_FILTER);
let list = Self {
widget: providers_box,
builder,
filter_model,
};
list
} }
pub fn set_model(&self, model: Rc<ProvidersModel>) { pub fn set_model(&self, model: Rc<ProvidersModel>) {
let self_ = imp::ProvidersList::from_instance(self);
let accounts_filter = gtk::CustomFilter::new(Some(Box::new(|object| { let accounts_filter = gtk::CustomFilter::new(Some(Box::new(|object| {
let provider = object.downcast_ref::<Provider>().unwrap(); let provider = object.downcast_ref::<Provider>().unwrap();
provider.has_accounts() provider.has_accounts()
}))); })));
self.filter_model.set_filter(Some(&accounts_filter)); self_.filter_model.set_filter(Some(&accounts_filter));
self.filter_model.set_model(Some(&model.model)); self_.filter_model.set_model(Some(&model.model));
} }
pub fn refilter(&self) { pub fn refilter(&self) {
if let Some(filter) = self.filter_model.get_filter() { let self_ = imp::ProvidersList::from_instance(self);
if let Some(filter) = self_.filter_model.get_filter() {
filter.changed(gtk::FilterChange::Different); filter.changed(gtk::FilterChange::Different);
} }
} }
pub fn search(&self, text: String) { pub fn search(&self, text: String) {
get_widget!(self.builder, gtk::ListBox, providers_list); let self_ = imp::ProvidersList::from_instance(self);
let accounts_filter = gtk::CustomFilter::new(Some(Box::new(move |object| { let accounts_filter = gtk::CustomFilter::new(Some(Box::new(move |object| {
let provider = object.downcast_ref::<Provider>().unwrap(); let provider = object.downcast_ref::<Provider>().unwrap();
provider.search_accounts(text.clone()); provider.search_accounts(text.clone());
provider.accounts().get_n_items() != 0 provider.accounts().get_n_items() != 0
}))); })));
self.filter_model.set_filter(Some(&accounts_filter)); self_.filter_model.set_filter(Some(&accounts_filter));
} }
pub fn init(&self, sender: Sender<Action>) { fn setup_widgets(&self) {
get_widget!(self.builder, gtk::ListBox, providers_list); let self_ = imp::ProvidersList::from_instance(self);
providers_list.bind_model( self_.providers_list.get().bind_model(
Some(&self.filter_model), Some(&self_.filter_model),
Some(Box::new(clone!(@strong sender => move |obj| { Some(Box::new(move |obj| {
let provider = obj.downcast_ref::<Provider>().unwrap(); let provider = obj.clone().downcast::<Provider>().unwrap();
let row = ProviderRow::new(provider, sender.clone()); ProviderRow::new(provider).upcast::<gtk::Widget>()
row.widget.upcast::<gtk::Widget>() })),
}))),
); );
} }
} }

View file

@ -1,52 +1,123 @@
use crate::application::Action;
use crate::models::{Account, Provider}; use crate::models::{Account, Provider};
use crate::widgets::accounts::AccountRow; use crate::widgets::accounts::AccountRow;
use glib::Sender; use gio::prelude::*;
use gtk::prelude::*; use gio::subclass::ObjectSubclass;
use glib::subclass::prelude::*;
use glib::{glib_object_subclass, glib_wrapper};
use gtk::{prelude::*, CompositeTemplate};
use std::cell::RefCell;
mod imp {
use super::*;
use glib::subclass;
use gtk::subclass::prelude::*;
pub struct ProviderRow<'a> { static PROPERTIES: [subclass::Property; 1] = [subclass::Property("provider", |name| {
pub widget: gtk::ListBoxRow, glib::ParamSpec::object(
provider: &'a Provider, name,
builder: gtk::Builder, "Provider",
sender: Sender<Action>, "The accounts provider",
} Provider::static_type(),
glib::ParamFlags::READWRITE | glib::ParamFlags::CONSTRUCT_ONLY,
)
})];
impl<'a> ProviderRow<'a> { #[derive(CompositeTemplate)]
pub fn new(provider: &'a Provider, sender: Sender<Action>) -> Self { pub struct ProviderRow {
let builder = pub provider: RefCell<Option<Provider>>,
gtk::Builder::from_resource("/com/belmoussaoui/Authenticator/provider_row.ui"); #[template_child(id = "name_label")]
get_widget!(builder, gtk::ListBoxRow, provider_row); pub name_label: TemplateChild<gtk::Label>,
#[template_child(id = "accounts_list")]
let row = Self { pub accounts_list: TemplateChild<gtk::ListBox>,
widget: provider_row,
builder,
sender,
provider,
};
row.init();
row
} }
fn init(&self) { impl ObjectSubclass for ProviderRow {
get_widget!(self.builder, gtk::Label, name); const NAME: &'static str = "ProviderRow";
type Type = super::ProviderRow;
type ParentType = gtk::ListBoxRow;
type Instance = subclass::simple::InstanceStruct<Self>;
type Class = subclass::simple::ClassStruct<Self>;
self.provider glib_object_subclass!();
.bind_property("name", &name, "label")
fn new() -> Self {
Self {
name_label: TemplateChild::default(),
accounts_list: TemplateChild::default(),
provider: RefCell::new(None),
}
}
fn class_init(klass: &mut Self::Class) {
klass.set_template_from_resource("/com/belmoussaoui/Authenticator/provider_row.ui");
Self::bind_template_children(klass);
klass.install_properties(&PROPERTIES);
}
}
impl ObjectImpl for ProviderRow {
fn set_property(&self, _obj: &Self::Type, id: usize, value: &glib::Value) {
let prop = &PROPERTIES[id];
match *prop {
subclass::Property("provider", ..) => {
let provider = value
.get()
.expect("type conformity checked by `Object::set_property`");
self.provider.replace(provider);
}
_ => unimplemented!(),
}
}
fn get_property(&self, _obj: &Self::Type, id: usize) -> glib::Value {
let prop = &PROPERTIES[id];
match *prop {
subclass::Property("provider", ..) => self.provider.borrow().to_value(),
_ => unimplemented!(),
}
}
fn constructed(&self, obj: &Self::Type) {
obj.init_template();
obj.setup_widgets();
self.parent_constructed(obj);
}
}
impl WidgetImpl for ProviderRow {}
impl ListBoxRowImpl for ProviderRow {}
}
glib_wrapper! {
pub struct ProviderRow(ObjectSubclass<imp::ProviderRow>) @extends gtk::Widget, gtk::ListBoxRow;
}
impl ProviderRow {
pub fn new(provider: Provider) -> Self {
glib::Object::new(Self::static_type(), &[("provider", &provider)])
.expect("Failed to create ProviderRow")
.downcast::<ProviderRow>()
.expect("Created object is of wrong type")
}
fn provider(&self) -> Provider {
let provider = self.get_property("provider").unwrap();
provider.get::<Provider>().unwrap().unwrap()
}
fn setup_widgets(&self) {
let self_ = imp::ProviderRow::from_instance(self);
self.provider()
.bind_property("name", &self_.name_label.get(), "label")
.flags(glib::BindingFlags::DEFAULT | glib::BindingFlags::SYNC_CREATE) .flags(glib::BindingFlags::DEFAULT | glib::BindingFlags::SYNC_CREATE)
.build(); .build();
get_widget!(self.builder, gtk::ListBox, accounts_list); self_.accounts_list.get().bind_model(
accounts_list.bind_model( Some(self.provider().accounts()),
Some(self.provider.accounts()), Some(Box::new(move |account: &glib::Object| {
Some(Box::new( let account = account.clone().downcast::<Account>().unwrap();
clone!(@strong self.sender as sender => move |account: &glib::Object| { AccountRow::new(account).upcast::<gtk::Widget>()
let account: &Account = account })),
.downcast_ref::<Account>()
.unwrap();
let row = AccountRow::new(account, sender.clone());
row.widget.upcast::<gtk::Widget>()
}),
)),
); );
} }
} }

View file

@ -5,11 +5,11 @@ use crate::models::ProvidersModel;
use crate::widgets::providers::ProvidersList; use crate::widgets::providers::ProvidersList;
use crate::window_state; use crate::window_state;
use gio::prelude::*; use gio::prelude::*;
use glib::subclass; use gio::subclass::ObjectSubclass;
use glib::subclass::prelude::*; use glib::subclass::prelude::*;
use glib::{glib_object_subclass, glib_wrapper};
use glib::{signal::Inhibit, Sender}; use glib::{signal::Inhibit, Sender};
use gtk::prelude::*; use gtk::{prelude::*, CompositeTemplate};
use gtk::subclass::prelude::{WidgetImpl, WindowImpl};
use libhandy::prelude::*; use libhandy::prelude::*;
use std::rc::Rc; use std::rc::Rc;
@ -19,45 +19,75 @@ pub enum View {
Accounts, Accounts,
} }
pub struct WindowPrivate { mod imp {
builder: gtk::Builder, use super::*;
settings: gio::Settings, use glib::subclass;
pub providers: Rc<ProvidersList>, use gtk::subclass::prelude::*;
} use libhandy::subclass::application_window::ApplicationWindowImpl as HdyApplicationWindowImpl;
impl ObjectSubclass for WindowPrivate { #[derive(Debug, CompositeTemplate)]
const NAME: &'static str = "Window"; pub struct Window {
type Type = super::Window; pub settings: gio::Settings,
type ParentType = libhandy::ApplicationWindow; pub providers: ProvidersList,
type Instance = subclass::simple::InstanceStruct<Self>; #[template_child(id = "search_entry")]
type Class = subclass::simple::ClassStruct<Self>; pub search_entry: TemplateChild<gtk::SearchEntry>,
#[template_child(id = "deck")]
pub deck: TemplateChild<libhandy::Leaflet>,
#[template_child(id = "container")]
pub container: TemplateChild<gtk::Box>,
#[template_child(id = "search_bar")]
pub search_bar: TemplateChild<gtk::SearchBar>,
#[template_child(id = "search_btn")]
pub search_btn: TemplateChild<gtk::ToggleButton>,
#[template_child(id = "password_entry")]
pub password_entry: TemplateChild<gtk::PasswordEntry>,
}
glib_object_subclass!(); impl ObjectSubclass for Window {
const NAME: &'static str = "Window";
type Type = super::Window;
type ParentType = libhandy::ApplicationWindow;
type Instance = subclass::simple::InstanceStruct<Self>;
type Class = subclass::simple::ClassStruct<Self>;
fn new() -> Self { glib_object_subclass!();
let builder = gtk::Builder::from_resource("/com/belmoussaoui/Authenticator/window.ui");
let settings = gio::Settings::new(APP_ID); fn new() -> Self {
let providers = Rc::new(ProvidersList::new()); let settings = gio::Settings::new(APP_ID);
Self { let providers = ProvidersList::new();
builder, Self {
settings, settings,
providers, providers,
search_entry: TemplateChild::default(),
deck: TemplateChild::default(),
container: TemplateChild::default(),
search_bar: TemplateChild::default(),
search_btn: TemplateChild::default(),
password_entry: TemplateChild::default(),
}
}
fn class_init(klass: &mut Self::Class) {
klass.set_template_from_resource("/com/belmoussaoui/Authenticator/window.ui");
Self::bind_template_children(klass);
} }
} }
impl ObjectImpl for Window {
fn constructed(&self, obj: &Self::Type) {
obj.init_template();
self.parent_constructed(obj);
}
}
impl WidgetImpl for Window {}
impl WindowImpl for Window {}
impl ApplicationWindowImpl for Window {}
impl HdyApplicationWindowImpl for Window {}
} }
impl ObjectImpl for WindowPrivate {}
impl WidgetImpl for WindowPrivate {}
impl WindowImpl for WindowPrivate {}
impl gtk::subclass::prelude::ApplicationWindowImpl for WindowPrivate {}
impl libhandy::subclass::prelude::ApplicationWindowImpl for WindowPrivate {}
glib_wrapper! { glib_wrapper! {
pub struct Window(ObjectSubclass<WindowPrivate>) pub struct Window(ObjectSubclass<imp::Window>)
@extends gtk::Widget, gtk::Window, gtk::ApplicationWindow, libhandy::ApplicationWindow, gio::ActionMap; @extends gtk::Widget, gtk::Window, gtk::ApplicationWindow, libhandy::ApplicationWindow, gio::ActionMap;
} }
@ -72,7 +102,7 @@ impl Window {
if PROFILE == "Devel" { if PROFILE == "Devel" {
window.get_style_context().add_class("devel"); window.get_style_context().add_class("devel");
} }
window.init(model, sender.clone()); window.init(model);
window.setup_actions(app, sender.clone()); window.setup_actions(app, sender.clone());
window.set_view(View::Login); // Start by default in an empty state window.set_view(View::Login); // Start by default in an empty state
window.setup_signals(app, sender); window.setup_signals(app, sender);
@ -80,22 +110,25 @@ impl Window {
} }
pub fn set_view(&self, view: View) { pub fn set_view(&self, view: View) {
let self_ = WindowPrivate::from_instance(self); let self_ = imp::Window::from_instance(self);
get_widget!(self_.builder, libhandy::Leaflet, deck);
match view { match view {
View::Login => { View::Login => {
deck.set_visible_child_name("login"); self_.deck.get().set_visible_child_name("login");
} }
View::Accounts => { View::Accounts => {
deck.set_visible_child_name("accounts"); self_.deck.get().set_visible_child_name("accounts");
} }
} }
} }
fn init(&self, model: Rc<ProvidersModel>, sender: Sender<Action>) { pub fn providers(&self) -> ProvidersList {
let self_ = WindowPrivate::from_instance(self); let self_ = imp::Window::from_instance(self);
self_.providers.clone()
}
fn init(&self, model: Rc<ProvidersModel>) {
let self_ = imp::Window::from_instance(self);
self_.providers.set_model(model.clone()); self_.providers.set_model(model.clone());
self_.providers.init(sender.clone());
// load latest window state // load latest window state
window_state::load(&self, &self_.settings); window_state::load(&self, &self_.settings);
// save window state on delete event // save window state on delete event
@ -110,31 +143,30 @@ impl Window {
get_widget!(builder, gtk::ShortcutsWindow, shortcuts); get_widget!(builder, gtk::ShortcutsWindow, shortcuts);
self.set_help_overlay(Some(&shortcuts)); self.set_help_overlay(Some(&shortcuts));
get_widget!(self_.builder, gtk::Box, container); self_.container.get().append(&self_.providers);
container.append(&self_.providers.widget);
get_widget!(self_.builder, gtk::SearchBar, search_bar); self_
get_widget!(self_.builder, gtk::ToggleButton, search_btn); .search_btn
search_btn .get()
.bind_property("active", &search_bar, "search-mode-enabled") .bind_property("active", &self_.search_bar.get(), "search-mode-enabled")
.flags(glib::BindingFlags::BIDIRECTIONAL | glib::BindingFlags::SYNC_CREATE) .flags(glib::BindingFlags::BIDIRECTIONAL | glib::BindingFlags::SYNC_CREATE)
.build(); .build();
get_widget!(self_.builder, gtk::SearchEntry, search_entry);
search_entry.connect_search_changed( self_.search_entry.get().connect_search_changed(
clone!(@weak self_.providers as providers => move |entry| { clone!(@weak self_.providers as providers => move |entry| {
let text = entry.get_text().unwrap().to_string(); let text = entry.get_text().unwrap().to_string();
providers.search(text); providers.search(text);
}), }),
); );
search_entry.connect_stop_search(clone!(@strong search_btn => move |entry| { let search_btn = self_.search_btn.get();
entry.set_text(""); self_
search_btn.set_active(false); .search_entry
})); .get()
.connect_stop_search(clone!(@weak search_btn => move |entry| {
get_widget!(self_.builder, libhandy::Leaflet, deck); entry.set_text("");
libhandy::ApplicationWindowExt::set_child(self, Some(&deck)); search_btn.set_active(false);
}));
let gtk_settings = gtk::Settings::get_default().unwrap(); let gtk_settings = gtk::Settings::get_default().unwrap();
self_.settings.bind( self_.settings.bind(
@ -147,12 +179,12 @@ impl Window {
} }
fn setup_actions(&self, app: &Application, sender: Sender<Action>) { fn setup_actions(&self, app: &Application, sender: Sender<Action>) {
let self_ = WindowPrivate::from_instance(self); let self_ = imp::Window::from_instance(self);
let search_btn = self_.search_btn.get();
action!( action!(
self, self,
"search", "search",
clone!(@strong self_.builder as builder => move |_,_| { clone!(@weak search_btn => move |_,_| {
get_widget!(builder, gtk::ToggleButton, search_btn);
search_btn.set_active(!search_btn.get_active()); search_btn.set_active(!search_btn.get_active());
}) })
); );
@ -165,11 +197,11 @@ impl Window {
}) })
); );
let password_entry = self_.password_entry.get();
action!( action!(
self, self,
"unlock", "unlock",
clone!(@strong sender, @strong self_.builder as builder, @strong app => move |_, _| { clone!(@strong sender, @weak password_entry, @strong app => move |_, _| {
get_widget!(builder, gtk::PasswordEntry, password_entry);
let password = password_entry.get_text().unwrap(); let password = password_entry.get_text().unwrap();
if Keyring::is_current_password(&password).unwrap() { if Keyring::is_current_password(&password).unwrap() {
password_entry.set_text(""); password_entry.set_text("");