messenger-gtk

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

commit ae8fc013b2f0879876cbe8f37634d6096d9053cd
parent 8884332b64fbcfd5971112301da5f4f99abe56fc
Author: Jacki <jacki@thejackimonster.de>
Date:   Sun, 21 Apr 2024 01:20:48 +0200

Implement dialog to list all files

Signed-off-by: Jacki <jacki@thejackimonster.de>

Diffstat:
Mresources/ui.gresource.xml | 1+
Mresources/ui/contacts.ui | 2+-
Aresources/ui/files.ui | 244+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mresources/ui/settings.ui | 15+++++++++++++++
Msrc/application.h | 2++
Msrc/ui/accounts.c | 3++-
Msrc/ui/contacts.c | 5+++--
Asrc/ui/files.c | 300+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/ui/files.h | 75+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/ui/meson.build | 1+
Msrc/ui/settings.c | 41++++++++++++++++++++++++++++++++++++++---
Msrc/ui/settings.h | 3+++
12 files changed, 685 insertions(+), 7 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/files.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> diff --git a/resources/ui/contacts.ui b/resources/ui/contacts.ui @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8"?> -<!-- Generated with glade 3.38.2 +<!-- Generated with glade 3.40.0 Copyright (C) 2021‑‑2022 GNUnet e.V. diff --git a/resources/ui/files.ui b/resources/ui/files.ui @@ -0,0 +1,244 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Generated with glade 3.40.0 --> +<interface> + <requires lib="gtk+" version="3.24"/> + <object class="GtkDialog" id="files_dialog"> + <property name="can-focus">False</property> + <property name="title" translatable="yes">Files</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="back_button"> + <property name="label" translatable="yes">Back</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="close_button"> + <property name="label" translatable="yes">Close</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="GtkStack" id="dialog_stack"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <child> + <object class="GtkBox" id="list_box"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkSearchEntry" id="file_search_entry"> + <property name="width-request">250</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="primary-icon-name">edit-find-symbolic</property> + <property name="primary-icon-activatable">False</property> + <property name="primary-icon-sensitive">False</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkScrolledWindow"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="shadow-type">in</property> + <child> + <object class="GtkViewport"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <child> + <object class="GtkListBox" id="files_listbox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="selection-mode">none</property> + </object> + </child> + </object> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="pack-type">end</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="name">files_list_page</property> + </packing> + </child> + <child> + <object class="GtkBox" id="info_box"> + <property name="width-request">240</property> + <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">12</property> + <property name="orientation">vertical</property> + <property name="spacing">8</property> + <child> + <object class="GtkImage"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="pixel-size">64</property> + <property name="icon-name">emblem-documents-symbolic</property> + <property name="icon_size">3</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="name_label"> + <property name="visible">True</property> + <property name="can-focus">False</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkProgressBar" id="storage_progress_bar"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="show-text">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">2</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="spacing">2</property> + <child> + <object class="GtkButton" id="delete_file_button"> + <property name="visible">True</property> + <property name="sensitive">False</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <child> + <object class="GtkImage"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="icon-name">user-trash-symbolic</property> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkButton" id="play_pause_button"> + <property name="visible">True</property> + <property name="sensitive">False</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <child> + <object class="GtkStack" id="play_icon_stack"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <child> + <object class="GtkImage" id="play_icon_image"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="icon-name">media-playback-start-symbolic</property> + </object> + <packing> + <property name="name">play_icon_page</property> + </packing> + </child> + <child> + <object class="GtkImage" id="pause_icon_image"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="icon-name">media-playback-pause-symbolic</property> + </object> + <packing> + <property name="name">page0</property> + <property name="title" translatable="yes">page0</property> + <property name="position">1</property> + </packing> + </child> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">3</property> + </packing> + </child> + </object> + <packing> + <property name="name">file_info_page</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + </object> + </child> + </object> +</interface> diff --git a/resources/ui/settings.ui b/resources/ui/settings.ui @@ -816,6 +816,7 @@ Author: Tobias Frisch <object class="GtkBox"> <property name="visible">True</property> <property name="can-focus">False</property> + <property name="spacing">2</property> <child> <object class="GtkButton" id="delete_files_button"> <property name="label" translatable="yes">Delete all files</property> @@ -831,6 +832,20 @@ Author: Tobias Frisch <property name="expand">False</property> <property name="fill">True</property> <property name="pack-type">end</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkButton" id="show_files_button"> + <property name="label" translatable="yes">Show all files</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="pack-type">end</property> <property name="position">1</property> </packing> </child> diff --git a/src/application.h b/src/application.h @@ -41,6 +41,7 @@ #include "ui/contact_info.h" #include "ui/contacts.h" #include "ui/delete_messages.h" +#include "ui/files.h" #include "ui/invite_contact.h" #include "ui/messenger.h" #include "ui/new_account.h" @@ -133,6 +134,7 @@ typedef struct MESSENGER_Application UI_NEW_TAG_Handle new_tag; UI_ACCOUNTS_Handle accounts; + UI_FILES_Handle files; UI_CONTACTS_Handle contacts; UI_SETTINGS_Handle settings; } ui; diff --git a/src/ui/accounts.c b/src/ui/accounts.c @@ -245,7 +245,8 @@ ui_accounts_dialog_cleanup(UI_ACCOUNTS_Handle *handle) { g_assert(handle); - g_object_unref(handle->builder); + if (handle->builder) + g_object_unref(handle->builder); guint show = handle->show_queued; memset(handle, 0, sizeof(*handle)); diff --git a/src/ui/contacts.c b/src/ui/contacts.c @@ -141,7 +141,7 @@ handle_dialog_destroy(UNUSED GtkWidget *window, ui_contacts_dialog_cleanup((UI_CONTACTS_Handle*) user_data); } -static int +static enum GNUNET_GenericReturnValue _iterate_contacts(void *cls, UNUSED struct GNUNET_CHAT_Handle *handle, struct GNUNET_CHAT_Contact *contact) @@ -257,7 +257,8 @@ ui_contacts_dialog_cleanup(UI_CONTACTS_Handle *handle) { g_assert(handle); - g_object_unref(handle->builder); + if (handle->builder) + g_object_unref(handle->builder); memset(handle, 0, sizeof(*handle)); } diff --git a/src/ui/files.c b/src/ui/files.c @@ -0,0 +1,300 @@ +/* + 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/files.c + */ + +#include "files.h" + +#include "file_entry.h" +#include "../application.h" +#include "../ui.h" +#include <gnunet/gnunet_common.h> + +static void +handle_back_button_click(GtkButton *button, + gpointer user_data) +{ + g_assert(user_data); + + UI_FILES_Handle *handle = (UI_FILES_Handle*) user_data; + + gtk_stack_set_visible_child(handle->dialog_stack, handle->list_box); + gtk_widget_set_visible(GTK_WIDGET(button), false); +} + +static void +handle_close_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_files_listbox_row_activated(UNUSED GtkListBox* listbox, + GtkListBoxRow* row, + gpointer user_data) +{ + g_assert((row) && (user_data)); + + MESSENGER_Application *app = (MESSENGER_Application*) user_data; + + if (!gtk_list_box_row_get_selectable(row)) + return; + + UI_FILES_Handle *handle = &(app->ui.files); + + UI_FILE_ENTRY_Handle *entry = (UI_FILE_ENTRY_Handle*) ( + g_object_get_qdata(G_OBJECT(row), app->quarks.ui) + ); + + const gchar *name = gtk_label_get_text(entry->name_label); + const gchar *size = gtk_label_get_text(entry->size_label); + + gtk_label_set_text(handle->name_label, name); + gtk_progress_bar_set_text(handle->storage_progress_bar, size); + + gtk_stack_set_visible_child(handle->dialog_stack, handle->info_box); + gtk_widget_set_visible(GTK_WIDGET(handle->back_button), true); +} + +static gboolean +handle_files_listbox_filter_func(GtkListBoxRow *row, + gpointer user_data) +{ + g_assert((row) && (user_data)); + + MESSENGER_Application *app = (MESSENGER_Application*) user_data; + + if (!gtk_list_box_row_get_selectable(row)) + return TRUE; + + const gchar *filter = gtk_entry_get_text( + GTK_ENTRY(app->ui.files.file_search_entry) + ); + + if (!filter) + return TRUE; + + UI_FILE_ENTRY_Handle *entry = (UI_FILE_ENTRY_Handle*) ( + g_object_get_qdata(G_OBJECT(row), app->quarks.ui) + ); + + if (!entry) + return FALSE; + + const gchar *name = gtk_label_get_text(entry->name_label); + + if (!name) + return FALSE; + + return g_str_match_string(filter, name, TRUE); +} + +static void +handle_file_search_entry_search_changed(UNUSED GtkSearchEntry* search_entry, + gpointer user_data) +{ + g_assert(user_data); + + GtkListBox *listbox = GTK_LIST_BOX(user_data); + + gtk_list_box_invalidate_filter(listbox); +} + +static void +handle_dialog_destroy(UNUSED GtkWidget *window, + gpointer user_data) +{ + g_assert(user_data); + + ui_files_dialog_cleanup((UI_FILES_Handle*) user_data); +} + +static enum GNUNET_GenericReturnValue +_iterate_files(void *cls, + UNUSED struct GNUNET_CHAT_Handle *handle, + struct GNUNET_CHAT_File *file) +{ + g_assert((cls) && (file)); + + MESSENGER_Application *app = (MESSENGER_Application*) cls; + GtkListBox *listbox = app->ui.files.files_listbox; + + UI_FILE_ENTRY_Handle* entry = ui_file_entry_new(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), app->quarks.data, file); + g_object_set_qdata_full( + G_OBJECT(row), + app->quarks.ui, + entry, + (GDestroyNotify) ui_file_entry_delete + ); + + return GNUNET_YES; +} + +void +ui_files_dialog_init(MESSENGER_Application *app, + UI_FILES_Handle *handle) +{ + g_assert((app) && (handle)); + + handle->builder = ui_builder_from_resource( + application_get_resource_path(app, "ui/files.ui") + ); + + handle->dialog = GTK_DIALOG( + gtk_builder_get_object(handle->builder, "files_dialog") + ); + + gtk_window_set_transient_for( + GTK_WINDOW(handle->dialog), + GTK_WINDOW(app->ui.messenger.main_window) + ); + + handle->dialog_stack = GTK_STACK( + gtk_builder_get_object(handle->builder, "dialog_stack") + ); + + handle->list_box = GTK_WIDGET( + gtk_builder_get_object(handle->builder, "list_box") + ); + + handle->info_box = GTK_WIDGET( + gtk_builder_get_object(handle->builder, "info_box") + ); + + handle->file_search_entry = GTK_SEARCH_ENTRY( + gtk_builder_get_object(handle->builder, "file_search_entry") + ); + + handle->files_listbox = GTK_LIST_BOX( + gtk_builder_get_object(handle->builder, "files_listbox") + ); + + gtk_list_box_set_filter_func( + handle->files_listbox, + handle_files_listbox_filter_func, + app, + NULL + ); + + g_signal_connect( + handle->file_search_entry, + "search-changed", + G_CALLBACK(handle_file_search_entry_search_changed), + handle->files_listbox + ); + + g_signal_connect( + handle->files_listbox, + "row-activated", + G_CALLBACK(handle_files_listbox_row_activated), + app + ); + + handle->name_label = GTK_LABEL( + gtk_builder_get_object(handle->builder, "name_label") + ); + + handle->storage_progress_bar = GTK_PROGRESS_BAR( + gtk_builder_get_object(handle->builder, "storage_progress_bar") + ); + + handle->delete_file_button = GTK_BUTTON( + gtk_builder_get_object(handle->builder, "delete_file_button") + ); + + handle->play_pause_button = GTK_BUTTON( + gtk_builder_get_object(handle->builder, "play_pause_button") + ); + + handle->play_icon_stack = GTK_STACK( + gtk_builder_get_object(handle->builder, "play_icon_stack") + ); + + handle->play_icon_image = GTK_WIDGET( + gtk_builder_get_object(handle->builder, "play_icon_image") + ); + + handle->pause_icon_image = GTK_WIDGET( + gtk_builder_get_object(handle->builder, "pause_icon_image") + ); + + handle->back_button = GTK_BUTTON( + gtk_builder_get_object(handle->builder, "back_button") + ); + + g_signal_connect( + handle->back_button, + "clicked", + G_CALLBACK(handle_back_button_click), + handle + ); + + handle->close_button = GTK_BUTTON( + gtk_builder_get_object(handle->builder, "close_button") + ); + + g_signal_connect( + handle->close_button, + "clicked", + G_CALLBACK(handle_close_button_click), + handle->dialog + ); + + g_signal_connect( + handle->dialog, + "destroy", + G_CALLBACK(handle_dialog_destroy), + handle + ); + + GNUNET_CHAT_iterate_files( + app->chat.messenger.handle, + _iterate_files, + app + ); + + gtk_list_box_invalidate_filter(handle->files_listbox); +} + +void +ui_files_dialog_cleanup(UI_FILES_Handle *handle) +{ + g_assert(handle); + + if (handle->builder) + g_object_unref(handle->builder); + + memset(handle, 0, sizeof(*handle)); +} diff --git a/src/ui/files.h b/src/ui/files.h @@ -0,0 +1,75 @@ +/* + 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/files.h + */ + +#ifndef UI_FILES_H_ +#define UI_FILES_H_ + +#include "messenger.h" + +typedef struct UI_FILES_Handle +{ + GtkBuilder *builder; + GtkDialog *dialog; + + GtkStack *dialog_stack; + GtkWidget *list_box; + GtkWidget *info_box; + + GtkSearchEntry *file_search_entry; + GtkListBox *files_listbox; + + GtkLabel *name_label; + GtkProgressBar *storage_progress_bar; + GtkButton *delete_file_button; + GtkButton *play_pause_button; + + GtkStack *play_icon_stack; + GtkWidget *play_icon_image; + GtkWidget *pause_icon_image; + + GtkButton *back_button; + GtkButton *close_button; +} UI_FILES_Handle; + +/** + * Initializes a handle for the files dialog + * of a given messenger application. + * + * @param app Messenger application + * @param handle Files dialog handle + */ +void +ui_files_dialog_init(MESSENGER_Application *app, + UI_FILES_Handle *handle); + +/** + * Cleans up the allocated resources and resets the + * state of a given files dialog handle. + * + * @param handle Files dialog handle + */ +void +ui_files_dialog_cleanup(UI_FILES_Handle *handle); + +#endif /* UI_FILES_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', + 'files.c', 'files.h', 'file_entry.c', 'file_entry.h', 'file_load_entry.c', 'file_load_entry.h', 'invite_contact.c', 'invite_contact.h', diff --git a/src/ui/settings.c b/src/ui/settings.c @@ -29,6 +29,7 @@ #include "../ui.h" #include "contact_entry.h" +#include "files.h" #include <gnunet/gnunet_chat_lib.h> #include <gnunet/gnunet_common.h> @@ -174,12 +175,34 @@ handle_leave_chats_button_click(UNUSED GtkButton* button, } static void +handle_show_files_button_click(UNUSED GtkButton* button, + gpointer user_data) +{ + g_assert(user_data); + + UI_SETTINGS_Handle *handle = (UI_SETTINGS_Handle*) user_data; + + handle->open_files = true; + + gtk_window_close(GTK_WINDOW(handle->dialog)); +} + +static void handle_dialog_destroy(UNUSED GtkWidget *window, gpointer user_data) { g_assert(user_data); - ui_settings_dialog_cleanup((UI_SETTINGS_Handle*) user_data); + MESSENGER_Application *app = (MESSENGER_Application*) user_data; + UI_SETTINGS_Handle *handle = &(app->ui.settings); + + if (handle->open_files) + { + ui_files_dialog_init(app, &(app->ui.files)); + gtk_widget_show(GTK_WIDGET(app->ui.files.dialog)); + } + + ui_settings_dialog_cleanup(handle); } static void @@ -509,6 +532,17 @@ ui_settings_dialog_init(MESSENGER_Application *app, &(app->settings.delete_files_delay) ); + handle->show_files_button = GTK_BUTTON( + gtk_builder_get_object(handle->builder, "show_files_button") + ); + + g_signal_connect( + handle->show_files_button, + "clicked", + G_CALLBACK(handle_show_files_button_click), + handle + ); + handle->delete_files_button = GTK_BUTTON( gtk_builder_get_object(handle->builder, "delete_files_button") ); @@ -544,7 +578,7 @@ ui_settings_dialog_init(MESSENGER_Application *app, handle->dialog, "destroy", G_CALLBACK(handle_dialog_destroy), - handle + app ); } @@ -553,7 +587,8 @@ ui_settings_dialog_cleanup(UI_SETTINGS_Handle *handle) { g_assert(handle); - g_object_unref(handle->builder); + if (handle->builder) + g_object_unref(handle->builder); memset(handle, 0, sizeof(*handle)); } diff --git a/src/ui/settings.h b/src/ui/settings.h @@ -51,10 +51,13 @@ typedef struct UI_SETTINGS_Handle GtkSwitch *auto_accept_files_switch; GtkFileChooserButton *download_folder_button; GtkComboBox *delete_files_combo_box; + GtkButton *show_files_button; GtkButton *delete_files_button; GtkComboBox *leave_chats_combo_box; GtkButton *leave_chats_button; + + gboolean open_files; } UI_SETTINGS_Handle; /**