blob: 3b0f8a2031e07d814d04a9748bf3009d7e323aeb [file] [log] [blame]
/*
* Copyright (C) 2015-2017 Savoir-faire Linux Inc.
* Author: Stepan Salenikovich <stepan.salenikovich@savoirfairelinux.com>
* Author: Nicolas Jäger <nicolas.jager@savoirfairelinux.com>
* Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "incomingcallview.h"
// Gtk
#include <gtk/gtk.h>
#include <QSize>
// Lrc
#include <api/newcallmodel.h>
#include <api/contactmodel.h>
#include <api/conversationmodel.h>
#include <api/contact.h>
#include <globalinstances.h>
// Client
#include "chatview.h"
#include "native/pixbufmanipulator.h"
#include "utils/drawing.h"
#include "utils/files.h"
struct _IncomingCallView
{
GtkBox parent;
};
struct _IncomingCallViewClass
{
GtkBoxClass parent_class;
};
typedef struct _IncomingCallViewPrivate IncomingCallViewPrivate;
struct _IncomingCallViewPrivate
{
GtkWidget *paned_call;
GtkWidget *image_incoming;
GtkWidget *label_name;
GtkWidget *label_bestId;
GtkWidget *spinner_status;
GtkWidget *label_status;
GtkWidget *button_accept_incoming;
GtkWidget *button_reject_incoming;
GtkWidget *frame_chat;
// The webkit_chat_container is created once, then reused for all chat views
GtkWidget *webkit_chat_container;
lrc::api::conversation::Info* conversation_;
AccountContainer* accountContainer_;
QMetaObject::Connection state_change_connection;
GSettings *settings;
};
G_DEFINE_TYPE_WITH_PRIVATE(IncomingCallView, incoming_call_view, GTK_TYPE_BOX);
#define INCOMING_CALL_VIEW_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), INCOMING_CALL_VIEW_TYPE, IncomingCallViewPrivate))
static void
incoming_call_view_dispose(GObject *object)
{
IncomingCallView *view;
IncomingCallViewPrivate *priv;
view = INCOMING_CALL_VIEW(object);
priv = INCOMING_CALL_VIEW_GET_PRIVATE(view);
QObject::disconnect(priv->state_change_connection);
g_clear_object(&priv->settings);
G_OBJECT_CLASS(incoming_call_view_parent_class)->dispose(object);
}
static gboolean
map_boolean_to_orientation(GValue *value, GVariant *variant, G_GNUC_UNUSED gpointer user_data)
{
if (g_variant_is_of_type(variant, G_VARIANT_TYPE_BOOLEAN)) {
if (g_variant_get_boolean(variant)) {
// true, chat should be horizontal (to the right)
g_value_set_enum(value, GTK_ORIENTATION_HORIZONTAL);
} else {
// false, chat should be vertical (at the bottom)
g_value_set_enum(value, GTK_ORIENTATION_VERTICAL);
}
return TRUE;
}
return FALSE;
}
static void
reject_incoming_call(G_GNUC_UNUSED GtkWidget *widget, ChatView *self)
{
auto priv = INCOMING_CALL_VIEW_GET_PRIVATE(self);
priv->accountContainer_->info.callModel->hangUp(priv->conversation_->callId);
}
static void
accept_incoming_call(G_GNUC_UNUSED GtkWidget *widget, ChatView *self)
{
auto priv = INCOMING_CALL_VIEW_GET_PRIVATE(self);
auto contactUri = priv->conversation_->participants[0];
try {
auto contact = priv->accountContainer_->info.contactModel->getContact(contactUri);
// If the contact is pending, we should accept its request
if (contact.profileInfo.type == lrc::api::profile::Type::PENDING)
priv->accountContainer_->info.conversationModel->makePermanent(contactUri);
// Accept call
priv->accountContainer_->info.callModel->accept(priv->conversation_->callId);
} catch (const std::out_of_range&) {
// ContactModel::getContact() exception
}
}
static void
incoming_call_view_init(IncomingCallView *view)
{
gtk_widget_init_template(GTK_WIDGET(view));
auto provider = gtk_css_provider_new();
gtk_css_provider_load_from_data(provider,
".flat-button { border: 0; border-radius: 50%; transition: all 0.3s ease; } \
.red-button { background: #dc3a37; } \
.green-button { background: #27ae60; } \
.red-button:hover { background: #dc2719; } \
.green-button:hover { background: #219d55; }",
-1, nullptr
);
gtk_style_context_add_provider_for_screen(gdk_display_get_default_screen(gdk_display_get_default()),
GTK_STYLE_PROVIDER(provider),
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
auto priv = INCOMING_CALL_VIEW_GET_PRIVATE(view);
/* bind the chat orientation to the gsetting */
priv->settings = g_settings_new_full(get_ring_schema(), NULL, NULL);
g_settings_bind_with_mapping(priv->settings, "chat-pane-horizontal",
priv->paned_call, "orientation",
G_SETTINGS_BIND_GET,
map_boolean_to_orientation,
nullptr, nullptr, nullptr);
g_signal_connect(priv->button_reject_incoming, "clicked", G_CALLBACK(reject_incoming_call), view);
g_signal_connect(priv->button_accept_incoming, "clicked", G_CALLBACK(accept_incoming_call), view);
}
static void
incoming_call_view_class_init(IncomingCallViewClass *klass)
{
G_OBJECT_CLASS(klass)->dispose = incoming_call_view_dispose;
gtk_widget_class_set_template_from_resource(GTK_WIDGET_CLASS (klass),
"/cx/ring/RingGnome/incomingcallview.ui");
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), IncomingCallView, paned_call);
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), IncomingCallView, image_incoming);
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), IncomingCallView, label_name);
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), IncomingCallView, label_bestId);
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), IncomingCallView, spinner_status);
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), IncomingCallView, label_status);
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), IncomingCallView, button_accept_incoming);
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), IncomingCallView, button_reject_incoming);
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), IncomingCallView, frame_chat);
}
static void
update_state(IncomingCallView *view)
{
IncomingCallViewPrivate *priv = INCOMING_CALL_VIEW_GET_PRIVATE(view);
// change state label
auto callId = priv->conversation_->callId;
if (!priv->accountContainer_->info.callModel->hasCall(callId)) return;
auto call = priv->accountContainer_->info.callModel->getCall(callId);
gchar *status = g_strdup_printf("%s", lrc::api::call::to_string(call.status).c_str());
gtk_label_set_text(GTK_LABEL(priv->label_status), status);
g_free(status);
if (call.status == lrc::api::call::Status::INCOMING_RINGING)
gtk_widget_show(priv->button_accept_incoming);
else
gtk_widget_hide(priv->button_accept_incoming);
gtk_widget_show(priv->button_reject_incoming);
gtk_widget_show(priv->spinner_status);
}
static void
update_name_and_photo(IncomingCallView *view)
{
auto priv = INCOMING_CALL_VIEW_GET_PRIVATE(view);
QVariant var_i = GlobalInstances::pixmapManipulator().conversationPhoto(
*priv->conversation_,
priv->accountContainer_->info,
QSize(110, 110),
false
);
std::shared_ptr<GdkPixbuf> image = var_i.value<std::shared_ptr<GdkPixbuf>>();
gtk_image_set_from_pixbuf(GTK_IMAGE(priv->image_incoming), image.get());
try {
auto contactInfo = priv->accountContainer_->info.contactModel->getContact(priv->conversation_->participants.front());
auto name = contactInfo.profileInfo.alias;
gtk_label_set_text(GTK_LABEL(priv->label_name), name.c_str());
auto bestId = contactInfo.registeredName;
if (name != bestId) {
gtk_label_set_text(GTK_LABEL(priv->label_bestId), bestId.c_str());
gtk_widget_show(priv->label_bestId);
}
} catch (const std::out_of_range&) {
// ContactModel::getContact() exception
}
}
static void
set_call_info(IncomingCallView *view) {
IncomingCallViewPrivate *priv = INCOMING_CALL_VIEW_GET_PRIVATE(view);
update_state(view);
update_name_and_photo(view);
// Update view if call state changes
priv->state_change_connection = QObject::connect(
&*priv->accountContainer_->info.callModel,
&lrc::api::NewCallModel::callStatusChanged,
[view, priv] (const std::string& callId) {
if (callId == priv->conversation_->callId) {
update_state(view);
update_name_and_photo(view);
}
});
auto chat_view = chat_view_new(WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container),
priv->accountContainer_,
priv->conversation_);
gtk_widget_show(chat_view);
chat_view_set_header_visible(CHAT_VIEW(chat_view), FALSE);
gtk_container_add(GTK_CONTAINER(priv->frame_chat), chat_view);
}
GtkWidget *
incoming_call_view_new(WebKitChatContainer* view,
AccountContainer* accountContainer,
lrc::api::conversation::Info* conversation)
{
auto self = g_object_new(INCOMING_CALL_VIEW_TYPE, NULL);
IncomingCallViewPrivate *priv = INCOMING_CALL_VIEW_GET_PRIVATE(self);
priv->webkit_chat_container = GTK_WIDGET(view);
priv->conversation_ = conversation;
priv->accountContainer_ = accountContainer;
set_call_info(INCOMING_CALL_VIEW(self));
return GTK_WIDGET(self);
}
lrc::api::conversation::Info
incoming_call_view_get_conversation(IncomingCallView *self)
{
g_return_val_if_fail(IS_INCOMING_CALL_VIEW(self), lrc::api::conversation::Info());
auto priv = INCOMING_CALL_VIEW_GET_PRIVATE(self);
return *priv->conversation_;
}