messenger-gtk

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

messenger.c (23896B)


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