messenger-gtk

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

commit 56a206cd17e1caf23fbfd679c39752e5e194f35d
parent 83bf19ca1bc25c2162fa7e1d9f06644dd7e6b267
Author: TheJackiMonster <thejackimonster@gmail.com>
Date:   Sun, 14 Nov 2021 17:31:52 +0100

Implemented multiple chats in parallel

Signed-off-by: TheJackiMonster <thejackimonster@gmail.com>

Diffstat:
MMakefile | 1+
Aresources/ui/chat.ui | 268+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mresources/ui/messenger.ui | 250+------------------------------------------------------------------------------
Msrc/application.c | 4+++-
Msrc/event.c | 86+++++++++++++++++++++++++++++++++++++------------------------------------------
Asrc/ui/chat.c | 249+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/ui/chat.h | 59+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/ui/chat_entry.c | 14+++++++++++++-
Msrc/ui/chat_entry.h | 12+++++++-----
Msrc/ui/messenger.c | 215++++++++++---------------------------------------------------------------------
Msrc/ui/messenger.h | 17++++-------------
11 files changed, 672 insertions(+), 503 deletions(-)

diff --git a/Makefile b/Makefile @@ -7,6 +7,7 @@ SOURCES = messenger_gtk.c\ application.c\ event.c\ chat/messenger.c\ + ui/chat.c\ ui/chat_entry.c\ ui/message.c\ ui/messenger.c\ diff --git a/resources/ui/chat.ui b/resources/ui/chat.ui @@ -0,0 +1,268 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Generated with glade 3.38.2 + +Copyright (C) 2021 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="chat_box"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkBox" id="chat-header-box"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="margin-start">8</property> + <property name="margin-end">8</property> + <property name="margin-top">8</property> + <property name="margin-bottom">8</property> + <property name="spacing">8</property> + <child> + <object class="GtkButton" id="back_button"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <property name="relief">none</property> + <child> + <object class="GtkImage"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="icon-name">go-previous-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="GtkBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="halign">start</property> + <property name="margin-start">4</property> + <property name="margin-end">4</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkLabel" id="chat_title"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="label" translatable="yes">Chat title</property> + <property name="xalign">0</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="chat_subtitle"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="label" translatable="yes">Chat subtitle</property> + <property name="xalign">0</property> + <attributes> + <attribute name="weight" value="light"/> + </attributes> + </object> + <packing> + <property name="expand">True</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> + <child> + <object class="GtkButton" id="chat_details_button"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <property name="relief">none</property> + <child> + <object class="GtkImage"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="icon-name">view-more-symbolic</property> + </object> + </child> + </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> + </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> + <child> + <object class="GtkViewport"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <child> + <object class="GtkStack"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <child> + <object class="GtkListBox" id="messages_listbox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="selection-mode">none</property> + <property name="activate-on-single-click">False</property> + </object> + <packing> + <property name="name">page0</property> + <property name="title" translatable="yes">page0</property> + </packing> + </child> + </object> + </child> + </object> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="margin-start">8</property> + <property name="margin-end">8</property> + <property name="margin-top">4</property> + <property name="margin-bottom">4</property> + <property name="spacing">4</property> + <child> + <object class="GtkButton" id="attach_file_button"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <property name="valign">center</property> + <property name="relief">none</property> + <child> + <object class="GtkImage"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="icon-name">mail-attachment-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="GtkTextView" id="send_text_view"> + <property name="width-request">210</property> + <property name="height-request">48</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="valign">end</property> + <property name="wrap-mode">word-char</property> + <property name="left-margin">8</property> + <property name="right-margin">8</property> + <property name="top-margin">8</property> + <property name="bottom-margin">8</property> + <property name="input-hints">GTK_INPUT_HINT_SPELLCHECK | GTK_INPUT_HINT_WORD_COMPLETION | GTK_INPUT_HINT_INHIBIT_OSK | GTK_INPUT_HINT_EMOJI | GTK_INPUT_HINT_NONE</property> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkButton" id="emoji_button"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <property name="valign">center</property> + <property name="relief">none</property> + <child> + <object class="GtkImage"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="icon-name">face-smile-symbolic</property> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + <child> + <object class="GtkButton" id="send_record_button"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <property name="valign">center</property> + <property name="relief">none</property> + <child> + <object class="GtkImage" id="send_record_symbol"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="icon-name">audio-input-microphone-symbolic</property> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">3</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + </object> +</interface> diff --git a/resources/ui/messenger.ui b/resources/ui/messenger.ui @@ -717,248 +717,12 @@ Author: Tobias Frisch <property name="flap-position">end</property> <property name="reveal-flap">False</property> <property name="fold-policy">always</property> - <child type="content"> - <object class="GtkBox"> + <child> + <object class="GtkStack" id="chats_stack"> <property name="visible">True</property> <property name="can-focus">False</property> - <property name="orientation">vertical</property> <child> - <object class="GtkBox" id="chat-header-box"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="margin-start">8</property> - <property name="margin-end">8</property> - <property name="margin-top">8</property> - <property name="margin-bottom">8</property> - <property name="spacing">8</property> - <child> - <object class="GtkButton" id="back_button"> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="receives-default">True</property> - <property name="relief">none</property> - <child> - <object class="GtkImage"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="icon-name">go-previous-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="GtkBox"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="halign">start</property> - <property name="margin-start">4</property> - <property name="margin-end">4</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkLabel" id="chat_title"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="label" translatable="yes">Chat title</property> - <property name="xalign">0</property> - <attributes> - <attribute name="weight" value="bold"/> - </attributes> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkLabel" id="chat_subtitle"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="label" translatable="yes">Chat subtitle</property> - <property name="xalign">0</property> - <attributes> - <attribute name="weight" value="light"/> - </attributes> - </object> - <packing> - <property name="expand">True</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> - <child> - <object class="GtkButton" id="chat_details_button"> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="receives-default">True</property> - <property name="relief">none</property> - <child> - <object class="GtkImage"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="icon-name">view-more-symbolic</property> - </object> - </child> - </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> - </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> - <child> - <object class="GtkViewport"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <child> - <object class="GtkStack"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <child> - <object class="GtkListBox" id="messages_listbox"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="selection-mode">none</property> - <property name="activate-on-single-click">False</property> - </object> - <packing> - <property name="name">page0</property> - <property name="title" translatable="yes">page0</property> - </packing> - </child> - </object> - </child> - </object> - </child> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - <child> - <object class="GtkBox"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="margin-start">8</property> - <property name="margin-end">8</property> - <property name="margin-top">4</property> - <property name="margin-bottom">4</property> - <property name="spacing">4</property> - <child> - <object class="GtkButton" id="attach_file_button"> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="receives-default">True</property> - <property name="valign">center</property> - <property name="relief">none</property> - <child> - <object class="GtkImage"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="icon-name">mail-attachment-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="GtkTextView" id="send_text_view"> - <property name="width-request">210</property> - <property name="height-request">48</property> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="valign">end</property> - <property name="wrap-mode">word-char</property> - <property name="left-margin">8</property> - <property name="right-margin">8</property> - <property name="top-margin">8</property> - <property name="bottom-margin">8</property> - <property name="input-hints">GTK_INPUT_HINT_SPELLCHECK | GTK_INPUT_HINT_WORD_COMPLETION | GTK_INPUT_HINT_INHIBIT_OSK | GTK_INPUT_HINT_EMOJI | GTK_INPUT_HINT_NONE</property> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - <child> - <object class="GtkButton" id="emoji_button"> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="receives-default">True</property> - <property name="valign">center</property> - <property name="relief">none</property> - <child> - <object class="GtkImage"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="icon-name">face-smile-symbolic</property> - </object> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">2</property> - </packing> - </child> - <child> - <object class="GtkButton" id="send_record_button"> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="receives-default">True</property> - <property name="valign">center</property> - <property name="relief">none</property> - <child> - <object class="GtkImage" id="send_record_symbol"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="icon-name">audio-input-microphone-symbolic</property> - </object> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">3</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">2</property> - </packing> + <placeholder/> </child> </object> </child> @@ -1108,12 +872,4 @@ Author: Tobias Frisch <widget name="settings-label"/> </widgets> </object> - <object class="GtkSizeGroup"> - <property name="mode">vertical</property> - <widgets> - <widget name="chats-header-box"/> - <widget name="chat-header-box"/> - <widget name="details-header-box"/> - </widgets> - </object> </interface> diff --git a/src/application.c b/src/application.c @@ -48,7 +48,7 @@ _application_activate(UNUSED GtkApplication* application, { MESSENGER_Application *app = (MESSENGER_Application*) user_data; - ui_messenger_run(app); + ui_messenger_init(app, &(app->ui.messenger)); } void @@ -140,6 +140,8 @@ application_run(MESSENGER_Application *app) pthread_join(app->chat.tid, NULL); + ui_messenger_cleanup(&(app->ui.messenger)); + g_hash_table_destroy(app->ui.bindings); notify_uninit(); diff --git a/src/event.c b/src/event.c @@ -27,21 +27,43 @@ #include "ui/chat_entry.h" #include "ui/message.h" -static int -_iterate_profile_contacts(void *cls, - UNUSED struct GNUNET_CHAT_Handle *handle, - UNUSED struct GNUNET_CHAT_Contact *contact) +static void +_add_new_chat_entry(MESSENGER_Application *app, + struct GNUNET_CHAT_Context *context) { - MESSENGER_Application *app = (MESSENGER_Application*) cls; - UI_MESSENGER_Handle *ui = &(app->ui.messenger); - UI_CHAT_ENTRY_Handle *entry = ui_chat_entry_new(); - + UI_CHAT_ENTRY_Handle *entry = ui_chat_entry_new(app); gtk_container_add(GTK_CONTAINER(ui->chats_listbox), entry->entry_box); + GNUNET_CHAT_context_set_user_pointer(context, entry); + + char context_id [9]; + g_snprintf(context_id, sizeof(context_id), "%08lx", (gulong) context); + + gtk_widget_set_name(entry->entry_box, context_id); - g_free(entry); //TODO: add to a list or similar? + gtk_stack_add_named( + ui->chats_stack, + entry->chat->chat_box, + context_id + ); + g_hash_table_insert( + app->ui.bindings, + entry->chat->send_text_view, + context + ); + + ui->chat_entries = g_list_append(ui->chat_entries, entry); +} + +static int +_iterate_profile_contacts(void *cls, + UNUSED struct GNUNET_CHAT_Handle *handle, + UNUSED struct GNUNET_CHAT_Contact *contact) +{ + MESSENGER_Application *app = (MESSENGER_Application*) cls; + _add_new_chat_entry(app, GNUNET_CHAT_contact_get_context(contact)); return GNUNET_YES; } @@ -51,15 +73,7 @@ _iterate_profile_groups(void *cls, UNUSED struct GNUNET_CHAT_Group *group) { MESSENGER_Application *app = (MESSENGER_Application*) cls; - - UI_MESSENGER_Handle *ui = &(app->ui.messenger); - - UI_CHAT_ENTRY_Handle *entry = ui_chat_entry_new(); - - gtk_container_add(GTK_CONTAINER(ui->chats_listbox), entry->entry_box); - - g_free(entry); //TODO: add to a list or similar? - + _add_new_chat_entry(app, GNUNET_CHAT_group_get_context(group)); return GNUNET_YES; } @@ -96,12 +110,6 @@ event_update_profile(MESSENGER_Application *app, GNUNET_CHAT_iterate_contacts(chat->handle, _iterate_profile_contacts, app); GNUNET_CHAT_iterate_groups(chat->handle, _iterate_profile_groups, app); - - for (int i = 0; i < 8; i++) { - UI_MESSAGE_Handle *message = ui_message_new(app, i % 2 == 0); - gtk_container_add(GTK_CONTAINER(ui->messages_listbox), message->message_box); - g_free(message); // TODO: this is just a test! - } } void @@ -117,26 +125,7 @@ event_update_chats(MESSENGER_Application *app, if (GNUNET_CHAT_context_get_user_pointer(context)) return; - UI_MESSENGER_Handle *ui = &(app->ui.messenger); - - UI_CHAT_ENTRY_Handle *entry = ui_chat_entry_new(); - gtk_container_add(GTK_CONTAINER(ui->chats_listbox), entry->entry_box); - g_free(entry); // TODO: free already? - - // TODO: put something better here to attach it! - GNUNET_CHAT_context_set_user_pointer(context, ui); - - g_hash_table_insert( - app->ui.bindings, - app->ui.messenger.send_record_button, - context - ); - - g_hash_table_insert( - app->ui.bindings, - app->ui.messenger.send_text_view, - context - ); + _add_new_chat_entry(app, context); } void @@ -149,7 +138,9 @@ event_receive_message(MESSENGER_Application *app, struct GNUNET_CHAT_Context *context = (struct GNUNET_CHAT_Context*) argv[0]; - if (!GNUNET_CHAT_context_get_user_pointer(context)) + UI_CHAT_ENTRY_Handle *handle = GNUNET_CHAT_context_get_user_pointer(context); + + if (!handle) return; const struct GNUNET_CHAT_Message *msg; @@ -185,9 +176,12 @@ event_receive_message(MESSENGER_Application *app, // TODO: check read receipt gtk_container_add( - GTK_CONTAINER(app->ui.messenger.messages_listbox), + GTK_CONTAINER(handle->chat->messages_listbox), message->message_box ); g_free(message); // TODO: this is just a test! + + gtk_label_set_text(handle->text_label, text? text : ""); + gtk_label_set_text(handle->timestamp_label, time? time : ""); } diff --git a/src/ui/chat.c b/src/ui/chat.c @@ -0,0 +1,249 @@ +/* + This file is part of GNUnet. + Copyright (C) 2021 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/chat.c + */ + +#include "chat.h" + +#include "messenger.h" +#include "../application.h" + +static void +handle_flap_via_button_click(UNUSED GtkButton* button, + gpointer user_data) +{ + HdyFlap* flap = HDY_FLAP(user_data); + + if (TRUE == hdy_flap_get_reveal_flap(flap)) { + hdy_flap_set_reveal_flap(flap, FALSE); + } else { + hdy_flap_set_reveal_flap(flap, TRUE); + } +} + +static void +handle_back_button_click(UNUSED GtkButton* button, + gpointer user_data) +{ + HdyLeaflet* leaflet = HDY_LEAFLET(user_data); + + GList* children = gtk_container_get_children(GTK_CONTAINER(leaflet)); + + if (children) { + hdy_leaflet_set_visible_child(leaflet, GTK_WIDGET(children->data)); + } +} + +static void +handle_send_text_buffer_changed(GtkTextBuffer *buffer, + gpointer user_data) +{ + GtkImage *symbol = GTK_IMAGE(user_data); + + GtkTextIter start, end; + gtk_text_buffer_get_start_iter(buffer, &start); + gtk_text_buffer_get_end_iter(buffer, &end); + + const gchar *text = gtk_text_buffer_get_text(buffer, &start, &end, TRUE); + + gtk_image_set_from_icon_name( + symbol, + 0 < g_utf8_strlen(text, 1)? + "mail-send-symbolic" : + "audio-input-microphone-symbolic", + GTK_ICON_SIZE_BUTTON + ); +} + +static gboolean +_send_text_from_view(MESSENGER_Application *app, + GtkTextView *text_view) +{ + GtkTextBuffer *buffer = gtk_text_view_get_buffer(text_view); + + GtkTextIter start, end; + gtk_text_buffer_get_start_iter(buffer, &start); + gtk_text_buffer_get_end_iter(buffer, &end); + + const gchar *text = gtk_text_buffer_get_text(buffer, &start, &end, TRUE); + + if (0 == g_utf8_strlen(text, 1)) + return FALSE; + + struct GNUNET_CHAT_Context *context = g_hash_table_lookup( + app->ui.bindings, text_view + ); + + if (context) + GNUNET_CHAT_context_send_text(context, text); + + gtk_text_buffer_delete(buffer, &start, &end); + return TRUE; +} + +static void +handle_send_record_button_click(GtkButton *button, + gpointer user_data) +{ + MESSENGER_Application *app = (MESSENGER_Application*) user_data; + + GtkTextView *text_view = GTK_TEXT_VIEW( + g_hash_table_lookup(app->ui.bindings, button) + ); + + if (!_send_text_from_view(app, text_view)) + { + // TODO: record audio and attach as file? + } +} + +static gboolean +handle_send_text_key_press (GtkWidget *widget, + GdkEventKey *event, + gpointer user_data) +{ + MESSENGER_Application *app = (MESSENGER_Application*) user_data; + + if ((app->ui.mobile) || + (event->state & GDK_SHIFT_MASK) || + ((event->keyval != GDK_KEY_Return) && + (event->keyval != GDK_KEY_KP_Enter))) + return FALSE; + + return _send_text_from_view(app, GTK_TEXT_VIEW(widget)); +} + +UI_CHAT_Handle* +ui_chat_new(MESSENGER_Application *app) +{ + UI_CHAT_Handle *handle = g_malloc(sizeof(UI_CHAT_Handle)); + UI_MESSENGER_Handle *messenger = &(app->ui.messenger); + + GtkBuilder* builder = gtk_builder_new_from_file( + "resources/ui/chat.ui" + ); + + handle->chat_box = GTK_WIDGET( + gtk_builder_get_object(builder, "chat_box") + ); + + handle->back_button = GTK_BUTTON( + gtk_builder_get_object(builder, "back_button") + ); + + g_object_bind_property( + messenger->leaflet_chat, + "folded", + handle->back_button, + "visible", + G_BINDING_SYNC_CREATE + ); + + g_signal_connect( + handle->back_button, + "clicked", + G_CALLBACK(handle_back_button_click), + messenger->leaflet_chat + ); + + handle->chat_title = GTK_LABEL( + gtk_builder_get_object(builder, "chat_title") + ); + + handle->chat_subtitle = GTK_LABEL( + gtk_builder_get_object(builder, "chat_subtitle") + ); + + handle->chat_details_button = GTK_BUTTON( + gtk_builder_get_object(builder, "chat_details_button") + ); + + g_signal_connect( + handle->chat_details_button, + "clicked", + G_CALLBACK(handle_flap_via_button_click), + messenger->flap_chat_details + ); + + handle->messages_listbox = GTK_LIST_BOX( + gtk_builder_get_object(builder, "messages_listbox") + ); + + handle->attach_file_button = GTK_BUTTON( + gtk_builder_get_object(builder, "attach_file_button") + ); + + handle->send_text_view = GTK_TEXT_VIEW( + gtk_builder_get_object(builder, "send_text_view") + ); + + handle->emoji_button = GTK_BUTTON( + gtk_builder_get_object(builder, "emoji_button") + ); + + handle->send_record_button = GTK_BUTTON( + gtk_builder_get_object(builder, "send_record_button") + ); + + handle->send_record_symbol = GTK_IMAGE( + gtk_builder_get_object(builder, "send_record_symbol") + ); + + GtkTextBuffer *send_text_buffer = gtk_text_view_get_buffer( + handle->send_text_view + ); + + g_signal_connect( + send_text_buffer, + "changed", + G_CALLBACK(handle_send_text_buffer_changed), + handle->send_record_symbol + ); + + g_signal_connect( + handle->send_record_button, + "clicked", + G_CALLBACK(handle_send_record_button_click), + app + ); + + g_signal_connect( + handle->send_text_view, + "key-press-event", + G_CALLBACK(handle_send_text_key_press), + app + ); + + g_hash_table_insert( + app->ui.bindings, + handle->send_record_button, + handle->send_text_view + ); + + return handle; +} + +void +ui_chat_delete(UI_CHAT_Handle *handle) +{ + g_free(handle); +} diff --git a/src/ui/chat.h b/src/ui/chat.h @@ -0,0 +1,59 @@ +/* + This file is part of GNUnet. + Copyright (C) 2021 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/chat.h + */ + +#ifndef UI_CHAT_H_ +#define UI_CHAT_H_ + +#include <gtk-3.0/gtk/gtk.h> +#include <libhandy-1/handy.h> +#include <libnotify/notify.h> + +typedef struct MESSENGER_Application MESSENGER_Application; + +typedef struct UI_CHAT_Handle +{ + GtkWidget *chat_box; + + GtkButton *back_button; + + GtkLabel *chat_title; + GtkLabel *chat_subtitle; + GtkButton *chat_details_button; + + GtkListBox *messages_listbox; + + GtkButton *attach_file_button; + GtkTextView *send_text_view; + GtkButton *emoji_button; + GtkButton *send_record_button; + GtkImage *send_record_symbol; +} UI_CHAT_Handle; + +UI_CHAT_Handle* +ui_chat_new(MESSENGER_Application *app); + +void +ui_chat_delete(UI_CHAT_Handle *handle); + +#endif /* UI_CHAT_H_ */ diff --git a/src/ui/chat_entry.c b/src/ui/chat_entry.c @@ -24,11 +24,15 @@ #include "chat_entry.h" +#include "../application.h" + UI_CHAT_ENTRY_Handle* -ui_chat_entry_new(void) +ui_chat_entry_new(MESSENGER_Application *app) { UI_CHAT_ENTRY_Handle* handle = g_malloc(sizeof(UI_CHAT_ENTRY_Handle)); + handle->chat = ui_chat_new(app); + GtkBuilder* builder = gtk_builder_new_from_file("resources/ui/chat_entry.ui"); handle->entry_box = GTK_WIDGET( @@ -57,3 +61,11 @@ ui_chat_entry_new(void) return handle; } + +void +ui_chat_entry_delete(UI_CHAT_ENTRY_Handle *handle) +{ + ui_chat_delete(handle->chat); + + g_free(handle); +} diff --git a/src/ui/chat_entry.h b/src/ui/chat_entry.h @@ -25,13 +25,12 @@ #ifndef UI_CHAT_ENTRY_H_ #define UI_CHAT_ENTRY_H_ -#include <gtk-3.0/gtk/gtk.h> -#include <libhandy-1/handy.h> - -typedef struct MESSENGER_Application MESSENGER_Application; +#include "chat.h" typedef struct UI_CHAT_ENTRY_Handle { + UI_CHAT_Handle *chat; + GtkWidget* entry_box; HdyAvatar* entry_avatar; @@ -44,6 +43,9 @@ typedef struct UI_CHAT_ENTRY_Handle } UI_CHAT_ENTRY_Handle; UI_CHAT_ENTRY_Handle* -ui_chat_entry_new(void); +ui_chat_entry_new(MESSENGER_Application *app); + +void +ui_chat_entry_delete(UI_CHAT_ENTRY_Handle *handle); #endif /* UI_CHAT_ENTRY_H_ */ diff --git a/src/ui/messenger.c b/src/ui/messenger.c @@ -26,6 +26,7 @@ #include <gtk-3.0/gdk/gdkkeys.h> +#include "chat_entry.h" #include "message.h" #include "new_platform.h" #include "../application.h" @@ -80,118 +81,25 @@ handle_new_platform_button_click(UNUSED GtkButton* button, static void handle_chats_listbox_row_activated(UNUSED GtkListBox* listbox, - UNUSED GtkListBoxRow* row, + GtkListBoxRow* row, gpointer user_data) { - HdyLeaflet* leaflet = HDY_LEAFLET(user_data); + UI_MESSENGER_Handle *handle = (UI_MESSENGER_Handle*) user_data; + + GtkStack *stack = handle->chats_stack; + HdyLeaflet *leaflet = handle->leaflet_chat; - GList* children = gtk_container_get_children(GTK_CONTAINER(leaflet)); + GList *children = gtk_container_get_children(GTK_CONTAINER(leaflet)); if ((children) && (children->next)) { hdy_leaflet_set_visible_child(leaflet, GTK_WIDGET(children->next->data)); } -} - -static void -handle_back_button_click(UNUSED GtkButton* button, - gpointer user_data) -{ - HdyLeaflet* leaflet = HDY_LEAFLET(user_data); - - GList* children = gtk_container_get_children(GTK_CONTAINER(leaflet)); - - if (children) { - hdy_leaflet_set_visible_child(leaflet, GTK_WIDGET(children->data)); - } -} - -static void -handle_send_text_buffer_changed(GtkTextBuffer *buffer, - gpointer user_data) -{ - GtkImage *symbol = GTK_IMAGE(user_data); - - GtkTextIter start, end; - gtk_text_buffer_get_start_iter(buffer, &start); - gtk_text_buffer_get_end_iter(buffer, &end); - - const gchar *text = gtk_text_buffer_get_text(buffer, &start, &end, TRUE); - - gtk_image_set_from_icon_name( - symbol, - 0 < g_utf8_strlen(text, 1)? - "mail-send-symbolic" : - "audio-input-microphone-symbolic", - GTK_ICON_SIZE_BUTTON - ); -} - -static void -handle_send_record_button_click(GtkButton *button, - gpointer user_data) -{ - MESSENGER_Application *app = (MESSENGER_Application*) user_data; - - GtkTextBuffer *buffer = gtk_text_view_get_buffer( - app->ui.messenger.send_text_view - ); - - GtkTextIter start, end; - gtk_text_buffer_get_start_iter(buffer, &start); - gtk_text_buffer_get_end_iter(buffer, &end); - - const gchar *text = gtk_text_buffer_get_text(buffer, &start, &end, TRUE); - - if (0 < g_utf8_strlen(text, 1)) - { - struct GNUNET_CHAT_Context *context = g_hash_table_lookup( - app->ui.bindings, button - ); - - if (context) - GNUNET_CHAT_context_send_text(context, text); - } - else - { - // TODO: record audio and attach as file? - } - - gtk_text_buffer_delete(buffer, &start, &end); -} -static gboolean -handle_send_text_key_press (GtkWidget *widget, - GdkEventKey *event, - gpointer user_data) -{ - MESSENGER_Application *app = (MESSENGER_Application*) user_data; - - if ((app->ui.mobile) || - (event->state & GDK_SHIFT_MASK) || - ((event->keyval != GDK_KEY_Return) && - (event->keyval != GDK_KEY_KP_Enter))) - return FALSE; - - GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW (widget)); - - GtkTextIter start, end; - gtk_text_buffer_get_start_iter(buffer, &start); - gtk_text_buffer_get_end_iter(buffer, &end); - - const gchar *text = gtk_text_buffer_get_text(buffer, &start, &end, TRUE); - - if (0 == g_utf8_strlen(text, 1)) - return FALSE; - - struct GNUNET_CHAT_Context *context = g_hash_table_lookup( - app->ui.bindings, widget + GtkWidget *entry = GTK_WIDGET( + gtk_container_get_children(GTK_CONTAINER(row))->data ); - if (context) - GNUNET_CHAT_context_send_text(context, text); - - gtk_text_buffer_delete(buffer, &start, &end); - return TRUE; + gtk_stack_set_visible_child_name(stack, gtk_widget_get_name(entry)); } static void @@ -207,6 +115,8 @@ void ui_messenger_init(MESSENGER_Application *app, UI_MESSENGER_Handle *handle) { + handle->chat_entries = g_list_alloc(); + GtkBuilder* builder = gtk_builder_new_from_file("resources/ui/messenger.ui"); handle->main_window = GTK_APPLICATION_WINDOW( @@ -257,25 +167,6 @@ ui_messenger_init(MESSENGER_Application *app, G_BINDING_INVERT_BOOLEAN ); - handle->back_button = GTK_BUTTON( - gtk_builder_get_object(builder, "back_button") - ); - - g_object_bind_property( - handle->leaflet_chat, - "folded", - handle->back_button, - "visible", - G_BINDING_SYNC_CREATE - ); - - g_signal_connect( - handle->back_button, - "clicked", - G_CALLBACK(handle_back_button_click), - handle->leaflet_chat - ); - handle->profile_avatar = HDY_AVATAR( gtk_builder_get_object(builder, "profile_avatar") ); @@ -372,26 +263,11 @@ ui_messenger_init(MESSENGER_Application *app, handle->chats_listbox, "row-activated", G_CALLBACK(handle_chats_listbox_row_activated), - handle->leaflet_chat - ); - - handle->chat_title = GTK_LABEL( - gtk_builder_get_object(builder, "chat_title") - ); - - handle->chat_subtitle = GTK_LABEL( - gtk_builder_get_object(builder, "chat_subtitle") - ); - - handle->chat_details_button = GTK_BUTTON( - gtk_builder_get_object(builder, "chat_details_button") + handle ); - g_signal_connect( - handle->chat_details_button, - "clicked", - G_CALLBACK(handle_flap_via_button_click), - handle->flap_chat_details + handle->chats_stack = GTK_STACK( + gtk_builder_get_object(builder, "chats_stack") ); handle->hide_chat_details_button = GTK_BUTTON( @@ -405,55 +281,6 @@ ui_messenger_init(MESSENGER_Application *app, handle->flap_chat_details ); - handle->messages_listbox = GTK_LIST_BOX( - gtk_builder_get_object(builder, "messages_listbox") - ); - - handle->attach_file_button = GTK_BUTTON( - gtk_builder_get_object(builder, "attach_file_button") - ); - - handle->send_text_view = GTK_TEXT_VIEW( - gtk_builder_get_object(builder, "send_text_view") - ); - - handle->emoji_button = GTK_BUTTON( - gtk_builder_get_object(builder, "emoji_button") - ); - - handle->send_record_button = GTK_BUTTON( - gtk_builder_get_object(builder, "send_record_button") - ); - - handle->send_record_symbol = GTK_IMAGE( - gtk_builder_get_object(builder, "send_record_symbol") - ); - - GtkTextBuffer *send_text_buffer = gtk_text_view_get_buffer( - handle->send_text_view - ); - - g_signal_connect( - send_text_buffer, - "changed", - G_CALLBACK(handle_send_text_buffer_changed), - handle->send_record_symbol - ); - - g_signal_connect( - handle->send_record_button, - "clicked", - G_CALLBACK(handle_send_record_button_click), - app - ); - - g_signal_connect( - handle->send_text_view, - "key-press-event", - G_CALLBACK(handle_send_text_key_press), - app - ); - gtk_widget_show(GTK_WIDGET(handle->main_window)); g_signal_connect( @@ -464,8 +291,16 @@ ui_messenger_init(MESSENGER_Application *app, ); } +static void +_free_ui_chat_entry (gpointer user_data) +{ + UI_CHAT_ENTRY_Handle* handle = (UI_CHAT_ENTRY_Handle*) user_data; + + ui_chat_entry_delete(handle); +} + void -ui_messenger_run(MESSENGER_Application *app) +ui_messenger_cleanup(UI_MESSENGER_Handle *handle) { - ui_messenger_init(app, &(app->ui.messenger)); + g_list_free_full(handle->chat_entries, _free_ui_chat_entry); } diff --git a/src/ui/messenger.h b/src/ui/messenger.h @@ -33,6 +33,8 @@ typedef struct MESSENGER_Application MESSENGER_Application; typedef struct UI_MESSENGER_Handle { + GList *chat_entries; + GtkApplicationWindow *main_window; HdyLeaflet *leaflet_chat; @@ -40,7 +42,6 @@ typedef struct UI_MESSENGER_Handle HdyFlap *flap_chat_details; HdyHeaderBar *title_bar; - GtkButton *back_button; HdyAvatar *profile_avatar; GtkLabel *profile_label; @@ -63,19 +64,9 @@ typedef struct UI_MESSENGER_Handle GtkSearchEntry *chats_search; GtkListBox *chats_listbox; - GtkLabel *chat_title; - GtkLabel *chat_subtitle; - GtkButton *chat_details_button; + GtkStack *chats_stack; GtkButton *hide_chat_details_button; - - GtkListBox *messages_listbox; - - GtkButton *attach_file_button; - GtkTextView *send_text_view; - GtkButton *emoji_button; - GtkButton *send_record_button; - GtkImage *send_record_symbol; } UI_MESSENGER_Handle; void @@ -83,6 +74,6 @@ ui_messenger_init(MESSENGER_Application *app, UI_MESSENGER_Handle *handle); void -ui_messenger_run(MESSENGER_Application *app); +ui_messenger_cleanup(UI_MESSENGER_Handle *handle); #endif /* UI_MESSENGER_H_ */