messenger-gtk

Gtk+3 graphical user interfaces for GNUnet Messenger
Log | Files | Refs | Submodules | README | LICENSE

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:
Mresources/ui.gresource.xml | 1+
Mresources/ui/chat.ui | 50+++++++++++++++++++++++++++++++++++++++++++++++++-
Aresources/ui/file_entry.ui | 97+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/ui.c | 50++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/ui.h | 12++++++++++++
Msrc/ui/chat.c | 182+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
Msrc/ui/chat.h | 2++
Asrc/ui/file_entry.c | 80+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/ui/file_entry.h | 76++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/ui/meson.build | 1+
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',