chatview.cpp 43.6 KB
Newer Older
1
/*
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>
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
#include <sstream>
30

31 32
// GTK
#include <glib/gi18n.h>
33
#include <clutter-gtk/clutter-gtk.h>
34

35 36 37
// Qt
#include <QSize>

38
// LRC
39
#include <api/call.h>
40 41 42
#include <api/contactmodel.h>
#include <api/conversationmodel.h>
#include <api/contact.h>
43
#include <api/lrc.h>
44
#include <api/newcallmodel.h>
45
#include <api/avmodel.h>
46

47
// Client
48
#include "marshals.h"
49 50
#include "utils/files.h"
#include "native/pixbufmanipulator.h"
51 52
#include "video/video_widget.h"

53 54 55
/* size of avatar */
static constexpr int AVATAR_WIDTH  = 150; /* px */
static constexpr int AVATAR_HEIGHT = 150; /* px */
56

57 58 59 60 61 62
enum class RecordAction {
    RECORD,
    STOP,
    SEND
};

63 64 65 66 67 68 69
struct CppImpl {
    struct Interaction {
        std::string conv;
        uint64_t id;
        lrc::api::interaction::Info info;
    };
    std::vector<Interaction> interactionsBuffer_;
70 71 72 73 74
    lrc::api::AVModel* avModel_;
    RecordAction current_action_ {RecordAction::RECORD};

    // store current recording location
    std::string saveFileName_;
75 76
};

77 78 79 80 81 82 83 84 85 86 87 88 89 90
struct _ChatView
{
    GtkBox parent;
};

struct _ChatViewClass
{
    GtkBoxClass parent_class;
};

typedef struct _ChatViewPrivate ChatViewPrivate;

struct _ChatViewPrivate
{
aviau's avatar
aviau committed
91 92
    GtkWidget *box_webkit_chat_container;
    GtkWidget *webkit_chat_container;
Stepan Salenikovich's avatar
Stepan Salenikovich committed
93

94 95
    GSettings *settings;

96
    lrc::api::conversation::Info* conversation_;
97
    AccountInfoPointer const * accountInfo_;
98

99
    QMetaObject::Connection new_interaction_connection;
100
    QMetaObject::Connection interaction_removed;
101 102
    QMetaObject::Connection update_interaction_connection;
    QMetaObject::Connection update_add_to_conversations;
103
    QMetaObject::Connection local_renderer_connection;
104 105

    gulong webkit_ready;
106
    gulong webkit_send_text;
107
    gulong webkit_drag_drop;
108 109

    bool ready_ {false};
110
    bool readyToRecord_ {false};
111
    bool useDarkTheme {false};
112
    bool startRecorderWhenReady {false};
113
    std::string background;
114 115 116 117 118 119 120 121
    CppImpl* cpp;

    bool video_started_by_settings;
    GtkWidget* video_widget;
    GtkWidget* record_popover;
    GtkWidget* button_retry;
    GtkWidget* button_main_action;
    GtkWidget* label_time;
122
    bool is_video_record {false};
123 124 125
    guint timer_duration = 0;
    uint32_t duration = 0;

126 127 128 129 130 131 132 133
};

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,
134
    HIDE_VIEW_CLICKED,
135 136
    PLACE_CALL_CLICKED,
    PLACE_AUDIO_CALL_CLICKED,
137 138
    ADD_CONVERSATION_CLICKED,
    SEND_TEXT_CLICKED,
139 140 141 142 143
    LAST_SIGNAL
};

static guint chat_view_signals[LAST_SIGNAL] = { 0 };

144 145 146
static void init_video_widget(ChatView* self);
static void on_main_action_clicked(ChatView *self);
static void reset_recorder(ChatView *self);
147

148 149 150 151 152 153 154 155 156
static void
chat_view_dispose(GObject *object)
{
    ChatView *view;
    ChatViewPrivate *priv;

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

157 158
    QObject::disconnect(priv->new_interaction_connection);
    QObject::disconnect(priv->update_interaction_connection);
159
    QObject::disconnect(priv->interaction_removed);
160
    QObject::disconnect(priv->update_add_to_conversations);
161
    QObject::disconnect(priv->local_renderer_connection);
aviau's avatar
aviau committed
162 163 164 165

    /* 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) {
166 167 168
        /* disconnect for webkit signals */
        g_signal_handler_disconnect(priv->webkit_chat_container, priv->webkit_ready);
        priv->webkit_ready = 0;
169 170
        g_signal_handler_disconnect(priv->webkit_chat_container, priv->webkit_send_text);
        priv->webkit_send_text = 0;
171 172
        g_signal_handler_disconnect(priv->webkit_chat_container, priv->webkit_drag_drop);
        priv->webkit_drag_drop = 0;
173

aviau's avatar
aviau committed
174 175 176 177 178 179
        gtk_container_remove(
            GTK_CONTAINER(priv->box_webkit_chat_container),
            GTK_WIDGET(priv->webkit_chat_container)
        );
        priv->webkit_chat_container = nullptr;
    }
180

181 182 183 184 185 186 187 188
    if (priv->video_widget) {
        gtk_widget_destroy(priv->video_widget);
    }

    if (priv->record_popover) {
        gtk_widget_destroy(priv->record_popover);
    }

189 190 191
    G_OBJECT_CLASS(chat_view_parent_class)->dispose(object);
}

192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219
void
chat_view_set_dark_mode(ChatView *self, gboolean darkMode, const std::string& background)
{
    auto priv = CHAT_VIEW_GET_PRIVATE(self);
    priv->useDarkTheme = darkMode;
    priv->background = background;
    if (!priv->ready_) return;
    webkit_chat_set_dark_mode(WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container), darkMode, background);
}

gboolean
on_redraw(GtkWidget*, cairo_t*, ChatView* self)
{
    g_return_val_if_fail(IS_CHAT_VIEW(self), G_SOURCE_REMOVE);
    auto* priv = CHAT_VIEW_GET_PRIVATE(CHAT_VIEW(self));

    auto color = get_ambient_color(gtk_widget_get_toplevel(GTK_WIDGET(self)));
    bool current_theme = (color.red + color.green + color.blue) / 3 < .5;
    if (priv->useDarkTheme != current_theme) {
        std::stringstream background;
        background << "#" << std::hex << (int)(color.red * 256)
                          << (int)(color.green * 256)
                          << (int)(color.blue * 256);
        chat_view_set_dark_mode(self, current_theme, background.str());
    }
    return false;
}

220 221 222 223 224 225
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);
}

226 227 228 229 230 231 232 233 234 235 236 237
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")
        );
    }
}

238 239 240 241
static void
placecall_clicked(ChatView *self)
{
    auto priv = CHAT_VIEW_GET_PRIVATE(self);
242
    if (!priv->conversation_) return;
243
    g_signal_emit(G_OBJECT(self), chat_view_signals[PLACE_CALL_CLICKED], 0, priv->conversation_->uid.c_str());
244 245
}

246 247 248 249
static void
place_audio_call_clicked(ChatView *self)
{
    auto priv = CHAT_VIEW_GET_PRIVATE(self);
250 251
    if (!priv->conversation_) return;
    g_signal_emit(G_OBJECT(self), chat_view_signals[PLACE_AUDIO_CALL_CLICKED], 0, priv->conversation_->uid.c_str());
252 253
}

254
static void
255
add_to_conversations_clicked(ChatView *self)
256 257
{
    auto priv = CHAT_VIEW_GET_PRIVATE(self);
258
    if (!priv->conversation_) return;
259 260 261 262 263 264 265 266 267
    g_signal_emit(G_OBJECT(self), chat_view_signals[ADD_CONVERSATION_CLICKED], 0, priv->conversation_->uid.c_str());
}

static void
send_text_clicked(ChatView *self, const std::string& body)
{
    auto priv = CHAT_VIEW_GET_PRIVATE(self);
    if (!priv->conversation_) return;
    g_signal_emit(G_OBJECT(self), chat_view_signals[SEND_TEXT_CLICKED], 0, priv->conversation_->uid.c_str(), body.c_str());
268 269
}

270
static gchar*
271
file_to_manipulate(GtkWindow* top_window, bool send)
272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297
{
    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;
}

298 299
static void update_chatview_frame(ChatView *self);

300 301 302 303 304 305 306 307
void
on_record_closed(GtkPopover*, ChatView *self)
{
    g_return_if_fail(IS_CHAT_VIEW(self));
    auto* priv = CHAT_VIEW_GET_PRIVATE(self);

    if (!priv->cpp->saveFileName_.empty()) {
        priv->cpp->avModel_->stopLocalRecorder(priv->cpp->saveFileName_);
308 309 310
        if (!priv->cpp->saveFileName_.empty()) {
            std::remove(priv->cpp->saveFileName_.c_str());
        }
311 312 313 314 315
        priv->cpp->saveFileName_ = "";
    }

    priv->cpp->current_action_ = RecordAction::RECORD;
    if (priv->timer_duration) g_source_remove(priv->timer_duration);
316 317 318 319
    if (priv->is_video_record) {
        priv->cpp->avModel_->stopPreview();
        QObject::disconnect(priv->local_renderer_connection);
    }
320 321 322 323
    priv->duration = 0;
}

void
324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357
init_control_box_buttons(ChatView *self, GtkWidget *box_contols) {
    g_return_if_fail(IS_CHAT_VIEW(self));
    auto* priv = CHAT_VIEW_GET_PRIVATE(self);

    std::string rsc = !priv->useDarkTheme && !priv->is_video_record ?
        "/net/jami/JamiGnome/retry" : "/net/jami/JamiGnome/retry-white";
    auto image_retry = gtk_image_new_from_resource (rsc.c_str());
    auto image_record = gtk_image_new_from_resource("/net/jami/JamiGnome/record");

    priv->button_retry = gtk_button_new();
    gtk_button_set_relief(GTK_BUTTON(priv->button_retry), GTK_RELIEF_NONE);
    gtk_widget_set_tooltip_text(priv->button_retry, _("Retry"));
    gtk_button_set_image(GTK_BUTTON(priv->button_retry), image_retry);
    gtk_widget_set_size_request(GTK_WIDGET(priv->button_retry), 48, 48);
    auto context = gtk_widget_get_style_context(GTK_WIDGET(priv->button_retry));
    gtk_style_context_add_class(context, "record-button");
    g_signal_connect_swapped(priv->button_retry, "clicked", G_CALLBACK(reset_recorder), self);
    gtk_container_add(GTK_CONTAINER(box_contols), priv->button_retry);

    priv->button_main_action = gtk_button_new();
    gtk_button_set_relief(GTK_BUTTON(priv->button_main_action), GTK_RELIEF_NONE);
    gtk_widget_set_tooltip_text(priv->button_main_action, _("Record"));
    gtk_button_set_image(GTK_BUTTON(priv->button_main_action), image_record);
    gtk_widget_set_size_request(GTK_WIDGET(priv->button_main_action), 48, 48);
    context = gtk_widget_get_style_context(GTK_WIDGET(priv->button_main_action));
    gtk_style_context_add_class(context, "record-button");
    g_signal_connect_swapped(priv->button_main_action, "clicked", G_CALLBACK(on_main_action_clicked), self);
    gtk_container_add(GTK_CONTAINER(box_contols), priv->button_main_action);

    gtk_widget_set_halign(box_contols, GTK_ALIGN_CENTER);
}

void
chat_view_show_recorder(ChatView *self, int pt_x, int pt_y, bool is_video_record)
358 359 360 361 362
{
    g_return_if_fail(IS_CHAT_VIEW(self));
    auto* priv = CHAT_VIEW_GET_PRIVATE(self);
    if (!priv->readyToRecord_) return;

363 364 365 366 367 368 369 370
    priv->is_video_record = is_video_record;
    if (is_video_record) priv->cpp->avModel_->startPreview();
    std::string css = is_video_record ? ".record-button { background: rgba(0, 0, 0, 0.2); border-radius: 50%; border: 0; transition: all 0.3s ease; } \
        .record-button:hover { background: rgba(0, 0, 0, 0.2); border-radius: 50%; border: 0; transition: all 0.3s ease; } \
        .label_time { color: white; }"
        : ".record-button { background: transparent; border-radius: 50%; border: 0; transition: all 0.3s ease; } \
        .record-button:hover { background: transparent; border-radius: 50%; border: 0; transition: all 0.3s ease; }";
    if (is_video_record) css += "";
371 372 373

    // CSS styles
    auto provider = gtk_css_provider_new();
374
    gtk_css_provider_load_from_data(provider, css.c_str(), -1, nullptr);
375 376 377 378 379 380 381 382 383 384 385 386 387 388
    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);

#if GTK_CHECK_VERSION(3,22,0)
    GdkRectangle workarea = {};
    gdk_monitor_get_workarea(
        gdk_display_get_primary_monitor(gdk_display_get_default()),
        &workarea);
    auto widget_size = std::max(300, workarea.width / 6);
#else
    auto widget_size = std::max(300, gdk_screen_width() / 6);
#endif

389 390 391
    auto width = .0;
    auto height = .0;
    if (is_video_record) {
392 393
        auto deviceId = priv->cpp->avModel_->getDefaultDevice();
        auto settings = priv->cpp->avModel_->getDeviceSettings(deviceId);
394 395 396 397 398 399 400 401 402 403 404 405
        auto res = settings.size;
        if (res.find("x") == std::string::npos) return;
        auto res_width = static_cast<double>(std::stoi(res.substr(0, res.find("x"))));
        auto res_height = static_cast<double>(std::stoi(res.substr(res.find("x") + 1)));
        auto max = std::max(res_width, res_height);

        width = res_width / max * widget_size;
        height = res_height / max * widget_size;
    } else {
        width = widget_size;
        height = 200;
    }
406 407 408 409 410 411 412

    if (priv->record_popover) {
        gtk_widget_destroy(priv->record_popover);
    }
    priv->record_popover = gtk_popover_new(GTK_WIDGET(priv->box_webkit_chat_container));
    g_signal_connect(priv->record_popover, "closed", G_CALLBACK(on_record_closed), self);
    gtk_popover_set_relative_to(GTK_POPOVER(priv->record_popover), GTK_WIDGET(priv->box_webkit_chat_container));
413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432

    if (is_video_record) {
        init_video_widget(self);
        gtk_container_add(GTK_CONTAINER(GTK_POPOVER(priv->record_popover)), priv->video_widget);
        gtk_widget_set_size_request(GTK_WIDGET(priv->video_widget), width, height);
    } else {
        auto* box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
        priv->label_time =  gtk_label_new("00:00");
        auto* attributes = pango_attr_list_new();
        auto* font_desc = pango_attr_font_desc_new(pango_font_description_from_string("24"));
        pango_attr_list_insert(attributes, font_desc);
        gtk_label_set_attributes(GTK_LABEL(priv->label_time), attributes);
        pango_attr_list_unref(attributes);
        auto* box_contols = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
        init_control_box_buttons(self, box_contols);
        gtk_box_pack_start(GTK_BOX(box), priv->label_time, true, true, 0);
        gtk_box_pack_end(GTK_BOX(box), box_contols, false, false, 0);
        gtk_container_add(GTK_CONTAINER(GTK_POPOVER(priv->record_popover)), box);
    }

433 434 435 436 437 438
    GdkRectangle rect;
    rect.width = 1;
    rect.height = 1;
    rect.x = pt_x;
    rect.y = pt_y;
    gtk_popover_set_pointing_to(GTK_POPOVER(priv->record_popover), &rect);
439
    gtk_widget_set_size_request(GTK_WIDGET(priv->record_popover), width, height);
440 441 442 443
#if GTK_CHECK_VERSION(3,22,0)
    gtk_popover_popdown(GTK_POPOVER(priv->record_popover));
#endif
    gtk_widget_show_all(priv->record_popover);
444
    gtk_widget_hide(priv->button_retry);
445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465
}

static gboolean
on_timer_duration_timeout(ChatView* view)
{
    g_return_val_if_fail(IS_CHAT_VIEW(view), G_SOURCE_REMOVE);
    auto* priv = CHAT_VIEW_GET_PRIVATE(view);
    priv->duration += 1;
    auto m = std::to_string(priv->duration / 60);
    if (m.length() == 1) {
        m = "0" + m;
    }
    auto s = std::to_string(priv->duration % 60);
    if (s.length() == 1) {
        s = "0" + s;
    }
    auto time_txt = m + ":" + s;
    gtk_label_set_text(GTK_LABEL(priv->label_time), time_txt.c_str());
    return G_SOURCE_CONTINUE;
}

466 467
static bool
start_recorder(ChatView* self)
468
{
469
    g_return_val_if_fail(IS_CHAT_VIEW(self), false);
470
    auto* priv = CHAT_VIEW_GET_PRIVATE(self);
471
    std::string file_name = priv->cpp->avModel_->startLocalRecorder(!priv->is_video_record);
472
    if (file_name.empty()) {
473 474 475
        priv->startRecorderWhenReady = true;
        g_warning("set_state: failed to start recording, wait preview");
        return false;
476 477
    }

478 479 480
    if (!priv->cpp->saveFileName_.empty()) {
        std::remove(priv->cpp->saveFileName_.c_str());
    }
481
    priv->cpp->saveFileName_ = file_name;
482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504
    return true;
}

static void
reset_recorder(ChatView *self)
{
    g_return_if_fail(IS_CHAT_VIEW(self));
    auto* priv = CHAT_VIEW_GET_PRIVATE(self);
    auto result = start_recorder(self);

    if (result) {
        gtk_widget_hide(GTK_WIDGET(priv->button_retry));
        std::string rsc = !priv->useDarkTheme && !priv->is_video_record ?
            "/net/jami/JamiGnome/stop" : "/net/jami/JamiGnome/stop-white";
        auto image = gtk_image_new_from_resource (rsc.c_str());
        gtk_button_set_image(GTK_BUTTON(priv->button_main_action), image);
        gtk_widget_set_tooltip_text(priv->button_main_action, _("Stop"));
        priv->cpp->current_action_ = RecordAction::STOP;
        gtk_label_set_text(GTK_LABEL(priv->label_time), "00:00");
        priv->duration = 0;
        priv->timer_duration = g_timeout_add(1000, (GSourceFunc)on_timer_duration_timeout, self);
    }

505 506
}

507
static void
508
webkit_chat_container_script_dialog(GtkWidget* webview, gchar *interaction, ChatView* self)
509
{
510 511
    auto priv = CHAT_VIEW_GET_PRIVATE(self);
    auto order = std::string(interaction);
512
    if (!priv->conversation_) return;
513
    if (order == "ACCEPT") {
514
        (*priv->accountInfo_)->conversationModel->makePermanent(priv->conversation_->uid);
515
    } else if (order == "REFUSE") {
516
        (*priv->accountInfo_)->conversationModel->removeConversation(priv->conversation_->uid);
517
    } else if (order == "BLOCK") {
518
        (*priv->accountInfo_)->conversationModel->removeConversation(priv->conversation_->uid, true);
519 520 521 522 523
    } else if (order == "UNBLOCK") {
        try {
            auto contactUri = priv->conversation_->participants.front();
            auto& contact = (*priv->accountInfo_)->contactModel->getContact(contactUri);
            (*priv->accountInfo_)->contactModel->addContact(contact);
524
        } catch (std::out_of_range&) {
525 526 527 528 529 530 531 532
            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);
533 534
    } else if (order.find("SEND:") == 0) {
        auto toSend = order.substr(std::string("SEND:").size());
535 536 537 538 539 540
        if ((*priv->accountInfo_)->profileInfo.type == lrc::api::profile::Type::RING) {
            (*priv->accountInfo_)->conversationModel->sendMessage(priv->conversation_->uid, toSend);
        } else {
            // For SIP accounts, we need to wait that the conversation is created to send text
            send_text_clicked(self, toSend);
        }
541
    } else if (order.find("SEND_FILE") == 0) {
542
        if (auto model = (*priv->accountInfo_)->conversationModel.get()) {
543 544 545 546
            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) {
547
        if (auto model = (*priv->accountInfo_)->conversationModel.get()) {
548 549
            try {
                auto interactionId = std::stoull(order.substr(std::string("ACCEPT_FILE:").size()));
550 551

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

Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
554
                // get preferred directory destination.
555 556 557 558 559 560 561 562 563 564 565
                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 += "/";
566 567 568 569
                auto wantedFilename = filename + info.displayName;
                auto duplicate = 0;
                while (std::ifstream(wantedFilename).good()) {
                    ++duplicate;
570 571 572 573 574
                    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);
575 576
                }
                model->acceptTransfer(priv->conversation_->uid, interactionId, wantedFilename);
577 578 579 580 581
            } catch (...) {
                // ignore
            }
        }
    } else if (order.find("REFUSE_FILE:") == 0) {
582
        if (auto model = (*priv->accountInfo_)->conversationModel.get()) {
583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599
            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);
        }
600
    } else if (order.find("ADD_TO_CONVERSATIONS") == 0) {
601
        add_to_conversations_clicked(self);
602 603 604 605 606 607 608 609
    } 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());
        }
610 611 612 613 614 615 616 617
    } 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());
        }
618 619 620 621 622 623 624 625
    } else if (order.find("VIDEO_RECORD:") == 0) {
        auto pos_str {order.substr(std::string("VIDEO_RECORD:").size())};
        auto sep_idx = pos_str.find("x");
        if (sep_idx == std::string::npos)
            return;
        try {
            int pt_x = stoi(pos_str.substr(0, sep_idx));
            int pt_y = stoi(pos_str.substr(sep_idx + 1));
626 627 628 629 630 631 632 633 634 635 636 637 638
            chat_view_show_recorder(self, pt_x, pt_y, true);
        } catch (...) {
            // ignore
        }
    } else if (order.find("AUDIO_RECORD:") == 0) {
        auto pos_str {order.substr(std::string("AUDIO_RECORD:").size())};
        auto sep_idx = pos_str.find("x");
        if (sep_idx == std::string::npos)
            return;
        try {
            int pt_x = stoi(pos_str.substr(0, sep_idx));
            int pt_y = stoi(pos_str.substr(sep_idx + 1));
            chat_view_show_recorder(self, pt_x, pt_y, false);
639 640 641
        } catch (...) {
            // ignore
        }
642 643 644
    }
}

645 646 647 648 649 650
static void
chat_view_init(ChatView *view)
{
    gtk_widget_init_template(GTK_WIDGET(view));

    ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(view);
651
    priv->settings = g_settings_new_full(get_settings_schema(), NULL, NULL);
652 653 654 655 656 657 658 659
}

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

aviau's avatar
aviau committed
662
    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), ChatView, box_webkit_chat_container);
663 664

    chat_view_signals[NEW_MESSAGES_DISPLAYED] = g_signal_new (
665
        "new-interactions-displayed",
666 667 668 669 670 671 672
        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);
673 674 675 676 677 678 679 680 681 682

    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);
683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702

    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);
703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722

    chat_view_signals[ADD_CONVERSATION_CLICKED] = g_signal_new (
        "add-conversation-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);

    chat_view_signals[SEND_TEXT_CLICKED] = g_signal_new (
        "send-text-clicked",
        G_TYPE_FROM_CLASS(klass),
        (GSignalFlags) (G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION),
        0,
        nullptr,
        nullptr,
        g_cclosure_user_marshal_VOID__STRING_STRING,
        G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_STRING);
723 724
}

aviau's avatar
aviau committed
725
static void
726
print_interaction_to_buffer(ChatView* self, uint64_t interactionId, const lrc::api::interaction::Info& interaction)
aviau's avatar
aviau committed
727 728 729
{
    ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);

730
    if (!priv->conversation_) return;
731
    if (!interaction.isRead)
732
        (*priv->accountInfo_)->conversationModel->setInteractionRead(priv->conversation_->uid, interactionId);
733 734

    webkit_chat_container_print_new_interaction(
aviau's avatar
aviau committed
735
        WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container),
736
        *(*priv->accountInfo_)->conversationModel,
737 738
        interactionId,
        interaction
aviau's avatar
aviau committed
739
    );
740 741
}

742
static void
743
update_interaction(ChatView* self, uint64_t interactionId, const lrc::api::interaction::Info& interaction)
744 745
{
    ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);
746 747
    webkit_chat_container_update_interaction(
        WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container),
748
        *(*priv->accountInfo_)->conversationModel,
749 750
        interactionId,
        interaction
751
    );
752
}
753

754 755 756 757 758 759 760 761 762 763
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
    );
}

764 765 766 767 768
static void
load_participants_images(ChatView *self)
{
    g_return_if_fail(IS_CHAT_VIEW(self));
    ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);
769

770
    // Contact
771
    if (!priv->conversation_) return;
772
    auto contactUri = priv->conversation_->participants.front();
773
    try{
774
        auto& contact = (*priv->accountInfo_)->contactModel->getContact(contactUri);
775 776 777 778 779 780 781 782 783 784 785
        std::string avatar_str = contact.profileInfo.avatar;
        if (avatar_str.empty()) {
            auto var_photo = Interfaces::PixbufManipulator().conversationPhoto(
                *priv->conversation_,
                **(priv->accountInfo_),
                QSize(AVATAR_WIDTH, AVATAR_HEIGHT),
                contact.isPresent
            );
            auto image = var_photo.value<std::shared_ptr<GdkPixbuf>>();
            auto ba = Interfaces::PixbufManipulator().toByteArray(var_photo);
            avatar_str = ba.toBase64().toStdString();
786
        }
787 788 789 790 791
        webkit_chat_container_set_sender_image(
            WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container),
            contactUri,
            avatar_str
        );
792 793
    } catch (const std::out_of_range&) {
        // ContactModel::getContact() exception
794 795
    }

796
    // For this account
797 798 799 800 801 802 803
    std::string avatar_str = (*priv->accountInfo_)->profileInfo.avatar;
    if (avatar_str.empty()) {
        auto default_photo = QVariant::fromValue(Interfaces::PixbufManipulator().scaleAndFrame(
            Interfaces::PixbufManipulator().generateAvatar("", "").get(),
            QSize(AVATAR_WIDTH, AVATAR_HEIGHT), false));
        auto ba = Interfaces::PixbufManipulator().toByteArray(default_photo);
        avatar_str = ba.toBase64().toStdString();
804
    }
805 806 807 808 809
    webkit_chat_container_set_sender_image(
        WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container),
        (*priv->accountInfo_)->profileInfo.uri,
        avatar_str
    );
810 811
}

812
static void
813
print_text_recording(ChatView *self)
814 815 816 817
{
    g_return_if_fail(IS_CHAT_VIEW(self));
    ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);

818
    // Read interactions
819
    if (!priv->conversation_) return;
820
    for (const auto& it: priv->conversation_->interactions) {
821
        if (!it.second.isRead)
822
            (*priv->accountInfo_)->conversationModel->setInteractionRead(priv->conversation_->uid, it.first);
823 824
    }

825 826
    webkit_chat_container_print_history(
        WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container),
827
        *(*priv->accountInfo_)->conversationModel,
828 829
        priv->conversation_->interactions
    );
830 831
}

aviau's avatar
aviau committed
832 833 834 835 836 837 838 839 840 841 842 843
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)
    );

844
    display_links_toggled(self);
845 846
    print_text_recording(self);
    load_participants_images(self);
847
    webkit_chat_set_dark_mode(WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container), priv->useDarkTheme, priv->background);
848

849
    priv->ready_ = true;
850
    for (const auto& interaction: priv->cpp->interactionsBuffer_) {
851 852
        if (interaction.conv == priv->conversation_->uid) {
            print_interaction_to_buffer(self, interaction.id, interaction.info);
853
        }
854
    }
855

856
    priv->update_interaction_connection = QObject::connect(
857
    &*(*priv->accountInfo_)->conversationModel, &lrc::api::ConversationModel::interactionStatusUpdated,
858
    [self, priv](const std::string& uid, uint64_t msgId, lrc::api::interaction::Info msg) {
859
        if (!priv->conversation_) return;
860
        if (uid == priv->conversation_->uid) {
861 862 863 864
            update_interaction(self, msgId, msg);
        }
    });

865 866 867 868 869 870 871 872 873
    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);
        }
    });

874
    if (!priv->conversation_) return;
875
    auto contactUri = priv->conversation_->participants.front();
876
    try {
877
        auto contactInfo = (*priv->accountInfo_)->contactModel->getContact(contactUri);
878 879 880 881 882 883 884 885 886
        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
    }
887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912

    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;
913 914
    if (bestName == alias)
        alias = "";
915 916 917 918 919 920
    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;

921 922 923
    webkit_chat_update_chatview_frame(WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container),
                                     (*priv->accountInfo_)->enabled,
                                     contactInfo.isBanned, temp, alias.c_str(), bestName.c_str());
924 925 926

    webkit_chat_container_set_invitation(WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container),
                                             (contactInfo.profileInfo.type == lrc::api::profile::Type::PENDING),
927
                                             bestName,
928
                                             contactInfo.profileInfo.uri);
929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950

    // 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&) {}
951
    chat_view_set_record_visible(self, lrc::api::Lrc::activeCalls().size() == 0);
aviau's avatar
aviau committed
952 953
}

954 955 956 957 958 959 960 961
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;

962
    GError *error = nullptr;
963
    auto* filename_uri = g_filename_from_uri(data, nullptr, &error);
964 965 966 967 968
    if (error) {
        g_warning("Unable to exec g_filename_from_uri on %s", data);
        g_error_free(error);
        return;
    }
969 970 971
    std::string data_str = filename_uri;
    g_free(filename_uri);

972 973 974
    // Only take files
    if (data_str.find("\r\n") == std::string::npos) return;
    const auto LEN_END = std::string("\r\n").length();
975 976 977 978 979 980
    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;
981 982 983 984 985 986 987
    }

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

988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003
static void
on_main_action_clicked(ChatView *self)
{
    g_return_if_fail(IS_CHAT_VIEW(self));
    auto* priv = CHAT_VIEW_GET_PRIVATE(self);

    switch (priv->cpp->current_action_) {
        case RecordAction::RECORD: {
            reset_recorder(self);
            break;
        }
        case RecordAction::STOP: {
            if (!priv->cpp->saveFileName_.empty()) {
                priv->cpp->avModel_->stopLocalRecorder(priv->cpp->saveFileName_);
            }
            gtk_widget_show(GTK_WIDGET(priv->button_retry));
1004 1005 1006
            std::string rsc = !priv->useDarkTheme && !priv->is_video_record ?
                "/net/jami/JamiGnome/send" : "/net/jami/JamiGnome/send-white";
            auto image = gtk_image_new_from_resource (rsc.c_str());
Sébastien Blin's avatar
Sébastien Blin committed
1007
            gtk_widget_set_tooltip_text(priv->button_main_action, _("Send"));
1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030
            gtk_button_set_image(GTK_BUTTON(priv->button_main_action), image);
            priv->cpp->current_action_ = RecordAction::SEND;
            g_source_remove(priv->timer_duration);
            break;
        }
        case RecordAction::SEND: {
            if (auto model = (*priv->accountInfo_)->conversationModel.get()) {
                model->sendFile(priv->conversation_->uid, priv->cpp->saveFileName_, g_path_get_basename(priv->cpp->saveFileName_.c_str()));
                priv->cpp->saveFileName_ = "";
            }
            gtk_widget_destroy(priv->record_popover);
            priv->cpp->current_action_ = RecordAction::RECORD;
            priv->cpp->avModel_->stopPreview();
            break;
        }
    }
}

static void
init_video_widget(ChatView* self)
{
    ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);
    if (priv->video_widget && GTK_IS_WIDGET(priv->video_widget)) gtk_widget_destroy(priv->video_widget);
1031
    priv->startRecorderWhenReady = false;
1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043
    priv->video_widget = video_widget_new();

    try {
        const lrc::api::video::Renderer* previewRenderer =
            &priv->cpp->avModel_->getRenderer(
            lrc::api::video::PREVIEW_RENDERER_ID);
        priv->video_started_by_settings = previewRenderer->isRendering();
        if (priv->video_started_by_settings) {
            video_widget_add_new_renderer(VIDEO_WIDGET(priv->video_widget),
                priv->cpp->avModel_, previewRenderer, VIDEO_RENDERER_REMOTE);
        } else {
            priv->video_started_by_settings = true;
1044
            QObject::disconnect(priv->local_renderer_connection);
1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055
            priv->local_renderer_connection = QObject::connect(
                &*priv->cpp->avModel_,
                &lrc::api::AVModel::rendererStarted,
                [=](const std::string& id) {
                    if (id != lrc::api::video::PREVIEW_RENDERER_ID
                        || !priv->readyToRecord_)
                        return;
                    video_widget_add_new_renderer(
                        VIDEO_WIDGET(priv->video_widget),
                        priv->cpp->avModel_,
                        previewRenderer, VIDEO_RENDERER_REMOTE);
1056 1057 1058
                    // Start recorder
                    if (priv->startRecorderWhenReady)
                        reset_recorder(self);
1059 1060 1061 1062 1063 1064 1065 1066 1067
                });
        }
    } catch (const std::out_of_range& e) {
        g_warning("Cannot start preview");
    }

    auto stage = gtk_clutter_embed_get_stage(GTK_CLUTTER_EMBED(priv->video_widget));

    auto* hbox_record_controls = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 15);
1068
    init_control_box_buttons(self, hbox_record_controls);
1069 1070

    priv->label_time = gtk_label_new("00:00");
1071
    auto context = gtk_widget_get_style_context(GTK_WIDGET(priv->label_time));
1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083
    gtk_style_context_add_class(context, "label_time");
    gtk_container_add(GTK_CONTAINER(hbox_record_controls), priv->label_time);

    gtk_widget_show_all(hbox_record_controls);
    gtk_widget_hide(priv->button_retry);

    auto actor_controls = gtk_clutter_actor_new_with_contents(hbox_record_controls);
    clutter_actor_add_child(stage, actor_controls);
    clutter_actor_set_x_align(actor_controls, CLUTTER_ACTOR_ALIGN_CENTER);
    clutter_actor_set_y_align(actor_controls, CLUTTER_ACTOR_ALIGN_END);
}

aviau's avatar
aviau committed
1084 1085 1086 1087 1088 1089 1090 1091
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);

1092
    update_chatview_frame(self);
aviau's avatar
aviau committed
1093

1094
    priv->webkit_ready = g_signal_connect_swapped(
aviau's avatar
aviau committed
1095 1096 1097 1098 1099 1100
        priv->webkit_chat_container,
        "ready",
        G_CALLBACK(webkit_chat_container_ready),
        self
    );

1101
    priv->webkit_send_text = g_signal_connect(priv->webkit_chat_container,
1102 1103
        "script-dialog",
        G_CALLBACK(webkit_chat_container_script_dialog),
1104 1105
        self);

1106 1107 1108 1109 1110 1111
    priv->webkit_drag_drop = g_signal_connect(
        priv->webkit_chat_container,
        "data-dropped",
        G_CALLBACK(on_webkit_drag_drop),
        self
    );
1112 1113 1114 1115 1116 1117 1118 1119

    g_signal_connect(
        self,
        "draw",
        G_CALLBACK(on_redraw),
        self
    );

1120 1121 1122 1123
    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;
1124 1125
        if (!priv->ready_ && priv->cpp) {
            priv->cpp->interactionsBuffer_.emplace_back(CppImpl::Interaction {
1126 1127 1128 1129 1130 1131
                uid, interactionId, interaction});
        } else if (uid == priv->conversation_->uid) {
            print_interaction_to_buffer(self, interactionId, interaction);
        }
    });

1132
    priv->cpp = new CppImpl();
1133

aviau's avatar
aviau committed
1134 1135
    if (webkit_chat_container_is_ready(WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container)))
        webkit_chat_container_ready(self);
1136
}
Stepan Salenikovich's avatar
Stepan Salenikovich committed
1137 1138

GtkWidget *
1139
chat_view_new (WebKitChatContainer* webkit_chat_container,
1140
               AccountInfoPointer const & accountInfo,
1141 1142
               lrc::api::conversation::Info* conversation,
               lrc::api::AVModel& avModel)
Stepan Salenikovich's avatar
Stepan Salenikovich committed
1143 1144 1145
{
    ChatView *self = CHAT_VIEW(g_object_new(CHAT_VIEW_TYPE, NULL));

aviau's avatar
aviau committed
1146 1147
    ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);
    priv->webkit_chat_container = GTK_WIDGET(webkit_chat_container);
1148
    priv->conversation_ = conversation;
1149
    priv->accountInfo_ = &accountInfo;
aviau's avatar
aviau committed
1150 1151

    build_chat_view(self);
1152 1153
    priv->cpp->avModel_ = &avModel;
    priv->readyToRecord_ = true;
Stepan Salenikovich's avatar
Stepan Salenikovich committed
1154 1155 1156
    return (GtkWidget *)self;
}

1157
void
1158
chat_view_update_temporary(ChatView* self)
1159
{
1160
    g_return_if_fail(IS_CHAT_VIEW(self));
1161
    update_chatview_frame(self);
1162 1163
}

1164 1165
lrc::api::conversation::Info
chat_view_get_conversation(ChatView *self)
1166
{
1167
    g_return_val_if_fail(IS_CHAT_VIEW(self), lrc::api::conversation::Info());
1168
    auto priv = CHAT_VIEW_GET_PRIVATE(self);
1169
    return *priv->conversation_;
1170
}
1171 1172 1173 1174 1175

void
chat_view_set_header_visible(ChatView *self, gboolean visible)
{
    auto priv = CHAT_VIEW_GET_PRIVATE(self);
1176
    webkit_chat_set_header_visible(WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container), visible);
1177
}
1178 1179 1180 1181 1182 1183 1184

void
chat_view_set_record_visible(ChatView *self, gboolean visible)
{
    auto priv = CHAT_VIEW_GET_PRIVATE(self);
    webkit_chat_set_record_visible(WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container), visible);
}