talkatu/talkatu

bf428b4a68da
Parents 8fc3991a77d0
Children 3b6c635cef20
Move TalkatuHistory from a GtkTextView base to GtkListBox

Make TalkatuInput return the local time if a timestamp has not yet been set.

Add an toggle button for setting the message's edited state

Added TalkatuHistoryRow which is what will be used to display message

Add TalkatuHistoryRow to the documentation.

Add TalkatuScrolledWindow that subclasses GtkScrolledWindow but autoscrolls and has keybindings callbacks for page up and page down.

Testing Done:
Compile, docs, and manual testing in the demo. The i18n stuff is broken for unrelated reasons, which I'll fix in another review request.

Bugs closed: TALKATU-78

Reviewed at https://reviews.imfreedom.org/r/56/
--- a/demo/data/demo.ui Sun Aug 02 16:14:42 2020 -0500
+++ b/demo/data/demo.ui Wed Aug 19 04:05:05 2020 -0500
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-<!-- Generated with glade 3.22.1
+<!-- Generated with glade 3.22.2
Talkatu
Copyright (C) 2017-2020 Gary Kramlich <grim@reaperworld.com>
@@ -22,25 +22,6 @@
<interface>
<requires lib="Talkatu" version="0.0"/>
<requires lib="gtk+" version="3.18"/>
- <object class="TalkatuTagTable" id="table_history"/>
- <object class="TalkatuHistoryBuffer" id="buffer_history">
- <property name="tag_table">table_history</property>
- </object>
- <object class="TalkatuTagTable" id="table_html"/>
- <object class="TalkatuHtmlBuffer" id="buffer_html">
- <property name="tag_table">table_html</property>
- <signal name="changed" handler="talkatu_demo_window_buffer_modified_cb" object="TalkatuDemoWindow" swapped="no"/>
- </object>
- <object class="TalkatuTagTable" id="table_markdown"/>
- <object class="TalkatuMarkdownBuffer" id="buffer_markdown">
- <property name="tag_table">table_markdown</property>
- <signal name="changed" handler="talkatu_demo_window_buffer_modified_cb" object="TalkatuDemoWindow" swapped="no"/>
- </object>
- <object class="TalkatuTagTable" id="table_plain"/>
- <object class="TalkatuBuffer" id="buffer_plain">
- <property name="tag_table">table_plain</property>
- <signal name="changed" handler="talkatu_demo_window_buffer_modified_cb" object="TalkatuDemoWindow" swapped="no"/>
- </object>
<!-- interface-license-type gplv2 -->
<!-- interface-name Talkatu -->
<!-- interface-description GTK+ widgets for chat applications -->
@@ -49,7 +30,7 @@
<property name="can_focus">False</property>
<property name="border_width">12</property>
<signal name="delete-event" handler="talkatu_demo_window_closed_cb" swapped="no"/>
- <child>
+ <child type="titlebar">
<placeholder/>
</child>
<child>
@@ -59,20 +40,18 @@
<property name="orientation">vertical</property>
<property name="wide_handle">True</property>
<child>
- <object class="GtkScrolledWindow">
+ <object class="TalkatuScrolledWindow">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="shadow_type">in</property>
<child>
- <object class="TalkatuHistory" id="view_history">
- <property name="name">view_history</property>
+ <object class="TalkatuHistory" id="history">
+ <property name="name">history</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="hexpand_set">True</property>
<property name="vexpand_set">True</property>
- <property name="editable">False</property>
- <property name="wrap_mode">word</property>
</object>
</child>
</object>
@@ -232,7 +211,7 @@
<object class="GtkToggleToolButton" id="toggle_toolbar">
<property name="visible">True</property>
<property name="can_focus">False</property>
- <property name="tooltip_text" translatable="yes">Toogle toolbar visibility</property>
+ <property name="tooltip_text" translatable="yes">Toggle toolbar visibility</property>
<property name="label" translatable="yes">Toolbar</property>
<property name="use_underline">True</property>
</object>
@@ -254,6 +233,19 @@
<property name="homogeneous">True</property>
</packing>
</child>
+ <child>
+ <object class="GtkToggleToolButton" id="toggle_edited">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="tooltip_text" translatable="yes">Toggle whether the message is edited</property>
+ <property name="label" translatable="yes">Edited</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
</object>
<packing>
<property name="expand">False</property>
@@ -435,6 +427,22 @@
</object>
</child>
</object>
+ <object class="TalkatuTagTable" id="table_history"/>
+ <object class="TalkatuTagTable" id="table_html"/>
+ <object class="TalkatuHtmlBuffer" id="buffer_html">
+ <property name="tag_table">table_html</property>
+ <signal name="changed" handler="talkatu_demo_window_buffer_modified_cb" object="TalkatuDemoWindow" swapped="no"/>
+ </object>
+ <object class="TalkatuTagTable" id="table_markdown"/>
+ <object class="TalkatuMarkdownBuffer" id="buffer_markdown">
+ <property name="tag_table">table_markdown</property>
+ <signal name="changed" handler="talkatu_demo_window_buffer_modified_cb" object="TalkatuDemoWindow" swapped="no"/>
+ </object>
+ <object class="TalkatuTagTable" id="table_plain"/>
+ <object class="TalkatuBuffer" id="buffer_plain">
+ <property name="tag_table">table_plain</property>
+ <signal name="changed" handler="talkatu_demo_window_buffer_modified_cb" object="TalkatuDemoWindow" swapped="no"/>
+ </object>
<object class="TalkatuWholeBuffer" id="buffer_whole">
<property name="tag_table">table_plain</property>
<property name="style">whole</property>
--- a/demo/talkatudemowindow.c Sun Aug 02 16:14:42 2020 -0500
+++ b/demo/talkatudemowindow.c Wed Aug 19 04:05:05 2020 -0500
@@ -23,7 +23,7 @@
struct _TalkatuDemoWindow {
GtkWindow parent;
- GtkWidget *view_history;
+ GtkWidget *history;
GtkWidget *editor;
GtkWidget *typing;
@@ -34,12 +34,12 @@
GtkToggleToolButton *toggle_toolbar;
GtkToggleToolButton *toggle_send_button;
+ GtkToggleToolButton *toggle_edited;
GtkTextBuffer *buffer_plain;
GtkTextBuffer *buffer_whole;
GtkTextBuffer *buffer_html;
GtkTextBuffer *buffer_markdown;
- GtkTextBuffer *buffer_history;
GtkWidget *author_button;
GtkWidget *author_popover;
@@ -222,8 +222,8 @@
talkatu_message_set_id(TALKATU_MESSAGE(input), id++);
- talkatu_history_buffer_write_message(
- TALKATU_HISTORY_BUFFER(window->buffer_history),
+ talkatu_history_write_message(
+ TALKATU_HISTORY(window->history),
TALKATU_MESSAGE(input)
);
@@ -276,8 +276,6 @@
talkatu_demo_window_init(TalkatuDemoWindow *window) {
gtk_widget_init_template(GTK_WIDGET(window));
- gtk_text_view_set_buffer(GTK_TEXT_VIEW(window->view_history), window->buffer_history);
-
/* activate the first menu item to make sure its label gets stored
* correctly.
*/
@@ -294,6 +292,12 @@
window->toggle_send_button, "active",
G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL
);
+
+ g_object_bind_property(
+ talkatu_editor_get_input(TALKATU_EDITOR(window->editor)), "edited",
+ window->toggle_edited, "active",
+ G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL
+ );
}
static void
@@ -305,7 +309,7 @@
"/org/bitbucket/rw_grim/talkatu/ui/demo/demo.ui"
);
- gtk_widget_class_bind_template_child(widget_class, TalkatuDemoWindow, view_history);
+ gtk_widget_class_bind_template_child(widget_class, TalkatuDemoWindow, history);
gtk_widget_class_bind_template_child(widget_class, TalkatuDemoWindow, editor);
gtk_widget_class_bind_template_child(widget_class, TalkatuDemoWindow, typing);
@@ -313,7 +317,6 @@
gtk_widget_class_bind_template_child(widget_class, TalkatuDemoWindow, buffer_whole);
gtk_widget_class_bind_template_child(widget_class, TalkatuDemoWindow, buffer_html);
gtk_widget_class_bind_template_child(widget_class, TalkatuDemoWindow, buffer_markdown);
- gtk_widget_class_bind_template_child(widget_class, TalkatuDemoWindow, buffer_history);
gtk_widget_class_bind_template_child(widget_class, TalkatuDemoWindow, toggle_plain);
gtk_widget_class_bind_template_child(widget_class, TalkatuDemoWindow, toggle_whole);
@@ -326,6 +329,7 @@
gtk_widget_class_bind_template_child(widget_class, TalkatuDemoWindow, toggle_toolbar);
gtk_widget_class_bind_template_child(widget_class, TalkatuDemoWindow, toggle_send_button);
+ gtk_widget_class_bind_template_child(widget_class, TalkatuDemoWindow, toggle_edited);
gtk_widget_class_bind_template_callback(widget_class, talkatu_demo_window_closed_cb);
gtk_widget_class_bind_template_callback(widget_class, talkatu_demo_window_buffer_changed_cb);
--- a/po/POTFILES Sun Aug 02 16:14:42 2020 -0500
+++ b/po/POTFILES Wed Aug 19 04:05:05 2020 -0500
@@ -4,6 +4,7 @@
talkatu/data/attachmentdialog.ui
talkatu/data/attachmentpreview.ui
talkatu/data/editor.ui
+talkatu/data/historyrow.ui
talkatu/data/linkdialog.ui
talkatu/data/messageactions.ui
talkatu/data/toolbar.ui
@@ -15,7 +16,7 @@
talkatu/talkatucodeset.c
talkatu/talkatucore.c
talkatu/talkatueditor.c
-talkatu/talkatuhistorybuffer.c
+talkatu/talkatuhistoryrow.c
talkatu/talkatuhistory.c
talkatu/talkatuhtmlbuffer.c
talkatu/talkatuinput.c
@@ -25,6 +26,7 @@
talkatu/talkatumenutoolbutton.c
talkatu/talkatumessageactions.c
talkatu/talkatumessage.c
+talkatu/talkatuscrolledwindow.c
talkatu/talkatusimpleattachment.c
talkatu/talkatutag.c
talkatu/talkatutagtable.c
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/talkatu/data/historyrow.ui Wed Aug 19 04:05:05 2020 -0500
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.2
+
+Talkatu
+Copyright (C) 2017-2020 Gary Kramlich <grim@reaperworld.com>
+
+This program 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 program 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 program; if not, see <http://www.gnu.org/licenses/>.
+
+-->
+<interface>
+ <requires lib="gtk+" version="3.18"/>
+ <!-- interface-license-type gplv2 -->
+ <!-- interface-name Talkatu -->
+ <!-- interface-description GTK widgets for chat applications -->
+ <!-- interface-copyright Gary Kramlich <grim@reaperworld.com> -->
+ <template class="TalkatuHistoryRow" parent="GtkListBoxRow">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkEventBox" id="avatar_event">
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkImage" id="avatar_image">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="stock">gtk-missing-image</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">8</property>
+ <child>
+ <object class="GtkLabel" id="author">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="timestamp">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="edited">
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">(edited)</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="content">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">0</property>
+ <property name="yalign">0</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>
--- a/talkatu/data/talkatu.gresource.xml Sun Aug 02 16:14:42 2020 -0500
+++ b/talkatu/data/talkatu.gresource.xml Wed Aug 19 04:05:05 2020 -0500
@@ -4,6 +4,7 @@
<file compressed="true">attachmentdialog.ui</file>
<file compressed="true">attachmentpreview.ui</file>
<file compressed="true">editor.ui</file>
+ <file compressed="true">historyrow.ui</file>
<file compressed="true">linkdialog.ui</file>
<file compressed="true">messageactions.ui</file>
<file compressed="true">toolbar.ui</file>
--- a/talkatu/meson.build Sun Aug 02 16:14:42 2020 -0500
+++ b/talkatu/meson.build Wed Aug 19 04:05:05 2020 -0500
@@ -13,7 +13,7 @@
'talkatucore.h',
'talkatueditor.h',
'talkatuhistory.h',
- 'talkatuhistorybuffer.h',
+ 'talkatuhistoryrow.h',
'talkatuhtmlbuffer.h',
'talkatuinput.h',
'talkatulinkdialog.h',
@@ -22,6 +22,7 @@
'talkatumenutoolbutton.h',
'talkatumessage.h',
'talkatumessageactions.h',
+ 'talkatuscrolledwindow.h',
'talkatusimpleattachment.h',
'talkatutag.h',
'talkatutagtable.h',
@@ -42,7 +43,7 @@
'talkatucore.c',
'talkatueditor.c',
'talkatuhistory.c',
- 'talkatuhistorybuffer.c',
+ 'talkatuhistoryrow.c',
'talkatuhtmlbuffer.c',
'talkatuinput.c',
'talkatulinkdialog.c',
@@ -51,6 +52,7 @@
'talkatumenutoolbutton.c',
'talkatumessage.c',
'talkatumessageactions.c',
+ 'talkatuscrolledwindow.c',
'talkatusimpleattachment.c',
'talkatutag.c',
'talkatutagtable.c',
--- a/talkatu/reference/talkatu-docs.xml Sun Aug 02 16:14:42 2020 -0500
+++ b/talkatu/reference/talkatu-docs.xml Wed Aug 19 04:05:05 2020 -0500
@@ -37,8 +37,8 @@
<xi:include href="xml/talkatumarkup.xml"/>
- <xi:include href="xml/talkatuhistorybuffer.xml"/>
<xi:include href="xml/talkatuhistory.xml"/>
+ <xi:include href="xml/talkatuhistoryrow.xml"/>
<xi:include href="xml/talkatuinput.xml"/>
@@ -52,6 +52,8 @@
<xi:include href="xml/talkatutooldrawer.xml"/>
<xi:include href="xml/talkatumenutoolbutton.xml"/>
+ <xi:include href="xml/talkatuscrolledwindow.xml"/>
+
<xi:include href="xml/talkatutypinglabel.xml"/>
<xi:include href="xml/talkatuattachment.xml"/>
--- a/talkatu/talkatu.xml Sun Aug 02 16:14:42 2020 -0500
+++ b/talkatu/talkatu.xml Wed Aug 19 04:05:05 2020 -0500
@@ -12,9 +12,11 @@
<glade-widget-class name="TalkatuAttachmentDialog" generic-name="attachment_dialog" title="Attachment Dialog"/>
<glade-widget-class name="TalkatuAttachmentPreview" generic-name="attachment_preview" title="Attachment Preview"/>
<glade-widget-class name="TalkatuHistory" generic-name="history" title="History"/>
+ <glade-widget-class name="TalkatuHistoryRow" generic-name="history_row" title="HistoryRow"/>
<glade-widget-class name="TalkatuInput" generic-name="input" title="Input"/>
<glade-widget-class name="TalkatuLinkDialog" generic-name="link_dialog" title="LinkDialog"/>
<glade-widget-class name="TalkatuMenuToolButton" generic-name="menu_tool_button" title="MenuToolButton"/>
+ <glade-widget-class name="TalkatuScrolledWindow" generic-name="scrolled_window" title="ScrolledWindow"/>
<glade-widget-class name="TalkatuToolDrawer" generic-name="tool_drawer" title="ToolDrawer"/>
<glade-widget-class name="TalkatuToolbar" generic-name="toolbar" title="Toolbar"/>
<glade-widget-class name="TalkatuView" generic-name="view" title="View"/>
@@ -45,11 +47,6 @@
</properties>
</glade-widget-class>
- <glade-widget-class name="TalkatuHistoryBuffer" generic-name="buffer" title="History Buffer" toplevel="True">
- <properties>
- <property translatable="True" multiline="True" id="text"/>
- </properties>
- </glade-widget-class>
</glade-widget-classes>
<glade-widget-group name="Talkatu" title="Talkatu">
<glade-widget-class-ref name="TalkatuAttachmentDialog"/>
@@ -57,12 +54,13 @@
<glade-widget-class-ref name="TalkatuBuffer"/>
<glade-widget-class-ref name="TalkatuEditor"/>
<glade-widget-class-ref name="TalkatuHistory"/>
- <glade-widget-class-ref name="TalkatuHistoryBuffer"/>
+ <glade-widget-class-ref name="TalkatuHistoryRow"/>
<glade-widget-class-ref name="TalkatuHtmlBuffer"/>
<glade-widget-class-ref name="TalkatuInput"/>
<glade-widget-class-ref name="TalkatuLinkDialog"/>
<glade-widget-class-ref name="TalkatuMarkdownBuffer"/>
<glade-widget-class-ref name="TalkatuMenuToolButton"/>
+ <glade-widget-class-ref name="TalkatuScrolledWindow"/>
<glade-widget-class-ref name="TalkatuToolDrawer"/>
<glade-widget-class-ref name="TalkatuToolbar"/>
<glade-widget-class-ref name="TalkatuTypingLabel"/>
--- a/talkatu/talkatuhistory.c Sun Aug 02 16:14:42 2020 -0500
+++ b/talkatu/talkatuhistory.c Wed Aug 19 04:05:05 2020 -0500
@@ -20,7 +20,7 @@
#include <talkatu/talkatuhistory.h>
#include <talkatu/talkatuattachmentpreview.h>
-#include <talkatu/talkatuhistorybuffer.h>
+#include <talkatu/talkatuhistoryrow.h>
#include <talkatu/talkatumessage.h>
/**
@@ -44,137 +44,20 @@
* A #TalkatuView subclass that is used to display a conversation.
*/
struct _TalkatuHistory {
- TalkatuView parent;
-
- GtkAdjustment *vadjustment;
- gboolean auto_scroll;
+ GtkListBox parent;
};
-G_DEFINE_TYPE(TalkatuHistory, talkatu_history, TALKATU_TYPE_VIEW)
-
-/******************************************************************************
- * Callbacks
- *****************************************************************************/
-static void
-talkatu_history_vadjustment_changed_cb(GtkAdjustment *adjustment,
- gpointer data)
-{
- TalkatuHistory *history = TALKATU_HISTORY(data);
-
- if(history->auto_scroll) {
- gdouble upper, pagesize;
-
- upper = gtk_adjustment_get_upper(adjustment);
- pagesize = gtk_adjustment_get_page_size(adjustment);
-
- gtk_adjustment_set_value(adjustment, upper - pagesize);
- }
-}
-
-static void
-talkatu_history_vadjustment_value_changed_cb(GtkAdjustment *adjustment,
- gpointer data)
-{
- TalkatuHistory *history = TALKATU_HISTORY(data);
- gdouble current, upper, pagesize;
-
- current = gtk_adjustment_get_value(adjustment);
- upper = gtk_adjustment_get_upper(adjustment);
- pagesize = gtk_adjustment_get_page_size(adjustment);
-
- history->auto_scroll = (current + pagesize >= upper);
-}
-
-static void
-talkatu_history_vadjustment_notify_cb(GObject *obj, GParamSpec *pspec,
- gpointer data)
-{
- TalkatuHistory *history = TALKATU_HISTORY(obj);
- GtkAdjustment *adjustment = NULL;
-
- adjustment = gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(obj));
- if(g_set_object(&history->vadjustment, adjustment)) {
- history->auto_scroll = TRUE;
-
- g_signal_connect(G_OBJECT(adjustment), "value-changed",
- G_CALLBACK(talkatu_history_vadjustment_value_changed_cb),
- history);
- g_signal_connect(G_OBJECT(adjustment), "changed",
- G_CALLBACK(talkatu_history_vadjustment_changed_cb),
- history);
- }
-}
-
-static void
-talkatu_history_insert_child_anchor_cb(GtkTextBuffer *buffer, GtkTextIter *iter,
- GtkTextChildAnchor *anchor,
- gpointer data)
-{
- TalkatuAttachment *attachment = NULL;
- GtkTextView *view = GTK_TEXT_VIEW(data);
-
- attachment = g_object_get_data(G_OBJECT(anchor), "talkatu:attachment");
- if(TALKATU_IS_ATTACHMENT(attachment)) {
- GtkWidget *preview = talkatu_attachment_preview_new(attachment);
- gtk_text_view_add_child_at_anchor(view, preview, anchor);
- gtk_widget_show(preview);
- }
-}
-
-static void
-talkatu_history_buffer_set_cb(GObject *view, GParamSpec *pspec, gpointer data) {
- GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(view));
-
- g_signal_handlers_disconnect_by_func(G_OBJECT(buffer),
- G_CALLBACK(talkatu_history_insert_child_anchor_cb),
- view);
- g_signal_connect_after(G_OBJECT(buffer), "insert-child-anchor",
- G_CALLBACK(talkatu_history_insert_child_anchor_cb),
- view);
-}
-
-/******************************************************************************
- * GtkTextViewClass overrides
- *****************************************************************************/
-static GtkTextBuffer *
-talkatu_history_create_buffer(GtkTextView *view) {
- return talkatu_history_buffer_new();
-}
+G_DEFINE_TYPE(TalkatuHistory, talkatu_history, GTK_TYPE_LIST_BOX)
/******************************************************************************
* GObject Stuff
*****************************************************************************/
static void
talkatu_history_init(TalkatuHistory *history) {
- gtk_text_view_set_editable(GTK_TEXT_VIEW(history), FALSE);
- gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(history), FALSE);
-
- g_signal_connect(G_OBJECT(history), "notify::buffer",
- G_CALLBACK(talkatu_history_buffer_set_cb), NULL);
-
- history->auto_scroll = TRUE;
-
- g_signal_connect(G_OBJECT(history), "notify::vadjustment",
- G_CALLBACK(talkatu_history_vadjustment_notify_cb), NULL);
-}
-
-static void
-talkatu_history_finalize(GObject *obj) {
- TalkatuHistory *history = TALKATU_HISTORY(obj);
-
- g_clear_object(&history->vadjustment);
-
- G_OBJECT_CLASS(talkatu_history_parent_class)->finalize(obj);
}
static void
talkatu_history_class_init(TalkatuHistoryClass *klass) {
- GObjectClass *obj_class = G_OBJECT_CLASS(klass);
- GtkTextViewClass *text_view_class = GTK_TEXT_VIEW_CLASS(klass);
-
- obj_class->finalize = talkatu_history_finalize;
-
- text_view_class->create_buffer = talkatu_history_create_buffer;
}
/******************************************************************************
@@ -196,40 +79,22 @@
}
/**
- * talkatu_history_page_up:
+ * talkatu_history_write_message:
* @history: The #TalkatuHistory instance.
+ * @message: The #TalkatuMessage to add to @history.
*
- * Scrolls @history up one page.
+ * Adds @message to @history. Messages are sorted by timestamp so make sure
+ * it is set correctly.
*/
void
-talkatu_history_page_up(TalkatuHistory *history) {
- g_return_if_fail(TALKATU_IS_HISTORY(history));
-
- if(history->vadjustment != NULL) {
- double value = 0.0;
-
- value = gtk_adjustment_get_value(history->vadjustment);
- value -= gtk_adjustment_get_page_increment(history->vadjustment);
- gtk_adjustment_set_value(history->vadjustment, value);
- }
-}
+talkatu_history_write_message(TalkatuHistory *history,
+ TalkatuMessage *message)
+{
+ GtkWidget *row = NULL;
-/**
- * talkatu_history_page_down:
- * @history: The #TalkatuHistory instance.
- *
- * Scrolls @history down one page.
- */
-void
-talkatu_history_page_down(TalkatuHistory *history) {
g_return_if_fail(TALKATU_IS_HISTORY(history));
+ g_return_if_fail(TALKATU_IS_MESSAGE(message));
- if(history->vadjustment != NULL) {
- double value = 0.0;
-
- value = gtk_adjustment_get_value(history->vadjustment);
- value += gtk_adjustment_get_page_increment(history->vadjustment);
- gtk_adjustment_set_value(history->vadjustment, value);
- }
+ row = talkatu_history_row_new(message);
+ gtk_container_add(GTK_CONTAINER(history), row);
}
-
--- a/talkatu/talkatuhistory.h Sun Aug 02 16:14:42 2020 -0500
+++ b/talkatu/talkatuhistory.h Wed Aug 19 04:05:05 2020 -0500
@@ -28,18 +28,17 @@
#include <gtk/gtk.h>
-#include <talkatu/talkatuview.h>
+#include <talkatu/talkatumessage.h>
G_BEGIN_DECLS
-#define TALKATU_TYPE_HISTORY (talkatu_history_get_type())
+#define TALKATU_TYPE_HISTORY (talkatu_history_get_type())
-G_DECLARE_FINAL_TYPE(TalkatuHistory, talkatu_history, TALKATU, HISTORY, TalkatuView)
+G_DECLARE_FINAL_TYPE(TalkatuHistory, talkatu_history, TALKATU, HISTORY, GtkListBox)
GtkWidget *talkatu_history_new(void);
-void talkatu_history_page_up(TalkatuHistory *history);
-void talkatu_history_page_down(TalkatuHistory *history);
+void talkatu_history_write_message(TalkatuHistory *history, TalkatuMessage *message);
G_END_DECLS
--- a/talkatu/talkatuhistorybuffer.c Sun Aug 02 16:14:42 2020 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,209 +0,0 @@
-/*
- * talkatu
- * Copyright (C) 2017-2019 Gary Kramlich <grim@reaperworld.com>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser 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
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, see <http://www.gnu.org/licenses/>.
- */
-#include <gtk/gtk.h>
-#include <glib/gi18n-lib.h>
-
-#include <talkatu/talkatuhistorybuffer.h>
-#include <talkatu/talkatutag.h>
-#include <talkatu/talkatutagtable.h>
-
-/**
- * SECTION:talkatuhistorybuffer
- * @Title: History Buffer
- * @Short_description: The buffer used for displaying chat history
- *
- * This buffer is used to displayed the chat history. It has some helper
- * methods to make it easier to add messages to it as text should not be
- * manually.
- */
-
-/**
- * TALKATU_TYPE_HISTORY_BUFFER:
- *
- * The standard _get_type macro for #TalkatuHistoryBuffer.
- */
-
-/**
- * TalkatuHistoryBuffer:
- *
- * A #TalkatuBuffer subclass that works on #TalkatuMessage's rather than raw
- * text.
- */
-
-/**
- * TalkatuHistoryBufferClass:
- * @write_message: The write_message vfunc is called to render a
- * #TalkatuMessage into the #TalkatuHistoryBuffer.
- *
- * The backing class to #TalkatuHistoryBuffer.
- */
-
-typedef struct {
- GtkTextBuffer *buffer;
- GtkTextIter *iter;
-} TalkatuHistoryBufferForeachAttachmentData;
-
-G_DEFINE_TYPE(TalkatuHistoryBuffer, talkatu_history_buffer, GTK_TYPE_TEXT_BUFFER);
-
-/******************************************************************************
- * Helpers
- *****************************************************************************/
-static void
-talkatu_history_buffer_add_attachment(TalkatuAttachment *attachment,
- gpointer data)
-{
- TalkatuHistoryBufferForeachAttachmentData *d = NULL;
- GtkTextChildAnchor *anchor = NULL;
-
- d = (TalkatuHistoryBufferForeachAttachmentData *)data;
-
- gtk_text_buffer_insert(d->buffer, d->iter, "\n", -1);
-
- /* we have to create the anchor first so that we can add the attachment to
- * it before it's inserted into the buffer.
- */
- anchor = gtk_text_child_anchor_new();
- g_object_set_data_full(G_OBJECT(anchor), "talkatu:attachment",
- g_object_ref(attachment), g_object_unref);
- gtk_text_buffer_insert_child_anchor(d->buffer, d->iter, anchor);
- g_object_unref(G_OBJECT(anchor));
-}
-
-/******************************************************************************
- * GObject Stuff
- *****************************************************************************/
-static void
-talkatu_history_buffer_real_write_message(TalkatuHistoryBuffer *talkatu_buffer,
- TalkatuMessage *message)
-{
- TalkatuHistoryBufferForeachAttachmentData data;
- GtkTextBuffer *buffer = GTK_TEXT_BUFFER(talkatu_buffer);
- GtkTextIter start, end;
- GDateTime *timestamp = NULL;
- gchar *author = NULL, *contents = NULL;
-
- /* if there's already text in the buffer, insert a newline */
- gtk_text_buffer_get_bounds(buffer, &start, &end);
- if(!gtk_text_iter_equal(&start, &end)) {
- gtk_text_buffer_insert(buffer, &end, "\n", -1);
- }
-
- timestamp = talkatu_message_get_timestamp(message);
- if(timestamp != NULL) {
- gchar *timestamp_str = g_date_time_format(timestamp, "(%-l:%M %p) ");
-
- gtk_text_buffer_insert_with_tags_by_name(
- buffer,
- &end,
- timestamp_str,
- -1,
- TALKATU_TAG_TIMESTAMP,
- NULL
- );
-
- g_free(timestamp_str);
- g_date_time_unref(timestamp);
- }
-
- author = talkatu_message_get_author(message);
- gtk_text_buffer_insert_with_tags_by_name(
- buffer,
- &end,
- author,
- -1,
- TALKATU_TAG_BOLD,
- TALKATU_TAG_AUTHOR,
- NULL
- );
- g_free(author);
-
- /* insert a space after the author */
- gtk_text_buffer_insert(buffer, &end, " ", -1);
-
- contents = talkatu_message_get_contents(message);
- gtk_text_buffer_insert_with_tags_by_name(
- buffer,
- &end,
- contents,
- -1,
- TALKATU_TAG_CONTENTS,
- NULL
- );
- g_free(contents);
-
- /* add any and all attachments */
- data.buffer = buffer;
- data.iter = &end;
- talkatu_message_foreach_attachment(message,
- talkatu_history_buffer_add_attachment,
- &data);
-}
-
-/******************************************************************************
- * GObject Stuff
- *****************************************************************************/
-static void
-talkatu_history_buffer_init(TalkatuHistoryBuffer *buffer) {
-}
-
-static void
-talkatu_history_buffer_class_init(TalkatuHistoryBufferClass *klass) {
- klass->write_message = talkatu_history_buffer_real_write_message;
-}
-
-/******************************************************************************
- * Public API
- *****************************************************************************/
-
-/**
- * talkatu_history_buffer_new:
- *
- * Creates a new TalkatuHistoryBuffer for display conversation history.
- *
- * Returns: (transfer full): The #TalkatuHistoryBuffer instance.
- */
-GtkTextBuffer *
-talkatu_history_buffer_new(void) {
- return GTK_TEXT_BUFFER(g_object_new(
- TALKATU_TYPE_HISTORY_BUFFER,
- "tag-table", talkatu_tag_table_new(),
- NULL
- ));
-}
-
-/**
- * talkatu_history_buffer_write_message:
- * @buffer: The #TalkatuHistoryBuffer instance.
- * @message: The #TalkatuMessage to write.
- *
- * Writes @message to @buffer.
- */
-void
-talkatu_history_buffer_write_message(TalkatuHistoryBuffer *buffer,
- TalkatuMessage *message)
-{
- TalkatuHistoryBufferClass *klass = NULL;
-
- g_return_if_fail(TALKATU_IS_HISTORY_BUFFER(buffer));
- g_return_if_fail(message != NULL);
-
- klass = TALKATU_HISTORY_BUFFER_GET_CLASS(buffer);
- if(klass && klass->write_message) {
- klass->write_message(buffer, message);
- }
-}
--- a/talkatu/talkatuhistorybuffer.h Sun Aug 02 16:14:42 2020 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,54 +0,0 @@
-/*
- * talkatu
- * Copyright (C) 2017-2019 Gary Kramlich <grim@reaperworld.com>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser 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
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, see <http://www.gnu.org/licenses/>.
- */
-
-#if !defined(TALKATU_GLOBAL_HEADER_INSIDE) && !defined(TALKATU_COMPILATION)
-#error "only <talkatu.h> may be included directly"
-#endif
-
-#ifndef TALKATU_HISTORY_BUFFER_H
-#define TALKATU_HISTORY_BUFFER_H
-
-#include <gtk/gtk.h>
-
-#include <talkatu/talkatubuffer.h>
-#include <talkatu/talkatumessage.h>
-
-G_BEGIN_DECLS
-
-#define TALKATU_TYPE_HISTORY_BUFFER (talkatu_history_buffer_get_type())
-
-G_DECLARE_DERIVABLE_TYPE(TalkatuHistoryBuffer, talkatu_history_buffer, TALKATU, HISTORY_BUFFER, GtkTextBuffer)
-
-struct _TalkatuHistoryBufferClass {
- /*< private >*/
- GtkTextBufferClass parent;
-
- /*< public >*/
- void (*write_message)(TalkatuHistoryBuffer *buffer, TalkatuMessage *message);
-
- /*< private >*/
- gpointer reserved[4];
-};
-
-GtkTextBuffer *talkatu_history_buffer_new(void);
-
-void talkatu_history_buffer_write_message(TalkatuHistoryBuffer *buffer, TalkatuMessage *message);
-
-G_END_DECLS
-
-#endif /* TALKATU_HISTORY_BUFFER_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/talkatu/talkatuhistoryrow.c Wed Aug 19 04:05:05 2020 -0500
@@ -0,0 +1,245 @@
+/*
+ * talkatu
+ * 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 Lesser 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+#include <glib/gi18n-lib.h>
+
+#include <gtk/gtk.h>
+
+#include <talkatu/talkatuhistoryrow.h>
+
+/**
+ * SECTION:talkatuhistoryrow
+ * @Title: History Row
+ * @Short_description: History message widget
+ *
+ * This widget is used to display a single message in the conversation history.
+ */
+
+/**
+ * TALKATU_TYPE_HISTORY_ROW:
+ *
+ * The standard _get_type macro for #TalkatuHistoryRow.
+ */
+
+/**
+ * TalkatuHistoryRow:
+ *
+ * A #GtkListBoxRow subclass that is used to display a #TalkatuMessage.
+ */
+struct _TalkatuHistoryRow {
+ GtkListBoxRow parent;
+
+ TalkatuMessage *message;
+
+ GtkWidget *avatar_event;
+ GtkWidget *avatar_image;
+ GtkWidget *author;
+ GtkWidget *timestamp;
+ GtkWidget *edited;
+ GtkWidget *content;
+};
+
+enum {
+ PROP_0 = 0,
+ PROP_MESSAGE,
+ N_PROPERTIES,
+};
+static GParamSpec *properties[N_PROPERTIES] = {NULL, };
+
+/******************************************************************************
+ * Helpers
+ *****************************************************************************/
+static void
+talkatu_history_row_update(TalkatuHistoryRow *row) {
+ if(!TALKATU_IS_MESSAGE(row->message)) {
+ gtk_label_set_text(GTK_LABEL(row->author), _("Unknown"));
+ gtk_label_set_text(GTK_LABEL(row->timestamp), _("Unknown"));
+ gtk_label_set_text(GTK_LABEL(row->content),
+ _("This message was lost."));
+ } else {
+ GDateTime *timestamp = NULL;
+ TalkatuContentType content_type;
+ gchar *author = NULL, *contents = NULL, *iso8601 = NULL;
+ gboolean edited = FALSE;
+
+ g_object_get(
+ G_OBJECT(row->message),
+ "author", &author,
+ "content-type", &content_type,
+ "contents", &contents,
+ "edited", &edited,
+ "timestamp", &timestamp,
+ NULL
+ );
+
+ iso8601 = g_date_time_format_iso8601(timestamp);
+
+ gtk_label_set_text(GTK_LABEL(row->author), author);
+ gtk_label_set_text(GTK_LABEL(row->timestamp), iso8601);
+
+ if(edited) {
+ gtk_widget_show(row->edited);
+ } else {
+ gtk_widget_hide(row->edited);
+ }
+
+ if(content_type == TALKATU_CONTENT_TYPE_PANGO) {
+ gtk_label_set_markup(GTK_LABEL(row->content), contents);
+ } else {
+ gtk_label_set_text(GTK_LABEL(row->content), contents);
+ }
+
+ g_free(author);
+ g_free(iso8601);
+ g_free(contents);
+ g_clear_pointer(&timestamp, g_date_time_unref);
+ }
+}
+
+static TalkatuMessage *
+talkatu_history_row_get_message(TalkatuHistoryRow *row) {
+ if(row->message == NULL) {
+ return NULL;
+ }
+
+ return TALKATU_MESSAGE(g_object_ref(G_OBJECT(row->message)));
+}
+
+static void
+talkatu_history_row_set_message(TalkatuHistoryRow *row,
+ TalkatuMessage *message)
+{
+ if(g_set_object(&row->message, message)) {
+ talkatu_history_row_update(row);
+
+ g_object_notify_by_pspec(G_OBJECT(row), properties[PROP_MESSAGE]);
+ }
+}
+
+/******************************************************************************
+ * GObject Stuff
+ *****************************************************************************/
+G_DEFINE_TYPE(TalkatuHistoryRow, talkatu_history_row, GTK_TYPE_LIST_BOX_ROW)
+
+static void
+talkatu_history_row_get_property(GObject *obj, guint param_id, GValue *value,
+ GParamSpec *pspec)
+{
+ TalkatuHistoryRow *row = TALKATU_HISTORY_ROW(obj);
+
+ switch(param_id) {
+ case PROP_MESSAGE:
+ g_value_set_object(value, talkatu_history_row_get_message(row));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
+ break;
+ }
+}
+
+static void
+talkatu_history_row_set_property(GObject *obj, guint param_id,
+ const GValue *value, GParamSpec *pspec)
+{
+ TalkatuHistoryRow *row = TALKATU_HISTORY_ROW(obj);
+
+ switch(param_id) {
+ case PROP_MESSAGE:
+ talkatu_history_row_set_message(row, g_value_get_object(value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
+ break;
+ }
+}
+
+static void
+talkatu_history_row_finalize(GObject *obj) {
+ TalkatuHistoryRow *row = TALKATU_HISTORY_ROW(obj);
+
+ g_clear_object(&row->message);
+
+ G_OBJECT_CLASS(talkatu_history_row_parent_class)->finalize(obj);
+}
+
+static void
+talkatu_history_row_init(TalkatuHistoryRow *row) {
+ gtk_widget_init_template(GTK_WIDGET(row));
+}
+
+static void
+talkatu_history_row_class_init(TalkatuHistoryRowClass *klass) {
+ GObjectClass *obj_class = G_OBJECT_CLASS(klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
+
+ obj_class->get_property = talkatu_history_row_get_property;
+ obj_class->set_property = talkatu_history_row_set_property;
+ obj_class->finalize = talkatu_history_row_finalize;
+
+ /**
+ * TalkatuHistoryRow::message:
+ *
+ * The message that this row is displaying.
+ */
+ properties[PROP_MESSAGE] = g_param_spec_object(
+ "message", "message", "The message to display",
+ G_TYPE_OBJECT,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS
+ );
+
+ gtk_widget_class_set_template_from_resource(
+ widget_class,
+ "/org/imfreedom/keep/talkatu/talkatu/ui/historyrow.ui"
+ );
+
+ g_object_class_install_properties(obj_class, N_PROPERTIES, properties);
+
+ gtk_widget_class_bind_template_child(widget_class, TalkatuHistoryRow,
+ avatar_event);
+ gtk_widget_class_bind_template_child(widget_class, TalkatuHistoryRow,
+ avatar_image);
+ gtk_widget_class_bind_template_child(widget_class, TalkatuHistoryRow,
+ author);
+ gtk_widget_class_bind_template_child(widget_class, TalkatuHistoryRow,
+ timestamp);
+ gtk_widget_class_bind_template_child(widget_class, TalkatuHistoryRow,
+ edited);
+ gtk_widget_class_bind_template_child(widget_class, TalkatuHistoryRow,
+ content);
+}
+
+/******************************************************************************
+ * Public API
+ *****************************************************************************/
+
+/**
+ * talkatu_history_row_new:
+ * @message: The #TalkatuMessage instance that will be displayed.
+ *
+ * Creates a new #TalkatuHistoryRow for displaying @message.
+ *
+ * Returns: (transfer full): The new #TalkatuHistoryRow instance.
+ */
+GtkWidget *talkatu_history_row_new(TalkatuMessage *message) {
+ g_return_val_if_fail(TALKATU_IS_MESSAGE(message), NULL);
+
+ return GTK_WIDGET(g_object_new(
+ TALKATU_TYPE_HISTORY_ROW,
+ "message", message,
+ NULL
+ ));
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/talkatu/talkatuhistoryrow.h Wed Aug 19 04:05:05 2020 -0500
@@ -0,0 +1,43 @@
+/*
+ * talkatu
+ * 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 Lesser 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(TALKATU_GLOBAL_HEADER_INSIDE) && !defined(TALKATU_COMPILATION)
+#error "only <talkatu.h> may be included directly"
+#endif
+
+#ifndef TALKATU_HISTORY_ROW_H
+#define TALKATU_HISTORY_ROW_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include <gtk/gtk.h>
+
+#include <talkatu/talkatumessage.h>
+
+G_BEGIN_DECLS
+
+#define TALKATU_TYPE_HISTORY_ROW (talkatu_history_row_get_type())
+
+G_DECLARE_FINAL_TYPE(TalkatuHistoryRow, talkatu_history_row, TALKATU, HISTORY_ROW, GtkListBoxRow)
+
+GtkWidget *talkatu_history_row_new(TalkatuMessage *message);
+
+G_END_DECLS
+
+#endif /* TALKATU_HISTORY_ROW_H */
--- a/talkatu/talkatuinput.c Sun Aug 02 16:14:42 2020 -0500
+++ b/talkatu/talkatuinput.c Wed Aug 19 04:05:05 2020 -0500
@@ -147,8 +147,15 @@
talkatu_input_get_timestamp(TalkatuInput *input) {
TalkatuInputPrivate *priv = talkatu_input_get_instance_private(input);
+ /* If the timestamp has been explicitly set, return that. */
+ if(priv->timestamp != NULL) {
+ return priv->timestamp;
+ }
- return priv->timestamp;
+ /* If no timestamp has been explicitly set, just return the current local
+ * time.
+ */
+ return g_date_time_new_now_local();
}
static void
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/talkatu/talkatuscrolledwindow.c Wed Aug 19 04:05:05 2020 -0500
@@ -0,0 +1,195 @@
+/*
+ * talkatu
+ * 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 Lesser 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+#include <gtk/gtk.h>
+
+#include <talkatu/talkatuscrolledwindow.h>
+
+/**
+ * SECTION:talkatuscrolledwindow
+ * @Title: Enhanced Scrolled Window
+ * @Short_description: An enhanced GtkScrolledWindow
+ *
+ * This widget is a simple subclass of #GtkScrolledWindow that has helpers for
+ * keyboard navigation as well as the ability to automatically scroll to the end
+ * when new items are added if the widget was already scrolled all the way to
+ * the bottom.
+ */
+
+/**
+ * TALKATU_TYPE_SCROLLED_WINDOW:
+ *
+ * The standard _get_type macro for #TalkatuScrolledWindow.
+ */
+
+/**
+ * TalkatuScrolledWindow:
+ *
+ * An enhanced #GtkScrolledWindow subclass.
+ */
+struct _TalkatuScrolledWindow {
+ GtkScrolledWindow parent;
+
+ GtkAdjustment *vadjustment;
+ gboolean auto_scroll;
+};
+
+G_DEFINE_TYPE(TalkatuScrolledWindow, talkatu_scrolled_window,
+ GTK_TYPE_SCROLLED_WINDOW)
+
+/******************************************************************************
+ * Callbacks
+ *****************************************************************************/
+static void
+talkatu_scrolled_window_vadjustment_changed_cb(GtkAdjustment *adjustment,
+ gpointer data)
+{
+ TalkatuScrolledWindow *sw = TALKATU_SCROLLED_WINDOW(data);
+
+ if(sw->auto_scroll) {
+ gdouble upper, pagesize;
+
+ upper = gtk_adjustment_get_upper(adjustment);
+ pagesize = gtk_adjustment_get_page_size(adjustment);
+
+ gtk_adjustment_set_value(adjustment, upper - pagesize);
+ }
+}
+
+static void
+talkatu_scrolled_window_vadjustment_value_changed_cb(GtkAdjustment *adjustment,
+ gpointer data)
+{
+ 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);
+}
+
+static void
+talkatu_scrolled_window_vadjustment_notify_cb(GObject *obj, GParamSpec *pspec,
+ gpointer data)
+{
+ TalkatuScrolledWindow *sw = TALKATU_SCROLLED_WINDOW(obj);
+ GtkAdjustment *adjustment = NULL;
+
+ adjustment = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(obj));
+ if(g_set_object(&sw->vadjustment, adjustment)) {
+ sw->auto_scroll = TRUE;
+
+ g_signal_connect(G_OBJECT(adjustment), "value-changed",
+ G_CALLBACK(talkatu_scrolled_window_vadjustment_value_changed_cb),
+ sw);
+ g_signal_connect(G_OBJECT(adjustment), "changed",
+ G_CALLBACK(talkatu_scrolled_window_vadjustment_changed_cb),
+ sw);
+ }
+}
+
+/******************************************************************************
+ * GObject Stuff
+ *****************************************************************************/
+static void
+talkatu_scrolled_window_init(TalkatuScrolledWindow *sw) {
+ sw->auto_scroll = TRUE;
+
+ g_signal_connect(G_OBJECT(sw), "notify::vadjustment",
+ G_CALLBACK(talkatu_scrolled_window_vadjustment_notify_cb),
+ NULL);
+}
+
+static void
+talkatu_scrolled_window_finalize(GObject *obj) {
+ TalkatuScrolledWindow *sw = TALKATU_SCROLLED_WINDOW(obj);
+
+ g_clear_object(&sw->vadjustment);
+
+ G_OBJECT_CLASS(talkatu_scrolled_window_parent_class)->finalize(obj);
+}
+
+static void
+talkatu_scrolled_window_class_init(TalkatuScrolledWindowClass *klass) {
+ GObjectClass *obj_class = G_OBJECT_CLASS(klass);
+
+ obj_class->finalize = talkatu_scrolled_window_finalize;
+}
+
+/******************************************************************************
+ * Public API
+ *****************************************************************************/
+
+/**
+ * talkatu_scrolled_window_new:
+ * @hadjustment: The GtkAdjustment for the horizontal position.
+ * @vadjustment: The GtkAdjustment for the vertical position.
+ *
+ * Creates a new #TalkatuScrolledWindow.
+ *
+ * Returns: (transfer full): The new #TalkatuScrolledWindow instance.
+ */
+GtkWidget *talkatu_scrolled_window_new(GtkAdjustment *hadjustment,
+ GtkAdjustment *vadjustment)
+{
+ return GTK_WIDGET(g_object_new(
+ TALKATU_TYPE_SCROLLED_WINDOW,
+ "hadjustment", hadjustment,
+ "vadjustment", vadjustment,
+ NULL
+ ));
+}
+
+/**
+ * talkatu_scrolled_window_page_up:
+ * @sw: The #TalkatuScrolledWindow instance.
+ *
+ * Scrolls @sw up one page.
+ */
+void
+talkatu_scrolled_window_page_up(TalkatuScrolledWindow *sw) {
+ g_return_if_fail(TALKATU_IS_SCROLLED_WINDOW(sw));
+
+ if(sw->vadjustment != NULL) {
+ double value = 0.0;
+
+ 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.
+ */
+void
+talkatu_scrolled_window_page_down(TalkatuScrolledWindow *sw) {
+ g_return_if_fail(TALKATU_IS_SCROLLED_WINDOW(sw));
+
+ if(sw->vadjustment != NULL) {
+ double value = 0.0;
+
+ value = gtk_adjustment_get_value(sw->vadjustment);
+ value += gtk_adjustment_get_page_increment(sw->vadjustment);
+ gtk_adjustment_set_value(sw->vadjustment, value);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/talkatu/talkatuscrolledwindow.h Wed Aug 19 04:05:05 2020 -0500
@@ -0,0 +1,43 @@
+/*
+ * talkatu
+ * 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 Lesser 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(TALKATU_GLOBAL_HEADER_INSIDE) && !defined(TALKATU_COMPILATION)
+#error "only <talkatu.h> may be included directly"
+#endif
+
+#ifndef TALKATU_SCROLLED_WINDOW_H
+#define TALKATU_SCROLLED_WINDOW_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define TALKATU_TYPE_SCROLLED_WINDOW (talkatu_scrolled_window_get_type())
+
+G_DECLARE_FINAL_TYPE(TalkatuScrolledWindow, talkatu_scrolled_window, TALKATU, SCROLLED_WINDOW, GtkScrolledWindow)
+
+GtkWidget *talkatu_scrolled_window_new(GtkAdjustment *hadjustment, GtkAdjustment *vadjustment);
+
+void talkatu_scrolled_window_page_up(TalkatuScrolledWindow *sw);
+
+G_END_DECLS
+
+#endif /* TALKATU_SCROLLED_WINDOW_H */