chatview.cpp 43 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
#include <sstream>
30

31 32
// GTK
#include <glib/gi18n.h>
Sébastien Blin's avatar
Sébastien Blin committed
33
#include <clutter-gtk/clutter-gtk.h>
34

35 36 37
// Qt
#include <QSize>

38
// LRC
Sébastien Blin's avatar
Sébastien Blin committed
39
#include <api/call.h>
40 41 42
#include <api/contactmodel.h>
#include <api/conversationmodel.h>
#include <api/contact.h>
Sébastien Blin's avatar
Sébastien Blin committed
43
#include <api/lrc.h>
44
#include <api/newcallmodel.h>
Sébastien Blin's avatar
Sébastien Blin committed
45
#include <api/avmodel.h>
46

47
// Client
48
#include "marshals.h"
49 50
#include "utils/files.h"
#include "native/pixbufmanipulator.h"
Sébastien Blin's avatar
Sébastien Blin committed
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

Sébastien Blin's avatar
Sébastien Blin committed
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_;
Sébastien Blin's avatar
Sébastien Blin committed
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;
Sébastien Blin's avatar
Sébastien Blin committed
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};
Sébastien Blin's avatar
Sébastien Blin committed
110
    bool readyToRecord_ {false};
111 112
    bool useDarkTheme {false};
    std::string background;
Sébastien Blin's avatar
Sébastien Blin committed
113 114 115 116 117 118 119 120
    CppImpl* cpp;

    bool video_started_by_settings;
    GtkWidget* video_widget;
    GtkWidget* record_popover;
    GtkWidget* button_retry;
    GtkWidget* button_main_action;
    GtkWidget* label_time;
Sébastien Blin's avatar
Sébastien Blin committed
121
    bool is_video_record {false};
Sébastien Blin's avatar
Sébastien Blin committed
122 123 124
    guint timer_duration = 0;
    uint32_t duration = 0;

125 126 127 128 129 130 131 132
};

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

static guint chat_view_signals[LAST_SIGNAL] = { 0 };

Sébastien Blin's avatar
Sébastien Blin committed
143 144 145
static void init_video_widget(ChatView* self);
static void on_main_action_clicked(ChatView *self);
static void reset_recorder(ChatView *self);
Sébastien Blin's avatar
Sébastien Blin committed
146

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

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

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

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

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

Sébastien Blin's avatar
Sébastien Blin committed
180 181 182 183 184 185 186 187
    if (priv->video_widget) {
        gtk_widget_destroy(priv->video_widget);
    }

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

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

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

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

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

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

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

253
static void
254
add_to_conversations_clicked(ChatView *self)
255 256
{
    auto priv = CHAT_VIEW_GET_PRIVATE(self);
257
    if (!priv->conversation_) return;
258 259 260 261 262 263 264 265 266
    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());
267 268
}

269
static gchar*
270
file_to_manipulate(GtkWindow* top_window, bool send)
271 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
{
    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
297 298
static void update_chatview_frame(ChatView *self);

Sébastien Blin's avatar
Sébastien Blin committed
299 300 301 302 303 304 305 306
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_);
Sébastien Blin's avatar
Sébastien Blin committed
307 308 309
        if (!priv->cpp->saveFileName_.empty()) {
            std::remove(priv->cpp->saveFileName_.c_str());
        }
Sébastien Blin's avatar
Sébastien Blin committed
310 311 312 313 314
        priv->cpp->saveFileName_ = "";
    }

    priv->cpp->current_action_ = RecordAction::RECORD;
    if (priv->timer_duration) g_source_remove(priv->timer_duration);
315 316 317 318
    if (priv->is_video_record) {
        priv->cpp->avModel_->stopPreview();
        QObject::disconnect(priv->local_renderer_connection);
    }
Sébastien Blin's avatar
Sébastien Blin committed
319 320 321 322
    priv->duration = 0;
}

void
Sébastien Blin's avatar
Sébastien Blin committed
323 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
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)
Sébastien Blin's avatar
Sébastien Blin committed
357 358 359 360 361
{
    g_return_if_fail(IS_CHAT_VIEW(self));
    auto* priv = CHAT_VIEW_GET_PRIVATE(self);
    if (!priv->readyToRecord_) return;

Sébastien Blin's avatar
Sébastien Blin committed
362 363 364 365 366 367 368 369
    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 += "";
Sébastien Blin's avatar
Sébastien Blin committed
370 371 372

    // CSS styles
    auto provider = gtk_css_provider_new();
Sébastien Blin's avatar
Sébastien Blin committed
373
    gtk_css_provider_load_from_data(provider, css.c_str(), -1, nullptr);
Sébastien Blin's avatar
Sébastien Blin committed
374 375 376 377 378 379 380 381 382 383 384 385 386 387
    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

Sébastien Blin's avatar
Sébastien Blin committed
388 389 390
    auto width = .0;
    auto height = .0;
    if (is_video_record) {
391 392
        auto deviceId = priv->cpp->avModel_->getDefaultDevice();
        auto settings = priv->cpp->avModel_->getDeviceSettings(deviceId);
Sébastien Blin's avatar
Sébastien Blin committed
393 394 395 396 397 398 399 400 401 402 403 404
        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;
    }
Sébastien Blin's avatar
Sébastien Blin committed
405 406 407 408 409 410 411

    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));
Sébastien Blin's avatar
Sébastien Blin committed
412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431

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

Sébastien Blin's avatar
Sébastien Blin committed
432 433 434 435 436 437
    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);
Sébastien Blin's avatar
Sébastien Blin committed
438
    gtk_widget_set_size_request(GTK_WIDGET(priv->record_popover), width, height);
Sébastien Blin's avatar
Sébastien Blin committed
439 440 441 442
#if GTK_CHECK_VERSION(3,22,0)
    gtk_popover_popdown(GTK_POPOVER(priv->record_popover));
#endif
    gtk_widget_show_all(priv->record_popover);
Sébastien Blin's avatar
Sébastien Blin committed
443
    gtk_widget_hide(priv->button_retry);
Sébastien Blin's avatar
Sébastien Blin committed
444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471
}

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

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

    gtk_widget_hide(GTK_WIDGET(priv->button_retry));
Sébastien Blin's avatar
Sébastien Blin committed
472 473 474
    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());
Sébastien Blin's avatar
Sébastien Blin committed
475
    gtk_button_set_image(GTK_BUTTON(priv->button_main_action), image);
Sébastien Blin's avatar
Sébastien Blin committed
476
    gtk_widget_set_tooltip_text(priv->button_main_action, _("Stop"));
Sébastien Blin's avatar
Sébastien Blin committed
477 478 479 480 481
    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);

Sébastien Blin's avatar
Sébastien Blin committed
482
    std::string file_name = priv->cpp->avModel_->startLocalRecorder(!priv->is_video_record);
Sébastien Blin's avatar
Sébastien Blin committed
483 484 485 486 487
    if (file_name.empty()) {
        g_warning("set_state: failed to start recording");
        return;
    }

Sébastien Blin's avatar
Sébastien Blin committed
488 489 490
    if (!priv->cpp->saveFileName_.empty()) {
        std::remove(priv->cpp->saveFileName_.c_str());
    }
Sébastien Blin's avatar
Sébastien Blin committed
491 492 493
    priv->cpp->saveFileName_ = file_name;
}

494
static void
Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
495
webkit_chat_container_script_dialog(GtkWidget* webview, gchar *interaction, ChatView* self)
496
{
497 498
    auto priv = CHAT_VIEW_GET_PRIVATE(self);
    auto order = std::string(interaction);
499
    if (!priv->conversation_) return;
500
    if (order == "ACCEPT") {
501
        (*priv->accountInfo_)->conversationModel->makePermanent(priv->conversation_->uid);
502
    } else if (order == "REFUSE") {
503
        (*priv->accountInfo_)->conversationModel->removeConversation(priv->conversation_->uid);
504
    } else if (order == "BLOCK") {
505
        (*priv->accountInfo_)->conversationModel->removeConversation(priv->conversation_->uid, true);
Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
506 507 508 509 510
    } else if (order == "UNBLOCK") {
        try {
            auto contactUri = priv->conversation_->participants.front();
            auto& contact = (*priv->accountInfo_)->contactModel->getContact(contactUri);
            (*priv->accountInfo_)->contactModel->addContact(contact);
511
        } catch (std::out_of_range&) {
Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
512 513 514 515 516 517 518 519
            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);
520 521
    } else if (order.find("SEND:") == 0) {
        auto toSend = order.substr(std::string("SEND:").size());
522 523 524 525 526 527
        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);
        }
528
    } else if (order.find("SEND_FILE") == 0) {
529
        if (auto model = (*priv->accountInfo_)->conversationModel.get()) {
530 531 532 533
            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) {
534
        if (auto model = (*priv->accountInfo_)->conversationModel.get()) {
535 536
            try {
                auto interactionId = std::stoull(order.substr(std::string("ACCEPT_FILE:").size()));
537 538

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

Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
541
                // get preferred directory destination.
542 543 544 545 546 547 548 549 550 551 552
                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 += "/";
553 554 555 556
                auto wantedFilename = filename + info.displayName;
                auto duplicate = 0;
                while (std::ifstream(wantedFilename).good()) {
                    ++duplicate;
557 558 559 560 561
                    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);
562 563
                }
                model->acceptTransfer(priv->conversation_->uid, interactionId, wantedFilename);
564 565 566 567 568
            } catch (...) {
                // ignore
            }
        }
    } else if (order.find("REFUSE_FILE:") == 0) {
569
        if (auto model = (*priv->accountInfo_)->conversationModel.get()) {
570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586
            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
587
    } else if (order.find("ADD_TO_CONVERSATIONS") == 0) {
588
        add_to_conversations_clicked(self);
589 590 591 592 593 594 595 596
    } 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());
        }
597 598 599 600 601 602 603 604
    } 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());
        }
Sébastien Blin's avatar
Sébastien Blin committed
605 606 607 608 609 610 611 612
    } 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));
Sébastien Blin's avatar
Sébastien Blin committed
613 614 615 616 617 618 619 620 621 622 623 624 625
            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);
Sébastien Blin's avatar
Sébastien Blin committed
626 627 628
        } catch (...) {
            // ignore
        }
629 630 631
    }
}

632 633 634 635 636 637
static void
chat_view_init(ChatView *view)
{
    gtk_widget_init_template(GTK_WIDGET(view));

    ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(view);
638
    priv->settings = g_settings_new_full(get_settings_schema(), NULL, NULL);
639 640 641 642 643 644 645 646
}

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

aviau's avatar
aviau committed
649
    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), ChatView, box_webkit_chat_container);
650 651

    chat_view_signals[NEW_MESSAGES_DISPLAYED] = g_signal_new (
652
        "new-interactions-displayed",
653 654 655 656 657 658 659
        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);
660 661 662 663 664 665 666 667 668 669

    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);
670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689

    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);
690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709

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

aviau's avatar
aviau committed
712
static void
713
print_interaction_to_buffer(ChatView* self, uint64_t interactionId, const lrc::api::interaction::Info& interaction)
aviau's avatar
aviau committed
714 715 716
{
    ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);

717
    if (!priv->conversation_) return;
718
    if (!interaction.isRead)
719
        (*priv->accountInfo_)->conversationModel->setInteractionRead(priv->conversation_->uid, interactionId);
720 721

    webkit_chat_container_print_new_interaction(
aviau's avatar
aviau committed
722
        WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container),
723
        *(*priv->accountInfo_)->conversationModel,
724 725
        interactionId,
        interaction
aviau's avatar
aviau committed
726
    );
727 728
}

729
static void
730
update_interaction(ChatView* self, uint64_t interactionId, const lrc::api::interaction::Info& interaction)
731 732
{
    ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);
733 734
    webkit_chat_container_update_interaction(
        WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container),
735
        *(*priv->accountInfo_)->conversationModel,
736 737
        interactionId,
        interaction
738
    );
739
}
740

741 742 743 744 745 746 747 748 749 750
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
    );
}

751 752 753 754 755
static void
load_participants_images(ChatView *self)
{
    g_return_if_fail(IS_CHAT_VIEW(self));
    ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);
756

757
    // Contact
758
    if (!priv->conversation_) return;
759
    auto contactUri = priv->conversation_->participants.front();
760
    try{
761
        auto& contact = (*priv->accountInfo_)->contactModel->getContact(contactUri);
762 763 764 765 766 767 768 769 770 771 772
        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();
773
        }
774 775 776 777 778
        webkit_chat_container_set_sender_image(
            WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container),
            contactUri,
            avatar_str
        );
779 780
    } catch (const std::out_of_range&) {
        // ContactModel::getContact() exception
781 782
    }

783
    // For this account
784 785 786 787 788 789 790
    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();
791
    }
792 793 794 795 796
    webkit_chat_container_set_sender_image(
        WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container),
        (*priv->accountInfo_)->profileInfo.uri,
        avatar_str
    );
797 798
}

799
static void
800
print_text_recording(ChatView *self)
801 802 803 804
{
    g_return_if_fail(IS_CHAT_VIEW(self));
    ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);

805
    // Read interactions
806
    if (!priv->conversation_) return;
807
    for (const auto& it: priv->conversation_->interactions) {
808
        if (!it.second.isRead)
809
            (*priv->accountInfo_)->conversationModel->setInteractionRead(priv->conversation_->uid, it.first);
810 811
    }

812 813
    webkit_chat_container_print_history(
        WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container),
814
        *(*priv->accountInfo_)->conversationModel,
815 816
        priv->conversation_->interactions
    );
817 818
}

aviau's avatar
aviau committed
819 820 821 822 823 824 825 826 827 828 829 830
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)
    );

831
    display_links_toggled(self);
832 833
    print_text_recording(self);
    load_participants_images(self);
834
    webkit_chat_set_dark_mode(WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container), priv->useDarkTheme, priv->background);
835

836
    priv->ready_ = true;
Sébastien Blin's avatar
Sébastien Blin committed
837
    for (const auto& interaction: priv->cpp->interactionsBuffer_) {
838 839
        if (interaction.conv == priv->conversation_->uid) {
            print_interaction_to_buffer(self, interaction.id, interaction.info);
840
        }
841
    }
842

843
    priv->update_interaction_connection = QObject::connect(
844
    &*(*priv->accountInfo_)->conversationModel, &lrc::api::ConversationModel::interactionStatusUpdated,
845
    [self, priv](const std::string& uid, uint64_t msgId, lrc::api::interaction::Info msg) {
846
        if (!priv->conversation_) return;
847
        if (uid == priv->conversation_->uid) {
848 849 850 851
            update_interaction(self, msgId, msg);
        }
    });

852 853 854 855 856 857 858 859 860
    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);
        }
    });

861
    if (!priv->conversation_) return;
862
    auto contactUri = priv->conversation_->participants.front();
863
    try {
864
        auto contactInfo = (*priv->accountInfo_)->contactModel->getContact(contactUri);
865 866 867 868 869 870 871 872 873
        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
874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899

    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;
900 901
    if (bestName == alias)
        alias = "";
Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
902 903 904 905 906 907
    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;

908 909 910
    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
911 912 913

    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
914
                                             bestName,
915
                                             contactInfo.profileInfo.uri);
916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937

    // 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&) {}
Sébastien Blin's avatar
Sébastien Blin committed
938
    chat_view_set_record_visible(self, lrc::api::Lrc::activeCalls().size() == 0);
aviau's avatar
aviau committed
939 940
}

941 942 943 944 945 946 947 948
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;

949
    GError *error = nullptr;
950
    auto* filename_uri = g_filename_from_uri(data, nullptr, &error);
951 952 953 954 955
    if (error) {
        g_warning("Unable to exec g_filename_from_uri on %s", data);
        g_error_free(error);
        return;
    }
956 957 958
    std::string data_str = filename_uri;
    g_free(filename_uri);

959 960 961
    // Only take files
    if (data_str.find("\r\n") == std::string::npos) return;
    const auto LEN_END = std::string("\r\n").length();
962 963 964 965 966 967
    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;
968 969 970 971 972 973 974
    }

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

Sébastien Blin's avatar
Sébastien Blin committed
975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990
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));
Sébastien Blin's avatar
Sébastien Blin committed
991 992 993
            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
994
            gtk_widget_set_tooltip_text(priv->button_main_action, _("Send"));
Sébastien Blin's avatar
Sébastien Blin committed
995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029
            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);
    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;
1030
            QObject::disconnect(priv->local_renderer_connection);
Sébastien Blin's avatar
Sébastien Blin committed
1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050
            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);
                });
        }
    } 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);
Sébastien Blin's avatar
Sébastien Blin committed
1051
    init_control_box_buttons(self, hbox_record_controls);
Sébastien Blin's avatar
Sébastien Blin committed
1052 1053

    priv->label_time = gtk_label_new("00:00");
Sébastien Blin's avatar
Sébastien Blin committed
1054
    auto context = gtk_widget_get_style_context(GTK_WIDGET(priv->label_time));
Sébastien Blin's avatar
Sébastien Blin committed
1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066
    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
1067 1068 1069 1070 1071 1072 1073 1074
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
1075
    update_chatview_frame(self);
aviau's avatar
aviau committed
1076

1077
    priv->webkit_ready = g_signal_connect_swapped(
aviau's avatar
aviau committed
1078 1079 1080 1081 1082 1083
        priv->webkit_chat_container,
        "ready",
        G_CALLBACK(webkit_chat_container_ready),
        self
    );

1084
    priv->webkit_send_text = g_signal_connect(priv->webkit_chat_container,
1085 1086
        "script-dialog",
        G_CALLBACK(webkit_chat_container_script_dialog),
1087 1088
        self);

1089 1090 1091 1092 1093 1094
    priv->webkit_drag_drop = g_signal_connect(
        priv->webkit_chat_container,
        "data-dropped",
        G_CALLBACK(on_webkit_drag_drop),
        self
    );
1095 1096 1097 1098 1099 1100 1101 1102

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

1103 1104 1105 1106
    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;
Sébastien Blin's avatar
Sébastien Blin committed
1107 1108
        if (!priv->ready_ && priv->cpp) {
            priv->cpp->interactionsBuffer_.emplace_back(CppImpl::Interaction {
1109 1110 1111 1112 1113 1114
                uid, interactionId, interaction});
        } else if (uid == priv->conversation_->uid) {
            print_interaction_to_buffer(self, interactionId, interaction);
        }
    });

Sébastien Blin's avatar
Sébastien Blin committed
1115
    priv->cpp = new CppImpl();
1116

aviau's avatar
aviau committed
1117 1118
    if (webkit_chat_container_is_ready(WEBKIT_CHAT_CONTAINER(priv->webkit_chat_container)))
        webkit_chat_container_ready(self);
1119
}
Stepan Salenikovich's avatar
Stepan Salenikovich committed
1120 1121

GtkWidget *
1122
chat_view_new (WebKitChatContainer* webkit_chat_container,
1123
               AccountInfoPointer const & accountInfo,
Sébastien Blin's avatar
Sébastien Blin committed
1124 1125
               lrc::api::conversation::Info* conversation,
               lrc::api::AVModel& avModel)
Stepan Salenikovich's avatar
Stepan Salenikovich committed
1126 1127 1128
{
    ChatView *self = CHAT_VIEW(g_object_new(CHAT_VIEW_TYPE, NULL));

aviau's avatar
aviau committed
1129 1130
    ChatViewPrivate *priv = CHAT_VIEW_GET_PRIVATE(self);
    priv->webkit_chat_container = GTK_WIDGET(webkit_chat_container);
1131
    priv->conversation_ = conversation;
1132
    priv->accountInfo_ = &accountInfo;
aviau's avatar
aviau committed
1133 1134

    build_chat_view(self);
Sébastien Blin's avatar
Sébastien Blin committed
1135 1136
    priv->cpp->avModel_ = &avModel;
    priv->readyToRecord_ = true;
Stepan Salenikovich's avatar
Stepan Salenikovich committed
1137 1138 1139
    return (GtkWidget *)self;
}

1140
void
1141
chat_view_update_temporary(ChatView* self)
1142
{
1143
    g_return_if_fail(IS_CHAT_VIEW(self));
Hugo Lefeuvre's avatar
Hugo Lefeuvre committed
1144
    update_chatview_frame(self);
1145 1146
}

1147 1148
lrc::api::conversation::Info
chat_view_get_conversation(ChatView *self)
1149
{
1150
    g_return_val_if_fail(IS_CHAT_VIEW(self), lrc::api::conversation::Info());
1151
    auto priv = CHAT_VIEW_GET_PRIVATE(self);
1152
    return *priv->conversation_;
1153
}
1154 1155 1156 1157 1158

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