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 }