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 }