# HG changeset patch
# Node ID b5b63eba0738
# Parent  3c633be6b13e
diff --git a/demo/data/demo.ui b/demo/data/demo.ui
--- a/demo/data/demo.ui
+++ b/demo/data/demo.ui
@@ -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">
     <property name="child">
       <object class="GtkPaned">
@@ -33,14 +34,11 @@
         <property name="orientation">vertical</property>
         <property name="wide-handle">1</property>
         <child>
-          <object class="TalkatuScrolledWindow">
-            <child internal-child="scrolled_window">
-              <object class="GtkScrolledWindow" id="scrolled_window">
-                <child>
-                  <object class="TalkatuHistory" id="history">
-                    <property name="name">history</property>
-                  </object>
-                </child>
+          <object class="GtkScrolledWindow">
+            <property name="vadjustment">auto_scroller</property>
+            <child>
+              <object class="TalkatuHistory" id="history">
+                <property name="name">history</property>
               </object>
             </child>
           </object>
diff --git a/po/POTFILES b/po/POTFILES
--- a/po/POTFILES
+++ b/po/POTFILES
@@ -16,6 +16,7 @@
 talkatu/talkatuattachment.c
 talkatu/talkatuattachmentdialog.c
 talkatu/talkatuattachmentpreview.c
+talkatu/talkatuautoscroller.c
 talkatu/talkatubuffer.c
 talkatu/talkatucodeset.c
 talkatu/talkatucore.c
@@ -29,7 +30,6 @@
 talkatu/talkatumarkdownbuffer.c
 talkatu/talkatumarkup.c
 talkatu/talkatumessage.c
-talkatu/talkatuscrolledwindow.c
 talkatu/talkatusimpleattachment.c
 talkatu/talkatutag.c
 talkatu/talkatutagtable.c
diff --git a/talkatu/data/scrolledwindow.ui b/talkatu/data/scrolledwindow.ui
deleted file mode 100644
--- a/talkatu/data/scrolledwindow.ui
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<interface>
-  <requires lib="gtk" version="4.0"/>
-  <template class="TalkatuScrolledWindow" parent="GtkWidget">
-    <child>
-      <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>
-    </child>
-  </template>
-  <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"/>
-  </object>
-</interface>
diff --git a/talkatu/data/talkatu.gresource.xml b/talkatu/data/talkatu.gresource.xml
--- a/talkatu/data/talkatu.gresource.xml
+++ b/talkatu/data/talkatu.gresource.xml
@@ -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>
diff --git a/talkatu/meson.build b/talkatu/meson.build
--- a/talkatu/meson.build
+++ b/talkatu/meson.build
@@ -8,6 +8,7 @@
 	'talkatuattachment.h',
 	'talkatuattachmentdialog.h',
 	'talkatuattachmentpreview.h',
+	'talkatuautoscroller.h',
 	'talkatubuffer.h',
 	'talkatucodeset.h',
 	'talkatucore.h',
@@ -22,7 +23,6 @@
 	'talkatumarkdownbuffer.h',
 	'talkatumarkup.h',
 	'talkatumessage.h',
-	'talkatuscrolledwindow.h',
 	'talkatusimpleattachment.h',
 	'talkatutag.h',
 	'talkatutagtable.h',
@@ -37,6 +37,7 @@
 	'talkatuattachment.c',
 	'talkatuattachmentdialog.c',
 	'talkatuattachmentpreview.c',
+	'talkatuautoscroller.c',
 	'talkatubuffer.c',
 	'talkatucodeset.c',
 	'talkatucore.c',
@@ -51,7 +52,6 @@
 	'talkatumarkdownbuffer.c',
 	'talkatumarkup.c',
 	'talkatumessage.c',
-	'talkatuscrolledwindow.c',
 	'talkatusimpleattachment.c',
 	'talkatutag.c',
 	'talkatutagtable.c',
diff --git a/talkatu/talkatuscrolledwindow.h b/talkatu/talkatuautoscroller.h
rename from talkatu/talkatuscrolledwindow.h
rename to talkatu/talkatuautoscroller.h
--- a/talkatu/talkatuscrolledwindow.h
+++ b/talkatu/talkatuautoscroller.h
@@ -20,8 +20,8 @@
 #error "only <talkatu.h> may be included directly"
 #endif
 
-#ifndef TALKATU_SCROLLED_WINDOW_H
-#define TALKATU_SCROLLED_WINDOW_H
+#ifndef TALKATU_AUTO_SCROLLER_H
+#define TALKATU_AUTO_SCROLLER_H
 
 #include <glib.h>
 #include <glib-object.h>
@@ -30,18 +30,16 @@
 
 G_BEGIN_DECLS
 
-#define TALKATU_TYPE_SCROLLED_WINDOW (talkatu_scrolled_window_get_type())
+#define TALKATU_TYPE_AUTO_SCROLLER (talkatu_auto_scroller_get_type())
 
-G_DECLARE_FINAL_TYPE(TalkatuScrolledWindow, talkatu_scrolled_window, TALKATU, SCROLLED_WINDOW, GtkWidget)
-
-GtkWidget *talkatu_scrolled_window_new(void);
+G_DECLARE_FINAL_TYPE(TalkatuAutoScroller, talkatu_auto_scroller, TALKATU,
+                     AUTO_SCROLLER, GtkAdjustment)
 
-void talkatu_scrolled_window_page_up(TalkatuScrolledWindow *sw);
-void talkatu_scrolled_window_page_down(TalkatuScrolledWindow *sw);
+GtkAdjustment *talkatu_auto_scroller_new(void);
 
-GtkWidget *talkatu_scrolled_window_get_child(TalkatuScrolledWindow *sw);
-void talkatu_scrolled_window_set_child(TalkatuScrolledWindow *sw, GtkWidget *child);
+void talkatu_auto_scroller_decrement(TalkatuAutoScroller *auto_scroller);
+void talkatu_auto_scroller_increment(TalkatuAutoScroller *auto_scroller);
 
 G_END_DECLS
 
-#endif /* TALKATU_SCROLLED_WINDOW_H */
+#endif /* TALKATU_AUTO_SCROLLER_H */
diff --git a/talkatu/talkatuscrolledwindow.c b/talkatu/talkatuautoscroller.c
rename from talkatu/talkatuscrolledwindow.c
rename to talkatu/talkatuautoscroller.c
--- a/talkatu/talkatuscrolledwindow.c
+++ b/talkatu/talkatuautoscroller.c
@@ -18,151 +18,89 @@
 
 #include <gtk/gtk.h>
 
-#include <talkatu/talkatuscrolledwindow.h>
+#include <talkatu/talkatuautoscroller.h>
 
 /**
- * TalkatuScrolledWindow:
- * This widget is a simple subclass of #GtkScrolledWindow that has helpers for
+ * TalkatuAutoScroller:
+ *
+ * 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
- * bottom when new items are added if the widget was already scrolled all the
+ * max when new items are added if the widget was already scrolled all the
  * way to the bottom.
  */
-struct _TalkatuScrolledWindow {
-	GtkWidget parent;
+struct _TalkatuAutoScroller {
+	GtkAdjustment parent;
 
-	GtkWidget *scrolled_window;
-	GtkAdjustment *vadjustment;
 	gboolean auto_scroll;
 };
 
-enum {
-	PROP_0,
-	PROP_CHILD,
-	N_PROPERTIES,
-};
-static GParamSpec *properties[N_PROPERTIES] = { NULL, };
+G_DEFINE_TYPE(TalkatuAutoScroller, talkatu_auto_scroller, GTK_TYPE_ADJUSTMENT)
 
 /******************************************************************************
  * Callbacks
  *****************************************************************************/
-static void
-talkatu_scrolled_window_vadjustment_changed_cb(GtkAdjustment *adjustment,
-                                               gpointer data)
-{
-	TalkatuScrolledWindow *sw = TALKATU_SCROLLED_WINDOW(data);
+static gboolean
+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;
+}
 
-	if(sw->auto_scroll) {
-		gdouble upper, pagesize;
+static void
+talkatu_auto_scroller_changed(GtkAdjustment *adjustment) {
+	TalkatuAutoScroller *auto_scroller = TALKATU_AUTO_SCROLLER(adjustment);
 
-		upper = gtk_adjustment_get_upper(adjustment);
-		pagesize = gtk_adjustment_get_page_size(adjustment);
-
-		gtk_adjustment_set_value(adjustment, upper - pagesize);
+	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
+		 * then scroll down.
+		 *
+		 * 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);
 	}
 }
 
 static void
-talkatu_scrolled_window_vadjustment_value_changed_cb(GtkAdjustment *adjustment,
-                                                     gpointer data)
-{
-	TalkatuScrolledWindow *sw = TALKATU_SCROLLED_WINDOW(data);
+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);
 
-	sw->auto_scroll = (current + pagesize >= upper);
+	auto_scroller->auto_scroll = (current + pagesize >= upper);
 }
 
 /******************************************************************************
  * GObject Implementation
  *****************************************************************************/
-G_DEFINE_TYPE(TalkatuScrolledWindow, talkatu_scrolled_window, GTK_TYPE_WIDGET)
-
 static void
-tatlkatu_scrolled_window_get_property(GObject *obj, guint param_id,
-                                      GValue *value, GParamSpec *pspec)
-{
-	TalkatuScrolledWindow *sw = TALKATU_SCROLLED_WINDOW(obj);
-
-	switch(param_id) {
-		case PROP_CHILD:
-			g_value_set_object(value, talkatu_scrolled_window_get_child(sw));
-			break;
-		default:
-			G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
-			break;
-	}
-}
-
-static void
-tatlkatu_scrolled_window_set_property(GObject *obj, guint param_id,
-                                      const GValue *value, GParamSpec *pspec)
-{
-	TalkatuScrolledWindow *sw = TALKATU_SCROLLED_WINDOW(obj);
-
-	switch(param_id) {
-		case PROP_CHILD:
-			talkatu_scrolled_window_set_child(sw, g_value_get_object(value));
-			break;
-		default:
-			G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
-			break;
-	}
+talkatu_auto_scroller_init(TalkatuAutoScroller *auto_scroller) {
+	auto_scroller->auto_scroll = TRUE;
 }
 
 static void
-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);
-}
-
-static void
-talkatu_scrolled_window_init(TalkatuScrolledWindow *sw) {
-	gtk_widget_init_template(GTK_WIDGET(sw));
-
-	sw->auto_scroll = TRUE;
-}
-
-static void
-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;
+talkatu_auto_scroller_class_init(TalkatuAutoScrollerClass *klass) {
+	GtkAdjustmentClass *adjustment_class = GTK_ADJUSTMENT_CLASS(klass);
 
-	/**
-	 * TalkatuScrolledWindow::child:
-	 *
-	 * The child widget that should be displayed.
-	 */
-	properties[PROP_CHILD] = g_param_spec_object(
-		"child", "child",
-		"The child widget to display",
-		GTK_TYPE_WIDGET,
-		G_PARAM_READWRITE);
-
-	gtk_widget_class_set_layout_manager_type(widget_class, GTK_TYPE_BIN_LAYOUT);
-
-	gtk_widget_class_set_template_from_resource(
-		widget_class,
-		"/org/imfreedom/keep/talkatu/talkatu/ui/scrolledwindow.ui"
-	);
-
-	gtk_widget_class_bind_template_child_internal(widget_class,
-	                                              TalkatuScrolledWindow,
-	                                              scrolled_window);
-	gtk_widget_class_bind_template_child(widget_class, TalkatuScrolledWindow,
-	                                     vadjustment);
-
-	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);
+	adjustment_class->changed = talkatu_auto_scroller_changed;
+	adjustment_class->value_changed = talkatu_auto_scroller_value_changed;
 }
 
 /******************************************************************************
@@ -170,78 +108,47 @@
  *****************************************************************************/
 
 /**
- * talkatu_scrolled_window_new:
+ * talkatu_auto_scroller_new:
  *
- * Creates a new #TalkatuScrolledWindow.
+ * Creates a new #TalkatuAutoScroller.
  *
- * Returns: (transfer full): The new #TalkatuScrolledWindow instance.
+ * Returns: (transfer full): The new #TalkatuAutoScroller instance.
  */
-GtkWidget *
-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.
- */
-void
-talkatu_scrolled_window_page_up(TalkatuScrolledWindow *sw) {
-	gdouble value = 0.0;
-
-	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);
+GtkAdjustment *
+talkatu_auto_scroller_new(void) {
+	return g_object_new(TALKATU_TYPE_AUTO_SCROLLER, NULL);
 }
 
 /**
- * talkatu_scrolled_window_page_down:
- * @sw: The #TalkatuScrolledWindow instance.
+ * talkatu_auto_scroller_decrement:
+ * @auto_scroller: The #TalkatuAutoScroller instance.
  *
- * Scrolls @sw down one page.
+ * Decrements the value of @auto_scroller by a page increment.
  */
 void
-talkatu_scrolled_window_page_down(TalkatuScrolledWindow *sw) {
+talkatu_auto_scroller_decrement(TalkatuAutoScroller *auto_scroller) {
 	gdouble value = 0.0;
 
-	g_return_if_fail(TALKATU_IS_SCROLLED_WINDOW(sw));
+	g_return_if_fail(TALKATU_IS_AUTO_SCROLLER(auto_scroller));
 
-	value = gtk_adjustment_get_value(sw->vadjustment);
-	value += gtk_adjustment_get_page_increment(sw->vadjustment);
-	gtk_adjustment_set_value(sw->vadjustment, value);
+	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_scrolled_window_get_child:
- * @sw: The scrolled window instance.
- *
- * Gets the child of @sw.
+ * talkatu_auto_scroller_increment:
+ * @auto_scroller: The #TalkatuAutoScroller instance.
  *
- * Returns: (transfer none) (nullable): The child of @sw.
- */
-GtkWidget *
-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.
+ * Increments the value of @auto_scroller by a page increment.
  */
 void
-talkatu_scrolled_window_set_child(TalkatuScrolledWindow *sw, GtkWidget *child)
-{
-	g_return_if_fail(TALKATU_IS_SCROLLED_WINDOW(sw));
+talkatu_auto_scroller_page_down(TalkatuAutoScroller *auto_scroller) {
+	gdouble value = 0.0;
+
+	g_return_if_fail(TALKATU_IS_AUTO_SCROLLER(auto_scroller));
 
-	gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(sw->scrolled_window),
-	                              child);
+	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);
 }
