commit 673f8487f949bc5992592cea6762e6cc8366b0e9
parent a37289b476f36d9c8bffc56b9ffc93f091ac70d9
Author: Jacki <jacki@thejackimonster.de>
Date: Fri, 22 Mar 2024 19:51:17 +0100
Implement dialog to add tags to messages
Signed-off-by: Jacki <jacki@thejackimonster.de>
Diffstat:
14 files changed, 816 insertions(+), 25 deletions(-)
diff --git a/resources/ui.gresource.xml b/resources/ui.gresource.xml
@@ -16,15 +16,17 @@
<file compressed="true">ui/message-sent.ui</file>
<file compressed="true">ui/message-status.ui</file>
<file compressed="true">ui/messenger.ui</file>
+ <file compressed="true">ui/new_account.ui</file>
<file compressed="true">ui/new_contact.ui</file>
<file compressed="true">ui/new_group.ui</file>
<file compressed="true">ui/new_lobby.ui</file>
<file compressed="true">ui/new_platform.ui</file>
- <file compressed="true">ui/new_account.ui</file>
+ <file compressed="true">ui/new_tag.ui</file>
<file compressed="true">ui/picker.ui</file>
<file compressed="true">ui/play_media.ui</file>
<file compressed="true">ui/account_entry.ui</file>
<file compressed="true">ui/send_file.ui</file>
<file compressed="true">ui/settings.ui</file>
+ <file compressed="true">ui/tag.ui</file>
</gresource>
</gresources>
diff --git a/resources/ui/new_tag.ui b/resources/ui/new_tag.ui
@@ -0,0 +1,133 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.40.0
+
+Copyright (C) 2024 GNUnet e.V.
+
+GNUnet is free software: you can redistribute it and/or modify it
+under the terms of the GNU Affero General Public License as published
+by the Free Software Foundation, either version 3 of the License,
+or (at your option) any later version.
+
+GNUnet 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
+Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+SPDX-License-Identifier: AGPL3.0-or-later
+Author: Tobias Frisch
+
+-->
+<interface>
+ <requires lib="gtk+" version="3.24"/>
+ <requires lib="libhandy" version="1.2"/>
+ <object class="GtkDialog" id="new_tag_dialog">
+ <property name="can-focus">False</property>
+ <property name="modal">True</property>
+ <property name="window-position">center-on-parent</property>
+ <property name="type-hint">dialog</property>
+ <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_button">
+ <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="confirm_button">
+ <property name="label" translatable="yes">Confirm</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">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="GtkBox">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <property name="border-width">8</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">4</property>
+ <child>
+ <object class="HdyAvatar" id="tag_avatar">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="halign">center</property>
+ <property name="margin-top">8</property>
+ <property name="margin-bottom">8</property>
+ <property name="icon-name">user-bookmarks-symbolic</property>
+ <property name="size">128</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label" translatable="yes">Tag:</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>
+ <child>
+ <object class="GtkEntry" id="tag_entry">
+ <property name="width-request">250</property>
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+</interface>
diff --git a/resources/ui/tag.ui b/resources/ui/tag.ui
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.40.0
+
+Copyright (C) 2024 GNUnet e.V.
+
+GNUnet is free software: you can redistribute it and/or modify it
+under the terms of the GNU Affero General Public License as published
+by the Free Software Foundation, either version 3 of the License,
+or (at your option) any later version.
+
+GNUnet 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
+Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+SPDX-License-Identifier: AGPL3.0-or-later
+Author: Tobias Frisch
+
+-->
+<interface>
+ <requires lib="gtk+" version="3.24"/>
+ <object class="GtkLabel" id="tag_label">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="ellipsize">end</property>
+ <property name="single-line-mode">True</property>
+ <style>
+ <class name="tag-label"/>
+ </style>
+ </object>
+</interface>
diff --git a/src/application.h b/src/application.h
@@ -48,6 +48,7 @@
#include "ui/new_group.h"
#include "ui/new_lobby.h"
#include "ui/new_platform.h"
+#include "ui/new_tag.h"
#include "ui/play_media.h"
#include "ui/send_file.h"
#include "ui/settings.h"
@@ -124,11 +125,12 @@ typedef struct MESSENGER_Application
UI_SEND_FILE_Handle send_file;
UI_PLAY_MEDIA_Handle play_media;
+ UI_NEW_ACCOUNT_Handle new_account;
UI_NEW_CONTACT_Handle new_contact;
UI_NEW_GROUP_Handle new_group;
UI_NEW_LOBBY_Handle new_lobby;
UI_NEW_PLATFORM_Handle new_platform;
- UI_NEW_ACCOUNT_Handle new_account;
+ UI_NEW_TAG_Handle new_tag;
UI_ACCOUNTS_Handle accounts;
UI_CONTACTS_Handle contacts;
diff --git a/src/ui.c b/src/ui.c
@@ -24,6 +24,8 @@
#include "ui.h"
+#include <gnunet/gnunet_common.h>
+
void
ui_label_set_text(GtkLabel *label, const char *text)
{
@@ -56,6 +58,23 @@ ui_entry_set_text(GtkEntry *entry, const char *text)
g_free(_text);
}
+char*
+ui_entry_get_text(GtkEntry *entry)
+{
+ g_assert(entry);
+
+ const gchar *text = gtk_entry_get_text(entry);
+
+ if (!text)
+ return NULL;
+
+ gchar *_text = g_locale_from_utf8(text, -1, NULL, NULL, NULL);
+ char *result = GNUNET_strdup(_text);
+ g_free(_text);
+
+ return result;
+}
+
void
ui_avatar_set_text(HdyAvatar *avatar, const char *text)
{
diff --git a/src/ui.h b/src/ui.h
@@ -51,6 +51,15 @@ ui_entry_set_text(GtkEntry *entry,
const char *text);
/**
+ * Returns the text from a GtkEntry applying automatic
+ * inverse utf8 conversion.
+ *
+ * @param entry Entry
+ */
+char*
+ui_entry_get_text(GtkEntry *entry);
+
+/**
* Sets the text of a HdyAvatar applying automatic utf8
* conversion.
*
diff --git a/src/ui/chat.c b/src/ui/chat.c
@@ -334,12 +334,8 @@ _iterate_message_tags(void *cls,
if (!_text)
return GNUNET_YES;
- gchar *match = g_strstr_len(_text, -1, filterTags->filter);
- if (match)
- {
+ if (g_strstr_len(_text, -1, filterTags->filter))
filterTags->matching = TRUE;
- g_free(match);
- }
g_free(_text);
return filterTags->matching? GNUNET_NO : GNUNET_YES;
@@ -390,7 +386,7 @@ handle_chat_messages_filter(GtkListBoxRow *row,
if (text)
result |= g_str_match_string(filter, text, TRUE);
- if (('#' == filter[0]) && (message->msg))
+ if (('#' == *filter) && (message->msg))
{
struct FilterTags filterTags;
filterTags.filter = &(filter[1]);
@@ -454,6 +450,94 @@ handle_chat_selection_close_button_click(UNUSED GtkButton *button,
}
void
+_new_tag_callback(MESSENGER_Application *app,
+ GList *selected,
+ const char *tag)
+{
+ g_assert(app);
+
+ GtkTextView *text_view = GTK_TEXT_VIEW(g_object_get_qdata(
+ G_OBJECT(app->ui.new_tag.dialog),
+ app->quarks.widget
+ ));
+
+ if (!text_view)
+ return;
+
+ struct GNUNET_CHAT_Context *context = (struct GNUNET_CHAT_Context*) (
+ g_object_get_qdata(
+ G_OBJECT(text_view),
+ app->quarks.data
+ )
+ );
+
+ UI_MESSAGE_Handle *message;
+ if ((!context) || (!tag))
+ goto cleanup;
+
+ while (selected)
+ {
+ GtkListBoxRow *row = GTK_LIST_BOX_ROW(selected->data);
+
+ if (!row)
+ goto skip_row;
+
+ message = (UI_MESSAGE_Handle*) g_object_get_qdata(
+ G_OBJECT(row),
+ app->quarks.ui
+ );
+
+ if ((!message) || (!(message->msg)))
+ goto skip_row;
+
+ GNUNET_CHAT_context_send_tag(
+ context,
+ message->msg,
+ tag
+ );
+
+ skip_row:
+ selected = selected->next;
+ }
+
+cleanup:
+ g_object_set_qdata(
+ G_OBJECT(app->ui.new_tag.dialog),
+ app->quarks.widget,
+ NULL
+ );
+}
+
+static void
+handle_chat_selection_tag_button_click(UNUSED GtkButton *button,
+ gpointer user_data)
+{
+ g_assert(user_data);
+
+ UI_CHAT_Handle *handle = (UI_CHAT_Handle*) user_data;
+
+ MESSENGER_Application *app = handle->app;
+
+ GList *selected = gtk_list_box_get_selected_rows(handle->messages_listbox);
+
+ ui_new_tag_dialog_init(app, &(app->ui.new_tag));
+
+ g_object_set_qdata(
+ G_OBJECT(app->ui.new_tag.dialog),
+ app->quarks.widget,
+ handle->send_text_view
+ );
+
+ ui_new_tag_dialog_link(
+ &(app->ui.new_tag),
+ _new_tag_callback,
+ selected
+ );
+
+ gtk_widget_show(GTK_WIDGET(app->ui.new_tag.dialog));
+}
+
+void
_delete_messages_callback(MESSENGER_Application *app,
GList *selected,
gulong delay)
@@ -1528,6 +1612,13 @@ ui_chat_new(MESSENGER_Application *app)
);
g_signal_connect(
+ handle->selection_tag_button,
+ "clicked",
+ G_CALLBACK(handle_chat_selection_tag_button_click),
+ handle
+ );
+
+ g_signal_connect(
handle->selection_delete_button,
"clicked",
G_CALLBACK(handle_chat_selection_delete_button_click),
@@ -2003,7 +2094,7 @@ ui_chat_add_message(UI_CHAT_Handle *handle,
void
ui_chat_remove_message(UI_CHAT_Handle *handle,
- UNUSED MESSENGER_Application *app,
+ MESSENGER_Application *app,
UI_MESSAGE_Handle *message)
{
g_assert((handle) && (message) && (message->message_box));
@@ -2016,7 +2107,7 @@ ui_chat_remove_message(UI_CHAT_Handle *handle,
if (parent == GTK_WIDGET(handle->messages_listbox))
gtk_container_remove(GTK_CONTAINER(handle->messages_listbox), row);
- ui_message_delete(message);
+ ui_message_delete(message, app);
}
void
diff --git a/src/ui/meson.build b/src/ui/meson.build
@@ -37,8 +37,10 @@ messenger_gtk_ui_sources = files([
'new_group.c', 'new_group.h',
'new_lobby.c', 'new_lobby.h',
'new_platform.c', 'new_platform.h',
+ 'new_tag.c', 'new_tag.h',
'picker.c', 'picker.h',
'play_media.c', 'play_media.h',
'send_file.c', 'send_file.h',
'settings.c', 'settings.h',
+ 'tag.c', 'tag.h',
])
diff --git a/src/ui/message.c b/src/ui/message.c
@@ -27,6 +27,8 @@
#include <gnunet/gnunet_chat_lib.h>
#include <gnunet/gnunet_common.h>
+#include "tag.h"
+
#include "../application.h"
#include "../contact.h"
#include "../file.h"
@@ -796,21 +798,42 @@ ui_message_add_tag(UI_MESSAGE_Handle *handle,
const char *tag_value = GNUNET_CHAT_message_get_text(tag_message);
- if ((!tag_value) || (!tag_value[0]))
+ if ((!tag_value) || (!*tag_value))
return;
- GtkLabel *tag_label = GTK_LABEL(gtk_label_new(NULL));
+ UI_TAG_Handle *tag = ui_tag_new(app);
+ ui_tag_set_message(tag, app, tag_message);
- if (!tag_label)
- return;
+ gtk_container_add(GTK_CONTAINER(handle->tag_flow_box), GTK_WIDGET(tag->tag_label));
+ gtk_widget_show_all(GTK_WIDGET(tag->tag_label));
+}
- ui_label_set_text(tag_label, tag_value);
- gtk_label_set_ellipsize(tag_label, PANGO_ELLIPSIZE_END);
+static void
+_remove_tag_from_message(UI_MESSAGE_Handle *handle,
+ MESSENGER_Application *app,
+ GtkWidget *child)
+{
+ g_assert((handle) && (app) && (child));
+
+ GList *items = gtk_container_get_children(GTK_CONTAINER(child));
+ UI_TAG_Handle *tag = NULL;
- g_object_set_qdata(G_OBJECT(tag_label), app->quarks.data, (gpointer) tag_message);
+ if (items)
+ {
+ GtkLabel *tag_label = GTK_LABEL(items->data);
- gtk_container_add(GTK_CONTAINER(handle->tag_flow_box), GTK_WIDGET(tag_label));
- gtk_widget_show_all(GTK_WIDGET(tag_label));
+ tag = g_object_get_qdata(
+ G_OBJECT(tag_label),
+ app->quarks.ui
+ );
+
+ g_list_free(items);
+ }
+
+ gtk_container_remove(GTK_CONTAINER(handle->tag_flow_box), child);
+
+ ui_tag_delete(tag);
+ gtk_widget_destroy(child);
}
void
@@ -858,22 +881,39 @@ ui_message_remove_tag(UI_MESSAGE_Handle *handle,
break;
}
- g_list_free(children);
+ if (children)
+ g_list_free(children);
if (!removable)
return;
- gtk_container_remove(GTK_CONTAINER(handle->tag_flow_box), removable);
- gtk_widget_destroy(removable);
+ _remove_tag_from_message(handle, app, removable);
}
void
-ui_message_delete(UI_MESSAGE_Handle *handle)
+ui_message_delete(UI_MESSAGE_Handle *handle,
+ MESSENGER_Application *app)
{
- g_assert(handle);
+ g_assert((handle) && (app));
ui_message_set_contact(handle, NULL);
+ GList *children = gtk_container_get_children(GTK_CONTAINER(handle->tag_flow_box));
+
+ GList *list = children;
+ while (list)
+ {
+ GtkWidget *child = GTK_WIDGET(list->data);
+
+ if (child)
+ _remove_tag_from_message(handle, app, child);
+
+ list = list->next;
+ }
+
+ if (children)
+ g_list_free(children);
+
_clear_message_preview_data(handle);
g_object_unref(handle->builder[1]);
diff --git a/src/ui/message.h b/src/ui/message.h
@@ -169,8 +169,10 @@ ui_message_remove_tag(UI_MESSAGE_Handle *handle,
* message handle.
*
* @param handle Message handle
+ * @param app Messenger application
*/
void
-ui_message_delete(UI_MESSAGE_Handle *handle);
+ui_message_delete(UI_MESSAGE_Handle *handle,
+ MESSENGER_Application *app);
#endif /* UI_MESSAGE_H_ */
diff --git a/src/ui/new_tag.c b/src/ui/new_tag.c
@@ -0,0 +1,210 @@
+/*
+ This file is part of GNUnet.
+ Copyright (C) 2024 GNUnet e.V.
+
+ GNUnet is free software: you can redistribute it and/or modify it
+ under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ GNUnet 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
+ Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+ SPDX-License-Identifier: AGPL3.0-or-later
+ */
+/*
+ * @author Tobias Frisch
+ * @file ui/new_tag.c
+ */
+
+#include "new_tag.h"
+
+#include "../application.h"
+#include "../ui.h"
+
+#include <gnunet/gnunet_common.h>
+
+static void
+_add_new_tag(MESSENGER_Application *app,
+ UI_NEW_TAG_Handle *handle)
+{
+ g_assert((app) && (handle));
+
+ char *tag = ui_entry_get_text(handle->tag_entry);
+
+ if (handle->callback)
+ handle->callback(
+ app,
+ handle->selected,
+ tag
+ );
+
+ if (tag)
+ GNUNET_free(tag);
+
+ gtk_window_close(GTK_WINDOW(handle->dialog));
+}
+
+static void
+handle_tag_entry_changed(GtkEditable *editable,
+ gpointer user_data)
+{
+ g_assert((editable) && (user_data));
+
+ HdyAvatar *avatar = HDY_AVATAR(user_data);
+ GtkEntry *entry = GTK_ENTRY(editable);
+
+ const gchar *text = gtk_entry_get_text(entry);
+
+ hdy_avatar_set_text(avatar, text);
+}
+
+static void
+handle_tag_entry_activate(UNUSED GtkEntry *entry,
+ gpointer user_data)
+{
+ g_assert(user_data);
+
+ MESSENGER_Application *app = (MESSENGER_Application*) user_data;
+
+ _add_new_tag(app, &(app->ui.new_tag));
+
+ gtk_window_close(GTK_WINDOW(app->ui.new_tag.dialog));
+}
+
+static void
+handle_cancel_button_click(UNUSED GtkButton *button,
+ gpointer user_data)
+{
+ g_assert(user_data);
+
+ GtkDialog *dialog = GTK_DIALOG(user_data);
+ gtk_window_close(GTK_WINDOW(dialog));
+}
+
+static void
+handle_confirm_button_click(UNUSED GtkButton *button,
+ gpointer user_data)
+{
+ g_assert(user_data);
+
+ MESSENGER_Application *app = (MESSENGER_Application*) user_data;
+
+ _add_new_tag(app, &(app->ui.new_tag));
+
+ gtk_window_close(GTK_WINDOW(app->ui.new_tag.dialog));
+}
+
+static void
+handle_dialog_destroy(UNUSED GtkWidget *window,
+ gpointer user_data)
+{
+ g_assert(user_data);
+
+ MESSENGER_Application *app = (MESSENGER_Application*) user_data;
+
+ ui_new_tag_dialog_cleanup(&(app->ui.new_tag));
+}
+
+void
+ui_new_tag_dialog_init(MESSENGER_Application *app,
+ UI_NEW_TAG_Handle *handle)
+{
+ g_assert((app) && (handle));
+
+ handle->selected = NULL;
+ handle->callback = NULL;
+
+ handle->builder = gtk_builder_new_from_resource(
+ application_get_resource_path(app, "ui/new_tag.ui")
+ );
+
+ handle->dialog = GTK_DIALOG(
+ gtk_builder_get_object(handle->builder, "new_tag_dialog")
+ );
+
+ gtk_window_set_transient_for(
+ GTK_WINDOW(handle->dialog),
+ GTK_WINDOW(app->ui.messenger.main_window)
+ );
+
+ handle->tag_avatar = HDY_AVATAR(
+ gtk_builder_get_object(handle->builder, "tag_avatar")
+ );
+
+ handle->tag_entry = GTK_ENTRY(
+ gtk_builder_get_object(handle->builder, "tag_entry")
+ );
+
+ g_signal_connect(
+ handle->tag_entry,
+ "changed",
+ G_CALLBACK(handle_tag_entry_changed),
+ handle->tag_avatar
+ );
+
+ g_signal_connect(
+ handle->tag_entry,
+ "activate",
+ G_CALLBACK(handle_tag_entry_activate),
+ app
+ );
+
+ handle->cancel_button = GTK_BUTTON(
+ gtk_builder_get_object(handle->builder, "cancel_button")
+ );
+
+ g_signal_connect(
+ handle->cancel_button,
+ "clicked",
+ G_CALLBACK(handle_cancel_button_click),
+ handle->dialog
+ );
+
+ handle->confirm_button = GTK_BUTTON(
+ gtk_builder_get_object(handle->builder, "confirm_button")
+ );
+
+ g_signal_connect(
+ handle->confirm_button,
+ "clicked",
+ G_CALLBACK(handle_confirm_button_click),
+ app
+ );
+
+ g_signal_connect(
+ handle->dialog,
+ "destroy",
+ G_CALLBACK(handle_dialog_destroy),
+ app
+ );
+}
+
+void
+ui_new_tag_dialog_link(UI_NEW_TAG_Handle *handle,
+ UI_NEW_TAG_Callback callback,
+ GList *selected)
+{
+ g_assert((handle) && (callback));
+
+ handle->selected = selected;
+ handle->callback = callback;
+}
+
+void
+ui_new_tag_dialog_cleanup(UI_NEW_TAG_Handle *handle)
+{
+ g_assert(handle);
+
+ if (handle->selected)
+ g_list_free(handle->selected);
+
+ g_object_unref(handle->builder);
+
+ memset(handle, 0, sizeof(*handle));
+}
diff --git a/src/ui/new_tag.h b/src/ui/new_tag.h
@@ -0,0 +1,87 @@
+/*
+ This file is part of GNUnet.
+ Copyright (C) 2024 GNUnet e.V.
+
+ GNUnet is free software: you can redistribute it and/or modify it
+ under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ GNUnet 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
+ Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+ SPDX-License-Identifier: AGPL3.0-or-later
+ */
+/*
+ * @author Tobias Frisch
+ * @file ui/new_tag.h
+ */
+
+#ifndef UI_NEW_TAG_H_
+#define UI_NEW_TAG_H_
+
+#include "messenger.h"
+
+typedef void
+(*UI_NEW_TAG_Callback) (
+ MESSENGER_Application *app,
+ GList *selected,
+ const char *tag
+);
+
+typedef struct UI_NEW_TAG_Handle
+{
+ GList *selected;
+
+ UI_NEW_TAG_Callback callback;
+
+ GtkBuilder *builder;
+ GtkDialog *dialog;
+
+ HdyAvatar *tag_avatar;
+ GtkEntry *tag_entry;
+
+ GtkButton *cancel_button;
+ GtkButton *confirm_button;
+} UI_NEW_TAG_Handle;
+
+/**
+ * Initializes a handle for the new tag dialog
+ * of a given messenger application.
+ *
+ * @param app Messenger application
+ * @param handle New tag dialog handle
+ */
+void
+ui_new_tag_dialog_init(MESSENGER_Application *app,
+ UI_NEW_TAG_Handle *handle);
+
+/**
+ * Links a custom list and a callback to a
+ * given new tag dialog which will be used
+ * to handle the event of tagging.
+ *
+ * @param handle New tag dialog handle
+ * @param callback New tag callback
+ * @param selected Selected messages
+ */
+void
+ui_new_tag_dialog_link(UI_NEW_TAG_Handle *handle,
+ UI_NEW_TAG_Callback callback,
+ GList *selected);
+
+/**
+ * Cleans up the allocated resources and resets the
+ * state of a given new tag dialog handle.
+ *
+ * @param handle New tag dialog handle
+ */
+void
+ui_new_tag_dialog_cleanup(UI_NEW_TAG_Handle *handle);
+
+#endif /* UI_NEW_TAG_H_ */
diff --git a/src/ui/tag.c b/src/ui/tag.c
@@ -0,0 +1,89 @@
+/*
+ This file is part of GNUnet.
+ Copyright (C) 2024 GNUnet e.V.
+
+ GNUnet is free software: you can redistribute it and/or modify it
+ under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ GNUnet 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
+ Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+ SPDX-License-Identifier: AGPL3.0-or-later
+ */
+/*
+ * @author Tobias Frisch
+ * @file ui/tag.c
+ */
+
+#include "tag.h"
+
+#include "../application.h"
+#include "../ui.h"
+
+UI_TAG_Handle*
+ui_tag_new(MESSENGER_Application *app)
+{
+ g_assert(app);
+
+ UI_TAG_Handle* handle = g_malloc(sizeof(UI_TAG_Handle));
+
+ memset(handle, 0, sizeof(*handle));
+
+ handle->builder = gtk_builder_new_from_resource(
+ application_get_resource_path(app, "ui/tag.ui")
+ );
+
+ handle->tag_label = GTK_LABEL(
+ gtk_builder_get_object(handle->builder, "tag_label")
+ );
+
+ g_object_set_qdata(
+ G_OBJECT(handle->tag_label),
+ app->quarks.ui,
+ handle
+ );
+
+ return handle;
+}
+
+void
+ui_tag_set_message(UI_TAG_Handle* handle,
+ MESSENGER_Application *app,
+ const struct GNUNET_CHAT_Message *message)
+{
+ g_assert((handle) && (message));
+
+ if (GNUNET_CHAT_KIND_TAG != GNUNET_CHAT_message_get_kind(message))
+ return;
+
+ const char *tag_value = GNUNET_CHAT_message_get_text(message);
+
+ GString *label = g_string_new("#");
+ g_string_append(label, tag_value);
+
+ ui_label_set_text(handle->tag_label, label->str);
+ g_string_free(label, TRUE);
+
+ g_object_set_qdata(
+ G_OBJECT(handle->tag_label),
+ app->quarks.data,
+ (gpointer) message
+ );
+}
+
+void
+ui_tag_delete(UI_TAG_Handle *handle)
+{
+ g_assert(handle);
+
+ g_object_unref(handle->builder);
+
+ g_free(handle);
+}
diff --git a/src/ui/tag.h b/src/ui/tag.h
@@ -0,0 +1,71 @@
+/*
+ This file is part of GNUnet.
+ Copyright (C) 2024 GNUnet e.V.
+
+ GNUnet is free software: you can redistribute it and/or modify it
+ under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ GNUnet 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
+ Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+ SPDX-License-Identifier: AGPL3.0-or-later
+ */
+/*
+ * @author Tobias Frisch
+ * @file ui/tag.h
+ */
+
+#ifndef UI_TAG_H_
+#define UI_TAG_H_
+
+#include "messenger.h"
+
+typedef struct UI_TAG_Handle
+{
+ GtkBuilder *builder;
+
+ GtkLabel *tag_label;
+} UI_TAG_Handle;
+
+/**
+ * Allocates and creates a new tag handle
+ * to manage a tag in a list for a given
+ * messenger application.
+ *
+ * @param app Messenger application
+ * @return New tag handle
+ */
+UI_TAG_Handle*
+ui_tag_new(MESSENGER_Application *app);
+
+/**
+ * Sets the content of the given tag handle
+ * respectively to visually represent a
+ * selected chat tag message.
+ *
+ * @param handle Tag handle
+ * @param app Messenger application
+ * @param message Chat tag message
+ */
+void
+ui_tag_set_message(UI_TAG_Handle* handle,
+ MESSENGER_Application *app,
+ const struct GNUNET_CHAT_Message *message);
+
+/**
+ * Frees its resources and destroys a given
+ * tag handle.
+ *
+ * @param handle Tag handle
+ */
+void
+ui_tag_delete(UI_TAG_Handle *handle);
+
+#endif /* UI_TAG_H_ */