--- a/demo/talkatudemowindow.c Tue Jan 28 21:53:46 2020 -0600
+++ b/demo/talkatudemowindow.c Wed Jan 29 08:35:45 2020 -0600
@@ -19,7 +19,6 @@
#include "talkatudemowindow.h"
-#include "talkatudemomessage.h"
struct _TalkatuDemoWindow {
@@ -45,7 +44,6 @@
GtkWidget *author_button;
GtkWidget *author_popover;
G_DEFINE_TYPE(TalkatuDemoWindow, talkatu_demo_window, GTK_TYPE_WINDOW);
@@ -204,36 +202,27 @@
-talkatu_demo_window_view_send_message_cb(TalkatuView *view, gpointer data) {
+talkatu_demo_window_view_send_message_cb(TalkatuInput *input, gpointer data) { TalkatuDemoWindow *window = TALKATU_DEMO_WINDOW(data);
- TalkatuContentType content_type = TALKATU_CONTENT_TYPE_PLAIN;
- gchar *plain_text = NULL;
- TalkatuMessage *message = NULL;
- GtkTextBuffer *input = gtk_text_view_get_buffer(GTK_TEXT_VIEW(view));
if(TALKATU_IS_HTML_BUFFER(input)) {
- content_type = TALKATU_CONTENT_TYPE_HTML;
+ talkatu_message_set_content_type(TALKATU_MESSAGE(input), + TALKATU_CONTENT_TYPE_HTML); } else if(TALKATU_IS_MARKDOWN_BUFFER(input)) {
- content_type = TALKATU_CONTENT_TYPE_MARKDOWN;
+ talkatu_message_set_content_type(TALKATU_MESSAGE(input), + TALKATU_CONTENT_TYPE_MARKDOWN); - plain_text = talkatu_buffer_get_plain_text(TALKATU_BUFFER(input));
- message = talkatu_demo_message_new(
+ talkatu_message_set_id(TALKATU_MESSAGE(input), id++); talkatu_history_buffer_write_message(
TALKATU_HISTORY_BUFFER(window->buffer_history),
- talkatu_buffer_clear(TALKATU_BUFFER(input));
- g_object_unref(G_OBJECT(message));
+ talkatu_message_set_contents(TALKATU_MESSAGE(input), ""); + talkatu_message_clear_attachments(TALKATU_MESSAGE(input)); @@ -266,8 +255,11 @@
TalkatuDemoWindow *window = TALKATU_DEMO_WINDOW(data);
if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(item))) {
- g_free(window->author);
- window->author = g_strdup(gtk_button_get_label(GTK_BUTTON(item)));
+ TalkatuEditor *editor = TALKATU_EDITOR(window->editor); + GtkWidget *input = talkatu_editor_get_input(editor); + talkatu_message_set_author(TALKATU_MESSAGE(input), + gtk_button_get_label(GTK_BUTTON(item))); --- a/talkatu/talkatuattachment.c Tue Jan 28 21:53:46 2020 -0600
+++ b/talkatu/talkatuattachment.c Wed Jan 29 08:35:45 2020 -0600
@@ -44,6 +44,8 @@
struct _TalkatuAttachment {
@@ -56,6 +58,7 @@
@@ -67,6 +70,13 @@
*****************************************************************************/
+talkatu_attachment_set_id(TalkatuAttachment *attachment, guint64 id) { + g_object_notify_by_pspec(G_OBJECT(attachment), properties[PROP_ID]); talkatu_attachment_set_content_type(TalkatuAttachment *attachment,
const gchar *content_type)
@@ -111,6 +121,9 @@
TalkatuAttachment *attachment = TALKATU_ATTACHMENT(obj);
+ g_value_set_uint64(value, talkatu_attachment_get_id(attachment)); g_value_set_string(value, talkatu_attachment_get_content_type(attachment));
@@ -131,6 +144,9 @@
TalkatuAttachment *attachment = TALKATU_ATTACHMENT(obj);
+ talkatu_attachment_set_id(attachment, g_value_get_uint64(value)); talkatu_attachment_set_content_type(attachment, g_value_get_string(value));
@@ -159,7 +175,7 @@
-talkatu_attachment_init(TalkatuAttachment *tag) {
+talkatu_attachment_init(TalkatuAttachment *attachment) { @@ -171,6 +187,12 @@
obj_class->finalize = talkatu_attachment_finalize;
+ properties[PROP_ID] = g_param_spec_uint64( + "id", "id", "The identifier of the attachment", + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS properties[PROP_CONTENT_TYPE] = g_param_spec_string(
"content-type", "content-type", "The content type of the attachment",
"application/octet-stream",
@@ -198,6 +220,7 @@
* talkatu_attachment_new:
+ * @id: The identifier of the attachment. * @content_type: The content type of the attachment.
* @filename: The filename of the attachment.
* @contents: A #GBytes that contains the data of the attachment.
@@ -207,8 +230,8 @@
* Returns: (transfer full): The new #TalkatuAttachment.
-talkatu_attachment_new(const gchar *content_type, const gchar *filename,
+talkatu_attachment_new(guint64 id, const gchar *content_type, + const gchar *filename, GBytes *contents) g_return_val_if_fail(content_type != NULL, NULL);
g_return_val_if_fail(filename != NULL, NULL);
@@ -216,6 +239,7 @@
return TALKATU_ATTACHMENT(g_object_new(
"content-type", content_type,
@@ -225,6 +249,7 @@
* talkatu_attachment_new_from_pixbuf:
+ * @id: The identifier of the attachment. * @filename: The filename of the attachment.
* @pixbuf: The #GdkPixbuf to create an attachment for.
@@ -233,7 +258,9 @@
* Returns: (transfer full): The new #TalkatuAttachment.
-talkatu_attachment_new_from_pixbuf(const gchar *filename, GdkPixbuf *pixbuf) {
+talkatu_attachment_new_from_pixbuf(guint64 id, const gchar *filename, TalkatuAttachment *attachment = NULL;
@@ -256,7 +283,7 @@
contents = g_bytes_new_take(data, data_sz);
- attachment = talkatu_attachment_new("image/png", filename, contents);
+ attachment = talkatu_attachment_new(id, "image/png", filename, contents); @@ -264,6 +291,38 @@
+ * talkatu_attachment_get_id: + * @attachment: The #TalkatuAttachment instance. + * Gets the ID associated with @attachment. + * Returns: The ID of @attachment. +talkatu_attachment_get_id(TalkatuAttachment *attachment) { + g_return_val_if_fail(TALKATU_IS_ATTACHMENT(attachment), 0); + * talkatu_attachment_get_hash_key: + * @attachment: The #TalkatuAttachment instance. + * Gets the hash key of @attachment. This should only be used when + * trying to address a #TalkatuAttachment in a #GHashTable that is using + * #g_int64_hash as the key function. + * Returns: (transfer none): The hash key of @attachment. +talkatu_attachment_get_hash_key(TalkatuAttachment *attachment) { + g_return_val_if_fail(TALKATU_IS_ATTACHMENT(attachment), NULL); + return &attachment->id; * talkatu_attachment_get_content_type:
* @attachment: The #TalkatuAttachment instance.
--- a/talkatu/talkatuattachment.h Tue Jan 28 21:53:46 2020 -0600
+++ b/talkatu/talkatuattachment.h Wed Jan 29 08:35:45 2020 -0600
@@ -33,8 +33,11 @@
typedef void (*TalkatuAttachmentForeachFunc)(TalkatuAttachment *attachment, gpointer data);
-TalkatuAttachment *talkatu_attachment_new(const gchar *content_type, const gchar *filename, GBytes *contents);
-TalkatuAttachment *talkatu_attachment_new_from_pixbuf(const gchar *filename, GdkPixbuf *pixbuf);
+TalkatuAttachment *talkatu_attachment_new(guint64 id, const gchar *content_type, const gchar *filename, GBytes *contents); +TalkatuAttachment *talkatu_attachment_new_from_pixbuf(guint64 id, const gchar *filename, GdkPixbuf *pixbuf); +guint64 talkatu_attachment_get_id(TalkatuAttachment *attachment); +guint64 *talkatu_attachment_get_hash_key(TalkatuAttachment *attachment); const gchar *talkatu_attachment_get_content_type(TalkatuAttachment *attachment);
const gchar *talkatu_attachment_get_filename(TalkatuAttachment *attachment);
--- a/talkatu/talkatubuffer.c Tue Jan 28 21:53:46 2020 -0600
+++ b/talkatu/talkatubuffer.c Wed Jan 29 08:35:45 2020 -0600
@@ -673,6 +673,7 @@
g_return_val_if_fail(TALKATU_IS_BUFFER(buffer), NULL);
gtk_text_buffer_get_bounds(GTK_TEXT_BUFFER(buffer), &start, &end);
return gtk_text_buffer_get_text(GTK_TEXT_BUFFER(buffer), &start, &end, FALSE);
--- a/talkatu/talkatuinput.c Tue Jan 28 21:53:46 2020 -0600
+++ b/talkatu/talkatuinput.c Wed Jan 29 08:35:45 2020 -0600
@@ -1,6 +1,6 @@
- * Copyright (C) 2017-2019 Gary Kramlich <grim@reaperworld.com>
+ * 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
@@ -25,10 +25,13 @@
#include "talkatu/talkatuattachmentdialog.h"
#include "talkatu/talkatuenums.h"
#include "talkatu/talkatumarkup.h"
+#include "talkatu/talkatumessage.h" + GHashTable *attachments; GspellTextView *gspell_view;
TalkatuInputSendBinding send_binding;
@@ -37,12 +40,33 @@
* is updated via cursor-moved and button-press callbacks.
GtkTextMark *context_mark;
+ /* TalkatuMessage properties: content type and contents are derived from + TalkatuContentType content_type; + TalkatuAttachmentForeachFunc func; +} TalkatuInputForeachAttachmentData; + PROP_ID = N_PROPERTIES, static GParamSpec *properties[N_PROPERTIES];
@@ -53,7 +77,209 @@
static guint signals[LAST_SIGNAL] = {0, };
-G_DEFINE_TYPE_WITH_PRIVATE(TalkatuInput, talkatu_input, TALKATU_TYPE_VIEW)
+static void talkatu_input_message_init(TalkatuMessageInterface *iface); + TalkatuInput, talkatu_input, TALKATU_TYPE_VIEW, + G_ADD_PRIVATE(TalkatuInput) + G_IMPLEMENT_INTERFACE(TALKATU_TYPE_MESSAGE, talkatu_input_message_init) +/****************************************************************************** + * TalkatuMessage Interface + *****************************************************************************/ +talkatu_input_get_id(TalkatuInput *input) { + TalkatuInputPrivate *priv = talkatu_input_get_instance_private(input); +talkatu_input_set_id(TalkatuInput *input, guint64 id) { + TalkatuInputPrivate *priv = talkatu_input_get_instance_private(input); + g_object_notify(G_OBJECT(input), "id"); +talkatu_input_get_timestamp(TalkatuInput *input) { + TalkatuInputPrivate *priv = talkatu_input_get_instance_private(input); + return priv->timestamp; +talkatu_input_set_timestamp(TalkatuInput *input, GDateTime *timestamp) { + TalkatuInputPrivate *priv = talkatu_input_get_instance_private(input); + g_clear_pointer(&priv->timestamp, g_date_time_unref); + if(timestamp != NULL) { + priv->timestamp = g_date_time_ref(timestamp); + g_object_notify(G_OBJECT(input), "timestamp"); +static TalkatuContentType +talkatu_input_get_content_type(TalkatuInput *input) { + /* TODO: look at our buffer and map it */ + return TALKATU_CONTENT_TYPE_PLAIN; +talkatu_input_set_content_type(TalkatuInput *input, + TalkatuContentType content_type) + /* TODO: set the buffer here? */ + g_object_notify(G_OBJECT(input), "content-type"); +talkatu_input_get_author(TalkatuInput *input) { + TalkatuInputPrivate *priv = talkatu_input_get_instance_private(input); +talkatu_input_set_author(TalkatuInput *input, const gchar *author) { + TalkatuInputPrivate *priv = talkatu_input_get_instance_private(input); + g_clear_pointer(&priv->author, g_free); + priv->author = g_strdup(author); + g_object_notify(G_OBJECT(input), "author"); +talkatu_input_get_contents(TalkatuInput *input) { + GtkTextBuffer *buffer = NULL; + buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(input)); + return talkatu_markup_get_html(buffer, NULL); +talkatu_input_set_contents(TalkatuInput *input, const gchar *contents) { + GtkTextBuffer *buffer = NULL; + buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(input)); + talkatu_buffer_clear(TALKATU_BUFFER(buffer)); + talkatu_markup_set_html(TALKATU_BUFFER(buffer), contents, -1); + g_object_notify(G_OBJECT(input), "contents"); +talkatu_input_get_edited(TalkatuInput *input) { + TalkatuInputPrivate *priv = talkatu_input_get_instance_private(input); +talkatu_input_set_edited(TalkatuInput *input, gboolean edited) { + TalkatuInputPrivate *priv = talkatu_input_get_instance_private(input); + g_object_notify(G_OBJECT(input), "edited"); +talkatu_input_add_attachment(TalkatuMessage *message, + TalkatuAttachment *attachment) + TalkatuInput *input = TALKATU_INPUT(message); + TalkatuInputPrivate *priv = talkatu_input_get_instance_private(input); + return g_hash_table_insert( + talkatu_attachment_get_hash_key(attachment), + g_object_ref(G_OBJECT(attachment)) +talkatu_input_remove_attachment(TalkatuMessage *message, gint64 id) { + TalkatuInput *input = TALKATU_INPUT(message); + TalkatuInputPrivate *priv = talkatu_input_get_instance_private(input); + return g_hash_table_remove(priv->attachments, &id); +static TalkatuAttachment * +talkatu_input_get_attachment(TalkatuMessage *message, gint64 id) { + TalkatuInput *input = TALKATU_INPUT(message); + TalkatuInputPrivate *priv = talkatu_input_get_instance_private(input); + TalkatuAttachment *attachment = NULL; + attachment = g_hash_table_lookup(priv->attachments, &id); + if(TALKATU_IS_ATTACHMENT(attachment)) { + return TALKATU_ATTACHMENT(g_object_ref(G_OBJECT(attachment))); +talkatu_input_foreach_attachment_helper(gpointer key, gpointer value, + TalkatuInputForeachAttachmentData *d = (TalkatuInputForeachAttachmentData *)data; + d->func(TALKATU_ATTACHMENT(value), d->data); +talkatu_input_foreach_attachment(TalkatuMessage *message, + TalkatuAttachmentForeachFunc func, + TalkatuInput *input = TALKATU_INPUT(message); + TalkatuInputPrivate *priv = talkatu_input_get_instance_private(input); + TalkatuInputForeachAttachmentData d = { + g_hash_table_foreach(priv->attachments, + talkatu_input_foreach_attachment_helper, +talkatu_input_clear_attachments(TalkatuMessage *message) { + TalkatuInput *input = TALKATU_INPUT(message); + TalkatuInputPrivate *priv = talkatu_input_get_instance_private(input); + g_hash_table_remove_all(priv->attachments); +talkatu_input_message_init(TalkatuMessageInterface *iface) { + iface->add_attachment = talkatu_input_add_attachment; + iface->remove_attachment = talkatu_input_remove_attachment; + iface->get_attachment = talkatu_input_get_attachment; + iface->foreach_attachment = talkatu_input_foreach_attachment; + iface->clear_attachments = talkatu_input_clear_attachments; /******************************************************************************
@@ -148,6 +374,7 @@
/* now send the attachment */
attachment = talkatu_attachment_dialog_get_attachment(adialog);
+ talkatu_message_add_attachment(TALKATU_MESSAGE(input), attachment); g_object_unref(G_OBJECT(attachment));
@@ -171,7 +398,8 @@
buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(input));
comment = talkatu_markup_get_html(buffer, NULL);
- attachment = talkatu_attachment_new_from_pixbuf("unknown.png", pixbuf);
+ attachment = talkatu_attachment_new_from_pixbuf(G_GUINT64_CONSTANT(0), + "unknown.png", pixbuf); dialog = talkatu_attachment_dialog_new(attachment, comment);
g_signal_connect(G_OBJECT(dialog), "response",
@@ -243,6 +471,24 @@
g_value_set_flags(value, talkatu_input_get_send_binding(input));
+ g_value_set_uint64(value, talkatu_input_get_id(input)); + g_value_set_boxed(value, talkatu_input_get_timestamp(input)); + case PROP_CONTENT_TYPE: + g_value_set_enum(value, talkatu_input_get_content_type(input)); + g_value_set_string(value, talkatu_input_get_author(input)); + g_value_set_string(value, talkatu_input_get_contents(input)); + g_value_set_boolean(value, talkatu_input_get_edited(input)); G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, pspec);
@@ -257,6 +503,24 @@
talkatu_input_set_send_binding(input, g_value_get_flags(value));
+ talkatu_input_set_id(input, g_value_get_uint64(value)); + talkatu_input_set_timestamp(input, g_value_get_boxed(value)); + case PROP_CONTENT_TYPE: + talkatu_input_set_content_type(input, g_value_get_enum(value)); + talkatu_input_set_author(input, g_value_get_string(value)); + talkatu_input_set_contents(input, g_value_get_string(value)); + talkatu_input_set_edited(input, g_value_get_boolean(value)); G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, pspec);
@@ -267,6 +531,9 @@
talkatu_input_init(TalkatuInput *input) {
TalkatuInputPrivate *priv = talkatu_input_get_instance_private(input);
+ priv->attachments = g_hash_table_new_full(g_int64_hash, g_int64_equal, /* we need to know when the buffer is changed in our parent so we can
* update our actions and other stuff.
@@ -292,6 +559,16 @@
+talkatu_input_finalize(GObject *obj) { + TalkatuInput *input = TALKATU_INPUT(obj); + TalkatuInputPrivate *priv = talkatu_input_get_instance_private(input); + g_hash_table_destroy(priv->attachments); + G_OBJECT_CLASS(talkatu_input_parent_class)->finalize(obj); talkatu_input_class_init(TalkatuInputClass *klass) {
GObjectClass *obj_class = G_OBJECT_CLASS(klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
@@ -300,6 +577,7 @@
obj_class->get_property = talkatu_input_get_property;
obj_class->set_property = talkatu_input_set_property;
+ obj_class->finalize = talkatu_input_finalize; widget_class->popup_menu = talkatu_input_popup_menu;
@@ -316,6 +594,14 @@
g_object_class_install_properties(obj_class, N_PROPERTIES, properties);
+ /* override the properties for the interface */ + g_object_class_override_property(obj_class, PROP_ID, "id"); + g_object_class_override_property(obj_class, PROP_TIMESTAMP, "timestamp"); + g_object_class_override_property(obj_class, PROP_CONTENT_TYPE, "content-type"); + g_object_class_override_property(obj_class, PROP_AUTHOR, "author"); + g_object_class_override_property(obj_class, PROP_CONTENTS, "contents"); + g_object_class_override_property(obj_class, PROP_EDITED, "edited"); * TalkatuInput::should-send-message:
* @talkatuinput: The #TalkatuInput instance.
--- a/talkatu/talkatumessage.c Tue Jan 28 21:53:46 2020 -0600
+++ b/talkatu/talkatumessage.c Wed Jan 29 08:35:45 2020 -0600
@@ -86,8 +86,9 @@
* The timestamp for when this message was created.
- pspec = g_param_spec_pointer(
+ pspec = g_param_spec_boxed( "timestamp", "timestamp", "The timestamp of the message",
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY
g_object_interface_install_property(iface, pspec);
@@ -237,6 +238,22 @@
+ * talkatu_message_set_content_type: + * @message: The #TalkatuMessage instance. + * @content_type: The new #TalkatuContentType to set. + * Sets the content type of @message to @content_type. +talkatu_message_set_content_type(TalkatuMessage *message, + TalkatuContentType content_type) + g_return_if_fail(TALKATU_IS_MESSAGE(message)); + g_object_set(G_OBJECT(message), "content-type", content_type, NULL); * talkatu_message_get_author:
* @message: The #TalkatuMessage instance.
--- a/talkatu/talkatumessage.h Tue Jan 28 21:53:46 2020 -0600
+++ b/talkatu/talkatumessage.h Wed Jan 29 08:35:45 2020 -0600
@@ -65,6 +65,7 @@
void talkatu_message_set_timestamp(TalkatuMessage *message, GDateTime *timestamp);
TalkatuContentType talkatu_message_get_content_type(TalkatuMessage *message);
+void talkatu_message_set_content_type(TalkatuMessage *message, TalkatuContentType content_type); gchar *talkatu_message_get_author(TalkatuMessage *message);
void talkatu_message_set_author(TalkatuMessage *message, const gchar *author);