--- a/demo/data/demo.ui Thu Nov 07 01:44:01 2019 -0600
+++ b/demo/data/demo.ui Thu Apr 23 22:36:57 2020 -0500
@@ -10,14 +10,17 @@
<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 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 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"/> <template class="TalkatuDemoWindow" parent="GtkWindow">
<property name="can_focus">False</property>
@@ -232,7 +235,7 @@
<property name="expand">False</property>
<property name="fill">True</property>
- <property name="position">1</property>
+ <property name="position">0</property> @@ -264,10 +267,30 @@
<property name="fill">False</property>
+ <child internal-child="send_button"> + <object class="GtkButton"> + <property name="can_focus">False</property> + <property name="receives_default">False</property> + <property name="fill">False</property> <property name="expand">True</property>
<property name="fill">True</property>
+ <property name="position">1</property> + <object class="TalkatuTypingLabel" id="typing"> + <property name="visible">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="expand">False</property> + <property name="fill">True</property> <property name="position">2</property>
@@ -392,5 +415,6 @@
<object class="TalkatuWholeBuffer" id="buffer_whole">
<property name="tag_table">table_plain</property>
<property name="style">whole</property>
+ <signal name="changed" handler="talkatu_demo_window_buffer_modified_cb" object="TalkatuDemoWindow" swapped="no"/> --- a/demo/talkatudemowindow.c Thu Nov 07 01:44:01 2019 -0600
+++ b/demo/talkatudemowindow.c Thu Apr 23 22:36:57 2020 -0500
@@ -26,6 +26,7 @@
GtkRadioToolButton *toggle_plain;
GtkRadioToolButton *toggle_whole;
@@ -158,6 +159,19 @@
+talkatu_demo_window_buffer_modified_cb(GtkTextBuffer *buffer, gpointer data) { + TalkatuDemoWindow *window = TALKATU_DEMO_WINDOW(data); + if(gtk_text_buffer_get_char_count(buffer) > 0) { + talkatu_typing_label_start_typing(TALKATU_TYPING_LABEL(window->typing), + talkatu_typing_label_finish_typing(TALKATU_TYPING_LABEL(window->typing), talkatu_demo_window_view_open_url_cb(TalkatuView *view, const gchar *url, gpointer data) {
gboolean success = FALSE;
@@ -295,6 +309,7 @@
gtk_widget_class_bind_template_child(widget_class, TalkatuDemoWindow, view_history);
gtk_widget_class_bind_template_child(widget_class, TalkatuDemoWindow, editor);
+ gtk_widget_class_bind_template_child(widget_class, TalkatuDemoWindow, typing); gtk_widget_class_bind_template_child(widget_class, TalkatuDemoWindow, buffer_plain);
gtk_widget_class_bind_template_child(widget_class, TalkatuDemoWindow, buffer_whole);
@@ -316,6 +331,7 @@
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);
+ gtk_widget_class_bind_template_callback(widget_class, talkatu_demo_window_buffer_modified_cb); gtk_widget_class_bind_template_callback(widget_class, talkatu_demo_window_view_open_url_cb);
gtk_widget_class_bind_template_callback(widget_class, talkatu_demo_window_view_send_message_cb);
--- a/po/POTFILES Thu Nov 07 01:44:01 2019 -0600
+++ b/po/POTFILES Thu Apr 23 22:36:57 2020 -0500
@@ -44,6 +44,8 @@
talkatu/talkatutooldrawer.c
talkatu/talkatutooldrawer.h
+talkatu/talkatutypinglabel.c +talkatu/talkatutypinglabel.h talkatu/talkatuwholebuffer.c
--- a/talkatu/meson.build Thu Nov 07 01:44:01 2019 -0600
+++ b/talkatu/meson.build Thu Apr 23 22:36:57 2020 -0500
@@ -22,6 +22,7 @@
+ 'talkatutypinglabel.h', @@ -45,6 +46,7 @@
+ 'talkatutypinglabel.c', --- a/talkatu/reference/talkatu-docs.xml Thu Nov 07 01:44:01 2019 -0600
+++ b/talkatu/reference/talkatu-docs.xml Thu Apr 23 22:36:57 2020 -0500
@@ -50,6 +50,8 @@
<xi:include href="xml/talkatutooldrawer.xml"/>
<xi:include href="xml/talkatumenutoolbutton.xml"/>
+ <xi:include href="xml/talkatutypinglabel.xml"/> <xi:include href="xml/talkatucore.xml"/>
<xi:include href="xml/talkatucodeset.xml"/>
<xi:include href="xml/talkatuversion.xml"/>
--- a/talkatu/talkatu.xml Thu Nov 07 01:44:01 2019 -0600
+++ b/talkatu/talkatu.xml Thu Apr 23 22:36:57 2020 -0500
@@ -15,8 +15,8 @@
<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"/>
<glade-widget-class name="TalkatuTagTable" generic-name="tag-table" title="Tag Table" toplevel="True"/>
+ <glade-widget-class name="TalkatuTypingLabel" generic-name="typing-label" title="Typing Label"/> <glade-widget-class name="TalkatuBuffer" generic-name="buffer" title="Buffer" toplevel="True">
@@ -59,6 +59,7 @@
<glade-widget-class-ref name="TalkatuMenuToolButton"/>
<glade-widget-class-ref name="TalkatuToolDrawer"/>
<glade-widget-class-ref name="TalkatuToolbar"/>
+ <glade-widget-class-ref name="TalkatuTypingLabel"/> <glade-widget-class-ref name="TalkatuView"/>
<glade-widget-class-ref name="TalkatuWholeBuffer"/>
<glade-widget-class-ref name="TalkatuTagTable"/>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/talkatu/talkatutypinglabel.c Thu Apr 23 22:36:57 2020 -0500
@@ -0,0 +1,296 @@
+ * 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 <pango/pango.h> +#include <talkatu/talkatutypinglabel.h> + * SECTION:talkatutypinglabel + * @Short_description: A GtkLabel that displays who's typing + * This widget implements a simple interface for displaying who is currently + * typing. You can call talkatu_typing_label_start_typing() whenever you + * receive a typing notification and #TalkatuTypingLabel will aggregate it for + * If there are no new calls to talkatu_typing_label_start_typing() for a + * specific user, then they will be removed after a timeout of 30 seconds. + * Usernames are passed in as strings and their default display can be + * overridden by connecting to the #TalkatuTypingLabel::changed signal. + * TALKATU_TYPE_TYPING_LABEL: + * The standard _get_type macro for #TalkatuTypingLabel. + * A #GtkLabel subclass that keeps track of who's typing. +} TalkatuTypingLabelPrivate; + * TalkatuTypingLabelClass: + * @changed: The changed vfunc is called to update the text in the widget. The + * default handler can be overridden to customize the text. + * The backing class to a #TalkatuTypingLabel. + TalkatuTypingLabel *label; +} TalkatuTypingLabelTimeoutData; +static guint signals[LAST_SIGNAL] = {0, }; +G_DEFINE_TYPE_WITH_PRIVATE(TalkatuTypingLabel, talkatu_typing_label, GTK_TYPE_LABEL) +#define TALKATU_TYPING_LABEL_TIMEOUT (30) +/****************************************************************************** + *****************************************************************************/ +talkatu_typing_label_timeout_cb(gpointer d) { + TalkatuTypingLabelTimeoutData *data = (TalkatuTypingLabelTimeoutData *)d; + talkatu_typing_label_finish_typing(data->label, data->who); + /* we return G_SOURCE_CONTINUE because talkatu_typing_label_finish_typing + * will remove the source and we don't have a way to tell it that we + * removed it. So it's best to let it remove it and we just won't get + return G_SOURCE_CONTINUE; +/****************************************************************************** + *****************************************************************************/ +talkatu_typing_label_typer_value_free(gpointer data) { + g_source_remove(GPOINTER_TO_UINT(data)); +/****************************************************************************** + *****************************************************************************/ +talkatu_typing_label_default_changed(TalkatuTypingLabel *label, + gtk_label_set_text(GTK_LABEL(label), ""); + n_typers = g_list_length(typers); + gtk_label_set_text(GTK_LABEL(label), ""); + } else if(n_typers > 3) { + gtk_label_set_text(GTK_LABEL(label), + _("Several people are typing...")); + text = g_strdup_printf(_("%s is typing..."), (gchar *)typers->data); + } else if(n_typers == 2) { + text = g_strdup_printf(_("%s and %s are typing..."), + (gchar *)typers->next->data); + text = g_strdup_printf(_("%s, %s, and %s are typing..."), + (gchar *)typers->next->data, + (gchar *)typers->next->next->data); + gtk_label_set_text(GTK_LABEL(label), text); +/****************************************************************************** + * GObject Implementation + *****************************************************************************/ +talkatu_typing_label_init(TalkatuTypingLabel *label) { + TalkatuTypingLabelPrivate *priv = NULL; + /* set our xalign to 0 to make it left justified... */ + gtk_label_set_xalign(GTK_LABEL(label), 0); + priv = talkatu_typing_label_get_instance_private(TALKATU_TYPING_LABEL(label)); + priv->typers = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, + talkatu_typing_label_typer_value_free); +talkatu_typing_label_finalize(GObject *obj) { + TalkatuTypingLabelPrivate *priv = NULL; + priv = talkatu_typing_label_get_instance_private(TALKATU_TYPING_LABEL(obj)); + g_hash_table_destroy(priv->typers); + G_OBJECT_CLASS(talkatu_typing_label_parent_class)->finalize(obj); +talkatu_typing_label_class_init(TalkatuTypingLabelClass *klass) { + GObjectClass *obj_class = G_OBJECT_CLASS(klass); + obj_class->finalize = talkatu_typing_label_finalize; + klass->changed = talkatu_typing_label_default_changed; + * TalkatuTypingLabel::changed: + * @talkatutypinglabel: The #TalkatuTypingLabel instance. + * @arg1: A #GList of who's typing. + * @user_data: User supplied data. + * Emitted when the typing state of an individual has changed. + signals[SIG_CHANGED] = g_signal_new( + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET(TalkatuTypingLabelClass, changed), +/****************************************************************************** + *****************************************************************************/ + * talkatu_typing_label_new: + * Creates a new #TalkatuTypingLabel that displays whos is typing. + * Returns: (transfer full): The new #TalkatuTypingLabel instance. +GtkWidget *talkatu_typing_label_new(void) { + return GTK_WIDGET(g_object_new( + TALKATU_TYPE_TYPING_LABEL, + * talkatu_typing_label_start_typing: + * @label: The #TalkatuTypingLabel instance. + * @who: The caller defined user that started typing. + * Adds @who to the list of users in @label that are currently typing. +talkatu_typing_label_start_typing(TalkatuTypingLabel *label, const gchar *who) { + TalkatuTypingLabelPrivate *priv = NULL; + TalkatuTypingLabelTimeoutData *typing_data = NULL; + g_return_if_fail(TALKATU_IS_TYPING_LABEL(label)); + g_return_if_fail(who != NULL); + priv = talkatu_typing_label_get_instance_private(label); + /* create a timeout to remove this person from the list */ + typing_data = g_new(TalkatuTypingLabelTimeoutData, 1); + typing_data->label = label; + /* this gets cleaned up by the hash table's key destroy function */ + typing_data->who = g_strdup(who); + timeout_id = g_timeout_add_seconds_full(G_PRIORITY_DEFAULT, + TALKATU_TYPING_LABEL_TIMEOUT, + talkatu_typing_label_timeout_cb, + value = GUINT_TO_POINTER(timeout_id); + if(!g_hash_table_replace(priv->typers, typing_data->who, value)) { + /* the user wasn't in the list so we need to emit our changed signal */ + GList *typers = g_hash_table_get_keys(priv->typers); + g_signal_emit(label, signals[SIG_CHANGED], 0, typers); + * talkatu_type_label_finish_typing: + * @label: The #TalkatuTypingLabel instance. + * @who: The caller defined user that has finished typing. + * Removes @who from the list of users in @label that are currently typing. +talkatu_typing_label_finish_typing(TalkatuTypingLabel *label, + TalkatuTypingLabelPrivate *priv = NULL; + g_return_if_fail(TALKATU_IS_TYPING_LABEL(label)); + g_return_if_fail(who != NULL); + priv = talkatu_typing_label_get_instance_private(label); + if(g_hash_table_remove(priv->typers, who)) { + GList *typers = g_hash_table_get_keys(priv->typers); + /* emit our changed signal */ + g_signal_emit(label, signals[SIG_CHANGED], 0, typers); --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/talkatu/talkatutypinglabel.h Thu Apr 23 22:36:57 2020 -0500
@@ -0,0 +1,54 @@
+ * 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" +#ifndef TALKATU_TYPING_LABEL_H +#define TALKATU_TYPING_LABEL_H +#include <glib-object.h> +#define TALKATU_TYPE_TYPING_LABEL (talkatu_typing_label_get_type()) +G_DECLARE_DERIVABLE_TYPE(TalkatuTypingLabel, talkatu_typing_label, TALKATU, TYPING_LABEL, GtkLabel) +struct _TalkatuTypingLabelClass { + void (*changed)(TalkatuTypingLabel *label, GList *typers); +GtkWidget *talkatu_typing_label_new(void); +void talkatu_typing_label_start_typing(TalkatuTypingLabel *label, const gchar *who); +void talkatu_typing_label_finish_typing(TalkatuTypingLabel *label, const gchar *who); +#endif /* TALKATU_TYPING_LABEL_H */