article-row: fix bold on initial load, add right-click menu
Set unread bold state directly in bind() instead of relying on
obj.notify("unread"), which was unreliable during list factory binding
(GLib may defer or drop notifications during initial bind).
Also add a right-click context menu on each article row with a single
"Mark as Unread" item. The menu is a GtkPopover positioned at the
cursor. Clicking it activates the new win.mark-article-unread action,
which takes the article ID as a string parameter and reuses the
existing mark-unread logic.
Refactor do_mark_unread() to delegate to the new do_mark_article_unread()
so the behaviour is consistent whether triggered from the toolbar button,
keyboard shortcut, or right-click menu.
This commit is contained in:
parent
571d80fa6b
commit
9a4bf4b9f8
2 changed files with 86 additions and 7 deletions
|
|
@ -44,6 +44,7 @@ mod imp {
|
|||
|
||||
pub bindings: RefCell<Vec<glib::Binding>>,
|
||||
pub unread_handler: RefCell<Option<(ArticleObject, glib::SignalHandlerId)>>,
|
||||
pub context_menu: std::cell::OnceCell<gtk4::Popover>,
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
|
|
@ -61,20 +62,88 @@ mod imp {
|
|||
}
|
||||
}
|
||||
|
||||
impl ObjectImpl for ArticleRow {}
|
||||
impl ObjectImpl for ArticleRow {
|
||||
fn constructed(&self) {
|
||||
self.parent_constructed();
|
||||
self.setup_context_menu();
|
||||
}
|
||||
|
||||
fn dispose(&self) {
|
||||
if let Some(popover) = self.context_menu.get() {
|
||||
popover.unparent();
|
||||
}
|
||||
}
|
||||
}
|
||||
impl WidgetImpl for ArticleRow {}
|
||||
impl BoxImpl for ArticleRow {}
|
||||
|
||||
impl ArticleRow {
|
||||
fn setup_context_menu(&self) {
|
||||
let button = gtk4::Button::with_label("Mark as Unread");
|
||||
button.set_has_frame(false);
|
||||
|
||||
let popover = gtk4::Popover::new();
|
||||
popover.set_child(Some(&button));
|
||||
popover.set_parent(&*self.obj());
|
||||
self.context_menu.set(popover.clone()).ok();
|
||||
|
||||
// Close popover and activate action when button is clicked.
|
||||
let imp_weak = self.downgrade();
|
||||
let popover_weak = popover.downgrade();
|
||||
button.connect_clicked(move |_| {
|
||||
if let Some(popover) = popover_weak.upgrade() {
|
||||
popover.popdown();
|
||||
}
|
||||
let Some(imp) = imp_weak.upgrade() else { return };
|
||||
let handler = imp.unread_handler.borrow();
|
||||
let Some((obj, _)) = handler.as_ref() else { return };
|
||||
let article_id = obj.article().id.clone();
|
||||
drop(handler);
|
||||
imp.obj()
|
||||
.activate_action(
|
||||
"win.mark-article-unread",
|
||||
Some(&article_id.to_variant()),
|
||||
)
|
||||
.ok();
|
||||
});
|
||||
|
||||
// Right-click gesture to show the popover at cursor position.
|
||||
let gesture = gtk4::GestureClick::new();
|
||||
gesture.set_button(3);
|
||||
let popover_weak2 = popover.downgrade();
|
||||
gesture.connect_pressed(move |gesture, _, x, y| {
|
||||
gesture.set_state(gtk4::EventSequenceState::Claimed);
|
||||
let Some(popover) = popover_weak2.upgrade() else { return };
|
||||
popover.set_pointing_to(Some(>k4::gdk::Rectangle::new(
|
||||
x as i32,
|
||||
y as i32,
|
||||
1,
|
||||
1,
|
||||
)));
|
||||
popover.popup();
|
||||
});
|
||||
self.obj().add_controller(gesture);
|
||||
}
|
||||
|
||||
pub fn bind(&self, obj: &ArticleObject) {
|
||||
let article = obj.article();
|
||||
self.feed_title_label.set_text(&article.feed_title);
|
||||
self.excerpt_label.set_text(&article.excerpt);
|
||||
self.date_label.set_text(&relative_time(article.published));
|
||||
|
||||
// Set initial bold state directly (using Pango markup to avoid
|
||||
// CSS specificity issues with the zoom font-size provider).
|
||||
let escaped = glib::markup_escape_text(&article.title);
|
||||
if article.unread {
|
||||
self.title_label.remove_css_class("dim-label");
|
||||
self.title_label.set_markup(&format!("<b>{escaped}</b>"));
|
||||
} else {
|
||||
self.title_label.add_css_class("dim-label");
|
||||
self.title_label.set_markup(&escaped);
|
||||
}
|
||||
drop(article);
|
||||
|
||||
// Register handler first, then trigger it for the initial state.
|
||||
// Using Pango markup for bold avoids CSS specificity issues.
|
||||
// Connect handler for future unread state changes.
|
||||
let title_label = self.title_label.clone();
|
||||
let id = obj.connect_notify_local(Some("unread"), move |obj, _| {
|
||||
let article = obj.article();
|
||||
|
|
@ -88,7 +157,6 @@ mod imp {
|
|||
}
|
||||
});
|
||||
*self.unread_handler.borrow_mut() = Some((obj.clone(), id));
|
||||
obj.notify("unread");
|
||||
}
|
||||
|
||||
pub fn unbind(&self) {
|
||||
|
|
@ -99,7 +167,6 @@ mod imp {
|
|||
b.unbind();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -77,6 +77,11 @@ pub mod imp {
|
|||
klass.install_action("win.reload", None, |win, _, _| win.imp().do_reload());
|
||||
klass.install_action("win.logout", None, |win, _, _| win.imp().do_logout());
|
||||
klass.install_action("win.mark-unread", None, |win, _, _| win.imp().do_mark_unread());
|
||||
klass.install_action("win.mark-article-unread", Some(glib::VariantTy::STRING), |win, _, param| {
|
||||
if let Some(id) = param.and_then(|p| p.get::<String>()) {
|
||||
win.imp().do_mark_article_unread(id);
|
||||
}
|
||||
});
|
||||
klass.install_action("win.open-in-browser", None, |win, _, _| {
|
||||
win.imp().do_open_in_browser()
|
||||
});
|
||||
|
|
@ -750,8 +755,11 @@ pub mod imp {
|
|||
fn do_mark_unread(&self) {
|
||||
let id = self.current_article_id.borrow().clone();
|
||||
let Some(id) = id else { return };
|
||||
self.do_mark_article_unread(id);
|
||||
}
|
||||
|
||||
// Find the ArticleObject in the store and set unread=true
|
||||
fn do_mark_article_unread(&self, id: String) {
|
||||
// Find the ArticleObject in the store and set unread=true.
|
||||
if let Some(store) = self.article_store.borrow().as_ref() {
|
||||
for i in 0..store.n_items() {
|
||||
if let Some(obj) = store.item(i).and_downcast::<ArticleObject>() {
|
||||
|
|
@ -762,7 +770,11 @@ pub mod imp {
|
|||
}
|
||||
}
|
||||
|
||||
*self.mark_unread_guard.borrow_mut() = true;
|
||||
// If this is the currently displayed article, guard against it
|
||||
// being immediately re-marked read when the selection fires.
|
||||
if self.current_article_id.borrow().as_deref() == Some(&*id) {
|
||||
*self.mark_unread_guard.borrow_mut() = true;
|
||||
}
|
||||
|
||||
let api = self.api.borrow().clone();
|
||||
let wt = self.write_token.borrow().clone();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue