widgets: Add a new ProviderEntryRow

That does completion without using GtkEntryCompletion which is deprecated
Fixes #335
This commit is contained in:
Bilal Elmoussaoui 2023-04-10 21:29:22 +02:00
parent 5abb9c6864
commit 961040be8e
15 changed files with 527 additions and 161 deletions

View file

@ -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>

View 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>

View file

@ -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>

View 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>

View file

@ -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">

View file

@ -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>

View file

@ -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));

View file

@ -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();

View file

@ -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

View file

@ -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)
}
}

View file

@ -1,6 +1,4 @@
#[allow(deprecated)]
mod add;
#[allow(deprecated)]
mod details;
mod qrcode_paintable;
mod row;

View file

@ -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")));

View 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());
}
}

View file

@ -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,

View file

@ -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");