/*
This file is part of GNUnet.
Copyright (C) 2021--2022 GNUnet e.V.
GNUnet is free software: you can redistribute it and/or modify it
under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License,
or (at your option) any later version.
GNUnet is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
SPDX-License-Identifier: AGPL3.0-or-later
*/
/*
* @author Tobias Frisch
* @file ui/chat.c
*/
#include "chat.h"
#include
#include
#include "chat_entry.h"
#include "file_load_entry.h"
#include "message.h"
#include "messenger.h"
#include "picker.h"
#include "account_entry.h"
#include "delete_messages.h"
#include "../application.h"
#include "../contact.h"
#include "../file.h"
#include "../ui.h"
static gboolean
_flap_chat_details_reveal_switch(gpointer user_data)
{
UI_CHAT_Handle *handle = (UI_CHAT_Handle*) user_data;
HdyFlap* flap = handle->flap_chat_details;
if (TRUE == hdy_flap_get_reveal_flap(flap)) {
hdy_flap_set_reveal_flap(flap, FALSE);
} else {
hdy_flap_set_reveal_flap(flap, TRUE);
}
gtk_widget_set_sensitive(GTK_WIDGET(handle->messages_listbox), TRUE);
return FALSE;
}
static void
handle_chat_details_via_button_click(UNUSED GtkButton* button,
gpointer user_data)
{
UI_CHAT_Handle *handle = (UI_CHAT_Handle*) user_data;
gtk_widget_set_sensitive(GTK_WIDGET(handle->messages_listbox), FALSE);
g_idle_add(
G_SOURCE_FUNC(_flap_chat_details_reveal_switch),
handle
);
}
static void
handle_popover_via_button_click(UNUSED GtkButton *button,
gpointer user_data)
{
GtkPopover *popover = GTK_POPOVER(user_data);
if (gtk_widget_is_visible(GTK_WIDGET(popover)))
gtk_popover_popdown(popover);
else
gtk_popover_popup(popover);
}
static void
handle_chat_contacts_listbox_row_activated(GtkListBox *listbox,
GtkListBoxRow *row,
gpointer user_data)
{
UI_CHAT_Handle *handle = (UI_CHAT_Handle*) user_data;
MESSENGER_Application *app = handle->app;
GtkTextView *text_view = GTK_TEXT_VIEW(
g_object_get_qdata(G_OBJECT(listbox), app->quarks.widget)
);
if (!text_view)
return;
if (!gtk_list_box_row_get_selectable(row))
{
ui_invite_contact_dialog_init(app, &(app->ui.invite_contact));
g_object_set_qdata(
G_OBJECT(app->ui.invite_contact.contacts_listbox),
app->quarks.widget,
text_view
);
gtk_widget_show(GTK_WIDGET(app->ui.invite_contact.dialog));
return;
}
struct GNUNET_CHAT_Contact *contact = (struct GNUNET_CHAT_Contact*) (
g_object_get_qdata(G_OBJECT(row), app->quarks.data)
);
if (!contact)
return;
hdy_flap_set_reveal_flap(handle->flap_chat_details, FALSE);
ui_contact_info_dialog_init(app, &(app->ui.contact_info));
ui_contact_info_dialog_update(&(app->ui.contact_info), contact, FALSE);
gtk_widget_show(GTK_WIDGET(app->ui.contact_info.dialog));
}
static void
handle_chat_messages_listbox_size_allocate(UNUSED GtkWidget *widget,
UNUSED GdkRectangle *allocation,
gpointer user_data)
{
UI_CHAT_Handle *handle = (UI_CHAT_Handle*) user_data;
GtkAdjustment *adjustment = gtk_scrolled_window_get_vadjustment(
handle->chat_scrolled_window
);
const gdouble value = gtk_adjustment_get_value(adjustment);
const gdouble upper = gtk_adjustment_get_upper(adjustment);
const gdouble page_size = gtk_adjustment_get_page_size(adjustment);
const gdouble edge_value = upper - page_size;
if (value >= handle->edge_value)
gtk_adjustment_set_value(adjustment, edge_value);
handle->edge_value = upper - page_size;
}
static void
handle_back_button_click(UNUSED GtkButton *button,
gpointer user_data)
{
HdyLeaflet *leaflet = HDY_LEAFLET(user_data);
GList *children = gtk_container_get_children(GTK_CONTAINER(leaflet));
if (children) {
hdy_leaflet_set_visible_child(leaflet, GTK_WIDGET(children->data));
}
}
static void
handle_reveal_identity_button_click(GtkButton *button,
gpointer user_data)
{
UI_CHAT_Handle *handle = (UI_CHAT_Handle*) user_data;
MESSENGER_Application *app = handle->app;
struct GNUNET_CHAT_Contact *contact = (struct GNUNET_CHAT_Contact*) (
g_object_get_qdata(G_OBJECT(button), app->quarks.data)
);
if (!contact)
return;
hdy_flap_set_reveal_flap(handle->flap_chat_details, FALSE);
ui_contact_info_dialog_init(app, &(app->ui.contact_info));
ui_contact_info_dialog_update(&(app->ui.contact_info), contact, TRUE);
gtk_widget_show(GTK_WIDGET(app->ui.contact_info.dialog));
}
static void
handle_leave_chat_button_click(UNUSED GtkButton *button,
gpointer user_data)
{
UI_CHAT_Handle *handle = (UI_CHAT_Handle*) user_data;
if ((!handle) || (!(handle->send_text_view)))
return;
struct GNUNET_CHAT_Context *context = (struct GNUNET_CHAT_Context*) (
g_object_get_qdata(
G_OBJECT(handle->send_text_view),
handle->app->quarks.data
)
);
if (!context)
return;
struct GNUNET_CHAT_Contact *contact = GNUNET_CHAT_context_get_contact(
context
);
struct GNUNET_CHAT_Group *group = GNUNET_CHAT_context_get_group(
context
);
if (contact)
GNUNET_CHAT_contact_delete(contact);
else if (group)
GNUNET_CHAT_group_leave(group);
UI_CHAT_ENTRY_Handle *entry = GNUNET_CHAT_context_get_user_pointer(context);
if ((!entry) || (!(handle->app)))
return;
ui_chat_entry_dispose(entry, handle->app);
}
static gint
handle_chat_messages_sort(GtkListBoxRow* row0,
GtkListBoxRow* row1,
gpointer user_data)
{
MESSENGER_Application *app = (MESSENGER_Application*) user_data;
UI_MESSAGE_Handle *message0 = (UI_MESSAGE_Handle*) (
g_object_get_qdata(G_OBJECT(row0), app->quarks.ui)
);
UI_MESSAGE_Handle *message1 = (UI_MESSAGE_Handle*) (
g_object_get_qdata(G_OBJECT(row1), app->quarks.ui)
);
if ((!message0) || (!message1))
return 0;
struct GNUNET_TIME_Absolute timestamp0 = message0->timestamp;
struct GNUNET_TIME_Absolute timestamp1 = message1->timestamp;
if (GNUNET_TIME_absolute_cmp(timestamp0, <, timestamp1))
return -1;
else if (GNUNET_TIME_absolute_cmp(timestamp0, >, timestamp1))
return +1;
else
return 0;
}
static void
handle_chat_messages_selected_rows_changed(GtkListBox *listbox,
gpointer user_data)
{
UI_CHAT_Handle *handle = (UI_CHAT_Handle*) user_data;
GList *selected = gtk_list_box_get_selected_rows(listbox);
uint32_t count = 0;
while (selected)
{
count++;
selected = selected->next;
}
GString *counter = g_string_new("");
g_string_append_printf(counter, "%u", count);
gtk_label_set_text(handle->selection_count_label, counter->str);
g_string_free(counter, TRUE);
if (count > 0)
gtk_stack_set_visible_child(handle->chat_title_stack, handle->selection_box);
else
gtk_stack_set_visible_child(handle->chat_title_stack, handle->title_box);
}
static void
handle_chat_selection_close_button_click(UNUSED GtkButton *button,
gpointer user_data)
{
GtkListBox *listbox = GTK_LIST_BOX(user_data);
gtk_list_box_unselect_all(listbox);
}
void
_delete_messages_callback(MESSENGER_Application *app,
GList *selected,
gulong delay)
{
UI_MESSAGE_Handle *message;
while (selected)
{
GtkListBoxRow *row = GTK_LIST_BOX_ROW(selected->data);
if (!row)
goto skip_row;
message = (UI_MESSAGE_Handle*) g_object_get_qdata(
G_OBJECT(row),
app->quarks.ui
);
if ((!message) || (!(message->msg)))
goto skip_row;
GNUNET_CHAT_message_delete(
message->msg,
GNUNET_TIME_relative_multiply(
GNUNET_TIME_relative_get_second_(),
delay
)
);
skip_row:
selected = selected->next;
}
}
static void
handle_chat_selection_delete_button_click(UNUSED GtkButton *button,
gpointer user_data)
{
UI_CHAT_Handle *handle = (UI_CHAT_Handle*) user_data;
MESSENGER_Application *app = handle->app;
GList *selected = gtk_list_box_get_selected_rows(handle->messages_listbox);
if (app->settings.hide_delete_dialog)
_delete_messages_callback(app, selected, 0);
else
{
ui_delete_messages_dialog_init(app, &(app->ui.delete_messages));
ui_delete_messages_dialog_link(
&(app->ui.delete_messages),
_delete_messages_callback,
selected
);
gtk_widget_show(GTK_WIDGET(app->ui.delete_messages.dialog));
}
}
static void
handle_attach_file_button_click(GtkButton *button,
gpointer user_data)
{
MESSENGER_Application *app = (MESSENGER_Application*) user_data;
GtkTextView *text_view = GTK_TEXT_VIEW(
g_object_get_qdata(G_OBJECT(button), app->quarks.widget)
);
if (!text_view)
return;
GtkWidget *dialog = gtk_file_chooser_dialog_new(
_("Select file"),
GTK_WINDOW(app->ui.messenger.main_window),
GTK_FILE_CHOOSER_ACTION_OPEN,
_("Cancel"),
GTK_RESPONSE_CANCEL,
_("Confirm"),
GTK_RESPONSE_ACCEPT,
NULL
);
if (GTK_RESPONSE_ACCEPT != gtk_dialog_run(GTK_DIALOG(dialog)))
goto close_dialog;
gchar *filename = gtk_file_chooser_get_filename(
GTK_FILE_CHOOSER(dialog)
);
if (!filename)
return;
ui_send_file_dialog_init(app, &(app->ui.send_file));
ui_send_file_dialog_update(&(app->ui.send_file), filename);
g_free(filename);
g_object_set_qdata(
G_OBJECT(app->ui.send_file.send_button),
app->quarks.widget,
text_view
);
gtk_widget_show(GTK_WIDGET(app->ui.send_file.dialog));
close_dialog:
gtk_widget_destroy(dialog);
}
static void
_update_send_record_symbol(GtkTextBuffer *buffer,
GtkImage *symbol,
gboolean picker_revealed)
{
GtkTextIter start, end;
gtk_text_buffer_get_start_iter(buffer, &start);
gtk_text_buffer_get_end_iter(buffer, &end);
const gchar *text = gtk_text_buffer_get_text(buffer, &start, &end, TRUE);
gtk_image_set_from_icon_name(
symbol,
(0 < strlen(text)) || (picker_revealed)?
"mail-send-symbolic" :
"audio-input-microphone-symbolic",
GTK_ICON_SIZE_BUTTON
);
}
static void
handle_send_text_buffer_changed(GtkTextBuffer *buffer,
gpointer user_data)
{
UI_CHAT_Handle *handle = (UI_CHAT_Handle*) user_data;
_update_send_record_symbol(
buffer,
handle->send_record_symbol,
gtk_revealer_get_child_revealed(handle->picker_revealer)
);
}
static gboolean
_send_text_from_view(MESSENGER_Application *app,
GtkTextView *text_view)
{
GtkTextBuffer *buffer = gtk_text_view_get_buffer(text_view);
GtkTextIter start, end;
gtk_text_buffer_get_start_iter(buffer, &start);
gtk_text_buffer_get_end_iter(buffer, &end);
const gchar *text = gtk_text_buffer_get_text(buffer, &start, &end, TRUE);
if (0 == strlen(text))
return FALSE;
struct GNUNET_CHAT_Context *context = (struct GNUNET_CHAT_Context*) (
g_object_get_qdata(G_OBJECT(text_view), app->quarks.data)
);
if (context)
GNUNET_CHAT_context_send_text(context, text);
gtk_text_buffer_delete(buffer, &start, &end);
return TRUE;
}
static void
_drop_any_recording(UI_CHAT_Handle *handle)
{
if ((handle->play_pipeline) && (handle->playing))
{
gst_element_set_state(handle->play_pipeline, GST_STATE_NULL);
handle->playing = FALSE;
}
_update_send_record_symbol(
gtk_text_view_get_buffer(handle->send_text_view),
handle->send_record_symbol,
FALSE
);
gtk_stack_set_visible_child(handle->send_stack, handle->send_text_box);
if (handle->recording_filename[0])
remove(handle->recording_filename);
handle->recording_filename[0] = 0;
handle->recorded = FALSE;
}
static void
handle_sending_recording_upload_file(UNUSED void *cls,
const struct GNUNET_CHAT_File *file,
uint64_t completed,
uint64_t size)
{
UI_FILE_LOAD_ENTRY_Handle *file_load = cls;
gtk_progress_bar_set_fraction(
file_load->load_progress_bar,
1.0 * completed / size
);
file_update_upload_info(file, completed, size);
if ((completed >= size) && (file_load->chat))
ui_chat_remove_file_load(file_load->chat, file_load);
}
static void
handle_send_record_button_click(GtkButton *button,
gpointer user_data)
{
MESSENGER_Application *app = (MESSENGER_Application*) user_data;
UI_CHAT_Handle *handle = (UI_CHAT_Handle*) (
g_object_get_qdata(G_OBJECT(button), app->quarks.ui)
);
struct GNUNET_CHAT_Context *context = (struct GNUNET_CHAT_Context*) (
g_object_get_qdata(G_OBJECT(handle->send_text_view), app->quarks.data)
);
if ((handle->recorded) && (context) &&
(handle->recording_filename[0]) &&
(!gtk_revealer_get_child_revealed(handle->picker_revealer)))
{
UI_FILE_LOAD_ENTRY_Handle *file_load = ui_file_load_entry_new(app);
ui_label_set_text(file_load->file_label, handle->recording_filename);
gtk_progress_bar_set_fraction(file_load->load_progress_bar, 0.0);
struct GNUNET_CHAT_File *file = GNUNET_CHAT_context_send_file(
context,
handle->recording_filename,
handle_sending_recording_upload_file,
file_load
);
if (file)
{
file_create_info(file);
ui_chat_add_file_load(handle, file_load);
}
else if (file_load)
ui_file_load_entry_delete(file_load);
_drop_any_recording(handle);
return;
}
if (gtk_stack_get_visible_child(handle->send_stack) != handle->send_text_box)
return;
GtkTextView *text_view = GTK_TEXT_VIEW(
g_object_get_qdata(G_OBJECT(button), app->quarks.widget)
);
_send_text_from_view(app, text_view);
}
static gboolean
handle_send_record_button_pressed(GtkWidget *widget,
UNUSED GdkEvent *event,
gpointer user_data)
{
MESSENGER_Application *app = (MESSENGER_Application*) user_data;
GtkTextView *text_view = GTK_TEXT_VIEW(
g_object_get_qdata(G_OBJECT(widget), app->quarks.widget)
);
GtkTextBuffer *buffer = gtk_text_view_get_buffer(text_view);
GtkTextIter start, end;
gtk_text_buffer_get_start_iter(buffer, &start);
gtk_text_buffer_get_end_iter(buffer, &end);
const gchar *text = gtk_text_buffer_get_text(buffer, &start, &end, TRUE);
if (0 < strlen(text))
return FALSE;
UI_CHAT_Handle *handle = (UI_CHAT_Handle*) (
g_object_get_qdata(G_OBJECT(widget), app->quarks.ui)
);
if ((handle->recorded) || (!(handle->record_pipeline)) ||
(handle->recording_filename[0]) ||
(gtk_revealer_get_child_revealed(handle->picker_revealer)) ||
(handle->send_text_box != gtk_stack_get_visible_child(handle->send_stack)))
return FALSE;
strcpy(handle->recording_filename, "/tmp/rec_XXXXXX.ogg");
int fd = mkstemps(handle->recording_filename, 4);
if (-1 == fd)
return FALSE;
else
close(fd);
if ((handle->play_pipeline) && (handle->playing))
{
gst_element_set_state(handle->play_pipeline, GST_STATE_NULL);
handle->playing = FALSE;
}
gtk_image_set_from_icon_name(
handle->play_pause_symbol,
"media-playback-start-symbolic",
GTK_ICON_SIZE_BUTTON
);
gtk_image_set_from_icon_name(
handle->send_record_symbol,
"media-record-symbolic",
GTK_ICON_SIZE_BUTTON
);
gtk_label_set_text(handle->recording_label, "00:00:00");
gtk_progress_bar_set_fraction(handle->recording_progress_bar, 0.0);
gtk_widget_set_sensitive(GTK_WIDGET(handle->recording_play_button), FALSE);
gtk_stack_set_visible_child(handle->send_stack, handle->send_recording_box);
g_object_set(
G_OBJECT(handle->record_sink),
"location",
handle->recording_filename,
NULL
);
gst_element_set_state(handle->record_pipeline, GST_STATE_PLAYING);
return TRUE;
}
static gboolean
handle_send_record_button_released(GtkWidget *widget,
UNUSED GdkEvent *event,
gpointer user_data)
{
MESSENGER_Application *app = (MESSENGER_Application*) user_data;
GtkTextView *text_view = GTK_TEXT_VIEW(
g_object_get_qdata(G_OBJECT(widget), app->quarks.widget)
);
GtkTextBuffer *buffer = gtk_text_view_get_buffer(text_view);
GtkTextIter start, end;
gtk_text_buffer_get_start_iter(buffer, &start);
gtk_text_buffer_get_end_iter(buffer, &end);
const gchar *text = gtk_text_buffer_get_text(buffer, &start, &end, TRUE);
if (0 < strlen(text))
return FALSE;
UI_CHAT_Handle *handle = (UI_CHAT_Handle*) (
g_object_get_qdata(G_OBJECT(widget), app->quarks.ui)
);
if ((handle->recorded) || (!(handle->record_pipeline)) ||
(!(handle->recording_filename[0])) ||
(gtk_revealer_get_child_revealed(handle->picker_revealer)) ||
(handle->send_recording_box != gtk_stack_get_visible_child(
handle->send_stack)))
return FALSE;
gtk_widget_set_sensitive(GTK_WIDGET(handle->recording_play_button), TRUE);
gst_element_set_state(handle->record_pipeline, GST_STATE_NULL);
handle->recorded = TRUE;
gtk_image_set_from_icon_name(
handle->send_record_symbol,
"mail-send-symbolic",
GTK_ICON_SIZE_BUTTON
);
return TRUE;
}
static gboolean
handle_send_text_key_press (GtkWidget *widget,
GdkEventKey *event,
gpointer user_data)
{
MESSENGER_Application *app = (MESSENGER_Application*) user_data;
if ((app->settings.mobile_design) ||
(event->state & GDK_SHIFT_MASK) ||
((event->keyval != GDK_KEY_Return) &&
(event->keyval != GDK_KEY_KP_Enter)))
return FALSE;
return _send_text_from_view(app, GTK_TEXT_VIEW(widget));
}
static void
handle_recording_close_button_click(UNUSED GtkButton *button,
gpointer user_data)
{
UI_CHAT_Handle *handle = (UI_CHAT_Handle*) user_data;
_drop_any_recording(handle);
}
static void
_stop_playing_recording(UI_CHAT_Handle *handle,
gboolean reset_bar)
{
gst_element_set_state(handle->play_pipeline, GST_STATE_NULL);
handle->playing = FALSE;
gtk_image_set_from_icon_name(
handle->play_pause_symbol,
"media-playback-start-symbolic",
GTK_ICON_SIZE_BUTTON
);
gtk_progress_bar_set_fraction(
handle->recording_progress_bar,
reset_bar? 0.0 : 1.0
);
if (handle->play_timer)
{
g_source_remove(handle->play_timer);
handle->play_timer = 0;
}
}
static void
handle_recording_play_button_click(UNUSED GtkButton *button,
gpointer user_data)
{
UI_CHAT_Handle *handle = (UI_CHAT_Handle*) user_data;
if ((!(handle->recorded)) || (!(handle->play_pipeline)))
return;
if (handle->playing)
_stop_playing_recording(handle, TRUE);
else if (handle->recording_filename[0])
{
g_object_set(
G_OBJECT(handle->play_source),
"location",
handle->recording_filename,
NULL
);
gst_element_set_state(handle->play_pipeline, GST_STATE_PLAYING);
handle->playing = TRUE;
gtk_image_set_from_icon_name(
handle->play_pause_symbol,
"media-playback-stop-symbolic",
GTK_ICON_SIZE_BUTTON
);
}
}
static void
handle_picker_button_click(UNUSED GtkButton *button,
gpointer user_data)
{
UI_CHAT_Handle *handle = (UI_CHAT_Handle*) user_data;
gboolean reveal = !gtk_revealer_get_child_revealed(handle->picker_revealer);
gtk_revealer_set_reveal_child(handle->picker_revealer, reveal);
_update_send_record_symbol(
gtk_text_view_get_buffer(handle->send_text_view),
handle->send_record_symbol,
reveal
);
}
static gboolean
_record_timer_func(gpointer user_data)
{
UI_CHAT_Handle *handle = (UI_CHAT_Handle*) user_data;
GString *time_string = g_string_new(NULL);
g_string_printf(
time_string,
"%02u:%02u:%02u",
(handle->record_time / 3600),
(handle->record_time / 60) % 60,
(handle->record_time % 60)
);
gtk_label_set_text(handle->recording_label, time_string->str);
g_string_free(time_string, TRUE);
if (!(handle->recorded))
{
handle->record_time++;
handle->record_timer = g_timeout_add_seconds(
1,
_record_timer_func,
handle
);
}
else
handle->record_timer = 0;
return FALSE;
}
static gboolean
_play_timer_func(gpointer user_data)
{
UI_CHAT_Handle *handle = (UI_CHAT_Handle*) user_data;
if (handle->play_time < handle->record_time * 100)
gtk_progress_bar_set_fraction(
handle->recording_progress_bar,
0.01 * handle->play_time / handle->record_time
);
else
gtk_progress_bar_set_fraction(
handle->recording_progress_bar,
1.0
);
if (handle->playing)
{
handle->play_time++;
handle->play_timer = g_timeout_add(
10,
_play_timer_func,
handle
);
}
else
handle->play_timer = 0;
return FALSE;
}
static gboolean
handle_record_bus_watch(UNUSED GstBus *bus,
GstMessage *msg,
gpointer data)
{
UI_CHAT_Handle *handle = (UI_CHAT_Handle*) data;
GstMessageType type = GST_MESSAGE_TYPE(msg);
switch (type)
{
case GST_MESSAGE_STREAM_START:
handle->record_time = 0;
handle->record_timer = g_timeout_add_seconds(
0,
_record_timer_func,
handle
);
break;
default:
break;
}
return TRUE;
}
static gboolean
handle_play_bus_watch(UNUSED GstBus *bus,
GstMessage *msg,
gpointer data)
{
UI_CHAT_Handle *handle = (UI_CHAT_Handle*) data;
GstMessageType type = GST_MESSAGE_TYPE(msg);
switch (type)
{
case GST_MESSAGE_STREAM_START:
handle->play_time = 0;
handle->play_timer = g_timeout_add_seconds(
0,
_play_timer_func,
handle
);
break;
case GST_MESSAGE_EOS:
if (handle->playing)
_stop_playing_recording(handle, FALSE);
break;
default:
break;
}
return TRUE;
}
static void
_setup_gst_pipelines(UI_CHAT_Handle *handle)
{
handle->record_pipeline = gst_parse_launch(
"autoaudiosrc ! audioconvert ! vorbisenc ! oggmux ! filesink name=sink",
NULL
);
handle->record_sink = gst_bin_get_by_name(
GST_BIN(handle->record_pipeline), "sink"
);
{
GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(handle->record_pipeline));
handle->record_watch = gst_bus_add_watch(
bus,
handle_record_bus_watch,
handle
);
gst_object_unref(bus);
}
handle->play_pipeline = gst_parse_launch(
"filesrc name=source ! oggdemux ! vorbisdec ! audioconvert ! autoaudiosink",
NULL
);
handle->play_source = gst_bin_get_by_name(
GST_BIN(handle->play_pipeline), "source"
);
{
GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(handle->play_pipeline));
handle->play_watch = gst_bus_add_watch(
bus,
handle_play_bus_watch,
handle
);
gst_object_unref(bus);
}
}
UI_CHAT_Handle*
ui_chat_new(MESSENGER_Application *app)
{
GNUNET_assert(app);
UI_CHAT_Handle *handle = g_malloc(sizeof(UI_CHAT_Handle));
UI_MESSENGER_Handle *messenger = &(app->ui.messenger);
memset(handle, 0, sizeof(*handle));
_setup_gst_pipelines(handle);
handle->app = app;
handle->loads = NULL;
handle->builder = gtk_builder_new_from_resource(
application_get_resource_path(app, "ui/chat.ui")
);
handle->chat_box = GTK_WIDGET(
gtk_builder_get_object(handle->builder, "chat_box")
);
handle->back_button = GTK_BUTTON(
gtk_builder_get_object(handle->builder, "back_button")
);
g_object_bind_property(
messenger->leaflet_chat,
"folded",
handle->back_button,
"visible",
G_BINDING_SYNC_CREATE
);
g_signal_connect(
handle->back_button,
"clicked",
G_CALLBACK(handle_back_button_click),
messenger->leaflet_chat
);
handle->flap_chat_details = HDY_FLAP(
gtk_builder_get_object(handle->builder, "flap_chat_details")
);
handle->chat_title_stack = GTK_STACK(
gtk_builder_get_object(handle->builder, "chat_title_stack")
);
handle->title_box = GTK_WIDGET(
gtk_builder_get_object(handle->builder, "title_box")
);
handle->selection_box = GTK_WIDGET(
gtk_builder_get_object(handle->builder, "selection_box")
);
handle->chat_avatar = HDY_AVATAR(
gtk_builder_get_object(handle->builder, "chat_avatar")
);
handle->chat_title = GTK_LABEL(
gtk_builder_get_object(handle->builder, "chat_title")
);
handle->chat_subtitle = GTK_LABEL(
gtk_builder_get_object(handle->builder, "chat_subtitle")
);
handle->chat_load_button = GTK_BUTTON(
gtk_builder_get_object(handle->builder, "chat_load_button")
);
handle->chat_load_popover = GTK_POPOVER(
gtk_builder_get_object(handle->builder, "chat_load_popover")
);
handle->chat_load_listbox = GTK_LIST_BOX(
gtk_builder_get_object(handle->builder, "chat_load_listbox")
);
g_signal_connect(
handle->chat_load_button,
"clicked",
G_CALLBACK(handle_popover_via_button_click),
handle->chat_load_popover
);
handle->chat_details_button = GTK_BUTTON(
gtk_builder_get_object(handle->builder, "chat_details_button")
);
g_signal_connect(
handle->chat_details_button,
"clicked",
G_CALLBACK(handle_chat_details_via_button_click),
handle
);
handle->chat_details_label = GTK_LABEL(
gtk_builder_get_object(handle->builder, "chat_details_label")
);
handle->hide_chat_details_button = GTK_BUTTON(
gtk_builder_get_object(handle->builder, "hide_chat_details_button")
);
g_signal_connect(
handle->hide_chat_details_button,
"clicked",
G_CALLBACK(handle_chat_details_via_button_click),
handle
);
handle->chat_details_contacts_box = GTK_BOX(
gtk_builder_get_object(handle->builder, "chat_details_contacts_box")
);
handle->chat_details_files_box = GTK_BOX(
gtk_builder_get_object(handle->builder, "chat_details_files_box")
);
handle->chat_details_avatar = HDY_AVATAR(
gtk_builder_get_object(handle->builder, "chat_details_avatar")
);
handle->reveal_identity_button = GTK_BUTTON(
gtk_builder_get_object(handle->builder, "reveal_identity_button")
);
g_signal_connect(
handle->reveal_identity_button,
"clicked",
G_CALLBACK(handle_reveal_identity_button_click),
handle
);
handle->leave_chat_button = GTK_BUTTON(
gtk_builder_get_object(handle->builder, "leave_chat_button")
);
g_signal_connect(
handle->leave_chat_button,
"clicked",
G_CALLBACK(handle_leave_chat_button_click),
handle
);
handle->chat_notifications_switch = GTK_SWITCH(
gtk_builder_get_object(handle->builder, "chat_notifications_switch")
);
handle->selection_close_button = GTK_BUTTON(
gtk_builder_get_object(handle->builder, "selection_close_button")
);
handle->selection_count_label = GTK_LABEL(
gtk_builder_get_object(handle->builder, "selection_count_label")
);
handle->selection_delete_button = GTK_BUTTON(
gtk_builder_get_object(handle->builder, "selection_delete_button")
);
handle->chat_scrolled_window = GTK_SCROLLED_WINDOW(
gtk_builder_get_object(handle->builder, "chat_scrolled_window")
);
handle->chat_contacts_listbox = GTK_LIST_BOX(
gtk_builder_get_object(handle->builder, "chat_contacts_listbox")
);
g_signal_connect(
handle->chat_contacts_listbox,
"row-activated",
G_CALLBACK(handle_chat_contacts_listbox_row_activated),
handle
);
handle->chat_files_listbox = GTK_LIST_BOX(
gtk_builder_get_object(handle->builder, "chat_files_listbox")
);
handle->messages_listbox = GTK_LIST_BOX(
gtk_builder_get_object(handle->builder, "messages_listbox")
);
gtk_list_box_set_sort_func(
handle->messages_listbox,
handle_chat_messages_sort,
app,
NULL
);
g_signal_connect(
handle->messages_listbox,
"selected-rows-changed",
G_CALLBACK(handle_chat_messages_selected_rows_changed),
handle
);
g_signal_connect(
handle->selection_close_button,
"clicked",
G_CALLBACK(handle_chat_selection_close_button_click),
handle->messages_listbox
);
g_signal_connect(
handle->selection_delete_button,
"clicked",
G_CALLBACK(handle_chat_selection_delete_button_click),
handle
);
g_signal_connect(
handle->messages_listbox,
"size-allocate",
G_CALLBACK(handle_chat_messages_listbox_size_allocate),
handle
);
handle->send_stack = GTK_STACK(
gtk_builder_get_object(handle->builder, "send_stack")
);
handle->send_text_box = GTK_WIDGET(
gtk_builder_get_object(handle->builder, "send_text_box")
);
handle->send_recording_box = GTK_WIDGET(
gtk_builder_get_object(handle->builder, "send_recording_box")
);
handle->attach_file_button = GTK_BUTTON(
gtk_builder_get_object(handle->builder, "attach_file_button")
);
g_signal_connect(
handle->attach_file_button,
"clicked",
G_CALLBACK(handle_attach_file_button_click),
app
);
handle->send_text_view = GTK_TEXT_VIEW(
gtk_builder_get_object(handle->builder, "send_text_view")
);
handle->emoji_button = GTK_BUTTON(
gtk_builder_get_object(handle->builder, "emoji_button")
);
handle->send_record_button = GTK_BUTTON(
gtk_builder_get_object(handle->builder, "send_record_button")
);
handle->send_record_symbol = GTK_IMAGE(
gtk_builder_get_object(handle->builder, "send_record_symbol")
);
GtkTextBuffer *send_text_buffer = gtk_text_view_get_buffer(
handle->send_text_view
);
g_signal_connect(
send_text_buffer,
"changed",
G_CALLBACK(handle_send_text_buffer_changed),
handle
);
g_signal_connect(
handle->send_record_button,
"clicked",
G_CALLBACK(handle_send_record_button_click),
app
);
g_signal_connect(
handle->send_record_button,
"button-press-event",
G_CALLBACK(handle_send_record_button_pressed),
app
);
g_signal_connect(
handle->send_record_button,
"button-release-event",
G_CALLBACK(handle_send_record_button_released),
app
);
g_signal_connect(
handle->send_text_view,
"key-press-event",
G_CALLBACK(handle_send_text_key_press),
app
);
g_object_set_qdata(
G_OBJECT(handle->chat_contacts_listbox),
app->quarks.widget,
handle->send_text_view
);
g_object_set_qdata(
G_OBJECT(handle->attach_file_button),
app->quarks.widget,
handle->send_text_view
);
g_object_set_qdata(
G_OBJECT(handle->send_record_button),
app->quarks.widget,
handle->send_text_view
);
g_object_set_qdata(
G_OBJECT(handle->send_record_button),
app->quarks.ui,
handle
);
handle->recording_close_button = GTK_BUTTON(
gtk_builder_get_object(handle->builder, "recording_close_button")
);
g_signal_connect(
handle->recording_close_button,
"clicked",
G_CALLBACK(handle_recording_close_button_click),
handle
);
handle->recording_play_button = GTK_BUTTON(
gtk_builder_get_object(handle->builder, "recording_play_button")
);
g_signal_connect(
handle->recording_play_button,
"clicked",
G_CALLBACK(handle_recording_play_button_click),
handle
);
handle->play_pause_symbol = GTK_IMAGE(
gtk_builder_get_object(handle->builder, "play_pause_symbol")
);
handle->recording_label = GTK_LABEL(
gtk_builder_get_object(handle->builder, "recording_label")
);
handle->recording_progress_bar = GTK_PROGRESS_BAR(
gtk_builder_get_object(handle->builder, "recording_progress_bar")
);
handle->picker_revealer = GTK_REVEALER(
gtk_builder_get_object(handle->builder, "picker_revealer")
);
handle->picker = ui_picker_new(app, handle);
gtk_container_add(
GTK_CONTAINER(handle->picker_revealer),
handle->picker->picker_box
);
g_signal_connect(
handle->emoji_button,
"clicked",
G_CALLBACK(handle_picker_button_click),
handle
);
return handle;
}
struct IterateChatGroupClosure {
MESSENGER_Application *app;
GtkListBox *listbox;
};
static int
iterate_ui_chat_update_group_contacts(void *cls,
UNUSED const struct GNUNET_CHAT_Group *group,
struct GNUNET_CHAT_Contact *contact)
{
struct IterateChatGroupClosure *closure = (
(struct IterateChatGroupClosure*) cls
);
GtkListBox *listbox = closure->listbox;
UI_ACCOUNT_ENTRY_Handle* entry = ui_account_entry_new(closure->app);
ui_account_entry_set_contact(entry, contact);
gtk_list_box_prepend(listbox, entry->entry_box);
GtkListBoxRow *row = GTK_LIST_BOX_ROW(
gtk_widget_get_parent(entry->entry_box)
);
g_object_set_qdata(G_OBJECT(row), closure->app->quarks.data, contact);
g_object_set_qdata_full(
G_OBJECT(row),
closure->app->quarks.ui,
entry,
(GDestroyNotify) ui_account_entry_delete
);
return GNUNET_YES;
}
void
ui_chat_update(UI_CHAT_Handle *handle,
MESSENGER_Application *app,
struct GNUNET_CHAT_Context* context)
{
GNUNET_assert((handle) && (app) && (context));
struct GNUNET_CHAT_Contact* contact;
struct GNUNET_CHAT_Group* group;
contact = GNUNET_CHAT_context_get_contact(context);
group = GNUNET_CHAT_context_get_group(context);
const char *title = NULL;
const char *icon = "action-unavailable-symbolic";
GString *subtitle = g_string_new("");
if (contact)
{
title = GNUNET_CHAT_contact_get_name(contact);
icon = "avatar-default-symbolic";
}
else if (group)
{
title = GNUNET_CHAT_group_get_name(group);
if ((title) && ('#' == *title))
icon = "network-wired-symbolic";
else
icon = "system-users-symbolic";
g_string_append_printf(
subtitle,
_("%d members"),
GNUNET_CHAT_group_iterate_contacts(group, NULL, NULL)
);
}
ui_avatar_set_text(handle->chat_avatar, title);
hdy_avatar_set_icon_name(handle->chat_avatar, icon);
ui_avatar_set_text(handle->chat_details_avatar, title);
hdy_avatar_set_icon_name(handle->chat_details_avatar, icon);
ui_label_set_text(handle->chat_title, title);
ui_label_set_text(handle->chat_details_label, title);
if (subtitle->len > 0)
gtk_label_set_text(handle->chat_subtitle, subtitle->str);
g_string_free(subtitle, TRUE);
GList* children = gtk_container_get_children(
GTK_CONTAINER(handle->chat_contacts_listbox)
);
while ((children) && (children->next)) {
GtkWidget *widget = GTK_WIDGET(children->data);
children = children->next;
gtk_container_remove(
GTK_CONTAINER(handle->chat_contacts_listbox),
widget
);
}
if (group)
{
struct IterateChatGroupClosure closure;
closure.app = app;
closure.listbox = handle->chat_contacts_listbox;
GNUNET_CHAT_group_iterate_contacts(
group,
iterate_ui_chat_update_group_contacts,
&closure
);
}
gtk_widget_set_visible(
GTK_WIDGET(handle->chat_details_contacts_box),
group? TRUE : FALSE
);
g_object_set_qdata(
G_OBJECT(handle->reveal_identity_button),
app->quarks.data,
contact
);
gtk_widget_set_visible(
GTK_WIDGET(handle->reveal_identity_button),
contact? TRUE : FALSE
);
gtk_widget_set_sensitive(
GTK_WIDGET(handle->leave_chat_button),
(contact) || (group)? TRUE : FALSE
);
const int status = GNUNET_CHAT_context_get_status(context);
const gboolean activated = (GNUNET_OK == status? TRUE : FALSE);
gtk_text_view_set_editable(handle->send_text_view, activated);
gtk_widget_set_sensitive(GTK_WIDGET(handle->send_text_view), activated);
gtk_widget_set_sensitive(GTK_WIDGET(handle->attach_file_button), activated);
gtk_widget_set_sensitive(GTK_WIDGET(handle->emoji_button), activated);
gtk_widget_set_sensitive(GTK_WIDGET(handle->send_record_button), activated);
if (!(handle->messages))
return;
GList *list = handle->messages;
while (list)
{
ui_message_refresh((UI_MESSAGE_Handle*) list->data);
list = list->next;
}
UI_MESSAGE_Handle *message = (UI_MESSAGE_Handle*) (handle->messages->data);
if (!(message->timestamp_label))
return;
const gchar *time = gtk_label_get_text(message->timestamp_label);
if (!group)
gtk_label_set_text(handle->chat_subtitle, time);
}
void
ui_chat_delete(UI_CHAT_Handle *handle)
{
GNUNET_assert(handle);
ui_picker_delete(handle->picker);
g_object_unref(handle->builder);
if (handle->messages)
g_list_free_full(handle->messages, (GDestroyNotify) ui_message_delete);
if (handle->loads)
g_list_free_full(handle->loads, (GDestroyNotify) ui_file_load_entry_delete);
if (handle->record_pipeline)
{
gst_element_set_state(handle->record_pipeline, GST_STATE_NULL);
gst_object_unref(GST_OBJECT(handle->record_pipeline));
}
if (handle->play_pipeline)
{
gst_element_set_state(handle->play_pipeline, GST_STATE_NULL);
gst_object_unref(GST_OBJECT(handle->play_pipeline));
}
if (handle->recording_filename[0])
remove(handle->recording_filename);
if (handle->record_timer)
g_source_remove(handle->record_timer);
if (handle->play_timer)
g_source_remove(handle->play_timer);
g_free(handle);
}
void
ui_chat_add_message(UI_CHAT_Handle *handle,
MESSENGER_Application *app,
UI_MESSAGE_Handle *message)
{
GNUNET_assert((handle) && (message) && (message->message_box));
gtk_container_add(
GTK_CONTAINER(handle->messages_listbox),
message->message_box
);
GtkWidget *row = gtk_widget_get_parent(message->message_box);
g_object_set_qdata(G_OBJECT(row), app->quarks.ui, message);
handle->messages = g_list_prepend(handle->messages, message);
gtk_list_box_invalidate_sort(handle->messages_listbox);
}
void
ui_chat_remove_message(UI_CHAT_Handle *handle,
UNUSED MESSENGER_Application *app,
UI_MESSAGE_Handle *message)
{
GNUNET_assert((handle) && (message) && (message->message_box));
handle->messages = g_list_remove(handle->messages, message);
GtkWidget *row = gtk_widget_get_parent(message->message_box);
gtk_container_remove(GTK_CONTAINER(handle->messages_listbox), row);
handle->messages = g_list_append(handle->messages, message);
}
void
ui_chat_add_file_load(UI_CHAT_Handle *handle,
UI_FILE_LOAD_ENTRY_Handle *file_load)
{
GNUNET_assert((handle) && (file_load));
gtk_container_add(
GTK_CONTAINER(handle->chat_load_listbox),
file_load->entry_box
);
handle->loads = g_list_append(handle->loads, file_load);
gtk_widget_show(GTK_WIDGET(handle->chat_load_button));
file_load->chat = handle;
}
void
ui_chat_remove_file_load(UI_CHAT_Handle *handle,
UI_FILE_LOAD_ENTRY_Handle *file_load)
{
GNUNET_assert((handle) && (file_load) && (handle == file_load->chat) &&
(file_load->entry_box));
handle->loads = g_list_remove(handle->loads, file_load);
gtk_container_remove(
GTK_CONTAINER(handle->chat_load_listbox),
gtk_widget_get_parent(file_load->entry_box)
);
if (handle->loads)
return;
if (gtk_widget_is_visible(GTK_WIDGET(handle->chat_load_popover)))
gtk_popover_popdown(handle->chat_load_popover);
gtk_widget_hide(GTK_WIDGET(handle->chat_load_button));
file_load->chat = NULL;
}