messenger-gtk

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

chat.c (51153B)


      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/chat.c
     23  */
     24 
     25 #include "chat.h"
     26 
     27 #include <gdk/gdkkeysyms.h>
     28 #include <gnunet/gnunet_chat_lib.h>
     29 #include <stdlib.h>
     30 
     31 #include "chat_entry.h"
     32 #include "chat_title.h"
     33 #include "file_entry.h"
     34 #include "file_load_entry.h"
     35 #include "media_preview.h"
     36 #include "message.h"
     37 #include "messenger.h"
     38 #include "picker.h"
     39 #include "account_entry.h"
     40 
     41 #include "../application.h"
     42 #include "../file.h"
     43 #include "../ui.h"
     44 
     45 static void
     46 handle_chat_details_folded(GObject* object,
     47                            GParamSpec* pspec,
     48                            gpointer user_data)
     49 {
     50   g_assert((object) && (pspec) && (user_data));
     51 
     52   HdyFlap* flap = HDY_FLAP(object);
     53   UI_CHAT_Handle *handle = (UI_CHAT_Handle*) user_data;
     54   UI_MESSENGER_Handle *messenger = &(handle->app->ui.messenger);
     55 
     56   const gboolean revealed = hdy_flap_get_reveal_flap(flap);
     57 
     58   hdy_leaflet_set_can_swipe_back(messenger->leaflet_title, !revealed);
     59   hdy_leaflet_set_can_swipe_back(messenger->leaflet_chat, !revealed);
     60 
     61   if (handle->title)
     62   {
     63     gtk_widget_set_sensitive(
     64       GTK_WIDGET(handle->title->back_button),
     65       !revealed
     66     );
     67 
     68     gtk_widget_set_sensitive(
     69       GTK_WIDGET(handle->title->chat_search_button),
     70       !revealed
     71     );
     72   }
     73 
     74   GValue value = G_VALUE_INIT;
     75   g_value_init(&value, G_TYPE_BOOLEAN);
     76   g_value_set_boolean(&value, !revealed);
     77 
     78   gtk_container_child_set_property(
     79     GTK_CONTAINER(messenger->leaflet_title),
     80     GTK_WIDGET(messenger->nav_bar),
     81     "navigatable",
     82     &value
     83   );
     84 
     85   gtk_container_child_set_property(
     86     GTK_CONTAINER(messenger->leaflet_chat),
     87     messenger->nav_box,
     88     "navigatable",
     89     &value
     90   );
     91 
     92   g_value_unset(&value);
     93 }
     94 
     95 static gboolean
     96 _flap_chat_details_reveal_switch(gpointer user_data)
     97 {
     98   g_assert(user_data);
     99 
    100   UI_CHAT_Handle *handle = (UI_CHAT_Handle*) user_data;
    101   HdyFlap* flap = handle->flap_chat_details;
    102 
    103   gboolean revealed = hdy_flap_get_reveal_flap(flap);
    104 
    105   hdy_flap_set_reveal_flap(flap, !revealed);
    106 
    107   gtk_widget_set_sensitive(GTK_WIDGET(handle->messages_listbox), TRUE);
    108   return FALSE;
    109 }
    110 
    111 static void
    112 handle_chat_details_via_button_click(UNUSED GtkButton* button,
    113                                      gpointer user_data)
    114 {
    115   g_assert(user_data);
    116 
    117   UI_CHAT_Handle *handle = (UI_CHAT_Handle*) user_data;
    118 
    119   gtk_widget_set_sensitive(GTK_WIDGET(handle->messages_listbox), FALSE);
    120   util_idle_add(
    121     G_SOURCE_FUNC(_flap_chat_details_reveal_switch),
    122     handle
    123   );
    124 }
    125 
    126 static void
    127 handle_chat_contacts_listbox_row_activated(GtkListBox *listbox,
    128                                            GtkListBoxRow *row,
    129                                            gpointer user_data)
    130 {
    131   g_assert((listbox) && (row) && (user_data));
    132 
    133   UI_CHAT_Handle *handle = (UI_CHAT_Handle*) user_data;
    134   MESSENGER_Application *app = handle->app;
    135 
    136   GtkTextView *text_view = GTK_TEXT_VIEW(
    137     g_object_get_qdata(G_OBJECT(listbox), app->quarks.widget)
    138   );
    139 
    140   if (!text_view)
    141     return;
    142 
    143   if (!gtk_list_box_row_get_selectable(row))
    144   {
    145     ui_invite_contact_dialog_init(app, &(app->ui.invite_contact));
    146 
    147     g_object_set_qdata(
    148       G_OBJECT(app->ui.invite_contact.contacts_listbox),
    149       app->quarks.widget,
    150       text_view
    151     );
    152 
    153     gtk_widget_show(GTK_WIDGET(app->ui.invite_contact.dialog));
    154     return;
    155   }
    156 
    157   struct GNUNET_CHAT_Contact *contact = (struct GNUNET_CHAT_Contact*) (
    158     g_object_get_qdata(G_OBJECT(row), app->quarks.data)
    159   );
    160 
    161   if (!contact)
    162     return;
    163 
    164   hdy_flap_set_reveal_flap(handle->flap_chat_details, FALSE);
    165 
    166   application_chat_lock(app);
    167 
    168   ui_contact_info_dialog_init(app, &(app->ui.contact_info));
    169   ui_contact_info_dialog_update(&(app->ui.contact_info), contact, FALSE);
    170 
    171   application_chat_unlock(app);
    172 
    173   gtk_widget_show(GTK_WIDGET(app->ui.contact_info.dialog));
    174 }
    175 
    176 static void
    177 handle_chat_messages_listbox_size_allocate(UNUSED GtkWidget *widget,
    178                                            UNUSED GdkRectangle *allocation,
    179                                            gpointer user_data)
    180 {
    181   g_assert(user_data);
    182 
    183   UI_CHAT_Handle *handle = (UI_CHAT_Handle*) user_data;
    184 
    185   GtkAdjustment *adjustment = gtk_scrolled_window_get_vadjustment(
    186       handle->chat_scrolled_window
    187   );
    188 
    189   const gdouble value = gtk_adjustment_get_value(adjustment);
    190   const gdouble upper = gtk_adjustment_get_upper(adjustment);
    191   const gdouble page_size = gtk_adjustment_get_page_size(adjustment);
    192 
    193   const gdouble edge_value = upper - page_size;
    194 
    195   if (value >= handle->edge_value)
    196     gtk_adjustment_set_value(adjustment, edge_value);
    197 
    198   handle->edge_value = upper - page_size;
    199 }
    200 
    201 static void
    202 handle_reveal_identity_button_click(GtkButton *button,
    203                                     gpointer user_data)
    204 {
    205   g_assert((button) && (user_data));
    206 
    207   UI_CHAT_Handle *handle = (UI_CHAT_Handle*) user_data;
    208   MESSENGER_Application *app = handle->app;
    209 
    210   struct GNUNET_CHAT_Contact *contact = (struct GNUNET_CHAT_Contact*) (
    211     g_object_get_qdata(G_OBJECT(button), app->quarks.data)
    212   );
    213 
    214   if (!contact)
    215     return;
    216 
    217   hdy_flap_set_reveal_flap(handle->flap_chat_details, FALSE);
    218 
    219   application_chat_lock(app);
    220 
    221   ui_contact_info_dialog_init(app, &(app->ui.contact_info));
    222   ui_contact_info_dialog_update(&(app->ui.contact_info), contact, TRUE);
    223 
    224   application_chat_unlock(app);
    225 
    226   gtk_widget_show(GTK_WIDGET(app->ui.contact_info.dialog));
    227 }
    228 
    229 static void
    230 handle_discourse_button_click(GtkButton *button,
    231                               gpointer user_data)
    232 {
    233   g_assert((button) && (user_data));
    234 
    235   UI_CHAT_Handle *handle = (UI_CHAT_Handle*) user_data;
    236   MESSENGER_Application *app = handle->app;
    237 
    238   hdy_flap_set_reveal_flap(handle->flap_chat_details, FALSE);
    239 
    240   ui_discourse_window_init(app, &(app->ui.discourse));
    241   ui_discourse_window_update(&(app->ui.discourse), handle->context);
    242 
    243   gtk_widget_show(GTK_WIDGET(app->ui.discourse.window));
    244 }
    245 
    246 static void
    247 handle_block_button_click(UNUSED GtkButton *button,
    248                           gpointer user_data)
    249 {
    250   g_assert(user_data);
    251 
    252   UI_CHAT_Handle *handle = (UI_CHAT_Handle*) user_data;
    253 
    254   struct GNUNET_CHAT_Contact *contact = (struct GNUNET_CHAT_Contact*) (
    255     g_object_get_qdata(G_OBJECT(handle->block_stack), handle->app->quarks.data)
    256   );
    257 
    258   if (!contact)
    259     return;
    260 
    261   application_chat_lock(handle->app);
    262   GNUNET_CHAT_contact_set_blocked(contact, GNUNET_YES);
    263   application_chat_unlock(handle->app);
    264 
    265   gtk_stack_set_visible_child(handle->block_stack, GTK_WIDGET(handle->unblock_button));
    266 }
    267 
    268 static void
    269 handle_unblock_button_click(UNUSED GtkButton *button,
    270                             gpointer user_data)
    271 {
    272   g_assert(user_data);
    273 
    274   UI_CHAT_Handle *handle = (UI_CHAT_Handle*) user_data;
    275 
    276   struct GNUNET_CHAT_Contact *contact = (struct GNUNET_CHAT_Contact*) (
    277     g_object_get_qdata(G_OBJECT(handle->block_stack), handle->app->quarks.data)
    278   );
    279 
    280   if (!contact)
    281     return;
    282 
    283   application_chat_lock(handle->app);
    284   GNUNET_CHAT_contact_set_blocked(contact, GNUNET_NO);
    285   application_chat_unlock(handle->app);
    286 
    287   gtk_stack_set_visible_child(handle->block_stack, GTK_WIDGET(handle->block_button));
    288 }
    289 
    290 static void
    291 handle_leave_chat_button_click(UNUSED GtkButton *button,
    292                                gpointer user_data)
    293 {
    294   g_assert(user_data);
    295 
    296   UI_CHAT_Handle *handle = (UI_CHAT_Handle*) user_data;
    297 
    298   if ((!handle) || (!(handle->context)))
    299     return;
    300 
    301   application_chat_lock(handle->app);
    302 
    303   struct GNUNET_CHAT_Contact *contact = GNUNET_CHAT_context_get_contact(
    304     handle->context
    305   );
    306 
    307   struct GNUNET_CHAT_Group *group = GNUNET_CHAT_context_get_group(
    308     handle->context
    309   );
    310 
    311   if (contact)
    312     GNUNET_CHAT_contact_delete(contact);
    313   else if (group)
    314     GNUNET_CHAT_group_leave(group);
    315 
    316   application_chat_unlock(handle->app);
    317 
    318   UI_CHAT_ENTRY_Handle *entry = GNUNET_CHAT_context_get_user_pointer(
    319     handle->context
    320   );
    321 
    322   if ((!entry) || (!(handle->app)))
    323     return;
    324 
    325   ui_chat_entry_dispose(entry, handle->app);
    326 }
    327 
    328 static gint
    329 handle_chat_messages_sort(GtkListBoxRow* row0,
    330                           GtkListBoxRow* row1,
    331                           gpointer user_data)
    332 {
    333   g_assert((row0) && (row1) && (user_data));
    334 
    335   MESSENGER_Application *app = (MESSENGER_Application*) user_data;
    336 
    337   UI_MESSAGE_Handle *message0 = (UI_MESSAGE_Handle*) (
    338     g_object_get_qdata(G_OBJECT(row0), app->quarks.ui)
    339   );
    340 
    341   UI_MESSAGE_Handle *message1 = (UI_MESSAGE_Handle*) (
    342     g_object_get_qdata(G_OBJECT(row1), app->quarks.ui)
    343   );
    344 
    345   if ((!message0) || (!message1))
    346     return 0;
    347 
    348   time_t timestamp0 = message0->timestamp;
    349   time_t timestamp1 = message1->timestamp;
    350 
    351   const double diff = difftime(timestamp0, timestamp1);
    352 
    353   if (diff < -0.0)
    354     return -1;
    355   else if (diff > +0.0)
    356     return +1;
    357   else
    358     return 0;
    359 }
    360 
    361 struct FilterTags
    362 {
    363   const gchar *filter;
    364   gboolean matching;
    365 };
    366 
    367 static enum GNUNET_GenericReturnValue
    368 _iterate_message_tags(void *cls,
    369                       struct GNUNET_CHAT_Message *message)
    370 {
    371   g_assert((cls) && (message));
    372 
    373   struct FilterTags *filterTags = (struct FilterTags*) cls;
    374 
    375   const char *text = GNUNET_CHAT_message_get_text(message);
    376   if (!text)
    377     return GNUNET_YES;
    378 
    379   gchar *_text = g_locale_to_utf8(text, -1, NULL, NULL, NULL);
    380   if (!_text)
    381     return GNUNET_YES;
    382 
    383   if (g_strstr_len(_text, -1, filterTags->filter))
    384     filterTags->matching = TRUE;
    385 
    386   g_free(_text);
    387   return filterTags->matching? GNUNET_NO : GNUNET_YES;
    388 }
    389 
    390 static gboolean
    391 handle_chat_messages_filter(GtkListBoxRow *row,
    392                             gpointer user_data)
    393 {
    394   g_assert((row) && (user_data));
    395 
    396   MESSENGER_Application *app = (MESSENGER_Application*) user_data;
    397 
    398   GtkListBox *listbox = GTK_LIST_BOX(gtk_widget_get_parent(GTK_WIDGET(row)));
    399 
    400   if (!listbox)
    401     return TRUE;
    402 
    403   UI_CHAT_Handle *chat = (UI_CHAT_Handle*) (
    404     g_object_get_qdata(G_OBJECT(listbox), app->quarks.ui)
    405   );
    406 
    407   if (!chat)
    408     return TRUE;
    409 
    410   const gchar *filter = gtk_entry_get_text(
    411     GTK_ENTRY(chat->chat_search_entry)
    412   );
    413 
    414   if (!filter)
    415     return TRUE;
    416 
    417   UI_MESSAGE_Handle *message = (UI_MESSAGE_Handle*) (
    418     g_object_get_qdata(G_OBJECT(row), app->quarks.ui)
    419   );
    420 
    421   if (!message)
    422     return TRUE;
    423 
    424   const gchar *sender = gtk_label_get_text(message->sender_label);
    425   const gchar *text = gtk_label_get_text(message->text_label);
    426 
    427   gboolean result = FALSE;
    428 
    429   if (sender)
    430     result |= g_str_match_string(filter, sender, TRUE);
    431 
    432   if (text)
    433     result |= g_str_match_string(filter, text, TRUE);
    434 
    435   if (('#' == *filter) && (message->msg))
    436   {
    437     struct FilterTags filterTags;
    438     filterTags.filter = &(filter[1]);
    439     filterTags.matching = FALSE;
    440 
    441     application_chat_lock(app);
    442 
    443     GNUNET_CHAT_message_iterate_tags(
    444       message->msg,
    445       _iterate_message_tags,
    446       &filterTags
    447     );
    448 
    449     application_chat_unlock(app);
    450 
    451     result |= filterTags.matching;
    452   }
    453 
    454   return result;
    455 }
    456 
    457 static void
    458 handle_chat_messages_selected_rows_changed(GtkListBox *listbox,
    459                                            gpointer user_data)
    460 {
    461   g_assert((listbox) && (user_data));
    462 
    463   UI_CHAT_TITLE_Handle *handle = (UI_CHAT_TITLE_Handle*) user_data;
    464 
    465   GList *selected = gtk_list_box_get_selected_rows(listbox);
    466   uint32_t count = 0;
    467 
    468   GList *item = selected;
    469   while (item)
    470   {
    471     count++;
    472     item = item->next;
    473   }
    474 
    475   if (selected)
    476     g_list_free(selected);
    477 
    478   GString *counter = g_string_new("");
    479   g_string_append_printf(counter, "%u", count);
    480   gtk_label_set_text(handle->selection_count_label, counter->str);
    481   g_string_free(counter, TRUE);
    482 
    483   gtk_widget_set_sensitive(GTK_WIDGET(handle->selection_tag_button), count == 1);
    484 
    485   if (count > 0)
    486     gtk_stack_set_visible_child(handle->chat_title_stack, handle->selection_box);
    487   else
    488     gtk_stack_set_visible_child(handle->chat_title_stack, handle->title_box);
    489 }
    490 
    491 static void
    492 handle_attach_file_button_click(GtkButton *button,
    493                                 gpointer user_data)
    494 {
    495   g_assert((button) && (user_data));
    496 
    497   MESSENGER_Application *app = (MESSENGER_Application*) user_data;
    498 
    499   GtkTextView *text_view = GTK_TEXT_VIEW(
    500     g_object_get_qdata(G_OBJECT(button), app->quarks.widget)
    501   );
    502 
    503   if (!text_view)
    504     return;
    505 
    506   GtkWidget *dialog = gtk_file_chooser_dialog_new(
    507     _("Select file"),
    508     GTK_WINDOW(app->ui.messenger.main_window),
    509     GTK_FILE_CHOOSER_ACTION_OPEN,
    510     _("Cancel"),
    511     GTK_RESPONSE_CANCEL,
    512     _("Confirm"),
    513     GTK_RESPONSE_ACCEPT,
    514     NULL
    515   );
    516 
    517   if (GTK_RESPONSE_ACCEPT != gtk_dialog_run(GTK_DIALOG(dialog)))
    518     goto close_dialog;
    519 
    520   gchar *filename = gtk_file_chooser_get_filename(
    521     GTK_FILE_CHOOSER(dialog)
    522   );
    523 
    524   if (!filename)
    525     return;
    526 
    527   ui_send_file_dialog_init(app, &(app->ui.send_file));
    528   ui_send_file_dialog_update(&(app->ui.send_file), filename);
    529 
    530   g_free(filename);
    531 
    532   g_object_set_qdata(
    533     G_OBJECT(app->ui.send_file.send_button),
    534     app->quarks.widget,
    535     text_view
    536   );
    537 
    538   gtk_widget_show(GTK_WIDGET(app->ui.send_file.dialog));
    539 
    540 close_dialog:
    541   gtk_widget_destroy(dialog);
    542 }
    543 
    544 static void
    545 _update_send_record_symbol(GtkTextBuffer *buffer,
    546                            GtkImage *symbol,
    547                            gboolean picker_revealed)
    548 {
    549   g_assert((buffer) && (symbol));
    550 
    551   GtkTextIter start, end;
    552   gtk_text_buffer_get_start_iter(buffer, &start);
    553   gtk_text_buffer_get_end_iter(buffer, &end);
    554 
    555   gchar *text = gtk_text_buffer_get_text(buffer, &start, &end, TRUE);
    556 
    557   gtk_image_set_from_icon_name(
    558     symbol,
    559     (0 < strlen(text)) || (picker_revealed)?
    560     "mail-send-symbolic" :
    561     "audio-input-microphone-symbolic",
    562     GTK_ICON_SIZE_BUTTON
    563   );
    564 
    565   g_free(text);
    566 }
    567 
    568 static void
    569 handle_send_text_buffer_changed(GtkTextBuffer *buffer,
    570                                 gpointer user_data)
    571 {
    572   g_assert((buffer) && (user_data));
    573 
    574   UI_CHAT_Handle *handle = (UI_CHAT_Handle*) user_data;
    575 
    576   _update_send_record_symbol(
    577     buffer,
    578     handle->send_record_symbol,
    579     gtk_revealer_get_child_revealed(handle->picker_revealer)
    580   );
    581 }
    582 
    583 static gboolean
    584 _send_text_from_view(MESSENGER_Application *app,
    585                      UI_CHAT_Handle *handle,
    586                      GtkTextView *text_view,
    587                      gint64 action_time)
    588 {
    589   g_assert((app) && (handle) && (text_view));
    590 
    591   GtkTextBuffer *buffer = gtk_text_view_get_buffer(text_view);
    592 
    593   GtkTextIter start, end;
    594   gtk_text_buffer_get_start_iter(buffer, &start);
    595   gtk_text_buffer_get_end_iter(buffer, &end);
    596 
    597   gchar *text = gtk_text_buffer_get_text(buffer, &start, &end, TRUE);
    598 
    599   if (0 == strlen(text))
    600   {
    601     g_free(text);
    602     return FALSE;
    603   }
    604 
    605   if (action_time >= UI_CHAT_SEND_BUTTON_HOLD_INTERVAL)
    606   {
    607     gtk_popover_popup(handle->send_popover);
    608     return FALSE;
    609   }
    610 
    611   if (handle->context)
    612   {
    613     application_chat_lock(app);
    614     GNUNET_CHAT_context_send_text(handle->context, text);
    615     application_chat_unlock(app);
    616   }
    617 
    618   g_free(text);
    619   gtk_text_buffer_delete(buffer, &start, &end);
    620   return TRUE;
    621 }
    622 
    623 static void
    624 _drop_any_recording(UI_CHAT_Handle *handle)
    625 {
    626   g_assert(handle);
    627 
    628   if ((handle->play_pipeline) && (handle->playing))
    629   {
    630     gst_element_set_state(handle->play_pipeline, GST_STATE_NULL);
    631     handle->playing = FALSE;
    632   }
    633 
    634   _update_send_record_symbol(
    635     gtk_text_view_get_buffer(handle->send_text_view),
    636     handle->send_record_symbol,
    637     FALSE
    638   );
    639 
    640   gtk_stack_set_visible_child(handle->send_stack, handle->send_text_box);
    641 
    642   if (handle->recording_filename[0])
    643     remove(handle->recording_filename);
    644 
    645   handle->recording_filename[0] = 0;
    646   handle->recorded = FALSE;
    647 }
    648 
    649 static void
    650 handle_sending_recording_upload_file(UNUSED void *cls,
    651                                      struct GNUNET_CHAT_File *file,
    652                                      uint64_t completed,
    653                                      uint64_t size)
    654 {
    655   g_assert(file);
    656 
    657   UI_FILE_LOAD_ENTRY_Handle *file_load = cls;
    658 
    659   gtk_progress_bar_set_fraction(
    660     file_load->load_progress_bar,
    661     1.0 * completed / size
    662   );
    663 
    664   file_update_upload_info(file, completed, size);
    665 
    666   if ((completed >= size) && (file_load->chat_title))
    667     ui_chat_title_remove_file_load(file_load->chat_title, file_load);
    668 }
    669 
    670 static void
    671 handle_send_record_button_click(GtkButton *button,
    672                                 gpointer user_data)
    673 {
    674   g_assert((button) && (user_data));
    675 
    676   MESSENGER_Application *app = (MESSENGER_Application*) user_data;
    677 
    678   UI_CHAT_Handle *handle = (UI_CHAT_Handle*) (
    679     g_object_get_qdata(G_OBJECT(button), app->quarks.ui)
    680   );
    681 
    682   if ((handle->recorded) && (handle->context) &&
    683       (handle->recording_filename[0]) &&
    684       (!gtk_revealer_get_child_revealed(handle->picker_revealer)))
    685   {
    686     UI_FILE_LOAD_ENTRY_Handle *file_load = ui_file_load_entry_new(app);
    687 
    688     ui_label_set_text(file_load->file_label, handle->recording_filename);
    689     gtk_progress_bar_set_fraction(file_load->load_progress_bar, 0.0);
    690 
    691     application_chat_lock(app);
    692 
    693     struct GNUNET_CHAT_File *file = GNUNET_CHAT_context_send_file(
    694       handle->context,
    695       handle->recording_filename,
    696       handle_sending_recording_upload_file,
    697       file_load
    698     );
    699 
    700     if (file)
    701     {
    702       file_create_info(file);
    703 
    704       ui_chat_title_add_file_load(handle->title, file_load);
    705     }
    706     else if (file_load)
    707       ui_file_load_entry_delete(file_load);
    708 
    709     application_chat_unlock(app);
    710 
    711     _drop_any_recording(handle);
    712     return;
    713   }
    714 
    715   if (gtk_stack_get_visible_child(handle->send_stack) != handle->send_text_box)
    716     return;
    717 
    718   GtkTextView *text_view = GTK_TEXT_VIEW(
    719     g_object_get_qdata(G_OBJECT(button), app->quarks.widget)
    720   );
    721 
    722   _send_text_from_view(app, handle, text_view, handle->send_pressed_time);
    723 }
    724 
    725 static void
    726 handle_send_later_button_click(GtkButton *button,
    727                                gpointer user_data)
    728 {
    729   g_assert((button) && (user_data));
    730 
    731   MESSENGER_Application *app = (MESSENGER_Application*) user_data;
    732 
    733   UI_CHAT_Handle *handle = (UI_CHAT_Handle*) (
    734     g_object_get_qdata(G_OBJECT(button), app->quarks.ui)
    735   );
    736 
    737   handle->send_pressed_time = 0;
    738 
    739   if (gtk_widget_is_visible(GTK_WIDGET(handle->send_popover)))
    740     gtk_popover_popdown(handle->send_popover);
    741 
    742   if (gtk_stack_get_visible_child(handle->send_stack) != handle->send_text_box)
    743     return;
    744 
    745   // TODO
    746 }
    747 
    748 static void
    749 handle_send_now_button_click(GtkButton *button,
    750                              gpointer user_data)
    751 {
    752   g_assert((button) && (user_data));
    753 
    754   MESSENGER_Application *app = (MESSENGER_Application*) user_data;
    755 
    756   UI_CHAT_Handle *handle = (UI_CHAT_Handle*) (
    757     g_object_get_qdata(G_OBJECT(button), app->quarks.ui)
    758   );
    759 
    760   if (gtk_widget_is_visible(GTK_WIDGET(handle->send_popover)))
    761     gtk_popover_popdown(handle->send_popover);
    762 
    763   if (gtk_stack_get_visible_child(handle->send_stack) != handle->send_text_box)
    764     return;
    765 
    766   GtkTextView *text_view = GTK_TEXT_VIEW(
    767     g_object_get_qdata(G_OBJECT(handle->send_record_button), app->quarks.widget)
    768   );
    769 
    770   _send_text_from_view(app, handle, text_view, 0);
    771 }
    772 
    773 static gboolean
    774 handle_send_record_button_pressed(GtkWidget *widget,
    775                                   GdkEvent *event,
    776                                   gpointer user_data)
    777 {
    778   g_assert((widget) && (event) && (user_data));
    779 
    780   GdkEventButton *ev = (GdkEventButton*) event;
    781   MESSENGER_Application *app = (MESSENGER_Application*) user_data;
    782 
    783   if (1 != ev->button)
    784     return FALSE;
    785 
    786   GtkTextView *text_view = GTK_TEXT_VIEW(
    787     g_object_get_qdata(G_OBJECT(widget), app->quarks.widget)
    788   );
    789 
    790   UI_CHAT_Handle *handle = (UI_CHAT_Handle*) (
    791     g_object_get_qdata(G_OBJECT(widget), app->quarks.ui)
    792   );
    793 
    794   handle->send_pressed_time = g_get_monotonic_time();
    795 
    796   GtkTextBuffer *buffer = gtk_text_view_get_buffer(text_view);
    797 
    798   GtkTextIter start, end;
    799   gtk_text_buffer_get_start_iter(buffer, &start);
    800   gtk_text_buffer_get_end_iter(buffer, &end);
    801 
    802   gchar *text = gtk_text_buffer_get_text(buffer, &start, &end, TRUE);
    803   const size_t text_len = strlen(text);
    804 
    805   g_free(text);
    806 
    807   if (0 < text_len)
    808     return FALSE;
    809 
    810   if ((handle->recorded) || (!(handle->record_pipeline)) ||
    811       (handle->recording_filename[0]) ||
    812       (gtk_revealer_get_child_revealed(handle->picker_revealer)) ||
    813       (handle->send_text_box != gtk_stack_get_visible_child(handle->send_stack)))
    814     return FALSE;
    815 
    816   strcpy(handle->recording_filename, "/tmp/rec_XXXXXX.ogg");
    817 
    818   int fd = mkstemps(handle->recording_filename, 4);
    819 
    820   if (-1 == fd)
    821     return FALSE;
    822   else
    823     close(fd);
    824 
    825   if ((handle->play_pipeline) && (handle->playing))
    826   {
    827     gst_element_set_state(handle->play_pipeline, GST_STATE_NULL);
    828     handle->playing = FALSE;
    829   }
    830 
    831   gtk_image_set_from_icon_name(
    832     handle->play_pause_symbol,
    833     "media-playback-start-symbolic",
    834     GTK_ICON_SIZE_BUTTON
    835   );
    836 
    837   gtk_image_set_from_icon_name(
    838     handle->send_record_symbol,
    839     "media-record-symbolic",
    840     GTK_ICON_SIZE_BUTTON
    841   );
    842 
    843   gtk_label_set_text(handle->recording_label, "00:00:00");
    844   gtk_progress_bar_set_fraction(handle->recording_progress_bar, 0.0);
    845 
    846   gtk_widget_set_sensitive(GTK_WIDGET(handle->recording_play_button), FALSE);
    847   gtk_stack_set_visible_child(handle->send_stack, handle->send_recording_box);
    848 
    849   g_object_set(
    850     G_OBJECT(handle->record_sink),
    851     "location",
    852     handle->recording_filename,
    853     NULL
    854   );
    855 
    856   gst_element_set_state(handle->record_pipeline, GST_STATE_PLAYING);
    857 
    858   return TRUE;
    859 }
    860 
    861 static gboolean
    862 handle_send_record_button_released(GtkWidget *widget,
    863                                    GdkEvent *event,
    864                                    gpointer user_data)
    865 {
    866   g_assert((widget) && (event) && (user_data));
    867 
    868   GdkEventButton *ev = (GdkEventButton*) event;
    869   MESSENGER_Application *app = (MESSENGER_Application*) user_data;
    870 
    871   UI_CHAT_Handle *handle = (UI_CHAT_Handle*) (
    872     g_object_get_qdata(G_OBJECT(widget), app->quarks.ui)
    873   );
    874 
    875   if ((gtk_stack_get_visible_child(handle->send_stack) == handle->send_text_box) &&
    876       (3 == ev->button))
    877   {
    878     handle->send_pressed_time = UI_CHAT_SEND_BUTTON_HOLD_INTERVAL;
    879 
    880     handle_send_record_button_click(GTK_BUTTON(widget), user_data);
    881     return FALSE;
    882   }
    883   else if (1 != ev->button)
    884     return FALSE;
    885 
    886   GtkTextView *text_view = GTK_TEXT_VIEW(
    887     g_object_get_qdata(G_OBJECT(widget), app->quarks.widget)
    888   );
    889 
    890   handle->send_pressed_time = g_get_monotonic_time() - handle->send_pressed_time;
    891 
    892   GtkTextBuffer *buffer = gtk_text_view_get_buffer(text_view);
    893 
    894   GtkTextIter start, end;
    895   gtk_text_buffer_get_start_iter(buffer, &start);
    896   gtk_text_buffer_get_end_iter(buffer, &end);
    897 
    898   gchar *text = gtk_text_buffer_get_text(buffer, &start, &end, TRUE);
    899   const size_t text_len = strlen(text);
    900 
    901   g_free(text);
    902 
    903   if (0 < text_len)
    904     return FALSE;
    905 
    906   if ((handle->recorded) || (!(handle->record_pipeline)) ||
    907       (!(handle->recording_filename[0])) ||
    908       (gtk_revealer_get_child_revealed(handle->picker_revealer)) ||
    909       (handle->send_recording_box != gtk_stack_get_visible_child(
    910 	  handle->send_stack)))
    911     return FALSE;
    912 
    913   gtk_widget_set_sensitive(GTK_WIDGET(handle->recording_play_button), TRUE);
    914 
    915   gst_element_set_state(handle->record_pipeline, GST_STATE_NULL);
    916   handle->recorded = TRUE;
    917 
    918   gtk_image_set_from_icon_name(
    919     handle->send_record_symbol,
    920     "mail-send-symbolic",
    921     GTK_ICON_SIZE_BUTTON
    922   );
    923 
    924   return TRUE;
    925 }
    926 
    927 static gboolean
    928 handle_send_text_key_press (GtkWidget *widget,
    929                             GdkEventKey *event,
    930                             gpointer user_data)
    931 {
    932   g_assert((widget) && (event) && (user_data));
    933 
    934   MESSENGER_Application *app = (MESSENGER_Application*) user_data;
    935 
    936   if ((event->state & GDK_SHIFT_MASK) ||
    937       ((event->keyval != GDK_KEY_Return) &&
    938        (event->keyval != GDK_KEY_KP_Enter)))
    939     return FALSE;
    940   
    941   UI_CHAT_Handle *handle = (UI_CHAT_Handle*) (
    942     g_object_get_qdata(G_OBJECT(widget), app->quarks.ui)
    943   );
    944 
    945   return _send_text_from_view(app, handle, GTK_TEXT_VIEW(widget), 0);
    946 }
    947 
    948 static void
    949 handle_recording_close_button_click(UNUSED GtkButton *button,
    950                                     gpointer user_data)
    951 {
    952   g_assert(user_data);
    953 
    954   UI_CHAT_Handle *handle = (UI_CHAT_Handle*) user_data;
    955 
    956   _drop_any_recording(handle);
    957 }
    958 
    959 static void
    960 _stop_playing_recording(UI_CHAT_Handle *handle,
    961                         gboolean reset_bar)
    962 {
    963   g_assert(handle);
    964 
    965   gst_element_set_state(handle->play_pipeline, GST_STATE_NULL);
    966   handle->playing = FALSE;
    967 
    968   gtk_image_set_from_icon_name(
    969     handle->play_pause_symbol,
    970     "media-playback-start-symbolic",
    971     GTK_ICON_SIZE_BUTTON
    972   );
    973 
    974   gtk_progress_bar_set_fraction(
    975     handle->recording_progress_bar,
    976     reset_bar? 0.0 : 1.0
    977   );
    978 
    979   if (handle->play_timer)
    980   {
    981     util_source_remove(handle->play_timer);
    982     handle->play_timer = 0;
    983   }
    984 }
    985 
    986 static void
    987 handle_recording_play_button_click(UNUSED GtkButton *button,
    988                                    gpointer user_data)
    989 {
    990   g_assert(user_data);
    991 
    992   UI_CHAT_Handle *handle = (UI_CHAT_Handle*) user_data;
    993 
    994   if ((!(handle->recorded)) || (!(handle->play_pipeline)))
    995     return;
    996 
    997   if (handle->playing)
    998     _stop_playing_recording(handle, TRUE);
    999   else if (handle->recording_filename[0])
   1000   {
   1001     GString* uri = g_string_new("file://");
   1002     g_string_append(uri, handle->recording_filename);
   1003 
   1004     g_object_set(
   1005       G_OBJECT(handle->play_pipeline),
   1006       "uri",
   1007       uri->str,
   1008       NULL
   1009     );
   1010 
   1011     g_string_free(uri, TRUE);
   1012 
   1013     gst_element_set_state(handle->play_pipeline, GST_STATE_PLAYING);
   1014     handle->playing = TRUE;
   1015 
   1016     gtk_image_set_from_icon_name(
   1017 	    handle->play_pause_symbol,
   1018     	"media-playback-stop-symbolic",
   1019     	GTK_ICON_SIZE_BUTTON
   1020     );
   1021   }
   1022 }
   1023 
   1024 static void
   1025 handle_search_entry_search_changed(UNUSED GtkSearchEntry* search_entry,
   1026                                    gpointer user_data)
   1027 {
   1028   g_assert(user_data);
   1029 
   1030   GtkListBox *listbox = GTK_LIST_BOX(user_data);
   1031 
   1032   gtk_list_box_invalidate_filter(listbox);
   1033 }
   1034 
   1035 static void
   1036 handle_picker_button_click(UNUSED GtkButton *button,
   1037                            gpointer user_data)
   1038 {
   1039   g_assert(user_data);
   1040 
   1041   UI_CHAT_Handle *handle = (UI_CHAT_Handle*) user_data;
   1042 
   1043   gboolean reveal = !gtk_revealer_get_child_revealed(handle->picker_revealer);
   1044 
   1045   gtk_revealer_set_reveal_child(handle->picker_revealer, reveal);
   1046 
   1047   _update_send_record_symbol(
   1048     gtk_text_view_get_buffer(handle->send_text_view),
   1049     handle->send_record_symbol,
   1050     reveal
   1051   );
   1052 }
   1053 
   1054 static gboolean
   1055 _record_timer_func(gpointer user_data)
   1056 {
   1057   g_assert(user_data);
   1058 
   1059   UI_CHAT_Handle *handle = (UI_CHAT_Handle*) user_data;
   1060 
   1061   GString *time_string = g_string_new(NULL);
   1062 
   1063   g_string_printf(
   1064     time_string,
   1065     "%02u:%02u:%02u",
   1066     (handle->record_time / 3600),
   1067     (handle->record_time / 60) % 60,
   1068     (handle->record_time % 60)
   1069   );
   1070 
   1071   gtk_label_set_text(handle->recording_label, time_string->str);
   1072   g_string_free(time_string, TRUE);
   1073 
   1074   if (!(handle->recorded))
   1075   {
   1076     handle->record_time++;
   1077     handle->record_timer = util_timeout_add_seconds(
   1078       1,
   1079       _record_timer_func,
   1080       handle
   1081     );
   1082   }
   1083   else
   1084     handle->record_timer = 0;
   1085 
   1086   return FALSE;
   1087 }
   1088 
   1089 static gboolean
   1090 _play_timer_func(gpointer user_data)
   1091 {
   1092   g_assert(user_data);
   1093 
   1094   UI_CHAT_Handle *handle = (UI_CHAT_Handle*) user_data;
   1095   gint64 pos, len;
   1096 
   1097   handle->play_timer = 0;
   1098 
   1099   if (!(handle->play_pipeline))
   1100     return FALSE;
   1101 
   1102   if (!gst_element_query_position(handle->play_pipeline, GST_FORMAT_TIME, &pos))
   1103     return FALSE;
   1104 
   1105   if (!gst_element_query_duration(handle->play_pipeline, GST_FORMAT_TIME, &len))
   1106     return FALSE;
   1107 
   1108   if (pos < len)
   1109     gtk_progress_bar_set_fraction(
   1110       handle->recording_progress_bar,
   1111       1.0 * pos / len
   1112     );
   1113   else
   1114     gtk_progress_bar_set_fraction(
   1115       handle->recording_progress_bar,
   1116       1.0
   1117     );
   1118 
   1119   if (handle->playing)
   1120     handle->play_timer = util_timeout_add(
   1121       10,
   1122       _play_timer_func,
   1123       handle
   1124     );
   1125 
   1126   return FALSE;
   1127 }
   1128 
   1129 static gboolean
   1130 handle_record_bus_watch(UNUSED GstBus *bus,
   1131                         GstMessage *msg,
   1132                         gpointer data)
   1133 {
   1134   g_assert((msg) && (data));
   1135 
   1136   UI_CHAT_Handle *handle = (UI_CHAT_Handle*) data;
   1137   GstMessageType type = GST_MESSAGE_TYPE(msg);
   1138 
   1139   switch (type)
   1140   {
   1141     case GST_MESSAGE_STREAM_START:
   1142       handle->record_time = 0;
   1143       handle->record_timer = util_idle_add(
   1144         _record_timer_func,
   1145         handle
   1146       );
   1147 
   1148       break;
   1149     default:
   1150       break;
   1151   }
   1152 
   1153   return TRUE;
   1154 }
   1155 
   1156 static gboolean
   1157 handle_play_bus_watch(UNUSED GstBus *bus,
   1158                       GstMessage *msg,
   1159                       gpointer data)
   1160 {
   1161   UI_CHAT_Handle *handle = (UI_CHAT_Handle*) data;
   1162   GstMessageType type = GST_MESSAGE_TYPE(msg);
   1163 
   1164   switch (type)
   1165   {
   1166     case GST_MESSAGE_STATE_CHANGED:
   1167     {
   1168       GstState old_state, new_state, pending_state;
   1169       gst_message_parse_state_changed(msg, &old_state, &new_state, &pending_state);
   1170 
   1171       if (GST_STATE_PLAYING == new_state)
   1172         handle->play_timer = util_idle_add(
   1173           _play_timer_func,
   1174           handle
   1175         );
   1176       else if (GST_STATE_PLAYING == old_state)
   1177         _stop_playing_recording(handle, FALSE);
   1178       break;
   1179     }
   1180     case GST_MESSAGE_EOS:
   1181       if (handle->playing)
   1182 	      _stop_playing_recording(handle, FALSE);
   1183       break;
   1184     default:
   1185       break;
   1186   }
   1187 
   1188   return TRUE;
   1189 }
   1190 
   1191 static void
   1192 _setup_gst_pipelines(UI_CHAT_Handle *handle)
   1193 {
   1194   g_assert(handle);
   1195 
   1196   handle->record_pipeline = gst_parse_launch(
   1197     "autoaudiosrc ! audioconvert ! vorbisenc ! oggmux ! filesink name=sink",
   1198     NULL
   1199   );
   1200 
   1201   handle->record_sink = gst_bin_get_by_name(
   1202     GST_BIN(handle->record_pipeline), "sink"
   1203   );
   1204 
   1205   {
   1206     GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(handle->record_pipeline));
   1207 
   1208     handle->record_watch = gst_bus_add_watch(
   1209       bus,
   1210       handle_record_bus_watch,
   1211       handle
   1212     );
   1213 
   1214     gst_object_unref(bus);
   1215   }
   1216 
   1217   handle->play_pipeline = gst_element_factory_make("playbin", NULL);
   1218   handle->play_sink = gst_element_factory_make("autoaudiosink", "asink");
   1219 
   1220   if ((!(handle->play_pipeline)) || (!(handle->play_sink)))
   1221     return;
   1222 
   1223   g_object_set(
   1224       G_OBJECT(handle->play_pipeline),
   1225       "audio-sink",
   1226       handle->play_sink,
   1227       NULL
   1228   );
   1229 
   1230   {
   1231     GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(handle->play_pipeline));
   1232 
   1233     handle->play_watch = gst_bus_add_watch(
   1234       bus,
   1235       handle_play_bus_watch,
   1236       handle
   1237     );
   1238 
   1239     gst_object_unref(bus);
   1240   }
   1241 }
   1242 
   1243 UI_CHAT_Handle*
   1244 ui_chat_new(MESSENGER_Application *app,
   1245             struct GNUNET_CHAT_Context *context)
   1246 {
   1247   g_assert((app) && (context));
   1248 
   1249   UI_CHAT_Handle *handle = g_malloc(sizeof(UI_CHAT_Handle));
   1250 
   1251   memset(handle, 0, sizeof(*handle));
   1252 
   1253   _setup_gst_pipelines(handle);
   1254 
   1255   handle->app = app;
   1256   handle->context = context;
   1257 
   1258   handle->title = ui_chat_title_new(handle->app, handle);
   1259 
   1260   handle->builder = ui_builder_from_resource(
   1261     application_get_resource_path(app, "ui/chat.ui")
   1262   );
   1263 
   1264   handle->chat_box = GTK_WIDGET(
   1265     gtk_builder_get_object(handle->builder, "chat_box")
   1266   );
   1267 
   1268   handle->flap_chat_details = HDY_FLAP(
   1269     gtk_builder_get_object(handle->builder, "flap_chat_details")
   1270   );
   1271 
   1272   g_signal_connect(
   1273     handle->flap_chat_details,
   1274     "notify::reveal-flap",
   1275     G_CALLBACK(handle_chat_details_folded),
   1276     handle
   1277   );
   1278 
   1279   handle->chat_search_bar = HDY_SEARCH_BAR(
   1280     gtk_builder_get_object(handle->builder, "chat_search_bar")
   1281   );
   1282 
   1283   handle->chat_search_entry = GTK_SEARCH_ENTRY(
   1284     gtk_builder_get_object(handle->builder, "chat_search_entry")
   1285   );
   1286 
   1287   handle->chat_details_label = GTK_LABEL(
   1288     gtk_builder_get_object(handle->builder, "chat_details_label")
   1289   );
   1290 
   1291   handle->hide_chat_details_button = GTK_BUTTON(
   1292     gtk_builder_get_object(handle->builder, "hide_chat_details_button")
   1293   );
   1294 
   1295   g_signal_connect(
   1296     handle->hide_chat_details_button,
   1297     "clicked",
   1298     G_CALLBACK(handle_chat_details_via_button_click),
   1299     handle
   1300   );
   1301 
   1302   handle->chat_details_contacts_box = GTK_BOX(
   1303     gtk_builder_get_object(handle->builder, "chat_details_contacts_box")
   1304   );
   1305 
   1306   handle->chat_details_files_box = GTK_BOX(
   1307     gtk_builder_get_object(handle->builder, "chat_details_files_box")
   1308   );
   1309 
   1310   handle->chat_details_media_box = GTK_BOX(
   1311     gtk_builder_get_object(handle->builder, "chat_details_media_box")
   1312   );
   1313 
   1314   handle->chat_details_avatar = HDY_AVATAR(
   1315     gtk_builder_get_object(handle->builder, "chat_details_avatar")
   1316   );
   1317 
   1318   handle->reveal_identity_button = GTK_BUTTON(
   1319     gtk_builder_get_object(handle->builder, "reveal_identity_button")
   1320   );
   1321 
   1322   g_signal_connect(
   1323     handle->reveal_identity_button,
   1324     "clicked",
   1325     G_CALLBACK(handle_reveal_identity_button_click),
   1326     handle
   1327   );
   1328 
   1329   handle->discourse_button = GTK_BUTTON(
   1330     gtk_builder_get_object(handle->builder, "discourse_button")
   1331   );
   1332 
   1333   g_signal_connect(
   1334     handle->discourse_button,
   1335     "clicked",
   1336     G_CALLBACK(handle_discourse_button_click),
   1337     handle
   1338   );
   1339 
   1340   handle->block_stack = GTK_STACK(
   1341     gtk_builder_get_object(handle->builder, "block_stack")
   1342   );
   1343 
   1344   handle->block_button = GTK_BUTTON(
   1345     gtk_builder_get_object(handle->builder, "block_button")
   1346   );
   1347 
   1348   g_signal_connect(
   1349     handle->block_button,
   1350     "clicked",
   1351     G_CALLBACK(handle_block_button_click),
   1352     handle
   1353   );
   1354 
   1355   handle->unblock_button = GTK_BUTTON(
   1356     gtk_builder_get_object(handle->builder, "unblock_button")
   1357   );
   1358 
   1359   g_signal_connect(
   1360     handle->unblock_button,
   1361     "clicked",
   1362     G_CALLBACK(handle_unblock_button_click),
   1363     handle
   1364   );
   1365 
   1366   handle->leave_chat_button = GTK_BUTTON(
   1367     gtk_builder_get_object(handle->builder, "leave_chat_button")
   1368   );
   1369 
   1370   g_signal_connect(
   1371     handle->leave_chat_button,
   1372     "clicked",
   1373     G_CALLBACK(handle_leave_chat_button_click),
   1374     handle
   1375   );
   1376 
   1377   handle->chat_notifications_switch = GTK_SWITCH(
   1378     gtk_builder_get_object(handle->builder, "chat_notifications_switch")
   1379   );
   1380 
   1381   handle->chat_scrolled_window = GTK_SCROLLED_WINDOW(
   1382     gtk_builder_get_object(handle->builder, "chat_scrolled_window")
   1383   );
   1384 
   1385   handle->chat_contacts_listbox = GTK_LIST_BOX(
   1386     gtk_builder_get_object(handle->builder, "chat_contacts_listbox")
   1387   );
   1388 
   1389   g_signal_connect(
   1390     handle->chat_contacts_listbox,
   1391     "row-activated",
   1392     G_CALLBACK(handle_chat_contacts_listbox_row_activated),
   1393     handle
   1394   );
   1395 
   1396   handle->chat_files_listbox = GTK_LIST_BOX(
   1397     gtk_builder_get_object(handle->builder, "chat_files_listbox")
   1398   );
   1399 
   1400   handle->chat_media_flowbox = GTK_FLOW_BOX(
   1401     gtk_builder_get_object(handle->builder, "chat_media_flowbox")
   1402   );
   1403 
   1404   handle->messages_listbox = GTK_LIST_BOX(
   1405     gtk_builder_get_object(handle->builder, "messages_listbox")
   1406   );
   1407 
   1408   gtk_list_box_set_sort_func(
   1409     handle->messages_listbox,
   1410     handle_chat_messages_sort,
   1411     app,
   1412     NULL
   1413   );
   1414 
   1415   gtk_list_box_set_filter_func(
   1416     handle->messages_listbox,
   1417     handle_chat_messages_filter,
   1418     app,
   1419     NULL
   1420   );
   1421 
   1422   g_signal_connect(
   1423     handle->chat_search_entry,
   1424     "search-changed",
   1425     G_CALLBACK(handle_search_entry_search_changed),
   1426     handle->messages_listbox
   1427   );
   1428 
   1429   g_signal_connect(
   1430     handle->messages_listbox,
   1431     "selected-rows-changed",
   1432     G_CALLBACK(handle_chat_messages_selected_rows_changed),
   1433     handle->title
   1434   );
   1435 
   1436   g_signal_connect(
   1437     handle->messages_listbox,
   1438     "size-allocate",
   1439     G_CALLBACK(handle_chat_messages_listbox_size_allocate),
   1440     handle
   1441   );
   1442 
   1443   handle->send_stack = GTK_STACK(
   1444     gtk_builder_get_object(handle->builder, "send_stack")
   1445   );
   1446 
   1447   handle->send_text_box = GTK_WIDGET(
   1448     gtk_builder_get_object(handle->builder, "send_text_box")
   1449   );
   1450 
   1451   handle->send_recording_box = GTK_WIDGET(
   1452     gtk_builder_get_object(handle->builder, "send_recording_box")
   1453   );
   1454 
   1455   handle->attach_file_button = GTK_BUTTON(
   1456     gtk_builder_get_object(handle->builder, "attach_file_button")
   1457   );
   1458 
   1459   g_signal_connect(
   1460     handle->attach_file_button,
   1461     "clicked",
   1462     G_CALLBACK(handle_attach_file_button_click),
   1463     app
   1464   );
   1465 
   1466   handle->send_text_view = GTK_TEXT_VIEW(
   1467     gtk_builder_get_object(handle->builder, "send_text_view")
   1468   );
   1469 
   1470   handle->emoji_button = GTK_BUTTON(
   1471     gtk_builder_get_object(handle->builder, "emoji_button")
   1472   );
   1473 
   1474   handle->send_record_button = GTK_BUTTON(
   1475     gtk_builder_get_object(handle->builder, "send_record_button")
   1476   );
   1477 
   1478   handle->send_record_symbol = GTK_IMAGE(
   1479     gtk_builder_get_object(handle->builder, "send_record_symbol")
   1480   );
   1481 
   1482   handle->send_popover = GTK_POPOVER(
   1483     gtk_builder_get_object(handle->builder, "send_popover")
   1484   );
   1485 
   1486   handle->send_now_button = GTK_BUTTON(
   1487     gtk_builder_get_object(handle->builder, "send_now_button")
   1488   );
   1489 
   1490   handle->send_later_button = GTK_BUTTON(
   1491     gtk_builder_get_object(handle->builder, "send_later_button")
   1492   );
   1493 
   1494   GtkTextBuffer *send_text_buffer = gtk_text_view_get_buffer(
   1495     handle->send_text_view
   1496   );
   1497 
   1498   g_signal_connect(
   1499     send_text_buffer,
   1500     "changed",
   1501     G_CALLBACK(handle_send_text_buffer_changed),
   1502     handle
   1503   );
   1504 
   1505   g_signal_connect(
   1506     handle->send_record_button,
   1507     "clicked",
   1508     G_CALLBACK(handle_send_record_button_click),
   1509     app
   1510   );
   1511 
   1512   g_signal_connect(
   1513     handle->send_later_button,
   1514     "clicked",
   1515     G_CALLBACK(handle_send_later_button_click),
   1516     app
   1517   );
   1518 
   1519   g_signal_connect(
   1520     handle->send_now_button,
   1521     "clicked",
   1522     G_CALLBACK(handle_send_now_button_click),
   1523     app
   1524   );
   1525 
   1526   g_signal_connect(
   1527     handle->send_record_button,
   1528     "button-press-event",
   1529     G_CALLBACK(handle_send_record_button_pressed),
   1530     app
   1531   );
   1532 
   1533   g_signal_connect(
   1534     handle->send_record_button,
   1535     "button-release-event",
   1536     G_CALLBACK(handle_send_record_button_released),
   1537     app
   1538   );
   1539 
   1540   g_signal_connect(
   1541     handle->send_text_view,
   1542     "key-press-event",
   1543     G_CALLBACK(handle_send_text_key_press),
   1544     app
   1545   );
   1546 
   1547   g_object_set_qdata(
   1548     G_OBJECT(handle->chat_contacts_listbox),
   1549     app->quarks.widget,
   1550     handle->send_text_view
   1551   );
   1552 
   1553   g_object_set_qdata(
   1554     G_OBJECT(handle->attach_file_button),
   1555     app->quarks.widget,
   1556     handle->send_text_view
   1557   );
   1558 
   1559   g_object_set_qdata(
   1560     G_OBJECT(handle->send_record_button),
   1561     app->quarks.widget,
   1562     handle->send_text_view
   1563   );
   1564 
   1565   g_object_set_qdata(
   1566     G_OBJECT(handle->send_text_view),
   1567     app->quarks.data,
   1568     context
   1569   );
   1570 
   1571   g_object_set_qdata(
   1572     G_OBJECT(handle->send_text_view),
   1573     app->quarks.ui,
   1574     handle
   1575   );
   1576 
   1577   g_object_set_qdata(
   1578     G_OBJECT(handle->send_record_button),
   1579     app->quarks.ui,
   1580     handle
   1581   );
   1582 
   1583   g_object_set_qdata(
   1584     G_OBJECT(handle->send_later_button),
   1585     app->quarks.ui,
   1586     handle
   1587   );
   1588 
   1589   g_object_set_qdata(
   1590     G_OBJECT(handle->send_now_button),
   1591     app->quarks.ui,
   1592     handle
   1593   );
   1594 
   1595   g_object_set_qdata(
   1596     G_OBJECT(handle->messages_listbox),
   1597     app->quarks.ui,
   1598     handle
   1599   );
   1600 
   1601   handle->recording_close_button = GTK_BUTTON(
   1602     gtk_builder_get_object(handle->builder, "recording_close_button")
   1603   );
   1604 
   1605   g_signal_connect(
   1606     handle->recording_close_button,
   1607     "clicked",
   1608     G_CALLBACK(handle_recording_close_button_click),
   1609     handle
   1610   );
   1611 
   1612   handle->recording_play_button = GTK_BUTTON(
   1613     gtk_builder_get_object(handle->builder, "recording_play_button")
   1614   );
   1615 
   1616   g_signal_connect(
   1617     handle->recording_play_button,
   1618     "clicked",
   1619     G_CALLBACK(handle_recording_play_button_click),
   1620     handle
   1621   );
   1622 
   1623   handle->play_pause_symbol = GTK_IMAGE(
   1624     gtk_builder_get_object(handle->builder, "play_pause_symbol")
   1625   );
   1626 
   1627   handle->recording_label = GTK_LABEL(
   1628     gtk_builder_get_object(handle->builder, "recording_label")
   1629   );
   1630 
   1631   handle->recording_progress_bar = GTK_PROGRESS_BAR(
   1632     gtk_builder_get_object(handle->builder, "recording_progress_bar")
   1633   );
   1634 
   1635   handle->picker_revealer = GTK_REVEALER(
   1636     gtk_builder_get_object(handle->builder, "picker_revealer")
   1637   );
   1638 
   1639   handle->picker = ui_picker_new(app, handle);
   1640 
   1641   gtk_container_add(
   1642     GTK_CONTAINER(handle->picker_revealer),
   1643     handle->picker->picker_box
   1644   );
   1645 
   1646   g_signal_connect(
   1647     handle->emoji_button,
   1648     "clicked",
   1649     G_CALLBACK(handle_picker_button_click),
   1650     handle
   1651   );
   1652 
   1653   return handle;
   1654 }
   1655 
   1656 struct IterateChatClosure {
   1657   MESSENGER_Application *app;
   1658   GtkContainer *container;
   1659 };
   1660 
   1661 static enum GNUNET_GenericReturnValue
   1662 iterate_ui_chat_update_group_contacts(void *cls,
   1663                                       UNUSED struct GNUNET_CHAT_Group *group,
   1664                                       struct GNUNET_CHAT_Contact *contact)
   1665 {
   1666   struct IterateChatClosure *closure = (
   1667     (struct IterateChatClosure*) cls
   1668   );
   1669 
   1670   GtkListBox *listbox = GTK_LIST_BOX(closure->container);
   1671   UI_ACCOUNT_ENTRY_Handle* entry = ui_account_entry_new(closure->app);
   1672 
   1673   ui_account_entry_set_contact(entry, contact);
   1674 
   1675   gtk_list_box_prepend(listbox, entry->entry_box);
   1676 
   1677   GtkListBoxRow *row = GTK_LIST_BOX_ROW(
   1678     gtk_widget_get_parent(entry->entry_box)
   1679   );
   1680 
   1681   g_object_set_qdata(G_OBJECT(row), closure->app->quarks.data, contact);
   1682   g_object_set_qdata_full(
   1683     G_OBJECT(row),
   1684     closure->app->quarks.ui,
   1685     entry,
   1686     (GDestroyNotify) ui_account_entry_delete
   1687   );
   1688 
   1689   return GNUNET_YES;
   1690 }
   1691 
   1692 static void
   1693 _chat_update_contacts(UI_CHAT_Handle *handle,
   1694                       MESSENGER_Application *app,
   1695                       struct GNUNET_CHAT_Group* group)
   1696 {
   1697   g_assert((handle) && (app));
   1698 
   1699   GList* children = gtk_container_get_children(
   1700     GTK_CONTAINER(handle->chat_contacts_listbox)
   1701   );
   1702 
   1703   GList *item = children;
   1704   while ((item) && (item->next)) {
   1705     GtkWidget *widget = GTK_WIDGET(item->data);
   1706     item = item->next;
   1707 
   1708     gtk_container_remove(
   1709       GTK_CONTAINER(handle->chat_contacts_listbox),
   1710       widget
   1711     );
   1712   }
   1713 
   1714   if (children)
   1715     g_list_free(children);
   1716 
   1717   if (group)
   1718   {
   1719     struct IterateChatClosure closure;
   1720     closure.app = app;
   1721     closure.container = GTK_CONTAINER(handle->chat_contacts_listbox);
   1722 
   1723     GNUNET_CHAT_group_iterate_contacts(
   1724 	    group,
   1725       iterate_ui_chat_update_group_contacts,
   1726       &closure
   1727     );
   1728   }
   1729 
   1730   gtk_widget_set_visible(
   1731     GTK_WIDGET(handle->chat_details_contacts_box),
   1732     group? TRUE : FALSE
   1733   );
   1734 }
   1735 
   1736 static enum GNUNET_GenericReturnValue
   1737 iterate_ui_chat_update_context_files(void *cls,
   1738                                      struct GNUNET_CHAT_Context *context,
   1739                                      struct GNUNET_CHAT_File *file)
   1740 {
   1741   struct IterateChatClosure *closure = (
   1742     (struct IterateChatClosure*) cls
   1743   );
   1744 
   1745   GtkListBox *listbox = GTK_LIST_BOX(closure->container);
   1746   UI_FILE_ENTRY_Handle* entry = ui_file_entry_new(closure->app);
   1747   ui_file_entry_update(entry, file);
   1748 
   1749   gtk_list_box_prepend(listbox, entry->entry_box);
   1750 
   1751   GtkListBoxRow *row = GTK_LIST_BOX_ROW(
   1752     gtk_widget_get_parent(entry->entry_box)
   1753   );
   1754 
   1755   g_object_set_qdata(G_OBJECT(row), closure->app->quarks.data, file);
   1756   g_object_set_qdata_full(
   1757     G_OBJECT(row),
   1758     closure->app->quarks.ui,
   1759     entry,
   1760     (GDestroyNotify) ui_file_entry_delete
   1761   );
   1762 
   1763   return GNUNET_YES;
   1764 }
   1765 
   1766 static void
   1767 _chat_update_files(UI_CHAT_Handle *handle,
   1768                    MESSENGER_Application *app,
   1769                    struct GNUNET_CHAT_Context *context)
   1770 {
   1771   g_assert((handle) && (app));
   1772 
   1773   GList* children = gtk_container_get_children(
   1774     GTK_CONTAINER(handle->chat_files_listbox)
   1775   );
   1776 
   1777   GList *item = children;
   1778   while (item) {
   1779     GtkWidget *widget = GTK_WIDGET(item->data);
   1780     item = item->next;
   1781 
   1782     gtk_container_remove(
   1783       GTK_CONTAINER(handle->chat_files_listbox),
   1784       widget
   1785     );
   1786   }
   1787 
   1788   if (children)
   1789     g_list_free(children);
   1790 
   1791   struct IterateChatClosure closure;
   1792   closure.app = app;
   1793   closure.container = GTK_CONTAINER(handle->chat_files_listbox);
   1794 
   1795   const int count = context? GNUNET_CHAT_context_iterate_files(
   1796     context,
   1797     iterate_ui_chat_update_context_files,
   1798     &closure
   1799   ) : 0;
   1800 
   1801   gtk_widget_set_visible(
   1802     GTK_WIDGET(handle->chat_details_files_box),
   1803     count? TRUE : FALSE
   1804   );
   1805 }
   1806 
   1807 static enum GNUNET_GenericReturnValue
   1808 iterate_ui_chat_update_context_media(void *cls,
   1809                                      struct GNUNET_CHAT_Context *context,
   1810                                      struct GNUNET_CHAT_File *file)
   1811 {
   1812   struct IterateChatClosure *closure = (
   1813     (struct IterateChatClosure*) cls
   1814   );
   1815 
   1816   GtkFlowBox *flowbox = GTK_FLOW_BOX(closure->container);
   1817   UI_MEDIA_PREVIEW_Handle* handle = ui_media_preview_new(closure->app);
   1818   ui_media_preview_update(handle, file);
   1819 
   1820   GdkPixbuf *image = file_get_current_preview_image(file);
   1821 
   1822   if (!image)
   1823   {
   1824     ui_media_preview_delete(handle);
   1825     return GNUNET_YES;
   1826   }
   1827 
   1828   gtk_flow_box_insert(flowbox, handle->media_box, 0);
   1829 
   1830   GtkFlowBoxChild *child = GTK_FLOW_BOX_CHILD(
   1831     gtk_widget_get_parent(handle->media_box)
   1832   );
   1833 
   1834   g_object_set_qdata(G_OBJECT(child), closure->app->quarks.data, file);
   1835   g_object_set_qdata(
   1836     G_OBJECT(child),
   1837     closure->app->quarks.ui,
   1838     handle
   1839   );
   1840 
   1841   gtk_widget_set_size_request(GTK_WIDGET(child), 80, 80);
   1842 
   1843   gtk_widget_show_all(GTK_WIDGET(child));
   1844   return GNUNET_YES;
   1845 }
   1846 
   1847 static void
   1848 _chat_update_media(UI_CHAT_Handle *handle,
   1849                    MESSENGER_Application *app,
   1850                    struct GNUNET_CHAT_Context *context)
   1851 {
   1852   g_assert((handle) && (app));
   1853 
   1854   GList* children = gtk_container_get_children(
   1855     GTK_CONTAINER(handle->chat_media_flowbox)
   1856   );
   1857 
   1858   GList *item = children;
   1859   while (item) {
   1860     GtkWidget *widget = GTK_WIDGET(item->data);
   1861     item = item->next;
   1862 
   1863     UI_MEDIA_PREVIEW_Handle *media = g_object_get_qdata(
   1864       G_OBJECT(widget),
   1865       app->quarks.ui
   1866     );
   1867 
   1868     ui_media_preview_delete(media);
   1869 
   1870     gtk_container_remove(
   1871       GTK_CONTAINER(handle->chat_media_flowbox),
   1872       widget
   1873     );
   1874   }
   1875 
   1876   if (children)
   1877     g_list_free(children);
   1878 
   1879   struct IterateChatClosure closure;
   1880   closure.app = app;
   1881   closure.container = GTK_CONTAINER(handle->chat_media_flowbox);
   1882 
   1883   const int count = context? GNUNET_CHAT_context_iterate_files(
   1884     context,
   1885     iterate_ui_chat_update_context_media,
   1886     &closure
   1887   ) : 0;
   1888 
   1889   gtk_widget_set_visible(
   1890     GTK_WIDGET(handle->chat_details_media_box),
   1891     count? TRUE : FALSE
   1892   );
   1893 }
   1894 
   1895 static const gchar*
   1896 _chat_get_default_subtitle(UI_CHAT_Handle *handle,
   1897                            MESSENGER_Application *app,
   1898                            struct GNUNET_CHAT_Group* group)
   1899 {
   1900   g_assert((handle) && (app));
   1901 
   1902   GList *rows = gtk_container_get_children(
   1903     GTK_CONTAINER(handle->messages_listbox)
   1904   );
   1905 
   1906   if (!rows)
   1907     return NULL;
   1908 
   1909   UI_MESSAGE_Handle *last_message = NULL;
   1910   for (GList *row = rows; row; row = row->next)
   1911   {
   1912     UI_MESSAGE_Handle *message = (UI_MESSAGE_Handle*) g_object_get_qdata(
   1913       G_OBJECT(row->data), app->quarks.ui
   1914     );
   1915 
   1916     if (!message)
   1917       continue;
   1918 
   1919     ui_message_refresh(message);
   1920     last_message = message;
   1921   }
   1922 
   1923   g_list_free(rows);
   1924 
   1925   if ((!last_message) || (!(last_message->timestamp_label)))
   1926     return NULL;
   1927 
   1928   return group? NULL : gtk_label_get_text(last_message->timestamp_label);
   1929 }
   1930 
   1931 void
   1932 ui_chat_update(UI_CHAT_Handle *handle,
   1933                MESSENGER_Application *app)
   1934 {
   1935   g_assert((handle) && (app));
   1936 
   1937   struct GNUNET_CHAT_Contact* contact;
   1938   struct GNUNET_CHAT_Group* group;
   1939 
   1940   contact = GNUNET_CHAT_context_get_contact(handle->context);
   1941   group = GNUNET_CHAT_context_get_group(handle->context);
   1942 
   1943   _chat_update_contacts(handle, app, group);
   1944   _chat_update_files(handle, app, handle->context);
   1945   _chat_update_media(handle, app, handle->context);
   1946 
   1947   const gchar *subtitle = _chat_get_default_subtitle(handle, app, group);
   1948 
   1949   ui_chat_title_update(handle->title, app, subtitle);
   1950 
   1951   g_object_set_qdata(
   1952     G_OBJECT(handle->reveal_identity_button),
   1953     app->quarks.data,
   1954     contact
   1955   );
   1956 
   1957   gtk_widget_set_visible(
   1958     GTK_WIDGET(handle->reveal_identity_button),
   1959     contact? TRUE : FALSE
   1960   );
   1961 
   1962   g_object_set_qdata(
   1963     G_OBJECT(handle->block_stack),
   1964     app->quarks.data,
   1965     contact
   1966   );
   1967 
   1968   if (GNUNET_YES == GNUNET_CHAT_contact_is_blocked(contact))
   1969     gtk_stack_set_visible_child(handle->block_stack, GTK_WIDGET(handle->unblock_button));
   1970   else
   1971     gtk_stack_set_visible_child(handle->block_stack, GTK_WIDGET(handle->block_button));
   1972 
   1973   gtk_widget_set_visible(
   1974     GTK_WIDGET(handle->block_stack),
   1975     contact? TRUE : FALSE
   1976   );
   1977 
   1978   gtk_widget_set_sensitive(
   1979     GTK_WIDGET(handle->leave_chat_button),
   1980     (contact) || (group)? TRUE : FALSE
   1981   );
   1982 
   1983   const int status = GNUNET_CHAT_context_get_status(handle->context);
   1984   const gboolean activated = (GNUNET_OK == status? TRUE : FALSE);
   1985 
   1986   gtk_text_view_set_editable(handle->send_text_view, activated);
   1987   gtk_widget_set_sensitive(GTK_WIDGET(handle->send_text_view), activated);
   1988 
   1989   gtk_widget_set_sensitive(GTK_WIDGET(handle->attach_file_button), activated);
   1990   gtk_widget_set_sensitive(GTK_WIDGET(handle->emoji_button), activated);
   1991   gtk_widget_set_sensitive(GTK_WIDGET(handle->send_record_button), activated);
   1992 }
   1993 
   1994 void
   1995 ui_chat_delete(UI_CHAT_Handle *handle)
   1996 {
   1997   g_assert(handle);
   1998 
   1999   GList *message_rows = gtk_container_get_children(GTK_CONTAINER(handle->messages_listbox));
   2000   GList *row_element = message_rows;
   2001 
   2002   while (row_element)
   2003   {
   2004     GtkWidget *row = GTK_WIDGET(row_element->data);
   2005 
   2006     if (!row)
   2007       goto skip_row;
   2008 
   2009     UI_MESSAGE_Handle *message = (UI_MESSAGE_Handle*) g_object_get_qdata(
   2010       G_OBJECT(row), handle->app->quarks.ui
   2011     );
   2012 
   2013     if (message)
   2014       ui_chat_remove_message(handle, handle->app, message);
   2015 
   2016   skip_row:
   2017     row_element = row_element->next;
   2018   }
   2019 
   2020   if (message_rows)
   2021     g_list_free(message_rows);
   2022 
   2023   ui_picker_delete(handle->picker);
   2024 
   2025   _chat_update_contacts(handle, handle->app, NULL);
   2026   _chat_update_media(handle, handle->app, NULL);
   2027   _chat_update_files(handle, handle->app, NULL);
   2028 
   2029   ui_chat_title_delete(handle->title);
   2030 
   2031   g_object_unref(handle->builder);
   2032 
   2033   if (handle->record_pipeline)
   2034   {
   2035     gst_element_set_state(handle->record_pipeline, GST_STATE_NULL);
   2036     gst_object_unref(GST_OBJECT(handle->record_pipeline));
   2037   }
   2038 
   2039   if (handle->play_pipeline)
   2040   {
   2041     gst_element_set_state(handle->play_pipeline, GST_STATE_NULL);
   2042     gst_object_unref(GST_OBJECT(handle->play_pipeline));
   2043   }
   2044 
   2045   if (handle->recording_filename[0])
   2046     remove(handle->recording_filename);
   2047 
   2048   if (handle->record_timer)
   2049     util_source_remove(handle->record_timer);
   2050 
   2051   if (handle->play_timer)
   2052     util_source_remove(handle->play_timer);
   2053 
   2054   g_free(handle);
   2055 }
   2056 
   2057 void
   2058 ui_chat_add_message(UI_CHAT_Handle *handle,
   2059                     MESSENGER_Application *app,
   2060                     UI_MESSAGE_Handle *message)
   2061 {
   2062   g_assert((handle) && (message) && (message->message_box));
   2063 
   2064   gtk_container_add(
   2065     GTK_CONTAINER(handle->messages_listbox),
   2066     message->message_box
   2067   );
   2068 
   2069   GtkWidget *row = gtk_widget_get_parent(message->message_box);
   2070   g_object_set_qdata(G_OBJECT(row), app->quarks.ui, message);
   2071 
   2072   gtk_list_box_invalidate_sort(handle->messages_listbox);
   2073 }
   2074 
   2075 void
   2076 ui_chat_remove_message(UI_CHAT_Handle *handle,
   2077                        MESSENGER_Application *app,
   2078                        UI_MESSAGE_Handle *message)
   2079 {
   2080   g_assert((handle) && (message) && (message->message_box));
   2081 
   2082   GtkWidget *row = gtk_widget_get_parent(message->message_box);
   2083   g_object_set_qdata(G_OBJECT(row), app->quarks.ui, NULL);
   2084 
   2085   GtkWidget *parent = gtk_widget_get_parent(row);
   2086 
   2087   if (parent == GTK_WIDGET(handle->messages_listbox))
   2088     gtk_container_remove(GTK_CONTAINER(handle->messages_listbox), row);
   2089 
   2090   ui_message_delete(message, app);
   2091 }