talkatu/talkatu

Merged in default (pull request #46)

2020-06-30, Gary Kramlich
db6cb7b86ab3
Merged in default (pull request #46)

Add attachments to messages

Approved-by: Elliott Sales de Andrade
--- a/demo/data/demo.ui Mon Jun 08 05:47:27 2020 +0000
+++ b/demo/data/demo.ui Tue Jun 30 05:14:12 2020 +0000
@@ -255,8 +255,8 @@
<property name="fill">False</property>
</packing>
</child>
- <child internal-child="view">
- <object class="TalkatuView">
+ <child internal-child="input">
+ <object class="TalkatuInput">
<property name="hexpand_set">True</property>
<property name="vexpand_set">True</property>
<property name="buffer">buffer_plain</property>
--- a/demo/meson.build Mon Jun 08 05:47:27 2020 +0000
+++ b/demo/meson.build Tue Jun 30 05:14:12 2020 +0000
@@ -3,8 +3,6 @@
###############################################################################
sources = [
'talkatudemo.c',
- 'talkatudemomessage.c',
- 'talkatudemomessage.h',
'talkatudemowindow.c',
'talkatudemowindow.h',
]
--- a/demo/talkatudemomessage.c Mon Jun 08 05:47:27 2020 +0000
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,239 +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 "talkatudemomessage.h"
-
-struct _TalkatuDemoMessage {
- GObject parent;
-
- guint64 id;
- GDateTime *timestamp;
- TalkatuContentType content_type;
- gchar *author;
- gchar *contents;
-};
-
-enum {
- PROP_0,
- N_PROPERTIES,
- /* overrides */
- PROP_ID = N_PROPERTIES,
- PROP_CONTENT_TYPE,
- PROP_AUTHOR,
- PROP_CONTENTS,
- PROP_TIMESTAMP,
-};
-
-static void
-talkatu_demo_message_iface_init(TalkatuMessageInterface *iface) {
-}
-
-G_DEFINE_TYPE_EXTENDED(
- TalkatuDemoMessage,
- talkatu_demo_message,
- G_TYPE_OBJECT,
- 0,
- G_IMPLEMENT_INTERFACE(TALKATU_TYPE_MESSAGE, talkatu_demo_message_iface_init)
-);
-
-/******************************************************************************
- * Accessors
- *****************************************************************************/
-static guint64
-talkatu_demo_message_get_id(TalkatuDemoMessage *message) {
- return message->id;
-}
-
-static void
-talkatu_demo_message_set_id(TalkatuDemoMessage *message, guint64 id) {
- message->id = id;
-
- g_object_notify(G_OBJECT(message), "id");
-}
-
-static GDateTime *
-talkatu_demo_message_get_timestamp(TalkatuDemoMessage *message) {
- return (message->timestamp) ? g_date_time_ref(message->timestamp) : NULL;
-}
-
-static void
-talkatu_demo_message_set_timestamp(TalkatuDemoMessage *message, GDateTime *timestamp) {
- g_clear_pointer(&message->timestamp, g_date_time_unref);
-
- if(timestamp) {
- message->timestamp = g_date_time_ref(timestamp);
- }
-}
-
-static TalkatuContentType
-talkatu_demo_message_get_content_type(TalkatuDemoMessage *message) {
- return message->content_type;
-}
-
-static void
-talkatu_demo_message_set_content_type(TalkatuDemoMessage *message, TalkatuContentType content_type) {
- message->content_type = content_type;
-
- g_object_notify(G_OBJECT(message), "content-type");
-}
-
-static gchar *
-talkatu_demo_message_get_author(TalkatuDemoMessage *message) {
- return message->author;
-}
-
-static void
-talkatu_demo_message_set_author(TalkatuDemoMessage *message, const gchar *author) {
- g_free(message->author);
-
- message->author = g_strdup(author);
-
- g_object_notify(G_OBJECT(message), "author");
-}
-
-static gchar *
-talkatu_demo_message_get_contents(TalkatuDemoMessage *message) {
- return message->contents;
-}
-
-static void
-talkatu_demo_message_set_contents(TalkatuDemoMessage *message, const gchar *contents) {
- g_free(message->contents);
-
- message->contents = g_strdup(contents);
-
- g_object_notify(G_OBJECT(message), "contents");
-}
-
-/******************************************************************************
- * GObject Stuff
- *****************************************************************************/
-static void
-talkatu_demo_message_get_property(GObject *obj,
- guint prop_id,
- GValue *value,
- GParamSpec *pspec)
-{
- TalkatuDemoMessage *message = TALKATU_DEMO_MESSAGE(obj);
-
- switch(prop_id) {
- case PROP_ID:
- g_value_set_uint64(value, talkatu_demo_message_get_id(message));
- break;
- case PROP_TIMESTAMP:
- g_value_set_pointer(value, talkatu_demo_message_get_timestamp(message));
- break;
- case PROP_CONTENT_TYPE:
- g_value_set_enum(value, talkatu_demo_message_get_content_type(message));
- break;
- case PROP_AUTHOR:
- g_value_set_string(value, talkatu_demo_message_get_author(message));
- break;
- case PROP_CONTENTS:
- g_value_set_string(value, talkatu_demo_message_get_contents(message));
- break;
- default:
- G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, pspec);
- break;
- }
-}
-
-static void
-talkatu_demo_message_set_property(GObject *obj,
- guint prop_id,
- const GValue *value,
- GParamSpec *pspec)
-{
- TalkatuDemoMessage *message = TALKATU_DEMO_MESSAGE(obj);
-
- switch(prop_id) {
- case PROP_ID:
- talkatu_demo_message_set_id(message, g_value_get_uint64(value));
- break;
- case PROP_TIMESTAMP:
- talkatu_demo_message_set_timestamp(message, g_value_get_pointer(value));
- break;
- case PROP_CONTENT_TYPE:
- talkatu_demo_message_set_content_type(message, g_value_get_enum(value));
- break;
- case PROP_AUTHOR:
- talkatu_demo_message_set_author(message, g_value_get_string(value));
- break;
- case PROP_CONTENTS:
- talkatu_demo_message_set_contents(message, g_value_get_string(value));
- break;
- default:
- G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, pspec);
- break;
- }
-}
-
-static void
-talkatu_demo_message_finalize(GObject *obj) {
- TalkatuDemoMessage *message = TALKATU_DEMO_MESSAGE(obj);
-
- talkatu_demo_message_set_timestamp(message, NULL);
- talkatu_demo_message_set_author(message, NULL);
- talkatu_demo_message_set_contents(message, NULL);
-
- G_OBJECT_CLASS(talkatu_demo_message_parent_class)->finalize(obj);
-}
-
-static void
-talkatu_demo_message_init(TalkatuDemoMessage *toolbar) {
-}
-
-static void
-talkatu_demo_message_class_init(TalkatuDemoMessageClass *klass) {
- GObjectClass *obj_class = G_OBJECT_CLASS(klass);
-
- obj_class->get_property = talkatu_demo_message_get_property;
- obj_class->set_property = talkatu_demo_message_set_property;
- obj_class->finalize = talkatu_demo_message_finalize;
-
- 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");
-}
-
-/******************************************************************************
- * Public API
- *****************************************************************************/
-TalkatuMessage *talkatu_demo_message_new(guint64 id,
- TalkatuContentType content_type,
- const gchar *author,
- const gchar *contents)
-{
- GObject *obj = NULL;
- GDateTime *timestamp = g_date_time_new_now_local();
-
- obj = g_object_new(
- TALKATU_TYPE_DEMO_MESSAGE,
- "id", id,
- "timestamp", timestamp,
- "content-type", content_type,
- "author", author,
- "contents", contents,
- NULL
- );
-
- g_date_time_unref(timestamp);
-
- return TALKATU_MESSAGE(obj);
-}
--- a/demo/talkatudemomessage.h Mon Jun 08 05:47:27 2020 +0000
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,37 +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/>.
- */
-
-#ifndef TALKATU_DEMO_MESSAGE_H
-#define TALKATU_DEMO_MESSAGE_H
-
-#include <glib.h>
-#include <glib-object.h>
-
-#include <talkatu/talkatu.h>
-
-G_BEGIN_DECLS
-
-#define TALKATU_TYPE_DEMO_MESSAGE (talkatu_demo_message_get_type())
-
-G_DECLARE_FINAL_TYPE(TalkatuDemoMessage, talkatu_demo_message, TALKATU_DEMO, MESSAGE, GObject)
-
-TalkatuMessage *talkatu_demo_message_new(guint64 id, TalkatuContentType content_type, const gchar *author, const gchar *message);
-
-G_END_DECLS
-
-#endif /* TALKATU_DEMO_MESSAGE_H */
--- a/demo/talkatudemowindow.c Mon Jun 08 05:47:27 2020 +0000
+++ b/demo/talkatudemowindow.c Tue Jun 30 05:14:12 2020 +0000
@@ -19,7 +19,6 @@
#include <gtk/gtk.h>
#include "talkatudemowindow.h"
-#include "talkatudemomessage.h"
struct _TalkatuDemoWindow {
GtkWindow parent;
@@ -45,7 +44,6 @@
GtkWidget *author_button;
GtkWidget *author_popover;
GtkWidget *author_item;
- gchar *author;
};
G_DEFINE_TYPE(TalkatuDemoWindow, talkatu_demo_window, GTK_TYPE_WINDOW);
@@ -141,7 +139,7 @@
static void
talkatu_demo_window_buffer_changed_cb(GtkToggleButton *toggle, gpointer data) {
TalkatuDemoWindow *window = TALKATU_DEMO_WINDOW(data);
- GtkWidget *view = talkatu_editor_get_view(TALKATU_EDITOR(window->editor));
+ GtkWidget *view = talkatu_editor_get_input(TALKATU_EDITOR(window->editor));
if(gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(window->toggle_plain))) {
g_message("switching to plain buffer");
@@ -161,14 +159,20 @@
static void
talkatu_demo_window_buffer_modified_cb(GtkTextBuffer *buffer, gpointer data) {
TalkatuDemoWindow *window = TALKATU_DEMO_WINDOW(data);
+ GtkWidget *input = talkatu_editor_get_input(TALKATU_EDITOR(window->editor));
+ gchar *author = NULL;
+
+ author = talkatu_message_get_author(TALKATU_MESSAGE(input));
if(gtk_text_buffer_get_char_count(buffer) > 0) {
talkatu_typing_label_start_typing(TALKATU_TYPING_LABEL(window->typing),
- window->author);
+ author);
} else {
talkatu_typing_label_finish_typing(TALKATU_TYPING_LABEL(window->typing),
- window->author);
+ author);
}
+
+ g_free(author);
}
static void
@@ -204,36 +208,27 @@
}
static void
-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));
static guint64 id = 0;
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(
- id++,
- content_type,
- window->author,
- plain_text
- );
- g_free(plain_text);
+ talkatu_message_set_id(TALKATU_MESSAGE(input), id++);
talkatu_history_buffer_write_message(
TALKATU_HISTORY_BUFFER(window->buffer_history),
- message
+ TALKATU_MESSAGE(input)
);
- 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));
}
static gboolean
@@ -266,8 +261,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)));
}
}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/talkatu/data/attachmentdialog.ui Tue Jun 30 05:14:12 2020 +0000
@@ -0,0 +1,104 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.1 -->
+<interface>
+ <requires lib="gtk+" version="3.18"/>
+ <template class="TalkatuAttachmentDialog" parent="GtkDialog">
+ <property name="can_focus">False</property>
+ <property name="border_width">12</property>
+ <property name="modal">True</property>
+ <property name="type_hint">dialog</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child internal-child="vbox">
+ <object class="GtkBox">
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">2</property>
+ <child internal-child="action_area">
+ <object class="GtkButtonBox">
+ <property name="can_focus">False</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="cancel">
+ <property name="label" translatable="yes">Cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="upload">
+ <property name="label" translatable="yes">Upload</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="has_default">True</property>
+ <property name="receives_default">True</property>
+ <accelerator key="Return" signal="clicked"/>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkImage" id="preview">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="pixel_size">256</property>
+ <property name="icon_name">text-x-generic-template</property>
+ <property name="icon_size">6</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="filename">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">&lt;filename&gt;</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="comment">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="has_focus">True</property>
+ <property name="placeholder_text" translatable="yes">Comment (optional)</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="-6">cancel</action-widget>
+ <action-widget response="-3">upload</action-widget>
+ </action-widgets>
+ </template>
+</interface>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/talkatu/data/attachmentpreview.ui Tue Jun 30 05:14:12 2020 +0000
@@ -0,0 +1,108 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.1 -->
+<interface>
+ <requires lib="gtk+" version="3.18"/>
+ <template class="TalkatuAttachmentPreview" parent="GtkInfoBar">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child internal-child="action_area">
+ <object class="GtkButtonBox">
+ <property name="can_focus">False</property>
+ <property name="spacing">6</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="relief">none</property>
+ <signal name="clicked" handler="talkatu_attachment_preview_download_cb" object="TalkatuAttachmentPreview" swapped="no"/>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">document-save-symbolic</property>
+ <property name="icon_size">5</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ <property name="non_homogeneous">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child internal-child="content_area">
+ <object class="GtkBox">
+ <property name="can_focus">False</property>
+ <property name="spacing">16</property>
+ <child>
+ <object class="GtkImage" id="preview">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">text-x-generic</property>
+ <property name="icon_size">6</property>
+ </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="GtkLabel" id="filename">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">unknown</property>
+ <property name="xalign">0</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="filesize">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">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>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </template>
+</interface>
--- a/talkatu/data/editor.ui Mon Jun 08 05:47:27 2020 +0000
+++ b/talkatu/data/editor.ui Tue Jun 30 05:14:12 2020 +0000
@@ -34,7 +34,7 @@
<property name="hscrollbar_policy">never</property>
<property name="shadow_type">in</property>
<child>
- <object class="TalkatuView" id="view">
+ <object class="TalkatuInput" id="input">
<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>
--- a/talkatu/data/talkatu.gresource.xml Mon Jun 08 05:47:27 2020 +0000
+++ b/talkatu/data/talkatu.gresource.xml Tue Jun 30 05:14:12 2020 +0000
@@ -1,9 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/org/bitbucket/pidgin/talkatu/ui">
- <file compressed="true">editor.ui</file>
- <file compressed="true">linkdialog.ui</file>
- <file compressed="true">messageactions.ui</file>
+ <file compressed="true">attachmentdialog.ui</file>
+ <file compressed="true">attachmentpreview.ui</file>
+ <file compressed="true">editor.ui</file>
+ <file compressed="true">linkdialog.ui</file>
+ <file compressed="true">messageactions.ui</file>
<file compressed="true">toolbar.ui</file>
</gresource>
</gresources>
--- a/talkatu/data/toolbar.ui Mon Jun 08 05:47:27 2020 +0000
+++ b/talkatu/data/toolbar.ui Tue Jun 30 05:14:12 2020 +0000
@@ -127,20 +127,6 @@
</packing>
</child>
<child>
- <object class="GtkToolButton" id="insert_image">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="action_name">talkatu.insert-image</property>
- <property name="label" translatable="yes">Insert Image</property>
- <property name="use_underline">True</property>
- <property name="icon_name">insert-image</property>
- </object>
- <packing>
- <property name="expand">False</property>
- <property name="homogeneous">True</property>
- </packing>
- </child>
- <child>
<object class="GtkToggleToolButton" id="insert_link">
<property name="visible">True</property>
<property name="can_focus">False</property>
@@ -158,8 +144,8 @@
<object class="GtkToolButton" id="insert_file">
<property name="visible">True</property>
<property name="can_focus">False</property>
- <property name="action_name">talkatu.insert-file</property>
- <property name="label" translatable="yes">Insert File</property>
+ <property name="action_name">talkatu.attach-file</property>
+ <property name="label" translatable="yes">Attach File</property>
<property name="icon_name">insert-object</property>
</object>
<packing>
--- a/talkatu/meson.build Mon Jun 08 05:47:27 2020 +0000
+++ b/talkatu/meson.build Tue Jun 30 05:14:12 2020 +0000
@@ -5,6 +5,9 @@
TALKATU_LIBRARY_VERSION = '0.1.0'
TALKATU_HEADERS = [
'talkatuactiongroup.h',
+ 'talkatuattachment.h',
+ 'talkatuattachmentdialog.h',
+ 'talkatuattachmentpreview.h',
'talkatubuffer.h',
'talkatucodeset.h',
'talkatucore.h',
@@ -12,6 +15,7 @@
'talkatuhistory.h',
'talkatuhistorybuffer.h',
'talkatuhtmlbuffer.h',
+ 'talkatuinput.h',
'talkatulinkdialog.h',
'talkatumarkdownbuffer.h',
'talkatumarkup.h',
@@ -29,6 +33,9 @@
TALKATU_SOURCES = [
'talkatuactiongroup.c',
+ 'talkatuattachment.c',
+ 'talkatuattachmentdialog.c',
+ 'talkatuattachmentpreview.c',
'talkatubuffer.c',
'talkatucodeset.c',
'talkatucore.c',
@@ -36,6 +43,7 @@
'talkatuhistory.c',
'talkatuhistorybuffer.c',
'talkatuhtmlbuffer.c',
+ 'talkatuinput.c',
'talkatulinkdialog.c',
'talkatumarkdownbuffer.c',
'talkatumarkup.c',
@@ -77,7 +85,7 @@
'talkatubuffer.h',
'talkatumessage.h',
'talkatutag.h',
- 'talkatuview.h',
+ 'talkatuinput.h',
]
###############################################################################
--- a/talkatu/reference/talkatu-docs.xml Mon Jun 08 05:47:27 2020 +0000
+++ b/talkatu/reference/talkatu-docs.xml Tue Jun 30 05:14:12 2020 +0000
@@ -40,6 +40,8 @@
<xi:include href="xml/talkatuhistorybuffer.xml"/>
<xi:include href="xml/talkatuhistory.xml"/>
+ <xi:include href="xml/talkatuinput.xml"/>
+
<xi:include href="xml/talkatueditor.xml"/>
<xi:include href="xml/talkatuview.xml"/>
@@ -52,6 +54,10 @@
<xi:include href="xml/talkatutypinglabel.xml"/>
+ <xi:include href="xml/talkatuattachment.xml"/>
+ <xi:include href="xml/talkatuattachmentdialog.xml"/>
+ <xi:include href="xml/talkatuattachmentpreview.xml"/>
+
<xi:include href="xml/talkatucore.xml"/>
<xi:include href="xml/talkatucodeset.xml"/>
<xi:include href="xml/talkatuversion.xml"/>
--- a/talkatu/talkatu.xml Mon Jun 08 05:47:27 2020 +0000
+++ b/talkatu/talkatu.xml Tue Jun 30 05:14:12 2020 +0000
@@ -9,7 +9,10 @@
</internal-children>
</glade-widget-class>
+ <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="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="TalkatuToolDrawer" generic-name="tool_drawer" title="ToolDrawer"/>
@@ -49,11 +52,14 @@
</glade-widget-class>
</glade-widget-classes>
<glade-widget-group name="Talkatu" title="Talkatu">
+ <glade-widget-class-ref name="TalkatuAttachmentDialog"/>
+ <glade-widget-class-ref name="TalkatuAttachmentPreview"/>
<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="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"/>
--- a/talkatu/talkatuactiongroup.c Mon Jun 08 05:47:27 2020 +0000
+++ b/talkatu/talkatuactiongroup.c Tue Jun 30 05:14:12 2020 +0000
@@ -18,8 +18,12 @@
#include <glib/gi18n-lib.h>
#include "talkatu/talkatuactiongroup.h"
+#include "talkatu/talkatuattachment.h"
+#include "talkatu/talkatuattachmentdialog.h"
#include "talkatu/talkatubuffer.h"
#include "talkatu/talkatulinkdialog.h"
+#include "talkatu/talkatumarkup.h"
+#include "talkatu/talkatumessage.h"
#include "talkatu/talkatutag.h"
/**
@@ -96,6 +100,12 @@
*/
/**
+ * TALKATU_ACTION_ATTACH_FILE:
+ *
+ * A constant that represents the attach file action.
+ */
+
+/**
* TALKATU_ACTION_INSERT_LINK:
*
* A constant that presents the action to activate when the user wants to
@@ -104,11 +114,13 @@
typedef struct {
GtkTextBuffer *buffer;
+ TalkatuInput *input;
} TalkatuActionGroupPrivate;
enum {
PROP_0,
PROP_BUFFER,
+ PROP_INPUT,
N_PROPERTIES
};
static GParamSpec *properties[N_PROPERTIES] = {NULL,};
@@ -128,11 +140,9 @@
talkatu_action_group_set_buffer(TalkatuActionGroup *ag, GtkTextBuffer *buffer) {
TalkatuActionGroupPrivate *priv = talkatu_action_group_get_instance_private(ag);
- if(priv->buffer) {
- g_object_unref(priv->buffer);
- }
+ g_return_if_fail(TALKATU_IS_BUFFER(buffer));
- priv->buffer = g_object_ref(buffer);
+ g_set_object(&priv->buffer, buffer);
}
static void
@@ -223,20 +233,23 @@
}
static void
-talkatu_action_format_toggle(GSimpleAction *action,
- GVariant *state,
- gpointer data)
+talkatu_action_format_toggle(GSimpleAction *action, GVariant *state,
+ gpointer data)
{
- GtkTextBuffer *buffer = GTK_TEXT_BUFFER(data);
+ TalkatuActionGroup *ag = TALKATU_ACTION_GROUP(data);
+ TalkatuActionGroupPrivate *priv = NULL;
GtkTextIter start, end;
- TalkatuBufferStyle style = talkatu_buffer_get_style(TALKATU_BUFFER(buffer));
+ TalkatuBufferStyle style;
gboolean apply = FALSE;
+ priv = talkatu_action_group_get_instance_private(ag);
+ style = talkatu_buffer_get_style(TALKATU_BUFFER(priv->buffer));
+
if(style == TALKATU_BUFFER_STYLE_WHOLE) {
- gtk_text_buffer_get_bounds(buffer, &start, &end);
+ gtk_text_buffer_get_bounds(priv->buffer, &start, &end);
apply = TRUE;
} else {
- if(gtk_text_buffer_get_selection_bounds(buffer, &start, &end)) {
+ if(gtk_text_buffer_get_selection_bounds(priv->buffer, &start, &end)) {
apply = TRUE;
}
}
@@ -251,65 +264,60 @@
return;
}
- gtk_text_buffer_begin_user_action(buffer);
+ gtk_text_buffer_begin_user_action(priv->buffer);
if(g_variant_get_boolean(state)) {
- gtk_text_buffer_apply_tag_by_name(buffer, tag_name, &start, &end);
+ gtk_text_buffer_apply_tag_by_name(priv->buffer, tag_name, &start, &end);
} else {
- gtk_text_buffer_remove_tag_by_name(buffer, tag_name, &start, &end);
+ gtk_text_buffer_remove_tag_by_name(priv->buffer, tag_name, &start, &end);
}
- gtk_text_buffer_end_user_action(buffer);
+ gtk_text_buffer_end_user_action(priv->buffer);
}
g_simple_action_set_state(action, state);
}
static void
-talkatu_action_reset_activate(GSimpleAction *act,
- GVariant *parameter,
- gpointer data)
+talkatu_action_reset_activate(GSimpleAction *act, GVariant *parameter,
+ gpointer data)
{
- GtkTextBuffer *buffer = GTK_TEXT_BUFFER(data);
- GSimpleActionGroup *ag = NULL;
+ TalkatuActionGroup *ag = TALKATU_ACTION_GROUP(data);
+ TalkatuActionGroupPrivate *priv = NULL;
GtkTextIter start, end;
TalkatuBufferStyle style;
gchar **actions = NULL, **action = NULL;
- g_return_if_fail(TALKATU_IS_BUFFER(buffer));
-
- style = talkatu_buffer_get_style(TALKATU_BUFFER(buffer));
- ag = talkatu_buffer_get_action_group(TALKATU_BUFFER(buffer));
-
- g_return_if_fail(TALKATU_IS_ACTION_GROUP(ag));
+ priv = talkatu_action_group_get_instance_private(ag);
+ style = talkatu_buffer_get_style(TALKATU_BUFFER(priv->buffer));
/* if there's no selection or this is a whole buffer format, select the
* whole buffer.
*/
- if(!gtk_text_buffer_get_selection_bounds(buffer, &start, &end) || style == TALKATU_BUFFER_STYLE_WHOLE) {
- gtk_text_buffer_get_bounds(buffer, &start, &end);
- gtk_text_buffer_select_range(buffer, &start, &end);
+ if(!gtk_text_buffer_get_selection_bounds(priv->buffer, &start, &end) || style == TALKATU_BUFFER_STYLE_WHOLE) {
+ gtk_text_buffer_get_bounds(priv->buffer, &start, &end);
+ gtk_text_buffer_select_range(priv->buffer, &start, &end);
}
/* run through all of the talkatu tags and remove them from the
* selection.
*/
- gtk_text_buffer_remove_tag_by_name(buffer, TALKATU_TAG_BOLD, &start, &end);
- gtk_text_buffer_remove_tag_by_name(buffer, TALKATU_TAG_ITALIC, &start, &end);
- gtk_text_buffer_remove_tag_by_name(buffer, TALKATU_TAG_UNDERLINE, &start, &end);
- gtk_text_buffer_remove_tag_by_name(buffer, TALKATU_TAG_STRIKETHROUGH, &start, &end);
- gtk_text_buffer_remove_tag_by_name(buffer, TALKATU_TAG_SUBSCRIPT, &start, &end);
- gtk_text_buffer_remove_tag_by_name(buffer, TALKATU_TAG_SUPERSCRIPT, &start, &end);
- gtk_text_buffer_remove_tag_by_name(buffer, TALKATU_TAG_PRE, &start, &end);
- gtk_text_buffer_remove_tag_by_name(buffer, TALKATU_TAG_CODE, &start, &end);
- gtk_text_buffer_remove_tag_by_name(buffer, TALKATU_TAG_SEARCH, &start, &end);
- gtk_text_buffer_remove_tag_by_name(buffer, TALKATU_TAG_H1, &start, &end);
- gtk_text_buffer_remove_tag_by_name(buffer, TALKATU_TAG_H2, &start, &end);
- gtk_text_buffer_remove_tag_by_name(buffer, TALKATU_TAG_H3, &start, &end);
- gtk_text_buffer_remove_tag_by_name(buffer, TALKATU_TAG_H4, &start, &end);
- gtk_text_buffer_remove_tag_by_name(buffer, TALKATU_TAG_H5, &start, &end);
- gtk_text_buffer_remove_tag_by_name(buffer, TALKATU_TAG_H6, &start, &end);
- gtk_text_buffer_remove_tag_by_name(buffer, TALKATU_TAG_ANCHOR, &start, &end);
+ gtk_text_buffer_remove_tag_by_name(priv->buffer, TALKATU_TAG_BOLD, &start, &end);
+ gtk_text_buffer_remove_tag_by_name(priv->buffer, TALKATU_TAG_ITALIC, &start, &end);
+ gtk_text_buffer_remove_tag_by_name(priv->buffer, TALKATU_TAG_UNDERLINE, &start, &end);
+ gtk_text_buffer_remove_tag_by_name(priv->buffer, TALKATU_TAG_STRIKETHROUGH, &start, &end);
+ gtk_text_buffer_remove_tag_by_name(priv->buffer, TALKATU_TAG_SUBSCRIPT, &start, &end);
+ gtk_text_buffer_remove_tag_by_name(priv->buffer, TALKATU_TAG_SUPERSCRIPT, &start, &end);
+ gtk_text_buffer_remove_tag_by_name(priv->buffer, TALKATU_TAG_PRE, &start, &end);
+ gtk_text_buffer_remove_tag_by_name(priv->buffer, TALKATU_TAG_CODE, &start, &end);
+ gtk_text_buffer_remove_tag_by_name(priv->buffer, TALKATU_TAG_SEARCH, &start, &end);
+ gtk_text_buffer_remove_tag_by_name(priv->buffer, TALKATU_TAG_H1, &start, &end);
+ gtk_text_buffer_remove_tag_by_name(priv->buffer, TALKATU_TAG_H2, &start, &end);
+ gtk_text_buffer_remove_tag_by_name(priv->buffer, TALKATU_TAG_H3, &start, &end);
+ gtk_text_buffer_remove_tag_by_name(priv->buffer, TALKATU_TAG_H4, &start, &end);
+ gtk_text_buffer_remove_tag_by_name(priv->buffer, TALKATU_TAG_H5, &start, &end);
+ gtk_text_buffer_remove_tag_by_name(priv->buffer, TALKATU_TAG_H6, &start, &end);
+ gtk_text_buffer_remove_tag_by_name(priv->buffer, TALKATU_TAG_ANCHOR, &start, &end);
/* now run through all the actions and deactive any that are active by
* activating them.
@@ -345,23 +353,122 @@
}
static void
-talkatu_action_insert_link(GSimpleAction *action,
- GVariant *parameter,
- gpointer user_data)
+talkatu_action_attach_file_attach_response_cb(GtkDialog *dialog, gint response,
+ gpointer data)
+{
+ if(response == GTK_RESPONSE_CANCEL) {
+ /* we call this separately for GTK_RESPONSE_CANCEL because
+ * GTK_RESPONSE_DELETE_EVENT already destroys the dialog.
+ */
+ gtk_widget_destroy(GTK_WIDGET(dialog));
+ } else if(response == GTK_RESPONSE_ACCEPT) {
+ TalkatuActionGroup *ag = TALKATU_ACTION_GROUP(data);
+ TalkatuActionGroupPrivate *priv = NULL;
+ TalkatuAttachment *attachment = NULL;
+ TalkatuAttachmentDialog *adialog = TALKATU_ATTACHMENT_DIALOG(dialog);
+ const gchar *comment = NULL;
+
+ priv = talkatu_action_group_get_instance_private(ag);
+
+ /* Set the message to the comment from the dialog. */
+ comment = talkatu_attachment_dialog_get_comment(adialog);
+ talkatu_markup_set_html(TALKATU_BUFFER(priv->buffer), comment, -1);
+
+ /* Add the attachment to the message */
+ attachment = talkatu_attachment_dialog_get_attachment(adialog);
+ talkatu_message_add_attachment(TALKATU_MESSAGE(priv->input), attachment);
+ g_object_unref(G_OBJECT(attachment));
+
+ /* Send the message! */
+ talkatu_input_send_message(priv->input);
+
+ gtk_widget_destroy(GTK_WIDGET(dialog));
+ }
+}
+
+static void
+talkatu_action_attach_file_response_cb(GtkDialog *dialog, gint response,
+ gpointer data)
{
- GtkTextBuffer *buffer = GTK_TEXT_BUFFER(user_data);
+ if(response == GTK_RESPONSE_CANCEL) {
+ /* we call this separately for GTK_RESPONSE_CANCEL because
+ * GTK_RESPONSE_DELETE_EVENT already destroys the dialog.
+ */
+ gtk_widget_destroy(GTK_WIDGET(dialog));
+ } else if(response == GTK_RESPONSE_ACCEPT) {
+ TalkatuActionGroup *ag = TALKATU_ACTION_GROUP(data);
+ TalkatuActionGroupPrivate *priv = NULL;
+ TalkatuAttachment *attachment = NULL;
+ GtkWidget *attach_dialog = NULL;
+ gchar *filename = NULL, *content_type = NULL, *comment = NULL;
+
+ priv = talkatu_action_group_get_instance_private(ag);
+
+ filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
+ content_type = g_content_type_guess(filename, NULL, 0, NULL);
+
+ attachment = talkatu_attachment_new(G_GUINT64_CONSTANT(0), content_type);
+ g_free(content_type);
+
+ talkatu_attachment_set_local_uri(attachment, filename);
+ g_free(filename);
+
+ gtk_widget_destroy(GTK_WIDGET(dialog));
+
+ comment = talkatu_markup_get_html(priv->buffer, NULL);
+ attach_dialog = talkatu_attachment_dialog_new(attachment, comment);
+ g_free(comment);
+
+ g_signal_connect(G_OBJECT(attach_dialog), "response",
+ G_CALLBACK(talkatu_action_attach_file_attach_response_cb),
+ data);
+ gtk_widget_show_all(attach_dialog);
+ }
+}
+
+static void
+talkatu_action_attach_file(GSimpleAction *action, GVariant *parameter,
+ gpointer data)
+{
+ TalkatuActionGroup *ag = TALKATU_ACTION_GROUP(data);
+ TalkatuActionGroupPrivate *priv = NULL;
+ GtkWidget *dialog = NULL;
+
+ priv = talkatu_action_group_get_instance_private(ag);
+ g_return_if_fail(TALKATU_IS_INPUT(priv->input));
+
+ dialog = gtk_file_chooser_dialog_new(_("Attach file..."),
+ NULL,
+ GTK_FILE_CHOOSER_ACTION_OPEN,
+ _("Cancel"), GTK_RESPONSE_CANCEL,
+ _("Open"), GTK_RESPONSE_ACCEPT,
+ NULL);
+ g_signal_connect(G_OBJECT(dialog), "response",
+ G_CALLBACK(talkatu_action_attach_file_response_cb),
+ ag);
+ gtk_widget_show_all(dialog);
+}
+
+static void
+talkatu_action_insert_link(GSimpleAction *action, GVariant *parameter,
+ gpointer data)
+{
+ TalkatuActionGroup *ag = TALKATU_ACTION_GROUP(data);
+ TalkatuActionGroupPrivate *priv = NULL;
GtkWidget *link_dialog = talkatu_link_dialog_new();
+ priv = talkatu_action_group_get_instance_private(ag);
+
if(gtk_dialog_run(GTK_DIALOG(link_dialog)) == GTK_RESPONSE_ACCEPT) {
- GtkTextTagTable *table = gtk_text_buffer_get_tag_table(buffer);
+ GtkTextTagTable *table = gtk_text_buffer_get_tag_table(priv->buffer);
GtkTextTag *anchor, *anchor_data;
GtkTextMark *insert_mark = NULL;
GtkTextIter insert;
gchar *label = NULL, *url = NULL;
/* if any text is selected, delete it */
- if(gtk_text_buffer_get_has_selection(buffer)) {
- gtk_text_buffer_delete_selection(buffer, TRUE, TRUE);
+ if(gtk_text_buffer_get_has_selection(priv->buffer)) {
+ gtk_text_buffer_delete_selection(priv->buffer, TRUE, TRUE);
}
/* grab our inputs from the dialog */
@@ -380,11 +487,11 @@
g_object_set_data_full(G_OBJECT(anchor_data), "talkatu-anchor-url", url, g_free);
gtk_text_tag_table_add(table, anchor_data);
- insert_mark = gtk_text_buffer_get_insert(buffer);
- gtk_text_buffer_get_iter_at_mark(buffer, &insert, insert_mark);
+ insert_mark = gtk_text_buffer_get_insert(priv->buffer);
+ gtk_text_buffer_get_iter_at_mark(priv->buffer, &insert, insert_mark);
gtk_text_buffer_insert_with_tags(
- buffer,
+ priv->buffer,
&insert,
label,
-1,
@@ -414,6 +521,9 @@
case PROP_BUFFER:
g_value_set_object(value, talkatu_action_group_get_buffer(ag));
break;
+ case PROP_INPUT:
+ g_value_set_object(value, talkatu_action_group_get_input(ag));
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, pspec);
break;
@@ -432,6 +542,9 @@
case PROP_BUFFER:
talkatu_action_group_set_buffer(ag, g_value_get_object(value));
break;
+ case PROP_INPUT:
+ talkatu_action_group_set_input(ag, g_value_get_object(value));
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, pspec);
break;
@@ -442,10 +555,8 @@
talkatu_action_group_finalize(GObject *obj) {
TalkatuActionGroupPrivate *priv = talkatu_action_group_get_instance_private(TALKATU_ACTION_GROUP(obj));
- if(priv->buffer) {
- g_object_unref(priv->buffer);
- priv->buffer = NULL;
- }
+ g_clear_object(&priv->buffer);
+ g_clear_object(&priv->input);
G_OBJECT_CLASS(talkatu_action_group_parent_class)->finalize(obj);
}
@@ -453,7 +564,6 @@
static void
talkatu_action_group_constructed(GObject *object) {
TalkatuActionGroup *ag = TALKATU_ACTION_GROUP(object);
- TalkatuActionGroupPrivate *priv = talkatu_action_group_get_instance_private(ag);
gint i;
GActionEntry entries[] = {
{
@@ -486,6 +596,9 @@
.name = TALKATU_ACTION_FORMAT_RESET,
.activate = talkatu_action_reset_activate,
}, {
+ .name = TALKATU_ACTION_ATTACH_FILE,
+ .activate = talkatu_action_attach_file,
+ }, {
.name = TALKATU_ACTION_INSERT_LINK,
.activate = talkatu_action_insert_link,
.state = "false",
@@ -507,7 +620,8 @@
NULL
);
- g_action_map_add_action_entries(G_ACTION_MAP(ag), entries, G_N_ELEMENTS(entries), priv->buffer);
+ g_action_map_add_action_entries(G_ACTION_MAP(ag), entries,
+ G_N_ELEMENTS(entries), ag);
/* disable all of the actions by default */
for(i = 0; i < G_N_ELEMENTS(entries); i++) {
@@ -532,10 +646,27 @@
obj_class->finalize = talkatu_action_group_finalize;
/* setup our properties */
+
+ /**
+ * TalkatuActionGroup::buffer:
+ *
+ * The #TalkatuBuffer that this action group is tied to.
+ */
properties[PROP_BUFFER] = g_param_spec_object(
"buffer", "buffer", "The buffer to work on",
- GTK_TYPE_TEXT_BUFFER,
- G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY
+ TALKATU_TYPE_BUFFER,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS
+ );
+
+ /**
+ * TalkatuActionGroup::input:
+ *
+ * The #TalkatuInput that this action group is tied to.
+ */
+ properties[PROP_INPUT] = g_param_spec_object(
+ "input", "input", "The input to work on",
+ TALKATU_TYPE_INPUT,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS
);
g_object_class_install_properties(obj_class, N_PROPERTIES, properties);
@@ -609,6 +740,44 @@
}
/**
+ * talkatu_action_group_set_input:
+ * @ag: The #TalkatuActionGroup instance.
+ * @input: A #TalkatuInput instance or %NULL.
+ *
+ * Binds @ag to @input for actions that need to work against the input widget.
+ */
+void
+talkatu_action_group_set_input(TalkatuActionGroup *ag, TalkatuInput *input) {
+ TalkatuActionGroupPrivate *priv = NULL;
+
+ g_return_if_fail(TALKATU_IS_ACTION_GROUP(ag));
+
+ priv = talkatu_action_group_get_instance_private(ag);
+ if(g_set_object(&priv->input, input)) {
+ g_object_notify_by_pspec(G_OBJECT(ag), properties[PROP_INPUT]);
+ }
+}
+
+/**
+ * talkatu_action_group_get_input:
+ * @ag: The #TalkatuActionGroup instance.
+ *
+ * Gets the #TalkatuInput bound to @ag.
+ *
+ * Returns: (transfer none): The #TalkatuInput that @ag is bound to.
+ */
+TalkatuInput *
+talkatu_action_group_get_input(TalkatuActionGroup *ag) {
+ TalkatuActionGroupPrivate *priv = NULL;
+
+ g_return_val_if_fail(TALKATU_IS_ACTION_GROUP(ag), NULL);
+
+ priv = talkatu_action_group_get_instance_private(ag);
+
+ return priv->input;
+}
+
+/**
* talkatu_action_group_enable_action:
* @ag: The #TalkatuActionGroup instance.
* @name: The name of the action to enable.
--- a/talkatu/talkatuactiongroup.h Mon Jun 08 05:47:27 2020 +0000
+++ b/talkatu/talkatuactiongroup.h Tue Jun 30 05:14:12 2020 +0000
@@ -26,6 +26,8 @@
#include <gio/gio.h>
#include <gtk/gtk.h>
+#include <talkatu/talkatuinput.h>
+
#define TALKATU_ACTION_FORMAT_BOLD ("format-bold")
#define TALKATU_ACTION_FORMAT_ITALIC ("format-italic")
#define TALKATU_ACTION_FORMAT_UNDERLINE ("format-underline")
@@ -34,6 +36,8 @@
#define TALKATU_ACTION_FORMAT_SHRINK ("format-shrink")
#define TALKATU_ACTION_FORMAT_RESET ("format-reset")
+#define TALKATU_ACTION_ATTACH_FILE ("attach-file")
+
#define TALKATU_ACTION_INSERT_LINK ("insert-link")
G_BEGIN_DECLS
@@ -57,6 +61,9 @@
GtkTextBuffer *talkatu_action_group_get_buffer(TalkatuActionGroup *ag);
+void talkatu_action_group_set_input(TalkatuActionGroup *ag, TalkatuInput *input);
+TalkatuInput *talkatu_action_group_get_input(TalkatuActionGroup *ag);
+
void talkatu_action_group_enable_action(TalkatuActionGroup *ag, const gchar *name);
const gchar *talkatu_action_name_for_tag_name(const gchar *tag_name);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/talkatu/talkatuattachment.c Tue Jun 30 05:14:12 2020 +0000
@@ -0,0 +1,471 @@
+/*
+ * 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 "talkatu/talkatuattachment.h"
+
+#include <glib/gi18n-lib.h>
+
+/**
+ * SECTION:talkatuattachment
+ * @Title: Attachments
+ * @Short_description: Message Attachments
+ *
+ * Attachments can be images, files, etc that can be attached to a
+ * #TalkatuMessage.
+ */
+
+/**
+ * TALKATU_TYPE_ATTACHMENT:
+ *
+ * The standard _get_type macro for #TalkatuAttachment.
+ */
+
+/**
+ * TalkatuAttachment:
+ *
+ * TalkatuAttachment represents an attached file. The files can be any type of
+ * regular file but only images will be previewed based on their actual
+ * contents.
+ */
+struct _TalkatuAttachment {
+ GObject parent;
+
+ guint64 id;
+ gchar *content_type;
+
+ gchar *local_uri;
+ gchar *remote_uri;
+
+ guint64 size;
+
+ GdkPixbuf *preview;
+ gboolean preview_ready;
+};
+
+G_DEFINE_TYPE(TalkatuAttachment, talkatu_attachment, G_TYPE_OBJECT);
+
+enum {
+ PROP_0 = 0,
+ PROP_ID,
+ PROP_CONTENT_TYPE,
+ PROP_LOCAL_URI,
+ PROP_REMOTE_URI,
+ PROP_SIZE,
+ N_PROPERTIES,
+};
+static GParamSpec *properties[N_PROPERTIES];
+
+/******************************************************************************
+ * Private Setters
+ *****************************************************************************/
+static void
+talkatu_attachment_set_content_type(TalkatuAttachment *attachment,
+ const gchar *content_type)
+{
+ if(attachment->content_type == content_type) {
+ return;
+ }
+
+ g_clear_pointer(&attachment->content_type, g_free);
+
+ attachment->content_type = g_strdup(content_type);
+
+ g_object_notify_by_pspec(G_OBJECT(attachment),
+ properties[PROP_CONTENT_TYPE]);
+}
+
+/******************************************************************************
+ * GObject Implementation
+ *****************************************************************************/
+static void
+talkatu_attachment_get_property(GObject *obj, guint prop_id, GValue *value, GParamSpec *pspec) {
+ TalkatuAttachment *attachment = TALKATU_ATTACHMENT(obj);
+
+ switch(prop_id) {
+ case PROP_ID:
+ g_value_set_uint64(value, talkatu_attachment_get_id(attachment));
+ break;
+ case PROP_CONTENT_TYPE:
+ g_value_set_string(value, talkatu_attachment_get_content_type(attachment));
+ break;
+ case PROP_LOCAL_URI:
+ g_value_set_string(value, talkatu_attachment_get_local_uri(attachment));
+ break;
+ case PROP_REMOTE_URI:
+ g_value_set_string(value, talkatu_attachment_get_remote_uri(attachment));
+ break;
+ case PROP_SIZE:
+ g_value_set_uint64(value, talkatu_attachment_get_size(attachment));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+talkatu_attachment_set_property(GObject *obj, guint prop_id, const GValue *value, GParamSpec *pspec) {
+ TalkatuAttachment *attachment = TALKATU_ATTACHMENT(obj);
+
+ switch(prop_id) {
+ case PROP_ID:
+ talkatu_attachment_set_id(attachment, g_value_get_uint64(value));
+ break;
+ case PROP_CONTENT_TYPE:
+ talkatu_attachment_set_content_type(attachment, g_value_get_string(value));
+ break;
+ case PROP_LOCAL_URI:
+ talkatu_attachment_set_local_uri(attachment, g_value_get_string(value));
+ break;
+ case PROP_REMOTE_URI:
+ talkatu_attachment_set_remote_uri(attachment, g_value_get_string(value));
+ break;
+ case PROP_SIZE:
+ talkatu_attachment_set_size(attachment, g_value_get_uint64(value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+talkatu_attachment_finalize(GObject *obj) {
+ TalkatuAttachment *attachment = TALKATU_ATTACHMENT(obj);
+
+ g_clear_pointer(&attachment->content_type, g_free);
+ g_clear_pointer(&attachment->local_uri, g_free);
+ g_clear_pointer(&attachment->remote_uri, g_free);
+ g_clear_object(&attachment->preview);
+
+ G_OBJECT_CLASS(talkatu_attachment_parent_class)->finalize(obj);
+}
+
+static void
+talkatu_attachment_init(TalkatuAttachment *attachment) {
+}
+
+static void
+talkatu_attachment_class_init(TalkatuAttachmentClass *klass) {
+ GObjectClass *obj_class = G_OBJECT_CLASS(klass);
+
+ obj_class->get_property = talkatu_attachment_get_property;
+ obj_class->set_property = talkatu_attachment_set_property;
+ obj_class->finalize = talkatu_attachment_finalize;
+
+ /* add our properties */
+ properties[PROP_ID] = g_param_spec_uint64(
+ "id", "id", "The identifier of the attachment",
+ 0, G_MAXUINT64, 0,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT | 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",
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS
+ );
+
+ properties[PROP_LOCAL_URI] = g_param_spec_string(
+ "local-uri", "local-uri", "The local URI of the attachment",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS
+ );
+
+ properties[PROP_REMOTE_URI] = g_param_spec_string(
+ "remote-uri", "remote-uri", "The remote URI of the attachment",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS
+ );
+
+ properties[PROP_SIZE] = g_param_spec_uint64(
+ "size", "size", "The file size of the attachment in bytes",
+ 0, G_MAXUINT64, 0,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS
+ );
+
+ g_object_class_install_properties(obj_class, N_PROPERTIES, properties);
+}
+
+/******************************************************************************
+ * Public API
+ *****************************************************************************/
+
+/**
+ * talkatu_attachment_new:
+ * @id: The identifier of the attachment.
+ * @content_type: The content type of the attachment.
+ *
+ * Creates a new attachment with @content_type.
+ *
+ * Returns: (transfer full): The new #TalkatuAttachment.
+ */
+TalkatuAttachment *
+talkatu_attachment_new(guint64 id, const gchar *content_type) {
+ g_return_val_if_fail(content_type != NULL, NULL);
+
+ return TALKATU_ATTACHMENT(g_object_new(
+ TALKATU_TYPE_ATTACHMENT,
+ "id", id,
+ "content-type", content_type,
+ NULL
+ ));
+}
+
+/**
+ * talkatu_attachment_get_id:
+ * @attachment: The #TalkatuAttachment instance.
+ *
+ * Gets the ID associated with @attachment.
+ *
+ * Returns: The ID of @attachment.
+ */
+guint64
+talkatu_attachment_get_id(TalkatuAttachment *attachment) {
+ g_return_val_if_fail(TALKATU_IS_ATTACHMENT(attachment), 0);
+
+ return attachment->id;
+}
+
+/**
+ * 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.
+ */
+guint64 *
+talkatu_attachment_get_hash_key(TalkatuAttachment *attachment) {
+ g_return_val_if_fail(TALKATU_IS_ATTACHMENT(attachment), NULL);
+
+ return &attachment->id;
+}
+
+/**
+ * talkatu_attachment_set_id:
+ * @attachment: The #TalkatuAttachment instance.
+ * @id: The new ID for @attachment.
+ *
+ * Sets the ID of @attachment to @id.
+ */
+void
+talkatu_attachment_set_id(TalkatuAttachment *attachment, guint64 id) {
+ g_return_if_fail(TALKATU_IS_ATTACHMENT(attachment));
+
+ if(attachment->id == id) {
+ return;
+ }
+
+ attachment->id = id;
+
+ g_object_notify_by_pspec(G_OBJECT(attachment), properties[PROP_ID]);
+}
+
+/**
+ * talkatu_attachment_get_content_type:
+ * @attachment: The #TalkatuAttachment instance.
+ *
+ * Returns the content type of the attachment.
+ *
+ * Returns: The content type of @attachment.
+ */
+const gchar *
+talkatu_attachment_get_content_type(TalkatuAttachment *attachment) {
+ g_return_val_if_fail(TALKATU_IS_ATTACHMENT(attachment), NULL);
+
+ return attachment->content_type;
+}
+
+/**
+ * talkatu_attachment_get_local_uri:
+ * @attachment: The #TalkatuAttachment instance.
+ *
+ * Gets the local URI if any for @attachment.
+ *
+ * Returns: (nullable): The local URI for @attachment.
+ */
+const gchar *
+talkatu_attachment_get_local_uri(TalkatuAttachment *attachment) {
+ g_return_val_if_fail(TALKATU_IS_ATTACHMENT(attachment), NULL);
+
+ return attachment->local_uri;
+}
+
+/**
+ * talkatu_attachment_set_local_uri:
+ * @attachment: The #TalkatuAttachment instance.
+ * @local_uri: The new local URI.
+ *
+ * Sets the local URI of @attachment.
+ */
+void
+talkatu_attachment_set_local_uri(TalkatuAttachment *attachment,
+ const gchar *local_uri)
+{
+ g_return_if_fail(TALKATU_IS_ATTACHMENT(attachment));
+
+ if(attachment->local_uri == local_uri) {
+ return;
+ }
+
+ g_free(attachment->local_uri);
+
+ if(local_uri != NULL) {
+ gchar *scheme = g_uri_parse_scheme(local_uri);
+ if(scheme == NULL) {
+ attachment->local_uri = g_filename_to_uri(local_uri, NULL, NULL);
+ } else {
+ g_free(scheme);
+ attachment->local_uri = g_strdup(local_uri);
+ }
+ } else {
+ attachment->local_uri = NULL;
+ }
+
+ g_object_notify_by_pspec(G_OBJECT(attachment), properties[PROP_LOCAL_URI]);
+}
+
+/**
+ * talkatu_attachment_get_remote_uri:
+ * @attachment: The #TalkatuAttachment instance.
+ *
+ * Gets the remote URI if any for @attachment.
+ *
+ * Returns: (nullable): The remote URI for @attachment.
+ */
+const gchar *
+talkatu_attachment_get_remote_uri(TalkatuAttachment *attachment) {
+ g_return_val_if_fail(TALKATU_IS_ATTACHMENT(attachment), NULL);
+
+ return attachment->remote_uri;
+}
+
+/**
+ * talkatu_attachment_set_remote_uri:
+ * @attachment: The #TalkatuAttachment instance.
+ * @remote_uri: The new remote URI.
+ *
+ * Sets the remote URI of @attachment.
+ */
+void
+talkatu_attachment_set_remote_uri(TalkatuAttachment *attachment,
+ const gchar *remote_uri)
+{
+ g_return_if_fail(TALKATU_IS_ATTACHMENT(attachment));
+
+ if(attachment->remote_uri == remote_uri) {
+ return;
+ }
+
+ g_free(attachment->remote_uri);
+ attachment->remote_uri = g_strdup(remote_uri);
+
+ g_object_notify_by_pspec(G_OBJECT(attachment), properties[PROP_REMOTE_URI]);
+}
+
+/**
+ * talkatu_attachment_get_size:
+ * @attachment: The #TalkatuAttachment instance.
+ *
+ * Gets the size of @attachment.
+ *
+ * Returns: The size of @attachment.
+ */
+guint64
+talkatu_attachment_get_size(TalkatuAttachment *attachment) {
+ g_return_val_if_fail(TALKATU_IS_ATTACHMENT(attachment), 0);
+
+ return attachment->size;
+}
+
+/**
+ * talkatu_attachment_set_size:
+ * @attachment: The #TalkatuAttachment instance.
+ * @size: The new size of @attachment.
+ *
+ * Sets the size of @attachment to @size.
+ */
+void
+talkatu_attachment_set_size(TalkatuAttachment *attachment, guint64 size) {
+ g_return_if_fail(TALKATU_IS_ATTACHMENT(attachment));
+
+ attachment->size = size;
+
+ g_object_notify_by_pspec(G_OBJECT(attachment), properties[PROP_SIZE]);
+}
+
+/**
+ * talkatu_attachment_get_filename:
+ * @attachment: The #TalkatuAttachment instance.
+ *
+ * Gets the base filename for @attachment. Remote URI will be checked before
+ * local URI, but the basename of one of those is what will be returned.
+ *
+ * Returns: (transfer full): The filename for @attachment.
+ */
+gchar *
+talkatu_attachment_get_filename(TalkatuAttachment *attachment) {
+ g_return_val_if_fail(TALKATU_IS_ATTACHMENT(attachment), NULL);
+
+ if(attachment->remote_uri != NULL && attachment->remote_uri[0] != '\0') {
+ return g_path_get_basename(attachment->remote_uri);
+ }
+
+ if(attachment->local_uri != NULL && attachment->local_uri[0] != '\0') {
+ return g_path_get_basename(attachment->local_uri);
+ }
+
+ return g_strdup("unknown");
+}
+
+/**
+ * talkatu_attachment_get_preview:
+ * @attachment: The #TalkatuAttachment instance.
+ *
+ * Create a #GIcon as a preview for @attachment.
+ *
+ * Returns: (transfer full): A preview image of @attachment.
+ */
+GIcon *
+talkatu_attachment_get_preview(TalkatuAttachment *attachment) {
+ const gchar *name = "text-x-generic-template";
+
+ g_return_val_if_fail(TALKATU_IS_ATTACHMENT(attachment), NULL);
+
+ if(g_str_has_prefix(attachment->content_type, "image/")) {
+ if(attachment->local_uri != NULL) {
+ GFile *file = g_file_new_for_uri(attachment->local_uri);
+ GIcon *icon = g_file_icon_new(file);
+
+ g_object_unref(G_OBJECT(file));
+
+ return icon;
+ }
+
+ name = "image-x-generic";
+ } else if(g_str_has_prefix(attachment->content_type, "text/")) {
+ name = "text-x-generic";
+ } else if(g_str_has_prefix(attachment->content_type, "audio/")) {
+ name = "audio-x-generic";
+ }
+
+ return g_themed_icon_new(name);
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/talkatu/talkatuattachment.h Tue Jun 30 05:14:12 2020 +0000
@@ -0,0 +1,59 @@
+/*
+ * 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_ATTACHMENT_H
+#define TALKATU_ATTACHMENT_H
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define TALKATU_TYPE_ATTACHMENT (talkatu_attachment_get_type())
+G_DECLARE_FINAL_TYPE(TalkatuAttachment, talkatu_attachment, TALKATU, ATTACHMENT, GObject)
+
+typedef void (*TalkatuAttachmentForeachFunc)(TalkatuAttachment *attachment, gpointer data);
+
+TalkatuAttachment *talkatu_attachment_new(guint64 id, const gchar *content_type);
+
+guint64 talkatu_attachment_get_id(TalkatuAttachment *attachment);
+guint64 *talkatu_attachment_get_hash_key(TalkatuAttachment *attachment);
+void talkatu_attachment_set_id(TalkatuAttachment *attachment, guint64 id);
+
+const gchar *talkatu_attachment_get_content_type(TalkatuAttachment *attachment);
+
+const gchar *talkatu_attachment_get_local_uri(TalkatuAttachment *attachment);
+void talkatu_attachment_set_local_uri(TalkatuAttachment *attachment, const gchar *local_uri);
+
+const gchar *talkatu_attachment_get_remote_uri(TalkatuAttachment *attachment);
+void talkatu_attachment_set_remote_uri(TalkatuAttachment *attachment, const gchar *remote_uri);
+
+gsize talkatu_attachment_get_size(TalkatuAttachment *attachment);
+void talkatu_attachment_set_size(TalkatuAttachment *attachment, gsize size);
+
+gchar *talkatu_attachment_get_filename(TalkatuAttachment *attachment);
+
+GIcon *talkatu_attachment_get_preview(TalkatuAttachment *attachment);
+
+G_END_DECLS
+
+#endif /* TALKATU_ATTACHMENT_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/talkatu/talkatuattachmentdialog.c Tue Jun 30 05:14:12 2020 +0000
@@ -0,0 +1,248 @@
+/*
+ * 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 "talkatu/talkatuattachmentdialog.h"
+
+/**
+ * SECTION:talkatuattachmentdialog
+ * @Title: Attachment Dialog
+ * @Short_description: A GtkDialog for sending message attachments.
+ *
+ * A #GtkDialog that allows the user to customize attachments for a message.
+ */
+
+/**
+ * TALKATU_TYPE_ATTACHMENT_DIALOG:
+ *
+ * The standard _get_type macro for #TalkatuAttachmentDialog.
+ */
+
+/**
+ * TalkatuAttachmentDialog:
+ *
+ * A #GtkDialog that allows the user to customize an image message.
+ */
+struct _TalkatuAttachmentDialog {
+ GtkDialog parent;
+
+ TalkatuAttachment *attachment;
+
+ GtkWidget *preview;
+ GtkWidget *filename;
+ GtkWidget *comment;
+};
+
+enum {
+ PROP_0 = 0,
+ PROP_ATTACHMENT,
+ PROP_COMMENT,
+ N_PROPERTIES
+};
+
+static GParamSpec *properties[N_PROPERTIES] = {NULL, };
+
+/******************************************************************************
+ * Setters
+ *****************************************************************************/
+static void
+talkatu_attachment_dialog_set_attachment(TalkatuAttachmentDialog *dialog,
+ TalkatuAttachment *attachment)
+{
+ if(g_set_object(&dialog->attachment, attachment)) {
+ if(TALKATU_IS_ATTACHMENT(attachment)) {
+ GIcon *preview = talkatu_attachment_get_preview(attachment);
+ gchar *filename = NULL;
+
+ if(G_IS_ICON(preview)) {
+ gtk_image_set_from_gicon(GTK_IMAGE(dialog->preview), preview,
+ GTK_ICON_SIZE_DIALOG);
+ g_object_unref(G_OBJECT(preview));
+ }
+
+ filename = talkatu_attachment_get_filename(attachment);
+ gtk_label_set_text(GTK_LABEL(dialog->filename), filename);
+ g_free(filename);
+ }
+
+ g_object_notify_by_pspec(G_OBJECT(dialog),
+ properties[PROP_ATTACHMENT]);
+ }
+}
+
+static void
+talkatu_attachment_dialog_set_comment(TalkatuAttachmentDialog *dialog,
+ const gchar *comment)
+{
+ if(GTK_IS_ENTRY(dialog->comment)) {
+ gtk_entry_set_text(GTK_ENTRY(dialog->comment), comment);
+
+ g_object_notify_by_pspec(G_OBJECT(dialog), properties[PROP_COMMENT]);
+ }
+}
+
+/******************************************************************************
+ * GObject Implementation
+ *****************************************************************************/
+G_DEFINE_TYPE(TalkatuAttachmentDialog, talkatu_attachment_dialog, GTK_TYPE_DIALOG)
+
+static void
+talkatu_attachment_dialog_get_property(GObject *obj, guint prop_id,
+ GValue *value, GParamSpec *pspec)
+{
+ TalkatuAttachmentDialog *dialog = TALKATU_ATTACHMENT_DIALOG(obj);
+
+ switch(prop_id) {
+ case PROP_ATTACHMENT:
+ g_value_take_object(value, talkatu_attachment_dialog_get_attachment(dialog));
+ break;
+ case PROP_COMMENT:
+ g_value_set_string(value, talkatu_attachment_dialog_get_comment(dialog));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+talkatu_attachment_dialog_set_property(GObject *obj, guint prop_id,
+ const GValue *value, GParamSpec *pspec)
+{
+ TalkatuAttachmentDialog *dialog = TALKATU_ATTACHMENT_DIALOG(obj);
+
+ switch(prop_id) {
+ case PROP_ATTACHMENT:
+ talkatu_attachment_dialog_set_attachment(dialog, g_value_get_object(value));
+ break;
+ case PROP_COMMENT:
+ talkatu_attachment_dialog_set_comment(dialog,
+ g_value_get_string(value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+talkatu_attachment_dialog_finalize(GObject *obj) {
+ TalkatuAttachmentDialog *dialog = TALKATU_ATTACHMENT_DIALOG(obj);
+
+ g_clear_object(&dialog->attachment);
+
+ G_OBJECT_CLASS(talkatu_attachment_dialog_parent_class)->finalize(obj);
+}
+
+static void
+talkatu_attachment_dialog_init(TalkatuAttachmentDialog *dialog) {
+ gtk_widget_init_template(GTK_WIDGET(dialog));
+}
+
+static void
+talkatu_attachment_dialog_class_init(TalkatuAttachmentDialogClass *klass) {
+ GObjectClass *obj_class = G_OBJECT_CLASS(klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
+
+ obj_class->get_property = talkatu_attachment_dialog_get_property;
+ obj_class->set_property = talkatu_attachment_dialog_set_property;
+ obj_class->finalize = talkatu_attachment_dialog_finalize;
+
+ properties[PROP_ATTACHMENT] = g_param_spec_object(
+ "attachment", "attachment",
+ "The attachment that this dialog is customizing",
+ TALKATU_TYPE_ATTACHMENT,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+ properties[PROP_COMMENT] = g_param_spec_string(
+ "comment", "comment",
+ "The comment to add to the attachment",
+ "",
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties(obj_class, N_PROPERTIES, properties);
+
+ gtk_widget_class_set_template_from_resource(
+ widget_class,
+ "/org/bitbucket/pidgin/talkatu/ui/attachmentdialog.ui"
+ );
+
+ gtk_widget_class_bind_template_child(widget_class, TalkatuAttachmentDialog, preview);
+ gtk_widget_class_bind_template_child(widget_class, TalkatuAttachmentDialog, filename);
+ gtk_widget_class_bind_template_child(widget_class, TalkatuAttachmentDialog, comment);
+}
+
+/******************************************************************************
+ * Public API
+ *****************************************************************************/
+
+/**
+ * talkatu_attachment_dialog_new:
+ * @attachment: The #TalkatuAttachment we're customizing.
+ * @comment: A default comment to display.
+ *
+ * Creates a new #TalkatuAttachmentDialog with the given attachment and comment.
+ *
+ * Returns: (transfer full): The new #TalkatuAttachmentDialog.
+ */
+GtkWidget *
+talkatu_attachment_dialog_new(TalkatuAttachment *attachment,
+ const gchar *comment)
+{
+ return GTK_WIDGET(g_object_new(
+ TALKATU_TYPE_ATTACHMENT_DIALOG,
+ "attachment", attachment,
+ "comment", comment,
+ NULL
+ ));
+}
+
+/**
+ * talkatu_attachment_dialog_get_attachment:
+ * @dialog: The #TalkatuAttachmentDialog.
+ *
+ * Gets the #TalkatuAttachment from @dialog.
+ *
+ * Returns: (transfer full): The #TalkatuAttachment for the file that the user
+ * selected.
+ */
+TalkatuAttachment *
+talkatu_attachment_dialog_get_attachment(TalkatuAttachmentDialog *dialog) {
+ g_return_val_if_fail(TALKATU_IS_ATTACHMENT_DIALOG(dialog), NULL);
+
+ return g_object_ref(dialog->attachment);
+}
+
+/**
+ * talkatu_attachment_dialog_get_comment:
+ * @dialog: The #TalkatuAttachmentDialog.
+ *
+ * Get the comment the user entered, or empty string if nothing was entered.
+ *
+ * Returns: The comment that the user entered.
+ */
+const gchar *
+talkatu_attachment_dialog_get_comment(TalkatuAttachmentDialog *dialog) {
+ g_return_val_if_fail(TALKATU_IS_ATTACHMENT_DIALOG(dialog), "");
+
+ if(GTK_IS_ENTRY(dialog->comment)) {
+ return gtk_entry_get_text(GTK_ENTRY(dialog->comment));
+ }
+
+ return "";
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/talkatu/talkatuattachmentdialog.h Tue Jun 30 05:14:12 2020 +0000
@@ -0,0 +1,45 @@
+/*
+ * 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_ATTACHMENT_DIALOG_H
+#define TALKATU_ATTACHMENT_DIALOG_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include <gtk/gtk.h>
+
+#include <talkatu/talkatuattachment.h>
+
+G_BEGIN_DECLS
+
+#define TALKATU_TYPE_ATTACHMENT_DIALOG (talkatu_attachment_dialog_get_type())
+G_DECLARE_FINAL_TYPE(TalkatuAttachmentDialog, talkatu_attachment_dialog, TALKATU, ATTACHMENT_DIALOG, GtkDialog)
+
+GtkWidget *talkatu_attachment_dialog_new(TalkatuAttachment *attachment, const gchar *comment);
+
+TalkatuAttachment *talkatu_attachment_dialog_get_attachment(TalkatuAttachmentDialog *dialog);
+const gchar *talkatu_attachment_dialog_get_comment(TalkatuAttachmentDialog *dialog);
+
+G_END_DECLS
+
+#endif /* TALKATU_ATTACHMENT_DIALOG_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/talkatu/talkatuattachmentpreview.c Tue Jun 30 05:14:12 2020 +0000
@@ -0,0 +1,217 @@
+/*
+ * 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/talkatuattachmentpreview.h"
+
+/**
+ * SECTION:talkatuattachmentpreview
+ * @Title: Attachment Preview
+ * @Short_description: Previews for TalkatuAttachments.
+ *
+ * A simple widget that displays a preview of a #TalkatuAttachment.
+ */
+
+/**
+ * TALKATU_TYPE_ATTACHMENT_PREVIEW:
+ *
+ * The standard _get_type macro for #TalkatuAttachmentPreview.
+ */
+
+/**
+ * TalkatuAttachmentPreview:
+ *
+ * A #GtkWidget that displays a preview of a #TalkatuAttachment and allows the
+ * user to save it.
+ */
+struct _TalkatuAttachmentPreview {
+ GtkInfoBar parent;
+
+ TalkatuAttachment *attachment;
+
+ GtkWidget *preview;
+ GtkWidget *filename;
+ GtkWidget *filesize;
+};
+
+enum {
+ PROP_0 = 0,
+ PROP_ATTACHMENT,
+ N_PROPERTIES
+};
+
+static GParamSpec *properties[N_PROPERTIES] = {NULL, };
+
+/******************************************************************************
+ * Callbacks
+ *****************************************************************************/
+static gboolean
+talkatu_attachment_preview_download_cb(GtkButton *button, gpointer data) {
+ return FALSE;
+}
+
+/******************************************************************************
+ * Setters
+ *****************************************************************************/
+static void
+talkatu_attachment_preview_set_attachment(TalkatuAttachmentPreview *preview,
+ TalkatuAttachment *attachment)
+{
+ if(g_set_object(&preview->attachment, attachment)) {
+ if(TALKATU_IS_ATTACHMENT(attachment)) {
+ GIcon *icon = talkatu_attachment_get_preview(attachment);
+ gchar *filename = NULL;
+ gchar *filesize = NULL;
+
+ if(G_IS_ICON(icon)) {
+ gtk_image_set_from_gicon(GTK_IMAGE(preview->preview), icon,
+ GTK_ICON_SIZE_DIALOG);
+ g_object_unref(G_OBJECT(icon));
+ }
+
+ filename = talkatu_attachment_get_filename(preview->attachment);
+ gtk_label_set_text(GTK_LABEL(preview->filename), filename);
+ g_free(filename);
+
+ filesize = g_format_size(talkatu_attachment_get_size(preview->attachment));
+ gtk_label_set_text(GTK_LABEL(preview->filesize), filesize);
+ g_free(filesize);
+ }
+
+ g_object_notify_by_pspec(G_OBJECT(preview),
+ properties[PROP_ATTACHMENT]);
+ }
+}
+
+/******************************************************************************
+ * GObject Implementation
+ *****************************************************************************/
+G_DEFINE_TYPE(TalkatuAttachmentPreview, talkatu_attachment_preview, GTK_TYPE_INFO_BAR)
+
+static void
+talkatu_attachment_preview_get_property(GObject *obj, guint prop_id,
+ GValue *value, GParamSpec *pspec)
+{
+ TalkatuAttachmentPreview *preview = TALKATU_ATTACHMENT_PREVIEW(obj);
+
+ switch(prop_id) {
+ case PROP_ATTACHMENT:
+ g_value_take_object(value, talkatu_attachment_preview_get_attachment(preview));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+talkatu_attachment_preview_set_property(GObject *obj, guint prop_id,
+ const GValue *value, GParamSpec *pspec)
+{
+ TalkatuAttachmentPreview *preview = TALKATU_ATTACHMENT_PREVIEW(obj);
+
+ switch(prop_id) {
+ case PROP_ATTACHMENT:
+ talkatu_attachment_preview_set_attachment(preview, g_value_get_object(value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+talkatu_attachment_preview_finalize(GObject *obj) {
+ TalkatuAttachmentPreview *preview = TALKATU_ATTACHMENT_PREVIEW(obj);
+
+ g_clear_object(&preview->attachment);
+
+ G_OBJECT_CLASS(talkatu_attachment_preview_parent_class)->finalize(obj);
+}
+
+static void
+talkatu_attachment_preview_init(TalkatuAttachmentPreview *preview) {
+ gtk_widget_init_template(GTK_WIDGET(preview));
+}
+
+static void
+talkatu_attachment_preview_class_init(TalkatuAttachmentPreviewClass *klass) {
+ GObjectClass *obj_class = G_OBJECT_CLASS(klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
+
+ obj_class->get_property = talkatu_attachment_preview_get_property;
+ obj_class->set_property = talkatu_attachment_preview_set_property;
+ obj_class->finalize = talkatu_attachment_preview_finalize;
+
+ properties[PROP_ATTACHMENT] = g_param_spec_object(
+ "attachment", "attachment",
+ "The attachment that this preview is customizing",
+ TALKATU_TYPE_ATTACHMENT,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties(obj_class, N_PROPERTIES, properties);
+
+ gtk_widget_class_set_template_from_resource(
+ widget_class,
+ "/org/bitbucket/pidgin/talkatu/ui/attachmentpreview.ui"
+ );
+
+ gtk_widget_class_bind_template_child(widget_class, TalkatuAttachmentPreview, preview);
+ gtk_widget_class_bind_template_child(widget_class, TalkatuAttachmentPreview, filename);
+ gtk_widget_class_bind_template_child(widget_class, TalkatuAttachmentPreview, filesize);
+
+ gtk_widget_class_bind_template_callback(widget_class, talkatu_attachment_preview_download_cb);
+}
+
+/******************************************************************************
+ * Public API
+ *****************************************************************************/
+
+/**
+ * talkatu_attachment_preview_new:
+ * @attachment: The #TalkatuAttachment we're previewing.
+ *
+ * Creates a new #TalkatuAttachmentPreview for the given attachment.
+ *
+ * Returns: (transfer full): The new #TalkatuAttachmentPreview.
+ */
+GtkWidget *
+talkatu_attachment_preview_new(TalkatuAttachment *attachment) {
+ g_return_val_if_fail(TALKATU_IS_ATTACHMENT(attachment), NULL);
+
+ return GTK_WIDGET(g_object_new(
+ TALKATU_TYPE_ATTACHMENT_PREVIEW,
+ "attachment", attachment,
+ NULL
+ ));
+}
+
+/**
+ * talkatu_attachment_preview_get_attachment:
+ * @preview: The #TalkatuAttachmentPreview.
+ *
+ * Gets the #TalkatuAttachment from @preview.
+ *
+ * Returns: (transfer full): The #TalkatuAttachment from @preview.
+ */
+TalkatuAttachment *
+talkatu_attachment_preview_get_attachment(TalkatuAttachmentPreview *preview) {
+ g_return_val_if_fail(TALKATU_IS_ATTACHMENT_PREVIEW(preview), NULL);
+
+ return g_object_ref(preview->attachment);
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/talkatu/talkatuattachmentpreview.h Tue Jun 30 05:14:12 2020 +0000
@@ -0,0 +1,44 @@
+/*
+ * 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_ATTACHMENT_PREVIEW_H
+#define TALKATU_ATTACHMENT_PREVIEW_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include <gtk/gtk.h>
+
+#include <talkatu/talkatuattachment.h>
+
+G_BEGIN_DECLS
+
+#define TALKATU_TYPE_ATTACHMENT_PREVIEW (talkatu_attachment_preview_get_type())
+G_DECLARE_FINAL_TYPE(TalkatuAttachmentPreview, talkatu_attachment_preview, TALKATU, ATTACHMENT_PREVIEW, GtkInfoBar)
+
+GtkWidget *talkatu_attachment_preview_new(TalkatuAttachment *attachment);
+
+TalkatuAttachment *talkatu_attachment_preview_get_attachment(TalkatuAttachmentPreview *preview);
+
+G_END_DECLS
+
+#endif /* TALKATU_ATTACHMENT_PREVIEW_H */
--- a/talkatu/talkatubuffer.c Mon Jun 08 05:47:27 2020 +0000
+++ b/talkatu/talkatubuffer.c Tue Jun 30 05:14:12 2020 +0000
@@ -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/talkatueditor.c Mon Jun 08 05:47:27 2020 +0000
+++ b/talkatu/talkatueditor.c Tue Jun 30 05:14:12 2020 +0000
@@ -17,10 +17,11 @@
*/
#include <gtk/gtk.h>
+#include <talkatu/talkatueditor.h>
+
#include <talkatu/talkatubuffer.h>
-#include <talkatu/talkatueditor.h>
+#include <talkatu/talkatuinput.h>
#include <talkatu/talkatutoolbar.h>
-#include <talkatu/talkatuview.h>
/**
* SECTION:talkatueditor
@@ -40,13 +41,13 @@
/**
* TalkatuEditor:
*
- * A composite widget that contains a #TalkatuToolbar, #TalkatuView, and an
+ * A composite widget that contains a #TalkatuToolbar, #TalkatuInput, and an
* optional send button in the common instant messaging input layout.
*/
struct _TalkatuEditor {
GtkBox parent;
- GtkWidget *view;
+ GtkWidget *input;
GtkWidget *toolbar;
GtkWidget *send_button;
};
@@ -55,7 +56,7 @@
PROP_0,
PROP_TOOLBAR,
PROP_SHOW_TOOLBAR,
- PROP_VIEW,
+ PROP_INPUT,
PROP_BUFFER,
PROP_SHOW_SEND_BUTTON,
N_PROPERTIES
@@ -71,7 +72,7 @@
static void
talkatu_editor_buffer_changed(TalkatuEditor *editor) {
GActionGroup *action_group = NULL;
- GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(editor->view));
+ GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(editor->input));
if(TALKATU_IS_BUFFER(buffer)) {
action_group = G_ACTION_GROUP(talkatu_buffer_get_action_group(TALKATU_BUFFER(buffer)));
@@ -92,9 +93,9 @@
talkatu_editor_send_button_clicked_cb(GtkButton *button, gpointer data) {
TalkatuEditor *editor = TALKATU_EDITOR(data);
- talkatu_view_send_message(TALKATU_VIEW(editor->view));
+ talkatu_input_send_message(TALKATU_INPUT(editor->input));
- gtk_widget_grab_focus(editor->view);
+ gtk_widget_grab_focus(editor->input);
}
/******************************************************************************
@@ -114,8 +115,8 @@
case PROP_SHOW_TOOLBAR:
g_value_set_boolean(value, talkatu_editor_get_toolbar_visible(editor));
break;
- case PROP_VIEW:
- g_value_set_object(value, talkatu_editor_get_view(editor));
+ case PROP_INPUT:
+ g_value_set_object(value, talkatu_editor_get_input(editor));
break;
case PROP_SHOW_SEND_BUTTON:
g_value_set_boolean(value, talkatu_editor_get_send_button_visible(editor));
@@ -187,9 +188,9 @@
G_PARAM_READWRITE | G_PARAM_CONSTRUCT
);
- properties[PROP_VIEW] = g_param_spec_object(
- "view", "view",
- "The view widget",
+ properties[PROP_INPUT] = g_param_spec_object(
+ "input", "input",
+ "The input widget",
TALKATU_TYPE_VIEW,
G_PARAM_READABLE
);
@@ -216,7 +217,7 @@
"/org/bitbucket/pidgin/talkatu/ui/editor.ui"
);
- gtk_widget_class_bind_template_child_internal(widget_class, TalkatuEditor, view);
+ gtk_widget_class_bind_template_child_internal(widget_class, TalkatuEditor, input);
gtk_widget_class_bind_template_child_internal(widget_class, TalkatuEditor, toolbar);
gtk_widget_class_bind_template_child_internal(widget_class, TalkatuEditor, send_button);
@@ -286,33 +287,33 @@
}
/**
- * talkatu_editor_get_view:
+ * talkatu_editor_get_input:
* @editor: The #TalkatuEditor instance.
*
- * Gets the #TalkatuView that @editor is using.
+ * Gets the #TalkatuInput that @editor is using.
*
- * Returns: (transfer none): The #TalkatuView that @editor is using.
+ * Returns: (transfer none): The #TalkatuInput that @editor is using.
*/
GtkWidget *
-talkatu_editor_get_view(TalkatuEditor *editor) {
+talkatu_editor_get_input(TalkatuEditor *editor) {
g_return_val_if_fail(TALKATU_IS_EDITOR(editor), NULL);
- return editor->view;
+ return editor->input;
}
/**
* talkatu_editor_get_buffer:
* @editor: The #TalkatuEditor instance.
*
- * Gets the #GtkTextBuffer for the internal view.
+ * Gets the #GtkTextBuffer for the internal input.
*
- * Returns: (transfer none): The #GtkTextBuffer for the internal view.
+ * Returns: (transfer none): The #GtkTextBuffer for the internal input.
*/
GtkTextBuffer *
talkatu_editor_get_buffer(TalkatuEditor *editor) {
g_return_val_if_fail(TALKATU_IS_EDITOR(editor), NULL);
- return gtk_text_view_get_buffer(GTK_TEXT_VIEW(editor->view));
+ return gtk_text_view_get_buffer(GTK_TEXT_VIEW(editor->input));
}
/**
@@ -320,13 +321,13 @@
* @editor: The #TalkatuEditor instance.
* @buffer: A new #GtkTextBuffer to use.
*
- * Sets the #GtkTextBuffer for the internal view to @buffer.
+ * Sets the #GtkTextBuffer for the internal input to @buffer.
*/
void
talkatu_editor_set_buffer(TalkatuEditor *editor, GtkTextBuffer *buffer) {
g_return_if_fail(TALKATU_IS_EDITOR(editor));
- gtk_text_view_set_buffer(GTK_TEXT_VIEW(editor->view), buffer);
+ gtk_text_view_set_buffer(GTK_TEXT_VIEW(editor->input), buffer);
}
/**
--- a/talkatu/talkatueditor.h Mon Jun 08 05:47:27 2020 +0000
+++ b/talkatu/talkatueditor.h Tue Jun 30 05:14:12 2020 +0000
@@ -39,7 +39,7 @@
void talkatu_editor_set_toolbar_visible(TalkatuEditor *editor, gboolean visible);
gboolean talkatu_editor_get_toolbar_visible(TalkatuEditor *editor);
-GtkWidget *talkatu_editor_get_view(TalkatuEditor *editor);
+GtkWidget *talkatu_editor_get_input(TalkatuEditor *editor);
GtkTextBuffer *talkatu_editor_get_buffer(TalkatuEditor *editor);
void talkatu_editor_set_buffer(TalkatuEditor *editor, GtkTextBuffer *buffer);
--- a/talkatu/talkatuhistory.c Mon Jun 08 05:47:27 2020 +0000
+++ b/talkatu/talkatuhistory.c Tue Jun 30 05:14:12 2020 +0000
@@ -1,6 +1,6 @@
/*
* talkatu
- * 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
@@ -18,6 +18,8 @@
#include <gtk/gtk.h>
#include <talkatu/talkatuhistory.h>
+
+#include <talkatu/talkatuattachmentpreview.h>
#include <talkatu/talkatuhistorybuffer.h>
#include <talkatu/talkatumessage.h>
@@ -103,6 +105,34 @@
}
}
+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
*****************************************************************************/
@@ -119,6 +149,9 @@
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",
--- a/talkatu/talkatuhistorybuffer.c Mon Jun 08 05:47:27 2020 +0000
+++ b/talkatu/talkatuhistorybuffer.c Tue Jun 30 05:14:12 2020 +0000
@@ -53,15 +53,45 @@
* 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;
@@ -115,6 +145,13 @@
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);
}
/******************************************************************************
--- a/talkatu/talkatuhtmlbuffer.c Mon Jun 08 05:47:27 2020 +0000
+++ b/talkatu/talkatuhtmlbuffer.c Tue Jun 30 05:14:12 2020 +0000
@@ -69,6 +69,7 @@
talkatu_action_group_enable_action(TALKATU_ACTION_GROUP(ag), TALKATU_ACTION_FORMAT_SHRINK);
talkatu_action_group_enable_action(TALKATU_ACTION_GROUP(ag), TALKATU_ACTION_FORMAT_GROW);
talkatu_action_group_enable_action(TALKATU_ACTION_GROUP(ag), TALKATU_ACTION_FORMAT_RESET);
+ talkatu_action_group_enable_action(TALKATU_ACTION_GROUP(ag), TALKATU_ACTION_ATTACH_FILE);
talkatu_action_group_enable_action(TALKATU_ACTION_GROUP(ag), TALKATU_ACTION_INSERT_LINK);
return ag;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/talkatu/talkatuinput.c Tue Jun 30 05:14:12 2020 +0000
@@ -0,0 +1,838 @@
+/*
+ * 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.h>
+#include <glib/gi18n-lib.h>
+#include <glib/gstdio.h>
+
+#include <gspell/gspell.h>
+
+#include "talkatu/talkatuinput.h"
+
+#include "talkatu/talkatuactiongroup.h"
+#include "talkatu/talkatuattachment.h"
+#include "talkatu/talkatuattachmentdialog.h"
+#include "talkatu/talkatuenums.h"
+#include "talkatu/talkatumarkup.h"
+#include "talkatu/talkatumessage.h"
+
+/**
+ * SECTION:talkatuinput
+ * @Title: Input Widget
+ * @Short_description: The main Talkatu input widget.
+ *
+ * #TalkatuInput handles all of the details for sending messages.
+ */
+
+/**
+ * TALKATU_TYPE_INPUT:
+ *
+ * The standard _get_type macro for #TalkatuInput.
+ */
+
+/**
+ * TalkatuInputClass:
+ * @should_send_message: The class handler for the
+ * #TalkatuInput::should_send_message signal.
+ * @send_message: The class handler for the #TalkatuInput::send_message signal.
+ *
+ * The backing class to #TalkatuInput instances.
+ */
+
+/**
+ * TalkatuInputSendBinding:
+ * @TALKATU_INPUT_SEND_BINDING_RETURN: Represents return.
+ * @TALKATU_INPUT_SEND_BINDING_KP_ENTER: Represents enter.
+ * @TALKATU_INPUT_SEND_BINDING_SHIFT_RETURN: Represents shift-return.
+ * @TALKATU_INPUT_SEND_BINDING_CONTROL_RETURN: Represents control-return.
+ *
+ * Flags for assigning and determining which key bindings should be used to
+ * send a message.
+ */
+
+typedef struct {
+ TalkatuView parent;
+
+ GHashTable *attachments;
+ guint64 attachment_id;
+
+ GspellTextView *gspell_view;
+
+ TalkatuInputSendBinding send_binding;
+
+ /* this mark is used to keep track of our context for the context menu. It
+ * is updated via cursor-moved and button-press callbacks.
+ */
+ GtkTextMark *context_mark;
+
+ /* TalkatuMessage properties: content type and contents are derived from
+ * the widget itself.
+ */
+ guint64 id;
+ GDateTime *timestamp;
+ TalkatuContentType content_type;
+ gchar *author;
+ gboolean edited;
+} TalkatuInputPrivate;
+
+typedef struct {
+ TalkatuAttachmentForeachFunc func;
+ gpointer data;
+} TalkatuInputForeachAttachmentData;
+
+enum {
+ PROP_0 = 0,
+ PROP_SEND_BINDING,
+ N_PROPERTIES,
+ /* overrides */
+ PROP_ID = N_PROPERTIES,
+ PROP_TIMESTAMP,
+ PROP_CONTENT_TYPE,
+ PROP_AUTHOR,
+ PROP_CONTENTS,
+ PROP_EDITED,
+};
+static GParamSpec *properties[N_PROPERTIES];
+
+enum {
+ SIG_SHOULD_SEND_MESSAGE,
+ SIG_SEND_MESSAGE,
+ LAST_SIGNAL,
+};
+static guint signals[LAST_SIGNAL] = {0, };
+
+static void talkatu_input_message_init(TalkatuMessageInterface *iface);
+
+G_DEFINE_TYPE_EXTENDED(
+ TalkatuInput, talkatu_input, TALKATU_TYPE_VIEW,
+ 0,
+ G_ADD_PRIVATE(TalkatuInput)
+ G_IMPLEMENT_INTERFACE(TALKATU_TYPE_MESSAGE, talkatu_input_message_init)
+);
+
+/******************************************************************************
+ * TalkatuMessage Interface
+ *****************************************************************************/
+static guint64
+talkatu_input_get_id(TalkatuInput *input) {
+ TalkatuInputPrivate *priv = talkatu_input_get_instance_private(input);
+
+ return priv->id;
+}
+
+static void
+talkatu_input_set_id(TalkatuInput *input, guint64 id) {
+ TalkatuInputPrivate *priv = talkatu_input_get_instance_private(input);
+
+ priv->id = id;
+
+ g_object_notify(G_OBJECT(input), "id");
+}
+
+static GDateTime *
+talkatu_input_get_timestamp(TalkatuInput *input) {
+ TalkatuInputPrivate *priv = talkatu_input_get_instance_private(input);
+
+
+ return priv->timestamp;
+}
+
+static void
+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;
+}
+
+static void
+talkatu_input_set_content_type(TalkatuInput *input,
+ TalkatuContentType content_type)
+{
+ /* TODO: set the buffer here? */
+
+ g_object_notify(G_OBJECT(input), "content-type");
+}
+
+static gchar *
+talkatu_input_get_author(TalkatuInput *input) {
+ TalkatuInputPrivate *priv = talkatu_input_get_instance_private(input);
+
+ return priv->author;
+}
+
+static void
+talkatu_input_set_author(TalkatuInput *input, const gchar *author) {
+ TalkatuInputPrivate *priv = talkatu_input_get_instance_private(input);
+
+ if(author == priv->author) {
+ return;
+ }
+
+ g_clear_pointer(&priv->author, g_free);
+
+ priv->author = g_strdup(author);
+
+ g_object_notify(G_OBJECT(input), "author");
+}
+
+static gchar *
+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);
+}
+
+static void
+talkatu_input_set_contents(TalkatuInput *input, const gchar *contents) {
+ GtkTextBuffer *buffer = NULL;
+
+ buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(input));
+
+ if(contents == NULL) {
+ talkatu_buffer_clear(TALKATU_BUFFER(buffer));
+ } else {
+ talkatu_markup_set_html(TALKATU_BUFFER(buffer), contents, -1);
+ }
+
+ g_object_notify(G_OBJECT(input), "contents");
+}
+
+static gboolean
+talkatu_input_get_edited(TalkatuInput *input) {
+ TalkatuInputPrivate *priv = talkatu_input_get_instance_private(input);
+
+ return priv->edited;
+}
+
+static void
+talkatu_input_set_edited(TalkatuInput *input, gboolean edited) {
+ TalkatuInputPrivate *priv = talkatu_input_get_instance_private(input);
+
+ priv->edited = edited;
+
+ g_object_notify(G_OBJECT(input), "edited");
+}
+
+static gboolean
+talkatu_input_add_attachment(TalkatuMessage *message,
+ TalkatuAttachment *attachment)
+{
+ TalkatuInput *input = TALKATU_INPUT(message);
+ TalkatuInputPrivate *priv = talkatu_input_get_instance_private(input);
+
+ talkatu_attachment_set_id(attachment, priv->attachment_id++);
+
+ return g_hash_table_insert(
+ priv->attachments,
+ talkatu_attachment_get_hash_key(attachment),
+ g_object_ref(G_OBJECT(attachment))
+ );
+}
+
+static gboolean
+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)));
+ }
+
+ return NULL;
+}
+
+static void
+talkatu_input_foreach_attachment_helper(gpointer key, gpointer value,
+ gpointer data)
+{
+ TalkatuInputForeachAttachmentData *d = (TalkatuInputForeachAttachmentData *)data;
+
+ d->func(TALKATU_ATTACHMENT(value), d->data);
+}
+
+static void
+talkatu_input_foreach_attachment(TalkatuMessage *message,
+ TalkatuAttachmentForeachFunc func,
+ gpointer data)
+{
+ TalkatuInput *input = TALKATU_INPUT(message);
+ TalkatuInputPrivate *priv = talkatu_input_get_instance_private(input);
+
+ TalkatuInputForeachAttachmentData d = {
+ .func = func,
+ .data = data
+ };
+
+ g_hash_table_foreach(priv->attachments,
+ talkatu_input_foreach_attachment_helper,
+ &d);
+}
+
+static void
+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);
+ priv->attachment_id = 0;
+}
+
+static void
+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;
+}
+
+/******************************************************************************
+ * Callbacks
+ *****************************************************************************/
+static void
+talkatu_input_send_message_cb(GtkMenuItem *item, gpointer data) {
+ talkatu_input_send_message(TALKATU_INPUT(data));
+
+ gtk_widget_grab_focus(GTK_WIDGET(data));
+}
+
+
+static void
+talkatu_input_populate_popup_cb(GtkTextView *view, GtkWidget *popup) {
+ TalkatuInputPrivate *priv = NULL;
+ GtkTextBuffer *buffer = NULL;
+ GtkTextIter iter;
+ GtkWidget *item = NULL;
+ gint pos = 0;
+
+ /* if the popup isn't a menu, bail */
+ if(!GTK_IS_MENU(popup)) {
+ return;
+ }
+
+ priv = talkatu_input_get_instance_private(TALKATU_INPUT(view));
+
+ buffer = gtk_text_view_get_buffer(view);
+
+ gtk_text_buffer_get_iter_at_mark(buffer, &iter, priv->context_mark);
+
+ /* add the send message item */
+ if(gtk_text_view_get_editable(view)) {
+ item = gtk_menu_item_new_with_label(_("Send message"));
+ g_signal_connect_after(
+ G_OBJECT(item),
+ "activate",
+ G_CALLBACK(talkatu_input_send_message_cb),
+ view
+ );
+ gtk_menu_shell_insert(GTK_MENU_SHELL(popup), item, pos++);
+ gtk_widget_show(item);
+
+ item = gtk_separator_menu_item_new();
+ gtk_menu_shell_insert(GTK_MENU_SHELL(popup), item , pos++);
+ gtk_widget_show(item);
+ }
+}
+
+static void
+talkatu_input_buffer_set_cb(GObject *view, GParamSpec *pspec, gpointer data) {
+ TalkatuInputPrivate *priv = NULL;
+ TalkatuInput *input = TALKATU_INPUT(view);
+ GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(view));
+ GtkTextIter start;
+
+ priv = talkatu_input_get_instance_private(input);
+
+ gspell_text_view_basic_setup(priv->gspell_view);
+
+ /* grab our context_mark */
+ gtk_text_buffer_get_start_iter(buffer, &start);
+ priv->context_mark = gtk_text_buffer_create_mark(buffer, NULL, &start, TRUE);
+
+ if(TALKATU_IS_BUFFER(buffer)) {
+ GSimpleActionGroup *ag = NULL;
+
+ ag = talkatu_buffer_get_action_group(TALKATU_BUFFER(buffer));
+
+ if(TALKATU_IS_ACTION_GROUP(ag)) {
+ /* Tell the action group of the buffer that we exist. */
+ talkatu_action_group_set_input(TALKATU_ACTION_GROUP(ag), input);
+ }
+ }
+}
+
+static void
+talkatu_input_attachment_response_cb(GtkDialog *dialog, gint response,
+ gpointer data)
+{
+ /* If the user hits escape response is set to GTK_RESPONSE_DELETE_EVENT
+ * and Gtk cleans up the dialog for us automatically.
+ */
+
+ if(response == GTK_RESPONSE_CANCEL) {
+ /* we call this separately for GTK_RESPONSE_CANCEL because
+ * GTK_RESPONSE_DELETE_EVENT already destroys the dialog.
+ */
+ gtk_widget_destroy(GTK_WIDGET(dialog));
+ } else if(response == GTK_RESPONSE_ACCEPT) {
+ GtkTextBuffer *buffer = NULL;
+ TalkatuAttachment *attachment = NULL;
+ TalkatuAttachmentDialog *adialog = TALKATU_ATTACHMENT_DIALOG(dialog);
+ TalkatuInput *input = TALKATU_INPUT(data);
+ const gchar *comment = NULL;
+ gchar *escaped = NULL;
+
+ comment = talkatu_attachment_dialog_get_comment(adialog);
+ escaped = g_markup_escape_text(comment, -1);
+
+ /* it's a pretty safe assumption that our buffer is a talkatu buffer */
+ buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(input));
+ talkatu_markup_set_html(TALKATU_BUFFER(buffer), escaped, -1);
+ g_free(escaped);
+
+ /* 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));
+
+ /* send the message */
+ talkatu_input_send_message(input);
+
+ /* kill the dialog */
+ gtk_widget_destroy(GTK_WIDGET(dialog));
+ }
+}
+
+static void
+talkatu_input_image_received_cb(GtkClipboard *clipboard, GdkPixbuf *pixbuf,
+ gpointer data)
+{
+ TalkatuAttachment *attachment = NULL;
+ GFile *file = NULL;
+ GFileIOStream *stream = NULL;
+ GOutputStream *ostream = NULL;
+ GtkWidget *dialog = NULL, *input = NULL;
+ GtkTextBuffer *buffer = NULL;
+ GError *error = NULL;
+ gchar *comment = NULL, *filename = NULL;
+ gboolean saved = FALSE;
+ guint64 size;
+
+ /* save the image on clipboard to a temp file so we can reference it in the
+ * attachment.
+ */
+ file = g_file_new_tmp("talkatu-clipboard-XXXXXX.png", &stream, &error);
+ if(error != NULL) {
+ g_warning("failed to create temp file: %s", error->message);
+ g_error_free(error);
+
+ return;
+ }
+
+ ostream = g_io_stream_get_output_stream(G_IO_STREAM(stream));
+
+ saved = gdk_pixbuf_save_to_stream(pixbuf, ostream, "png", NULL, &error,
+ NULL);
+ if(!saved) {
+ g_object_unref(G_OBJECT(file));
+
+ g_warning("failed to save an image from the clipboard: %s",
+ error->message);
+ g_error_free(error);
+
+ g_io_stream_close(G_IO_STREAM(stream), NULL, NULL);
+ g_object_unref(G_OBJECT(stream));
+
+ return;
+ }
+
+ /* Now that all of the data has been written, use g_seekable_tell to get
+ * the size of the file.
+ */
+ size = g_seekable_tell(G_SEEKABLE(ostream));
+
+ if(!g_io_stream_close(G_IO_STREAM(stream), NULL, &error)) {
+ g_object_unref(G_OBJECT(file));
+
+ g_warning("failed to save an image from the clipboard: %s",
+ error->message);
+ g_error_free(error);
+
+ g_object_unref(G_OBJECT(stream));
+
+ return;
+ }
+
+ /* now create the message and its attachment */
+ input = GTK_WIDGET(data);
+ buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(input));
+ comment = talkatu_markup_get_html(buffer, NULL);
+
+ attachment = talkatu_attachment_new(G_GUINT64_CONSTANT(0), "image/png");
+
+ filename = g_file_get_uri(file);
+ talkatu_attachment_set_local_uri(attachment, filename);
+ g_free(filename);
+
+ /* for the remote side, we'll set the filename to something
+ * non-descriptive.
+ */
+ talkatu_attachment_set_remote_uri(attachment, "unknown.png");
+
+ talkatu_attachment_set_size(attachment, size);
+
+ dialog = talkatu_attachment_dialog_new(attachment, comment);
+ g_signal_connect(G_OBJECT(dialog), "response",
+ G_CALLBACK(talkatu_input_attachment_response_cb), input);
+ gtk_widget_show_all(dialog);
+
+ g_object_unref(G_OBJECT(file));
+ g_free(comment);
+ g_object_unref(G_OBJECT(stream));
+}
+
+/******************************************************************************
+ * Default Signal Handlers
+ *****************************************************************************/
+static gboolean
+talkatu_input_popup_menu(GtkWidget *widget) {
+ TalkatuInputPrivate *priv = talkatu_input_get_instance_private(TALKATU_INPUT(widget));
+ GtkTextBuffer *buffer = NULL;
+ GtkTextIter iter;
+
+ buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(widget));
+
+ gtk_text_buffer_get_iter_at_mark(
+ buffer,
+ &iter,
+ gtk_text_buffer_get_insert(buffer)
+ );
+
+ gtk_text_buffer_move_mark(buffer, priv->context_mark, &iter);
+
+ return GTK_WIDGET_CLASS(talkatu_input_parent_class)->popup_menu(widget);
+}
+
+static void
+talkatu_input_should_send_message(TalkatuInput *input, TalkatuInputSendBinding binding) {
+ TalkatuInputPrivate *priv = talkatu_input_get_instance_private(input);
+
+ if((priv->send_binding & binding) != 0) {
+ talkatu_input_send_message(input);
+ } else if(gtk_text_view_get_editable(GTK_TEXT_VIEW(input))) {
+ GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(input));
+
+ gtk_text_buffer_insert_at_cursor(buffer, "\n", 1);
+ }
+}
+
+/******************************************************************************
+ * GtkTextViewClass overrides
+ *****************************************************************************/
+static void
+talkatu_input_paste_clipboard(GtkTextView *view) {
+ GtkClipboard *clipboard =
+ gtk_widget_get_clipboard(GTK_WIDGET(view), GDK_SELECTION_CLIPBOARD);
+
+ if(gtk_clipboard_wait_is_image_available(clipboard)) {
+ gtk_clipboard_request_image(clipboard, talkatu_input_image_received_cb,
+ view);
+ } else {
+ GTK_TEXT_VIEW_CLASS(talkatu_input_parent_class)->paste_clipboard(view);
+ }
+}
+
+/******************************************************************************
+ * GObject Implementation
+ *****************************************************************************/
+static void
+talkatu_input_get_property(GObject *obj, guint prop_id, GValue *value, GParamSpec *pspec) {
+ TalkatuInput *input = TALKATU_INPUT(obj);
+
+ switch(prop_id) {
+ case PROP_SEND_BINDING:
+ g_value_set_flags(value, talkatu_input_get_send_binding(input));
+ break;
+ case PROP_ID:
+ g_value_set_uint64(value, talkatu_input_get_id(input));
+ break;
+ case PROP_TIMESTAMP:
+ g_value_set_boxed(value, talkatu_input_get_timestamp(input));
+ break;
+ case PROP_CONTENT_TYPE:
+ g_value_set_enum(value, talkatu_input_get_content_type(input));
+ break;
+ case PROP_AUTHOR:
+ g_value_set_string(value, talkatu_input_get_author(input));
+ break;
+ case PROP_CONTENTS:
+ g_value_set_string(value, talkatu_input_get_contents(input));
+ break;
+ case PROP_EDITED:
+ g_value_set_boolean(value, talkatu_input_get_edited(input));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+talkatu_input_set_property(GObject *obj, guint prop_id, const GValue *value, GParamSpec *pspec) {
+ TalkatuInput *input = TALKATU_INPUT(obj);
+
+ switch(prop_id) {
+ case PROP_SEND_BINDING:
+ talkatu_input_set_send_binding(input, g_value_get_flags(value));
+ break;
+ case PROP_ID:
+ talkatu_input_set_id(input, g_value_get_uint64(value));
+ break;
+ case PROP_TIMESTAMP:
+ talkatu_input_set_timestamp(input, g_value_get_boxed(value));
+ break;
+ case PROP_CONTENT_TYPE:
+ talkatu_input_set_content_type(input, g_value_get_enum(value));
+ break;
+ case PROP_AUTHOR:
+ talkatu_input_set_author(input, g_value_get_string(value));
+ break;
+ case PROP_CONTENTS:
+ talkatu_input_set_contents(input, g_value_get_string(value));
+ break;
+ case PROP_EDITED:
+ talkatu_input_set_edited(input, g_value_get_boolean(value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+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,
+ NULL, g_object_unref);
+
+ /* we need to know when the buffer is changed in our parent so we can
+ * update our actions and other stuff.
+ */
+ g_signal_connect(
+ G_OBJECT(input),
+ "notify::buffer",
+ G_CALLBACK(talkatu_input_buffer_set_cb),
+ NULL
+ );
+
+ /* setup GSpell */
+ priv->gspell_view = gspell_text_view_get_from_gtk_text_view(GTK_TEXT_VIEW(input));
+
+ /* we need to connect this signal *AFTER* everything to make sure our items
+ * end up in the correct place.
+ */
+ g_signal_connect_after(
+ G_OBJECT(input),
+ "populate-popup",
+ G_CALLBACK(talkatu_input_populate_popup_cb),
+ NULL
+ );
+}
+
+static void
+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);
+}
+
+static void
+talkatu_input_class_init(TalkatuInputClass *klass) {
+ GObjectClass *obj_class = G_OBJECT_CLASS(klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
+ GtkTextViewClass *text_view_class = GTK_TEXT_VIEW_CLASS(klass);
+ GtkBindingSet *binding_set = NULL;
+
+ 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;
+
+ text_view_class->paste_clipboard = talkatu_input_paste_clipboard;
+
+ klass->should_send_message = talkatu_input_should_send_message;
+
+ /* add our properties */
+ properties[PROP_SEND_BINDING] = g_param_spec_flags(
+ "send-binding", "send-binding", "The keybindings that will trigger the send signal",
+ TALKATU_TYPE_INPUT_SEND_BINDING,
+ TALKATU_INPUT_SEND_BINDING_RETURN | TALKATU_INPUT_SEND_BINDING_KP_ENTER,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT
+ );
+ 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.
+ * @arg1: The #TalkatuInputSendBinding that was entered.
+ * @user_data: User supplied data.
+ *
+ * Emitted when a potential keybinding to send the message is entered to
+ * determine if the message should be sent.
+ */
+ signals[SIG_SHOULD_SEND_MESSAGE] = g_signal_new(
+ "should-send-message",
+ G_TYPE_FROM_CLASS(klass),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET(TalkatuInputClass, should_send_message),
+ NULL,
+ NULL,
+ NULL,
+ G_TYPE_NONE,
+ 1,
+ TALKATU_TYPE_INPUT_SEND_BINDING
+ );
+
+ /**
+ * TalkatuInput::send-message:
+ * @talkatuinput: The #TalkatuInput instance.
+ * @user_data: User supplied data.
+ *
+ * Emitted when a message should be sent.
+ */
+ signals[SIG_SEND_MESSAGE] = g_signal_new(
+ "send-message",
+ G_TYPE_FROM_CLASS(klass),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET(TalkatuInputClass, send_message),
+ NULL,
+ NULL,
+ NULL,
+ G_TYPE_NONE,
+ 0
+ );
+
+ /* setup key bindings */
+ binding_set = gtk_binding_set_by_class(talkatu_input_parent_class);
+
+ gtk_binding_entry_add_signal(binding_set, GDK_KEY_Return, 0, "should-send-message", 1, TALKATU_TYPE_INPUT_SEND_BINDING, TALKATU_INPUT_SEND_BINDING_RETURN);
+ gtk_binding_entry_add_signal(binding_set, GDK_KEY_Return, GDK_SHIFT_MASK, "should-send-message", 1, TALKATU_TYPE_INPUT_SEND_BINDING, TALKATU_INPUT_SEND_BINDING_SHIFT_RETURN);
+ gtk_binding_entry_add_signal(binding_set, GDK_KEY_Return, GDK_CONTROL_MASK, "should-send-message", 1, TALKATU_TYPE_INPUT_SEND_BINDING, TALKATU_INPUT_SEND_BINDING_CONTROL_RETURN);
+ gtk_binding_entry_add_signal(binding_set, GDK_KEY_KP_Enter, 0, "should-send-message", 1, TALKATU_TYPE_INPUT_SEND_BINDING, TALKATU_INPUT_SEND_BINDING_KP_ENTER);
+}
+
+/******************************************************************************
+ * Public API
+ *****************************************************************************/
+GtkWidget *
+talkatu_input_new(void) {
+ return NULL;
+}
+
+/**
+ * talkatu_input_set_send_binding:
+ * @input: The #TalkatuInput instance.
+ * @bindings: The #TalkatuInputSendBinding value.
+ *
+ * Sets the bindings for when the send-message signal should be emitted.
+ */
+void
+talkatu_input_set_send_binding(TalkatuInput *input,
+ TalkatuInputSendBinding bindings)
+{
+ TalkatuInputPrivate *priv = NULL;
+
+ g_return_if_fail(TALKATU_IS_INPUT(input));
+
+ priv = talkatu_input_get_instance_private(TALKATU_INPUT(input));
+
+ priv->send_binding = bindings;
+
+ g_object_notify_by_pspec(G_OBJECT(input), properties[PROP_SEND_BINDING]);
+}
+
+/**
+ * talkatu_input_get_send_binding:
+ * @input: The #TalkatuInput instance.
+ *
+ * Gets the #TalkatuInputSendBinding which determines when send-message
+ * signal will be emitted.
+ *
+ * Returns: The #TalkatuInputSendBinding.
+ */
+TalkatuInputSendBinding
+talkatu_input_get_send_binding(TalkatuInput *input) {
+ TalkatuInputPrivate *priv = NULL;
+
+ g_return_val_if_fail(TALKATU_IS_INPUT(input), 0);
+
+ priv = talkatu_input_get_instance_private(TALKATU_INPUT(priv));
+
+ return priv->send_binding;
+}
+
+/**
+ * talkatu_input_send_message:
+ * @input: The #TalkatuInput instance.
+ *
+ * Emits the signal that @input is trying to send a message. This is used for
+ * cases like the optional send button in #TalkatuEditor and other instances
+ * where the user has performed an action to send a message.
+ */
+void
+talkatu_input_send_message(TalkatuInput *input) {
+ g_return_if_fail(TALKATU_IS_INPUT(input));
+
+ g_signal_emit(input, signals[SIG_SEND_MESSAGE], 0);
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/talkatu/talkatuinput.h Tue Jun 30 05:14:12 2020 +0000
@@ -0,0 +1,67 @@
+/*
+ * 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_INPUT_H
+#define TALKATU_INPUT_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include <gtk/gtk.h>
+
+#include <talkatu/talkatuview.h>
+
+typedef enum _TalkatuInputSendBinding /*< prefix=TALKATU_INPUT_SEND_BINDING,underscore_name=TALKATU_INPUT_SEND_BINDING >*/
+{
+ TALKATU_INPUT_SEND_BINDING_RETURN = 1 << 0,
+ TALKATU_INPUT_SEND_BINDING_KP_ENTER = 1 << 1,
+ TALKATU_INPUT_SEND_BINDING_SHIFT_RETURN = 1 << 2,
+ TALKATU_INPUT_SEND_BINDING_CONTROL_RETURN = 1 << 3,
+} TalkatuInputSendBinding;
+
+G_BEGIN_DECLS
+
+#define TALKATU_TYPE_INPUT (talkatu_input_get_type())
+G_DECLARE_DERIVABLE_TYPE(TalkatuInput, talkatu_input, TALKATU, INPUT, TalkatuView)
+
+struct _TalkatuInputClass {
+ /*< private >*/
+ TalkatuViewClass parent;
+
+ /*< public >*/
+ void (*should_send_message)(TalkatuInput *input, TalkatuInputSendBinding binding);
+ void (*send_message)(TalkatuInput *input);
+
+ /*< private >*/
+ gpointer reserved[4];
+};
+
+GtkWidget *talkatu_input_new(void);
+
+void talkatu_input_set_send_binding(TalkatuInput *input, TalkatuInputSendBinding bindings);
+TalkatuInputSendBinding talkatu_input_get_send_binding(TalkatuInput *input);
+
+void talkatu_input_send_message(TalkatuInput *input);
+
+G_END_DECLS
+
+#endif /* TALKATU_INPUT_H */
--- a/talkatu/talkatumarkdownbuffer.c Mon Jun 08 05:47:27 2020 +0000
+++ b/talkatu/talkatumarkdownbuffer.c Tue Jun 30 05:14:12 2020 +0000
@@ -274,6 +274,7 @@
talkatu_action_group_enable_action(TALKATU_ACTION_GROUP(ag), TALKATU_ACTION_FORMAT_UNDERLINE);
talkatu_action_group_enable_action(TALKATU_ACTION_GROUP(ag), TALKATU_ACTION_FORMAT_STRIKETHROUGH);
talkatu_action_group_enable_action(TALKATU_ACTION_GROUP(ag), TALKATU_ACTION_FORMAT_RESET);
+ talkatu_action_group_enable_action(TALKATU_ACTION_GROUP(ag), TALKATU_ACTION_ATTACH_FILE);
talkatu_action_group_enable_action(TALKATU_ACTION_GROUP(ag), TALKATU_ACTION_INSERT_LINK);
return ag;
--- a/talkatu/talkatumessage.c Mon Jun 08 05:47:27 2020 +0000
+++ b/talkatu/talkatumessage.c Tue Jun 30 05:14:12 2020 +0000
@@ -44,10 +44,24 @@
/**
* TalkatuMessageInterface:
+ * @add_attachment: The add_attachment vfunc is called to add an attachment to
+ * the message.
+ * @remove_attachment: The remove_attachment vfunc is called to remove an
+ * attachment from the message.
+ * @get_attachment: The get_attachment vfunc gets an attachment from the
+ * message.
+ * @foreach_attachment: The foreach_attachment vfunc is called to iterate over
+ * each attachment in the message and call the provided
+ * TalkatuForeachAttachmentFunc on them.
+ * @clear_attachments: The clear_attachments vfunc is called to clear all
+ * attachments from the message.
*
- * #TalkatuMessage is an interface to be implemented to standardize the way
+ * #TalkatuMessage is an interface to be implemented that standardizes the way
* messages are handled. All of its properties should be overridden with a
* sensible value returned for them.
+ *
+ * It also needs to implement a storage mechanism for attachments which are
+ * identified by a #guint64.
*/
/**
@@ -86,8 +100,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_TYPE_DATE_TIME,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY
);
g_object_interface_install_property(iface, pspec);
@@ -237,6 +252,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.
+ */
+void
+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.
*
@@ -335,3 +366,118 @@
g_object_set(G_OBJECT(message), "edited", edited, NULL);
}
+/**
+ * talkatu_message_add_attachment:
+ * @message: The #TalkatuMessage instance.
+ * @attachment: The #TalkatuAttachment instance.
+ *
+ * Adds @attachment to @message.
+ *
+ * Returns %TRUE if an attachment with the same ID did not already exist.
+ */
+gboolean
+talkatu_message_add_attachment(TalkatuMessage *message,
+ TalkatuAttachment *attachment)
+{
+ TalkatuMessageInterface *iface = NULL;
+
+ g_return_val_if_fail(TALKATU_IS_MESSAGE(message), FALSE);
+ g_return_val_if_fail(TALKATU_IS_ATTACHMENT(attachment), FALSE);
+
+ iface = TALKATU_MESSAGE_GET_IFACE(message);
+ if(iface && iface->add_attachment) {
+ return iface->add_attachment(message, attachment);
+ }
+
+ return FALSE;
+}
+
+/**
+ * talkatu_message_remove_attachment:
+ * @message: The #TalkatuMessage instance.
+ * @id: The id of the #TalkatuAttachment
+ *
+ * Removes the #TalkatuAttachment identified by @id if it exists.
+ *
+ * Returns: %TRUE if the #TalkatuAttachment was found and removed, %FALSE
+ * otherwise.
+ */
+gboolean
+talkatu_message_remove_attachment(TalkatuMessage *message, gint64 id) {
+ TalkatuMessageInterface *iface = NULL;
+
+ g_return_val_if_fail(TALKATU_IS_MESSAGE(message), FALSE);
+
+ iface = TALKATU_MESSAGE_GET_IFACE(message);
+ if(iface && iface->remove_attachment) {
+ return iface->remove_attachment(message, id);
+ }
+
+ return FALSE;
+}
+
+/**
+ * talkatu_message_get_attachment:
+ * @message: The #TalkatuMessage instance.
+ * @id: The id of the #TalkatuAttachment to get.
+ *
+ * Retrieves the #TalkatuAttachment identified by @id from @message.
+ *
+ * Returns: (transfer full): The #TalkatuAttachment if it was found, otherwise
+ * %NULL.
+ */
+TalkatuAttachment *
+talkatu_message_get_attachment(TalkatuMessage *message, gint64 id) {
+ TalkatuMessageInterface *iface = NULL;
+
+ g_return_val_if_fail(TALKATU_IS_MESSAGE(message), NULL);
+
+ iface = TALKATU_MESSAGE_GET_IFACE(message);
+ if(iface && iface->get_attachment) {
+ return iface->get_attachment(message, id);
+ }
+
+ return NULL;
+}
+
+/**
+ * talkatu_message_foreach_attachment:
+ * @message: The #TalkatuMessage instance.
+ * @func: (scope call): The #TalkatuAttachmentForeachFunc to call.
+ * @data: User data to pass to @func.
+ *
+ * Calls @func for each #TalkatuAttachment that's attached to @message.
+ */
+void
+talkatu_message_foreach_attachment(TalkatuMessage *message,
+ TalkatuAttachmentForeachFunc func,
+ gpointer data)
+{
+ TalkatuMessageInterface *iface = NULL;
+
+ g_return_if_fail(TALKATU_IS_MESSAGE(message));
+ g_return_if_fail(func != NULL);
+
+ iface = TALKATU_MESSAGE_GET_IFACE(message);
+ if(iface && iface->foreach_attachment) {
+ iface->foreach_attachment(message, func, data);
+ }
+}
+
+/**
+ * talkatu_message_clear_attachments:
+ * @message: The #TalkatuMessage instance.
+ *
+ * Removes all attachments from @message.
+ */
+void
+talkatu_message_clear_attachments(TalkatuMessage *message) {
+ TalkatuMessageInterface *iface = NULL;
+
+ g_return_if_fail(TALKATU_IS_MESSAGE(message));
+
+ iface = TALKATU_MESSAGE_GET_IFACE(message);
+ if(iface && iface->clear_attachments) {
+ iface->clear_attachments(message);
+ }
+}
--- a/talkatu/talkatumessage.h Mon Jun 08 05:47:27 2020 +0000
+++ b/talkatu/talkatumessage.h Tue Jun 30 05:14:12 2020 +0000
@@ -28,6 +28,8 @@
#include <gtk/gtk.h>
+#include <talkatu/talkatuattachment.h>
+
typedef enum _TalkatuContentType /*< prefix=TALKATU_CONTENT_TYPE,underscore_name=TALKATU_CONTENT_TYPE >*/
{
TALKATU_CONTENT_TYPE_PLAIN = 0,
@@ -45,6 +47,13 @@
/*< private >*/
GTypeInterface parent;
+ /*< public >*/
+ gboolean (*add_attachment)(TalkatuMessage *message, TalkatuAttachment *attachment);
+ gboolean (*remove_attachment)(TalkatuMessage *message, gint64 id);
+ TalkatuAttachment *(*get_attachment)(TalkatuMessage *message, gint64 id);
+ void (*foreach_attachment)(TalkatuMessage *message, TalkatuAttachmentForeachFunc func, gpointer data);
+ void (*clear_attachments)(TalkatuMessage *message);
+
/*< private >*/
gpointer reserved[4];
};
@@ -56,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);
@@ -66,6 +76,12 @@
gboolean talkatu_message_get_edited(TalkatuMessage *message);
void talkatu_message_set_edited(TalkatuMessage *message, gboolean edited);
+gboolean talkatu_message_add_attachment(TalkatuMessage *message, TalkatuAttachment *attachment);
+gboolean talkatu_message_remove_attachment(TalkatuMessage *message, gint64 id);
+TalkatuAttachment *talkatu_message_get_attachment(TalkatuMessage *message, gint64 id);
+void talkatu_message_foreach_attachment(TalkatuMessage *message, TalkatuAttachmentForeachFunc func, gpointer data);
+void talkatu_message_clear_attachments(TalkatuMessage *message);
+
G_END_DECLS
#endif /* TALKATU_MESSAGE_H */
--- a/talkatu/talkatuview.c Mon Jun 08 05:47:27 2020 +0000
+++ b/talkatu/talkatuview.c Tue Jun 30 05:14:12 2020 +0000
@@ -18,13 +18,14 @@
#include <glib/gi18n-lib.h>
#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>
-#include <gspell/gspell.h>
#include <gumbo.h>
#include <stdio.h>
#include "talkatu/talkatuactiongroup.h"
+#include "talkatu/talkatuattachment.h"
+#include "talkatu/talkatuattachmentdialog.h"
#include "talkatu/talkatubuffer.h"
-#include "talkatu/talkatuenums.h"
+#include "talkatu/talkatumarkup.h"
#include "talkatu/talkatutag.h"
#include "talkatu/talkatuview.h"
@@ -47,26 +48,12 @@
* TalkatuViewClass:
* @format_activate: The class handler for the #TalkatuView::format_activate
* signal.
- * @should_send_message: The class handler for the
- * #TalkatuView::should_send_message signal.
- * @send_message: The class handler for the #TalkatuView::send_message signal.
* @open_url: The class handler for the #TalkatuView::open_url signal.
*
* The backing class to #TalkatuView instances.
*/
/**
- * TalkatuViewSendBinding:
- * @TALKATU_VIEW_SEND_BINDING_RETURN: Represents return.
- * @TALKATU_VIEW_SEND_BINDING_KP_ENTER: Represents enter.
- * @TALKATU_VIEW_SEND_BINDING_SHIFT_RETURN: Represents shift-return.
- * @TALKATU_VIEW_SEND_BINDING_CONTROL_RETURN: Represents control-return.
- *
- * Flags for assigning and determining which key bindings should be used to
- * send a message.
- */
-
-/**
* TalkatuView:
*
* A #GtkTextView subclass that's preconfigured with a #TalkatuBuffer.
@@ -74,13 +61,6 @@
typedef struct {
GSimpleActionGroup *action_group;
- TalkatuViewSendBinding send_binding;
- GspellTextView *gspell_view;
-
- /* this mark is used to keep track of our context for the context menu. It
- * is updated via cursor-moved and button-press callbacks.
- */
- GtkTextMark *context_mark;
/* we cache the cursor that's displayed while hovering over a link as well
* the tag for links/anchors to avoid extra lookups.
@@ -90,16 +70,7 @@
} TalkatuViewPrivate;
enum {
- PROP_0 = 0,
- PROP_SEND_BINDING,
- N_PROPERTIES,
-};
-static GParamSpec *properties[N_PROPERTIES];
-
-enum {
SIG_FORMAT_ACTIVATE,
- SIG_SHOULD_SEND_MESSAGE,
- SIG_SEND_MESSAGE,
SIG_OPEN_URL,
LAST_SIGNAL,
};
@@ -251,15 +222,11 @@
TalkatuViewPrivate *priv = talkatu_view_get_instance_private(TALKATU_VIEW(view));
GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(view));
GtkTextTagTable *table = gtk_text_buffer_get_tag_table(buffer);
- GtkTextIter start;
if(TALKATU_IS_BUFFER(buffer)) {
priv->action_group = talkatu_buffer_get_action_group(TALKATU_BUFFER(buffer));
}
- gtk_text_buffer_get_start_iter(buffer, &start);
- priv->context_mark = gtk_text_buffer_create_mark(buffer, NULL, &start, TRUE);
-
/* check for the anchor tag, if we have it, add a signal handler to it */
priv->tag_anchor = gtk_text_tag_table_lookup(table, TALKATU_TAG_ANCHOR);
if(priv->tag_anchor != NULL) {
@@ -270,8 +237,6 @@
NULL
);
}
-
- gspell_text_view_basic_setup(priv->gspell_view);
}
/******************************************************************************
@@ -290,82 +255,6 @@
}
}
-static void
-talkatu_view_should_send_message(TalkatuView *view, TalkatuViewSendBinding binding) {
- TalkatuViewPrivate *priv = talkatu_view_get_instance_private(view);
-
- if((priv->send_binding & binding) != 0) {
- talkatu_view_send_message(view);
- } else if(gtk_text_view_get_editable(GTK_TEXT_VIEW(view))) {
- GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(view));
-
- gtk_text_buffer_insert_at_cursor(buffer, "\n", 1);
- }
-}
-
-static gboolean
-talkatu_view_popup_menu(GtkWidget *widget) {
- TalkatuViewPrivate *priv = talkatu_view_get_instance_private(TALKATU_VIEW(widget));
- GtkTextBuffer *buffer = NULL;
- GtkTextIter iter;
-
- buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(widget));
-
- gtk_text_buffer_get_iter_at_mark(
- buffer,
- &iter,
- gtk_text_buffer_get_insert(buffer)
- );
-
- gtk_text_buffer_move_mark(buffer, priv->context_mark, &iter);
-
- return GTK_WIDGET_CLASS(talkatu_view_parent_class)->popup_menu(widget);
-}
-
-static void
-talkatu_view_send_message_cb(GtkMenuItem *item, gpointer data) {
- talkatu_view_send_message(TALKATU_VIEW(data));
-
- gtk_widget_grab_focus(GTK_WIDGET(data));
-}
-
-static void
-talkatu_view_populate_popup_cb(GtkTextView *view, GtkWidget *popup) {
- TalkatuViewPrivate *priv = NULL;
- GtkTextBuffer *buffer = NULL;
- GtkTextIter iter;
- GtkWidget *item = NULL;
- gint pos = 0;
-
- /* if the popup isn't a menu, bail */
- if(!GTK_IS_MENU(popup)) {
- return;
- }
-
- priv = talkatu_view_get_instance_private(TALKATU_VIEW(view));
-
- buffer = gtk_text_view_get_buffer(view);
-
- gtk_text_buffer_get_iter_at_mark(buffer, &iter, priv->context_mark);
-
- /* add the send message item */
- if(gtk_text_view_get_editable(view)) {
- item = gtk_menu_item_new_with_label(_("Send message"));
- g_signal_connect_after(
- G_OBJECT(item),
- "activate",
- G_CALLBACK(talkatu_view_send_message_cb),
- view
- );
- gtk_menu_shell_insert(GTK_MENU_SHELL(popup), item, pos++);
- gtk_widget_show(item);
-
- item = gtk_separator_menu_item_new();
- gtk_menu_shell_insert(GTK_MENU_SHELL(popup), item , pos++);
- gtk_widget_show(item);
- }
-}
-
static gboolean
talkatu_view_query_tooltip(GtkWidget *widget,
gint x,
@@ -458,34 +347,6 @@
}
static void
-talkatu_view_get_property(GObject *obj, guint prop_id, GValue *value, GParamSpec *pspec) {
- TalkatuView *view = TALKATU_VIEW(obj);
-
- switch(prop_id) {
- case PROP_SEND_BINDING:
- g_value_set_flags(value, talkatu_view_get_send_binding(view));
- break;
- default:
- G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, pspec);
- break;
- }
-}
-
-static void
-talkatu_view_set_property(GObject *obj, guint prop_id, const GValue *value, GParamSpec *pspec) {
- TalkatuView *view = TALKATU_VIEW(obj);
-
- switch(prop_id) {
- case PROP_SEND_BINDING:
- talkatu_view_set_send_binding(view, g_value_get_flags(value));
- break;
- default:
- G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, pspec);
- break;
- }
-}
-
-static void
talkatu_view_init(TalkatuView *view) {
TalkatuViewPrivate *priv = talkatu_view_get_instance_private(view);
@@ -511,19 +372,6 @@
G_CALLBACK(talkatu_view_buffer_set_cb),
NULL
);
-
- /* setup GSpell */
- priv->gspell_view = gspell_text_view_get_from_gtk_text_view(GTK_TEXT_VIEW(view));
-
- /* we need to connect this signal *AFTER* everything to make sure our items
- * end up in the correct place.
- */
- g_signal_connect_after(
- G_OBJECT(view),
- "populate-popup",
- G_CALLBACK(talkatu_view_populate_popup_cb),
- NULL
- );
}
static void
@@ -533,28 +381,15 @@
GtkTextViewClass *text_view_class = GTK_TEXT_VIEW_CLASS(klass);
GtkBindingSet *binding_set = NULL;
- obj_class->get_property = talkatu_view_get_property;
- obj_class->set_property = talkatu_view_set_property;
obj_class->finalize = talkatu_view_finalize;
widget_class->motion_notify_event = talkatu_view_motion_notify_event;
- widget_class->popup_menu = talkatu_view_popup_menu;
widget_class->query_tooltip = talkatu_view_query_tooltip;
text_view_class->create_buffer = talkatu_view_create_buffer;
/* add our default signal handlers */
klass->format_activate = talkatu_view_format_activate;
- klass->should_send_message = talkatu_view_should_send_message;
-
- /* add our properties */
- properties[PROP_SEND_BINDING] = g_param_spec_flags(
- "send-binding", "send-binding", "The keybindings that will trigger the send signal",
- TALKATU_TYPE_VIEW_SEND_BINDING,
- TALKATU_VIEW_SEND_BINDING_RETURN | TALKATU_VIEW_SEND_BINDING_KP_ENTER,
- G_PARAM_READWRITE | G_PARAM_CONSTRUCT
- );
- g_object_class_install_properties(obj_class, N_PROPERTIES, properties);
/* add our signals */
@@ -580,47 +415,6 @@
);
/**
- * TalkatuView::should-send-message:
- * @talkatutextview: The #TalkatuView instance.
- * @arg1: The #TalkatuViewSendBinding that was entered.
- * @user_data: User supplied data.
- *
- * Emitted when a potential keybinding to send the message is entered to
- * determine if the message should be sent.
- */
- signals[SIG_SHOULD_SEND_MESSAGE] = g_signal_new(
- "should-send-message",
- G_TYPE_FROM_CLASS(klass),
- G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
- G_STRUCT_OFFSET(TalkatuViewClass, should_send_message),
- NULL,
- NULL,
- NULL,
- G_TYPE_NONE,
- 1,
- TALKATU_TYPE_VIEW_SEND_BINDING
- );
-
- /**
- * TalkatuView::send-message:
- * @talkatutextview: The #TalkatuView instance.
- * @user_data: User supplied data.
- *
- * Emitted when a message should be sent.
- */
- signals[SIG_SEND_MESSAGE] = g_signal_new(
- "send-message",
- G_TYPE_FROM_CLASS(klass),
- G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
- G_STRUCT_OFFSET(TalkatuViewClass, send_message),
- NULL,
- NULL,
- NULL,
- G_TYPE_NONE,
- 0
- );
-
- /**
* TalkatuView::open-url:
* @talkatutextview: The #TalkatuView instances.
* @url: The URL to open.
@@ -656,11 +450,6 @@
gtk_binding_entry_add_signal(binding_set, GDK_KEY_equal, GDK_CONTROL_MASK, "format-activate", 1, G_TYPE_STRING, TALKATU_ACTION_FORMAT_GROW);
gtk_binding_entry_add_signal(binding_set, GDK_KEY_minus, GDK_CONTROL_MASK, "format-activate", 1, G_TYPE_STRING, TALKATU_ACTION_FORMAT_SHRINK);
gtk_binding_entry_add_signal(binding_set, GDK_KEY_r, GDK_CONTROL_MASK, "format-activate", 1, G_TYPE_STRING, TALKATU_ACTION_FORMAT_RESET);
-
- gtk_binding_entry_add_signal(binding_set, GDK_KEY_Return, 0, "should-send-message", 1, TALKATU_TYPE_VIEW_SEND_BINDING, TALKATU_VIEW_SEND_BINDING_RETURN);
- gtk_binding_entry_add_signal(binding_set, GDK_KEY_Return, GDK_SHIFT_MASK, "should-send-message", 1, TALKATU_TYPE_VIEW_SEND_BINDING, TALKATU_VIEW_SEND_BINDING_SHIFT_RETURN);
- gtk_binding_entry_add_signal(binding_set, GDK_KEY_Return, GDK_CONTROL_MASK, "should-send-message", 1, TALKATU_TYPE_VIEW_SEND_BINDING, TALKATU_VIEW_SEND_BINDING_CONTROL_RETURN);
- gtk_binding_entry_add_signal(binding_set, GDK_KEY_KP_Enter, 0, "should-send-message", 1, TALKATU_TYPE_VIEW_SEND_BINDING, TALKATU_VIEW_SEND_BINDING_KP_ENTER);
}
/******************************************************************************
@@ -692,58 +481,3 @@
"buffer", buffer,
NULL));
}
-
-/**
- * talkatu_view_set_send_binding:
- * @view: The #TalkatuView instance.
- * @bindings: The #TalkatuViewSendBinding value.
- *
- * Sets the bindings for when the send-message signal should be emitted.
- */
-void
-talkatu_view_set_send_binding(TalkatuView *view, TalkatuViewSendBinding bindings) {
- TalkatuViewPrivate *priv = NULL;
-
- g_return_if_fail(TALKATU_IS_VIEW(view));
-
- priv = talkatu_view_get_instance_private(view);
-
- priv->send_binding = bindings;
-
- g_object_notify_by_pspec(G_OBJECT(view), properties[PROP_SEND_BINDING]);
-}
-
-/**
- * talkatu_view_get_send_binding:
- * @view: The #TalkatuView instance.
- *
- * Gets the #TalkatuViewSendBinding which determines when send-message
- * signal will be emitted.
- *
- * Returns: The #TalkatuViewSendBinding.
- */
-TalkatuViewSendBinding
-talkatu_view_get_send_binding(TalkatuView *view) {
- TalkatuViewPrivate *priv = NULL;
-
- g_return_val_if_fail(TALKATU_IS_VIEW(view), 0);
-
- priv = talkatu_view_get_instance_private(view);
-
- return priv->send_binding;
-}
-
-/**
- * talkatu_view_send_message:
- * @view: The #TalkatuView instance.
- *
- * Emits the signal that @view is trying to send a message. This is used for
- * cases like the optional send button in #TalkatuEditor and other instances
- * where the user has performed an action to send a message.
- */
-void
-talkatu_view_send_message(TalkatuView *view) {
- g_return_if_fail(TALKATU_IS_VIEW(view));
-
- g_signal_emit(view, signals[SIG_SEND_MESSAGE], 0);
-}
--- a/talkatu/talkatuview.h Mon Jun 08 05:47:27 2020 +0000
+++ b/talkatu/talkatuview.h Tue Jun 30 05:14:12 2020 +0000
@@ -28,14 +28,6 @@
#include <gtk/gtk.h>
-typedef enum _TalkatuViewSendBinding /*< prefix=TALKATU_VIEW_SEND_BINDING,underscore_name=TALKATU_VIEW_SEND_BINDING >*/
-{
- TALKATU_VIEW_SEND_BINDING_RETURN = 1 << 0,
- TALKATU_VIEW_SEND_BINDING_KP_ENTER = 1 << 1,
- TALKATU_VIEW_SEND_BINDING_SHIFT_RETURN = 1 << 2,
- TALKATU_VIEW_SEND_BINDING_CONTROL_RETURN = 1 << 3,
-} TalkatuViewSendBinding;
-
G_BEGIN_DECLS
#define TALKATU_TYPE_VIEW (talkatu_view_get_type())
@@ -49,9 +41,6 @@
/*< public >*/
void (*format_activate)(TalkatuView *view, const gchar *action_name);
- void (*should_send_message)(TalkatuView *view, TalkatuViewSendBinding binding);
- void (*send_message)(TalkatuView *view);
-
void (*open_url)(TalkatuView *view, const gchar *url);
/*< private >*/
@@ -61,11 +50,6 @@
GtkWidget *talkatu_view_new(void);
GtkWidget *talkatu_view_new_with_buffer(GtkTextBuffer *buffer);
-void talkatu_view_set_send_binding(TalkatuView *view, TalkatuViewSendBinding bindings);
-TalkatuViewSendBinding talkatu_view_get_send_binding(TalkatuView *view);
-
-void talkatu_view_send_message(TalkatuView *view);
-
G_END_DECLS
#endif /* TALKATU_VIEW_H */