commit e0b8f28cf38b9a73fa151f59479fe6bf98cb6f65
parent 993e76af5614faecfeef2865b4e12c503f96819e
Author: Jacki <jacki@thejackimonster.de>
Date: Sat, 23 Mar 2024 17:45:41 +0100
Add file entries to chat details
Signed-off-by: Jacki <jacki@thejackimonster.de>
Diffstat:
10 files changed, 505 insertions(+), 46 deletions(-)
diff --git a/resources/ui.gresource.xml b/resources/ui.gresource.xml
@@ -9,6 +9,7 @@
<file compressed="true">ui/contact_info.ui</file>
<file compressed="true">ui/contacts.ui</file>
<file compressed="true">ui/delete_messages.ui</file>
+ <file compressed="true">ui/file_entry.ui</file>
<file compressed="true">ui/file_load_entry.ui</file>
<file compressed="true">ui/invite_contact.ui</file>
<file compressed="true">ui/message_content.ui</file>
diff --git a/resources/ui/chat.ui b/resources/ui/chat.ui
@@ -1028,6 +1028,54 @@ Author: Tobias Frisch
</packing>
</child>
<child>
+ <object class="GtkBox" id="chat_details_media_box">
+ <property name="can-focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label" translatable="yes">Media</property>
+ <property name="xalign">0</property>
+ <attributes>
+ <attribute name="weight" value="semibold"/>
+ </attributes>
+ <style>
+ <class name="details-group-title"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFlowBox" id="chat_media_flowbox">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="homogeneous">True</property>
+ <property name="max-children-per-line">3</property>
+ <property name="selection-mode">none</property>
+ <property name="activate-on-single-click">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <style>
+ <class name="details-group"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">4</property>
+ </packing>
+ </child>
+ <child>
<object class="GtkBox" id="chat_details_files_box">
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
@@ -1067,7 +1115,7 @@ Author: Tobias Frisch
</style>
</object>
<packing>
- <property name="expand">True</property>
+ <property name="expand">False</property>
<property name="fill">True</property>
<property name="position">5</property>
</packing>
diff --git a/resources/ui/file_entry.ui b/resources/ui/file_entry.ui
@@ -0,0 +1,97 @@
+<?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="GtkBox" id="entry_box">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="border-width">4</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="border-width">8</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkImage" id="file_image">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="icon-name">folder-documents-symbolic</property>
+ <property name="icon_size">3</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </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>
+ <child>
+ <object class="GtkLabel" id="name_label">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="ellipsize">end</property>
+ <property name="single-line-mode">True</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="size_label">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="xalign">1</property>
+ <property name="yalign">1</property>
+ <attributes>
+ <attribute name="variant" value="all-small-caps"/>
+ </attributes>
+ </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>
+</interface>
diff --git a/src/ui.c b/src/ui.c
@@ -43,6 +43,56 @@ ui_label_set_text(GtkLabel *label, const char *text)
}
void
+ui_label_set_size(GtkLabel *label,
+ uint64_t size)
+{
+ g_assert(label);
+
+ GString* string = g_string_new(NULL);
+
+ if (size < 100)
+ g_string_printf(string, "%lu B", size);
+ else
+ {
+ char dim = 'K';
+ size = (size + 50) / 100;
+
+ while (size >= 1000)
+ {
+ size = (size + 500) / 1000;
+ switch (dim)
+ {
+ case 'K':
+ dim = 'M';
+ break;
+ case 'M':
+ dim = 'G';
+ break;
+ case 'G':
+ dim = 'T';
+ break;
+ case 'T':
+ dim = 'P';
+ break;
+ default:
+ break;
+ }
+ }
+
+ g_string_printf(
+ string,
+ "%lu.%lu %cB",
+ size / 10,
+ size % 10,
+ dim
+ );
+ }
+
+ gtk_label_set_text(label, string->str);
+ g_string_free(string, TRUE);
+}
+
+void
ui_entry_set_text(GtkEntry *entry, const char *text)
{
g_assert(entry);
diff --git a/src/ui.h b/src/ui.h
@@ -27,6 +27,7 @@
#include <gtk-3.0/gtk/gtk.h>
#include <libhandy-1/handy.h>
+#include <stdint.h>
/**
* Sets the text of a GtkLabel applying automatic utf8
@@ -40,6 +41,17 @@ ui_label_set_text(GtkLabel *label,
const char *text);
/**
+ * Sets the text of a GtkLabel applying conversion from
+ * file size to string representation.
+ *
+ * @param label Label
+ * @param size File size
+ */
+void
+ui_label_set_size(GtkLabel *label,
+ uint64_t size);
+
+/**
* Sets the text of a GtkEntry applying automatic utf8
* conversion.
*
diff --git a/src/ui/chat.c b/src/ui/chat.c
@@ -26,10 +26,12 @@
#include <gdk/gdkkeysyms.h>
#include <gnunet/gnunet_chat_lib.h>
+#include <gnunet/gnunet_common.h>
#include <gnunet/gnunet_time_lib.h>
#include <stdlib.h>
#include "chat_entry.h"
+#include "file_entry.h"
#include "file_load_entry.h"
#include "message.h"
#include "messenger.h"
@@ -1487,6 +1489,10 @@ ui_chat_new(MESSENGER_Application *app)
gtk_builder_get_object(handle->builder, "chat_details_files_box")
);
+ handle->chat_details_media_box = GTK_BOX(
+ gtk_builder_get_object(handle->builder, "chat_details_media_box")
+ );
+
handle->chat_details_avatar = HDY_AVATAR(
gtk_builder_get_object(handle->builder, "chat_details_avatar")
);
@@ -1578,6 +1584,10 @@ ui_chat_new(MESSENGER_Application *app)
gtk_builder_get_object(handle->builder, "chat_files_listbox")
);
+ handle->chat_media_flowbox = GTK_FLOW_BOX(
+ gtk_builder_get_object(handle->builder, "chat_media_flowbox")
+ );
+
handle->messages_listbox = GTK_LIST_BOX(
gtk_builder_get_object(handle->builder, "messages_listbox")
);
@@ -1845,18 +1855,18 @@ ui_chat_new(MESSENGER_Application *app)
return handle;
}
-struct IterateChatGroupClosure {
+struct IterateChatClosure {
MESSENGER_Application *app;
GtkListBox *listbox;
};
-static int
+static enum GNUNET_GenericReturnValue
iterate_ui_chat_update_group_contacts(void *cls,
UNUSED const struct GNUNET_CHAT_Group *group,
struct GNUNET_CHAT_Contact *contact)
{
- struct IterateChatGroupClosure *closure = (
- (struct IterateChatGroupClosure*) cls
+ struct IterateChatClosure *closure = (
+ (struct IterateChatClosure*) cls
);
GtkListBox *listbox = closure->listbox;
@@ -1881,6 +1891,121 @@ iterate_ui_chat_update_group_contacts(void *cls,
return GNUNET_YES;
}
+static void
+_chat_update_contacts(UI_CHAT_Handle *handle,
+ MESSENGER_Application *app,
+ struct GNUNET_CHAT_Group* group)
+{
+ g_assert((handle) && (app));
+
+ GList* children = gtk_container_get_children(
+ GTK_CONTAINER(handle->chat_contacts_listbox)
+ );
+
+ GList *item = children;
+ while ((item) && (item->next)) {
+ GtkWidget *widget = GTK_WIDGET(item->data);
+ item = item->next;
+
+ gtk_container_remove(
+ GTK_CONTAINER(handle->chat_contacts_listbox),
+ widget
+ );
+ }
+
+ if (children)
+ g_list_free(children);
+
+ if (group)
+ {
+ struct IterateChatClosure closure;
+ closure.app = app;
+ closure.listbox = handle->chat_contacts_listbox;
+
+ GNUNET_CHAT_group_iterate_contacts(
+ group,
+ iterate_ui_chat_update_group_contacts,
+ &closure
+ );
+ }
+
+ gtk_widget_set_visible(
+ GTK_WIDGET(handle->chat_details_contacts_box),
+ group? TRUE : FALSE
+ );
+}
+
+static enum GNUNET_GenericReturnValue
+iterate_ui_chat_update_context_files(void *cls,
+ struct GNUNET_CHAT_Context *context,
+ struct GNUNET_CHAT_File *file)
+{
+ struct IterateChatClosure *closure = (
+ (struct IterateChatClosure*) cls
+ );
+
+ GtkListBox *listbox = closure->listbox;
+ UI_FILE_ENTRY_Handle* entry = ui_file_entry_new(closure->app);
+ ui_file_entry_update(entry, file);
+
+ gtk_list_box_prepend(listbox, entry->entry_box);
+
+ GtkListBoxRow *row = GTK_LIST_BOX_ROW(
+ gtk_widget_get_parent(entry->entry_box)
+ );
+
+ g_object_set_qdata(G_OBJECT(row), closure->app->quarks.data, file);
+ g_object_set_qdata_full(
+ G_OBJECT(row),
+ closure->app->quarks.ui,
+ entry,
+ (GDestroyNotify) ui_file_entry_delete
+ );
+
+ return GNUNET_YES;
+}
+
+static void
+_chat_update_files(UI_CHAT_Handle *handle,
+ MESSENGER_Application *app,
+ struct GNUNET_CHAT_Context *context)
+{
+ g_assert((handle) && (app) && (context));
+
+ GList* children = gtk_container_get_children(
+ GTK_CONTAINER(handle->chat_files_listbox)
+ );
+
+ GList *item = children;
+ while (item) {
+ GtkWidget *widget = GTK_WIDGET(item->data);
+ item = item->next;
+
+ gtk_container_remove(
+ GTK_CONTAINER(handle->chat_files_listbox),
+ widget
+ );
+ }
+
+ if (children)
+ g_list_free(children);
+
+ struct IterateChatClosure closure;
+ closure.app = app;
+ closure.listbox = handle->chat_files_listbox;
+
+ const int count = GNUNET_CHAT_context_iterate_files(
+ context,
+ iterate_ui_chat_update_context_files,
+ &closure
+ );
+
+ gtk_widget_set_visible(
+ GTK_WIDGET(handle->chat_details_files_box),
+ count? TRUE : FALSE
+ );
+}
+
void
ui_chat_update(UI_CHAT_Handle *handle,
MESSENGER_Application *app,
@@ -1934,41 +2059,8 @@ ui_chat_update(UI_CHAT_Handle *handle,
g_string_free(subtitle, TRUE);
- GList* children = gtk_container_get_children(
- GTK_CONTAINER(handle->chat_contacts_listbox)
- );
-
- GList *item = children;
- while ((item) && (item->next)) {
- GtkWidget *widget = GTK_WIDGET(item->data);
- item = item->next;
-
- gtk_container_remove(
- GTK_CONTAINER(handle->chat_contacts_listbox),
- widget
- );
- }
-
- if (children)
- g_list_free(children);
-
- if (group)
- {
- struct IterateChatGroupClosure closure;
- closure.app = app;
- closure.listbox = handle->chat_contacts_listbox;
-
- GNUNET_CHAT_group_iterate_contacts(
- group,
- iterate_ui_chat_update_group_contacts,
- &closure
- );
- }
-
- gtk_widget_set_visible(
- GTK_WIDGET(handle->chat_details_contacts_box),
- group? TRUE : FALSE
- );
+ _chat_update_contacts(handle, app, group);
+ _chat_update_files(handle, app, context);
g_object_set_qdata(
G_OBJECT(handle->reveal_identity_button),
@@ -2118,13 +2210,13 @@ ui_chat_remove_message(UI_CHAT_Handle *handle,
void
ui_chat_add_file_load(UI_CHAT_Handle *handle,
- UI_FILE_LOAD_ENTRY_Handle *file_load)
+ UI_FILE_LOAD_ENTRY_Handle *file_load)
{
g_assert((handle) && (file_load));
gtk_container_add(
- GTK_CONTAINER(handle->chat_load_listbox),
- file_load->entry_box
+ GTK_CONTAINER(handle->chat_load_listbox),
+ file_load->entry_box
);
handle->loads = g_list_append(handle->loads, file_load);
@@ -2136,7 +2228,7 @@ ui_chat_add_file_load(UI_CHAT_Handle *handle,
void
ui_chat_remove_file_load(UI_CHAT_Handle *handle,
- UI_FILE_LOAD_ENTRY_Handle *file_load)
+ UI_FILE_LOAD_ENTRY_Handle *file_load)
{
g_assert((handle) && (file_load) && (handle == file_load->chat) &&
(file_load->entry_box));
@@ -2144,8 +2236,8 @@ ui_chat_remove_file_load(UI_CHAT_Handle *handle,
handle->loads = g_list_remove(handle->loads, file_load);
gtk_container_remove(
- GTK_CONTAINER(handle->chat_load_listbox),
- gtk_widget_get_parent(file_load->entry_box)
+ GTK_CONTAINER(handle->chat_load_listbox),
+ gtk_widget_get_parent(file_load->entry_box)
);
if (handle->loads)
diff --git a/src/ui/chat.h b/src/ui/chat.h
@@ -97,6 +97,7 @@ typedef struct UI_CHAT_Handle
GtkButton *hide_chat_details_button;
GtkBox *chat_details_contacts_box;
GtkBox *chat_details_files_box;
+ GtkBox *chat_details_media_box;
HdyAvatar *chat_details_avatar;
@@ -117,6 +118,7 @@ typedef struct UI_CHAT_Handle
GtkListBox *chat_contacts_listbox;
GtkListBox *chat_files_listbox;
+ GtkFlowBox *chat_media_flowbox;
GtkListBox *messages_listbox;
GtkStack *send_stack;
diff --git a/src/ui/file_entry.c b/src/ui/file_entry.c
@@ -0,0 +1,80 @@
+/*
+ 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/file_entry.c
+ */
+
+#include "file_entry.h"
+
+#include "../application.h"
+#include "../ui.h"
+#include <gnunet/gnunet_chat_lib.h>
+#include <stdint.h>
+
+UI_FILE_ENTRY_Handle*
+ui_file_entry_new(MESSENGER_Application *app)
+{
+ g_assert(app);
+
+ UI_FILE_ENTRY_Handle* handle = g_malloc(sizeof(UI_FILE_ENTRY_Handle));
+
+ handle->builder = gtk_builder_new_from_resource(
+ application_get_resource_path(app, "ui/file_entry.ui")
+ );
+
+ handle->entry_box = GTK_WIDGET(
+ gtk_builder_get_object(handle->builder, "entry_box")
+ );
+
+ handle->file_image = GTK_IMAGE(
+ gtk_builder_get_object(handle->builder, "file_image")
+ );
+
+ handle->name_label = GTK_LABEL(
+ gtk_builder_get_object(handle->builder, "name_label")
+ );
+
+ handle->size_label = GTK_LABEL(
+ gtk_builder_get_object(handle->builder, "size_label")
+ );
+
+ return handle;
+}
+
+void
+ui_file_entry_update(UI_FILE_ENTRY_Handle *handle,
+ struct GNUNET_CHAT_File *file)
+{
+ g_assert((handle) && (file));
+
+ ui_label_set_text(handle->name_label, GNUNET_CHAT_file_get_name(file));
+ ui_label_set_size(handle->size_label, GNUNET_CHAT_file_get_size(file));
+}
+
+void
+ui_file_entry_delete(UI_FILE_ENTRY_Handle *handle)
+{
+ g_assert(handle);
+
+ g_object_unref(handle->builder);
+
+ g_free(handle);
+}
diff --git a/src/ui/file_entry.h b/src/ui/file_entry.h
@@ -0,0 +1,76 @@
+/*
+ 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/file_entry.h
+ */
+
+#ifndef UI_FILE_ENTRY_H_
+#define UI_FILE_ENTRY_H_
+
+#include <gnunet/gnunet_chat_lib.h>
+
+#include "messenger.h"
+
+typedef struct UI_CHAT_Handle UI_CHAT_Handle;
+
+typedef struct UI_FILE_ENTRY_Handle
+{
+ GtkBuilder *builder;
+
+ GtkWidget *entry_box;
+
+ GtkImage *file_image;
+ GtkLabel *name_label;
+ GtkLabel *size_label;
+} UI_FILE_ENTRY_Handle;
+
+/**
+ * Allocates and creates a new file entry handle
+ * to manage loading files for a given messenger
+ * application.
+ *
+ * @param app Messenger application
+ * @return New file entry handle
+ */
+UI_FILE_ENTRY_Handle*
+ui_file_entry_new(MESSENGER_Application *app);
+
+/**
+ * Updates a file entry handle with a selected
+ * file to represent it visually.
+ *
+ * @param handle File entry handle
+ * @param file Chat file
+ */
+void
+ui_file_entry_update(UI_FILE_ENTRY_Handle *handle,
+ struct GNUNET_CHAT_File *file);
+
+/**
+ * Frees its resources and destroys a given file
+ * entry handle.
+ *
+ * @param handle File entry handle
+ */
+void
+ui_file_entry_delete(UI_FILE_ENTRY_Handle *handle);
+
+#endif /* UI_FILE_ENTRY_H_ */
diff --git a/src/ui/meson.build b/src/ui/meson.build
@@ -28,6 +28,7 @@ messenger_gtk_ui_sources = files([
'contact_info.c', 'contact_info.h',
'contacts.c', 'contacts.h',
'delete_messages.c', 'delete_messages.h',
+ 'file_entry.c', 'file_entry.h',
'file_load_entry.c', 'file_load_entry.h',
'invite_contact.c', 'invite_contact.h',
'message.c', 'message.h',