implement autocomplete selection event

This commit is contained in:
yggverse 2025-03-10 19:36:48 +02:00
parent 5b0de227c0
commit 907dcd4893
2 changed files with 79 additions and 27 deletions

View file

@ -82,7 +82,7 @@ impl Request for Entry {
entry.update_primary_icon(profile); entry.update_primary_icon(profile);
// Init additional features // Init additional features
let suggestion = Suggestion::build(&entry); let suggestion = Rc::new(Suggestion::build(profile, &entry));
// Connect events // Connect events
entry.connect_icon_release({ entry.connect_icon_release({
@ -102,7 +102,11 @@ impl Request for Entry {
entry.connect_has_focus_notify(|this| this.update_secondary_icon()); entry.connect_has_focus_notify(|this| this.update_secondary_icon());
entry.connect_changed({ suggestion
.clone()
.signal_handler_id
.borrow_mut()
.replace(entry.connect_changed({
let profile = profile.clone(); let profile = profile.clone();
let item_action = item_action.clone(); let item_action = item_action.clone();
move |this| { move |this| {
@ -116,10 +120,10 @@ impl Request for Entry {
// Show search suggestions // Show search suggestions
if this.focus_child().is_some() { if this.focus_child().is_some() {
suggestion.update(&profile, this, None); suggestion.update(None);
} }
} }
}); })); // `suggestion` wants `signal_handler_id` to block this event on autocomplete navigation
entry.connect_activate({ entry.connect_activate({
let item_action = item_action.clone(); let item_action = item_action.clone();
@ -142,7 +146,7 @@ impl Request for Entry {
&& state.contains(StateFlags::ACTIVE | StateFlags::FOCUS_WITHIN) && state.contains(StateFlags::ACTIVE | StateFlags::FOCUS_WITHIN)
&& this.selection_bounds().is_none() && this.selection_bounds().is_none()
{ {
this.select_region(0, this.text_length().into()); this.select_region(0, -1);
} }
// Update last focus state // Update last focus state
has_focus.replace(state.contains(StateFlags::FOCUS_WITHIN)); has_focus.replace(state.contains(StateFlags::FOCUS_WITHIN));

View file

@ -1,5 +1,6 @@
mod item; mod item;
use super::Profile;
use adw::{ use adw::{
prelude::{ActionRowExt, PopoverExt, PreferencesRowExt}, prelude::{ActionRowExt, PopoverExt, PreferencesRowExt},
ActionRow, ActionRow,
@ -9,13 +10,19 @@ use gtk::{
prelude::{Cast, CastNone}, prelude::{Cast, CastNone},
ListStore, ListStore,
}, },
prelude::{EntryExt, ListItemExt, WidgetExt}, glib::SignalHandlerId,
prelude::{EditableExt, EntryExt, ListItemExt, WidgetExt},
Entry, ListItem, ListView, Popover, SignalListItemFactory, SingleSelection, Entry, ListItem, ListView, Popover, SignalListItemFactory, SingleSelection,
}; };
pub use item::Item; pub use item::Item;
use sourceview::prelude::ListModelExt;
use std::{cell::RefCell, rc::Rc};
pub struct Suggestion { pub struct Suggestion {
list_store: ListStore, list_store: ListStore,
request: Entry,
profile: Rc<Profile>,
pub signal_handler_id: Rc<RefCell<Option<SignalHandlerId>>>,
pub popover: Popover, pub popover: Popover,
} }
@ -23,9 +30,12 @@ impl Suggestion {
// Constructors // Constructors
/// Create new `Self` /// Create new `Self`
pub fn build(request: &Entry) -> Self { pub fn build(profile: &Rc<Profile>, request: &Entry) -> Self {
let list_store = ListStore::new::<Item>(); let list_store = ListStore::new::<Item>();
let signal_handler_id = Rc::new(RefCell::new(None));
Self { Self {
profile: profile.clone(),
request: request.clone(),
popover: { popover: {
let p = Popover::builder() let p = Popover::builder()
.autohide(false) .autohide(false)
@ -34,8 +44,10 @@ impl Suggestion {
.child( .child(
&gtk::ScrolledWindow::builder() &gtk::ScrolledWindow::builder()
//.css_classes(["view"]) //.css_classes(["view"])
.child( .child(&{
&ListView::builder() let list_view = ListView::builder()
.show_separators(true)
.single_click_activate(true)
.model( .model(
&SingleSelection::builder() &SingleSelection::builder()
.model(&list_store) .model(&list_store)
@ -63,8 +75,37 @@ impl Suggestion {
}); });
f f
}) })
.build(), .build();
) list_view.connect_activate({
let request = request.clone();
let signal_handler_id = signal_handler_id.clone();
move |this, i| {
use gtk::prelude::ObjectExt;
if let Some(signal_handler_id) =
signal_handler_id.borrow().as_ref()
{
request.block_signal(signal_handler_id);
}
request.set_text(
&this
.model()
.unwrap()
.item(i)
.unwrap()
.downcast_ref::<Item>()
.unwrap()
.request(),
);
request.select_region(0, -1);
if let Some(signal_handler_id) =
signal_handler_id.borrow().as_ref()
{
request.unblock_signal(signal_handler_id);
}
}
});
list_view
})
.max_content_height(400) .max_content_height(400)
.hscrollbar_policy(gtk::PolicyType::Never) .hscrollbar_policy(gtk::PolicyType::Never)
.propagate_natural_height(true) .propagate_natural_height(true)
@ -87,17 +128,18 @@ impl Suggestion {
}); });
p p
}, },
signal_handler_id,
list_store, list_store,
} }
} }
pub fn update(&self, profile: &super::Profile, request: &Entry, limit: Option<usize>) { pub fn update(&self, limit: Option<usize>) {
use gtk::prelude::EditableExt; use gtk::prelude::EditableExt;
use itertools::Itertools; use itertools::Itertools;
if request.text_length() > 0 { if self.request.text_length() > 0 {
self.list_store.remove_all(); self.list_store.remove_all();
let query = request.text(); let query = self.request.text();
let items = profile.bookmark.contains_request(&query, limit); let items = self.profile.bookmark.contains_request(&query, limit);
if !items.is_empty() { if !items.is_empty() {
for item in items for item in items
.into_iter() .into_iter()
@ -109,6 +151,12 @@ impl Suggestion {
item.request.clone(), item.request.clone(),
)); // @TODO )); // @TODO
} }
self.popover
.child()
.unwrap()
.downcast_ref::<gtk::ScrolledWindow>()
.unwrap()
.set_height_request(-1);
self.popover.popup(); self.popover.popup();
return; return;
} }