talkatu/talkatu

b55f80536e48
Parents 9f2390f06e9b
Children be6fa83442f7
Add a new TalkatuTypingLabel widget. Fixes TALKATU-65
--- 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>
<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>
<template class="TalkatuDemoWindow" parent="GtkWindow">
<property name="can_focus">False</property>
@@ -232,7 +235,7 @@
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
- <property name="position">1</property>
+ <property name="position">0</property>
</packing>
</child>
<child>
@@ -264,10 +267,30 @@
<property name="fill">False</property>
</packing>
</child>
+ <child internal-child="send_button">
+ <object class="GtkButton">
+ <property name="can_focus">False</property>
+ <property name="receives_default">False</property>
+ </object>
+ <packing>
+ <property name="fill">False</property>
+ </packing>
+ </child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <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>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
@@ -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"/>
</object>
</interface>
--- 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 @@
GtkWidget *view_history;
GtkWidget *editor;
+ GtkWidget *typing;
GtkRadioToolButton *toggle_plain;
GtkRadioToolButton *toggle_whole;
@@ -158,6 +159,19 @@
}
static void
+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),
+ window->author);
+ } else {
+ talkatu_typing_label_finish_typing(TALKATU_TYPING_LABEL(window->typing),
+ window->author);
+ }
+}
+
+static void
talkatu_demo_window_view_open_url_cb(TalkatuView *view, const gchar *url, gpointer data) {
GError *error = NULL;
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/talkatutoolbar.h
talkatu/talkatutooldrawer.c
talkatu/talkatutooldrawer.h
+talkatu/talkatutypinglabel.c
+talkatu/talkatutypinglabel.h
talkatu/talkatuview.c
talkatu/talkatuview.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 @@
'talkatutagtable.h',
'talkatutoolbar.h',
'talkatutooldrawer.h',
+ 'talkatutypinglabel.h',
'talkatuview.h',
'talkatuwholebuffer.h',
]
@@ -45,6 +46,7 @@
'talkatutagtable.c',
'talkatutoolbar.c',
'talkatutooldrawer.c',
+ 'talkatutypinglabel.c',
'talkatuview.c',
'talkatuwholebuffer.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">
<properties>
@@ -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 @@
+/*
+ * 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 <pango/pango.h>
+
+#include <talkatu/talkatutypinglabel.h>
+
+/**
+ * SECTION:talkatutypinglabel
+ * @Title: TypingLabel
+ * @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
+ * you.
+ *
+ * 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.
+ */
+
+/**
+ * TalkatuTypingLabel:
+ *
+ * A #GtkLabel subclass that keeps track of who's typing.
+ */
+typedef struct {
+ GtkLabel parent;
+
+ GHashTable *typers;
+} 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.
+ */
+
+typedef struct {
+ TalkatuTypingLabel *label;
+ gchar *who;
+} TalkatuTypingLabelTimeoutData;
+
+enum {
+ SIG_CHANGED,
+ LAST_SIGNAL
+};
+static guint signals[LAST_SIGNAL] = {0, };
+
+G_DEFINE_TYPE_WITH_PRIVATE(TalkatuTypingLabel, talkatu_typing_label, GTK_TYPE_LABEL)
+
+#define TALKATU_TYPING_LABEL_TIMEOUT (30)
+
+/******************************************************************************
+ * Callbacks
+ *****************************************************************************/
+static gboolean
+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
+ * called again.
+ */
+ return G_SOURCE_CONTINUE;
+}
+
+/******************************************************************************
+ * Helpers
+ *****************************************************************************/
+static void
+talkatu_typing_label_typer_value_free(gpointer data) {
+ g_source_remove(GPOINTER_TO_UINT(data));
+}
+
+/******************************************************************************
+ * Default Handlers
+ *****************************************************************************/
+static void
+talkatu_typing_label_default_changed(TalkatuTypingLabel *label,
+ GList *typers)
+{
+ gint n_typers = 0;
+
+ if(typers == NULL) {
+ gtk_label_set_text(GTK_LABEL(label), "");
+
+ return;
+ }
+
+ n_typers = g_list_length(typers);
+ if(n_typers == 0) {
+ gtk_label_set_text(GTK_LABEL(label), "");
+ } else if(n_typers > 3) {
+ gtk_label_set_text(GTK_LABEL(label),
+ _("Several people are typing..."));
+ } else {
+ gchar *text = NULL;
+
+ if(n_typers == 1) {
+ 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->data,
+ (gchar *)typers->next->data);
+ } else {
+ text = g_strdup_printf(_("%s, %s, and %s are typing..."),
+ (gchar *)typers->data,
+ (gchar *)typers->next->data,
+ (gchar *)typers->next->next->data);
+ }
+
+ gtk_label_set_text(GTK_LABEL(label), text);
+ g_free(text);
+ }
+}
+
+/******************************************************************************
+ * GObject Implementation
+ *****************************************************************************/
+static void
+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);
+}
+
+static void
+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);
+}
+
+static void
+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(
+ "changed",
+ G_TYPE_FROM_CLASS(klass),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET(TalkatuTypingLabelClass, changed),
+ NULL,
+ NULL,
+ NULL,
+ G_TYPE_NONE,
+ 1,
+ G_TYPE_POINTER
+ );
+
+}
+
+/******************************************************************************
+ * Public API
+ *****************************************************************************/
+
+/**
+ * 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,
+ NULL
+ ));
+}
+
+/**
+ * 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.
+ */
+void
+talkatu_typing_label_start_typing(TalkatuTypingLabel *label, const gchar *who) {
+ TalkatuTypingLabelPrivate *priv = NULL;
+ TalkatuTypingLabelTimeoutData *typing_data = NULL;
+ guint timeout_id = 0;
+ gpointer value = 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,
+ typing_data,
+ g_free);
+
+ 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);
+
+ g_list_free(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.
+ */
+void
+talkatu_typing_label_finish_typing(TalkatuTypingLabel *label,
+ const gchar* who)
+{
+ 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);
+
+ g_list_free(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 @@
+/*
+ * 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_TYPING_LABEL_H
+#define TALKATU_TYPING_LABEL_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define TALKATU_TYPE_TYPING_LABEL (talkatu_typing_label_get_type())
+G_DECLARE_DERIVABLE_TYPE(TalkatuTypingLabel, talkatu_typing_label, TALKATU, TYPING_LABEL, GtkLabel)
+
+struct _TalkatuTypingLabelClass {
+ /*< private >*/
+ GtkLabelClass parent;
+
+ /*< public >*/
+ void (*changed)(TalkatuTypingLabel *label, GList *typers);
+
+ /*< private >*/
+ gpointer reserved[4];
+};
+
+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);
+
+G_END_DECLS
+
+#endif /* TALKATU_TYPING_LABEL_H */
+