messenger-gtk

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

commit f52675513361ac166800d2216969ab370c845318
parent 5481b37fcf1a820e55719da9593216e58774da18
Author: TheJackiMonster <thejackimonster@gmail.com>
Date:   Thu, 17 Mar 2022 13:05:06 +0100

Implemented contact details dialog

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

Diffstat:
Mresources/ui/contact_info.ui | 46+++++++++++++++++++++++++++++++++++++++++-----
Msrc/application.h | 4+++-
Msrc/ui/chat.c | 46+++++++++++++++++++++++++++++++---------------
Msrc/ui/contact_info.c | 339++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Msrc/ui/contact_info.h | 17+++++++++++++++--
5 files changed, 426 insertions(+), 26 deletions(-)

diff --git a/resources/ui/contact_info.ui b/resources/ui/contact_info.ui @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- Generated with glade 3.38.2 -Copyright (C) 2021‑‑2022 GNUnet e.V. +Copyright (C) 2022 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 @@ -25,6 +25,7 @@ Author: Tobias Frisch <requires lib="libhandy" version="1.2"/> <object class="GtkDialog" id="contact_info_dialog"> <property name="can-focus">False</property> + <property name="title" translatable="yes">Contact Information</property> <property name="modal">True</property> <property name="window-position">center-on-parent</property> <property name="type-hint">dialog</property> @@ -40,7 +41,6 @@ Author: Tobias Frisch <child> <object class="GtkButton" id="back_button"> <property name="label" translatable="yes">Back</property> - <property name="visible">True</property> <property name="can-focus">True</property> <property name="receives-default">True</property> </object> @@ -114,10 +114,42 @@ Author: Tobias Frisch </packing> </child> <child> - <object class="GtkEntry" id="contact_name"> - <property name="width-request">250</property> + <object class="GtkBox"> <property name="visible">True</property> - <property name="can-focus">True</property> + <property name="can-focus">False</property> + <property name="spacing">4</property> + <child> + <object class="GtkEntry" id="contact_name"> + <property name="width-request">210</property> + <property name="visible">True</property> + <property name="sensitive">False</property> + <property name="can-focus">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkButton" id="contact_edit_button"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <child> + <object class="GtkImage" id="contact_edit_symbol"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="icon-name">document-edit-symbolic</property> + </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> @@ -161,6 +193,10 @@ Author: Tobias Frisch <property name="height-request">250</property> <property name="visible">True</property> <property name="can-focus">False</property> + <property name="margin-start">16</property> + <property name="margin-end">16</property> + <property name="margin-top">16</property> + <property name="margin-bottom">16</property> </object> <packing> <property name="name">drawing_page</property> diff --git a/src/application.h b/src/application.h @@ -32,6 +32,7 @@ #include "ui/about.h" #include "ui/accounts.h" +#include "ui/contact_info.h" #include "ui/contacts.h" #include "ui/delete_messages.h" #include "ui/invite_contact.h" @@ -94,9 +95,10 @@ typedef struct MESSENGER_Application UI_MESSENGER_Handle messenger; UI_ABOUT_Handle about; + UI_CONTACT_INFO_Handle contact_info; + UI_DELETE_MESSAGES_Handle delete_messages; UI_INVITE_CONTACT_Handle invite_contact; UI_SEND_FILE_Handle send_file; - UI_DELETE_MESSAGES_Handle delete_messages; UI_NEW_CONTACT_Handle new_contact; UI_NEW_GROUP_Handle new_group; diff --git a/src/ui/chat.c b/src/ui/chat.c @@ -77,7 +77,8 @@ handle_chat_contacts_listbox_row_activated(GtkListBox *listbox, GtkListBoxRow *row, gpointer user_data) { - MESSENGER_Application *app = (MESSENGER_Application*) user_data; + UI_CHAT_Handle *handle = (UI_CHAT_Handle*) user_data; + MESSENGER_Application *app = handle->app; GtkTextView *text_view = GTK_TEXT_VIEW( g_object_get_qdata(G_OBJECT(listbox), app->quarks.widget) @@ -104,19 +105,15 @@ handle_chat_contacts_listbox_row_activated(GtkListBox *listbox, g_object_get_qdata(G_OBJECT(row), app->quarks.data) ); - if ((!contact) || (!GNUNET_CHAT_contact_get_key(contact)) || - (GNUNET_YES == GNUNET_CHAT_contact_is_owned(contact))) + if (!contact) return; - struct GNUNET_CHAT_Context *context = GNUNET_CHAT_contact_get_context( - contact - ); + hdy_flap_set_reveal_flap(handle->flap_chat_details, FALSE); - if (!context) - return; + ui_contact_info_dialog_init(app, &(app->ui.contact_info)); + ui_contact_info_dialog_update(&(app->ui.contact_info), contact, FALSE); - if (GNUNET_SYSERR == GNUNET_CHAT_context_get_status(context)) - GNUNET_CHAT_context_request(context); + gtk_widget_show(GTK_WIDGET(app->ui.contact_info.dialog)); } static void @@ -156,12 +153,25 @@ handle_back_button_click(UNUSED GtkButton *button, } static void -handle_reveal_identity_button_click(UNUSED GtkButton *button, +handle_reveal_identity_button_click(GtkButton *button, gpointer user_data) { UI_CHAT_Handle *handle = (UI_CHAT_Handle*) user_data; + MESSENGER_Application *app = handle->app; + + struct GNUNET_CHAT_Contact *contact = (struct GNUNET_CHAT_Contact*) ( + g_object_get_qdata(G_OBJECT(button), app->quarks.data) + ); - // TODO + if (!contact) + return; + + hdy_flap_set_reveal_flap(handle->flap_chat_details, FALSE); + + ui_contact_info_dialog_init(app, &(app->ui.contact_info)); + ui_contact_info_dialog_update(&(app->ui.contact_info), contact, TRUE); + + gtk_widget_show(GTK_WIDGET(app->ui.contact_info.dialog)); } static void @@ -1197,7 +1207,7 @@ ui_chat_new(MESSENGER_Application *app) handle->chat_contacts_listbox, "row-activated", G_CALLBACK(handle_chat_contacts_listbox_row_activated), - app + handle ); handle->chat_files_listbox = GTK_LIST_BOX( @@ -1443,8 +1453,8 @@ ui_chat_update(UI_CHAT_Handle *handle, { GNUNET_assert((handle) && (app) && (context)); - const struct GNUNET_CHAT_Contact* contact; - const struct GNUNET_CHAT_Group* group; + struct GNUNET_CHAT_Contact* contact; + struct GNUNET_CHAT_Group* group; contact = GNUNET_CHAT_context_get_contact(context); group = GNUNET_CHAT_context_get_group(context); @@ -1524,6 +1534,12 @@ ui_chat_update(UI_CHAT_Handle *handle, group? TRUE : FALSE ); + g_object_set_qdata( + G_OBJECT(handle->reveal_identity_button), + app->quarks.data, + contact + ); + gtk_widget_set_visible( GTK_WIDGET(handle->reveal_identity_button), contact? TRUE : FALSE diff --git a/src/ui/contact_info.c b/src/ui/contact_info.c @@ -24,9 +24,137 @@ #include "contact_info.h" +#include "chat_entry.h" #include "../application.h" static void +handle_contact_edit_button_click(UNUSED GtkButton *button, + gpointer user_data) +{ + UI_CONTACT_INFO_Handle *handle = (UI_CONTACT_INFO_Handle*) user_data; + + gboolean editable = gtk_widget_is_sensitive( + GTK_WIDGET(handle->contact_name_entry) + ); + + struct GNUNET_CHAT_Contact *contact = (struct GNUNET_CHAT_Contact*) ( + g_object_get_qdata( + G_OBJECT(handle->contact_name_entry), + handle->app->quarks.data + ) + ); + + const gchar *name = gtk_entry_get_text(handle->contact_name_entry); + + if ((editable) && (contact)) + GNUNET_CHAT_contact_set_name( + contact, + (name) && (g_utf8_strlen(name, 1))? name : NULL + ); + + gtk_image_set_from_icon_name( + handle->contact_edit_symbol, + editable? + "document-edit-symbolic" : + "emblem-ok-symbolic", + GTK_ICON_SIZE_BUTTON + ); + + gtk_widget_set_sensitive( + GTK_WIDGET(handle->contact_name_entry), + !editable + ); +} + +static void +handle_contact_name_entry_activate(UNUSED GtkEntry *entry, + gpointer user_data) +{ + UI_CONTACT_INFO_Handle *handle = (UI_CONTACT_INFO_Handle*) user_data; + + handle_contact_edit_button_click(handle->contact_edit_button, handle); +} + +static void +_contact_info_reveal_identity(UI_CONTACT_INFO_Handle *handle) +{ + gtk_widget_set_visible(GTK_WIDGET(handle->back_button), TRUE); + + gtk_stack_set_visible_child( + handle->contact_info_stack, + GTK_WIDGET(handle->id_drawing_area) + ); +} + +static void +handle_reveal_identity_button_click(UNUSED GtkButton *button, + gpointer user_data) +{ + _contact_info_reveal_identity((UI_CONTACT_INFO_Handle*) user_data); +} + +static void +handle_open_chat_button_click(UNUSED GtkButton *button, + gpointer user_data) +{ + UI_CONTACT_INFO_Handle *handle = (UI_CONTACT_INFO_Handle*) user_data; + + struct GNUNET_CHAT_Contact *contact = (struct GNUNET_CHAT_Contact*) ( + g_object_get_qdata( + G_OBJECT(handle->contact_name_entry), + handle->app->quarks.data + ) + ); + + if (!contact) + return; + + struct GNUNET_CHAT_Context *context = GNUNET_CHAT_contact_get_context( + contact + ); + + if (!context) + return; + + if (GNUNET_SYSERR == GNUNET_CHAT_context_get_status(context)) + { + GNUNET_CHAT_context_request(context); + goto close_dialog; + } + + UI_CHAT_ENTRY_Handle *entry = GNUNET_CHAT_context_get_user_pointer(context); + + if (!entry) + return; + + GtkListBoxRow *row = GTK_LIST_BOX_ROW( + gtk_widget_get_parent(entry->entry_box) + ); + + gtk_list_box_select_row(handle->app->ui.messenger.chats_listbox, row); + gtk_list_box_invalidate_filter(handle->app->ui.messenger.chats_listbox); + + gtk_widget_activate(GTK_WIDGET(row)); + +close_dialog: + gtk_window_close(GTK_WINDOW(handle->dialog)); +} + +static void +handle_back_button_click(UNUSED GtkButton *button, + gpointer user_data) +{ + UI_CONTACT_INFO_Handle *handle = (UI_CONTACT_INFO_Handle*) user_data; + + gtk_widget_set_visible(GTK_WIDGET(handle->back_button), FALSE); + + gtk_stack_set_visible_child( + handle->contact_info_stack, + handle->details_box + ); +} + +static void handle_close_button_click(UNUSED GtkButton *button, gpointer user_data) { @@ -41,10 +169,106 @@ handle_dialog_destroy(UNUSED GtkWidget *window, ui_contact_info_dialog_cleanup((UI_CONTACT_INFO_Handle*) user_data); } +static gboolean +handle_id_drawing_area_draw(GtkWidget* drawing_area, + cairo_t* cairo, + gpointer user_data) +{ + UI_CONTACT_INFO_Handle *handle = (UI_CONTACT_INFO_Handle*) user_data; + + GtkStyleContext* context = gtk_widget_get_style_context(drawing_area); + + if (!context) + return FALSE; + + const guint width = gtk_widget_get_allocated_width(drawing_area); + const guint height = gtk_widget_get_allocated_height(drawing_area); + + gtk_render_background(context, cairo, 0, 0, width, height); + + if ((!(handle->qr)) || (handle->qr->width <= 0)) + return FALSE; + + const guint m = 3; + const guint w = handle->qr->width; + const guint w2 = w + m * 2; + + guchar *pixels = (guchar*) g_malloc(sizeof(guchar) * w2 * w2 * 3); + + guint x, y, z; + for (y = 0; y < w2; y++) + for (x = 0; x < w2; x++) + { + guchar value; + + if ((x >= m) && (y >= m) && (x - m < w) && (y - m < w)) + value = ((handle->qr->data[(y - m) * w + x - m]) & 1); + else + value = 0; + + for (z = 0; z < 3; z++) + pixels[(y * w2 + x) * 3 + z] = value? 0x00 : 0xff; + } + + GdkPixbuf *image = gdk_pixbuf_new_from_data( + pixels, + GDK_COLORSPACE_RGB, + FALSE, + 8, + w2, + w2, + w2 * 3, + NULL, + NULL + ); + + if (!image) + return FALSE; + + int dwidth = gdk_pixbuf_get_width(image); + int dheight = gdk_pixbuf_get_height(image); + + double ratio_width = 1.0 * width / dwidth; + double ratio_height = 1.0 * height / dheight; + + const double ratio = ratio_width < ratio_height? ratio_width : ratio_height; + + dwidth = (int) (dwidth * ratio); + dheight = (int) (dheight * ratio); + + double dx = (width - dwidth) * 0.5; + double dy = (height - dheight) * 0.5; + + const int interp_type = (ratio >= 1.0? + GDK_INTERP_NEAREST : + GDK_INTERP_BILINEAR + ); + + GdkPixbuf* scaled = gdk_pixbuf_scale_simple( + image, + dwidth, + dheight, + interp_type + ); + + gtk_render_icon(context, cairo, scaled, dx, dy); + + cairo_fill(cairo); + + g_object_unref(scaled); + g_object_unref(image); + + g_free(pixels); + + return FALSE; +} + void ui_contact_info_dialog_init(MESSENGER_Application *app, UI_CONTACT_INFO_Handle *handle) { + handle->app = app; + handle->builder = gtk_builder_new_from_resource( application_get_resource_path(app, "ui/contact_info.ui") ); @@ -70,26 +294,76 @@ ui_contact_info_dialog_init(MESSENGER_Application *app, gtk_builder_get_object(handle->builder, "contact_avatar") ); - handle->contact_name = GTK_ENTRY( + handle->contact_name_entry = GTK_ENTRY( gtk_builder_get_object(handle->builder, "contact_name") ); + handle->contact_edit_button = GTK_BUTTON( + gtk_builder_get_object(handle->builder, "contact_edit_button") + ); + + handle->contact_edit_symbol = GTK_IMAGE( + gtk_builder_get_object(handle->builder, "contact_edit_symbol") + ); + + g_signal_connect( + handle->contact_name_entry, + "activate", + G_CALLBACK(handle_contact_name_entry_activate), + handle + ); + + g_signal_connect( + handle->contact_edit_button, + "clicked", + G_CALLBACK(handle_contact_edit_button_click), + handle + ); + handle->reveal_identity_button = GTK_BUTTON( gtk_builder_get_object(handle->builder, "reveal_identity_button") ); + g_signal_connect( + handle->reveal_identity_button, + "clicked", + G_CALLBACK(handle_reveal_identity_button_click), + handle + ); + handle->open_chat_button = GTK_BUTTON( gtk_builder_get_object(handle->builder, "open_chat_button") ); + g_signal_connect( + handle->open_chat_button, + "clicked", + G_CALLBACK(handle_open_chat_button_click), + handle + ); + handle->id_drawing_area = GTK_DRAWING_AREA( gtk_builder_get_object(handle->builder, "id_drawing_area") ); + handle->id_draw_signal = g_signal_connect( + handle->id_drawing_area, + "draw", + G_CALLBACK(handle_id_drawing_area_draw), + handle + ); + 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") ); @@ -111,15 +385,74 @@ ui_contact_info_dialog_init(MESSENGER_Application *app, void ui_contact_info_dialog_update(UI_CONTACT_INFO_Handle *handle, - struct GNUNET_CHAT_Contact *contact) + struct GNUNET_CHAT_Contact *contact, + gboolean reveal) { - // TODO + const char *name = GNUNET_CHAT_contact_get_name(contact); + + hdy_avatar_set_text(handle->contact_avatar, name? name : ""); + gtk_entry_set_text(handle->contact_name_entry, name? name : ""); + + g_object_set_qdata( + G_OBJECT(handle->contact_name_entry), + handle->app->quarks.data, + contact + ); + + const char *key = GNUNET_CHAT_contact_get_key(contact); + + if (handle->qr) + QRcode_free(handle->qr); + + if (key) + handle->qr = QRcode_encodeString( + key, + 0, + QR_ECLEVEL_L, + QR_MODE_8, + 0 + ); + else + handle->qr = NULL; + + if (handle->id_drawing_area) + gtk_widget_queue_draw(GTK_WIDGET(handle->id_drawing_area)); + + gtk_widget_set_sensitive( + GTK_WIDGET(handle->reveal_identity_button), + key? TRUE : FALSE + ); + + struct GNUNET_CHAT_Context *context = GNUNET_CHAT_contact_get_context( + contact + ); + + gtk_widget_set_sensitive( + GTK_WIDGET(handle->open_chat_button), + context? TRUE : FALSE + ); + + gtk_widget_set_visible( + GTK_WIDGET(handle->open_chat_button), + GNUNET_YES != GNUNET_CHAT_contact_is_owned(contact) + ); + + if (reveal) + _contact_info_reveal_identity(handle); } void ui_contact_info_dialog_cleanup(UI_CONTACT_INFO_Handle *handle) { + g_signal_handler_disconnect( + handle->id_drawing_area, + handle->id_draw_signal + ); + g_object_unref(handle->builder); + if (handle->qr) + QRcode_free(handle->qr); + memset(handle, 0, sizeof(*handle)); } diff --git a/src/ui/contact_info.h b/src/ui/contact_info.h @@ -27,8 +27,14 @@ #include "messenger.h" +#include <cairo/cairo.h> +#include <gdk/gdkpixbuf.h> +#include <qrencode.h> + typedef struct UI_CONTACT_INFO_Handle { + MESSENGER_Application *app; + GtkBuilder *builder; GtkDialog *dialog; @@ -36,15 +42,21 @@ typedef struct UI_CONTACT_INFO_Handle GtkWidget *details_box; HdyAvatar *contact_avatar; - GtkEntry *contact_name; + GtkEntry *contact_name_entry; + + GtkButton *contact_edit_button; + GtkImage *contact_edit_symbol; GtkButton *reveal_identity_button; GtkButton *open_chat_button; GtkDrawingArea *id_drawing_area; + gulong id_draw_signal; GtkButton *back_button; GtkButton *close_button; + + QRcode *qr; } UI_CONTACT_INFO_Handle; void @@ -53,7 +65,8 @@ ui_contact_info_dialog_init(MESSENGER_Application *app, void ui_contact_info_dialog_update(UI_CONTACT_INFO_Handle *handle, - struct GNUNET_CHAT_Contact *contact); + struct GNUNET_CHAT_Contact *contact, + gboolean reveal); void ui_contact_info_dialog_cleanup(UI_CONTACT_INFO_Handle *handle);