mirror of
https://gitlab.gnome.org/World/Authenticator.git
synced 2025-03-04 08:44: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"
|
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"
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 |_: >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 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();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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());
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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};
|
||||||
|
|
|
@ -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(¤t_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() {
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>()
|
})),
|
||||||
}))),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>()
|
|
||||||
}),
|
|
||||||
)),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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("");
|
||||||
|
|
Loading…
Add table
Reference in a new issue