message.c (23521B)
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/message.c 23 */ 24 25 #include "message.h" 26 27 #include <gnunet/gnunet_chat_lib.h> 28 #include <gnunet/gnunet_common.h> 29 30 #include "tag.h" 31 32 #include "../application.h" 33 #include "../contact.h" 34 #include "../file.h" 35 #include "../ui.h" 36 37 static void 38 handle_downloading_file(void *cls, 39 struct GNUNET_CHAT_File *file, 40 uint64_t completed, 41 uint64_t size) 42 { 43 g_assert((cls) && (file)); 44 45 MESSENGER_Application *app = (MESSENGER_Application*) cls; 46 47 if (!app) 48 return; 49 50 file_update_download_info(file, app, completed, size); 51 } 52 53 static void 54 handle_file_button_click(GtkButton *button, 55 gpointer user_data) 56 { 57 g_assert((button) && (user_data)); 58 59 MESSENGER_Application *app = (MESSENGER_Application*) user_data; 60 61 UI_MESSAGE_Handle* handle = (UI_MESSAGE_Handle*) ( 62 g_object_get_qdata(G_OBJECT(button), app->quarks.ui) 63 ); 64 65 if (!handle) 66 return; 67 68 struct GNUNET_CHAT_File *file = (struct GNUNET_CHAT_File*) ( 69 g_object_get_qdata(G_OBJECT(handle->file_progress_bar), app->quarks.data) 70 ); 71 72 if (!file) 73 return; 74 75 application_chat_lock(app); 76 uint64_t size = GNUNET_CHAT_file_get_size(file); 77 application_chat_unlock(app); 78 79 if (size <= 0) 80 return; 81 82 application_chat_lock(app); 83 84 uint64_t local_size = GNUNET_CHAT_file_get_local_size(file); 85 const gboolean downloading = (GNUNET_YES == GNUNET_CHAT_file_is_downloading(file)); 86 87 application_chat_unlock(app); 88 89 if (downloading) 90 { 91 application_chat_lock(app); 92 GNUNET_CHAT_file_stop_download(file); 93 application_chat_unlock(app); 94 95 gtk_image_set_from_icon_name( 96 handle->file_status_image, 97 "folder-download-symbolic", 98 GTK_ICON_SIZE_BUTTON 99 ); 100 } 101 else if (local_size < size) 102 { 103 application_chat_lock(app); 104 GNUNET_CHAT_file_start_download( 105 file, 106 handle_downloading_file, 107 app 108 ); 109 application_chat_unlock(app); 110 111 gtk_image_set_from_icon_name( 112 handle->file_status_image, 113 "process-stop-symbolic", 114 GTK_ICON_SIZE_BUTTON 115 ); 116 } 117 else if (size > 0) 118 { 119 application_chat_lock(app); 120 const gchar *preview = GNUNET_CHAT_file_open_preview(file); 121 application_chat_unlock(app); 122 123 if (!preview) 124 return; 125 126 GString* uri = g_string_new("file://"); 127 g_string_append(uri, preview); 128 129 if (!g_app_info_launch_default_for_uri(uri->str, NULL, NULL)) 130 { 131 application_chat_lock(app); 132 GNUNET_CHAT_file_close_preview(file); 133 application_chat_unlock(app); 134 } 135 136 g_string_free(uri, TRUE); 137 } 138 } 139 140 static void 141 handle_accept_button_click(GtkButton *button, 142 gpointer user_data) 143 { 144 g_assert((button) && (user_data)); 145 146 MESSENGER_Application *app = (MESSENGER_Application*) user_data; 147 148 UI_MESSAGE_Handle* handle = (UI_MESSAGE_Handle*) ( 149 g_object_get_qdata(G_OBJECT(button), app->quarks.ui) 150 ); 151 152 if ((!handle) || (!(handle->status_cb))) 153 return; 154 155 handle->status_cb(app, true, handle->status_cls); 156 } 157 158 static void 159 handle_deny_button_click(GtkButton *button, 160 gpointer user_data) 161 { 162 g_assert((button) && (user_data)); 163 164 MESSENGER_Application *app = (MESSENGER_Application*) user_data; 165 166 UI_MESSAGE_Handle* handle = (UI_MESSAGE_Handle*) ( 167 g_object_get_qdata(G_OBJECT(button), app->quarks.ui) 168 ); 169 170 if ((!handle) || (!(handle->status_cb))) 171 return; 172 173 handle->status_cb(app, false, handle->status_cls); 174 } 175 176 static void 177 handle_media_button_click(GtkButton *button, 178 gpointer user_data) 179 { 180 g_assert((button) && (user_data)); 181 182 MESSENGER_Application *app = (MESSENGER_Application*) user_data; 183 184 UI_MESSAGE_Handle* handle = (UI_MESSAGE_Handle*) ( 185 g_object_get_qdata(G_OBJECT(button), app->quarks.ui) 186 ); 187 188 if (!handle) 189 return; 190 191 struct GNUNET_CHAT_File *file = (struct GNUNET_CHAT_File*) ( 192 g_object_get_qdata(G_OBJECT(handle->media_progress_bar), app->quarks.data) 193 ); 194 195 if (!file) 196 return; 197 198 application_chat_lock(app); 199 const gchar *preview = GNUNET_CHAT_file_open_preview(file); 200 application_chat_unlock(app); 201 202 if (!preview) 203 return; 204 205 ui_play_media_window_init(app, &(app->ui.play_media)); 206 207 GString* uri = g_string_new("file://"); 208 g_string_append(uri, preview); 209 210 ui_play_media_window_update( 211 &(app->ui.play_media), 212 uri->str, 213 file 214 ); 215 216 gtk_widget_show(GTK_WIDGET(app->ui.play_media.window)); 217 g_string_free(uri, TRUE); 218 } 219 220 static gboolean 221 handle_preview_drawing_area_draw(GtkWidget* drawing_area, 222 cairo_t* cairo, 223 gpointer user_data) 224 { 225 g_assert((drawing_area) && (cairo) && (user_data)); 226 227 UI_MESSAGE_Handle *handle = (UI_MESSAGE_Handle*) user_data; 228 229 GtkStyleContext* context = gtk_widget_get_style_context(drawing_area); 230 231 const guint width = gtk_widget_get_allocated_width(drawing_area); 232 const guint height = gtk_widget_get_allocated_height(drawing_area); 233 234 gtk_render_background(context, cairo, 0, 0, width, height); 235 236 struct GNUNET_CHAT_File *file = (struct GNUNET_CHAT_File *) g_object_get_qdata( 237 G_OBJECT(handle->message_box), 238 handle->app->quarks.data 239 ); 240 241 if (!file) 242 return FALSE; 243 244 GdkPixbuf *image = file_get_current_preview_image(file); 245 246 if (!image) 247 return FALSE; 248 249 int dwidth = gdk_pixbuf_get_width(image); 250 int dheight = gdk_pixbuf_get_height(image); 251 252 gint optimal_height = width * dheight / dwidth; 253 254 gtk_widget_set_size_request( 255 GTK_WIDGET(drawing_area), 256 width, 257 optimal_height 258 ); 259 260 double ratio_width = 1.0 * width / dwidth; 261 double ratio_height = 1.0 * height / dheight; 262 263 const double ratio = ratio_width < ratio_height? ratio_width : ratio_height; 264 265 dwidth = (int) (dwidth * ratio); 266 dheight = (int) (dheight * ratio); 267 268 double dx = (width - dwidth) * 0.5; 269 double dy = (height - dheight) * 0.5; 270 271 const int interp_type = (ratio >= 1.0? 272 GDK_INTERP_NEAREST : 273 GDK_INTERP_BILINEAR 274 ); 275 276 GdkPixbuf* scaled = gdk_pixbuf_scale_simple( 277 image, 278 dwidth, 279 dheight, 280 interp_type 281 ); 282 283 gtk_render_icon(context, cairo, scaled, dx, dy); 284 285 cairo_fill(cairo); 286 287 g_object_unref(scaled); 288 return FALSE; 289 } 290 291 UI_MESSAGE_Handle* 292 ui_message_new(MESSENGER_Application *app, 293 UI_MESSAGE_Type type) 294 { 295 g_assert(app); 296 297 UI_MESSAGE_Handle* handle = g_malloc(sizeof(UI_MESSAGE_Handle)); 298 299 handle->type = type; 300 301 handle->timestamp = ((time_t) -1); 302 handle->msg = NULL; 303 handle->contact = NULL; 304 305 handle->status_cb = NULL; 306 handle->status_cls = NULL; 307 308 const char *ui_builder_file; 309 310 switch (handle->type) 311 { 312 case UI_MESSAGE_SENT: 313 ui_builder_file = "ui/message-sent.ui"; 314 break; 315 case UI_MESSAGE_STATUS: 316 ui_builder_file = "ui/message-status.ui"; 317 break; 318 default: 319 ui_builder_file = "ui/message.ui"; 320 break; 321 } 322 323 handle->builder[0] = ui_builder_from_resource( 324 application_get_resource_path(app, ui_builder_file) 325 ); 326 327 handle->message_box = GTK_WIDGET( 328 gtk_builder_get_object(handle->builder[0], "message_box") 329 ); 330 331 handle->sender_avatar = HDY_AVATAR( 332 gtk_builder_get_object(handle->builder[0], "sender_avatar") 333 ); 334 335 handle->sender_label = GTK_LABEL( 336 gtk_builder_get_object(handle->builder[0], "sender_label") 337 ); 338 339 handle->private_image = GTK_IMAGE( 340 gtk_builder_get_object(handle->builder[0], "private_image") 341 ); 342 343 if (UI_MESSAGE_STATUS == handle->type) 344 { 345 handle->deny_revealer = GTK_REVEALER( 346 gtk_builder_get_object(handle->builder[0], "deny_revealer") 347 ); 348 349 handle->accept_revealer = GTK_REVEALER( 350 gtk_builder_get_object(handle->builder[0], "accept_revealer") 351 ); 352 353 handle->deny_button = GTK_BUTTON( 354 gtk_builder_get_object(handle->builder[0], "deny_button") 355 ); 356 357 handle->accept_button = GTK_BUTTON( 358 gtk_builder_get_object(handle->builder[0], "accept_button") 359 ); 360 361 g_object_set_qdata(G_OBJECT(handle->accept_button), app->quarks.ui, handle); 362 g_object_set_qdata(G_OBJECT(handle->deny_button), app->quarks.ui, handle); 363 364 g_signal_connect( 365 handle->accept_button, 366 "clicked", 367 G_CALLBACK(handle_accept_button_click), 368 app 369 ); 370 371 g_signal_connect( 372 handle->deny_button, 373 "clicked", 374 G_CALLBACK(handle_deny_button_click), 375 app 376 ); 377 } 378 else 379 { 380 handle->deny_revealer = NULL; 381 handle->accept_revealer = NULL; 382 383 handle->deny_button = NULL; 384 handle->accept_button = NULL; 385 } 386 387 GtkContainer *content_box = GTK_CONTAINER( 388 gtk_builder_get_object(handle->builder[0], "content_box") 389 ); 390 391 handle->tag_flow_box = GTK_FLOW_BOX( 392 gtk_builder_get_object(handle->builder[0], "tag_flow_box") 393 ); 394 395 handle->builder[1] = ui_builder_from_resource( 396 application_get_resource_path(app, "ui/message_content.ui") 397 ); 398 399 handle->timestamp_label = GTK_LABEL( 400 gtk_builder_get_object(handle->builder[1], "timestamp_label") 401 ); 402 403 handle->read_receipt_image = GTK_IMAGE( 404 gtk_builder_get_object(handle->builder[1], "read_receipt_image") 405 ); 406 407 handle->content_stack = GTK_STACK( 408 gtk_builder_get_object(handle->builder[1], "content_stack") 409 ); 410 411 handle->text_label = GTK_LABEL( 412 gtk_builder_get_object(handle->builder[1], "text_label") 413 ); 414 415 handle->file_revealer = GTK_REVEALER( 416 gtk_builder_get_object(handle->builder[1], "file_revealer") 417 ); 418 419 handle->filename_label = GTK_LABEL( 420 gtk_builder_get_object(handle->builder[1], "filename_label") 421 ); 422 423 handle->file_progress_bar = GTK_PROGRESS_BAR( 424 gtk_builder_get_object(handle->builder[1], "file_progress_bar") 425 ); 426 427 handle->file_button = GTK_BUTTON( 428 gtk_builder_get_object(handle->builder[1], "file_button") 429 ); 430 431 g_signal_connect( 432 handle->file_button, 433 "clicked", 434 G_CALLBACK(handle_file_button_click), 435 app 436 ); 437 438 handle->file_status_image = GTK_IMAGE( 439 gtk_builder_get_object(handle->builder[1], "file_status_image") 440 ); 441 442 g_object_set_qdata(G_OBJECT(handle->file_button), app->quarks.ui, handle); 443 444 handle->preview_drawing_area = GTK_DRAWING_AREA( 445 gtk_builder_get_object(handle->builder[1], "preview_drawing_area") 446 ); 447 448 g_signal_connect( 449 handle->preview_drawing_area, 450 "draw", 451 G_CALLBACK(handle_preview_drawing_area_draw), 452 handle 453 ); 454 455 handle->media_revealer = GTK_REVEALER( 456 gtk_builder_get_object(handle->builder[1], "media_revealer") 457 ); 458 459 handle->media_type_image = GTK_IMAGE( 460 gtk_builder_get_object(handle->builder[1], "media_type_image") 461 ); 462 463 handle->media_label = GTK_LABEL( 464 gtk_builder_get_object(handle->builder[1], "media_label") 465 ); 466 467 handle->media_progress_bar = GTK_PROGRESS_BAR( 468 gtk_builder_get_object(handle->builder[1], "media_progress_bar") 469 ); 470 471 handle->media_button = GTK_BUTTON( 472 gtk_builder_get_object(handle->builder[1], "media_button") 473 ); 474 475 handle->app = app; 476 477 g_object_set_qdata(G_OBJECT(handle->media_button), app->quarks.ui, handle); 478 479 g_signal_connect( 480 handle->media_button, 481 "clicked", 482 G_CALLBACK(handle_media_button_click), 483 app 484 ); 485 486 switch (handle->type) 487 { 488 case UI_MESSAGE_STATUS: 489 gtk_widget_set_visible(GTK_WIDGET(handle->timestamp_label), FALSE); 490 break; 491 default: 492 break; 493 } 494 495 gtk_container_add(content_box, GTK_WIDGET( 496 gtk_builder_get_object(handle->builder[1], "message_content_box") 497 )); 498 499 return handle; 500 } 501 502 static int 503 _iterate_read_receipts(void *cls, 504 UNUSED struct GNUNET_CHAT_Message *message, 505 struct GNUNET_CHAT_Contact *contact, 506 int read_receipt) 507 { 508 g_assert((cls) && (message) && (contact)); 509 510 int *count_read_receipts = (int*) cls; 511 512 if ((GNUNET_YES == read_receipt) && 513 (GNUNET_NO == GNUNET_CHAT_contact_is_owned(contact))) 514 (*count_read_receipts)++; 515 516 return GNUNET_YES; 517 } 518 519 void 520 ui_message_refresh(UI_MESSAGE_Handle *handle) 521 { 522 g_assert(handle); 523 524 if ((!(handle->msg)) || 525 (GNUNET_YES != GNUNET_CHAT_message_is_sent(handle->msg))) 526 return; 527 528 if (!(handle->read_receipt_image)) 529 return; 530 531 int count = 0; 532 if ((0 < GNUNET_CHAT_message_get_read_receipt(handle->msg, _iterate_read_receipts, &count)) && 533 (0 < count)) 534 gtk_widget_show(GTK_WIDGET(handle->read_receipt_image)); 535 else 536 gtk_widget_hide(GTK_WIDGET(handle->read_receipt_image)); 537 } 538 539 gboolean 540 _message_media_supports_file_extension(const gchar *filename) 541 { 542 if (!filename) 543 return FALSE; 544 545 const char* extension = strrchr(filename, '.'); 546 547 if (!extension) 548 return FALSE; 549 550 if (0 == g_strcmp0(extension, ".ogg")) 551 return TRUE; 552 if (0 == g_strcmp0(extension, ".mp3")) 553 return TRUE; 554 if (0 == g_strcmp0(extension, ".wav")) 555 return TRUE; 556 557 return FALSE; 558 } 559 560 static void 561 _update_invitation_message(UI_MESSAGE_Handle *handle, 562 MESSENGER_Application *app, 563 struct GNUNET_CHAT_Invitation *invitation) 564 { 565 g_assert((handle) && (app) && (invitation)); 566 567 enum GNUNET_GenericReturnValue accepted, rejected; 568 accepted = GNUNET_CHAT_invitation_is_accepted(invitation); 569 rejected = GNUNET_CHAT_invitation_is_rejected(invitation); 570 571 if (handle->deny_button) 572 gtk_widget_set_sensitive( 573 GTK_WIDGET(handle->deny_button), 574 GNUNET_YES == accepted || GNUNET_YES == rejected? FALSE : TRUE 575 ); 576 577 if (handle->accept_button) 578 gtk_widget_set_sensitive( 579 GTK_WIDGET(handle->accept_button), 580 GNUNET_YES == accepted || GNUNET_YES == rejected? FALSE : TRUE 581 ); 582 583 if (handle->deny_revealer) 584 gtk_revealer_set_reveal_child( 585 GTK_REVEALER(handle->deny_revealer), 586 GNUNET_YES == accepted || GNUNET_YES == rejected? FALSE : TRUE 587 ); 588 589 if (handle->accept_revealer) 590 gtk_revealer_set_reveal_child( 591 GTK_REVEALER(handle->accept_revealer), 592 GNUNET_YES == accepted || GNUNET_YES == rejected? FALSE : TRUE 593 ); 594 595 if ((app->settings.accept_all_invitations) && 596 (GNUNET_NO == accepted) && (handle->accept_button)) 597 gtk_button_clicked(handle->accept_button); 598 } 599 600 static void 601 _update_message_with_file(UI_MESSAGE_Handle *handle, 602 MESSENGER_Application *app, 603 struct GNUNET_CHAT_File *file) 604 { 605 g_assert(handle); 606 607 struct GNUNET_CHAT_File *prev = g_object_get_qdata( 608 G_OBJECT(handle->preview_drawing_area), 609 app->quarks.data 610 ); 611 612 if (prev) 613 file_remove_widget_from_preview(file, GTK_WIDGET(handle->preview_drawing_area)); 614 if (file) 615 file_add_widget_to_preview(file, GTK_WIDGET(handle->preview_drawing_area)); 616 617 g_object_set_qdata( 618 G_OBJECT(handle->preview_drawing_area), 619 app->quarks.data, 620 file 621 ); 622 } 623 624 static void 625 _update_file_message(UI_MESSAGE_Handle *handle, 626 MESSENGER_Application *app, 627 struct GNUNET_CHAT_File *file) 628 { 629 g_assert((handle) && (app) && (file)); 630 631 const char *filename = GNUNET_CHAT_file_get_name(file); 632 633 uint64_t size = GNUNET_CHAT_file_get_size(file); 634 uint64_t local_size = GNUNET_CHAT_file_get_local_size(file); 635 636 gboolean autostart_download = FALSE; 637 638 if ((size <= 0) || (size > local_size)) 639 { 640 gtk_image_set_from_icon_name( 641 handle->file_status_image, 642 "folder-download-symbolic", 643 GTK_ICON_SIZE_BUTTON 644 ); 645 646 if ((app->settings.accept_all_files) && 647 (GNUNET_YES != GNUNET_CHAT_file_is_downloading(file))) 648 autostart_download = TRUE; 649 650 goto file_content; 651 } 652 653 if ((!(handle->preview_drawing_area)) || 654 (GNUNET_CHAT_file_get_size(file) != GNUNET_CHAT_file_get_local_size(file))) 655 goto file_progress; 656 657 file_load_preview_image(file); 658 659 GdkPixbuf *image = file_get_current_preview_image(file); 660 661 if (image) 662 { 663 gtk_widget_set_size_request( 664 GTK_WIDGET(handle->preview_drawing_area), 665 250, 666 -1 667 ); 668 669 gtk_stack_set_visible_child( 670 handle->content_stack, 671 GTK_WIDGET(handle->preview_drawing_area) 672 ); 673 674 _update_message_with_file(handle, app, file); 675 return; 676 } 677 678 if (_message_media_supports_file_extension(filename)) 679 { 680 gtk_image_set_from_icon_name( 681 handle->media_type_image, 682 "audio-x-generic-symbolic", 683 GTK_ICON_SIZE_DND 684 ); 685 686 goto media_content; 687 } 688 689 if (!ui_play_media_window_supports_file_extension(filename)) 690 goto file_progress; 691 692 media_content: 693 ui_label_set_text(handle->media_label, filename); 694 695 gtk_stack_set_visible_child( 696 handle->content_stack, 697 GTK_WIDGET(handle->media_revealer) 698 ); 699 700 gtk_revealer_set_reveal_child(handle->media_revealer, TRUE); 701 702 g_object_set_qdata( 703 G_OBJECT(handle->media_progress_bar), 704 app->quarks.data, 705 file 706 ); 707 708 return; 709 710 file_progress: 711 gtk_progress_bar_set_fraction(handle->file_progress_bar, 1.0); 712 713 gtk_image_set_from_icon_name( 714 handle->file_status_image, 715 "document-open-symbolic", 716 GTK_ICON_SIZE_BUTTON 717 ); 718 719 file_content: 720 ui_label_set_text(handle->filename_label, filename); 721 722 gtk_stack_set_visible_child( 723 handle->content_stack, 724 GTK_WIDGET(handle->file_revealer) 725 ); 726 727 gtk_revealer_set_reveal_child(handle->file_revealer, TRUE); 728 729 g_object_set_qdata( 730 G_OBJECT(handle->file_progress_bar), 731 app->quarks.data, 732 file 733 ); 734 735 if (autostart_download) 736 gtk_button_clicked(handle->file_button); 737 } 738 739 void 740 ui_message_update(UI_MESSAGE_Handle *handle, 741 MESSENGER_Application *app, 742 struct GNUNET_CHAT_Message *msg) 743 { 744 g_assert((handle) && (app)); 745 746 struct GNUNET_CHAT_File *file = NULL; 747 struct GNUNET_CHAT_Invitation *invitation = NULL; 748 749 if (handle->msg) 750 GNUNET_CHAT_message_set_user_pointer(handle->msg, NULL); 751 752 handle->msg = msg; 753 754 if (msg) 755 GNUNET_CHAT_message_set_user_pointer(msg, handle); 756 757 ui_message_refresh(handle); 758 759 if (msg) 760 { 761 if (GNUNET_YES == GNUNET_CHAT_message_is_private(msg)) 762 gtk_widget_show(GTK_WIDGET(handle->private_image)); 763 764 invitation = GNUNET_CHAT_message_get_invitation(msg); 765 file = GNUNET_CHAT_message_get_file(msg); 766 767 handle->timestamp = GNUNET_CHAT_message_get_timestamp(msg); 768 769 if (handle->accept_button) 770 g_object_set_qdata(G_OBJECT(handle->accept_button), app->quarks.data, invitation); 771 772 g_object_set_qdata(G_OBJECT(handle->message_box), app->quarks.data, file); 773 } 774 else 775 { 776 if (handle->accept_button) 777 invitation = (struct GNUNET_CHAT_Invitation*) ( 778 g_object_get_qdata(G_OBJECT(handle->accept_button), app->quarks.data) 779 ); 780 781 file = (struct GNUNET_CHAT_File*) ( 782 g_object_get_qdata(G_OBJECT(handle->message_box), app->quarks.data) 783 ); 784 } 785 786 if (invitation) 787 _update_invitation_message(handle, app, invitation); 788 789 if (file) 790 _update_file_message(handle, app, file); 791 } 792 793 void 794 ui_message_set_contact(UI_MESSAGE_Handle *handle, 795 struct GNUNET_CHAT_Contact *contact) 796 { 797 g_assert(handle); 798 799 if (handle->contact) 800 { 801 contact_remove_name_avatar_from_info(handle->contact, handle->sender_avatar); 802 contact_remove_name_label_from_info(handle->contact, handle->sender_label); 803 contact_remove_visible_widget_to_info(handle->contact, handle->message_box); 804 } 805 806 if (contact) 807 { 808 contact_add_name_avatar_to_info(contact, handle->sender_avatar); 809 contact_add_name_label_to_info(contact, handle->sender_label); 810 contact_add_visible_widget_to_info(contact, handle->message_box); 811 } 812 813 handle->contact = contact; 814 } 815 816 void 817 ui_message_set_status_callback(UI_MESSAGE_Handle *handle, 818 UI_MESSAGE_StatusCallback cb, 819 gpointer cls) 820 { 821 g_assert(handle); 822 823 handle->status_cb = cb; 824 handle->status_cls = cls; 825 } 826 827 void 828 ui_message_add_tag(UI_MESSAGE_Handle *handle, 829 MESSENGER_Application *app, 830 struct GNUNET_CHAT_Message *tag_message) 831 { 832 g_assert((handle) && (app) && (tag_message)); 833 834 if ((GNUNET_CHAT_KIND_TAG != GNUNET_CHAT_message_get_kind(tag_message)) || 835 (GNUNET_CHAT_message_get_target(tag_message) != handle->msg)) 836 return; 837 838 const char *tag_value = GNUNET_CHAT_message_get_text(tag_message); 839 840 if ((!tag_value) || (!*tag_value)) 841 return; 842 843 UI_TAG_Handle *tag = ui_tag_new(app); 844 ui_tag_set_message(tag, app, tag_message); 845 846 gtk_container_add(GTK_CONTAINER(handle->tag_flow_box), GTK_WIDGET(tag->tag_label)); 847 gtk_widget_show_all(GTK_WIDGET(tag->tag_label)); 848 } 849 850 static void 851 _remove_tag_from_message(UI_MESSAGE_Handle *handle, 852 MESSENGER_Application *app, 853 GtkWidget *child) 854 { 855 g_assert((handle) && (app) && (child)); 856 857 GList *items = gtk_container_get_children(GTK_CONTAINER(child)); 858 UI_TAG_Handle *tag = NULL; 859 860 if (items) 861 { 862 GtkLabel *tag_label = GTK_LABEL(items->data); 863 864 tag = g_object_get_qdata( 865 G_OBJECT(tag_label), 866 app->quarks.ui 867 ); 868 869 g_list_free(items); 870 } 871 872 gtk_container_remove(GTK_CONTAINER(handle->tag_flow_box), child); 873 874 ui_tag_delete(tag); 875 gtk_widget_destroy(child); 876 } 877 878 void 879 ui_message_remove_tag(UI_MESSAGE_Handle *handle, 880 MESSENGER_Application *app, 881 struct GNUNET_CHAT_Message *tag_message) 882 { 883 g_assert((handle) && (app) && (tag_message)); 884 885 if ((GNUNET_CHAT_KIND_TAG != GNUNET_CHAT_message_get_kind(tag_message)) || 886 (GNUNET_CHAT_message_get_target(tag_message) != handle->msg)) 887 return; 888 889 GList *children = gtk_container_get_children(GTK_CONTAINER(handle->tag_flow_box)); 890 891 if (!children) 892 return; 893 894 GtkWidget *removable = NULL; 895 896 GList *list = children; 897 while (list) 898 { 899 GtkFlowBoxChild *child = GTK_FLOW_BOX_CHILD(list->data); 900 GList *items = gtk_container_get_children(GTK_CONTAINER(child)); 901 902 if (items) 903 { 904 GtkLabel *tag_label = GTK_LABEL(items->data); 905 906 const struct GNUNET_CHAT_Message *msg = g_object_get_qdata( 907 G_OBJECT(tag_label), 908 app->quarks.data 909 ); 910 911 if (tag_message == msg) 912 removable = GTK_WIDGET(child); 913 914 g_list_free(items); 915 } 916 917 list = list->next; 918 919 if (removable) 920 break; 921 } 922 923 if (children) 924 g_list_free(children); 925 926 if (!removable) 927 return; 928 929 _remove_tag_from_message(handle, app, removable); 930 } 931 932 void 933 ui_message_delete(UI_MESSAGE_Handle *handle, 934 MESSENGER_Application *app) 935 { 936 g_assert((handle) && (app)); 937 938 _update_message_with_file(handle, app, NULL); 939 ui_message_set_contact(handle, NULL); 940 941 GList *children = gtk_container_get_children(GTK_CONTAINER(handle->tag_flow_box)); 942 943 GList *list = children; 944 while (list) 945 { 946 GtkWidget *child = GTK_WIDGET(list->data); 947 948 if (child) 949 _remove_tag_from_message(handle, app, child); 950 951 list = list->next; 952 } 953 954 if (children) 955 g_list_free(children); 956 957 g_object_unref(handle->builder[1]); 958 g_object_unref(handle->builder[0]); 959 960 g_free(handle); 961 }