chatview.cpp 17.4 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;
58
    GtkWidget *button_place_audio_call;
Stepan Salenikovich's avatar
Stepan Salenikovich committed
59

60
61
    GSettings *settings;

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

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

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

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,
80
    HIDE_VIEW_CLICKED,
81
82
83
84
85
86
87
88
89
90
91
92
93
94
    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);

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

    /* 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) {
102
103
104
        /* disconnect for webkit signals */
        g_signal_handler_disconnect(priv->webkit_chat_container, priv->webkit_ready);
        priv->webkit_ready = 0;
105
106
        g_signal_handler_disconnect(priv->webkit_chat_container, priv->webkit_send_text);
        priv->webkit_send_text = 0;
107

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

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

118
119
120
121
122
123
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);
}

124
125
126
127
128
129
130
131
132
133
134
135
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")
        );
    }
}

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

144
145
146
147
148
149
150
151
152
153
154
static void
place_audio_call_clicked(ChatView *self)
{
    auto priv = CHAT_VIEW_GET_PRIVATE(self);

    if (!priv->conversation_)
        return;

    priv->accountContainer_->info.conversationModel->placeAudioOnlyCall(priv->conversation_->uid);
}

155
static void
156
button_add_to_conversations_clicked(ChatView *self)
157
158
{
    auto priv = CHAT_VIEW_GET_PRIVATE(self);
159
    if (!priv->conversation_) return;
160
    priv->accountContainer_->info.conversationModel->makePermanent(priv->conversation_->uid);
161
162
}

163
static void
164
webkit_chat_container_script_dialog(G_GNUC_UNUSED GtkWidget* webview, gchar *interaction, ChatView* self)
165
{
166
167
    auto priv = CHAT_VIEW_GET_PRIVATE(self);
    auto order = std::string(interaction);
168
    if (!priv->conversation_) return;
169
170
171
172
173
174
175
176
177
178
    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);
179
180
181
    }
}

182
183
184
185
186
187
static void
chat_view_init(ChatView *view)
{
    gtk_widget_init_template(GTK_WIDGET(view));

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

190
    g_signal_connect(priv->button_close_chatview, "clicked", G_CALLBACK(hide_chat_view), view);
191
    g_signal_connect_swapped(priv->button_placecall, "clicked", G_CALLBACK(placecall_clicked), view);
192
    g_signal_connect_swapped(priv->button_add_to_conversations, "clicked", G_CALLBACK(button_add_to_conversations_clicked), view);
193
    g_signal_connect_swapped(priv->button_place_audio_call, "clicked", G_CALLBACK(place_audio_call_clicked), view);
194
195
196
197
198
199
200
201
202
203
}

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
204
    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), ChatView, box_webkit_chat_container);
Stepan Salenikovich's avatar
Stepan Salenikovich committed
205
206
    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);
207
    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), ChatView, label_cm);
208
    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), ChatView, button_close_chatview);
209
    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), ChatView, button_placecall);
210
    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), ChatView, button_add_to_conversations);
211
    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), ChatView, button_place_audio_call);
212
213

    chat_view_signals[NEW_MESSAGES_DISPLAYED] = g_signal_new (
214
        "new-interactions-displayed",
215
216
217
218
219
220
221
        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);
222
223
224
225
226
227
228
229
230
231

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

aviau's avatar
aviau committed
234
static void
235
print_interaction_to_buffer(ChatView* self, uint64_t interactionId, const lrc::api::interaction::Info& interaction)
aviau's avatar
aviau committed
236
237
238
{
    ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);

239
    if (!priv->conversation_) return;
240
241
242
243
    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
244
        WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container),
245
246
        interactionId,
        interaction
aviau's avatar
aviau committed
247
    );
248
249
}

250
static void
251
update_interaction(ChatView* self, uint64_t interactionId, const lrc::api::interaction::Info& interaction)
252
253
{
    ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);
254
255
256
257
    webkit_chat_container_update_interaction(
        WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container),
        interactionId,
        interaction
258
    );
259
}
260

261
262
263
264
265
static void
load_participants_images(ChatView *self)
{
    g_return_if_fail(IS_CHAT_VIEW(self));
    ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);
266

267
    // Contact
268
    if (!priv->conversation_) return;
269
270
271
    auto contactUri = priv->conversation_->participants.front();
    auto& contact = priv->accountContainer_->info.contactModel->getContact(contactUri);
    if (!contact.profileInfo.avatar.empty()) {
272
273
        webkit_chat_container_set_sender_image(
            WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container),
274
275
            priv->accountContainer_->info.contactModel->getContactProfileId(contactUri),
            contact.profileInfo.avatar
276
277
278
        );
    }

279
280
281
282
283
284
285
    // 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
        );
286
287
288
    }
}

289
static void
290
print_text_recording(ChatView *self)
291
292
293
294
{
    g_return_if_fail(IS_CHAT_VIEW(self));
    ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);

295
    if (!priv->conversation_) return;
296
297
    for (const auto& interaction : priv->conversation_->interactions)
        print_interaction_to_buffer(self, interaction.first, interaction.second);
298

299
    QObject::disconnect(priv->new_interaction_connection);
300
301
}

302
static void
303
update_add_to_conversations(ChatView *self)
304
305
306
{
    ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);

307
    if (!priv->conversation_) return;
308
309
310
311
312
    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);
313
314
}

Stepan Salenikovich's avatar
Stepan Salenikovich committed
315
316
317
318
319
static void
update_contact_methods(ChatView *self)
{
    g_return_if_fail(IS_CHAT_VIEW(self));
    ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);
320
    if (!priv->conversation_) return;
321
322
    auto contactUri = priv->conversation_->participants.front();
    auto contactInfo = priv->accountContainer_->info.contactModel->getContact(contactUri);
323
324
    auto bestId = std::string(contactInfo.registeredName).empty() ? contactInfo.profileInfo.uri : contactInfo.registeredName;
    if (contactInfo.profileInfo.alias == bestId) {
325
        gtk_widget_hide(priv->label_cm);
326
    } else {
327
        gtk_label_set_text(GTK_LABEL(priv->label_cm), bestId.c_str());
328
        gtk_widget_show(priv->label_cm);
329
    }
330

Stepan Salenikovich's avatar
Stepan Salenikovich committed
331
332
333
}

static void
334
update_name(ChatView *self)
Stepan Salenikovich's avatar
Stepan Salenikovich committed
335
{
336
    g_return_if_fail(IS_CHAT_VIEW(self));
Stepan Salenikovich's avatar
Stepan Salenikovich committed
337
    ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);
338
    if (!priv->conversation_) return;
339
340
341
342
343
    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
344
345
}

aviau's avatar
aviau committed
346
347
348
349
350
351
352
353
354
355
356
357
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)
    );

358
    display_links_toggled(self);
359
360
361
362
363
364
    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) {
365
        if (!priv->conversation_) return;
366
367
368
369
        if(uid == priv->conversation_->uid) {
            print_interaction_to_buffer(self, interactionId, interaction);
        }
    });
370

371
372
373
    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) {
374
        if (!priv->conversation_) return;
375
376
377
378
379
        if(uid == priv->conversation_->uid) {
            update_interaction(self, msgId, msg);
        }
    });

380
    if (!priv->conversation_) return;
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
    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
398
399
400
401
402
403
404
405
406
407
}

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

408
    update_name(self);
409
    update_add_to_conversations(self);
410
    update_contact_methods(self);
aviau's avatar
aviau committed
411

412
    priv->webkit_ready = g_signal_connect_swapped(
aviau's avatar
aviau committed
413
414
415
416
417
418
        priv->webkit_chat_container,
        "ready",
        G_CALLBACK(webkit_chat_container_ready),
        self
    );

419
    priv->webkit_send_text = g_signal_connect(priv->webkit_chat_container,
420
421
        "script-dialog",
        G_CALLBACK(webkit_chat_container_script_dialog),
422
423
        self);

aviau's avatar
aviau committed
424
425
426
    if (webkit_chat_container_is_ready(WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container)))
        webkit_chat_container_ready(self);

427
    gtk_widget_set_visible(priv->hbox_chat_info, TRUE);
aviau's avatar
aviau committed
428

429
}
Stepan Salenikovich's avatar
Stepan Salenikovich committed
430
431

GtkWidget *
432
433
434
chat_view_new (WebKitChatContainer* webkit_chat_container,
               AccountContainer* accountContainer,
               lrc::api::conversation::Info* conversation)
Stepan Salenikovich's avatar
Stepan Salenikovich committed
435
436
437
{
    ChatView *self = CHAT_VIEW(g_object_new(CHAT_VIEW_TYPE, NULL));

aviau's avatar
aviau committed
438
439
    ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);
    priv->webkit_chat_container = GTK_WIDGET(webkit_chat_container);
440
441
    priv->conversation_ = conversation;
    priv->accountContainer_ = accountContainer;
aviau's avatar
aviau committed
442
443

    build_chat_view(self);
Stepan Salenikovich's avatar
Stepan Salenikovich committed
444
445
446
    return (GtkWidget *)self;
}

447
448
void
chat_view_update_temporary(ChatView* self, bool newValue)
449
{
450
    g_return_if_fail(IS_CHAT_VIEW(self));
451
452
    auto priv = CHAT_VIEW_GET_PRIVATE(self);

453
454
455
456
457
    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_);
458
    if (!priv->conversation_) return;
459
460
461
462
463
464
465
466
467
468
    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);
469
470
}

471
472
bool
chat_view_get_temporary(ChatView *self)
473
{
474
    g_return_val_if_fail(IS_CHAT_VIEW(self), false);
475
    auto priv = CHAT_VIEW_GET_PRIVATE(self);
476
    return priv->isTemporary_;
477
478
}

479
480
lrc::api::conversation::Info
chat_view_get_conversation(ChatView *self)
481
{
482
    g_return_val_if_fail(IS_CHAT_VIEW(self), lrc::api::conversation::Info());
483
    auto priv = CHAT_VIEW_GET_PRIVATE(self);
484
    return *priv->conversation_;
485
}
486
487
488
489
490
491
492

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