mirror of
https://gitlab.gnome.org/World/Authenticator.git
synced 2025-03-04 08:44:40 +01:00
widgets: Add a new ProviderEntryRow
That does completion without using GtkEntryCompletion which is deprecated Fixes #335
This commit is contained in:
parent
5abb9c6864
commit
961040be8e
15 changed files with 527 additions and 161 deletions
|
@ -9,6 +9,7 @@
|
|||
<file compressed="true" preprocess="xml-stripblanks" alias="account_row.ui">resources/ui/account_row.ui</file>
|
||||
|
||||
<!-- Providers -->
|
||||
<file compressed="true" preprocess="xml-stripblanks" alias="provider_entry_row.ui">resources/ui/provider_entry_row.ui</file>
|
||||
<file compressed="true" preprocess="xml-stripblanks" alias="provider_image.ui">resources/ui/provider_image.ui</file>
|
||||
<file compressed="true" preprocess="xml-stripblanks" alias="provider_page.ui">resources/ui/provider_page.ui</file>
|
||||
<file compressed="true" preprocess="xml-stripblanks" alias="provider_row.ui">resources/ui/provider_row.ui</file>
|
||||
|
|
|
@ -101,19 +101,10 @@
|
|||
<object class="GtkListBox">
|
||||
<property name="selection-mode">none</property>
|
||||
<child>
|
||||
<object class="AdwActionRow">
|
||||
<property name="activatable-widget">provider_entry</property>
|
||||
<property name="title" translatable="yes">Provider</property>
|
||||
<property name="subtitle" translatable="yes">Token issuer</property>
|
||||
<child>
|
||||
<object class="GtkEntry" id="provider_entry">
|
||||
<property name="halign">end</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="completion">provider_completion</property>
|
||||
<property name="enable-emoji-completion">True</property>
|
||||
</object>
|
||||
</child>
|
||||
<object class="ProviderEntryRow" id="provider_entry">
|
||||
<signal name="notify::provider" handler="on_provider_changed" swapped="true" />
|
||||
<signal name="create" handler="on_provider_create" swapped="true" />
|
||||
<property name="model" bind-source="AccountAddDialog" bind-property="model" />
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
|
@ -264,23 +255,4 @@
|
|||
</object>
|
||||
</child>
|
||||
</template>
|
||||
<object class="GtkEntryCompletion" id="provider_completion">
|
||||
<property name="minimum-key-length">1</property>
|
||||
<property name="text-column">1</property>
|
||||
<property name="inline-selection">True</property>
|
||||
<signal name="match-selected" handler="match_selected" swapped="true" />
|
||||
<signal name="no-matches" handler="no_matches_selected" swapped="true" />
|
||||
<child>
|
||||
<object class="GtkCellRendererText" />
|
||||
<attributes>
|
||||
<attribute name="text">1</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkSizeGroup">
|
||||
<widgets>
|
||||
<widget name="provider_entry"/>
|
||||
<widget name="counter_spinbutton"/>
|
||||
</widgets>
|
||||
</object>
|
||||
</interface>
|
||||
|
|
|
@ -1,17 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<interface>
|
||||
<object class="GtkEntryCompletion" id="provider_completion">
|
||||
<property name="minimum-key-length">1</property>
|
||||
<property name="text-column">1</property>
|
||||
<property name="inline-selection">True</property>
|
||||
<signal name="match-selected" handler="provider_match_selected" swapped="true" />
|
||||
<child>
|
||||
<object class="GtkCellRendererText" />
|
||||
<attributes>
|
||||
<attribute name="text">1</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkAdjustment" id="counter_adjustment">
|
||||
<property name="lower">0</property>
|
||||
<property name="upper">4294967295</property>
|
||||
|
@ -98,17 +86,10 @@
|
|||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwActionRow">
|
||||
<property name="title" translatable="yes">Provider</property>
|
||||
<child>
|
||||
<object class="GtkEntry" id="provider_entry">
|
||||
<property name="halign">end</property>
|
||||
<property name="valign">center</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="completion">provider_completion</property>
|
||||
<property name="enable-emoji-completion">True</property>
|
||||
</object>
|
||||
</child>
|
||||
<object class="ProviderEntryRow" id="provider_entry">
|
||||
<signal name="notify::provider" handler="on_provider_notify" swapped="true" />
|
||||
<signal name="create" handler="on_provider_create" swapped="true" />
|
||||
<property name="model" bind-source="AccountDetailsPage" bind-property="model" />
|
||||
</object>
|
||||
</child>
|
||||
<style>
|
||||
|
|
66
data/resources/ui/provider_entry_row.ui
Normal file
66
data/resources/ui/provider_entry_row.ui
Normal file
|
@ -0,0 +1,66 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<interface>
|
||||
<template parent="AdwEntryRow" class="ProviderEntryRow">
|
||||
<property name="title" translatable="yes">Provider</property>
|
||||
<child>
|
||||
<object class="GtkEventControllerKey">
|
||||
<signal name="key-pressed" handler="on_key_pressed" swapped="yes"/>
|
||||
</object>
|
||||
</child>
|
||||
</template>
|
||||
<object class="GtkPopover" id="popover">
|
||||
<property name="halign">start</property>
|
||||
<property name="autohide">False</property>
|
||||
<property name="has-arrow">False</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="width-request">360</property>
|
||||
<child>
|
||||
<object class="GtkStack" id="stack">
|
||||
<child>
|
||||
<object class="GtkStackPage">
|
||||
<property name="name">empty</property>
|
||||
<property name="child">
|
||||
<object class="AdwStatusPage">
|
||||
<property name="title" translatable="yes">No Results Found</property>
|
||||
<property name="icon-name">system-search-symbolic</property>
|
||||
<property name="child">
|
||||
<object class="GtkButton">
|
||||
<property name="label" translatable="yes">New Provider</property>
|
||||
<property name="action-name">entry.create</property>
|
||||
<property name="halign">center</property>
|
||||
<style>
|
||||
<class name="suggested-action" />
|
||||
<class name="pill" />
|
||||
</style>
|
||||
</object>
|
||||
</property>
|
||||
<style>
|
||||
<class name="compact" />
|
||||
</style>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkStackPage">
|
||||
<property name="name">list</property>
|
||||
<property name="child">
|
||||
<object class="GtkScrolledWindow">
|
||||
<property name="max-content-height">400</property>
|
||||
<property name="propagate-natural-height">True</property>
|
||||
<property name="hscrollbar-policy">never</property>
|
||||
<property name="vscrollbar-policy">automatic</property>
|
||||
<child>
|
||||
<object class="GtkListView" id="list_view">
|
||||
<property name="single-click-activate">True</property>
|
||||
<signal name="activate" handler="on_provider_activated" swapped="yes"/>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</interface>
|
|
@ -13,7 +13,7 @@
|
|||
<object class="AdwLeafletPage">
|
||||
<property name="name">providers</property>
|
||||
<property name="child">
|
||||
<object class="GtkBox">
|
||||
<object class="GtkBox" id="providers_page">
|
||||
<property name="width-request">200</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
|
@ -122,7 +122,7 @@
|
|||
<object class="AdwLeafletPage">
|
||||
<property name="navigatable">False</property>
|
||||
<property name="child">
|
||||
<object class="GtkBox">
|
||||
<object class="GtkBox" id="separator_page">
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkWindowHandle" id="header_separator">
|
||||
|
|
|
@ -291,6 +291,7 @@
|
|||
<object class="AccountDetailsPage" id="account_details">
|
||||
<signal name="provider-changed" handler="on_provider_changed" swapped="true" />
|
||||
<signal name="removed" handler="on_account_removed" swapped="true" />
|
||||
<property name="model" bind-source="Window" bind-property="model" />
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
|
|
|
@ -121,7 +121,7 @@ mod imp {
|
|||
let model = &app.imp().model;
|
||||
let window = app.active_window();
|
||||
let providers = ProvidersDialog::new(model);
|
||||
providers.connect_changed(clone!(@weak window => move |_| {
|
||||
providers.connect_changed(clone!(@weak window => move |_, _| {
|
||||
window.providers().refilter();
|
||||
}));
|
||||
providers.set_transient_for(Some(&window));
|
||||
|
|
|
@ -123,20 +123,6 @@ impl ProvidersModel {
|
|||
found
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
pub fn completion_model(&self) -> gtk::ListStore {
|
||||
let store = gtk::ListStore::new(&[u32::static_type(), String::static_type()]);
|
||||
for pos in 0..self.n_items() {
|
||||
let obj = self.item(pos).unwrap();
|
||||
let provider = obj.downcast_ref::<Provider>().unwrap();
|
||||
store.set(
|
||||
&store.append(),
|
||||
&[(0, &provider.id()), (1, &provider.name())],
|
||||
);
|
||||
}
|
||||
store
|
||||
}
|
||||
|
||||
pub fn append(&self, provider: &Provider) {
|
||||
let pos = {
|
||||
let mut data = self.imp().0.borrow_mut();
|
||||
|
|
|
@ -5,13 +5,15 @@ use gtk::{
|
|||
gio,
|
||||
glib::{self, clone},
|
||||
prelude::*,
|
||||
Inhibit,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
backup::RestorableItem,
|
||||
models::{Account, OTPUri, Provider, ProvidersModel, OTP},
|
||||
widgets::{providers::ProviderPage, screenshot, Camera, ErrorRevealer, ProviderImage, UrlRow},
|
||||
widgets::{
|
||||
providers::{ProviderEntryRow, ProviderPage},
|
||||
screenshot, Camera, ErrorRevealer, ProviderImage, UrlRow,
|
||||
},
|
||||
};
|
||||
|
||||
mod imp {
|
||||
|
@ -50,7 +52,7 @@ mod imp {
|
|||
#[template_child]
|
||||
pub digits_label: TemplateChild<gtk::Label>,
|
||||
#[template_child]
|
||||
pub provider_entry: TemplateChild<gtk::Entry>,
|
||||
pub provider_entry: TemplateChild<ProviderEntryRow>,
|
||||
#[template_child]
|
||||
pub method_label: TemplateChild<gtk::Label>,
|
||||
#[template_child]
|
||||
|
@ -60,8 +62,6 @@ mod imp {
|
|||
#[template_child]
|
||||
pub period_row: TemplateChild<adw::ActionRow>,
|
||||
#[template_child]
|
||||
pub provider_completion: TemplateChild<gtk::EntryCompletion>,
|
||||
#[template_child]
|
||||
pub error_revealer: TemplateChild<ErrorRevealer>,
|
||||
#[template_child]
|
||||
pub provider_page: TemplateChild<ProviderPage>,
|
||||
|
@ -133,10 +133,8 @@ mod imp {
|
|||
|
||||
fn constructed(&self) {
|
||||
self.parent_constructed();
|
||||
self.obj().action_set_enabled("add.save", false);
|
||||
|
||||
self.provider_completion
|
||||
.set_model(Some(&self.model.get().unwrap().completion_model()));
|
||||
let obj = self.obj();
|
||||
obj.action_set_enabled("add.save", false);
|
||||
}
|
||||
}
|
||||
impl WidgetImpl for AccountAddDialog {}
|
||||
|
@ -179,36 +177,6 @@ impl AccountAddDialog {
|
|||
self.action_set_enabled("add.save", is_valid);
|
||||
}
|
||||
|
||||
#[template_callback]
|
||||
fn match_selected(&self, store: gtk::ListStore, iter: gtk::TreeIter) -> Inhibit {
|
||||
let provider_id = store.get::<u32>(&iter, 0);
|
||||
let provider = self.model().find_by_id(provider_id);
|
||||
self.set_provider(provider);
|
||||
|
||||
Inhibit(false)
|
||||
}
|
||||
|
||||
#[template_callback]
|
||||
fn no_matches_selected(&self, completion: gtk::EntryCompletion) {
|
||||
// in case the provider doesn't exists, let the user create a new one by showing
|
||||
// a dialog for that TODO: replace this whole completion provider thing
|
||||
// with a custom widget
|
||||
let imp = self.imp();
|
||||
let entry = completion.entry().unwrap();
|
||||
|
||||
imp.deck.set_visible_child_name("create-provider");
|
||||
imp.provider_page
|
||||
.imp()
|
||||
.back_btn
|
||||
.set_action_name(Some("add.previous"));
|
||||
imp.provider_page.imp().revealer.set_reveal_child(true);
|
||||
imp.provider_page.set_provider(None);
|
||||
|
||||
let name_entry = imp.provider_page.name_entry();
|
||||
name_entry.set_text(&entry.text());
|
||||
name_entry.set_position(entry.cursor_position());
|
||||
}
|
||||
|
||||
#[template_callback]
|
||||
fn deck_visible_child_name_notify(&self, _pspec: glib::ParamSpec, deck: adw::Leaflet) {
|
||||
if deck.visible_child_name().as_deref() != Some("camera") {
|
||||
|
@ -233,14 +201,34 @@ impl AccountAddDialog {
|
|||
}
|
||||
}
|
||||
|
||||
#[template_callback]
|
||||
fn on_provider_changed(&self) {
|
||||
let provider = self.imp().provider_entry.provider();
|
||||
self.set_provider(provider);
|
||||
}
|
||||
|
||||
#[template_callback]
|
||||
fn on_provider_create(&self, entry: &ProviderEntryRow) {
|
||||
let imp = self.imp();
|
||||
|
||||
imp.deck.set_visible_child_name("create-provider");
|
||||
imp.provider_page
|
||||
.imp()
|
||||
.back_btn
|
||||
.set_action_name(Some("add.previous"));
|
||||
imp.provider_page.imp().revealer.set_reveal_child(true);
|
||||
imp.provider_page.set_provider(None);
|
||||
|
||||
let name_entry = imp.provider_page.name_entry();
|
||||
name_entry.set_text(&entry.text());
|
||||
}
|
||||
|
||||
#[template_callback]
|
||||
fn provider_created(&self, provider: Provider, _page: ProviderPage) {
|
||||
let imp = self.imp();
|
||||
let model = self.model();
|
||||
model.append(&provider);
|
||||
|
||||
imp.provider_completion
|
||||
.set_model(Some(&model.completion_model()));
|
||||
self.set_provider(Some(provider));
|
||||
imp.deck.set_visible_child_name("main");
|
||||
}
|
||||
|
@ -328,9 +316,7 @@ impl AccountAddDialog {
|
|||
let imp = self.imp();
|
||||
if let Some(provider) = provider {
|
||||
imp.more_list.set_visible(true);
|
||||
imp.provider_entry.set_text(&provider.name());
|
||||
imp.period_label.set_text(&provider.period().to_string());
|
||||
|
||||
imp.image.set_provider(Some(&provider));
|
||||
|
||||
imp.method_label
|
||||
|
|
|
@ -9,7 +9,7 @@ use gtk::{
|
|||
use super::{QRCodeData, QRCodePaintable};
|
||||
use crate::{
|
||||
models::{Account, Provider, ProvidersModel},
|
||||
widgets::UrlRow,
|
||||
widgets::{providers::ProviderEntryRow, ProvidersDialog, UrlRow},
|
||||
};
|
||||
mod imp {
|
||||
use std::cell::RefCell;
|
||||
|
@ -19,8 +19,9 @@ mod imp {
|
|||
|
||||
use super::*;
|
||||
|
||||
#[derive(Default, gtk::CompositeTemplate)]
|
||||
#[derive(Default, gtk::CompositeTemplate, glib::Properties)]
|
||||
#[template(resource = "/com/belmoussaoui/Authenticator/account_details_page.ui")]
|
||||
#[properties(wrapper_type = super::AccountDetailsPage)]
|
||||
pub struct AccountDetailsPage {
|
||||
#[template_child]
|
||||
pub website_row: TemplateChild<UrlRow>,
|
||||
|
@ -49,11 +50,10 @@ mod imp {
|
|||
pub qrcode_paintable: QRCodePaintable,
|
||||
pub account: RefCell<Option<Account>>,
|
||||
#[template_child]
|
||||
pub provider_completion: TemplateChild<gtk::EntryCompletion>,
|
||||
#[template_child]
|
||||
pub provider_entry: TemplateChild<gtk::Entry>,
|
||||
pub provider_entry: TemplateChild<ProviderEntryRow>,
|
||||
pub selected_provider: RefCell<Option<Provider>>,
|
||||
pub providers_model: OnceCell<ProvidersModel>,
|
||||
#[property(get, set)]
|
||||
pub model: OnceCell<ProvidersModel>,
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
|
@ -106,6 +106,18 @@ mod imp {
|
|||
SIGNALS.as_ref()
|
||||
}
|
||||
|
||||
fn properties() -> &'static [glib::ParamSpec] {
|
||||
Self::derived_properties()
|
||||
}
|
||||
|
||||
fn set_property(&self, id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
|
||||
self.derived_set_property(id, value, pspec)
|
||||
}
|
||||
|
||||
fn property(&self, id: usize, pspec: &glib::ParamSpec) -> glib::Value {
|
||||
self.derived_property(id, pspec)
|
||||
}
|
||||
|
||||
fn constructed(&self) {
|
||||
self.parent_constructed();
|
||||
self.qrcode_picture
|
||||
|
@ -148,6 +160,26 @@ impl AccountDetailsPage {
|
|||
dialog.present();
|
||||
}
|
||||
|
||||
#[template_callback]
|
||||
fn on_provider_create(&self, entry: ProviderEntryRow) {
|
||||
let model = self.model();
|
||||
let window = self.root().and_downcast::<gtk::Window>();
|
||||
let dialog = ProvidersDialog::new(&model);
|
||||
dialog.create_with(&entry.text());
|
||||
dialog.connect_changed(move |_dialog, provider| {
|
||||
entry.set_selected_provider(Some(provider), true);
|
||||
});
|
||||
dialog.set_transient_for(window.as_ref());
|
||||
dialog.present();
|
||||
}
|
||||
|
||||
#[template_callback]
|
||||
fn on_provider_notify(&self) {
|
||||
if let Some(provider) = self.imp().provider_entry.provider() {
|
||||
self.set_provider(provider);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_account(&self, account: &Account) {
|
||||
let imp = self.imp();
|
||||
let qr_code = QRCodeData::from(String::from(account.otp_uri()));
|
||||
|
@ -156,21 +188,15 @@ impl AccountDetailsPage {
|
|||
if account.provider().method().is_event_based() {
|
||||
imp.counter_spinbutton.set_value(account.counter() as f64);
|
||||
}
|
||||
imp.provider_entry
|
||||
.set_selected_provider(Some(account.provider()), true);
|
||||
self.set_provider(account.provider());
|
||||
imp.account_label.set_text(&account.name());
|
||||
imp.account.replace(Some(account.clone()));
|
||||
}
|
||||
|
||||
pub fn set_providers_model(&self, model: ProvidersModel) {
|
||||
self.imp()
|
||||
.provider_completion
|
||||
.set_model(Some(&model.completion_model()));
|
||||
self.imp().providers_model.set(model).unwrap();
|
||||
}
|
||||
|
||||
fn set_provider(&self, provider: Provider) {
|
||||
let imp = self.imp();
|
||||
imp.provider_entry.set_text(&provider.name());
|
||||
imp.algorithm_label
|
||||
.set_text(&provider.algorithm().to_locale_string());
|
||||
imp.method_label
|
||||
|
@ -211,7 +237,6 @@ impl AccountDetailsPage {
|
|||
selected_provider.add_account(account);
|
||||
current_provider.remove_account(account);
|
||||
account.set_provider(selected_provider)?;
|
||||
imp.provider_entry.set_text(&selected_provider.name());
|
||||
self.emit_by_name::<()>("provider-changed", &[]);
|
||||
}
|
||||
}
|
||||
|
@ -225,17 +250,4 @@ impl AccountDetailsPage {
|
|||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[template_callback]
|
||||
fn provider_match_selected(&self, store: gtk::ListStore, iter: gtk::TreeIter) -> gtk::Inhibit {
|
||||
let provider_id = store.get::<u32>(&iter, 0);
|
||||
let model = self.imp().providers_model.get().unwrap();
|
||||
let provider = model.find_by_id(provider_id);
|
||||
self.set_provider(
|
||||
provider.unwrap_or_else(clone!(@strong self as page => move || {
|
||||
page.imp().account.borrow().as_ref().unwrap().provider()
|
||||
})),
|
||||
);
|
||||
gtk::Inhibit(false)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
#[allow(deprecated)]
|
||||
mod add;
|
||||
#[allow(deprecated)]
|
||||
mod details;
|
||||
mod qrcode_paintable;
|
||||
mod row;
|
||||
|
|
|
@ -16,6 +16,8 @@ enum View {
|
|||
}
|
||||
|
||||
mod imp {
|
||||
use std::cell::Cell;
|
||||
|
||||
use adw::subclass::window::AdwWindowImpl;
|
||||
use glib::subclass::Signal;
|
||||
use once_cell::sync::OnceCell;
|
||||
|
@ -50,6 +52,11 @@ mod imp {
|
|||
pub placeholder_page: TemplateChild<adw::StatusPage>,
|
||||
#[template_child]
|
||||
pub toast_overlay: TemplateChild<adw::ToastOverlay>,
|
||||
#[template_child]
|
||||
pub providers_page: TemplateChild<gtk::Box>,
|
||||
#[template_child]
|
||||
pub separator_page: TemplateChild<gtk::Box>,
|
||||
pub create_only: Cell<bool>,
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
|
@ -84,8 +91,11 @@ mod imp {
|
|||
impl ObjectImpl for ProvidersDialog {
|
||||
fn signals() -> &'static [Signal] {
|
||||
use once_cell::sync::Lazy;
|
||||
static SIGNALS: Lazy<Vec<Signal>> =
|
||||
Lazy::new(|| vec![Signal::builder("changed").build()]);
|
||||
static SIGNALS: Lazy<Vec<Signal>> = Lazy::new(|| {
|
||||
vec![Signal::builder("changed")
|
||||
.param_types([Provider::static_type()])
|
||||
.build()]
|
||||
});
|
||||
SIGNALS.as_ref()
|
||||
}
|
||||
|
||||
|
@ -150,15 +160,26 @@ impl ProvidersDialog {
|
|||
glib::Object::builder().property("model", model).build()
|
||||
}
|
||||
|
||||
pub fn create_with(&self, name: &str) {
|
||||
self.add_provider();
|
||||
let imp = self.imp();
|
||||
imp.page.name_entry().set_text(name);
|
||||
imp.create_only.set(true);
|
||||
imp.providers_list.set_visible(false);
|
||||
imp.providers_page.set_visible(false);
|
||||
imp.separator_page.set_visible(false);
|
||||
}
|
||||
|
||||
pub fn connect_changed<F>(&self, callback: F) -> glib::SignalHandlerId
|
||||
where
|
||||
F: Fn(&Self) + 'static,
|
||||
F: Fn(&Self, Provider) + 'static,
|
||||
{
|
||||
self.connect_local(
|
||||
"changed",
|
||||
false,
|
||||
clone!(@weak self as dialog => @default-return None, move |_| {
|
||||
callback(&dialog);
|
||||
clone!(@weak self as dialog => @default-return None, move |args| {
|
||||
let provider = args[1].get::<Provider>().unwrap();
|
||||
callback(&dialog, provider);
|
||||
None
|
||||
}),
|
||||
)
|
||||
|
@ -234,6 +255,7 @@ impl ProvidersDialog {
|
|||
imp.title_stack.set_visible_child_name("title");
|
||||
}
|
||||
}
|
||||
|
||||
#[template_callback]
|
||||
fn on_row_activated(&self, row: ProviderActionRow, _list: gtk::ListBox) {
|
||||
let provider = row.provider();
|
||||
|
@ -242,24 +264,27 @@ impl ProvidersDialog {
|
|||
|
||||
#[template_callback]
|
||||
fn on_provider_created(&self, provider: Provider, _page: ProviderPage) {
|
||||
let model = self
|
||||
.imp()
|
||||
let imp = self.imp();
|
||||
let model = imp
|
||||
.filter_model
|
||||
.model()
|
||||
.and_downcast::<ProvidersModel>()
|
||||
.unwrap();
|
||||
model.append(&provider);
|
||||
self.emit_by_name::<()>("changed", &[]);
|
||||
self.imp()
|
||||
.toast_overlay
|
||||
.add_toast(adw::Toast::new(&gettext("Provider created successfully")));
|
||||
self.set_view(View::Placeholder);
|
||||
self.emit_by_name::<()>("changed", &[&provider]);
|
||||
if imp.create_only.get() {
|
||||
self.close();
|
||||
} else {
|
||||
imp.toast_overlay
|
||||
.add_toast(adw::Toast::new(&gettext("Provider created successfully")));
|
||||
self.set_view(View::Placeholder);
|
||||
}
|
||||
}
|
||||
|
||||
#[template_callback]
|
||||
fn on_provider_updated(&self, _provider: Provider, _page: ProviderPage) {
|
||||
fn on_provider_updated(&self, provider: Provider, _page: ProviderPage) {
|
||||
self.set_view(View::List);
|
||||
self.emit_by_name::<()>("changed", &[]);
|
||||
self.emit_by_name::<()>("changed", &[&provider]);
|
||||
self.imp()
|
||||
.toast_overlay
|
||||
.add_toast(adw::Toast::new(&gettext("Provider updated successfully")));
|
||||
|
@ -275,7 +300,7 @@ impl ProvidersDialog {
|
|||
.unwrap();
|
||||
model.delete_provider(&provider);
|
||||
self.set_view(View::Placeholder);
|
||||
self.emit_by_name::<()>("changed", &[]);
|
||||
self.emit_by_name::<()>("changed", &[&provider]);
|
||||
self.imp()
|
||||
.toast_overlay
|
||||
.add_toast(adw::Toast::new(&gettext("Provider removed successfully")));
|
||||
|
|
337
src/widgets/providers/entry_row.rs
Normal file
337
src/widgets/providers/entry_row.rs
Normal file
|
@ -0,0 +1,337 @@
|
|||
use adw::{prelude::*, subclass::prelude::*};
|
||||
use gtk::{
|
||||
gdk,
|
||||
glib::{self, clone},
|
||||
Inhibit,
|
||||
};
|
||||
|
||||
use crate::models::{Provider, ProvidersModel};
|
||||
|
||||
mod imp {
|
||||
use std::cell::RefCell;
|
||||
|
||||
use glib::{subclass::Signal, translate::*};
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
extern "C" {
|
||||
fn gtk_list_item_set_focusable(
|
||||
item: *mut gtk::ffi::GtkListItem,
|
||||
focusable: glib::ffi::gboolean,
|
||||
);
|
||||
}
|
||||
|
||||
use super::*;
|
||||
use crate::models::ProviderSorter;
|
||||
|
||||
#[derive(Debug, Default, glib::Properties, gtk::CompositeTemplate)]
|
||||
#[template(resource = "/com/belmoussaoui/Authenticator/provider_entry_row.ui")]
|
||||
#[properties(wrapper_type = super::ProviderEntryRow)]
|
||||
pub struct ProviderEntryRow {
|
||||
#[template_child]
|
||||
pub popover: TemplateChild<gtk::Popover>,
|
||||
#[template_child]
|
||||
pub list_view: TemplateChild<gtk::ListView>,
|
||||
#[template_child]
|
||||
pub stack: TemplateChild<gtk::Stack>,
|
||||
pub filter_model: gtk::FilterListModel,
|
||||
#[property(get, set = Self::set_provider, nullable)]
|
||||
pub provider: RefCell<Option<Provider>>,
|
||||
#[property(get, set = Self::set_model)]
|
||||
pub model: RefCell<Option<ProvidersModel>>,
|
||||
pub selection_model: gtk::SingleSelection,
|
||||
pub changed_handler: RefCell<Option<glib::SignalHandlerId>>,
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for ProviderEntryRow {
|
||||
const NAME: &'static str = "ProviderEntryRow";
|
||||
type Type = super::ProviderEntryRow;
|
||||
type ParentType = adw::EntryRow;
|
||||
type Interfaces = (gtk::Editable,);
|
||||
|
||||
fn class_init(klass: &mut Self::Class) {
|
||||
klass.bind_template();
|
||||
klass.bind_template_instance_callbacks();
|
||||
klass.install_action("entry.create", None, |widget, _, _| {
|
||||
widget.imp().popover.popdown();
|
||||
widget.emit_by_name::<()>("create", &[]);
|
||||
});
|
||||
}
|
||||
|
||||
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
|
||||
obj.init_template();
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectImpl for ProviderEntryRow {
|
||||
fn properties() -> &'static [glib::ParamSpec] {
|
||||
Self::derived_properties()
|
||||
}
|
||||
|
||||
fn property(&self, id: usize, pspec: &glib::ParamSpec) -> glib::Value {
|
||||
if let Some(value) = self.delegate_get_property(id, pspec) {
|
||||
value
|
||||
} else {
|
||||
self.derived_property(id, pspec)
|
||||
}
|
||||
}
|
||||
|
||||
fn set_property(&self, id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
|
||||
if !self.delegate_set_property(id, value, pspec) {
|
||||
self.derived_set_property(id, value, pspec)
|
||||
}
|
||||
}
|
||||
|
||||
fn signals() -> &'static [Signal] {
|
||||
static SIGNALS: Lazy<Vec<Signal>> =
|
||||
Lazy::new(|| vec![Signal::builder("create").action().build()]);
|
||||
SIGNALS.as_ref()
|
||||
}
|
||||
|
||||
fn constructed(&self) {
|
||||
self.parent_constructed();
|
||||
let obj = self.obj();
|
||||
self.popover.set_parent(&*obj);
|
||||
|
||||
let handler_id = obj.connect_changed(move |entry| {
|
||||
entry.on_changed();
|
||||
});
|
||||
self.changed_handler.replace(Some(handler_id));
|
||||
|
||||
let factory = gtk::SignalListItemFactory::new();
|
||||
factory.connect_setup(move |_factory, item| {
|
||||
let item = item.downcast_ref::<gtk::ListItem>().unwrap();
|
||||
unsafe {
|
||||
gtk_list_item_set_focusable(item.as_ptr(), true.into_glib());
|
||||
}
|
||||
item.set_activatable(true);
|
||||
item.set_selectable(true);
|
||||
let row = gtk::Label::builder()
|
||||
.halign(gtk::Align::Start)
|
||||
.focusable(true)
|
||||
.build();
|
||||
item.set_child(Some(&row));
|
||||
});
|
||||
factory.connect_bind(move |_factory, item| {
|
||||
let item = item.downcast_ref::<gtk::ListItem>().unwrap();
|
||||
let provider = item.item().and_downcast::<Provider>().unwrap();
|
||||
let child = item.child().and_downcast::<gtk::Label>().unwrap();
|
||||
child.set_label(&provider.name());
|
||||
});
|
||||
self.list_view.set_factory(Some(&factory));
|
||||
|
||||
let sorter = ProviderSorter::default();
|
||||
let property_expression = Provider::this_expression("name");
|
||||
|
||||
let filter = gtk::StringFilter::new(Some(&property_expression));
|
||||
self.filter_model.set_filter(Some(&filter));
|
||||
let stack = self.stack.get();
|
||||
let popover = self.popover.get();
|
||||
self.selection_model.connect_items_changed(
|
||||
clone!(@weak stack, @weak popover => move |model, _, _ ,_| {
|
||||
if model.n_items() == 0 {
|
||||
stack.set_visible_child_name("empty");
|
||||
popover.remove_css_class("menu");
|
||||
} else {
|
||||
popover.add_css_class("menu");
|
||||
stack.set_visible_child_name("list");
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
let sorted_model =
|
||||
gtk::SortListModel::new(Some(self.filter_model.clone()), Some(sorter));
|
||||
self.selection_model.set_model(Some(&sorted_model));
|
||||
self.selection_model.set_autoselect(false);
|
||||
self.list_view.set_model(Some(&self.selection_model));
|
||||
}
|
||||
|
||||
fn dispose(&self) {
|
||||
self.popover.unparent();
|
||||
}
|
||||
}
|
||||
|
||||
impl WidgetImpl for ProviderEntryRow {
|
||||
fn focus(&self, dir: gtk::DirectionType) -> bool {
|
||||
if self.popover.is_visible() && dir == gtk::DirectionType::TabForward
|
||||
|| dir == gtk::DirectionType::TabBackward
|
||||
{
|
||||
let matches = self.selection_model.n_items();
|
||||
let mut selected = self.selection_model.selected();
|
||||
|
||||
if dir == gtk::DirectionType::TabForward {
|
||||
if selected == gtk::INVALID_LIST_POSITION || selected == matches - 1 {
|
||||
selected = 0;
|
||||
} else {
|
||||
selected += 1;
|
||||
}
|
||||
} else {
|
||||
if selected == gtk::INVALID_LIST_POSITION || selected == 0 {
|
||||
selected = matches - 1;
|
||||
} else {
|
||||
selected -= 1;
|
||||
}
|
||||
}
|
||||
self.selection_model.set_selected(selected);
|
||||
let item = self.selection_model.selected_item();
|
||||
self.obj()
|
||||
.update_selected_provider(item.and_downcast_ref::<Provider>());
|
||||
return true;
|
||||
}
|
||||
self.parent_focus(dir)
|
||||
}
|
||||
}
|
||||
impl ListBoxRowImpl for ProviderEntryRow {}
|
||||
impl PreferencesRowImpl for ProviderEntryRow {}
|
||||
impl EntryRowImpl for ProviderEntryRow {}
|
||||
impl EditableImpl for ProviderEntryRow {}
|
||||
impl ProviderEntryRow {
|
||||
fn set_model(&self, model: &ProvidersModel) {
|
||||
self.filter_model.set_model(Some(model));
|
||||
self.model.replace(Some(model.clone()));
|
||||
}
|
||||
|
||||
fn set_provider(&self, item: Option<Provider>) {
|
||||
println!("setting provider");
|
||||
// Do nothing if it is the already set provider
|
||||
let obj = self.obj();
|
||||
if item == obj.provider() {
|
||||
return;
|
||||
}
|
||||
obj.update_selected_provider(item.as_ref());
|
||||
let guard = obj.freeze_notify();
|
||||
self.provider.replace(item);
|
||||
drop(guard);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct ProviderEntryRow(ObjectSubclass<imp::ProviderEntryRow>)
|
||||
@extends gtk::Widget, gtk::ListBoxRow, adw::PreferencesRow, adw::EntryRow,
|
||||
@implements gtk::Editable;
|
||||
}
|
||||
|
||||
#[gtk::template_callbacks]
|
||||
impl ProviderEntryRow {
|
||||
#[template_callback]
|
||||
fn on_changed(&self) {
|
||||
let imp = self.imp();
|
||||
let text = self.text();
|
||||
let filter = imp
|
||||
.filter_model
|
||||
.filter()
|
||||
.and_downcast::<gtk::StringFilter>()
|
||||
.unwrap();
|
||||
imp.popover.popup();
|
||||
filter.set_search(Some(&text));
|
||||
}
|
||||
|
||||
#[template_callback]
|
||||
fn on_provider_activated(&self, position: u32) {
|
||||
println!("activated");
|
||||
let imp = self.imp();
|
||||
let item = imp.selection_model.item(position);
|
||||
imp.popover.popdown();
|
||||
self.set_provider(item.and_downcast::<Provider>());
|
||||
}
|
||||
|
||||
// Re-implementation of ephy-location-entry.c
|
||||
#[template_callback]
|
||||
fn on_key_pressed(
|
||||
&self,
|
||||
keyval: gdk::Key,
|
||||
_keycode: u32,
|
||||
modifier: gdk::ModifierType,
|
||||
) -> Inhibit {
|
||||
println!("keypress");
|
||||
let imp = self.imp();
|
||||
const PAGE_STEP: u32 = 20;
|
||||
if !modifier.is_empty() {
|
||||
return Inhibit(false);
|
||||
}
|
||||
if ![
|
||||
gdk::Key::Up,
|
||||
gdk::Key::KP_Up,
|
||||
gdk::Key::Down,
|
||||
gdk::Key::KP_Down,
|
||||
gdk::Key::Page_Up,
|
||||
gdk::Key::KP_Page_Up,
|
||||
gdk::Key::Page_Down,
|
||||
gdk::Key::KP_Page_Down,
|
||||
]
|
||||
.contains(&keyval)
|
||||
{
|
||||
return Inhibit(false);
|
||||
}
|
||||
|
||||
if !imp.popover.is_visible() {
|
||||
return Inhibit(false);
|
||||
}
|
||||
let matches = imp.selection_model.n_items();
|
||||
let mut selected = imp.selection_model.selected();
|
||||
|
||||
if keyval == gdk::Key::Up || keyval == gdk::Key::KP_Up {
|
||||
if selected == gtk::INVALID_LIST_POSITION {
|
||||
selected = matches - 1;
|
||||
} else if selected == 0 {
|
||||
selected = gtk::INVALID_LIST_POSITION;
|
||||
} else {
|
||||
selected -= 1;
|
||||
}
|
||||
}
|
||||
if keyval == gdk::Key::Down || keyval == gdk::Key::KP_Down {
|
||||
if selected == gtk::INVALID_LIST_POSITION {
|
||||
selected = 0;
|
||||
} else if selected == matches - 1 {
|
||||
selected = gtk::INVALID_LIST_POSITION;
|
||||
} else {
|
||||
selected += 1;
|
||||
}
|
||||
}
|
||||
if keyval == gdk::Key::Page_Up || keyval == gdk::Key::KP_Page_Up {
|
||||
if selected == gtk::INVALID_LIST_POSITION {
|
||||
selected = matches - 1;
|
||||
} else if selected == 0 {
|
||||
selected = gtk::INVALID_LIST_POSITION;
|
||||
} else if selected < PAGE_STEP {
|
||||
selected = 0;
|
||||
} else {
|
||||
selected -= PAGE_STEP;
|
||||
}
|
||||
}
|
||||
if keyval == gdk::Key::Page_Down || keyval == gdk::Key::KP_Page_Down {
|
||||
if selected == gtk::INVALID_LIST_POSITION {
|
||||
selected = 0;
|
||||
} else if selected == matches - 1 {
|
||||
selected = gtk::INVALID_LIST_POSITION;
|
||||
} else if (selected + PAGE_STEP) > matches - 1 {
|
||||
selected = matches - 1;
|
||||
} else {
|
||||
selected += PAGE_STEP;
|
||||
}
|
||||
}
|
||||
|
||||
if selected == gtk::INVALID_LIST_POSITION {
|
||||
self.error_bell();
|
||||
return Inhibit(true);
|
||||
}
|
||||
imp.selection_model.set_selected(selected);
|
||||
let item = imp.selection_model.selected_item();
|
||||
self.update_selected_provider(item.and_downcast_ref::<Provider>());
|
||||
Inhibit(true)
|
||||
}
|
||||
|
||||
fn update_selected_provider(&self, provider: Option<&Provider>) {
|
||||
let imp = self.imp();
|
||||
let handler_id = imp.changed_handler.borrow();
|
||||
self.block_signal(handler_id.as_ref().unwrap());
|
||||
if let Some(item) = provider {
|
||||
self.set_text(&item.name());
|
||||
} else {
|
||||
self.set_text("");
|
||||
}
|
||||
self.set_position(-1);
|
||||
self.unblock_signal(handler_id.as_ref().unwrap());
|
||||
}
|
||||
}
|
|
@ -1,10 +1,12 @@
|
|||
mod dialog;
|
||||
mod entry_row;
|
||||
mod image;
|
||||
mod list;
|
||||
mod page;
|
||||
mod row;
|
||||
pub use self::{
|
||||
dialog::ProvidersDialog,
|
||||
entry_row::ProviderEntryRow,
|
||||
image::ProviderImage,
|
||||
list::{ProvidersList, ProvidersListView},
|
||||
page::ProviderPage,
|
||||
|
|
|
@ -164,7 +164,6 @@ mod imp {
|
|||
if is_maximized {
|
||||
win.maximize();
|
||||
}
|
||||
self.account_details.set_providers_model(win.model());
|
||||
|
||||
if config::PROFILE == "Devel" {
|
||||
win.add_css_class("devel");
|
||||
|
|
Loading…
Add table
Reference in a new issue