messenger-gtk

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

messenger.c (24882B)


      1 /*
      2    This file is part of GNUnet.
      3    Copyright (C) 2021--2026 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/messenger.c
     23  */
     24 
     25 #include "messenger.h"
     26 
     27 #include <gnunet/gnunet_chat_lib.h>
     28 #include <gtk-3.0/gdk/gdkkeys.h>
     29 #include <stdio.h>
     30 
     31 #include "account_entry.h"
     32 #include "chat_entry.h"
     33 #include "chat_title.h"
     34 #include "contacts.h"
     35 #include "message.h"
     36 #include "new_contact.h"
     37 #include "new_group.h"
     38 #include "new_lobby.h"
     39 #include "new_platform.h"
     40 #include "settings.h"
     41 
     42 #include "../account.h"
     43 #include "../application.h"
     44 #include "../secret.h"
     45 #include "../ui.h"
     46 
     47 static void
     48 handle_user_details_folded(GObject* object,
     49                            GParamSpec* pspec,
     50                            gpointer user_data)
     51 {
     52   g_assert((object) && (pspec) && (user_data));
     53 
     54   HdyFlap* flap = HDY_FLAP(object);
     55   UI_MESSENGER_Handle *handle = (UI_MESSENGER_Handle*) user_data;
     56 
     57   const gboolean revealed = hdy_flap_get_reveal_flap(flap);
     58 
     59   gtk_widget_set_sensitive(
     60     GTK_WIDGET(handle->chats_search_entry),
     61     !revealed
     62   );
     63 
     64   gtk_widget_set_sensitive(
     65     GTK_WIDGET(handle->chats_search_button),
     66     !revealed
     67   );
     68 
     69   GValue value = G_VALUE_INIT;
     70   g_value_init(&value, G_TYPE_BOOLEAN);
     71   g_value_set_boolean(&value, !revealed);
     72 
     73   gtk_container_child_set_property(
     74     GTK_CONTAINER(handle->leaflet_title),
     75     GTK_WIDGET(handle->main_bar),
     76     "navigatable",
     77     &value
     78   );
     79 
     80   gtk_container_child_set_property(
     81     GTK_CONTAINER(handle->leaflet_chat),
     82     handle->main_box,
     83     "navigatable",
     84     &value
     85   );
     86 
     87   g_value_unset(&value);
     88 }
     89 
     90 static void
     91 handle_profile_button_click(UNUSED GtkButton* button,
     92                             gpointer user_data)
     93 {
     94   g_assert(user_data);
     95 
     96   UI_MESSENGER_Handle *handle = (UI_MESSENGER_Handle*) user_data;
     97   MESSENGER_Application *app = handle->app;
     98 
     99   hdy_flap_set_reveal_flap(handle->flap_user_details, FALSE);
    100 
    101   ui_contact_info_dialog_init(app, &(app->ui.contact_info));
    102   ui_contact_info_dialog_update(&(app->ui.contact_info), NULL, FALSE);
    103 
    104   gtk_widget_show(GTK_WIDGET(app->ui.contact_info.dialog));
    105 }
    106 
    107 static gboolean
    108 _flap_user_details_reveal_switch(gpointer user_data)
    109 {
    110   g_assert(user_data);
    111 
    112   UI_MESSENGER_Handle *handle = (UI_MESSENGER_Handle*) user_data;
    113   HdyFlap* flap = handle->flap_user_details;
    114 
    115   if (TRUE == hdy_flap_get_reveal_flap(flap)) {
    116     hdy_flap_set_reveal_flap(flap, FALSE);
    117   } else {
    118     hdy_flap_set_reveal_flap(flap, TRUE);
    119   }
    120 
    121   gtk_widget_set_sensitive(GTK_WIDGET(handle->chats_search_entry), TRUE);
    122   gtk_widget_set_sensitive(GTK_WIDGET(handle->chats_listbox), TRUE);
    123   return FALSE;
    124 }
    125 
    126 static void
    127 handle_user_details_via_button_click(UNUSED GtkButton* button,
    128                                      gpointer user_data)
    129 {
    130   g_assert(user_data);
    131 
    132   UI_MESSENGER_Handle *handle = (UI_MESSENGER_Handle*) user_data;
    133 
    134   gtk_widget_set_sensitive(GTK_WIDGET(handle->chats_search_entry), FALSE);
    135   gtk_widget_set_sensitive(GTK_WIDGET(handle->chats_listbox), FALSE);
    136   util_idle_add(
    137     G_SOURCE_FUNC(_flap_user_details_reveal_switch),
    138     handle
    139   );
    140 }
    141 
    142 static void
    143 handle_lobby_button_click(UNUSED GtkButton* button,
    144                           gpointer user_data)
    145 {
    146   g_assert(user_data);
    147 
    148   MESSENGER_Application *app = (MESSENGER_Application*) user_data;
    149 
    150   hdy_flap_set_reveal_flap(HDY_FLAP(app->ui.messenger.flap_user_details), FALSE);
    151 
    152   ui_new_lobby_dialog_init(app, &(app->ui.new_lobby));
    153 
    154   gtk_widget_show(GTK_WIDGET(app->ui.new_lobby.dialog));
    155 }
    156 
    157 static void
    158 _switch_details_revealer_visibility(UI_MESSENGER_Handle *handle,
    159                                     gboolean state)
    160 {
    161   g_assert(handle);
    162 
    163   GtkRevealer *revealer = handle->account_details_revealer;
    164   GtkImage *symbol = handle->account_details_symbol;
    165 
    166   gtk_revealer_set_reveal_child(revealer, state);
    167   gtk_image_set_from_icon_name(
    168     symbol,
    169     state?
    170     "go-up-symbolic" :
    171     "go-down-symbolic",
    172     GTK_ICON_SIZE_BUTTON
    173   );
    174 }
    175 
    176 static void
    177 handle_account_details_button_click(UNUSED GtkButton* button,
    178                                     gpointer user_data)
    179 {
    180   g_assert(user_data);
    181 
    182   UI_MESSENGER_Handle *handle = (UI_MESSENGER_Handle*) user_data;
    183 
    184   GtkRevealer *revealer = handle->account_details_revealer;
    185 
    186   gboolean old_state = gtk_revealer_get_reveal_child(revealer);
    187 
    188   _switch_details_revealer_visibility(handle, !old_state);
    189 }
    190 
    191 static void
    192 _account_secret_lookup(MESSENGER_Application *app,
    193                        const char *secret,
    194                        uint32_t secret_len,
    195                        gboolean success,
    196                        gboolean error,
    197                        gpointer user_data)
    198 {
    199   g_assert((app) && (user_data));
    200 
    201   struct GNUNET_CHAT_Account *account = user_data;
    202 
    203   if (error)
    204   {
    205     fprintf(stderr, "ERROR: Looking up secret failed\n");
    206   }
    207   else if ((success) && (secret) && (secret_len > 0))
    208   {
    209     _switch_details_revealer_visibility(&(app->ui.messenger), FALSE);
    210     hdy_flap_set_reveal_flap(HDY_FLAP(app->ui.messenger.flap_user_details), FALSE);
    211 
    212     application_chat_lock(app);
    213     GNUNET_CHAT_connect(app->chat.messenger.handle, account, secret, secret_len);
    214     application_chat_unlock(app);
    215   }
    216   else
    217   {
    218     const char *name;
    219 
    220     application_chat_lock(app);
    221     name = GNUNET_CHAT_account_get_name(account);
    222     application_chat_unlock(app);
    223 
    224     secret_operation_generate(app, name, &_account_secret_lookup, account);
    225   }
    226 }
    227 
    228 static void
    229 handle_accounts_listbox_row_activated(UNUSED GtkListBox* listbox,
    230                                       GtkListBoxRow* row,
    231                                       gpointer user_data)
    232 {
    233   g_assert((row) && (user_data));
    234 
    235   MESSENGER_Application *app = (MESSENGER_Application*) user_data;
    236 
    237   if (row == app->ui.messenger.add_account_listbox_row)
    238   {
    239     _switch_details_revealer_visibility(&(app->ui.messenger), FALSE);
    240     hdy_flap_set_reveal_flap(HDY_FLAP(app->ui.messenger.flap_user_details), FALSE);
    241 
    242     ui_new_account_dialog_init(app, &(app->ui.new_account));
    243 
    244     gtk_widget_show(GTK_WIDGET(app->ui.new_account.dialog));
    245     return;
    246   }
    247 
    248   struct GNUNET_CHAT_Account *account = (struct GNUNET_CHAT_Account*) (
    249     g_object_get_qdata(G_OBJECT(row), app->quarks.data)
    250   );
    251 
    252   if (!account)
    253     return;
    254 
    255   const struct GNUNET_CHAT_Account *current;
    256   const char *name;
    257 
    258 
    259   application_chat_lock(app);
    260   current = GNUNET_CHAT_get_connected(app->chat.messenger.handle);
    261   name = GNUNET_CHAT_account_get_name(account);
    262   application_chat_unlock(app);
    263 
    264   if (account == current)
    265     return;
    266 
    267   secret_operation_lookup(app, name, &_account_secret_lookup, account);
    268 }
    269 
    270 static void
    271 handle_new_contact_button_click(UNUSED GtkButton* button,
    272                                 gpointer user_data)
    273 {
    274   g_assert(user_data);
    275 
    276   MESSENGER_Application *app = (MESSENGER_Application*) user_data;
    277 
    278   hdy_flap_set_reveal_flap(HDY_FLAP(app->ui.messenger.flap_user_details), FALSE);
    279   ui_new_contact_dialog_init(app, &(app->ui.new_contact));
    280   gtk_widget_show(GTK_WIDGET(app->ui.new_contact.dialog));
    281 }
    282 
    283 static void
    284 handle_new_group_button_click(UNUSED GtkButton* button,
    285                               gpointer user_data)
    286 {
    287   g_assert(user_data);
    288 
    289   MESSENGER_Application *app = (MESSENGER_Application*) user_data;
    290 
    291   hdy_flap_set_reveal_flap(HDY_FLAP(app->ui.messenger.flap_user_details), FALSE);
    292   ui_new_group_dialog_init(app, &(app->ui.new_group));
    293   gtk_widget_show(GTK_WIDGET(app->ui.new_group.dialog));
    294 }
    295 
    296 static void
    297 handle_new_platform_button_click(UNUSED GtkButton* button,
    298                                  gpointer user_data)
    299 {
    300   g_assert(user_data);
    301 
    302   MESSENGER_Application *app = (MESSENGER_Application*) user_data;
    303 
    304   hdy_flap_set_reveal_flap(HDY_FLAP(app->ui.messenger.flap_user_details), FALSE);
    305   ui_new_platform_dialog_init(app, &(app->ui.new_platform));
    306   gtk_widget_show(GTK_WIDGET(app->ui.new_platform.dialog));
    307 }
    308 
    309 static void
    310 handle_contacts_button_click(UNUSED GtkButton* button,
    311                              gpointer user_data)
    312 {
    313   g_assert(user_data);
    314 
    315   MESSENGER_Application *app = (MESSENGER_Application*) user_data;
    316 
    317   hdy_flap_set_reveal_flap(HDY_FLAP(app->ui.messenger.flap_user_details), FALSE);
    318   ui_contacts_dialog_init(app, &(app->ui.contacts));
    319   gtk_widget_show(GTK_WIDGET(app->ui.contacts.dialog));
    320 }
    321 
    322 static void
    323 handle_settings_button_click(UNUSED GtkButton* button,
    324                              gpointer user_data)
    325 {
    326   g_assert(user_data);
    327 
    328   MESSENGER_Application *app = (MESSENGER_Application*) user_data;
    329 
    330   hdy_flap_set_reveal_flap(HDY_FLAP(app->ui.messenger.flap_user_details), FALSE);
    331   ui_settings_dialog_init(app, &(app->ui.settings));
    332   gtk_widget_show(GTK_WIDGET(app->ui.settings.dialog));
    333 }
    334 
    335 static void
    336 handle_about_button_click(UNUSED GtkButton* button,
    337                           gpointer user_data)
    338 {
    339   g_assert(user_data);
    340 
    341   MESSENGER_Application *app = (MESSENGER_Application*) user_data;
    342 
    343   hdy_flap_set_reveal_flap(HDY_FLAP(app->ui.messenger.flap_user_details), FALSE);
    344   ui_about_dialog_init(app, &(app->ui.about));
    345   gtk_widget_show(GTK_WIDGET(app->ui.about.dialog));
    346 }
    347 
    348 static void
    349 handle_chats_listbox_row_activated(UNUSED GtkListBox* listbox,
    350                                    GtkListBoxRow* row,
    351                                    gpointer user_data)
    352 {
    353   g_assert((row) && (user_data));
    354 
    355   MESSENGER_Application *app = (MESSENGER_Application*) user_data;
    356 
    357   if (!gtk_list_box_row_get_selectable(row))
    358     return;
    359 
    360   UI_CHAT_ENTRY_Handle *entry = (UI_CHAT_ENTRY_Handle*) (
    361     g_object_get_qdata(G_OBJECT(row), app->quarks.ui)
    362   );
    363 
    364   if ((!entry) || (!(entry->chat)) || (!(entry->chat->chat_box)))
    365     return;
    366 
    367   UI_MESSENGER_Handle *ui = &(app->ui.messenger);
    368   GList *children = gtk_container_get_children(GTK_CONTAINER(ui->leaflet_chat));
    369 
    370   if ((children) && (children->next))
    371     hdy_leaflet_set_visible_child(ui->leaflet_chat, GTK_WIDGET(children->next->data));
    372 
    373   if (children)
    374     g_list_free(children);
    375 
    376   gtk_stack_set_visible_child(ui->chats_stack, entry->chat->chat_box);
    377   gtk_stack_set_visible_child(ui->chat_title_stack, entry->chat->title->chat_title_box);
    378 }
    379 
    380 static gint
    381 handle_chats_listbox_sort_func(GtkListBoxRow* row0,
    382                                GtkListBoxRow* row1,
    383                                gpointer user_data)
    384 {
    385   g_assert((row0) && (row1) && (user_data));
    386 
    387   MESSENGER_Application *app = (MESSENGER_Application*) user_data;
    388 
    389   if ((!gtk_list_box_row_get_selectable(row0)) ||
    390       (!gtk_list_box_row_get_selectable(row1)))
    391     return 0;
    392 
    393   UI_CHAT_ENTRY_Handle *entry0 = (UI_CHAT_ENTRY_Handle*) (
    394     g_object_get_qdata(G_OBJECT(row0), app->quarks.ui)
    395   );
    396 
    397   UI_CHAT_ENTRY_Handle *entry1 = (UI_CHAT_ENTRY_Handle*) (
    398     g_object_get_qdata(G_OBJECT(row1), app->quarks.ui)
    399   );
    400 
    401   if ((!entry0) || (!entry1))
    402     return 0;
    403 
    404   time_t timestamp0 = entry0->timestamp;
    405   time_t timestamp1 = entry1->timestamp;
    406 
    407   const double diff = difftime(timestamp0, timestamp1);
    408 
    409   if (diff > +0.0)
    410     return -1;
    411   else if (diff < -0.0)
    412     return +1;
    413   else
    414     return 0;
    415 }
    416 
    417 static gboolean
    418 handle_chats_listbox_filter_func(GtkListBoxRow *row,
    419                                  gpointer user_data)
    420 {
    421   g_assert((row) && (user_data));
    422 
    423   MESSENGER_Application *app = (MESSENGER_Application*) user_data;
    424 
    425   if ((!gtk_list_box_row_get_selectable(row)) ||
    426       (gtk_list_box_row_is_selected(row)))
    427     return TRUE;
    428 
    429   const gchar *filter = gtk_entry_get_text(
    430     GTK_ENTRY(app->ui.messenger.chats_search_entry)
    431   );
    432 
    433   if (!filter)
    434     return TRUE;
    435 
    436   UI_CHAT_ENTRY_Handle *entry = (UI_CHAT_ENTRY_Handle*) (
    437     g_object_get_qdata(G_OBJECT(row), app->quarks.ui)
    438   );
    439 
    440   if ((!entry) || (!(entry->title_label)))
    441     return FALSE;
    442 
    443   const gchar *title = gtk_label_get_text(entry->title_label);
    444 
    445   if (!title)
    446     return FALSE;
    447 
    448   return g_str_match_string(filter, title, TRUE);
    449 }
    450 
    451 static void
    452 handle_search_button_click(UNUSED GtkButton *button,
    453                            gpointer user_data)
    454 {
    455   g_assert(user_data);
    456 
    457   UI_MESSENGER_Handle *handle = (UI_MESSENGER_Handle*) user_data;
    458 
    459   if (handle->search_box == gtk_stack_get_visible_child(handle->chats_title_stack))
    460   {
    461     gtk_stack_set_visible_child(handle->chats_title_stack, handle->title_box);
    462     gtk_entry_set_text(GTK_ENTRY(handle->chats_search_entry), "");
    463   }
    464   else
    465     gtk_stack_set_visible_child(handle->chats_title_stack, handle->search_box);
    466 }
    467 
    468 static void
    469 handle_chats_search_changed(UNUSED GtkSearchEntry *search,
    470                             gpointer user_data)
    471 {
    472   g_assert(user_data);
    473 
    474   GtkListBox *listbox = GTK_LIST_BOX(user_data);
    475 
    476   gtk_list_box_invalidate_filter(listbox);
    477 }
    478 
    479 static void
    480 handle_main_window_destroy(UNUSED GtkWidget *window,
    481                            gpointer user_data)
    482 {
    483   g_assert(user_data);
    484 
    485   MESSENGER_Application *app = (MESSENGER_Application*) user_data;
    486 
    487 #ifndef MESSENGER_APPLICATION_NO_PORTAL
    488   if (app->parent)
    489     xdp_parent_free(app->parent);
    490 
    491   app->parent = NULL;
    492 #endif
    493 
    494   ui_messenger_cleanup(&(app->ui.messenger));
    495   
    496   account_cleanup_infos();
    497 
    498   application_exit(app, MESSENGER_QUIT);
    499 }
    500 
    501 void
    502 ui_messenger_init(MESSENGER_Application *app,
    503                   UI_MESSENGER_Handle *handle)
    504 {
    505   g_assert((app) && (handle));
    506 
    507   memset(handle, 0, sizeof(*handle));
    508   handle->app = app;
    509 
    510   handle->builder = ui_builder_from_resource(
    511     application_get_resource_path(app, "ui/messenger.ui")
    512   );
    513 
    514   handle->main_window = GTK_APPLICATION_WINDOW(
    515     gtk_builder_get_object(handle->builder, "main_window")
    516   );
    517 
    518   gtk_window_set_startup_id(
    519     GTK_WINDOW(handle->main_window),
    520     MESSENGER_APPLICATION_ID
    521   );
    522 
    523   gtk_window_set_icon_name(
    524     GTK_WINDOW(handle->main_window),
    525     MESSENGER_APPLICATION_ID
    526   );
    527 
    528   gtk_application_add_window(
    529     app->application,
    530     GTK_WINDOW(handle->main_window)
    531   );
    532 
    533   gtk_window_set_default_size(
    534     GTK_WINDOW(handle->main_window),
    535     1100, 700
    536   );
    537 
    538   handle->leaflet_title = HDY_LEAFLET(
    539     gtk_builder_get_object(handle->builder, "leaflet_title")
    540   );
    541 
    542   handle->leaflet_chat = HDY_LEAFLET(
    543     gtk_builder_get_object(handle->builder, "leaflet_chat")
    544   );
    545 
    546   g_object_bind_property(
    547     handle->leaflet_chat,
    548     "visible_child_name",
    549     handle->leaflet_title,
    550     "visible_child_name",
    551     G_BINDING_SYNC_CREATE
    552   );
    553 
    554   handle->flap_user_details = HDY_FLAP(
    555     gtk_builder_get_object(handle->builder, "flap_user_details")
    556   );
    557 
    558   g_signal_connect(
    559     handle->flap_user_details,
    560     "notify::reveal-flap",
    561     G_CALLBACK(handle_user_details_folded),
    562     handle
    563   );
    564 
    565   handle->nav_box = GTK_WIDGET(
    566     gtk_builder_get_object(handle->builder, "nav_box")
    567   );
    568 
    569   handle->main_box = GTK_WIDGET(
    570     gtk_builder_get_object(handle->builder, "main_box")
    571   );
    572 
    573   handle->nav_bar = HDY_HEADER_BAR(
    574     gtk_builder_get_object(handle->builder, "nav_bar")
    575   );
    576 
    577   handle->main_bar = HDY_HEADER_BAR(
    578     gtk_builder_get_object(handle->builder, "main_bar")
    579   );
    580 
    581   GtkLabel* application_name_label = GTK_LABEL(
    582     gtk_builder_get_object(handle->builder, "application-name-label")
    583   );
    584   
    585   GtkLabel* application_version_label = GTK_LABEL(
    586     gtk_builder_get_object(handle->builder, "application-version-label")
    587   );
    588 
    589   gtk_label_set_text(
    590     application_name_label,
    591     MESSENGER_APPLICATION_APPNAME
    592   );
    593   
    594   gtk_label_set_text(
    595     application_version_label,
    596     MESSENGER_APPLICATION_VERSION
    597   );
    598 
    599   g_object_bind_property(
    600     handle->leaflet_chat,
    601     "folded",
    602     handle->main_bar,
    603     "show-close-button",
    604     G_BINDING_INVERT_BOOLEAN
    605   );
    606   
    607   handle->profile_button = GTK_BUTTON(
    608     gtk_builder_get_object(handle->builder, "profile_button")
    609   );
    610 
    611   g_signal_connect(
    612     handle->profile_button,
    613     "clicked",
    614     G_CALLBACK(handle_profile_button_click),
    615     handle
    616   );
    617 
    618   handle->profile_avatar = HDY_AVATAR(
    619     gtk_builder_get_object(handle->builder, "profile_avatar")
    620   );
    621 
    622   handle->profile_label = GTK_LABEL(
    623     gtk_builder_get_object(handle->builder, "profile_label")
    624   );
    625 
    626   handle->profile_key_label = GTK_LABEL(
    627     gtk_builder_get_object(handle->builder, "profile_key_label")
    628   );
    629 
    630   handle->hide_user_details_button = GTK_BUTTON(
    631     gtk_builder_get_object(handle->builder, "hide_user_details_button")
    632   );
    633 
    634   g_signal_connect(
    635     handle->hide_user_details_button,
    636     "clicked",
    637     G_CALLBACK(handle_user_details_via_button_click),
    638     handle
    639   );
    640 
    641   handle->lobby_button = GTK_BUTTON(
    642     gtk_builder_get_object(handle->builder, "lobby_button")
    643   );
    644 
    645   g_signal_connect(
    646     handle->lobby_button,
    647     "clicked",
    648     G_CALLBACK(handle_lobby_button_click),
    649     app
    650   );
    651 
    652   handle->account_details_button = GTK_BUTTON(
    653     gtk_builder_get_object(handle->builder, "account_details_button")
    654   );
    655 
    656   handle->account_details_symbol = GTK_IMAGE(
    657     gtk_builder_get_object(handle->builder, "account_details_symbol")
    658   );
    659 
    660   handle->account_details_revealer = GTK_REVEALER(
    661     gtk_builder_get_object(handle->builder, "account_details_revealer")
    662   );
    663 
    664   g_signal_connect(
    665     handle->account_details_button,
    666     "clicked",
    667     G_CALLBACK(handle_account_details_button_click),
    668     handle
    669   );
    670 
    671   handle->accounts_listbox = GTK_LIST_BOX(
    672     gtk_builder_get_object(handle->builder, "accounts_listbox")
    673   );
    674 
    675   handle->add_account_listbox_row = GTK_LIST_BOX_ROW(
    676     gtk_builder_get_object(handle->builder, "add_account_listbox_row")
    677   );
    678 
    679   g_signal_connect(
    680     handle->accounts_listbox,
    681     "row-activated",
    682     G_CALLBACK(handle_accounts_listbox_row_activated),
    683     app
    684   );
    685 
    686   handle->new_contact_button = GTK_BUTTON(
    687     gtk_builder_get_object(handle->builder, "new_contact_button")
    688   );
    689 
    690   handle->new_group_button = GTK_BUTTON(
    691     gtk_builder_get_object(handle->builder, "new_group_button")
    692   );
    693 
    694   handle->new_platform_button = GTK_BUTTON(
    695     gtk_builder_get_object(handle->builder, "new_platform_button")
    696   );
    697 
    698   g_signal_connect(
    699     handle->new_contact_button,
    700     "clicked",
    701     G_CALLBACK(handle_new_contact_button_click),
    702     app
    703   );
    704 
    705   g_signal_connect(
    706     handle->new_group_button,
    707     "clicked",
    708     G_CALLBACK(handle_new_group_button_click),
    709     app
    710   );
    711 
    712   g_signal_connect(
    713     handle->new_platform_button,
    714     "clicked",
    715     G_CALLBACK(handle_new_platform_button_click),
    716     app
    717   );
    718 
    719   handle->contacts_button = GTK_BUTTON(
    720     gtk_builder_get_object(handle->builder, "contacts_button")
    721   );
    722 
    723   handle->settings_button = GTK_BUTTON(
    724     gtk_builder_get_object(handle->builder, "settings_button")
    725   );
    726 
    727   g_signal_connect(
    728     handle->contacts_button,
    729     "clicked",
    730     G_CALLBACK(handle_contacts_button_click),
    731     app
    732   );
    733 
    734   g_signal_connect(
    735     handle->settings_button,
    736     "clicked",
    737     G_CALLBACK(handle_settings_button_click),
    738     app
    739   );
    740 
    741   handle->about_button = GTK_BUTTON(
    742     gtk_builder_get_object(handle->builder, "about_button")
    743   );
    744 
    745   g_signal_connect(
    746     handle->about_button,
    747     "clicked",
    748     G_CALLBACK(handle_about_button_click),
    749     app
    750   );
    751 
    752   handle->chats_title_stack = GTK_STACK(
    753     gtk_builder_get_object(handle->builder, "chats_title_stack")
    754   );
    755 
    756   handle->title_box = GTK_WIDGET(
    757     gtk_builder_get_object(handle->builder, "title_box")
    758   );
    759 
    760   handle->search_box = GTK_WIDGET(
    761     gtk_builder_get_object(handle->builder, "search_box")
    762   );
    763 
    764   g_object_bind_property(
    765     handle->leaflet_chat,
    766     "folded",
    767     handle->nav_bar,
    768     "hexpand",
    769     G_BINDING_SYNC_CREATE
    770   );
    771 
    772   handle->user_details_button = GTK_BUTTON(
    773     gtk_builder_get_object(handle->builder, "user_details_button")
    774   );
    775 
    776   g_signal_connect(
    777     handle->user_details_button,
    778     "clicked",
    779     G_CALLBACK(handle_user_details_via_button_click),
    780     handle
    781   );
    782 
    783   handle->chats_search_button = GTK_BUTTON(
    784     gtk_builder_get_object(handle->builder, "chats_search_button")
    785   );
    786 
    787   g_signal_connect(
    788     handle->chats_search_button,
    789     "clicked",
    790     G_CALLBACK(handle_search_button_click),
    791     handle
    792   );
    793 
    794   handle->chats_search_entry = GTK_SEARCH_ENTRY(
    795     gtk_builder_get_object(handle->builder, "chats_search_entry")
    796   );
    797 
    798   handle->search_icon_stack = GTK_STACK(
    799     gtk_builder_get_object(handle->builder, "search_icon_stack")
    800   );
    801 
    802   g_object_bind_property(
    803     handle->chats_title_stack,
    804     "visible_child_name",
    805     handle->search_icon_stack,
    806     "visible_child_name",
    807     G_BINDING_SYNC_CREATE
    808   );
    809 
    810   handle->chats_listbox = GTK_LIST_BOX(
    811     gtk_builder_get_object(handle->builder, "chats_listbox")
    812   );
    813 
    814   gtk_list_box_set_sort_func(
    815     handle->chats_listbox,
    816     handle_chats_listbox_sort_func,
    817     app,
    818     NULL
    819   );
    820 
    821   gtk_list_box_set_filter_func(
    822     handle->chats_listbox,
    823     handle_chats_listbox_filter_func,
    824     app,
    825     NULL
    826   );
    827 
    828   g_signal_connect(
    829     handle->chats_search_entry,
    830     "search-changed",
    831     G_CALLBACK(handle_chats_search_changed),
    832     handle->chats_listbox
    833   );
    834 
    835   g_signal_connect(
    836     handle->chats_listbox,
    837     "row-activated",
    838     G_CALLBACK(handle_chats_listbox_row_activated),
    839     app
    840   );
    841 
    842   handle->chats_stack = GTK_STACK(
    843     gtk_builder_get_object(handle->builder, "chats_stack")
    844   );
    845 
    846   handle->no_chat_box = GTK_WIDGET(
    847     gtk_builder_get_object(handle->builder, "no_chat_box")
    848   );
    849 
    850   handle->chat_title_stack = GTK_STACK(
    851     gtk_builder_get_object(handle->builder, "chat_title_stack")
    852   );
    853 
    854   g_signal_connect(
    855     handle->main_window,
    856     "destroy",
    857     G_CALLBACK(handle_main_window_destroy),
    858     app
    859   );
    860 }
    861 
    862 static int
    863 _messenger_iterate_accounts(void *cls,
    864                             struct GNUNET_CHAT_Handle *handle,
    865                             struct GNUNET_CHAT_Account *account)
    866 {
    867   g_assert((cls) && (handle) && (account));
    868 
    869   MESSENGER_Application *app = (MESSENGER_Application*) cls;
    870   UI_MESSENGER_Handle *ui = &(app->ui.messenger);
    871 
    872   UI_ACCOUNT_ENTRY_Handle *entry = ui_account_entry_new(app);
    873 
    874   ui_account_entry_set_account(entry, account);
    875 
    876   gtk_list_box_prepend(ui->accounts_listbox, entry->entry_box);
    877 
    878   GtkWidget *row = gtk_widget_get_parent(entry->entry_box);
    879 
    880   g_object_set_qdata(G_OBJECT(row), app->quarks.data, account);
    881 
    882   if (account == GNUNET_CHAT_get_connected(handle))
    883     gtk_widget_activate(row);
    884 
    885   ui_account_entry_delete(entry);
    886   return GNUNET_YES;
    887 }
    888 
    889 static void
    890 _clear_accounts_listbox(GtkWidget *widget,
    891                         gpointer data)
    892 {
    893   g_assert((widget) && (data));
    894 
    895   GtkListBoxRow *row = GTK_LIST_BOX_ROW(widget);
    896   GtkListBox *listbox = GTK_LIST_BOX(data);
    897 
    898   if (!gtk_list_box_row_get_selectable(row))
    899     return;
    900 
    901   gtk_container_remove(
    902     GTK_CONTAINER(listbox),
    903     widget
    904   );
    905 }
    906 
    907 static gboolean
    908 _close_messenger_missing_account(gpointer data)
    909 {
    910   g_assert(data);
    911 
    912   UI_MESSENGER_Handle *handle = (UI_MESSENGER_Handle*) data;  
    913 
    914   gtk_window_close(GTK_WINDOW(handle->main_window));
    915   return FALSE;
    916 }
    917 
    918 void
    919 ui_messenger_refresh(MESSENGER_Application *app,
    920                      UI_MESSENGER_Handle *handle)
    921 {
    922   g_assert((app) && (handle));
    923 
    924   if (!(handle->accounts_listbox))
    925     return;
    926 
    927   gtk_container_foreach(
    928     GTK_CONTAINER(handle->accounts_listbox),
    929     _clear_accounts_listbox,
    930     handle->accounts_listbox
    931   );
    932 
    933   application_chat_lock(app);
    934 
    935   GNUNET_CHAT_iterate_accounts(
    936     app->chat.messenger.handle,
    937     _messenger_iterate_accounts,
    938     app
    939   );
    940 
    941   application_chat_unlock(app);
    942 
    943   if (gtk_list_box_get_selected_row(handle->accounts_listbox))
    944     return;
    945   
    946   gtk_widget_hide(GTK_WIDGET(handle->main_window));
    947 
    948   handle->account_refresh = util_idle_add(
    949     G_SOURCE_FUNC(_close_messenger_missing_account),
    950     handle
    951   );
    952 }
    953 
    954 gboolean
    955 ui_messenger_is_context_active(UI_MESSENGER_Handle *handle,
    956                                struct GNUNET_CHAT_Context *context)
    957 {
    958   g_assert((handle) && (context));
    959 
    960   if (!gtk_window_is_active(GTK_WINDOW(handle->main_window)))
    961     return FALSE;
    962 
    963   UI_CHAT_ENTRY_Handle *entry = GNUNET_CHAT_context_get_user_pointer(context);
    964 
    965   if ((!entry) || (!(entry->entry_box)))
    966     return FALSE;
    967 
    968   GtkListBoxRow *row = GTK_LIST_BOX_ROW(
    969     gtk_widget_get_parent(entry->entry_box)
    970   );
    971 
    972   if (!row)
    973     return FALSE;
    974 
    975   return gtk_list_box_row_is_selected(row);
    976 }
    977 
    978 void
    979 ui_messenger_cleanup(UI_MESSENGER_Handle *handle)
    980 {
    981   g_assert(handle);
    982 
    983   g_object_unref(handle->builder);
    984 
    985   if (handle->chat_entries)
    986     g_list_free_full(handle->chat_entries, (GDestroyNotify) ui_chat_entry_delete);
    987 
    988   if (handle->chat_selection)
    989     util_source_remove(handle->chat_selection);
    990 
    991   if (handle->account_refresh)
    992     util_source_remove(handle->account_refresh);
    993 
    994   memset(handle, 0, sizeof(*handle));
    995 }