Commit a448f607 authored by Stepan Salenikovich's avatar Stepan Salenikovich

gnome: add text message support

Refs #74038

Change-Id: I98b4443b9dc5a8975a63d12ee3bd71f68d4b1452
parent 7ec8fe86
......@@ -39,6 +39,10 @@
#include <contactmethod.h>
#include <person.h>
#include "delegates/pixbufdelegate.h"
#include <media/media.h>
#include <media/text.h>
#include <media/textrecording.h>
#include "models/gtkqtreemodel.h"
struct _CurrentCallView
{
......@@ -60,12 +64,21 @@ struct _CurrentCallViewPrivate
GtkWidget *label_duration;
GtkWidget *frame_video;
GtkWidget *video_widget;
GtkWidget *button_hangup;
GtkWidget *revealer_chat;
GtkWidget *togglebutton_chat;
GtkWidget *textview_chat;
GtkWidget *button_chat_input;
GtkWidget *entry_chat_input;
GtkWidget *scrolledwindow_chat;
Call *call;
QMetaObject::Connection state_change_connection;
QMetaObject::Connection call_details_connection;
QMetaObject::Connection local_renderer_connection;
QMetaObject::Connection remote_renderer_connection;
QMetaObject::Connection media_added_connection;
QMetaObject::Connection new_message_connection;
};
G_DEFINE_TYPE_WITH_PRIVATE(CurrentCallView, current_call_view, GTK_TYPE_BOX);
......@@ -85,14 +98,65 @@ current_call_view_dispose(GObject *object)
QObject::disconnect(priv->call_details_connection);
QObject::disconnect(priv->local_renderer_connection);
QObject::disconnect(priv->remote_renderer_connection);
QObject::disconnect(priv->media_added_connection);
QObject::disconnect(priv->new_message_connection);
G_OBJECT_CLASS(current_call_view_parent_class)->dispose(object);
}
static void
chat_toggled(GtkToggleButton *togglebutton, CurrentCallView *self)
{
g_return_if_fail(IS_CURRENT_CALL_VIEW(self));
CurrentCallViewPrivate *priv = CURRENT_CALL_VIEW_GET_PRIVATE(self);
gtk_revealer_set_reveal_child(GTK_REVEALER(priv->revealer_chat),
gtk_toggle_button_get_active(togglebutton));
if (gtk_toggle_button_get_active(togglebutton)) {
/* create an outgoing media to bring up chat history, if any */
priv->call->addOutgoingMedia<Media::Text>();
}
}
static void
send_chat(G_GNUC_UNUSED GtkWidget *widget, CurrentCallView *self)
{
g_return_if_fail(IS_CURRENT_CALL_VIEW(self));
CurrentCallViewPrivate *priv = CURRENT_CALL_VIEW_GET_PRIVATE(self);
/* make sure there is text to send */
const gchar *text = gtk_entry_get_text(GTK_ENTRY(priv->entry_chat_input));
if (text && strlen(text) > 0) {
priv->call->addOutgoingMedia<Media::Text>()->send(text);
/* clear the entry */
gtk_entry_set_text(GTK_ENTRY(priv->entry_chat_input), "");
}
}
static void
scroll_to_bottom(GtkAdjustment *adjustment, G_GNUC_UNUSED gpointer user_data)
{
gtk_adjustment_set_value(adjustment,
gtk_adjustment_get_upper(adjustment) - gtk_adjustment_get_page_size(adjustment));
}
static void
current_call_view_init(CurrentCallView *view)
{
gtk_widget_init_template(GTK_WIDGET(view));
CurrentCallViewPrivate *priv = CURRENT_CALL_VIEW_GET_PRIVATE(view);
g_signal_connect(priv->togglebutton_chat, "toggled", G_CALLBACK(chat_toggled), view);
g_signal_connect(priv->button_chat_input, "clicked", G_CALLBACK(send_chat), view);
g_signal_connect(priv->entry_chat_input, "activate", G_CALLBACK(send_chat), view);
/* the adjustment params will change only when the model is created and when
* new messages are added; in these cases we want to scroll to the bottom of
* the chat treeview */
GtkAdjustment *adjustment = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(priv->scrolledwindow_chat));
g_signal_connect(adjustment, "changed", G_CALLBACK(scroll_to_bottom), NULL);
}
static void
......@@ -108,7 +172,12 @@ current_call_view_class_init(CurrentCallViewClass *klass)
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, label_status);
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, label_duration);
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, frame_video);
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, button_hangup);
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, revealer_chat);
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, togglebutton_chat);
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, textview_chat);
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, button_chat_input);
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, entry_chat_input);
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, scrolledwindow_chat);
}
GtkWidget *
......@@ -207,15 +276,64 @@ on_button_press_in_video_event(GtkWidget *self, GdkEventButton *event, CurrentCa
return TRUE;
}
static void
print_message_to_buffer(const QModelIndex &idx, GtkTextBuffer *buffer)
{
if (idx.isValid()) {
QVariant message = idx.data();
gchar *text = g_strdup_printf("%s\n", message.value<QString>().toUtf8().constData());
GtkTextIter iter;
gtk_text_buffer_get_end_iter(buffer, &iter);
gtk_text_buffer_insert(buffer, &iter, text, -1);
g_free(text);
} else {
g_warning("QModelIndex in im model is not valid");
}
}
static void
parse_chat_model(QAbstractItemModel *model, CurrentCallView *self)
{
g_return_if_fail(IS_CURRENT_CALL_VIEW(self));
CurrentCallViewPrivate *priv = CURRENT_CALL_VIEW_GET_PRIVATE(self);
/* new model, disconnect from the old model updates and clear the text buffer */
QObject::disconnect(priv->new_message_connection);
GtkTextBuffer *new_buffer = gtk_text_buffer_new(NULL);
gtk_text_view_set_buffer(GTK_TEXT_VIEW(priv->textview_chat), new_buffer);
g_object_unref(new_buffer);
/* put all the messages in the im model into the text view */
for (int row = 0; row < model->rowCount(); ++row) {
QModelIndex idx = model->index(row, 0);
print_message_to_buffer(idx, new_buffer);
}
/* append new messages */
priv->new_message_connection = QObject::connect(
model,
&QAbstractItemModel::rowsInserted,
[priv, model] (const QModelIndex &parent, int first, int last) {
for (int row = first; row <= last; ++row) {
QModelIndex idx = model->index(row, 0, parent);
print_message_to_buffer(idx, gtk_text_view_get_buffer(GTK_TEXT_VIEW(priv->textview_chat)));
}
}
);
}
void
current_call_view_set_call_info(CurrentCallView *view, const QModelIndex& idx) {
CurrentCallViewPrivate *priv = CURRENT_CALL_VIEW_GET_PRIVATE(view);
Call *call = CallModel::instance()->getCall(idx);
priv->call = CallModel::instance()->getCall(idx);
/* get call image */
QVariant var_i = PixbufDelegate::instance()->callPhoto(call, QSize(60, 60), false);
QVariant var_i = PixbufDelegate::instance()->callPhoto(priv->call, QSize(60, 60), false);
std::shared_ptr<GdkPixbuf> image = var_i.value<std::shared_ptr<GdkPixbuf>>();
gtk_image_set_from_pixbuf(GTK_IMAGE(priv->image_peer), image.get());
......@@ -225,19 +343,19 @@ current_call_view_set_call_info(CurrentCallView *view, const QModelIndex& idx) {
gtk_label_set_text(GTK_LABEL(priv->label_identity), ba_name.constData());
/* change some things depending on call state */
update_state(view, call);
update_details(view, call);
update_state(view, priv->call);
update_details(view, priv->call);
priv->state_change_connection = QObject::connect(
call,
priv->call,
&Call::stateChanged,
[=]() { update_state(view, call); }
[view, priv]() { update_state(view, priv->call); }
);
priv->call_details_connection = QObject::connect(
call,
priv->call,
static_cast<void (Call::*)(void)>(&Call::changed),
[=]() { update_details(view, call); }
[view, priv]() { update_details(view, priv->call); }
);
/* video widget */
......@@ -247,14 +365,14 @@ current_call_view_set_call_info(CurrentCallView *view, const QModelIndex& idx) {
/* check if we already have a renderer */
video_widget_push_new_renderer(VIDEO_WIDGET(priv->video_widget),
call->videoRenderer(),
priv->call->videoRenderer(),
VIDEO_RENDERER_REMOTE);
/* callback for remote renderer */
priv->remote_renderer_connection = QObject::connect(
call,
priv->call,
&Call::videoStarted,
[=](Video::Renderer *renderer) {
[priv](Video::Renderer *renderer) {
video_widget_push_new_renderer(VIDEO_WIDGET(priv->video_widget),
renderer,
VIDEO_RENDERER_REMOTE);
......@@ -271,7 +389,7 @@ current_call_view_set_call_info(CurrentCallView *view, const QModelIndex& idx) {
priv->local_renderer_connection = QObject::connect(
Video::PreviewManager::instance(),
&Video::PreviewManager::previewStarted,
[=](Video::Renderer *renderer) {
[priv](Video::Renderer *renderer) {
video_widget_push_new_renderer(VIDEO_WIDGET(priv->video_widget),
renderer,
VIDEO_RENDERER_LOCAL);
......@@ -282,4 +400,25 @@ current_call_view_set_call_info(CurrentCallView *view, const QModelIndex& idx) {
g_signal_connect(priv->video_widget, "button-press-event",
G_CALLBACK(on_button_press_in_video_event),
view);
/* check if text media is already present */
if (priv->call->hasMedia(Media::Media::Type::TEXT, Media::Media::Direction::IN)) {
Media::Text *text = priv->call->firstMedia<Media::Text>(Media::Media::Direction::IN);
parse_chat_model(text->recording()->instantMessagingModel(), view);
} else if (priv->call->hasMedia(Media::Media::Type::TEXT, Media::Media::Direction::OUT)) {
Media::Text *text = priv->call->firstMedia<Media::Text>(Media::Media::Direction::OUT);
parse_chat_model(text->recording()->instantMessagingModel(), view);
} else {
/* monitor media for messaging text messaging */
priv->media_added_connection = QObject::connect(
priv->call,
&Call::mediaAdded,
[view, priv] (Media::Media* media) {
if (media->type() == Media::Media::Type::TEXT) {
parse_chat_model(((Media::Text*)media)->recording()->instantMessagingModel(), view);
QObject::disconnect(priv->media_added_connection);
}
}
);
}
}
......@@ -60,8 +60,84 @@
<property name="visible">False</property>
<property name="shadow-type">GTK_SHADOW_NONE</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
</packing>
</child>
<!-- end of video widget -->
<!-- start of text message area -->
<child>
<object class="GtkRevealer" id="revealer_chat">
<property name="visible">True</property>
<child>
<object class="GtkBox" id="vbox_chat">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<property name="spacing">5</property>
<property name="margin-start">5</property>
<!-- start of chat text view -->
<child>
<object class="GtkScrolledWindow" id="scrolledwindow_chat">
<property name="visible">True</property>
<property name="min-content-height">150</property>
<child>
<object class="GtkTextView" id="textview_chat">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="editable">False</property>
<property name="wrap-mode">word-char</property>
<property name="left-margin">5</property>
<property name="right-margin">5</property>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
</packing>
</child>
<!-- end of chat text view -->
<!-- start of chat entry -->
<child>
<object class="GtkBox" id="hbox_chat_input">
<property name="visible">True</property>
<property name="orientation">horizontal</property>
<property name="spacing">0</property>
<style>
<class name="linked"/>
</style>
<child>
<object class="GtkEntry" id="entry_chat_input">
<property name="visible">True</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
</packing>
</child>
<child>
<object class="GtkButton" id="button_chat_input">
<property name="visible">True</property>
<property name="label" translatable="yes">Send</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
</packing>
</child>
<!-- end of chat entry -->
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
</packing>
</child>
<!-- end of text message area -->
<!-- call status box -->
<child>
<object class="GtkBox" id="hbox_call_status">
......@@ -109,83 +185,106 @@
<!-- box which contains the control buttons -->
<child>
<object class="GtkBox" id="hbox_call_controls">
<object class="GtkBox" id="hbox_controls">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">center</property>
<property name="valign">start</property>
<property name="border_width">10</property>
<style>
<class name="linked"/>
</style>
<!-- call conrol button box -->
<child>
<object class="GtkButton" id="button_hangup">
<property name="label" translatable="yes">Hang-up</property>
<object class="GtkButtonBox" id="buttonbox_call_controls">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="action-name">app.hangup</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
</packing>
</child>
<child>
<object class="GtkToggleButton" id="togglebutton_hold">
<property name="label" translatable="yes">Hold</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="action-name">app.hold</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
</packing>
</child>
<child>
<object class="GtkToggleButton" id="togglebutton_muteaudio">
<property name="label" translatable="yes">Mute audio</property>
<!-- TODO: set visible when feature enabled -->
<property name="visible">False</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="action-name">app.mute_audio</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
</packing>
</child>
<child>
<object class="GtkToggleButton" id="togglebutton_mutevideo">
<property name="label" translatable="yes">Mute video</property>
<!-- TODO: set visible when feature enabled -->
<property name="visible">False</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="action-name">app.mute_video</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
</packing>
</child>
<child>
<object class="GtkToggleButton" id="togglebutton_record">
<property name="label" translatable="yes">Record</property>
<!-- TODO: set visible when feature enabled -->
<property name="visible">False</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="action-name">app.record</property>
<property name="can_focus">False</property>
<property name="halign">center</property>
<property name="valign">start</property>
<style>
<class name="linked"/>
</style>
<child>
<object class="GtkButton" id="button_hangup">
<property name="label" translatable="yes">Hang-up</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="action-name">app.hangup</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
</packing>
</child>
<child>
<object class="GtkToggleButton" id="togglebutton_hold">
<property name="label" translatable="yes">Hold</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="action-name">app.hold</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
</packing>
</child>
<child>
<object class="GtkToggleButton" id="togglebutton_muteaudio">
<property name="label" translatable="yes">Mute audio</property>
<!-- TODO: set visible when feature enabled -->
<property name="visible">False</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="action-name">app.mute_audio</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
</packing>
</child>
<child>
<object class="GtkToggleButton" id="togglebutton_mutevideo">
<property name="label" translatable="yes">Mute video</property>
<!-- TODO: set visible when feature enabled -->
<property name="visible">False</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="action-name">app.mute_video</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
</packing>
</child>
<child>
<object class="GtkToggleButton" id="togglebutton_record">
<property name="label" translatable="yes">Record</property>
<!-- TODO: set visible when feature enabled -->
<property name="visible">False</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="action-name">app.record</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
</packing>
</child>
<child>
<object class="GtkToggleButton" id="togglebutton_chat">
<property name="label" translatable="yes">Chat</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="expand">True</property>
<property name="fill">True</property>
</packing>
</child>
<!-- end of call control button box -->
</object>
<packing>
<property name="expand">False</property>
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment