mirror of
https://gitlab.gnome.org/World/Authenticator.git
synced 2025-03-04 00:34:40 +01:00
subclassing: use composite templates everywhere
This commit is contained in:
parent
c581c12ab3
commit
ddddb97c5f
25 changed files with 2226 additions and 1857 deletions
2452
Cargo.lock
generated
2452
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -20,6 +20,7 @@ surf = "2.1"
|
|||
url = "2.1"
|
||||
zbar-rust = "0.0"
|
||||
secret-service = "1.1"
|
||||
once_cell = "1.5"
|
||||
|
||||
[dependencies.gtk]
|
||||
git = "https://github.com/gtk-rs/gtk4-rs"
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
<!-- UI Files -->
|
||||
<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">window.ui</file>
|
||||
<file>window.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" alias="preferences_provider_page.ui">resources/ui/preferences_provider_page.ui</file>
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<interface>
|
||||
<object class="HdyWindow" id="add_dialog">
|
||||
<template parent="HdyWindow" class="AccountAddDialog">
|
||||
<property name="modal">True</property>
|
||||
<property name="default_width">360</property>
|
||||
<property name="default_height">600</property>
|
||||
<property name="title">Add a new account</property>
|
||||
<property name="destroy_with_parent">True</property>
|
||||
<child>
|
||||
<object class="HdyLeaflet">
|
||||
<child>
|
||||
|
@ -143,8 +142,7 @@
|
|||
</child>
|
||||
<child>
|
||||
<object class="GtkListBox" id="more_list">
|
||||
<property name="visible">False</property>
|
||||
|
||||
<property name="visible">False</property>
|
||||
<property name="selection_mode">none</property>
|
||||
<child>
|
||||
<object class="HdyActionRow" id="provider_website_row">
|
||||
|
@ -211,7 +209,7 @@
|
|||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</template>
|
||||
<object class="GtkEntryCompletion" id="provider_completion">
|
||||
<property name="minimum_key_length">2</property>
|
||||
<property name="text_column">1</property>
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
<attribute name="action">account.delete</attribute>
|
||||
</item>
|
||||
</menu>
|
||||
<object class="GtkListBoxRow" id="account_row">
|
||||
<template parent="GtkListBoxRow" class="AccountRow">
|
||||
<property name="can_focus">True</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="selectable">False</property>
|
||||
|
@ -118,5 +118,5 @@
|
|||
<style>
|
||||
<class name="account-row"/>
|
||||
</style>
|
||||
</object>
|
||||
</template>
|
||||
</interface>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<property name="step_increment">1</property>
|
||||
<property name="page_increment">10</property>
|
||||
</object>
|
||||
<object class="HdyPreferencesWindow" id="preferences_window">
|
||||
<template class="PreferencesWindow" parent="HdyPreferencesWindow">
|
||||
<property name="default_width">360</property>
|
||||
<property name="default_height">600</property>
|
||||
<property name="search-enabled">False</property>
|
||||
|
@ -126,6 +126,6 @@
|
|||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</template>
|
||||
</interface>
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<interface>
|
||||
<object class="GtkBox" id="password_page">
|
||||
<template parent="GtkBox" class="PasswordPage">
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkHeaderBar">
|
||||
|
@ -123,7 +123,7 @@
|
|||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</template>
|
||||
<object class="GtkSizeGroup">
|
||||
<property name="mode">GTK_SIZE_GROUP_HORIZONTAL</property>
|
||||
<widgets>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<interface>
|
||||
<object class="GtkListBoxRow" id="provider_row">
|
||||
<template parent="GtkListBoxRow" class="ProviderRow">
|
||||
<property name="can_focus">True</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="selectable">False</property>
|
||||
|
@ -9,7 +9,7 @@
|
|||
<property name="orientation">vertical</property>
|
||||
<property name="vexpand">True</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="name">
|
||||
<object class="GtkLabel" id="name_label">
|
||||
<property name="halign">start</property>
|
||||
<style>
|
||||
<class name="title-2" />
|
||||
|
@ -29,5 +29,5 @@
|
|||
<style>
|
||||
<class name="provider-row"/>
|
||||
</style>
|
||||
</object>
|
||||
</template>
|
||||
</interface>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<interface>
|
||||
<object class="GtkBox" id="providers_box">
|
||||
<template parent="GtkBox" class="ProvidersList">
|
||||
<property name="hexpand">True</property>
|
||||
<property name="vexpand">True</property>
|
||||
<property name="orientation">vertical</property>
|
||||
|
@ -123,5 +123,5 @@
|
|||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</template>
|
||||
</interface>
|
||||
|
|
|
@ -18,157 +18,161 @@
|
|||
<attribute name="action">app.about</attribute>
|
||||
</item>
|
||||
</menu>
|
||||
<object class="HdyLeaflet" id="deck">
|
||||
<property name="can-unfold">False</property>
|
||||
<template class="Window" parent="HdyApplicationWindow">
|
||||
<property name="icon_name">@APP_ID@</property>
|
||||
<child>
|
||||
<object class="HdyLeafletPage">
|
||||
<property name="name">accounts</property>
|
||||
<property name="child">
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">vertical</property>
|
||||
<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="HdyLeaflet" id="deck">
|
||||
<property name="can-unfold">False</property>
|
||||
<child>
|
||||
<object class="HdyLeafletPage">
|
||||
<property name="name">accounts</property>
|
||||
<property name="child">
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<child>
|
||||
<object class="GtkWindowControls">
|
||||
<property name="side">start</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="hexpand">true</property>
|
||||
<property name="margin-top">6</property>
|
||||
<property name="margin-bottom">6</property>
|
||||
<property name="margin-start">6</property>
|
||||
<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="GtkWindowControls">
|
||||
<property name="side">end</property>
|
||||
<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 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">
|
||||
<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>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="halign">center</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="icon-name">@app-id@</property>
|
||||
<property name="pixel-size">128</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="halign">center</property>
|
||||
<property name="margin-top">12</property>
|
||||
<property name="label" translatable="yes">Authenticator is locked</property>
|
||||
<style>
|
||||
<class name="title-2"/>
|
||||
</style>
|
||||
<object class="GtkBox">
|
||||
<child>
|
||||
<object class="GtkWindowControls">
|
||||
<property name="side">start</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="hexpand">true</property>
|
||||
<property name="margin-top">6</property>
|
||||
<property name="margin-bottom">6</property>
|
||||
<property name="margin-start">6</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkWindowControls">
|
||||
<property name="side">end</property>
|
||||
<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>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="halign">center</property>
|
||||
<property name="valign">start</property>
|
||||
<property name="margin-top">24</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>
|
||||
<child>
|
||||
<object class="GtkPasswordEntry" id="password_entry">
|
||||
<object class="GtkImage">
|
||||
<property name="halign">center</property>
|
||||
<property name="activates-default">True</property>
|
||||
<property name="show-peek-icon">True</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="icon-name">@app-id@</property>
|
||||
<property name="pixel-size">128</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>
|
||||
<object class="GtkLabel">
|
||||
<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>
|
||||
<class name="suggested-action"/>
|
||||
<class name="pill-button" />
|
||||
<class name="large-button" />
|
||||
<class name="title-2"/>
|
||||
</style>
|
||||
</object>
|
||||
</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>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
|
||||
</child>
|
||||
</property>
|
||||
</object>
|
||||
</property>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</template>
|
||||
</interface>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::config;
|
||||
use crate::helpers::Keyring;
|
||||
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 glib::subclass::prelude::*;
|
||||
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];
|
||||
|
||||
match *prop {
|
||||
subclass::Property("locked", ..) => Ok(self.locked.get().to_value()),
|
||||
subclass::Property("can-be-locked", ..) => Ok(self.can_be_locked.get().to_value()),
|
||||
subclass::Property("locked", ..) => self.locked.get().to_value(),
|
||||
subclass::Property("can-be-locked", ..) => self.can_be_locked.get().to_value(),
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
@ -127,11 +127,11 @@ impl ApplicationImpl for ApplicationPrivate {
|
|||
action!(
|
||||
app,
|
||||
"preferences",
|
||||
clone!(@strong app, @weak self.model as model => move |_,_| {
|
||||
clone!(@strong app => move |_,_| {
|
||||
let window = app.get_active_window().unwrap();
|
||||
let preferences = PreferencesWindow::new(model);
|
||||
preferences.widget.set_transient_for(Some(&window));
|
||||
preferences.widget.show();
|
||||
let preferences = PreferencesWindow::new();
|
||||
preferences.set_transient_for(Some(&window));
|
||||
preferences.show();
|
||||
})
|
||||
);
|
||||
|
||||
|
@ -249,23 +249,21 @@ impl Application {
|
|||
|
||||
match action {
|
||||
Action::OpenAddAccountDialog => {
|
||||
let dialog = AddAccountDialog::new(self_.model.clone(), self_.sender.clone());
|
||||
dialog.widget.set_transient_for(Some(&active_window));
|
||||
dialog.widget.show();
|
||||
let dialog = AccountAddDialog::new(self_.model.clone(), self_.sender.clone());
|
||||
dialog.set_transient_for(Some(&active_window));
|
||||
dialog.show();
|
||||
}
|
||||
Action::AccountCreated(account, provider) => {
|
||||
let win_ = active_window.downcast_ref::<Window>().unwrap();
|
||||
let priv_ = WindowPrivate::from_instance(win_);
|
||||
let win = active_window.downcast_ref::<Window>().unwrap();
|
||||
|
||||
self_.model.add_account(&account, &provider);
|
||||
priv_.providers.refilter();
|
||||
win.providers().refilter();
|
||||
}
|
||||
Action::AccountRemoved(account) => {
|
||||
let win_ = active_window.downcast_ref::<Window>().unwrap();
|
||||
let priv_ = WindowPrivate::from_instance(win_);
|
||||
let win = active_window.downcast_ref::<Window>().unwrap();
|
||||
|
||||
self_.model.remove_account(&account);
|
||||
priv_.providers.refilter();
|
||||
win.providers().refilter();
|
||||
}
|
||||
Action::SetView(view) => {
|
||||
let win_ = active_window.downcast_ref::<Window>().unwrap();
|
||||
|
|
|
@ -46,7 +46,7 @@ pub(crate) fn scan(screenshot: &gio::File) -> Result<OtpAuth> {
|
|||
let img = image::load_from_memory(&data)?;
|
||||
|
||||
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();
|
||||
|
||||
|
|
|
@ -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];
|
||||
|
||||
match *prop {
|
||||
subclass::Property("id", ..) => Ok(self.id.get().to_value()),
|
||||
subclass::Property("name", ..) => Ok(self.name.borrow().to_value()),
|
||||
subclass::Property("token-id", ..) => Ok(self.token_id.borrow().to_value()),
|
||||
subclass::Property("provider-id", ..) => Ok(self.provider_id.get().to_value()),
|
||||
subclass::Property("id", ..) => self.id.get().to_value(),
|
||||
subclass::Property("name", ..) => self.name.borrow().to_value(),
|
||||
subclass::Property("token-id", ..) => self.token_id.borrow().to_value(),
|
||||
subclass::Property("provider-id", ..) => self.provider_id.get().to_value(),
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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];
|
||||
|
||||
match *prop {
|
||||
subclass::Property("id", ..) => Ok(self.id.get().to_value()),
|
||||
subclass::Property("name", ..) => Ok(self.name.borrow().to_value()),
|
||||
subclass::Property("period", ..) => Ok(self.period.get().to_value()),
|
||||
subclass::Property("algorithm", ..) => Ok(self.algorithm.borrow().to_value()),
|
||||
subclass::Property("website", ..) => Ok(self.website.borrow().to_value()),
|
||||
subclass::Property("help-url", ..) => Ok(self.help_url.borrow().to_value()),
|
||||
subclass::Property("image-uri", ..) => Ok(self.image_uri.borrow().to_value()),
|
||||
subclass::Property("id", ..) => self.id.get().to_value(),
|
||||
subclass::Property("name", ..) => self.name.borrow().to_value(),
|
||||
subclass::Property("period", ..) => self.period.get().to_value(),
|
||||
subclass::Property("algorithm", ..) => self.algorithm.borrow().to_value(),
|
||||
subclass::Property("website", ..) => self.website.borrow().to_value(),
|
||||
subclass::Property("help-url", ..) => self.help_url.borrow().to_value(),
|
||||
subclass::Property("image-uri", ..) => self.image_uri.borrow().to_value(),
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,61 +3,155 @@ use crate::helpers::qrcode;
|
|||
use crate::models::{Account, Provider, ProvidersModel};
|
||||
use anyhow::Result;
|
||||
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 gtk::prelude::*;
|
||||
use gtk::{prelude::*, CompositeTemplate};
|
||||
use libhandy::ActionRowExt;
|
||||
use once_cell::sync::OnceCell;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
pub enum AddAccountAction {
|
||||
pub enum AccountAddAction {
|
||||
SetIcon(gio::File),
|
||||
SetProvider(Provider),
|
||||
Save,
|
||||
ScanQR,
|
||||
}
|
||||
|
||||
pub struct AddAccountDialog {
|
||||
pub widget: libhandy::Window,
|
||||
builder: gtk::Builder,
|
||||
global_sender: Sender<Action>,
|
||||
sender: Sender<AddAccountAction>,
|
||||
receiver: RefCell<Option<Receiver<AddAccountAction>>>,
|
||||
model: Rc<ProvidersModel>,
|
||||
selected_provider: Rc<RefCell<Option<Provider>>>,
|
||||
actions: gio::SimpleActionGroup,
|
||||
mod imp {
|
||||
use super::*;
|
||||
use glib::subclass;
|
||||
use gtk::subclass::prelude::*;
|
||||
|
||||
#[derive(CompositeTemplate)]
|
||||
pub struct AccountAddDialog {
|
||||
pub global_sender: OnceCell<Sender<Action>>,
|
||||
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 {
|
||||
pub fn new(model: Rc<ProvidersModel>, global_sender: Sender<Action>) -> Rc<Self> {
|
||||
let builder = gtk::Builder::from_resource("/com/belmoussaoui/Authenticator/account_add.ui");
|
||||
get_widget!(builder, libhandy::Window, add_dialog);
|
||||
impl AccountAddDialog {
|
||||
pub fn new(model: Rc<ProvidersModel>, global_sender: Sender<Action>) -> Self {
|
||||
let dialog = glib::Object::new(Self::static_type(), &[])
|
||||
.expect("Failed to create AccountAddDialog")
|
||||
.downcast::<AccountAddDialog>()
|
||||
.expect("Created object is of wrong type");
|
||||
|
||||
let (sender, r) = glib::MainContext::channel(glib::PRIORITY_DEFAULT);
|
||||
let receiver = RefCell::new(Some(r));
|
||||
let actions = gio::SimpleActionGroup::new();
|
||||
let self_ = imp::AccountAddDialog::from_instance(&dialog);
|
||||
self_.model.set(model);
|
||||
self_.global_sender.set(global_sender);
|
||||
|
||||
let add_account_dialog = Rc::new(Self {
|
||||
widget: add_dialog,
|
||||
builder,
|
||||
global_sender,
|
||||
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
|
||||
dialog.setup_actions();
|
||||
dialog.setup_signals();
|
||||
dialog.setup_widgets();
|
||||
dialog
|
||||
}
|
||||
|
||||
fn setup_signals(&self) {
|
||||
get_widget!(self.builder, gtk::Entry, username_entry);
|
||||
get_widget!(self.builder, gtk::Entry, token_entry);
|
||||
let self_ = imp::AccountAddDialog::from_instance(self);
|
||||
|
||||
let validate_entries = clone!(@weak username_entry, @weak token_entry, @strong self.actions as actions => move |_: >k::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 |_: >k::Entry| {
|
||||
let username = username_entry.get_text().unwrap();
|
||||
let token = token_entry.get_text().unwrap();
|
||||
|
||||
|
@ -70,27 +164,33 @@ impl AddAccountDialog {
|
|||
token_entry.connect_changed(validate_entries);
|
||||
|
||||
let event_controller = gtk::EventControllerKey::new();
|
||||
event_controller.connect_key_pressed(clone!(@weak self.widget as widget => @default-return Inhibit(false), move |_, k, _, _| {
|
||||
if k == 65307 {
|
||||
widget.close();
|
||||
}
|
||||
Inhibit(false)
|
||||
}));
|
||||
self.widget.add_controller(&event_controller);
|
||||
event_controller.connect_key_pressed(
|
||||
clone!(@weak self as widget => @default-return Inhibit(false), move |_, k, _, _| {
|
||||
if k == 65307 {
|
||||
widget.close();
|
||||
}
|
||||
Inhibit(false)
|
||||
}),
|
||||
);
|
||||
self.add_controller(&event_controller);
|
||||
}
|
||||
|
||||
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(
|
||||
clone!(@strong self.builder as builder, @strong self.model as model,
|
||||
@strong self.sender as sender => move |screenshot| {
|
||||
clone!(@weak token_entry, @weak username_entry, @strong self_.model as model,
|
||||
@strong self_.sender as sender => move |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 {
|
||||
get_widget!(builder, gtk::Entry, @username_entry).set_text(&username);
|
||||
username_entry.set_text(&username);
|
||||
}
|
||||
if let Some(ref provider) = otpauth.issuer {
|
||||
let provider = model.find_by_name(provider).unwrap();
|
||||
send!(sender, AddAccountAction::SetProvider(provider));
|
||||
let provider = model.get().unwrap().find_by_name(provider).unwrap();
|
||||
send!(sender, AccountAddAction::SetProvider(provider));
|
||||
}
|
||||
}
|
||||
}),
|
||||
|
@ -99,98 +199,100 @@ impl AddAccountDialog {
|
|||
}
|
||||
|
||||
fn save(&self) -> Result<()> {
|
||||
if let Some(provider) = self.selected_provider.borrow().clone() {
|
||||
let username = get_widget!(self.builder, gtk::Entry, @username_entry)
|
||||
.get_text()
|
||||
.unwrap();
|
||||
let token = get_widget!(self.builder, gtk::Entry, @token_entry)
|
||||
.get_text()
|
||||
.unwrap();
|
||||
let self_ = imp::AccountAddDialog::from_instance(self);
|
||||
|
||||
if let Some(provider) = self_.selected_provider.get().clone() {
|
||||
let username = self_.username_entry.get().get_text().unwrap();
|
||||
let token = self_.token_entry.get().get_text().unwrap();
|
||||
|
||||
let account = Account::create(&username, &token, provider.id())?;
|
||||
send!(
|
||||
self.global_sender,
|
||||
Action::AccountCreated(account, provider)
|
||||
self_.global_sender.get().unwrap(),
|
||||
Action::AccountCreated(account, provider.clone())
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_provider(&self, provider: Provider) {
|
||||
get_widget!(self.builder, gtk::ListBox, @more_list).show();
|
||||
get_widget!(self.builder, gtk::Entry, @provider_entry).set_text(&provider.name());
|
||||
get_widget!(self.builder, gtk::Label, @period_label)
|
||||
.set_text(&format!("{} seconds", provider.period()));
|
||||
get_widget!(self.builder, gtk::Label, @algorithm_label)
|
||||
let self_ = imp::AccountAddDialog::from_instance(self);
|
||||
|
||||
self_.more_list.get().show();
|
||||
|
||||
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());
|
||||
|
||||
if let Some(ref website) = provider.website() {
|
||||
get_widget!(self.builder, libhandy::ActionRow, provider_website_row);
|
||||
provider_website_row.set_subtitle(Some(website));
|
||||
self_.provider_website_row.get().set_subtitle(Some(website));
|
||||
}
|
||||
if let Some(ref help_url) = provider.help_url() {
|
||||
get_widget!(self.builder, libhandy::ActionRow, provider_help_row);
|
||||
provider_help_row.set_subtitle(Some(help_url));
|
||||
self_.provider_help_row.get().set_subtitle(Some(help_url));
|
||||
}
|
||||
|
||||
get_widget!(self.builder, gtk::Stack, @image_stack).set_visible_child_name("loading");
|
||||
get_widget!(self.builder, gtk::Spinner, @spinner).start();
|
||||
self_.image_stack.get().set_visible_child_name("loading");
|
||||
self_.spinner.get().start();
|
||||
|
||||
let p = provider.clone();
|
||||
let sender = self.sender.clone();
|
||||
let sender = self_.sender.clone();
|
||||
spawn!(async move {
|
||||
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) {
|
||||
let self_ = imp::AccountAddDialog::from_instance(self);
|
||||
action!(
|
||||
self.actions,
|
||||
self_.actions,
|
||||
"back",
|
||||
clone!(@weak self.widget as dialog => move |_, _| {
|
||||
clone!(@weak self as dialog => move |_, _| {
|
||||
dialog.destroy();
|
||||
})
|
||||
);
|
||||
|
||||
action!(
|
||||
self.actions,
|
||||
self_.actions,
|
||||
"save",
|
||||
clone!(@strong self.sender as sender => move |_, _| {
|
||||
send!(sender, AddAccountAction::Save);
|
||||
clone!(@strong self_.sender as sender => move |_, _| {
|
||||
send!(sender, AccountAddAction::Save);
|
||||
})
|
||||
);
|
||||
|
||||
action!(
|
||||
self.actions,
|
||||
self_.actions,
|
||||
"scan-qr",
|
||||
clone!(@strong self.sender as sender => move |_, _| {
|
||||
send!(sender, AddAccountAction::ScanQR);
|
||||
clone!(@strong self_.sender as sender => move |_, _| {
|
||||
send!(sender, AccountAddAction::ScanQR);
|
||||
})
|
||||
);
|
||||
self.widget.insert_action_group("add", Some(&self.actions));
|
||||
get_action!(self.actions, @save).set_enabled(false);
|
||||
self.insert_action_group("add", Some(&self_.actions));
|
||||
get_action!(self_.actions, @save).set_enabled(false);
|
||||
}
|
||||
|
||||
fn setup_widgets(&self, dialog: Rc<Self>) {
|
||||
let receiver = self.receiver.borrow_mut().take().unwrap();
|
||||
fn setup_widgets(&self) {
|
||||
let self_ = imp::AccountAddDialog::from_instance(self);
|
||||
let receiver = self_.receiver.borrow_mut().take().unwrap();
|
||||
receiver.attach(
|
||||
None,
|
||||
clone!(@strong dialog => move |action| dialog.do_action(action)),
|
||||
clone!(@weak self as dialog => move |action| dialog.do_action(action)),
|
||||
);
|
||||
|
||||
get_widget!(self.builder, gtk::EntryCompletion, provider_completion);
|
||||
provider_completion.set_model(Some(&self.model.completion_model()));
|
||||
|
||||
get_widget!(self.builder, gtk::Entry, @token_entry);
|
||||
|
||||
provider_completion.connect_match_selected(
|
||||
clone!(@strong dialog, @strong self.model as model => move |_, store, iter| {
|
||||
self_
|
||||
.provider_completion
|
||||
.get()
|
||||
.set_model(Some(&self_.model.get().unwrap().completion_model()));
|
||||
self_.provider_completion.get().connect_match_selected(
|
||||
clone!(@strong self as dialog, @strong self_.model as model => move |_, store, iter| {
|
||||
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);
|
||||
|
||||
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 {
|
||||
AddAccountAction::SetIcon(file) => {
|
||||
get_widget!(self.builder, gtk::Image, @image)
|
||||
.set_from_file(file.get_path().unwrap());
|
||||
get_widget!(self.builder, gtk::Spinner, @spinner).stop();
|
||||
get_widget!(self.builder, gtk::Stack, @image_stack).set_visible_child_name("image");
|
||||
AccountAddAction::SetIcon(file) => {
|
||||
let self_ = imp::AccountAddDialog::from_instance(self);
|
||||
self_.image.get().set_from_file(file.get_path().unwrap());
|
||||
self_.spinner.get().stop();
|
||||
self_.image_stack.get().set_visible_child_name("image");
|
||||
}
|
||||
AddAccountAction::SetProvider(p) => self.set_provider(p),
|
||||
AddAccountAction::Save => {
|
||||
AccountAddAction::SetProvider(p) => self.set_provider(p),
|
||||
AccountAddAction::Save => {
|
||||
if self.save().is_ok() {
|
||||
self.widget.close();
|
||||
self.close();
|
||||
}
|
||||
}
|
||||
AddAccountAction::ScanQR => {
|
||||
AccountAddAction::ScanQR => {
|
||||
self.scan_qr();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
mod add;
|
||||
mod row;
|
||||
|
||||
pub use self::add::AddAccountDialog;
|
||||
pub use self::add::AccountAddDialog;
|
||||
pub use self::row::AccountRow;
|
||||
|
|
|
@ -1,84 +1,161 @@
|
|||
use crate::application::Action;
|
||||
use crate::models::Account;
|
||||
use gio::ActionMapExt;
|
||||
use glib::Sender;
|
||||
use gtk::prelude::*;
|
||||
use gio::prelude::*;
|
||||
use gio::{subclass::ObjectSubclass, ActionMapExt};
|
||||
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> {
|
||||
pub widget: gtk::ListBoxRow,
|
||||
builder: gtk::Builder,
|
||||
sender: Sender<Action>,
|
||||
account: &'a Account,
|
||||
actions: gio::SimpleActionGroup,
|
||||
}
|
||||
static PROPERTIES: [subclass::Property; 1] = [subclass::Property("account", |name| {
|
||||
glib::ParamSpec::object(
|
||||
name,
|
||||
"Account",
|
||||
"The account",
|
||||
Account::static_type(),
|
||||
glib::ParamFlags::READWRITE | glib::ParamFlags::CONSTRUCT_ONLY,
|
||||
)
|
||||
})];
|
||||
|
||||
impl<'a> AccountRow<'a> {
|
||||
pub fn new(account: &'a Account, sender: Sender<Action>) -> Self {
|
||||
let builder = gtk::Builder::from_resource("/com/belmoussaoui/Authenticator/account_row.ui");
|
||||
get_widget!(builder, gtk::ListBoxRow, account_row);
|
||||
let actions = gio::SimpleActionGroup::new();
|
||||
let row = Self {
|
||||
widget: account_row,
|
||||
builder,
|
||||
sender,
|
||||
account,
|
||||
actions,
|
||||
};
|
||||
row.init();
|
||||
row
|
||||
#[derive(CompositeTemplate)]
|
||||
pub struct AccountRow {
|
||||
pub account: RefCell<Option<Account>>,
|
||||
pub actions: gio::SimpleActionGroup,
|
||||
#[template_child(id = "name_label")]
|
||||
pub name_label: TemplateChild<gtk::Label>,
|
||||
#[template_child(id = "name_entry")]
|
||||
pub name_entry: TemplateChild<gtk::Entry>,
|
||||
#[template_child(id = "edit_stack")]
|
||||
pub edit_stack: TemplateChild<gtk::Stack>,
|
||||
}
|
||||
|
||||
fn init(&self) {
|
||||
self.widget
|
||||
.insert_action_group("account", Some(&self.actions));
|
||||
impl ObjectSubclass for AccountRow {
|
||||
const NAME: &'static str = "AccountRow";
|
||||
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);
|
||||
get_widget!(self.builder, gtk::Entry, name_entry);
|
||||
glib_object_subclass!();
|
||||
|
||||
self.account
|
||||
.bind_property("name", &name_label, "label")
|
||||
fn new() -> Self {
|
||||
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)
|
||||
.build();
|
||||
|
||||
self.account
|
||||
.bind_property("name", &name_entry, "text")
|
||||
self.account()
|
||||
.bind_property("name", &self_.name_entry.get(), "text")
|
||||
.flags(glib::BindingFlags::DEFAULT | glib::BindingFlags::SYNC_CREATE)
|
||||
.build();
|
||||
|
||||
action!(
|
||||
self.actions,
|
||||
"delete",
|
||||
clone!(@strong self.sender as sender, @strong self.account as account => move |_, _| {
|
||||
send!(sender, Action::AccountRemoved(account.clone()));
|
||||
})
|
||||
self_.name_entry.get().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());
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
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!(
|
||||
self.actions,
|
||||
self_.actions,
|
||||
"edit",
|
||||
clone!(@strong self.builder as builder => move |_, _| {
|
||||
get_widget!(builder, gtk::Stack, edit_stack);
|
||||
clone!(@weak edit_stack => move |_, _| {
|
||||
edit_stack.set_visible_child_name("edit");
|
||||
})
|
||||
);
|
||||
|
||||
let name_entry = self_.name_entry.get();
|
||||
action!(
|
||||
self.actions,
|
||||
self_.actions,
|
||||
"save",
|
||||
clone!(@weak name_entry,
|
||||
@strong self.account as account,
|
||||
@strong self.builder as builder => move |_, _| {
|
||||
clone!(@weak edit_stack, @weak name_entry => move |_, _| {
|
||||
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");
|
||||
})
|
||||
);
|
||||
|
||||
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());
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ mod preferences;
|
|||
mod providers;
|
||||
mod window;
|
||||
|
||||
pub use self::accounts::AddAccountDialog;
|
||||
pub use self::accounts::AccountAddDialog;
|
||||
pub use self::preferences::PreferencesWindow;
|
||||
pub use self::providers::ProvidersList;
|
||||
pub use self::window::{View, Window, WindowPrivate};
|
||||
pub use self::window::{View, Window};
|
||||
|
|
|
@ -1,70 +1,131 @@
|
|||
use crate::helpers::Keyring;
|
||||
use gio::{ActionExt, ActionMapExt};
|
||||
use gtk::prelude::*;
|
||||
use std::cell::Cell;
|
||||
use std::rc::Rc;
|
||||
|
||||
pub struct PasswordPage {
|
||||
pub widget: gtk::Box,
|
||||
builder: gtk::Builder,
|
||||
actions: gio::SimpleActionGroup,
|
||||
has_set_password: Cell<bool>,
|
||||
use gio::prelude::*;
|
||||
use gio::subclass::ObjectSubclass;
|
||||
use glib::subclass::prelude::*;
|
||||
use glib::{glib_object_subclass, glib_wrapper};
|
||||
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 {
|
||||
pub fn new(actions: gio::SimpleActionGroup) -> Rc<Self> {
|
||||
let builder = gtk::Builder::from_resource(
|
||||
"/com/belmoussaoui/Authenticator/preferences_password_page.ui",
|
||||
);
|
||||
get_widget!(builder, gtk::Box, password_page);
|
||||
|
||||
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
|
||||
pub fn new() -> Self {
|
||||
glib::Object::new(Self::static_type(), &[])
|
||||
.expect("Failed to create PasswordPage")
|
||||
.downcast::<PasswordPage>()
|
||||
.expect("Created object is of wrong type")
|
||||
}
|
||||
|
||||
fn validate(&self) {
|
||||
get_widget!(self.builder, gtk::PasswordEntry, current_password_entry);
|
||||
get_widget!(self.builder, gtk::PasswordEntry, password_entry);
|
||||
get_widget!(self.builder, gtk::PasswordEntry, confirm_password_entry);
|
||||
let self_ = imp::PasswordPage::from_instance(self);
|
||||
|
||||
let current_password = current_password_entry.get_text().unwrap();
|
||||
let password = password_entry.get_text().unwrap();
|
||||
let password_repeat = confirm_password_entry.get_text().unwrap();
|
||||
let current_password = self_.current_password_entry.get().get_text().unwrap();
|
||||
let password = self_.password_entry.get().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 != ""
|
||||
} else {
|
||||
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>) {
|
||||
get_widget!(self.builder, gtk::PasswordEntry, current_password_entry);
|
||||
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);
|
||||
fn setup_widgets(&self) {
|
||||
let self_ = imp::PasswordPage::from_instance(self);
|
||||
|
||||
password_entry.connect_changed(clone!(@strong page => move |_| page.validate()));
|
||||
confirm_password_entry.connect_changed(clone!(@strong page => move |_| page.validate()));
|
||||
self_
|
||||
.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() {
|
||||
current_password_row.hide();
|
||||
if !self_.has_set_password.get() {
|
||||
self_.current_password_row.get().hide();
|
||||
} else {
|
||||
current_password_entry
|
||||
.connect_changed(clone!(@strong page => move |_| page.validate()));
|
||||
self_
|
||||
.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,
|
||||
"save_password",
|
||||
clone!(@strong page => move |_, _| {
|
||||
|
@ -79,28 +140,27 @@ impl PasswordPage {
|
|||
page.reset();
|
||||
})
|
||||
);
|
||||
|
||||
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) {
|
||||
if Keyring::reset_password().is_ok() {
|
||||
get_action!(self.actions, @close_page).activate(None);
|
||||
get_action!(self.actions, @save_password).set_enabled(false);
|
||||
get_action!(self.actions, @reset_password).set_enabled(false);
|
||||
get_widget!(self.builder, libhandy::ActionRow, @current_password_row).hide();
|
||||
self.has_set_password.set(false);
|
||||
let self_ = imp::PasswordPage::from_instance(self);
|
||||
|
||||
// get_action!(self.actions, @close_page).activate(None);
|
||||
// get_action!(self.actions, @save_password).set_enabled(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) {
|
||||
get_widget!(self.builder, gtk::PasswordEntry, current_password_entry);
|
||||
get_widget!(self.builder, gtk::PasswordEntry, password_entry);
|
||||
get_widget!(self.builder, gtk::PasswordEntry, confirm_password_entry);
|
||||
let self_ = imp::PasswordPage::from_instance(self);
|
||||
|
||||
let current_password = current_password_entry.get_text().unwrap();
|
||||
let password = password_entry.get_text().unwrap();
|
||||
let current_password = self_.current_password_entry.get().get_text().unwrap();
|
||||
let password = self_.password_entry.get().get_text().unwrap();
|
||||
|
||||
if Keyring::has_set_password().unwrap_or(false) {
|
||||
if !Keyring::is_current_password(¤t_password).unwrap_or(false) {
|
||||
|
@ -109,14 +169,14 @@ impl PasswordPage {
|
|||
}
|
||||
|
||||
if Keyring::set_password(&password).is_ok() {
|
||||
get_widget!(self.builder, libhandy::ActionRow, @current_password_row).show();
|
||||
current_password_entry.set_text("");
|
||||
password_entry.set_text("");
|
||||
confirm_password_entry.set_text("");
|
||||
get_action!(self.actions, @save_password).set_enabled(false);
|
||||
get_action!(self.actions, @reset_password).set_enabled(true);
|
||||
get_action!(self.actions, @close_page).activate(None);
|
||||
self.has_set_password.set(true);
|
||||
self_.current_password_row.get().show();
|
||||
self_.current_password_entry.get().set_text("");
|
||||
self_.password_entry.get().set_text("");
|
||||
self_.confirm_password_entry.get().set_text("");
|
||||
// get_action!(self.actions, @save_password).set_enabled(false);
|
||||
// get_action!(self.actions, @reset_password).set_enabled(true);
|
||||
// get_action!(self.actions, @close_page).activate(None);
|
||||
self_.has_set_password.set(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,20 +1,17 @@
|
|||
use super::window::PreferencesAction;
|
||||
use crate::models::{Algorithm, Provider};
|
||||
use glib::translate::ToGlib;
|
||||
use glib::Sender;
|
||||
use gtk::prelude::*;
|
||||
use libhandy::ComboRowExt;
|
||||
use libhandy::{ComboRowExt, EnumListModelExt};
|
||||
use std::rc::Rc;
|
||||
|
||||
pub struct ProviderPage {
|
||||
pub widget: gtk::Box,
|
||||
builder: gtk::Builder,
|
||||
sender: Sender<PreferencesAction>,
|
||||
algorithms_model: libhandy::EnumListModel,
|
||||
}
|
||||
|
||||
impl ProviderPage {
|
||||
pub fn new(sender: Sender<PreferencesAction>) -> Rc<Self> {
|
||||
pub fn new() -> Rc<Self> {
|
||||
let builder = gtk::Builder::from_resource(
|
||||
"/com/belmoussaoui/Authenticator/preferences_provider_page.ui",
|
||||
);
|
||||
|
@ -23,7 +20,6 @@ impl ProviderPage {
|
|||
|
||||
let page = Rc::new(Self {
|
||||
widget: provider_page,
|
||||
sender,
|
||||
builder,
|
||||
algorithms_model,
|
||||
});
|
||||
|
@ -49,7 +45,6 @@ impl ProviderPage {
|
|||
.find_position(provider.algorithm().to_glib()),
|
||||
);
|
||||
|
||||
let p = provider.clone();
|
||||
/*let sender = self.sender.clone();
|
||||
spawn!(async move {
|
||||
if let Ok(file) = p.favicon().await {
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
use super::window::PreferencesAction;
|
||||
use crate::models::{Provider, ProvidersModel};
|
||||
use gio::ListModelExt;
|
||||
use glib::Sender;
|
||||
use gtk::prelude::*;
|
||||
use std::rc::Rc;
|
||||
|
||||
|
@ -9,11 +7,10 @@ pub struct ProvidersPage {
|
|||
pub widget: libhandy::PreferencesPage,
|
||||
builder: gtk::Builder,
|
||||
model: Rc<ProvidersModel>,
|
||||
sender: Sender<PreferencesAction>,
|
||||
}
|
||||
|
||||
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(
|
||||
"/com/belmoussaoui/Authenticator/preferences_providers_page.ui",
|
||||
);
|
||||
|
@ -23,7 +20,6 @@ impl ProvidersPage {
|
|||
widget: providers_page,
|
||||
builder,
|
||||
model,
|
||||
sender,
|
||||
});
|
||||
page.init();
|
||||
page
|
||||
|
@ -44,13 +40,15 @@ impl ProvidersPage {
|
|||
let selection_model = gtk::NoSelection::new(Some(&self.model.model));
|
||||
providers_list.set_model(Some(&selection_model));
|
||||
|
||||
providers_list.connect_activate(
|
||||
clone!(@strong self.sender as sender => move|listview, pos|{
|
||||
let model = listview.get_model().unwrap();
|
||||
let provider = model.get_object(pos).unwrap().downcast::<Provider>().unwrap();
|
||||
send!(sender, PreferencesAction::EditProvider(provider));
|
||||
}),
|
||||
);
|
||||
providers_list.connect_activate(move |listview, pos| {
|
||||
let model = listview.get_model().unwrap();
|
||||
let provider = model
|
||||
.get_object(pos)
|
||||
.unwrap()
|
||||
.downcast::<Provider>()
|
||||
.unwrap();
|
||||
// send!(sender, PreferencesAction::EditProvider(provider));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,118 +1,131 @@
|
|||
use super::password_page::PasswordPage;
|
||||
use super::provider_page::ProviderPage;
|
||||
use super::providers_page::ProvidersPage;
|
||||
use crate::config;
|
||||
use crate::models::{Provider, ProvidersModel};
|
||||
use gio::prelude::*;
|
||||
use gio::ActionMapExt;
|
||||
use gio::SettingsExt;
|
||||
use glib::{Receiver, Sender};
|
||||
use gtk::prelude::*;
|
||||
use gio::{subclass::ObjectSubclass, SettingsExt};
|
||||
use glib::subclass::prelude::*;
|
||||
use glib::{glib_object_subclass, glib_wrapper};
|
||||
use gtk::{prelude::*, CompositeTemplate};
|
||||
use libhandy::PreferencesWindowExt;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
pub enum PreferencesAction {
|
||||
EditProvider(Provider),
|
||||
mod imp {
|
||||
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 {
|
||||
pub widget: libhandy::PreferencesWindow,
|
||||
builder: gtk::Builder,
|
||||
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>>>,
|
||||
glib_wrapper! {
|
||||
pub struct PreferencesWindow(ObjectSubclass<imp::PreferencesWindow>)
|
||||
@extends gtk::Widget, gtk::Window, libhandy::Window, libhandy::PreferencesWindow;
|
||||
}
|
||||
|
||||
impl PreferencesWindow {
|
||||
pub fn new(providers_model: Rc<ProvidersModel>) -> Rc<Self> {
|
||||
let builder = gtk::Builder::from_resource("/com/belmoussaoui/Authenticator/preferences.ui");
|
||||
get_widget!(builder, libhandy::PreferencesWindow, preferences_window);
|
||||
let settings = gio::Settings::new(config::APP_ID);
|
||||
let actions = gio::SimpleActionGroup::new();
|
||||
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
|
||||
pub fn new() -> Self {
|
||||
glib::Object::new(Self::static_type(), &[])
|
||||
.expect("Failed to create PreferencesWindow")
|
||||
.downcast::<PreferencesWindow>()
|
||||
.expect("Created object is of wrong type")
|
||||
}
|
||||
|
||||
fn init(&self, preferences: Rc<Self>) {
|
||||
get_widget!(self.builder, gtk::Switch, dark_theme_switch);
|
||||
self.settings.bind(
|
||||
fn setup_widgets(&self) {
|
||||
let self_ = imp::PreferencesWindow::from_instance(self);
|
||||
|
||||
self_.settings.bind(
|
||||
"dark-theme",
|
||||
&dark_theme_switch,
|
||||
&self_.dark_theme.get(),
|
||||
"active",
|
||||
gio::SettingsBindFlags::DEFAULT,
|
||||
);
|
||||
|
||||
get_widget!(self.builder, gtk::Switch, auto_lock_switch);
|
||||
self.settings.bind(
|
||||
self_.settings.bind(
|
||||
"auto-lock",
|
||||
&auto_lock_switch,
|
||||
&self_.auto_lock.get(),
|
||||
"active",
|
||||
gio::SettingsBindFlags::DEFAULT,
|
||||
);
|
||||
|
||||
get_widget!(self.builder, gtk::SpinButton, lock_timeout_spin_btn);
|
||||
auto_lock_switch
|
||||
.bind_property("active", &lock_timeout_spin_btn, "sensitive")
|
||||
self_
|
||||
.auto_lock
|
||||
.get()
|
||||
.bind_property("active", &self_.lock_timeout.get(), "sensitive")
|
||||
.flags(glib::BindingFlags::DEFAULT | glib::BindingFlags::SYNC_CREATE)
|
||||
.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) {
|
||||
let self_ = imp::PreferencesWindow::from_instance(self);
|
||||
|
||||
action!(
|
||||
self.actions,
|
||||
self_.actions,
|
||||
"show_password_page",
|
||||
clone!(@strong self.builder as builder,
|
||||
@strong self.password_page.widget as password_page,
|
||||
@strong self.widget as widget => move |_, _| {
|
||||
widget.present_subpage(&password_page);
|
||||
clone!(@weak self as win, @weak self_.password_page as password_page => move |_, _| {
|
||||
win.present_subpage(&password_page);
|
||||
})
|
||||
);
|
||||
action!(
|
||||
self.actions,
|
||||
self_.actions,
|
||||
"close_page",
|
||||
clone!(@strong self.builder as builder,
|
||||
@strong self.widget as widget => move |_, _| {
|
||||
widget.close_subpage();
|
||||
clone!(@weak self as win => move |_, _| {
|
||||
win.close_subpage();
|
||||
})
|
||||
);
|
||||
self.widget
|
||||
.insert_action_group("preferences", Some(&self.actions));
|
||||
self.insert_action_group("preferences", Some(&self_.actions));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,69 +1,107 @@
|
|||
use crate::application::Action;
|
||||
use crate::models::{Provider, ProvidersModel};
|
||||
use crate::widgets::providers::ProviderRow;
|
||||
use gio::ListModelExt;
|
||||
use glib::Sender;
|
||||
use gtk::prelude::*;
|
||||
use gio::{subclass::ObjectSubclass, ListModelExt};
|
||||
use glib::subclass::prelude::*;
|
||||
use glib::{glib_object_subclass, glib_wrapper};
|
||||
use gtk::{prelude::*, CompositeTemplate};
|
||||
use std::rc::Rc;
|
||||
|
||||
pub struct ProvidersList {
|
||||
pub widget: gtk::Box,
|
||||
builder: gtk::Builder,
|
||||
pub filter_model: gtk::FilterListModel,
|
||||
mod imp {
|
||||
use super::*;
|
||||
use glib::subclass;
|
||||
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 {
|
||||
pub fn new() -> Self {
|
||||
let builder =
|
||||
gtk::Builder::from_resource("/com/belmoussaoui/Authenticator/providers_list.ui");
|
||||
get_widget!(builder, gtk::Box, providers_box);
|
||||
|
||||
let filter_model = gtk::FilterListModel::new(gtk::NONE_FILTER_LIST_MODEL, gtk::NONE_FILTER);
|
||||
|
||||
let list = Self {
|
||||
widget: providers_box,
|
||||
builder,
|
||||
filter_model,
|
||||
};
|
||||
list
|
||||
glib::Object::new(Self::static_type(), &[])
|
||||
.expect("Failed to create ProvidersList")
|
||||
.downcast::<ProvidersList>()
|
||||
.expect("Created object is of wrong type")
|
||||
}
|
||||
|
||||
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 provider = object.downcast_ref::<Provider>().unwrap();
|
||||
provider.has_accounts()
|
||||
})));
|
||||
self.filter_model.set_filter(Some(&accounts_filter));
|
||||
self.filter_model.set_model(Some(&model.model));
|
||||
self_.filter_model.set_filter(Some(&accounts_filter));
|
||||
self_.filter_model.set_model(Some(&model.model));
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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 provider = object.downcast_ref::<Provider>().unwrap();
|
||||
provider.search_accounts(text.clone());
|
||||
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>) {
|
||||
get_widget!(self.builder, gtk::ListBox, providers_list);
|
||||
fn setup_widgets(&self) {
|
||||
let self_ = imp::ProvidersList::from_instance(self);
|
||||
|
||||
providers_list.bind_model(
|
||||
Some(&self.filter_model),
|
||||
Some(Box::new(clone!(@strong sender => move |obj| {
|
||||
let provider = obj.downcast_ref::<Provider>().unwrap();
|
||||
let row = ProviderRow::new(provider, sender.clone());
|
||||
row.widget.upcast::<gtk::Widget>()
|
||||
}))),
|
||||
self_.providers_list.get().bind_model(
|
||||
Some(&self_.filter_model),
|
||||
Some(Box::new(move |obj| {
|
||||
let provider = obj.clone().downcast::<Provider>().unwrap();
|
||||
ProviderRow::new(provider).upcast::<gtk::Widget>()
|
||||
})),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,52 +1,123 @@
|
|||
use crate::application::Action;
|
||||
use crate::models::{Account, Provider};
|
||||
use crate::widgets::accounts::AccountRow;
|
||||
use glib::Sender;
|
||||
use gtk::prelude::*;
|
||||
use gio::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> {
|
||||
pub widget: gtk::ListBoxRow,
|
||||
provider: &'a Provider,
|
||||
builder: gtk::Builder,
|
||||
sender: Sender<Action>,
|
||||
}
|
||||
static PROPERTIES: [subclass::Property; 1] = [subclass::Property("provider", |name| {
|
||||
glib::ParamSpec::object(
|
||||
name,
|
||||
"Provider",
|
||||
"The accounts provider",
|
||||
Provider::static_type(),
|
||||
glib::ParamFlags::READWRITE | glib::ParamFlags::CONSTRUCT_ONLY,
|
||||
)
|
||||
})];
|
||||
|
||||
impl<'a> ProviderRow<'a> {
|
||||
pub fn new(provider: &'a Provider, sender: Sender<Action>) -> Self {
|
||||
let builder =
|
||||
gtk::Builder::from_resource("/com/belmoussaoui/Authenticator/provider_row.ui");
|
||||
get_widget!(builder, gtk::ListBoxRow, provider_row);
|
||||
|
||||
let row = Self {
|
||||
widget: provider_row,
|
||||
builder,
|
||||
sender,
|
||||
provider,
|
||||
};
|
||||
row.init();
|
||||
row
|
||||
#[derive(CompositeTemplate)]
|
||||
pub struct ProviderRow {
|
||||
pub provider: RefCell<Option<Provider>>,
|
||||
#[template_child(id = "name_label")]
|
||||
pub name_label: TemplateChild<gtk::Label>,
|
||||
#[template_child(id = "accounts_list")]
|
||||
pub accounts_list: TemplateChild<gtk::ListBox>,
|
||||
}
|
||||
|
||||
fn init(&self) {
|
||||
get_widget!(self.builder, gtk::Label, name);
|
||||
impl ObjectSubclass for ProviderRow {
|
||||
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
|
||||
.bind_property("name", &name, "label")
|
||||
glib_object_subclass!();
|
||||
|
||||
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)
|
||||
.build();
|
||||
|
||||
get_widget!(self.builder, gtk::ListBox, accounts_list);
|
||||
accounts_list.bind_model(
|
||||
Some(self.provider.accounts()),
|
||||
Some(Box::new(
|
||||
clone!(@strong self.sender as sender => move |account: &glib::Object| {
|
||||
let account: &Account = account
|
||||
.downcast_ref::<Account>()
|
||||
.unwrap();
|
||||
let row = AccountRow::new(account, sender.clone());
|
||||
row.widget.upcast::<gtk::Widget>()
|
||||
}),
|
||||
)),
|
||||
self_.accounts_list.get().bind_model(
|
||||
Some(self.provider().accounts()),
|
||||
Some(Box::new(move |account: &glib::Object| {
|
||||
let account = account.clone().downcast::<Account>().unwrap();
|
||||
AccountRow::new(account).upcast::<gtk::Widget>()
|
||||
})),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,11 +5,11 @@ use crate::models::ProvidersModel;
|
|||
use crate::widgets::providers::ProvidersList;
|
||||
use crate::window_state;
|
||||
use gio::prelude::*;
|
||||
use glib::subclass;
|
||||
use gio::subclass::ObjectSubclass;
|
||||
use glib::subclass::prelude::*;
|
||||
use glib::{glib_object_subclass, glib_wrapper};
|
||||
use glib::{signal::Inhibit, Sender};
|
||||
use gtk::prelude::*;
|
||||
use gtk::subclass::prelude::{WidgetImpl, WindowImpl};
|
||||
use gtk::{prelude::*, CompositeTemplate};
|
||||
use libhandy::prelude::*;
|
||||
use std::rc::Rc;
|
||||
|
||||
|
@ -19,45 +19,75 @@ pub enum View {
|
|||
Accounts,
|
||||
}
|
||||
|
||||
pub struct WindowPrivate {
|
||||
builder: gtk::Builder,
|
||||
settings: gio::Settings,
|
||||
pub providers: Rc<ProvidersList>,
|
||||
}
|
||||
mod imp {
|
||||
use super::*;
|
||||
use glib::subclass;
|
||||
use gtk::subclass::prelude::*;
|
||||
use libhandy::subclass::application_window::ApplicationWindowImpl as HdyApplicationWindowImpl;
|
||||
|
||||
impl ObjectSubclass for WindowPrivate {
|
||||
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>;
|
||||
#[derive(Debug, CompositeTemplate)]
|
||||
pub struct Window {
|
||||
pub settings: gio::Settings,
|
||||
pub providers: ProvidersList,
|
||||
#[template_child(id = "search_entry")]
|
||||
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 {
|
||||
let builder = gtk::Builder::from_resource("/com/belmoussaoui/Authenticator/window.ui");
|
||||
let settings = gio::Settings::new(APP_ID);
|
||||
let providers = Rc::new(ProvidersList::new());
|
||||
Self {
|
||||
builder,
|
||||
settings,
|
||||
providers,
|
||||
glib_object_subclass!();
|
||||
|
||||
fn new() -> Self {
|
||||
let settings = gio::Settings::new(APP_ID);
|
||||
let providers = ProvidersList::new();
|
||||
Self {
|
||||
settings,
|
||||
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! {
|
||||
pub struct Window(ObjectSubclass<WindowPrivate>)
|
||||
pub struct Window(ObjectSubclass<imp::Window>)
|
||||
@extends gtk::Widget, gtk::Window, gtk::ApplicationWindow, libhandy::ApplicationWindow, gio::ActionMap;
|
||||
}
|
||||
|
||||
|
@ -72,7 +102,7 @@ impl Window {
|
|||
if PROFILE == "Devel" {
|
||||
window.get_style_context().add_class("devel");
|
||||
}
|
||||
window.init(model, sender.clone());
|
||||
window.init(model);
|
||||
window.setup_actions(app, sender.clone());
|
||||
window.set_view(View::Login); // Start by default in an empty state
|
||||
window.setup_signals(app, sender);
|
||||
|
@ -80,22 +110,25 @@ impl Window {
|
|||
}
|
||||
|
||||
pub fn set_view(&self, view: View) {
|
||||
let self_ = WindowPrivate::from_instance(self);
|
||||
get_widget!(self_.builder, libhandy::Leaflet, deck);
|
||||
let self_ = imp::Window::from_instance(self);
|
||||
match view {
|
||||
View::Login => {
|
||||
deck.set_visible_child_name("login");
|
||||
self_.deck.get().set_visible_child_name("login");
|
||||
}
|
||||
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>) {
|
||||
let self_ = WindowPrivate::from_instance(self);
|
||||
pub fn providers(&self) -> ProvidersList {
|
||||
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.init(sender.clone());
|
||||
// load latest window state
|
||||
window_state::load(&self, &self_.settings);
|
||||
// save window state on delete event
|
||||
|
@ -110,31 +143,30 @@ impl Window {
|
|||
get_widget!(builder, gtk::ShortcutsWindow, shortcuts);
|
||||
self.set_help_overlay(Some(&shortcuts));
|
||||
|
||||
get_widget!(self_.builder, gtk::Box, container);
|
||||
container.append(&self_.providers.widget);
|
||||
self_.container.get().append(&self_.providers);
|
||||
|
||||
get_widget!(self_.builder, gtk::SearchBar, search_bar);
|
||||
get_widget!(self_.builder, gtk::ToggleButton, search_btn);
|
||||
search_btn
|
||||
.bind_property("active", &search_bar, "search-mode-enabled")
|
||||
self_
|
||||
.search_btn
|
||||
.get()
|
||||
.bind_property("active", &self_.search_bar.get(), "search-mode-enabled")
|
||||
.flags(glib::BindingFlags::BIDIRECTIONAL | glib::BindingFlags::SYNC_CREATE)
|
||||
.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| {
|
||||
let text = entry.get_text().unwrap().to_string();
|
||||
providers.search(text);
|
||||
}),
|
||||
);
|
||||
|
||||
search_entry.connect_stop_search(clone!(@strong search_btn => move |entry| {
|
||||
entry.set_text("");
|
||||
search_btn.set_active(false);
|
||||
}));
|
||||
|
||||
get_widget!(self_.builder, libhandy::Leaflet, deck);
|
||||
libhandy::ApplicationWindowExt::set_child(self, Some(&deck));
|
||||
let search_btn = self_.search_btn.get();
|
||||
self_
|
||||
.search_entry
|
||||
.get()
|
||||
.connect_stop_search(clone!(@weak search_btn => move |entry| {
|
||||
entry.set_text("");
|
||||
search_btn.set_active(false);
|
||||
}));
|
||||
|
||||
let gtk_settings = gtk::Settings::get_default().unwrap();
|
||||
self_.settings.bind(
|
||||
|
@ -147,12 +179,12 @@ impl Window {
|
|||
}
|
||||
|
||||
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!(
|
||||
self,
|
||||
"search",
|
||||
clone!(@strong self_.builder as builder => move |_,_| {
|
||||
get_widget!(builder, gtk::ToggleButton, search_btn);
|
||||
clone!(@weak search_btn => move |_,_| {
|
||||
search_btn.set_active(!search_btn.get_active());
|
||||
})
|
||||
);
|
||||
|
@ -165,11 +197,11 @@ impl Window {
|
|||
})
|
||||
);
|
||||
|
||||
let password_entry = self_.password_entry.get();
|
||||
action!(
|
||||
self,
|
||||
"unlock",
|
||||
clone!(@strong sender, @strong self_.builder as builder, @strong app => move |_, _| {
|
||||
get_widget!(builder, gtk::PasswordEntry, password_entry);
|
||||
clone!(@strong sender, @weak password_entry, @strong app => move |_, _| {
|
||||
let password = password_entry.get_text().unwrap();
|
||||
if Keyring::is_current_password(&password).unwrap() {
|
||||
password_entry.set_text("");
|
||||
|
|
Loading…
Add table
Reference in a new issue