chatview.cpp 25.3 KB
Newer Older
1
/*
Sébastien Blin's avatar
Sébastien Blin committed
2
 *  Copyright (C) 2016-2019 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>
Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
7
 *  Author: Hugo Lefeuvre <hugo.lefeuvre@savoirfairelinux.com>
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
 *
 *  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"

26
27
// std
#include <algorithm>
28
#include <fstream>
29

30
31
32
// GTK
#include <glib/gi18n.h>

33
34
35
36
// LRC
#include <api/contactmodel.h>
#include <api/conversationmodel.h>
#include <api/contact.h>
37
38
#include <api/newcallmodel.h>
#include <api/call.h>
39

40
41
// Client
#include "utils/files.h"
42

43
44
45
46
47
48
49
50
51
struct CppImpl {
    struct Interaction {
        std::string conv;
        uint64_t id;
        lrc::api::interaction::Info info;
    };
    std::vector<Interaction> interactionsBuffer_;
};

52
53
54
55
56
57
58
59
60
61
62
63
64
65
struct _ChatView
{
    GtkBox parent;
};

struct _ChatViewClass
{
    GtkBoxClass parent_class;
};

typedef struct _ChatViewPrivate ChatViewPrivate;

struct _ChatViewPrivate
{
aviau's avatar
aviau committed
66
67
    GtkWidget *box_webkit_chat_container;
    GtkWidget *webkit_chat_container;
Stepan Salenikovich's avatar
Stepan Salenikovich committed
68

69
70
    GSettings *settings;

71
    lrc::api::conversation::Info* conversation_;
72
    AccountInfoPointer const * accountInfo_;
73

74
    QMetaObject::Connection new_interaction_connection;
75
    QMetaObject::Connection interaction_removed;
76
77
    QMetaObject::Connection update_interaction_connection;
    QMetaObject::Connection update_add_to_conversations;
78
79

    gulong webkit_ready;
80
    gulong webkit_send_text;
81
    gulong webkit_drag_drop;
82
83
84

    bool ready_ {false};
    CppImpl* cpp_;
85
86
87
88
89
90
91
92
};

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,
93
    HIDE_VIEW_CLICKED,
94
95
    PLACE_CALL_CLICKED,
    PLACE_AUDIO_CALL_CLICKED,
96
97
98
99
100
101
102
103
104
105
106
107
108
109
    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);

110
111
    QObject::disconnect(priv->new_interaction_connection);
    QObject::disconnect(priv->update_interaction_connection);
112
    QObject::disconnect(priv->interaction_removed);
113
    QObject::disconnect(priv->update_add_to_conversations);
aviau's avatar
aviau committed
114
115
116
117

    /* 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) {
118
119
120
        /* disconnect for webkit signals */
        g_signal_handler_disconnect(priv->webkit_chat_container, priv->webkit_ready);
        priv->webkit_ready = 0;
121
122
        g_signal_handler_disconnect(priv->webkit_chat_container, priv->webkit_send_text);
        priv->webkit_send_text = 0;
123
124
        g_signal_handler_disconnect(priv->webkit_chat_container, priv->webkit_drag_drop);
        priv->webkit_drag_drop = 0;
125

aviau's avatar
aviau committed
126
127
128
129
130
131
        gtk_container_remove(
            GTK_CONTAINER(priv->box_webkit_chat_container),
            GTK_WIDGET(priv->webkit_chat_container)
        );
        priv->webkit_chat_container = nullptr;
    }
132
133
134
135

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

136
137
138
139
140
141
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);
}

142
143
144
145
146
147
148
149
150
151
152
153
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")
        );
    }
}

154
155
156
157
static void
placecall_clicked(ChatView *self)
{
    auto priv = CHAT_VIEW_GET_PRIVATE(self);
158
    if (!priv->conversation_) return;
159
    g_signal_emit(G_OBJECT(self), chat_view_signals[PLACE_CALL_CLICKED], 0, priv->conversation_->uid.c_str());
160
161
}

162
163
164
165
static void
place_audio_call_clicked(ChatView *self)
{
    auto priv = CHAT_VIEW_GET_PRIVATE(self);
166
167
    if (!priv->conversation_) return;
    g_signal_emit(G_OBJECT(self), chat_view_signals[PLACE_AUDIO_CALL_CLICKED], 0, priv->conversation_->uid.c_str());
168
169
}

170
static void
171
button_add_to_conversations_clicked(ChatView *self)
172
173
{
    auto priv = CHAT_VIEW_GET_PRIVATE(self);
174
    if (!priv->conversation_) return;
175
    (*priv->accountInfo_)->conversationModel->makePermanent(priv->conversation_->uid);
176
177
}

178
static gchar*
179
file_to_manipulate(GtkWindow* top_window, bool send)
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
{
    GtkWidget* dialog;
    GtkFileChooserAction action = send? GTK_FILE_CHOOSER_ACTION_OPEN : GTK_FILE_CHOOSER_ACTION_SAVE;
    gint res;
    gchar* filename = nullptr;

    dialog = gtk_file_chooser_dialog_new(send? _("Send File") : _("Save File"),
                                         top_window,
                                         action,
                                         _("_Cancel"),
                                         GTK_RESPONSE_CANCEL,
                                         send? _("_Open"): _("_Save"),
                                         GTK_RESPONSE_ACCEPT,
                                         nullptr);

    res = gtk_dialog_run (GTK_DIALOG(dialog));

    if (res == GTK_RESPONSE_ACCEPT) {
        auto chooser = GTK_FILE_CHOOSER(dialog);
        filename = gtk_file_chooser_get_filename(chooser);
    }
    gtk_widget_destroy (dialog);

    return filename;
}

Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
206
207
static void update_chatview_frame(ChatView *self);

208
static void
Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
209
webkit_chat_container_script_dialog(GtkWidget* webview, gchar *interaction, ChatView* self)
210
{
211
212
    auto priv = CHAT_VIEW_GET_PRIVATE(self);
    auto order = std::string(interaction);
213
    if (!priv->conversation_) return;
214
    if (order == "ACCEPT") {
215
        (*priv->accountInfo_)->conversationModel->makePermanent(priv->conversation_->uid);
216
    } else if (order == "REFUSE") {
217
        (*priv->accountInfo_)->conversationModel->removeConversation(priv->conversation_->uid);
218
    } else if (order == "BLOCK") {
219
        (*priv->accountInfo_)->conversationModel->removeConversation(priv->conversation_->uid, true);
Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
220
221
222
223
224
    } else if (order == "UNBLOCK") {
        try {
            auto contactUri = priv->conversation_->participants.front();
            auto& contact = (*priv->accountInfo_)->contactModel->getContact(contactUri);
            (*priv->accountInfo_)->contactModel->addContact(contact);
225
        } catch (std::out_of_range&) {
Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
226
227
228
229
230
231
232
233
            g_debug("webkit_chat_container_script_dialog: oor while retrieving invalid contact info. Chatview bug ?");
        }
    } else if (order.find("PLACE_CALL") == 0) {
        placecall_clicked(self);
    } else if (order.find("PLACE_AUDIO_CALL") == 0) {
        place_audio_call_clicked(self);
    } else if (order.find("CLOSE_CHATVIEW") == 0) {
        hide_chat_view(webview, self);
234
235
236
    } else if (order.find("SEND:") == 0) {
        // Get text body
        auto toSend = order.substr(std::string("SEND:").size());
237
        (*priv->accountInfo_)->conversationModel->sendMessage(priv->conversation_->uid, toSend);
238
    } else if (order.find("SEND_FILE") == 0) {
239
        if (auto model = (*priv->accountInfo_)->conversationModel.get()) {
240
241
242
243
            if (auto filename = file_to_manipulate(GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(self))), true))
                model->sendFile(priv->conversation_->uid, filename, g_path_get_basename(filename));
        }
    } else if (order.find("ACCEPT_FILE:") == 0) {
244
        if (auto model = (*priv->accountInfo_)->conversationModel.get()) {
245
246
            try {
                auto interactionId = std::stoull(order.substr(std::string("ACCEPT_FILE:").size()));
247
248

                lrc::api::datatransfer::Info info = {};
249
                (*priv->accountInfo_)->conversationModel->getTransferInfo(interactionId, info);
250

Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
251
                // get preferred directory destination.
252
253
254
255
256
257
258
259
260
261
262
                auto* download_directory_variant = g_settings_get_value(priv->settings, "download-folder");
                char* download_directory_value;
                g_variant_get(download_directory_variant, "&s", &download_directory_value);
                std::string default_download_dir = g_get_user_special_dir (G_USER_DIRECTORY_DOWNLOAD);
                auto current_value = std::string(download_directory_value);
                if (current_value.empty()) {
                    g_settings_set_value(priv->settings, "download-folder", g_variant_new("s", default_download_dir.c_str()));
                }
                // get full path
                std::string filename = current_value.empty()? default_download_dir.c_str() : download_directory_value;
                if (!filename.empty() && filename.back() != '/') filename += "/";
263
264
265
266
                auto wantedFilename = filename + info.displayName;
                auto duplicate = 0;
                while (std::ifstream(wantedFilename).good()) {
                    ++duplicate;
267
268
269
270
271
                    auto extensionIdx = info.displayName.find_last_of(".");
                    if (extensionIdx == std::string::npos)
                        wantedFilename = filename + info.displayName + " (" + std::to_string(duplicate) + ")";
                    else
                        wantedFilename = filename + info.displayName.substr(0, extensionIdx) + " (" + std::to_string(duplicate) + ")" + info.displayName.substr(extensionIdx);
272
273
                }
                model->acceptTransfer(priv->conversation_->uid, interactionId, wantedFilename);
274
275
276
277
278
            } catch (...) {
                // ignore
            }
        }
    } else if (order.find("REFUSE_FILE:") == 0) {
279
        if (auto model = (*priv->accountInfo_)->conversationModel.get()) {
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
            try {
                auto interactionId = std::stoull(order.substr(std::string("REFUSE_FILE:").size()));
                model->cancelTransfer(priv->conversation_->uid, interactionId);
            } catch (...) {
                // ignore
            }
        }
    } else if (order.find("OPEN_FILE:") == 0) {
        // Get text body
        auto filename {"file://" + order.substr(std::string("OPEN_FILE:").size())};
        filename.erase(std::find_if(filename.rbegin(), filename.rend(),
            std::not1(std::ptr_fun<int, int>(std::isspace))).base(), filename.end());
        GError* error = nullptr;
        if (!gtk_show_uri(nullptr, filename.c_str(), GDK_CURRENT_TIME, &error)) {
            g_debug("Could not open file: %s", error->message);
            g_error_free(error);
        }
Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
297
298
    } else if (order.find("ADD_TO_CONVERSATIONS") == 0) {
        button_add_to_conversations_clicked(self);
299
300
301
302
303
304
305
306
    } else if (order.find("DELETE_INTERACTION:") == 0) {
        try {
            auto interactionId = std::stoull(order.substr(std::string("DELETE_INTERACTION:").size()));
            if (!priv->conversation_) return;
            (*priv->accountInfo_)->conversationModel->clearInteractionFromConversation(priv->conversation_->uid, interactionId);
        } catch (...) {
            g_warning("delete interaction failed: can't find %s", order.substr(std::string("DELETE_INTERACTION:").size()).c_str());
        }
307
308
309
310
311
312
313
314
    } else if (order.find("RETRY_INTERACTION:") == 0) {
        try {
            auto interactionId = std::stoull(order.substr(std::string("RETRY_INTERACTION:").size()));
            if (!priv->conversation_) return;
            (*priv->accountInfo_)->conversationModel->retryInteraction(priv->conversation_->uid, interactionId);
        } catch (...) {
            g_warning("delete interaction failed: can't find %s", order.substr(std::string("RETRY_INTERACTION:").size()).c_str());
        }
315
316
317
    }
}

318
319
320
321
322
323
static void
chat_view_init(ChatView *view)
{
    gtk_widget_init_template(GTK_WIDGET(view));

    ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(view);
324
    priv->settings = g_settings_new_full(get_ring_schema(), NULL, NULL);
325
326
327
328
329
330
331
332
}

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),
333
                                                "/net/jami/JamiGnome/chatview.ui");
334

aviau's avatar
aviau committed
335
    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), ChatView, box_webkit_chat_container);
336
337

    chat_view_signals[NEW_MESSAGES_DISPLAYED] = g_signal_new (
338
        "new-interactions-displayed",
339
340
341
342
343
344
345
        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);
346
347
348
349
350
351
352
353
354
355

    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);
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375

    chat_view_signals[PLACE_CALL_CLICKED] = g_signal_new (
        "place-call-clicked",
        G_TYPE_FROM_CLASS(klass),
        (GSignalFlags) (G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED),
        0,
        nullptr,
        nullptr,
        g_cclosure_marshal_VOID__STRING,
        G_TYPE_NONE, 1, G_TYPE_STRING);

    chat_view_signals[PLACE_AUDIO_CALL_CLICKED] = g_signal_new (
        "place-audio-call-clicked",
        G_TYPE_FROM_CLASS(klass),
        (GSignalFlags) (G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION),
        0,
        nullptr,
        nullptr,
        g_cclosure_marshal_VOID__STRING,
        G_TYPE_NONE, 1, G_TYPE_STRING);
376
377
}

aviau's avatar
aviau committed
378
static void
379
print_interaction_to_buffer(ChatView* self, uint64_t interactionId, const lrc::api::interaction::Info& interaction)
aviau's avatar
aviau committed
380
381
382
{
    ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);

383
    if (!priv->conversation_) return;
384
    if (interaction.status == lrc::api::interaction::Status::UNREAD)
385
        (*priv->accountInfo_)->conversationModel->setInteractionRead(priv->conversation_->uid, interactionId);
386
387

    webkit_chat_container_print_new_interaction(
aviau's avatar
aviau committed
388
        WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container),
389
        *(*priv->accountInfo_)->conversationModel,
390
391
        interactionId,
        interaction
aviau's avatar
aviau committed
392
    );
393
394
}

395
static void
396
update_interaction(ChatView* self, uint64_t interactionId, const lrc::api::interaction::Info& interaction)
397
398
{
    ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);
399
400
    webkit_chat_container_update_interaction(
        WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container),
401
        *(*priv->accountInfo_)->conversationModel,
402
403
        interactionId,
        interaction
404
    );
405
}
406

407
408
409
410
411
412
413
414
415
416
static void
remove_interaction(ChatView* self, uint64_t interactionId)
{
    ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);
    webkit_chat_container_remove_interaction(
        WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container),
        interactionId
    );
}

417
418
419
420
421
static void
load_participants_images(ChatView *self)
{
    g_return_if_fail(IS_CHAT_VIEW(self));
    ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);
422

423
    // Contact
424
    if (!priv->conversation_) return;
425
    auto contactUri = priv->conversation_->participants.front();
426
    try{
427
        auto& contact = (*priv->accountInfo_)->contactModel->getContact(contactUri);
428
429
430
        if (!contact.profileInfo.avatar.empty()) {
            webkit_chat_container_set_sender_image(
                WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container),
431
                (*priv->accountInfo_)->contactModel->getContactProfileId(contactUri),
432
433
434
435
436
                contact.profileInfo.avatar
                );
        }
    } catch (const std::out_of_range&) {
        // ContactModel::getContact() exception
437
438
    }

439
    // For this account
440
    if (!(*priv->accountInfo_)->profileInfo.avatar.empty()) {
441
442
        webkit_chat_container_set_sender_image(
            WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container),
443
444
            (*priv->accountInfo_)->contactModel->getContactProfileId((*priv->accountInfo_)->profileInfo.uri),
            (*priv->accountInfo_)->profileInfo.avatar
445
        );
446
447
448
    }
}

449
static void
450
print_text_recording(ChatView *self)
451
452
453
454
{
    g_return_if_fail(IS_CHAT_VIEW(self));
    ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);

455
    // Read interactions
456
    if (!priv->conversation_) return;
457
458
    for (const auto& it: priv->conversation_->interactions) {
        if (it.second.status == lrc::api::interaction::Status::UNREAD)
459
            (*priv->accountInfo_)->conversationModel->setInteractionRead(priv->conversation_->uid, it.first);
460
461
    }

462
463
    webkit_chat_container_print_history(
        WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container),
464
        *(*priv->accountInfo_)->conversationModel,
465
466
        priv->conversation_->interactions
    );
467
468
}

aviau's avatar
aviau committed
469
470
471
472
473
474
475
476
477
478
479
480
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)
    );

481
    display_links_toggled(self);
482
483
484
    print_text_recording(self);
    load_participants_images(self);

485
486
487
488
    priv->ready_ = true;
    for (const auto& interaction: priv->cpp_->interactionsBuffer_) {
        if (interaction.conv == priv->conversation_->uid) {
            print_interaction_to_buffer(self, interaction.id, interaction.info);
489
        }
490
    }
491

492
    priv->update_interaction_connection = QObject::connect(
493
    &*(*priv->accountInfo_)->conversationModel, &lrc::api::ConversationModel::interactionStatusUpdated,
494
    [self, priv](const std::string& uid, uint64_t msgId, lrc::api::interaction::Info msg) {
495
        if (!priv->conversation_) return;
496
        if (uid == priv->conversation_->uid) {
497
498
499
500
            update_interaction(self, msgId, msg);
        }
    });

501
502
503
504
505
506
507
508
509
    priv->interaction_removed = QObject::connect(
    &*(*priv->accountInfo_)->conversationModel, &lrc::api::ConversationModel::interactionRemoved,
    [self, priv](const std::string& convUid, uint64_t interactionId) {
        if (!priv->conversation_) return;
        if (convUid == priv->conversation_->uid) {
            remove_interaction(self, interactionId);
        }
    });

510
    if (!priv->conversation_) return;
511
    auto contactUri = priv->conversation_->participants.front();
512
    try {
513
        auto contactInfo = (*priv->accountInfo_)->contactModel->getContact(contactUri);
514
515
516
517
518
519
520
521
522
        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());
    } catch (const std::out_of_range&) {
        // ContactModel::getContact() exception
    }
Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548

    update_chatview_frame(self);
}

static void
update_chatview_frame(ChatView* self)
{
    g_return_if_fail(IS_CHAT_VIEW(self));
    ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);
    if (!priv->conversation_) return;

    auto contactUri = priv->conversation_->participants.front();

    lrc::api::contact::Info contactInfo;
    try {
        contactInfo = (*priv->accountInfo_)->contactModel->getContact(contactUri);
    } catch (const std::out_of_range&) {
        g_debug("update_chatview_frame: failed to retrieve contactInfo");
        return;
    }

    // get alias and bestName
    auto alias = contactInfo.profileInfo.alias;
    auto bestName = contactInfo.registeredName;
    if (bestName.empty())
        bestName = contactInfo.profileInfo.uri;
549
550
    if (bestName == alias)
        alias = "";
Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
551
552
553
554
555
556
    bestName.erase(std::remove(bestName.begin(), bestName.end(), '\r'), bestName.end());
    alias.erase(std::remove(alias.begin(), alias.end(), '\r'), alias.end());

    // get temporary status
    bool temp = contactInfo.profileInfo.type == lrc::api::profile::Type::TEMPORARY || contactInfo.profileInfo.type == lrc::api::profile::Type::PENDING;

557
558
559
    webkit_chat_update_chatview_frame(WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container),
                                     (*priv->accountInfo_)->enabled,
                                     contactInfo.isBanned, temp, alias.c_str(), bestName.c_str());
Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
560
561
562

    webkit_chat_container_set_invitation(WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container),
                                             (contactInfo.profileInfo.type == lrc::api::profile::Type::PENDING),
Sébastien Blin's avatar
Sébastien Blin committed
563
564
                                             bestName,
                                             (*priv->accountInfo_)->contactModel->getContactProfileId(contactInfo.profileInfo.uri));
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586

    // hide navbar if we are in call
    try {
        std::string callId;
        if (priv->conversation_->confId.empty()) {
            callId = priv->conversation_->callId;
        } else {
            callId = priv->conversation_->confId;
        }

        if (*priv->accountInfo_) {
            const lrc::api::call::Status& status = (*priv->accountInfo_)->callModel->getCall(callId).status;
            if (status != lrc::api::call::Status::ENDED &&
                status != lrc::api::call::Status::INVALID &&
                status != lrc::api::call::Status::TERMINATING) {
                g_debug("call has status %s, hiding", lrc::api::call::to_string(status).c_str());
                chat_view_set_header_visible(self, FALSE);
            } else {
                chat_view_set_header_visible(self, TRUE);
            }
        }
    } catch (const std::out_of_range&) {}
aviau's avatar
aviau committed
587
588
}

589
590
591
592
593
594
595
596
static void
on_webkit_drag_drop(GtkWidget*, gchar* data, ChatView* self)
{
    g_return_if_fail(IS_CHAT_VIEW(self));
    ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);
    if (!priv->conversation_) return;
    if (!data) return;

597
    GError *error = nullptr;
598
    auto* filename_uri = g_filename_from_uri(data, nullptr, &error);
599
600
601
602
603
    if (error) {
        g_warning("Unable to exec g_filename_from_uri on %s", data);
        g_error_free(error);
        return;
    }
604
605
606
    std::string data_str = filename_uri;
    g_free(filename_uri);

607
608
609
    // Only take files
    if (data_str.find("\r\n") == std::string::npos) return;
    const auto LEN_END = std::string("\r\n").length();
610
611
612
613
614
615
    if (data_str.length() > LEN_END) {
        // remove \r\n from the string
        data_str = data_str.substr(0, data_str.length() - LEN_END);
    } else {
        // Nothing valid to drop, abort.
        return;
616
617
618
619
620
621
622
    }

    if (auto model = (*priv->accountInfo_)->conversationModel.get()) {
        model->sendFile(priv->conversation_->uid, data_str, g_path_get_basename(data_str.c_str()));
    }
}

aviau's avatar
aviau committed
623
624
625
626
627
628
629
630
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);

Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
631
    update_chatview_frame(self);
aviau's avatar
aviau committed
632

633
    priv->webkit_ready = g_signal_connect_swapped(
aviau's avatar
aviau committed
634
635
636
637
638
639
        priv->webkit_chat_container,
        "ready",
        G_CALLBACK(webkit_chat_container_ready),
        self
    );

640
    priv->webkit_send_text = g_signal_connect(priv->webkit_chat_container,
641
642
        "script-dialog",
        G_CALLBACK(webkit_chat_container_script_dialog),
643
644
        self);

645
646
647
648
649
650
651
    priv->webkit_drag_drop = g_signal_connect(
        priv->webkit_chat_container,
        "data-dropped",
        G_CALLBACK(on_webkit_drag_drop),
        self
    );

652
653
654
655
656
657
658
659
660
661
662
663
664
665
    priv->new_interaction_connection = QObject::connect(
    &*(*priv->accountInfo_)->conversationModel, &lrc::api::ConversationModel::newInteraction,
    [self, priv](const std::string& uid, uint64_t interactionId, lrc::api::interaction::Info interaction) {
        if (!priv->conversation_) return;
        if (!priv->ready_ && priv->cpp_) {
            priv->cpp_->interactionsBuffer_.emplace_back(CppImpl::Interaction {
                uid, interactionId, interaction});
        } else if (uid == priv->conversation_->uid) {
            print_interaction_to_buffer(self, interactionId, interaction);
        }
    });

    priv->cpp_ = new CppImpl();

aviau's avatar
aviau committed
666
667
    if (webkit_chat_container_is_ready(WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container)))
        webkit_chat_container_ready(self);
668
}
Stepan Salenikovich's avatar
Stepan Salenikovich committed
669
670

GtkWidget *
671
chat_view_new (WebKitChatContainer* webkit_chat_container,
672
               AccountInfoPointer const & accountInfo,
673
               lrc::api::conversation::Info* conversation)
Stepan Salenikovich's avatar
Stepan Salenikovich committed
674
675
676
{
    ChatView *self = CHAT_VIEW(g_object_new(CHAT_VIEW_TYPE, NULL));

aviau's avatar
aviau committed
677
678
    ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);
    priv->webkit_chat_container = GTK_WIDGET(webkit_chat_container);
679
    priv->conversation_ = conversation;
680
    priv->accountInfo_ = &accountInfo;
aviau's avatar
aviau committed
681
682

    build_chat_view(self);
Stepan Salenikovich's avatar
Stepan Salenikovich committed
683
684
685
    return (GtkWidget *)self;
}

686
void
687
chat_view_update_temporary(ChatView* self)
688
{
689
    g_return_if_fail(IS_CHAT_VIEW(self));
Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
690
    update_chatview_frame(self);
691
692
}

693
694
lrc::api::conversation::Info
chat_view_get_conversation(ChatView *self)
695
{
696
    g_return_val_if_fail(IS_CHAT_VIEW(self), lrc::api::conversation::Info());
697
    auto priv = CHAT_VIEW_GET_PRIVATE(self);
698
    return *priv->conversation_;
699
}
700
701
702
703
704

void
chat_view_set_header_visible(ChatView *self, gboolean visible)
{
    auto priv = CHAT_VIEW_GET_PRIVATE(self);
Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
705
    webkit_chat_set_header_visible(WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container), visible);
706
}