Replace TalkatuScrolledWindow with TalkatuAutoScroller
GtkScrolledWindow is a final type in GTK4, so we can no longer subclass it. To
work around this, TalkatuAutoScroller was created which is a GtkAdjustment
subclass that does the same thing TalkatuScrolledWindow did.
Testing Done:
Ran the demo and verified that auto scroll worked properly.
Reviewed at https://reviews.imfreedom.org/r/1658/
--- a/demo/data/demo.ui Thu Aug 25 03:22:24 2022 -0500
+++ b/demo/data/demo.ui Fri Aug 26 03:08:51 2022 -0500
@@ -25,6 +25,7 @@
<!-- interface-name Talkatu -->
<!-- interface-description GTK widgets for chat applications -->
<!-- interface-copyright Gary Kramlich <grim@reaperworld.com> -->
+ <object class="TalkatuAutoScroller" id="auto_scroller"/> <template class="TalkatuDemoWindow" parent="GtkApplicationWindow">
<object class="GtkPaned">
@@ -33,14 +34,11 @@
<property name="orientation">vertical</property>
<property name="wide-handle">1</property>
- <object class="TalkatuScrolledWindow">
- <child internal-child="scrolled_window">
- <object class="GtkScrolledWindow" id="scrolled_window">
- <object class="TalkatuHistory" id="history">
- <property name="name">history</property>
+ <object class="GtkScrolledWindow"> + <property name="vadjustment">auto_scroller</property> + <object class="TalkatuHistory" id="history"> + <property name="name">history</property> --- a/po/POTFILES Thu Aug 25 03:22:24 2022 -0500
+++ b/po/POTFILES Fri Aug 26 03:08:51 2022 -0500
@@ -16,6 +16,7 @@
talkatu/talkatuattachment.c
talkatu/talkatuattachmentdialog.c
talkatu/talkatuattachmentpreview.c
+talkatu/talkatuautoscroller.c @@ -29,7 +30,6 @@
talkatu/talkatumarkdownbuffer.c
-talkatu/talkatuscrolledwindow.c
talkatu/talkatusimpleattachment.c
talkatu/talkatutagtable.c
--- a/talkatu/data/scrolledwindow.ui Thu Aug 25 03:22:24 2022 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
- <requires lib="gtk" version="4.0"/>
- <template class="TalkatuScrolledWindow" parent="GtkWidget">
- <object class="GtkScrolledWindow" id="scrolled_window">
- <property name="vexpand">1</property>
- <property name="hexpand">1</property>
- <property name="focusable">1</property>
- <property name="vadjustment">vadjustment</property>
- <property name="min-content-height">150</property>
- <property name="vscrollbar-policy">2</property>
- <object class="GtkAdjustment" id="vadjustment">
- <property name="upper">100</property>
- <property name="step-increment">1</property>
- <property name="page-increment">10</property>
- <signal name="changed" handler="talkatu_scrolled_window_vadjustment_changed_cb" object="TalkatuScrolledWindow" swapped="no"/>
- <signal name="value-changed" handler="talkatu_scrolled_window_vadjustment_value_changed_cb" object="TalkatuScrolledWindow" swapped="no"/>
--- a/talkatu/data/talkatu.gresource.xml Thu Aug 25 03:22:24 2022 -0500
+++ b/talkatu/data/talkatu.gresource.xml Fri Aug 26 03:08:51 2022 -0500
@@ -8,7 +8,6 @@
<file compressed="true">historyrow.ui</file>
<file compressed="true">input.ui</file>
<file compressed="true">linkdialog.ui</file>
- <file compressed="true">scrolledwindow.ui</file>
<file compressed="true">toolbar.ui</file>
<file compressed="true">typinglabel.ui</file>
<file compressed="true">view.ui</file>
--- a/talkatu/meson.build Thu Aug 25 03:22:24 2022 -0500
+++ b/talkatu/meson.build Fri Aug 26 03:08:51 2022 -0500
@@ -8,6 +8,7 @@
'talkatuattachmentdialog.h',
'talkatuattachmentpreview.h',
+ 'talkatuautoscroller.h', @@ -22,7 +23,6 @@
'talkatumarkdownbuffer.h',
- 'talkatuscrolledwindow.h',
'talkatusimpleattachment.h',
@@ -37,6 +37,7 @@
'talkatuattachmentdialog.c',
'talkatuattachmentpreview.c',
+ 'talkatuautoscroller.c', @@ -51,7 +52,6 @@
'talkatumarkdownbuffer.c',
- 'talkatuscrolledwindow.c',
'talkatusimpleattachment.c',
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/talkatu/talkatuautoscroller.c Fri Aug 26 03:08:51 2022 -0500
@@ -0,0 +1,154 @@
+ * Talkatu - GTK widgets for chat applications + * Copyright (C) 2017-2020 Gary Kramlich <grim@reaperworld.com> + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this library; if not, see <https://www.gnu.org/licenses/>. +#include <talkatu/talkatuautoscroller.h> + * This is a simple subclass of [class@Gtk.Adjustment] that has helpers for + * keyboard navigation as well as the ability to automatically scroll to the + * max when new items are added if the widget was already scrolled all the +struct _TalkatuAutoScroller { +G_DEFINE_TYPE(TalkatuAutoScroller, talkatu_auto_scroller, GTK_TYPE_ADJUSTMENT) +/****************************************************************************** + *****************************************************************************/ +talkatu_auto_scroller_scroll(gpointer data) { + GtkAdjustment *adjustment = data; + gdouble upper, pagesize; + upper = gtk_adjustment_get_upper(adjustment); + pagesize = gtk_adjustment_get_page_size(adjustment); + gtk_adjustment_set_value(adjustment, upper - pagesize); + return G_SOURCE_REMOVE; +talkatu_auto_scroller_changed(GtkAdjustment *adjustment) { + TalkatuAutoScroller *auto_scroller = TALKATU_AUTO_SCROLLER(adjustment); + if(auto_scroller->auto_scroll) { + /* If we set the value here, we interrupt the updates to the other + * values of the adjustment which breaks the adjustment. + * The specific behavior we've seen is that the upper property doesn't + * get updated when we call set value here. Which means you can't even + * scroll down anymore in a scrolled window because you're already at + * the upper bound. However, if you scroll up, even if there's no where + * to scroll, it will update the adjustment's properties and you can + * By using a timeout to set the value during the next main loop + * iteration we avoid this problem. + g_timeout_add(0, talkatu_auto_scroller_scroll, adjustment); +talkatu_auto_scroller_value_changed(GtkAdjustment *adjustment) { + TalkatuAutoScroller *auto_scroller = TALKATU_AUTO_SCROLLER(adjustment); + gdouble current, upper, pagesize; + current = gtk_adjustment_get_value(adjustment); + upper = gtk_adjustment_get_upper(adjustment); + pagesize = gtk_adjustment_get_page_size(adjustment); + auto_scroller->auto_scroll = (current + pagesize >= upper); +/****************************************************************************** + * GObject Implementation + *****************************************************************************/ +talkatu_auto_scroller_init(TalkatuAutoScroller *auto_scroller) { + auto_scroller->auto_scroll = TRUE; +talkatu_auto_scroller_class_init(TalkatuAutoScrollerClass *klass) { + GtkAdjustmentClass *adjustment_class = GTK_ADJUSTMENT_CLASS(klass); + adjustment_class->changed = talkatu_auto_scroller_changed; + adjustment_class->value_changed = talkatu_auto_scroller_value_changed; +/****************************************************************************** + *****************************************************************************/ + * talkatu_auto_scroller_new: + * Creates a new #TalkatuAutoScroller. + * Returns: (transfer full): The new #TalkatuAutoScroller instance. +talkatu_auto_scroller_new(void) { + return g_object_new(TALKATU_TYPE_AUTO_SCROLLER, NULL); + * talkatu_auto_scroller_decrement: + * @auto_scroller: The #TalkatuAutoScroller instance. + * Decrements the value of @auto_scroller by a page increment. +talkatu_auto_scroller_decrement(TalkatuAutoScroller *auto_scroller) { + g_return_if_fail(TALKATU_IS_AUTO_SCROLLER(auto_scroller)); + value = gtk_adjustment_get_value(GTK_ADJUSTMENT(auto_scroller)); + value -= gtk_adjustment_get_page_increment(GTK_ADJUSTMENT(auto_scroller)); + gtk_adjustment_set_value(GTK_ADJUSTMENT(auto_scroller), value); + * talkatu_auto_scroller_increment: + * @auto_scroller: The #TalkatuAutoScroller instance. + * Increments the value of @auto_scroller by a page increment. +talkatu_auto_scroller_page_down(TalkatuAutoScroller *auto_scroller) { + g_return_if_fail(TALKATU_IS_AUTO_SCROLLER(auto_scroller)); + value = gtk_adjustment_get_value(GTK_ADJUSTMENT(auto_scroller)); + value += gtk_adjustment_get_page_increment(GTK_ADJUSTMENT(auto_scroller)); + gtk_adjustment_set_value(GTK_ADJUSTMENT(auto_scroller), value); --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/talkatu/talkatuautoscroller.h Fri Aug 26 03:08:51 2022 -0500
@@ -0,0 +1,45 @@
+ * Talkatu - GTK widgets for chat applications + * Copyright (C) 2017-2020 Gary Kramlich <grim@reaperworld.com> + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this library; if not, see <https://www.gnu.org/licenses/>. +#if !defined(TALKATU_GLOBAL_HEADER_INSIDE) && !defined(TALKATU_COMPILATION) +#error "only <talkatu.h> may be included directly" +#ifndef TALKATU_AUTO_SCROLLER_H +#define TALKATU_AUTO_SCROLLER_H +#include <glib-object.h> +#define TALKATU_TYPE_AUTO_SCROLLER (talkatu_auto_scroller_get_type()) +G_DECLARE_FINAL_TYPE(TalkatuAutoScroller, talkatu_auto_scroller, TALKATU, + AUTO_SCROLLER, GtkAdjustment) +GtkAdjustment *talkatu_auto_scroller_new(void); +void talkatu_auto_scroller_decrement(TalkatuAutoScroller *auto_scroller); +void talkatu_auto_scroller_increment(TalkatuAutoScroller *auto_scroller); +#endif /* TALKATU_AUTO_SCROLLER_H */ --- a/talkatu/talkatuscrolledwindow.c Thu Aug 25 03:22:24 2022 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,247 +0,0 @@
- * Talkatu - GTK widgets for chat applications
- * Copyright (C) 2017-2020 Gary Kramlich <grim@reaperworld.com>
- * This library is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- * You should have received a copy of the GNU General Public License
- * along with this library; if not, see <https://www.gnu.org/licenses/>.
-#include <talkatu/talkatuscrolledwindow.h>
- * TalkatuScrolledWindow:
- * This widget is a simple subclass of #GtkScrolledWindow that has helpers for
- * keyboard navigation as well as the ability to automatically scroll to the
- * bottom when new items are added if the widget was already scrolled all the
-struct _TalkatuScrolledWindow {
- GtkWidget *scrolled_window;
- GtkAdjustment *vadjustment;
-static GParamSpec *properties[N_PROPERTIES] = { NULL, };
-/******************************************************************************
- *****************************************************************************/
-talkatu_scrolled_window_vadjustment_changed_cb(GtkAdjustment *adjustment,
- TalkatuScrolledWindow *sw = TALKATU_SCROLLED_WINDOW(data);
- gdouble upper, pagesize;
- upper = gtk_adjustment_get_upper(adjustment);
- pagesize = gtk_adjustment_get_page_size(adjustment);
- gtk_adjustment_set_value(adjustment, upper - pagesize);
-talkatu_scrolled_window_vadjustment_value_changed_cb(GtkAdjustment *adjustment,
- TalkatuScrolledWindow *sw = TALKATU_SCROLLED_WINDOW(data);
- gdouble current, upper, pagesize;
- current = gtk_adjustment_get_value(adjustment);
- upper = gtk_adjustment_get_upper(adjustment);
- pagesize = gtk_adjustment_get_page_size(adjustment);
- sw->auto_scroll = (current + pagesize >= upper);
-/******************************************************************************
- * GObject Implementation
- *****************************************************************************/
-G_DEFINE_TYPE(TalkatuScrolledWindow, talkatu_scrolled_window, GTK_TYPE_WIDGET)
-tatlkatu_scrolled_window_get_property(GObject *obj, guint param_id,
- GValue *value, GParamSpec *pspec)
- TalkatuScrolledWindow *sw = TALKATU_SCROLLED_WINDOW(obj);
- g_value_set_object(value, talkatu_scrolled_window_get_child(sw));
- G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
-tatlkatu_scrolled_window_set_property(GObject *obj, guint param_id,
- const GValue *value, GParamSpec *pspec)
- TalkatuScrolledWindow *sw = TALKATU_SCROLLED_WINDOW(obj);
- talkatu_scrolled_window_set_child(sw, g_value_get_object(value));
- G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
-talkatu_scrolled_window_dispose(GObject *obj) {
- TalkatuScrolledWindow *sw = TALKATU_SCROLLED_WINDOW(obj);
- g_clear_pointer(&sw->scrolled_window, gtk_widget_unparent);
- G_OBJECT_CLASS(talkatu_scrolled_window_parent_class)->dispose(obj);
-talkatu_scrolled_window_init(TalkatuScrolledWindow *sw) {
- gtk_widget_init_template(GTK_WIDGET(sw));
- sw->auto_scroll = TRUE;
-talkatu_scrolled_window_class_init(TalkatuScrolledWindowClass *klass) {
- GObjectClass *obj_class = G_OBJECT_CLASS(klass);
- GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
- obj_class->get_property = tatlkatu_scrolled_window_get_property;
- obj_class->set_property = tatlkatu_scrolled_window_set_property;
- obj_class->dispose = talkatu_scrolled_window_dispose;
- * TalkatuScrolledWindow::child:
- * The child widget that should be displayed.
- properties[PROP_CHILD] = g_param_spec_object(
- "The child widget to display",
- gtk_widget_class_set_layout_manager_type(widget_class, GTK_TYPE_BIN_LAYOUT);
- gtk_widget_class_set_template_from_resource(
- "/org/imfreedom/keep/talkatu/talkatu/ui/scrolledwindow.ui"
- gtk_widget_class_bind_template_child_internal(widget_class,
- gtk_widget_class_bind_template_child(widget_class, TalkatuScrolledWindow,
- gtk_widget_class_bind_template_callback(widget_class, talkatu_scrolled_window_vadjustment_changed_cb);
- gtk_widget_class_bind_template_callback(widget_class, talkatu_scrolled_window_vadjustment_value_changed_cb);
-/******************************************************************************
- *****************************************************************************/
- * talkatu_scrolled_window_new:
- * Creates a new #TalkatuScrolledWindow.
- * Returns: (transfer full): The new #TalkatuScrolledWindow instance.
-talkatu_scrolled_window_new(void) {
- return g_object_new(TALKATU_TYPE_SCROLLED_WINDOW, NULL);
- * talkatu_scrolled_window_page_up:
- * @sw: The #TalkatuScrolledWindow instance.
- * Scrolls @sw up one page.
-talkatu_scrolled_window_page_up(TalkatuScrolledWindow *sw) {
- g_return_if_fail(TALKATU_IS_SCROLLED_WINDOW(sw));
- value = gtk_adjustment_get_value(sw->vadjustment);
- value -= gtk_adjustment_get_page_increment(sw->vadjustment);
- gtk_adjustment_set_value(sw->vadjustment, value);
- * talkatu_scrolled_window_page_down:
- * @sw: The #TalkatuScrolledWindow instance.
- * Scrolls @sw down one page.
-talkatu_scrolled_window_page_down(TalkatuScrolledWindow *sw) {
- g_return_if_fail(TALKATU_IS_SCROLLED_WINDOW(sw));
- value = gtk_adjustment_get_value(sw->vadjustment);
- value += gtk_adjustment_get_page_increment(sw->vadjustment);
- gtk_adjustment_set_value(sw->vadjustment, value);
- * talkatu_scrolled_window_get_child:
- * @sw: The scrolled window instance.
- * Gets the child of @sw.
- * Returns: (transfer none) (nullable): The child of @sw.
-talkatu_scrolled_window_get_child(TalkatuScrolledWindow *sw) {
- g_return_val_if_fail(TALKATU_IS_SCROLLED_WINDOW(sw), NULL);
- return gtk_scrolled_window_get_child(GTK_SCROLLED_WINDOW(sw->scrolled_window));
- * talkatu_scrolled_window_set_child:
- * @sw: The scrolled window instance.
- * @child: (nullable): The new child or NULL to unset.
- * Sets the child of @sw to @child.
-talkatu_scrolled_window_set_child(TalkatuScrolledWindow *sw, GtkWidget *child)
- g_return_if_fail(TALKATU_IS_SCROLLED_WINDOW(sw));
- gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(sw->scrolled_window),
--- a/talkatu/talkatuscrolledwindow.h Thu Aug 25 03:22:24 2022 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,47 +0,0 @@
- * Talkatu - GTK widgets for chat applications
- * Copyright (C) 2017-2020 Gary Kramlich <grim@reaperworld.com>
- * This library is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- * You should have received a copy of the GNU General Public License
- * along with this library; if not, see <https://www.gnu.org/licenses/>.
-#if !defined(TALKATU_GLOBAL_HEADER_INSIDE) && !defined(TALKATU_COMPILATION)
-#error "only <talkatu.h> may be included directly"
-#ifndef TALKATU_SCROLLED_WINDOW_H
-#define TALKATU_SCROLLED_WINDOW_H
-#include <glib-object.h>
-#define TALKATU_TYPE_SCROLLED_WINDOW (talkatu_scrolled_window_get_type())
-G_DECLARE_FINAL_TYPE(TalkatuScrolledWindow, talkatu_scrolled_window, TALKATU, SCROLLED_WINDOW, GtkWidget)
-GtkWidget *talkatu_scrolled_window_new(void);
-void talkatu_scrolled_window_page_up(TalkatuScrolledWindow *sw);
-void talkatu_scrolled_window_page_down(TalkatuScrolledWindow *sw);
-GtkWidget *talkatu_scrolled_window_get_child(TalkatuScrolledWindow *sw);
-void talkatu_scrolled_window_set_child(TalkatuScrolledWindow *sw, GtkWidget *child);
-#endif /* TALKATU_SCROLLED_WINDOW_H */