messenger-gtk

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

message.c (23521B)


      1 /*
      2    This file is part of GNUnet.
      3    Copyright (C) 2021--2024 GNUnet e.V.
      4 
      5    GNUnet is free software: you can redistribute it and/or modify it
      6    under the terms of the GNU Affero General Public License as published
      7    by the Free Software Foundation, either version 3 of the License,
      8    or (at your option) any later version.
      9 
     10    GNUnet is distributed in the hope that it will be useful, but
     11    WITHOUT ANY WARRANTY; without even the implied warranty of
     12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     13    Affero General Public License for more details.
     14 
     15    You should have received a copy of the GNU Affero General Public License
     16    along with this program.  If not, see <http://www.gnu.org/licenses/>.
     17 
     18    SPDX-License-Identifier: AGPL3.0-or-later
     19  */
     20 /*
     21  * @author Tobias Frisch
     22  * @file ui/message.c
     23  */
     24 
     25 #include "message.h"
     26 
     27 #include <gnunet/gnunet_chat_lib.h>
     28 #include <gnunet/gnunet_common.h>
     29 
     30 #include "tag.h"
     31 
     32 #include "../application.h"
     33 #include "../contact.h"
     34 #include "../file.h"
     35 #include "../ui.h"
     36 
     37 static void
     38 handle_downloading_file(void *cls,
     39                         struct GNUNET_CHAT_File *file,
     40                         uint64_t completed,
     41                         uint64_t size)
     42 {
     43   g_assert((cls) && (file));
     44 
     45   MESSENGER_Application *app = (MESSENGER_Application*) cls;
     46 
     47   if (!app)
     48     return;
     49 
     50   file_update_download_info(file, app, completed, size);
     51 }
     52 
     53 static void
     54 handle_file_button_click(GtkButton *button,
     55                          gpointer user_data)
     56 {
     57   g_assert((button) && (user_data));
     58 
     59   MESSENGER_Application *app = (MESSENGER_Application*) user_data;
     60 
     61   UI_MESSAGE_Handle* handle = (UI_MESSAGE_Handle*) (
     62     g_object_get_qdata(G_OBJECT(button), app->quarks.ui)
     63   );
     64 
     65   if (!handle)
     66     return;
     67 
     68   struct GNUNET_CHAT_File *file = (struct GNUNET_CHAT_File*) (
     69     g_object_get_qdata(G_OBJECT(handle->file_progress_bar), app->quarks.data)
     70   );
     71 
     72   if (!file)
     73     return;
     74 
     75   application_chat_lock(app);
     76   uint64_t size = GNUNET_CHAT_file_get_size(file);
     77   application_chat_unlock(app);
     78 
     79   if (size <= 0)
     80     return;
     81 
     82   application_chat_lock(app);
     83 
     84   uint64_t local_size = GNUNET_CHAT_file_get_local_size(file);
     85   const gboolean downloading = (GNUNET_YES == GNUNET_CHAT_file_is_downloading(file));
     86 
     87   application_chat_unlock(app);
     88 
     89   if (downloading)
     90   {
     91     application_chat_lock(app);
     92     GNUNET_CHAT_file_stop_download(file);
     93     application_chat_unlock(app);
     94 
     95     gtk_image_set_from_icon_name(
     96       handle->file_status_image,
     97       "folder-download-symbolic",
     98       GTK_ICON_SIZE_BUTTON
     99     );
    100   }
    101   else if (local_size < size)
    102   {
    103     application_chat_lock(app);
    104     GNUNET_CHAT_file_start_download(
    105       file,
    106       handle_downloading_file,
    107       app
    108     );
    109     application_chat_unlock(app);
    110 
    111     gtk_image_set_from_icon_name(
    112     	handle->file_status_image,
    113     	"process-stop-symbolic",
    114     	GTK_ICON_SIZE_BUTTON
    115     );
    116   }
    117   else if (size > 0)
    118   {
    119     application_chat_lock(app);
    120     const gchar *preview = GNUNET_CHAT_file_open_preview(file);
    121     application_chat_unlock(app);
    122 
    123     if (!preview)
    124       return;
    125 
    126     GString* uri = g_string_new("file://");
    127     g_string_append(uri, preview);
    128 
    129     if (!g_app_info_launch_default_for_uri(uri->str, NULL, NULL))
    130     {
    131       application_chat_lock(app);
    132       GNUNET_CHAT_file_close_preview(file);
    133       application_chat_unlock(app);
    134     }
    135 
    136     g_string_free(uri, TRUE);
    137   }
    138 }
    139 
    140 static void
    141 handle_accept_button_click(GtkButton *button,
    142                            gpointer user_data)
    143 {
    144   g_assert((button) && (user_data));
    145 
    146   MESSENGER_Application *app = (MESSENGER_Application*) user_data;
    147 
    148   UI_MESSAGE_Handle* handle = (UI_MESSAGE_Handle*) (
    149       g_object_get_qdata(G_OBJECT(button), app->quarks.ui)
    150   );
    151 
    152   if ((!handle) || (!(handle->status_cb)))
    153     return;
    154 
    155   handle->status_cb(app, true, handle->status_cls);
    156 }
    157 
    158 static void
    159 handle_deny_button_click(GtkButton *button,
    160                          gpointer user_data)
    161 {
    162   g_assert((button) && (user_data));
    163 
    164   MESSENGER_Application *app = (MESSENGER_Application*) user_data;
    165 
    166   UI_MESSAGE_Handle* handle = (UI_MESSAGE_Handle*) (
    167       g_object_get_qdata(G_OBJECT(button), app->quarks.ui)
    168   );
    169 
    170   if ((!handle) || (!(handle->status_cb)))
    171     return;
    172 
    173   handle->status_cb(app, false, handle->status_cls);
    174 }
    175 
    176 static void
    177 handle_media_button_click(GtkButton *button,
    178                           gpointer user_data)
    179 {
    180   g_assert((button) && (user_data));
    181 
    182   MESSENGER_Application *app = (MESSENGER_Application*) user_data;
    183 
    184   UI_MESSAGE_Handle* handle = (UI_MESSAGE_Handle*) (
    185       g_object_get_qdata(G_OBJECT(button), app->quarks.ui)
    186   );
    187 
    188   if (!handle)
    189     return;
    190 
    191   struct GNUNET_CHAT_File *file = (struct GNUNET_CHAT_File*) (
    192       g_object_get_qdata(G_OBJECT(handle->media_progress_bar), app->quarks.data)
    193   );
    194 
    195   if (!file)
    196     return;
    197 
    198   application_chat_lock(app);
    199   const gchar *preview = GNUNET_CHAT_file_open_preview(file);
    200   application_chat_unlock(app);
    201 
    202   if (!preview)
    203     return;
    204 
    205   ui_play_media_window_init(app, &(app->ui.play_media));
    206 
    207   GString* uri = g_string_new("file://");
    208   g_string_append(uri, preview);
    209 
    210   ui_play_media_window_update(
    211       &(app->ui.play_media),
    212       uri->str,
    213       file
    214   );
    215 
    216   gtk_widget_show(GTK_WIDGET(app->ui.play_media.window));
    217   g_string_free(uri, TRUE);
    218 }
    219 
    220 static gboolean
    221 handle_preview_drawing_area_draw(GtkWidget* drawing_area,
    222                                  cairo_t* cairo,
    223                                  gpointer user_data)
    224 {
    225   g_assert((drawing_area) && (cairo) && (user_data));
    226 
    227   UI_MESSAGE_Handle *handle = (UI_MESSAGE_Handle*) user_data;
    228 
    229   GtkStyleContext* context = gtk_widget_get_style_context(drawing_area);
    230 
    231   const guint width = gtk_widget_get_allocated_width(drawing_area);
    232   const guint height = gtk_widget_get_allocated_height(drawing_area);
    233 
    234   gtk_render_background(context, cairo, 0, 0, width, height);
    235 
    236   struct GNUNET_CHAT_File *file = (struct GNUNET_CHAT_File *) g_object_get_qdata(
    237     G_OBJECT(handle->message_box),
    238     handle->app->quarks.data
    239   );
    240 
    241   if (!file)
    242     return FALSE;
    243 
    244   GdkPixbuf *image = file_get_current_preview_image(file);
    245 
    246   if (!image)
    247     return FALSE;
    248 
    249   int dwidth = gdk_pixbuf_get_width(image);
    250   int dheight = gdk_pixbuf_get_height(image);
    251 
    252   gint optimal_height = width * dheight / dwidth;
    253 
    254   gtk_widget_set_size_request(
    255     GTK_WIDGET(drawing_area),
    256     width,
    257     optimal_height
    258   );
    259 
    260   double ratio_width = 1.0 * width / dwidth;
    261   double ratio_height = 1.0 * height / dheight;
    262 
    263   const double ratio = ratio_width < ratio_height? ratio_width : ratio_height;
    264 
    265   dwidth = (int) (dwidth * ratio);
    266   dheight = (int) (dheight * ratio);
    267 
    268   double dx = (width - dwidth) * 0.5;
    269   double dy = (height - dheight) * 0.5;
    270 
    271   const int interp_type = (ratio >= 1.0?
    272     GDK_INTERP_NEAREST :
    273     GDK_INTERP_BILINEAR
    274   );
    275 
    276   GdkPixbuf* scaled = gdk_pixbuf_scale_simple(
    277     image,
    278     dwidth,
    279     dheight,
    280     interp_type
    281   );
    282 
    283   gtk_render_icon(context, cairo, scaled, dx, dy);
    284 
    285   cairo_fill(cairo);
    286 
    287   g_object_unref(scaled);
    288   return FALSE;
    289 }
    290 
    291 UI_MESSAGE_Handle*
    292 ui_message_new(MESSENGER_Application *app,
    293                UI_MESSAGE_Type type)
    294 {
    295   g_assert(app);
    296 
    297   UI_MESSAGE_Handle* handle = g_malloc(sizeof(UI_MESSAGE_Handle));
    298 
    299   handle->type = type;
    300 
    301   handle->timestamp = ((time_t) -1);
    302   handle->msg = NULL;
    303   handle->contact = NULL;
    304 
    305   handle->status_cb = NULL;
    306   handle->status_cls = NULL;
    307 
    308   const char *ui_builder_file;
    309 
    310   switch (handle->type)
    311   {
    312     case UI_MESSAGE_SENT:
    313       ui_builder_file = "ui/message-sent.ui";
    314       break;
    315     case UI_MESSAGE_STATUS:
    316       ui_builder_file = "ui/message-status.ui";
    317       break;
    318     default:
    319       ui_builder_file = "ui/message.ui";
    320       break;
    321   }
    322 
    323   handle->builder[0] = ui_builder_from_resource(
    324     application_get_resource_path(app, ui_builder_file)
    325   );
    326 
    327   handle->message_box = GTK_WIDGET(
    328     gtk_builder_get_object(handle->builder[0], "message_box")
    329   );
    330 
    331   handle->sender_avatar = HDY_AVATAR(
    332     gtk_builder_get_object(handle->builder[0], "sender_avatar")
    333   );
    334 
    335   handle->sender_label = GTK_LABEL(
    336     gtk_builder_get_object(handle->builder[0], "sender_label")
    337   );
    338 
    339   handle->private_image = GTK_IMAGE(
    340     gtk_builder_get_object(handle->builder[0], "private_image")
    341   );
    342 
    343   if (UI_MESSAGE_STATUS == handle->type)
    344   {
    345     handle->deny_revealer = GTK_REVEALER(
    346 	    gtk_builder_get_object(handle->builder[0], "deny_revealer")
    347     );
    348 
    349     handle->accept_revealer = GTK_REVEALER(
    350     	gtk_builder_get_object(handle->builder[0], "accept_revealer")
    351     );
    352 
    353     handle->deny_button = GTK_BUTTON(
    354 	    gtk_builder_get_object(handle->builder[0], "deny_button")
    355     );
    356 
    357     handle->accept_button = GTK_BUTTON(
    358 	    gtk_builder_get_object(handle->builder[0], "accept_button")
    359     );
    360 
    361     g_object_set_qdata(G_OBJECT(handle->accept_button), app->quarks.ui, handle);
    362     g_object_set_qdata(G_OBJECT(handle->deny_button), app->quarks.ui, handle);
    363 
    364     g_signal_connect(
    365       handle->accept_button,
    366       "clicked",
    367       G_CALLBACK(handle_accept_button_click),
    368       app
    369     );
    370 
    371     g_signal_connect(
    372       handle->deny_button,
    373       "clicked",
    374       G_CALLBACK(handle_deny_button_click),
    375       app
    376     );
    377   }
    378   else
    379   {
    380     handle->deny_revealer = NULL;
    381     handle->accept_revealer = NULL;
    382 
    383     handle->deny_button = NULL;
    384     handle->accept_button = NULL;
    385   }
    386 
    387   GtkContainer *content_box = GTK_CONTAINER(
    388     gtk_builder_get_object(handle->builder[0], "content_box")
    389   );
    390 
    391   handle->tag_flow_box = GTK_FLOW_BOX(
    392     gtk_builder_get_object(handle->builder[0], "tag_flow_box")
    393   );
    394 
    395   handle->builder[1] = ui_builder_from_resource(
    396     application_get_resource_path(app, "ui/message_content.ui")
    397   );
    398 
    399   handle->timestamp_label = GTK_LABEL(
    400     gtk_builder_get_object(handle->builder[1], "timestamp_label")
    401   );
    402 
    403   handle->read_receipt_image = GTK_IMAGE(
    404     gtk_builder_get_object(handle->builder[1], "read_receipt_image")
    405   );
    406 
    407   handle->content_stack = GTK_STACK(
    408     gtk_builder_get_object(handle->builder[1], "content_stack")
    409   );
    410 
    411   handle->text_label = GTK_LABEL(
    412     gtk_builder_get_object(handle->builder[1], "text_label")
    413   );
    414 
    415   handle->file_revealer = GTK_REVEALER(
    416     gtk_builder_get_object(handle->builder[1], "file_revealer")
    417   );
    418 
    419   handle->filename_label = GTK_LABEL(
    420     gtk_builder_get_object(handle->builder[1], "filename_label")
    421   );
    422 
    423   handle->file_progress_bar = GTK_PROGRESS_BAR(
    424     gtk_builder_get_object(handle->builder[1], "file_progress_bar")
    425   );
    426 
    427   handle->file_button = GTK_BUTTON(
    428     gtk_builder_get_object(handle->builder[1], "file_button")
    429   );
    430 
    431   g_signal_connect(
    432     handle->file_button,
    433     "clicked",
    434     G_CALLBACK(handle_file_button_click),
    435     app
    436   );
    437 
    438   handle->file_status_image = GTK_IMAGE(
    439     gtk_builder_get_object(handle->builder[1], "file_status_image")
    440   );
    441 
    442   g_object_set_qdata(G_OBJECT(handle->file_button), app->quarks.ui, handle);
    443 
    444   handle->preview_drawing_area = GTK_DRAWING_AREA(
    445     gtk_builder_get_object(handle->builder[1], "preview_drawing_area")
    446   );
    447 
    448   g_signal_connect(
    449     handle->preview_drawing_area,
    450     "draw",
    451     G_CALLBACK(handle_preview_drawing_area_draw),
    452     handle
    453   );
    454 
    455   handle->media_revealer = GTK_REVEALER(
    456     gtk_builder_get_object(handle->builder[1], "media_revealer")
    457   );
    458 
    459   handle->media_type_image = GTK_IMAGE(
    460     gtk_builder_get_object(handle->builder[1], "media_type_image")
    461   );
    462 
    463   handle->media_label = GTK_LABEL(
    464     gtk_builder_get_object(handle->builder[1], "media_label")
    465   );
    466 
    467   handle->media_progress_bar = GTK_PROGRESS_BAR(
    468     gtk_builder_get_object(handle->builder[1], "media_progress_bar")
    469   );
    470 
    471   handle->media_button = GTK_BUTTON(
    472     gtk_builder_get_object(handle->builder[1], "media_button")
    473   );
    474 
    475   handle->app = app;
    476 
    477   g_object_set_qdata(G_OBJECT(handle->media_button), app->quarks.ui, handle);
    478 
    479   g_signal_connect(
    480     handle->media_button,
    481     "clicked",
    482     G_CALLBACK(handle_media_button_click),
    483     app
    484   );
    485 
    486   switch (handle->type)
    487   {
    488     case UI_MESSAGE_STATUS:
    489       gtk_widget_set_visible(GTK_WIDGET(handle->timestamp_label), FALSE);
    490       break;
    491     default:
    492       break;
    493   }
    494 
    495   gtk_container_add(content_box, GTK_WIDGET(
    496     gtk_builder_get_object(handle->builder[1], "message_content_box")
    497   ));
    498 
    499   return handle;
    500 }
    501 
    502 static int
    503 _iterate_read_receipts(void *cls,
    504                        UNUSED struct GNUNET_CHAT_Message *message,
    505                        struct GNUNET_CHAT_Contact *contact,
    506                        int read_receipt)
    507 {
    508   g_assert((cls) && (message) && (contact));
    509 
    510   int *count_read_receipts = (int*) cls;
    511 
    512   if ((GNUNET_YES == read_receipt) &&
    513       (GNUNET_NO == GNUNET_CHAT_contact_is_owned(contact)))
    514     (*count_read_receipts)++;
    515 
    516   return GNUNET_YES;
    517 }
    518 
    519 void
    520 ui_message_refresh(UI_MESSAGE_Handle *handle)
    521 {
    522   g_assert(handle);
    523 
    524   if ((!(handle->msg)) ||
    525       (GNUNET_YES != GNUNET_CHAT_message_is_sent(handle->msg)))
    526     return;
    527 
    528   if (!(handle->read_receipt_image))
    529     return;
    530 
    531   int count = 0;
    532   if ((0 < GNUNET_CHAT_message_get_read_receipt(handle->msg, _iterate_read_receipts, &count)) &&
    533       (0 < count))
    534     gtk_widget_show(GTK_WIDGET(handle->read_receipt_image));
    535   else
    536     gtk_widget_hide(GTK_WIDGET(handle->read_receipt_image));
    537 }
    538 
    539 gboolean
    540 _message_media_supports_file_extension(const gchar *filename)
    541 {
    542   if (!filename)
    543     return FALSE;
    544 
    545   const char* extension = strrchr(filename, '.');
    546 
    547   if (!extension)
    548     return FALSE;
    549 
    550   if (0 == g_strcmp0(extension, ".ogg"))
    551     return TRUE;
    552   if (0 == g_strcmp0(extension, ".mp3"))
    553     return TRUE;
    554   if (0 == g_strcmp0(extension, ".wav"))
    555     return TRUE;
    556 
    557   return FALSE;
    558 }
    559 
    560 static void
    561 _update_invitation_message(UI_MESSAGE_Handle *handle,
    562                            MESSENGER_Application *app,
    563                            struct GNUNET_CHAT_Invitation *invitation)
    564 {
    565   g_assert((handle) && (app) && (invitation));
    566 
    567   enum GNUNET_GenericReturnValue accepted, rejected;
    568   accepted = GNUNET_CHAT_invitation_is_accepted(invitation);
    569   rejected = GNUNET_CHAT_invitation_is_rejected(invitation);
    570 
    571   if (handle->deny_button)
    572     gtk_widget_set_sensitive(
    573       GTK_WIDGET(handle->deny_button), 
    574       GNUNET_YES == accepted || GNUNET_YES == rejected? FALSE : TRUE
    575     );
    576 
    577   if (handle->accept_button)
    578     gtk_widget_set_sensitive(
    579       GTK_WIDGET(handle->accept_button), 
    580       GNUNET_YES == accepted || GNUNET_YES == rejected? FALSE : TRUE
    581     );
    582 
    583   if (handle->deny_revealer)
    584     gtk_revealer_set_reveal_child(
    585       GTK_REVEALER(handle->deny_revealer),
    586       GNUNET_YES == accepted || GNUNET_YES == rejected? FALSE : TRUE
    587     );
    588 
    589   if (handle->accept_revealer)
    590     gtk_revealer_set_reveal_child(
    591       GTK_REVEALER(handle->accept_revealer),
    592       GNUNET_YES == accepted || GNUNET_YES == rejected? FALSE : TRUE
    593     );
    594 
    595   if ((app->settings.accept_all_invitations) &&
    596       (GNUNET_NO == accepted) && (handle->accept_button))
    597     gtk_button_clicked(handle->accept_button);
    598 }
    599 
    600 static void
    601 _update_message_with_file(UI_MESSAGE_Handle *handle,
    602                           MESSENGER_Application *app,
    603                           struct GNUNET_CHAT_File *file)
    604 {
    605   g_assert(handle);
    606 
    607   struct GNUNET_CHAT_File *prev = g_object_get_qdata(
    608     G_OBJECT(handle->preview_drawing_area),
    609     app->quarks.data
    610   );
    611 
    612   if (prev)
    613     file_remove_widget_from_preview(file, GTK_WIDGET(handle->preview_drawing_area));
    614   if (file)
    615     file_add_widget_to_preview(file, GTK_WIDGET(handle->preview_drawing_area));
    616 
    617   g_object_set_qdata(
    618     G_OBJECT(handle->preview_drawing_area),
    619     app->quarks.data,
    620     file
    621   );
    622 }
    623 
    624 static void
    625 _update_file_message(UI_MESSAGE_Handle *handle,
    626                      MESSENGER_Application *app,
    627                      struct GNUNET_CHAT_File *file)
    628 {
    629   g_assert((handle) && (app) && (file));
    630 
    631   const char *filename = GNUNET_CHAT_file_get_name(file);
    632 
    633   uint64_t size = GNUNET_CHAT_file_get_size(file);
    634   uint64_t local_size = GNUNET_CHAT_file_get_local_size(file);
    635 
    636   gboolean autostart_download = FALSE;
    637 
    638   if ((size <= 0) || (size > local_size))
    639   {
    640     gtk_image_set_from_icon_name(
    641       handle->file_status_image,
    642       "folder-download-symbolic",
    643       GTK_ICON_SIZE_BUTTON
    644     );
    645 
    646     if ((app->settings.accept_all_files) &&
    647         (GNUNET_YES != GNUNET_CHAT_file_is_downloading(file)))
    648       autostart_download = TRUE;
    649 
    650     goto file_content;
    651   }
    652 
    653   if ((!(handle->preview_drawing_area)) ||
    654       (GNUNET_CHAT_file_get_size(file) != GNUNET_CHAT_file_get_local_size(file)))
    655     goto file_progress;
    656 
    657   file_load_preview_image(file);
    658 
    659   GdkPixbuf *image = file_get_current_preview_image(file);
    660 
    661   if (image)
    662   {
    663     gtk_widget_set_size_request(
    664       GTK_WIDGET(handle->preview_drawing_area),
    665       250,
    666       -1
    667     );
    668 
    669     gtk_stack_set_visible_child(
    670       handle->content_stack,
    671       GTK_WIDGET(handle->preview_drawing_area)
    672     );
    673 
    674     _update_message_with_file(handle, app, file);
    675     return;
    676   }
    677 
    678   if (_message_media_supports_file_extension(filename))
    679   {
    680     gtk_image_set_from_icon_name(
    681       handle->media_type_image,
    682       "audio-x-generic-symbolic",
    683       GTK_ICON_SIZE_DND
    684     );
    685 
    686     goto media_content;
    687   }
    688 
    689   if (!ui_play_media_window_supports_file_extension(filename))
    690     goto file_progress;
    691 
    692 media_content:
    693   ui_label_set_text(handle->media_label, filename);
    694 
    695   gtk_stack_set_visible_child(
    696     handle->content_stack,
    697     GTK_WIDGET(handle->media_revealer)
    698   );
    699 
    700   gtk_revealer_set_reveal_child(handle->media_revealer, TRUE);
    701 
    702   g_object_set_qdata(
    703     G_OBJECT(handle->media_progress_bar),
    704     app->quarks.data,
    705     file
    706   );
    707 
    708   return;
    709 
    710 file_progress:
    711   gtk_progress_bar_set_fraction(handle->file_progress_bar, 1.0);
    712 
    713   gtk_image_set_from_icon_name(
    714     handle->file_status_image,
    715     "document-open-symbolic",
    716     GTK_ICON_SIZE_BUTTON
    717   );
    718 
    719 file_content:
    720   ui_label_set_text(handle->filename_label, filename);
    721 
    722   gtk_stack_set_visible_child(
    723     handle->content_stack,
    724     GTK_WIDGET(handle->file_revealer)
    725   );
    726 
    727   gtk_revealer_set_reveal_child(handle->file_revealer, TRUE);
    728 
    729   g_object_set_qdata(
    730     G_OBJECT(handle->file_progress_bar),
    731     app->quarks.data,
    732     file
    733   );
    734 
    735   if (autostart_download)
    736     gtk_button_clicked(handle->file_button);
    737 }
    738 
    739 void
    740 ui_message_update(UI_MESSAGE_Handle *handle,
    741                   MESSENGER_Application *app,
    742                   struct GNUNET_CHAT_Message *msg)
    743 {
    744   g_assert((handle) && (app));
    745 
    746   struct GNUNET_CHAT_File *file = NULL;
    747   struct GNUNET_CHAT_Invitation *invitation = NULL;
    748 
    749   if (handle->msg)
    750     GNUNET_CHAT_message_set_user_pointer(handle->msg, NULL);
    751 
    752   handle->msg = msg;
    753 
    754   if (msg)
    755     GNUNET_CHAT_message_set_user_pointer(msg, handle);
    756 
    757   ui_message_refresh(handle);
    758 
    759   if (msg)
    760   {
    761     if (GNUNET_YES == GNUNET_CHAT_message_is_private(msg))
    762       gtk_widget_show(GTK_WIDGET(handle->private_image));
    763     
    764     invitation = GNUNET_CHAT_message_get_invitation(msg);
    765     file = GNUNET_CHAT_message_get_file(msg);
    766 
    767     handle->timestamp = GNUNET_CHAT_message_get_timestamp(msg);
    768 
    769     if (handle->accept_button)
    770       g_object_set_qdata(G_OBJECT(handle->accept_button), app->quarks.data, invitation);
    771 
    772     g_object_set_qdata(G_OBJECT(handle->message_box), app->quarks.data, file);
    773   }
    774   else
    775   {
    776     if (handle->accept_button)
    777       invitation = (struct GNUNET_CHAT_Invitation*) (
    778         g_object_get_qdata(G_OBJECT(handle->accept_button), app->quarks.data)
    779       );
    780 
    781     file = (struct GNUNET_CHAT_File*) (
    782 	    g_object_get_qdata(G_OBJECT(handle->message_box), app->quarks.data)
    783     );
    784   }
    785 
    786   if (invitation)
    787     _update_invitation_message(handle, app, invitation);
    788 
    789   if (file)
    790     _update_file_message(handle, app, file);
    791 }
    792 
    793 void
    794 ui_message_set_contact(UI_MESSAGE_Handle *handle,
    795                        struct GNUNET_CHAT_Contact *contact)
    796 {
    797   g_assert(handle);
    798 
    799   if (handle->contact)
    800   {
    801     contact_remove_name_avatar_from_info(handle->contact, handle->sender_avatar);
    802     contact_remove_name_label_from_info(handle->contact, handle->sender_label);
    803     contact_remove_visible_widget_to_info(handle->contact, handle->message_box);
    804   }
    805 
    806   if (contact)
    807   {
    808     contact_add_name_avatar_to_info(contact, handle->sender_avatar);
    809     contact_add_name_label_to_info(contact, handle->sender_label);
    810     contact_add_visible_widget_to_info(contact, handle->message_box);
    811   }
    812 
    813   handle->contact = contact;
    814 }
    815 
    816 void
    817 ui_message_set_status_callback(UI_MESSAGE_Handle *handle,
    818                                UI_MESSAGE_StatusCallback cb,
    819                                gpointer cls)
    820 {
    821   g_assert(handle);
    822 
    823   handle->status_cb = cb;
    824   handle->status_cls = cls;
    825 }
    826 
    827 void
    828 ui_message_add_tag(UI_MESSAGE_Handle *handle,
    829                    MESSENGER_Application *app,
    830                    struct GNUNET_CHAT_Message *tag_message)
    831 {
    832   g_assert((handle) && (app) && (tag_message));
    833 
    834   if ((GNUNET_CHAT_KIND_TAG != GNUNET_CHAT_message_get_kind(tag_message)) ||
    835       (GNUNET_CHAT_message_get_target(tag_message) != handle->msg))
    836     return;
    837 
    838   const char *tag_value = GNUNET_CHAT_message_get_text(tag_message);
    839 
    840   if ((!tag_value) || (!*tag_value))
    841     return;
    842 
    843   UI_TAG_Handle *tag = ui_tag_new(app);
    844   ui_tag_set_message(tag, app, tag_message);
    845 
    846   gtk_container_add(GTK_CONTAINER(handle->tag_flow_box), GTK_WIDGET(tag->tag_label));
    847   gtk_widget_show_all(GTK_WIDGET(tag->tag_label));
    848 }
    849 
    850 static void
    851 _remove_tag_from_message(UI_MESSAGE_Handle *handle,
    852                          MESSENGER_Application *app,
    853                          GtkWidget *child)
    854 {
    855   g_assert((handle) && (app) && (child));
    856 
    857   GList *items = gtk_container_get_children(GTK_CONTAINER(child));
    858   UI_TAG_Handle *tag = NULL;
    859 
    860   if (items)
    861   {
    862     GtkLabel *tag_label = GTK_LABEL(items->data);
    863 
    864     tag = g_object_get_qdata(
    865       G_OBJECT(tag_label),
    866       app->quarks.ui
    867     );
    868 
    869     g_list_free(items);
    870   }
    871 
    872   gtk_container_remove(GTK_CONTAINER(handle->tag_flow_box), child);
    873 
    874   ui_tag_delete(tag);
    875   gtk_widget_destroy(child);
    876 }
    877 
    878 void
    879 ui_message_remove_tag(UI_MESSAGE_Handle *handle,
    880                       MESSENGER_Application *app,
    881                       struct GNUNET_CHAT_Message *tag_message)
    882 {
    883   g_assert((handle) && (app) && (tag_message));
    884 
    885   if ((GNUNET_CHAT_KIND_TAG != GNUNET_CHAT_message_get_kind(tag_message)) ||
    886       (GNUNET_CHAT_message_get_target(tag_message) != handle->msg))
    887     return;
    888   
    889   GList *children = gtk_container_get_children(GTK_CONTAINER(handle->tag_flow_box));
    890 
    891   if (!children)
    892     return;
    893 
    894   GtkWidget *removable = NULL;
    895 
    896   GList *list = children;
    897   while (list)
    898   {
    899     GtkFlowBoxChild *child = GTK_FLOW_BOX_CHILD(list->data);
    900     GList *items = gtk_container_get_children(GTK_CONTAINER(child));
    901 
    902     if (items)
    903     {
    904       GtkLabel *tag_label = GTK_LABEL(items->data);
    905 
    906       const struct GNUNET_CHAT_Message *msg = g_object_get_qdata(
    907         G_OBJECT(tag_label),
    908         app->quarks.data
    909       );
    910 
    911       if (tag_message == msg)
    912         removable = GTK_WIDGET(child);
    913 
    914       g_list_free(items);
    915     }
    916 
    917     list = list->next;
    918 
    919     if (removable)
    920       break;
    921   }
    922 
    923   if (children)
    924     g_list_free(children);
    925 
    926   if (!removable)
    927     return;
    928 
    929   _remove_tag_from_message(handle, app, removable);
    930 }
    931 
    932 void
    933 ui_message_delete(UI_MESSAGE_Handle *handle,
    934                   MESSENGER_Application *app)
    935 {
    936   g_assert((handle) && (app));
    937 
    938   _update_message_with_file(handle, app, NULL);
    939   ui_message_set_contact(handle, NULL);
    940 
    941   GList *children = gtk_container_get_children(GTK_CONTAINER(handle->tag_flow_box));
    942 
    943   GList *list = children;
    944   while (list)
    945   {
    946     GtkWidget *child = GTK_WIDGET(list->data);
    947 
    948     if (child)
    949       _remove_tag_from_message(handle, app, child);
    950 
    951     list = list->next;
    952   }
    953 
    954   if (children)
    955     g_list_free(children);
    956 
    957   g_object_unref(handle->builder[1]);
    958   g_object_unref(handle->builder[0]);
    959 
    960   g_free(handle);
    961 }