| /* |
| * 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_; |
| } |