chatview.cpp 16.3 KB
Newer Older
1
/*
Guillaume Roguez's avatar
Guillaume Roguez committed
2
 *  Copyright (C) 2016-2017 Savoir-faire Linux Inc.
3
 *  Author: Stepan Salenikovich <stepan.salenikovich@savoirfairelinux.com>
aviau's avatar
aviau committed
4
 *  Author: Alexandre Viau <alexandre.viau@savoirfairelinux.com>
5
6
 *  Author: Nicolas Jäger <nicolas.jager@savoirfairelinux.com>
 *  Author: Sébastien Blin <sebastien.blin@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 "chatview.h"

25
26
// std
#include <algorithm>
27

28
29
30
31
// LRC
#include <api/contactmodel.h>
#include <api/conversationmodel.h>
#include <api/contact.h>
32

33
34
// Client
#include "utils/files.h"
35

36
37
38
39
40
41
42
43
44
45
46
47
48
49
struct _ChatView
{
    GtkBox parent;
};

struct _ChatViewClass
{
    GtkBoxClass parent_class;
};

typedef struct _ChatViewPrivate ChatViewPrivate;

struct _ChatViewPrivate
{
aviau's avatar
aviau committed
50
51
    GtkWidget *box_webkit_chat_container;
    GtkWidget *webkit_chat_container;
Stepan Salenikovich's avatar
Stepan Salenikovich committed
52
53
    GtkWidget *hbox_chat_info;
    GtkWidget *label_peer;
54
    GtkWidget *label_cm;
55
    GtkWidget *button_close_chatview;
56
    GtkWidget *button_placecall;
57
    GtkWidget *button_add_to_conversations;
Stepan Salenikovich's avatar
Stepan Salenikovich committed
58

59
60
    GSettings *settings;

61
62
63
    lrc::api::conversation::Info* conversation_;
    AccountContainer* accountContainer_;
    bool isTemporary_;
64

65
66
67
    QMetaObject::Connection new_interaction_connection;
    QMetaObject::Connection update_interaction_connection;
    QMetaObject::Connection update_add_to_conversations;
68
69

    gulong webkit_ready;
70
    gulong webkit_send_text;
71
72
73
74
75
76
77
78
};

G_DEFINE_TYPE_WITH_PRIVATE(ChatView, chat_view, GTK_TYPE_BOX);

#define CHAT_VIEW_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), CHAT_VIEW_TYPE, ChatViewPrivate))

enum {
    NEW_MESSAGES_DISPLAYED,
79
    HIDE_VIEW_CLICKED,
80
81
82
83
84
85
86
87
88
89
90
91
92
93
    LAST_SIGNAL
};

static guint chat_view_signals[LAST_SIGNAL] = { 0 };

static void
chat_view_dispose(GObject *object)
{
    ChatView *view;
    ChatViewPrivate *priv;

    view = CHAT_VIEW(object);
    priv = CHAT_VIEW_GET_PRIVATE(view);

94
95
96
    QObject::disconnect(priv->new_interaction_connection);
    QObject::disconnect(priv->update_interaction_connection);
    QObject::disconnect(priv->update_add_to_conversations);
aviau's avatar
aviau committed
97
98
99
100

    /* Destroying the box will also destroy its children, and we wouldn't
     * want that. So we remove the webkit_chat_container from the box. */
    if (priv->webkit_chat_container) {
101
102
103
        /* disconnect for webkit signals */
        g_signal_handler_disconnect(priv->webkit_chat_container, priv->webkit_ready);
        priv->webkit_ready = 0;
104
105
        g_signal_handler_disconnect(priv->webkit_chat_container, priv->webkit_send_text);
        priv->webkit_send_text = 0;
106

aviau's avatar
aviau committed
107
108
109
110
111
112
        gtk_container_remove(
            GTK_CONTAINER(priv->box_webkit_chat_container),
            GTK_WIDGET(priv->webkit_chat_container)
        );
        priv->webkit_chat_container = nullptr;
    }
113
114
115
116

    G_OBJECT_CLASS(chat_view_parent_class)->dispose(object);
}

117
118
119
120
121
122
static void
hide_chat_view(G_GNUC_UNUSED GtkWidget *widget, ChatView *self)
{
    g_signal_emit(G_OBJECT(self), chat_view_signals[HIDE_VIEW_CLICKED], 0);
}

123
124
125
126
127
128
129
130
131
132
133
134
static void
display_links_toggled(ChatView *self)
{
    auto priv = CHAT_VIEW_GET_PRIVATE(self);
    if (priv->webkit_chat_container) {
        webkit_chat_container_set_display_links(
            WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container),
            g_settings_get_boolean(priv->settings, "enable-display-links")
        );
    }
}

135
136
137
138
static void
placecall_clicked(ChatView *self)
{
    auto priv = CHAT_VIEW_GET_PRIVATE(self);
139
    priv->accountContainer_->info.conversationModel->placeCall(priv->conversation_->uid);
140
141
}

142
static void
143
button_add_to_conversations_clicked(ChatView *self)
144
145
{
    auto priv = CHAT_VIEW_GET_PRIVATE(self);
146
    priv->accountContainer_->info.conversationModel->makePermanent(priv->conversation_->uid);
147
148
}

149
static void
150
webkit_chat_container_script_dialog(G_GNUC_UNUSED GtkWidget* webview, gchar *interaction, ChatView* self)
151
{
152
153
154
155
156
157
158
159
160
161
162
163
    auto priv = CHAT_VIEW_GET_PRIVATE(self);
    auto order = std::string(interaction);
    if (order == "ACCEPT") {
        priv->accountContainer_->info.conversationModel->makePermanent(priv->conversation_->uid);
    } else if (order == "REFUSE") {
        priv->accountContainer_->info.conversationModel->removeConversation(priv->conversation_->uid);
    } else if (order == "BLOCK") {
        priv->accountContainer_->info.conversationModel->removeConversation(priv->conversation_->uid, true);
    } else if (order.find("SEND:") == 0) {
        // Get text body
        auto toSend = order.substr(std::string("SEND:").size());
        priv->accountContainer_->info.conversationModel->sendMessage(priv->conversation_->uid, toSend);
164
165
166
    }
}

167
168
169
170
171
172
static void
chat_view_init(ChatView *view)
{
    gtk_widget_init_template(GTK_WIDGET(view));

    ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(view);
173
    priv->settings = g_settings_new_full(get_ring_schema(), NULL, NULL);
174

175
    g_signal_connect(priv->button_close_chatview, "clicked", G_CALLBACK(hide_chat_view), view);
176
    g_signal_connect_swapped(priv->button_placecall, "clicked", G_CALLBACK(placecall_clicked), view);
177
    g_signal_connect_swapped(priv->button_add_to_conversations, "clicked", G_CALLBACK(button_add_to_conversations_clicked), view);
178
179
180
181
182
183
184
185
186
187
}

static void
chat_view_class_init(ChatViewClass *klass)
{
    G_OBJECT_CLASS(klass)->dispose = chat_view_dispose;

    gtk_widget_class_set_template_from_resource(GTK_WIDGET_CLASS (klass),
                                                "/cx/ring/RingGnome/chatview.ui");

aviau's avatar
aviau committed
188
    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), ChatView, box_webkit_chat_container);
Stepan Salenikovich's avatar
Stepan Salenikovich committed
189
190
    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), ChatView, hbox_chat_info);
    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), ChatView, label_peer);
191
    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), ChatView, label_cm);
192
    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), ChatView, button_close_chatview);
193
    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), ChatView, button_placecall);
194
    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), ChatView, button_add_to_conversations);
195
196

    chat_view_signals[NEW_MESSAGES_DISPLAYED] = g_signal_new (
197
        "new-interactions-displayed",
198
199
200
201
202
203
204
        G_TYPE_FROM_CLASS(klass),
        (GSignalFlags) (G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION),
        0,
        nullptr,
        nullptr,
        g_cclosure_marshal_VOID__VOID,
        G_TYPE_NONE, 0);
205
206
207
208
209
210
211
212
213
214

    chat_view_signals[HIDE_VIEW_CLICKED] = g_signal_new (
        "hide-view-clicked",
        G_TYPE_FROM_CLASS(klass),
        (GSignalFlags) (G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION),
        0,
        nullptr,
        nullptr,
        g_cclosure_marshal_VOID__VOID,
        G_TYPE_NONE, 0);
215
216
}

aviau's avatar
aviau committed
217
static void
218
print_interaction_to_buffer(ChatView* self, uint64_t interactionId, const lrc::api::interaction::Info& interaction)
aviau's avatar
aviau committed
219
220
221
{
    ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);

222
223
224
225
    if (interaction.status == lrc::api::interaction::Status::UNREAD)
        priv->accountContainer_->info.conversationModel->setInteractionRead(priv->conversation_->uid, interactionId);

    webkit_chat_container_print_new_interaction(
aviau's avatar
aviau committed
226
        WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container),
227
228
        interactionId,
        interaction
aviau's avatar
aviau committed
229
    );
230
231
}

232
static void
233
update_interaction(ChatView* self, uint64_t interactionId, const lrc::api::interaction::Info& interaction)
234
235
{
    ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);
236
237
238
239
    webkit_chat_container_update_interaction(
        WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container),
        interactionId,
        interaction
240
    );
241
}
242

243
244
245
246
247
static void
load_participants_images(ChatView *self)
{
    g_return_if_fail(IS_CHAT_VIEW(self));
    ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);
248

249
250
251
252
    // Contact
    auto contactUri = priv->conversation_->participants.front();
    auto& contact = priv->accountContainer_->info.contactModel->getContact(contactUri);
    if (!contact.profileInfo.avatar.empty()) {
253
254
        webkit_chat_container_set_sender_image(
            WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container),
255
256
            priv->accountContainer_->info.contactModel->getContactProfileId(contactUri),
            contact.profileInfo.avatar
257
258
259
        );
    }

260
261
262
263
264
265
266
    // For this account
    if (!priv->accountContainer_->info.profileInfo.avatar.empty()) {
        webkit_chat_container_set_sender_image(
            WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container),
            priv->accountContainer_->info.contactModel->getContactProfileId(priv->accountContainer_->info.profileInfo.uri),
            priv->accountContainer_->info.profileInfo.avatar
        );
267
268
269
    }
}

270
static void
271
print_text_recording(ChatView *self)
272
273
274
275
{
    g_return_if_fail(IS_CHAT_VIEW(self));
    ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);

276
277
    for (const auto& interaction : priv->conversation_->interactions)
        print_interaction_to_buffer(self, interaction.first, interaction.second);
278

279
    QObject::disconnect(priv->new_interaction_connection);
280
281
}

282
static void
283
update_add_to_conversations(ChatView *self)
284
285
286
{
    ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);

287
288
289
290
291
    auto participant = priv->conversation_->participants[0];
    auto contactInfo = priv->accountContainer_->info.contactModel->getContact(participant);
    if(contactInfo.profileInfo.type != lrc::api::profile::Type::TEMPORARY
       && contactInfo.profileInfo.type != lrc::api::profile::Type::PENDING)
        gtk_widget_hide(priv->button_add_to_conversations);
292
293
}

Stepan Salenikovich's avatar
Stepan Salenikovich committed
294
295
296
297
298
static void
update_contact_methods(ChatView *self)
{
    g_return_if_fail(IS_CHAT_VIEW(self));
    ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);
299
300
301
    auto contactUri = priv->conversation_->participants.front();
    auto contactInfo = priv->accountContainer_->info.contactModel->getContact(contactUri);
    if (contactInfo.profileInfo.alias == contactInfo.registeredName) {
302
        gtk_widget_hide(priv->label_cm);
303
304
305
    } else {
        gtk_label_set_text(GTK_LABEL(priv->label_cm), contactInfo.registeredName.c_str());
        gtk_widget_show(priv->label_cm);
306
    }
307

Stepan Salenikovich's avatar
Stepan Salenikovich committed
308
309
310
}

static void
311
update_name(ChatView *self)
Stepan Salenikovich's avatar
Stepan Salenikovich committed
312
{
313
    g_return_if_fail(IS_CHAT_VIEW(self));
Stepan Salenikovich's avatar
Stepan Salenikovich committed
314
    ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);
315
316
317
318
319
    auto contactUri = priv->conversation_->participants.front();
    auto contactInfo = priv->accountContainer_->info.contactModel->getContact(contactUri);
    auto alias = contactInfo.profileInfo.alias;
    alias.erase(std::remove(alias.begin(), alias.end(), '\r'), alias.end());
    gtk_label_set_text(GTK_LABEL(priv->label_peer), alias.c_str());
Stepan Salenikovich's avatar
Stepan Salenikovich committed
320
321
}

aviau's avatar
aviau committed
322
323
324
325
326
327
328
329
330
331
332
333
static void
webkit_chat_container_ready(ChatView* self)
{
    /* The webkit chat container has loaded the javascript libraries, we can
     * now use it. */

    ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);

    webkit_chat_container_clear(
        WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container)
    );

334
    display_links_toggled(self);
335
336
337
338
339
340
341
342
343
344
    print_text_recording(self);
    load_participants_images(self);

    priv->new_interaction_connection = QObject::connect(
    &*priv->accountContainer_->info.conversationModel, &lrc::api::ConversationModel::newUnreadMessage,
    [self, priv](const std::string& uid, uint64_t interactionId, lrc::api::interaction::Info interaction) {
        if(uid == priv->conversation_->uid) {
            print_interaction_to_buffer(self, interactionId, interaction);
        }
    });
345

346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
    priv->update_interaction_connection = QObject::connect(
    &*priv->accountContainer_->info.conversationModel, &lrc::api::ConversationModel::interactionStatusUpdated,
    [self, priv](const std::string& uid, uint64_t msgId, lrc::api::interaction::Info msg) {
        if(uid == priv->conversation_->uid) {
            update_interaction(self, msgId, msg);
        }
    });

    auto contactUri = priv->conversation_->participants.front();
    auto contactInfo = priv->accountContainer_->info.contactModel->getContact(contactUri);
    priv->isTemporary_ = contactInfo.profileInfo.type == lrc::api::profile::Type::TEMPORARY
                         || contactInfo.profileInfo.type == lrc::api::profile::Type::PENDING;
    webkit_chat_container_set_temporary(WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container), priv->isTemporary_);
    auto bestName = contactInfo.profileInfo.alias;
    if (bestName.empty())
        bestName = contactInfo.registeredName;
    if (bestName.empty())
        bestName = contactInfo.profileInfo.uri;
    bestName.erase(std::remove(bestName.begin(), bestName.end(), '\r'), bestName.end());
    webkit_chat_container_set_invitation(WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container),
                                         (contactInfo.profileInfo.type == lrc::api::profile::Type::PENDING),
                                         bestName);
    webkit_chat_disable_send_interaction(WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container),
                                        (contactInfo.profileInfo.type == lrc::api::profile::Type::SIP)
                                         && priv->conversation_->callId.empty());
aviau's avatar
aviau committed
371
372
373
374
375
376
377
378
379
380
}

static void
build_chat_view(ChatView* self)
{
    ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);

    gtk_container_add(GTK_CONTAINER(priv->box_webkit_chat_container), priv->webkit_chat_container);
    gtk_widget_show(priv->webkit_chat_container);

381
    update_name(self);
382
    update_add_to_conversations(self);
383
    update_contact_methods(self);
aviau's avatar
aviau committed
384

385
    priv->webkit_ready = g_signal_connect_swapped(
aviau's avatar
aviau committed
386
387
388
389
390
391
        priv->webkit_chat_container,
        "ready",
        G_CALLBACK(webkit_chat_container_ready),
        self
    );

392
    priv->webkit_send_text = g_signal_connect(priv->webkit_chat_container,
393
394
        "script-dialog",
        G_CALLBACK(webkit_chat_container_script_dialog),
395
396
        self);

aviau's avatar
aviau committed
397
398
399
    if (webkit_chat_container_is_ready(WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container)))
        webkit_chat_container_ready(self);

400
    gtk_widget_set_visible(priv->hbox_chat_info, TRUE);
aviau's avatar
aviau committed
401

402
}
Stepan Salenikovich's avatar
Stepan Salenikovich committed
403
404

GtkWidget *
405
406
407
chat_view_new (WebKitChatContainer* webkit_chat_container,
               AccountContainer* accountContainer,
               lrc::api::conversation::Info* conversation)
Stepan Salenikovich's avatar
Stepan Salenikovich committed
408
409
410
{
    ChatView *self = CHAT_VIEW(g_object_new(CHAT_VIEW_TYPE, NULL));

aviau's avatar
aviau committed
411
412
    ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);
    priv->webkit_chat_container = GTK_WIDGET(webkit_chat_container);
413
414
    priv->conversation_ = conversation;
    priv->accountContainer_ = accountContainer;
aviau's avatar
aviau committed
415
416

    build_chat_view(self);
Stepan Salenikovich's avatar
Stepan Salenikovich committed
417
418
419
    return (GtkWidget *)self;
}

420
421
void
chat_view_update_temporary(ChatView* self, bool newValue)
422
{
423
    g_return_if_fail(IS_CHAT_VIEW(self));
424
425
    auto priv = CHAT_VIEW_GET_PRIVATE(self);

426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
    priv->isTemporary_ = newValue;
    if (!priv->isTemporary_) {
        gtk_widget_hide(priv->button_add_to_conversations);
    }
    webkit_chat_container_set_temporary(WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container), priv->isTemporary_);
    auto contactUri = priv->conversation_->participants.front();
    auto contactInfo = priv->accountContainer_->info.contactModel->getContact(contactUri);
    auto bestName = contactInfo.profileInfo.alias;
    if (bestName.empty())
        bestName = contactInfo.registeredName;
    if (bestName.empty())
        bestName = contactInfo.profileInfo.uri;
    webkit_chat_container_set_invitation(WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container),
                                         newValue,
                                         bestName);
441
442
}

443
444
bool
chat_view_get_temporary(ChatView *self)
445
{
446
    g_return_val_if_fail(IS_CHAT_VIEW(self), false);
447
    auto priv = CHAT_VIEW_GET_PRIVATE(self);
448
    return priv->isTemporary_;
449
450
}

451
452
lrc::api::conversation::Info
chat_view_get_conversation(ChatView *self)
453
{
454
    g_return_val_if_fail(IS_CHAT_VIEW(self), lrc::api::conversation::Info());
455
    auto priv = CHAT_VIEW_GET_PRIVATE(self);
456
    return *priv->conversation_;
457
}
458
459
460
461
462
463
464

void
chat_view_set_header_visible(ChatView *self, gboolean visible)
{
    auto priv = CHAT_VIEW_GET_PRIVATE(self);
    gtk_widget_set_visible(priv->hbox_chat_info, visible);
}