incomingcallview.cpp 12.6 KB
Newer Older
1
/*
Sébastien Blin's avatar
Sébastien Blin committed
2
 *  Copyright (C) 2015-2019 Savoir-faire Linux Inc.
3
 *  Author: Stepan Salenikovich <stepan.salenikovich@savoirfairelinux.com>
4 5
 *  Author: Nicolas Jäger <nicolas.jager@savoirfairelinux.com>
 *  Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com>
6
 *  Author: Hugo Lefeuvre <hugo.lefeuvre@savoirfairelinux.com>
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
 *
 *  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"

25 26 27 28
/* std */
#include <stdexcept>

// GTK+ related
29
#include <gtk/gtk.h>
30 31 32 33
#include <glib/gi18n.h>
#include <glib/gstdio.h>

// Qt
34 35 36
#include <QSize>

// Lrc
37
#include <api/avmodel.h>
38 39 40 41
#include <api/newcallmodel.h>
#include <api/contactmodel.h>
#include <api/conversationmodel.h>
#include <api/contact.h>
42
#include <globalinstances.h>
43 44

// Client
45
#include "chatview.h"
46
#include "messagingwidget.h"
47 48
#include "native/pixbufmanipulator.h"
#include "utils/drawing.h"
49
#include "utils/files.h"
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64

struct _IncomingCallView
{
    GtkBox parent;
};

struct _IncomingCallViewClass
{
    GtkBoxClass parent_class;
};

typedef struct _IncomingCallViewPrivate IncomingCallViewPrivate;

struct _IncomingCallViewPrivate
{
65
    GtkWidget *paned_call;
66
    GtkWidget *image_incoming;
67
    GtkWidget *label_name;
Nicolas Jager's avatar
Nicolas Jager committed
68
    GtkWidget *label_bestId;
69
    GtkWidget *spinner_status;
Stepan Salenikovich's avatar
Stepan Salenikovich committed
70 71 72
    GtkWidget *label_status;
    GtkWidget *button_accept_incoming;
    GtkWidget *button_reject_incoming;
73
    GtkWidget *frame_chat;
74 75
    GtkWidget *box_messaging_widget;
    GtkWidget *messaging_widget;
Stepan Salenikovich's avatar
Stepan Salenikovich committed
76

77
    // The webkit_chat_container is created once, then reused for all chat views
aviau's avatar
aviau committed
78 79
    GtkWidget *webkit_chat_container;

80
    lrc::api::conversation::Info* conversation_;
81
    AccountInfoPointer const * accountInfo_;
82

Stepan Salenikovich's avatar
Stepan Salenikovich committed
83
    QMetaObject::Connection state_change_connection;
84 85

    GSettings *settings;
86 87 88 89 90 91
};

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))

Stepan Salenikovich's avatar
Stepan Salenikovich committed
92
static void
93
incoming_call_view_dispose(GObject *object)
Stepan Salenikovich's avatar
Stepan Salenikovich committed
94 95 96 97 98 99 100
{
    IncomingCallView *view;
    IncomingCallViewPrivate *priv;

    view = INCOMING_CALL_VIEW(object);
    priv = INCOMING_CALL_VIEW_GET_PRIVATE(view);

Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
101 102 103 104 105
    // navbar was hidden during init, we need to make it visible again before view destruction
    auto children = gtk_container_get_children(GTK_CONTAINER(priv->frame_chat));
    auto chat_view = children->data;
    chat_view_set_header_visible(CHAT_VIEW(chat_view), TRUE);

Stepan Salenikovich's avatar
Stepan Salenikovich committed
106 107
    QObject::disconnect(priv->state_change_connection);

108 109
    g_clear_object(&priv->settings);

Stepan Salenikovich's avatar
Stepan Salenikovich committed
110 111 112
    G_OBJECT_CLASS(incoming_call_view_parent_class)->dispose(object);
}

113 114 115
static gboolean
map_boolean_to_orientation(GValue *value, GVariant *variant, G_GNUC_UNUSED gpointer user_data)
{
116
    bool ret = FALSE;
117 118 119 120 121 122 123 124
    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);
        }
125
        ret = TRUE;
126
    }
127
    return ret;
128 129
}

130
static void
131
reject_incoming_call(IncomingCallView *self)
132
{
Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
133
    g_return_if_fail(IS_INCOMING_CALL_VIEW(self));
134
    auto priv = INCOMING_CALL_VIEW_GET_PRIVATE(self);
135
    (*priv->accountInfo_)->callModel->hangUp(priv->conversation_->callId);
136 137 138
}

static void
139
accept_incoming_call(IncomingCallView *self)
140
{
Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
141
    g_return_if_fail(IS_INCOMING_CALL_VIEW(self));
142
    auto priv = INCOMING_CALL_VIEW_GET_PRIVATE(self);
143

144
    try {
145
        auto contactUri = priv->conversation_->participants.at(0);
146
        auto contact = (*priv->accountInfo_)->contactModel->getContact(contactUri);
147 148
        // If the contact is pending, we should accept its request
        if (contact.profileInfo.type == lrc::api::profile::Type::PENDING)
149
            (*priv->accountInfo_)->conversationModel->makePermanent(priv->conversation_->uid);
150
        // Accept call
151
        (*priv->accountInfo_)->callModel->accept(priv->conversation_->callId);
152 153 154
    } catch (const std::out_of_range&) {
        // ContactModel::getContact() exception
    }
155 156
}

157 158 159 160 161 162 163 164
static void
on_leave_action(IncomingCallView *view)
{
    g_return_if_fail(IS_INCOMING_CALL_VIEW(view));
    auto priv = INCOMING_CALL_VIEW_GET_PRIVATE(view);
    (*priv->accountInfo_)->conversationModel->selectConversation(priv->conversation_->uid);
}

165 166 167 168
static void
incoming_call_view_init(IncomingCallView *view)
{
    gtk_widget_init_template(GTK_WIDGET(view));
169

170 171 172 173 174 175 176 177 178
    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
    );
179

180 181 182 183
    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);

184 185 186
    auto priv = INCOMING_CALL_VIEW_GET_PRIVATE(view);

    /* bind the chat orientation to the gsetting */
187
    priv->settings = g_settings_new_full(get_settings_schema(), NULL, NULL);
188 189 190 191 192
    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);
193

194 195
    g_signal_connect_swapped(priv->button_reject_incoming, "clicked", G_CALLBACK(reject_incoming_call), view);
    g_signal_connect_swapped(priv->button_accept_incoming, "clicked", G_CALLBACK(accept_incoming_call), view);
196 197 198 199 200
}

static void
incoming_call_view_class_init(IncomingCallViewClass *klass)
{
201
    G_OBJECT_CLASS(klass)->dispose = incoming_call_view_dispose;
Stepan Salenikovich's avatar
Stepan Salenikovich committed
202

203
    gtk_widget_class_set_template_from_resource(GTK_WIDGET_CLASS (klass),
204
                                                "/net/jami/JamiGnome/incomingcallview.ui");
205

206
    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), IncomingCallView, paned_call);
207
    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), IncomingCallView, image_incoming);
208
    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), IncomingCallView, label_name);
Nicolas Jager's avatar
Nicolas Jager committed
209
    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), IncomingCallView, label_bestId);
210
    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), IncomingCallView, spinner_status);
Stepan Salenikovich's avatar
Stepan Salenikovich committed
211 212
    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_reject_incoming);
213
    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), IncomingCallView, button_accept_incoming);
214
    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), IncomingCallView, frame_chat);
215
    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), IncomingCallView, box_messaging_widget);
216 217
}

Stepan Salenikovich's avatar
Stepan Salenikovich committed
218
static void
219
update_state(IncomingCallView *view)
Stepan Salenikovich's avatar
Stepan Salenikovich committed
220
{
Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
221
    g_return_if_fail(IS_INCOMING_CALL_VIEW(view));
Stepan Salenikovich's avatar
Stepan Salenikovich committed
222 223
    IncomingCallViewPrivate *priv = INCOMING_CALL_VIEW_GET_PRIVATE(view);

224 225
    // change state label
    auto callId = priv->conversation_->callId;
226 227
    if (!(*priv->accountInfo_)->callModel->hasCall(callId)) return;
    auto call = (*priv->accountInfo_)->callModel->getCall(callId);
Stepan Salenikovich's avatar
Stepan Salenikovich committed
228

229
    gtk_label_set_text(GTK_LABEL(priv->label_status), lrc::api::call::to_string(call.status).c_str());
Stepan Salenikovich's avatar
Stepan Salenikovich committed
230

231 232 233 234
    if (call.status == lrc::api::call::Status::INCOMING_RINGING)
        gtk_widget_show(priv->button_accept_incoming);
    else
        gtk_widget_hide(priv->button_accept_incoming);
235

236 237 238 239 240 241 242 243
    if (call.status != lrc::api::call::Status::PEER_BUSY &&
             call.status != lrc::api::call::Status::ENDED) {
        gtk_widget_hide(priv->messaging_widget);
        gtk_widget_show(priv->label_status);
        gtk_widget_show(priv->spinner_status);
        gtk_widget_show(priv->button_reject_incoming);
        gtk_widget_show(priv->spinner_status);
    }
Stepan Salenikovich's avatar
Stepan Salenikovich committed
244 245
}

246
static void
247 248
update_name_and_photo(IncomingCallView *view)
{
Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
249
    g_return_if_fail(IS_INCOMING_CALL_VIEW(view));
250
    auto priv = INCOMING_CALL_VIEW_GET_PRIVATE(view);
251

252 253
    QVariant var_i = GlobalInstances::pixmapManipulator().conversationPhoto(
        *priv->conversation_,
254
        **(priv->accountInfo_),
255 256 257
        QSize(110, 110),
        false
    );
258 259
    std::shared_ptr<GdkPixbuf> image = var_i.value<std::shared_ptr<GdkPixbuf>>();
    gtk_image_set_from_pixbuf(GTK_IMAGE(priv->image_incoming), image.get());
260

261
    try {
262
        auto contactInfo = (*priv->accountInfo_)->contactModel->getContact(priv->conversation_->participants.front());
263

264 265
        auto name = contactInfo.profileInfo.alias;
        gtk_label_set_text(GTK_LABEL(priv->label_name), name.c_str());
266

267 268 269 270 271 272 273
        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
274
    }
275 276 277
}

static void
278
set_call_info(IncomingCallView *view) {
Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
279
    g_return_if_fail(IS_INCOMING_CALL_VIEW(view));
280 281
    IncomingCallViewPrivate *priv = INCOMING_CALL_VIEW_GET_PRIVATE(view);

282 283
    update_state(view);
    update_name_and_photo(view);
Stepan Salenikovich's avatar
Stepan Salenikovich committed
284

285
    // Update view if call state changes
Stepan Salenikovich's avatar
Stepan Salenikovich committed
286
    priv->state_change_connection = QObject::connect(
287
    &*(*priv->accountInfo_)->callModel,
288 289 290 291 292 293 294
    &lrc::api::NewCallModel::callStatusChanged,
    [view, priv] (const std::string& callId) {
        if (callId == priv->conversation_->callId) {
            update_state(view);
            update_name_and_photo(view);
        }
    });
295

296
    auto chat_view = chat_view_new(WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container),
297
                                                         *priv->accountInfo_,
298
                                                         priv->conversation_);
299 300 301
    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);
302
}
303 304

GtkWidget *
305
incoming_call_view_new(WebKitChatContainer* view,
306
                       lrc::api::AVModel& avModel,
307
                       AccountInfoPointer const & accountInfo,
308
                       lrc::api::conversation::Info* conversation)
309 310
{
    auto self = g_object_new(INCOMING_CALL_VIEW_TYPE, NULL);
aviau's avatar
aviau committed
311 312

    IncomingCallViewPrivate *priv = INCOMING_CALL_VIEW_GET_PRIVATE(self);
313 314
    priv->webkit_chat_container = GTK_WIDGET(view);
    priv->conversation_ = conversation;
315
    priv->accountInfo_ = &accountInfo;
aviau's avatar
aviau committed
316

317 318 319 320
    priv->messaging_widget = messaging_widget_new(avModel, conversation, accountInfo);
    gtk_box_pack_start(GTK_BOX(priv->box_messaging_widget), priv->messaging_widget, TRUE, TRUE, 0);
    g_signal_connect_swapped(priv->messaging_widget, "leave-action", G_CALLBACK(on_leave_action), self);

321
    set_call_info(INCOMING_CALL_VIEW(self));
322 323 324 325

    return GTK_WIDGET(self);
}

326 327
lrc::api::conversation::Info
incoming_call_view_get_conversation(IncomingCallView *self)
328
{
329
    g_return_val_if_fail(IS_INCOMING_CALL_VIEW(self), lrc::api::conversation::Info());
330 331
    auto priv = INCOMING_CALL_VIEW_GET_PRIVATE(self);

332
    return *priv->conversation_;
333
}
334 335 336 337 338 339 340 341 342 343 344 345 346 347 348

void
incoming_call_view_let_a_message(IncomingCallView* view, lrc::api::conversation::Info conv)
{
    g_return_if_fail(IS_INCOMING_CALL_VIEW(view));
    auto priv = INCOMING_CALL_VIEW_GET_PRIVATE(view);
    g_return_if_fail(priv->conversation_->uid == conv.uid);

    gtk_widget_hide(priv->label_status);
    gtk_widget_hide(priv->spinner_status);
    gtk_widget_hide(priv->button_accept_incoming);
    gtk_widget_hide(priv->button_reject_incoming);

    gtk_widget_show(priv->messaging_widget);
}