chatview.cpp 16.9 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
    if (!priv->conversation_) return;
140
    priv->accountContainer_->info.conversationModel->placeCall(priv->conversation_->uid);
141
142
}

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

151
static void
152
webkit_chat_container_script_dialog(G_GNUC_UNUSED GtkWidget* webview, gchar *interaction, ChatView* self)
153
{
154
155
    auto priv = CHAT_VIEW_GET_PRIVATE(self);
    auto order = std::string(interaction);
156
    if (!priv->conversation_) return;
157
158
159
160
161
162
163
164
165
166
    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);
167
168
169
    }
}

170
171
172
173
174
175
static void
chat_view_init(ChatView *view)
{
    gtk_widget_init_template(GTK_WIDGET(view));

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

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

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
191
    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), ChatView, box_webkit_chat_container);
Stepan Salenikovich's avatar
Stepan Salenikovich committed
192
193
    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);
194
    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), ChatView, label_cm);
195
    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), ChatView, button_close_chatview);
196
    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), ChatView, button_placecall);
197
    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), ChatView, button_add_to_conversations);
198
199

    chat_view_signals[NEW_MESSAGES_DISPLAYED] = g_signal_new (
200
        "new-interactions-displayed",
201
202
203
204
205
206
207
        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);
208
209
210
211
212
213
214
215
216
217

    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);
218
219
}

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

225
    if (!priv->conversation_) return;
226
227
228
229
    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
230
        WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container),
231
232
        interactionId,
        interaction
aviau's avatar
aviau committed
233
    );
234
235
}

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

247
248
249
250
251
static void
load_participants_images(ChatView *self)
{
    g_return_if_fail(IS_CHAT_VIEW(self));
    ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);
252

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

265
266
267
268
269
270
271
    // 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
        );
272
273
274
    }
}

275
static void
276
print_text_recording(ChatView *self)
277
278
279
280
{
    g_return_if_fail(IS_CHAT_VIEW(self));
    ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);

281
    if (!priv->conversation_) return;
282
283
    for (const auto& interaction : priv->conversation_->interactions)
        print_interaction_to_buffer(self, interaction.first, interaction.second);
284

285
    QObject::disconnect(priv->new_interaction_connection);
286
287
}

288
static void
289
update_add_to_conversations(ChatView *self)
290
291
292
{
    ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);

293
    if (!priv->conversation_) return;
294
295
296
297
298
    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);
299
300
}

Stepan Salenikovich's avatar
Stepan Salenikovich committed
301
302
303
304
305
static void
update_contact_methods(ChatView *self)
{
    g_return_if_fail(IS_CHAT_VIEW(self));
    ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);
306
    if (!priv->conversation_) return;
307
308
    auto contactUri = priv->conversation_->participants.front();
    auto contactInfo = priv->accountContainer_->info.contactModel->getContact(contactUri);
309
310
    auto bestId = std::string(contactInfo.registeredName).empty() ? contactInfo.profileInfo.uri : contactInfo.registeredName;
    if (contactInfo.profileInfo.alias == bestId) {
311
        gtk_widget_hide(priv->label_cm);
312
    } else {
313
        gtk_label_set_text(GTK_LABEL(priv->label_cm), bestId.c_str());
314
        gtk_widget_show(priv->label_cm);
315
    }
316

Stepan Salenikovich's avatar
Stepan Salenikovich committed
317
318
319
}

static void
320
update_name(ChatView *self)
Stepan Salenikovich's avatar
Stepan Salenikovich committed
321
{
322
    g_return_if_fail(IS_CHAT_VIEW(self));
Stepan Salenikovich's avatar
Stepan Salenikovich committed
323
    ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);
324
    if (!priv->conversation_) return;
325
326
327
328
329
    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
330
331
}

aviau's avatar
aviau committed
332
333
334
335
336
337
338
339
340
341
342
343
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)
    );

344
    display_links_toggled(self);
345
346
347
348
349
350
    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) {
351
        if (!priv->conversation_) return;
352
353
354
355
        if(uid == priv->conversation_->uid) {
            print_interaction_to_buffer(self, interactionId, interaction);
        }
    });
356

357
358
359
    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) {
360
        if (!priv->conversation_) return;
361
362
363
364
365
        if(uid == priv->conversation_->uid) {
            update_interaction(self, msgId, msg);
        }
    });

366
    if (!priv->conversation_) return;
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
    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
384
385
386
387
388
389
390
391
392
393
}

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

394
    update_name(self);
395
    update_add_to_conversations(self);
396
    update_contact_methods(self);
aviau's avatar
aviau committed
397

398
    priv->webkit_ready = g_signal_connect_swapped(
aviau's avatar
aviau committed
399
400
401
402
403
404
        priv->webkit_chat_container,
        "ready",
        G_CALLBACK(webkit_chat_container_ready),
        self
    );

405
    priv->webkit_send_text = g_signal_connect(priv->webkit_chat_container,
406
407
        "script-dialog",
        G_CALLBACK(webkit_chat_container_script_dialog),
408
409
        self);

aviau's avatar
aviau committed
410
411
412
    if (webkit_chat_container_is_ready(WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container)))
        webkit_chat_container_ready(self);

413
    gtk_widget_set_visible(priv->hbox_chat_info, TRUE);
aviau's avatar
aviau committed
414

415
}
Stepan Salenikovich's avatar
Stepan Salenikovich committed
416
417

GtkWidget *
418
419
420
chat_view_new (WebKitChatContainer* webkit_chat_container,
               AccountContainer* accountContainer,
               lrc::api::conversation::Info* conversation)
Stepan Salenikovich's avatar
Stepan Salenikovich committed
421
422
423
{
    ChatView *self = CHAT_VIEW(g_object_new(CHAT_VIEW_TYPE, NULL));

aviau's avatar
aviau committed
424
425
    ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);
    priv->webkit_chat_container = GTK_WIDGET(webkit_chat_container);
426
427
    priv->conversation_ = conversation;
    priv->accountContainer_ = accountContainer;
aviau's avatar
aviau committed
428
429

    build_chat_view(self);
Stepan Salenikovich's avatar
Stepan Salenikovich committed
430
431
432
    return (GtkWidget *)self;
}

433
434
void
chat_view_update_temporary(ChatView* self, bool newValue)
435
{
436
    g_return_if_fail(IS_CHAT_VIEW(self));
437
438
    auto priv = CHAT_VIEW_GET_PRIVATE(self);

439
440
441
442
443
    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_);
444
    if (!priv->conversation_) return;
445
446
447
448
449
450
451
452
453
454
    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);
455
456
}

457
458
bool
chat_view_get_temporary(ChatView *self)
459
{
460
    g_return_val_if_fail(IS_CHAT_VIEW(self), false);
461
    auto priv = CHAT_VIEW_GET_PRIVATE(self);
462
    return priv->isTemporary_;
463
464
}

465
466
lrc::api::conversation::Info
chat_view_get_conversation(ChatView *self)
467
{
468
    g_return_val_if_fail(IS_CHAT_VIEW(self), lrc::api::conversation::Info());
469
    auto priv = CHAT_VIEW_GET_PRIVATE(self);
470
    return *priv->conversation_;
471
}
472
473
474
475
476
477
478

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