currentcallview.cpp 26 KB
Newer Older
Stepan Salenikovich's avatar
Stepan Salenikovich committed
1
/*
2
 *  Copyright (C) 2015 Savoir-faire Linux Inc.
Stepan Salenikovich's avatar
Stepan Salenikovich committed
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
 *  Author: Stepan Salenikovich <stepan.salenikovich@savoirfairelinux.com>
 *
 *  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.
 *
 *  Additional permission under GNU GPL version 3 section 7:
 *
 *  If you modify this program, or any covered work, by linking or
 *  combining it with the OpenSSL project's OpenSSL library (or a
 *  modified version of that library), containing parts covered by the
24
 *  terms of the OpenSSL or SSLeay licenses, Savoir-faire Linux Inc.
Stepan Salenikovich's avatar
Stepan Salenikovich committed
25 26 27 28 29 30 31 32 33
 *  grants you additional permission to convey the resulting work.
 *  Corresponding Source for a non-source form of such a combination
 *  shall include the source code for the parts of OpenSSL used as well
 *  as that of the covered work.
 */

#include "currentcallview.h"

#include <gtk/gtk.h>
34
#include <glib/gi18n.h>
Stepan Salenikovich's avatar
Stepan Salenikovich committed
35 36
#include <call.h>
#include <callmodel.h>
37 38
#include "utils/drawing.h"
#include "video/video_widget.h"
Stepan Salenikovich's avatar
Stepan Salenikovich committed
39
#include <video/previewmanager.h>
40 41
#include <contactmethod.h>
#include <person.h>
42 43
#include <globalinstances.h>
#include "native/pixbufmanipulator.h"
44 45 46 47
#include <media/media.h>
#include <media/text.h>
#include <media/textrecording.h>
#include "models/gtkqtreemodel.h"
48
#include "ringnotify.h"
49 50
#include <audio/codecmodel.h>
#include <account.h>
51
#include "utils/files.h"
52
#include <clutter-gtk/clutter-gtk.h>
53
#include "chatview.h"
Stepan Salenikovich's avatar
Stepan Salenikovich committed
54

55 56 57
static constexpr int CONTROLS_FADE_TIMEOUT = 3000000; /* microseconds */
static constexpr int FADE_DURATION = 500; /* miliseconds */

Stepan Salenikovich's avatar
Stepan Salenikovich committed
58 59 60 61 62 63 64 65 66 67 68 69 70 71
struct _CurrentCallView
{
    GtkBox parent;
};

struct _CurrentCallViewClass
{
    GtkBoxClass parent_class;
};

typedef struct _CurrentCallViewPrivate CurrentCallViewPrivate;

struct _CurrentCallViewPrivate
{
72 73
    GtkWidget *hbox_call_info;
    GtkWidget *hbox_call_controls;
Stepan Salenikovich's avatar
Stepan Salenikovich committed
74 75 76 77
    GtkWidget *image_peer;
    GtkWidget *label_identity;
    GtkWidget *label_status;
    GtkWidget *label_duration;
78
    GtkWidget *paned_call;
79 80
    GtkWidget *frame_video;
    GtkWidget *video_widget;
81
    GtkWidget *frame_chat;
82
    GtkWidget *togglebutton_chat;
83
    GtkWidget *button_hangup;
84 85 86 87 88 89 90
    GtkWidget *scalebutton_quality;
    GtkWidget *checkbutton_autoquality;

    /* flag used to keep track of the video quality scale pressed state;
     * we do not want to update the codec bitrate until the user releases the
     * scale button */
    gboolean quality_scale_pressed;
91 92

    Call *call;
93

Stepan Salenikovich's avatar
Stepan Salenikovich committed
94 95
    QMetaObject::Connection state_change_connection;
    QMetaObject::Connection call_details_connection;
96 97
    QMetaObject::Connection local_renderer_connection;
    QMetaObject::Connection remote_renderer_connection;
98 99

    GSettings *settings;
100 101 102 103 104 105

    // for clutter animations and to know when to fade in/out the overlays
    ClutterTransition *fade_info;
    ClutterTransition *fade_controls;
    gint64 time_last_mouse_motion;
    guint timer_fade;
Stepan Salenikovich's avatar
Stepan Salenikovich committed
106 107 108 109 110 111
};

G_DEFINE_TYPE_WITH_PRIVATE(CurrentCallView, current_call_view, GTK_TYPE_BOX);

#define CURRENT_CALL_VIEW_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), CURRENT_CALL_VIEW_TYPE, CurrentCallViewPrivate))

112 113 114 115 116 117 118
enum {
    VIDEO_DOUBLE_CLICKED,
    LAST_SIGNAL
};

static guint current_call_view_signals[LAST_SIGNAL] = { 0 };

Stepan Salenikovich's avatar
Stepan Salenikovich committed
119 120 121 122 123 124 125 126 127 128 129
static void
current_call_view_dispose(GObject *object)
{
    CurrentCallView *view;
    CurrentCallViewPrivate *priv;

    view = CURRENT_CALL_VIEW(object);
    priv = CURRENT_CALL_VIEW_GET_PRIVATE(view);

    QObject::disconnect(priv->state_change_connection);
    QObject::disconnect(priv->call_details_connection);
130 131 132
    QObject::disconnect(priv->local_renderer_connection);
    QObject::disconnect(priv->remote_renderer_connection);

133 134
    g_clear_object(&priv->settings);

135 136
    g_source_remove(priv->timer_fade);

Stepan Salenikovich's avatar
Stepan Salenikovich committed
137 138 139
    G_OBJECT_CLASS(current_call_view_parent_class)->dispose(object);
}

140
static void
141
show_chat_view(CurrentCallView *self)
142 143 144 145
{
    g_return_if_fail(IS_CURRENT_CALL_VIEW(self));
    CurrentCallViewPrivate *priv = CURRENT_CALL_VIEW_GET_PRIVATE(self);

146
    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->togglebutton_chat), TRUE);
147 148 149
}

static void
150
chat_toggled(GtkToggleButton *togglebutton, CurrentCallView *self)
151 152 153 154
{
    g_return_if_fail(IS_CURRENT_CALL_VIEW(self));
    CurrentCallViewPrivate *priv = CURRENT_CALL_VIEW_GET_PRIVATE(self);

155 156 157 158 159
    if (gtk_toggle_button_get_active(togglebutton)) {
        gtk_widget_show_all(priv->frame_chat);
        gtk_widget_grab_focus(priv->frame_chat);
    } else {
        gtk_widget_hide(priv->frame_chat);
160 161 162
    }
}

163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178
gboolean
map_boolean_to_orientation(GValue *value, GVariant *variant, G_GNUC_UNUSED gpointer user_data)
{
    if (g_variant_is_of_type(variant, G_VARIANT_TYPE_BOOLEAN)) {
        if (g_variant_get_boolean(variant)) {
            // true, chat should be horizontal (to the right)
            g_value_set_enum(value, GTK_ORIENTATION_HORIZONTAL);
        } else {
            // false, chat should be vertical (at the bottom)
            g_value_set_enum(value, GTK_ORIENTATION_VERTICAL);
        }
        return TRUE;
    }
    return FALSE;
}

179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237
static gboolean
timeout_check_last_motion_event(CurrentCallView *self)
{
    g_return_val_if_fail(IS_CURRENT_CALL_VIEW(self), G_SOURCE_REMOVE);
    auto priv = CURRENT_CALL_VIEW_GET_PRIVATE(self);

    auto current_time = g_get_monotonic_time();
    if (current_time - priv->time_last_mouse_motion >= CONTROLS_FADE_TIMEOUT) {
        // timeout has passed, hide the controls
        if (clutter_timeline_get_direction(CLUTTER_TIMELINE(priv->fade_info)) == CLUTTER_TIMELINE_BACKWARD) {
            clutter_timeline_set_direction(CLUTTER_TIMELINE(priv->fade_info), CLUTTER_TIMELINE_FORWARD);
            clutter_timeline_set_direction(CLUTTER_TIMELINE(priv->fade_controls), CLUTTER_TIMELINE_FORWARD);
            if (!clutter_timeline_is_playing(CLUTTER_TIMELINE(priv->fade_info))) {
                clutter_timeline_rewind(CLUTTER_TIMELINE(priv->fade_info));
                clutter_timeline_rewind(CLUTTER_TIMELINE(priv->fade_controls));
                clutter_timeline_start(CLUTTER_TIMELINE(priv->fade_info));
                clutter_timeline_start(CLUTTER_TIMELINE(priv->fade_controls));
            }
        }
    }

    return G_SOURCE_CONTINUE;
}

static gboolean
mouse_moved(CurrentCallView *self)
{
    g_return_val_if_fail(IS_CURRENT_CALL_VIEW(self), FALSE);
    auto priv = CURRENT_CALL_VIEW_GET_PRIVATE(self);

    priv->time_last_mouse_motion = g_get_monotonic_time();

    // since the mouse moved, make sure the controls are shown
    if (clutter_timeline_get_direction(CLUTTER_TIMELINE(priv->fade_info)) == CLUTTER_TIMELINE_FORWARD) {
        clutter_timeline_set_direction(CLUTTER_TIMELINE(priv->fade_info), CLUTTER_TIMELINE_BACKWARD);
        clutter_timeline_set_direction(CLUTTER_TIMELINE(priv->fade_controls), CLUTTER_TIMELINE_BACKWARD);
        if (!clutter_timeline_is_playing(CLUTTER_TIMELINE(priv->fade_info))) {
            clutter_timeline_rewind(CLUTTER_TIMELINE(priv->fade_info));
            clutter_timeline_rewind(CLUTTER_TIMELINE(priv->fade_controls));
            clutter_timeline_start(CLUTTER_TIMELINE(priv->fade_info));
            clutter_timeline_start(CLUTTER_TIMELINE(priv->fade_controls));
        }
    }

    return FALSE; // propogate event
}

static ClutterTransition *
create_fade_out_transition()
{
    auto transition  = clutter_property_transition_new("opacity");
    clutter_transition_set_from(transition, G_TYPE_UINT, 255);
    clutter_transition_set_to(transition, G_TYPE_UINT, 0);
    clutter_timeline_set_duration(CLUTTER_TIMELINE(transition), FADE_DURATION);
    clutter_timeline_set_repeat_count(CLUTTER_TIMELINE(transition), 0);
    clutter_timeline_set_progress_mode(CLUTTER_TIMELINE(transition), CLUTTER_EASE_IN_OUT_CUBIC);
    return transition;
}

238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259
static gboolean
video_widget_focus(GtkWidget *widget, GtkDirectionType direction, CurrentCallView *self)
{
    g_return_val_if_fail(IS_CURRENT_CALL_VIEW(self), FALSE);
    auto priv = CURRENT_CALL_VIEW_GET_PRIVATE(self);

    // if this widget already has focus, we want the focus to move to the next widget, otherwise we
    // will get stuck in a focus loop on the buttons
    if (gtk_widget_has_focus(widget))
        return FALSE;

    // otherwise we want the focus to go to and change between the call control buttons
    if (gtk_widget_child_focus(GTK_WIDGET(priv->hbox_call_controls), direction)) {
        // selected a child, make sure call controls are shown
        mouse_moved(self);
        return TRUE;
    }

    // did not select the next child, propogate the event
    return FALSE;
}

260 261 262 263 264 265 266 267 268 269 270 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 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 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 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405
static GtkBox *
gtk_scale_button_get_box(GtkScaleButton *button)
{
    GtkWidget *box = NULL;
    if (auto dock = gtk_scale_button_get_popup(button)) {
        // the dock is a popover which contains the box
        box = gtk_bin_get_child(GTK_BIN(dock));
        if (box) {
            if (GTK_IS_FRAME(box)) {
                // support older versions of gtk; the box used to be in a frame
                box = gtk_bin_get_child(GTK_BIN(box));
            }
        }
    }

    return GTK_BOX(box);
}

/**
 * This gets the GtkScaleButtonScale widget (which is a GtkScale) from the
 * given GtkScaleButton in order to be able to modify its properties and connect
 * to its signals
 */
static GtkScale *
gtk_scale_button_get_scale(GtkScaleButton *button)
{
    GtkScale *scale = NULL;

    if (auto box = gtk_scale_button_get_box(button)) {
        GList *children = gtk_container_get_children(GTK_CONTAINER(box));
        for (GList *c = children; c && !scale; c = c->next) {
            if (GTK_IS_SCALE(c->data))
                scale = GTK_SCALE(c->data);
        }
        g_list_free(children);
    }

    return scale;
}

static void
set_quality(Call *call, gboolean auto_quality_on, double desired_quality)
{
    /* set auto quality true or false, also set the bitrate and quality values;
     * the slider is from 0 to 100, use the min and max vals to scale each value accordingly */
    if (const auto& codecModel = call->account()->codecModel()) {
        const auto& videoCodecs = codecModel->videoCodecs();

        for (int i=0; i < videoCodecs->rowCount();i++) {
            const auto& idx = videoCodecs->index(i,0);

            if (auto_quality_on) {
                // g_debug("enable auto quality");
                videoCodecs->setData(idx, "true", CodecModel::Role::AUTO_QUALITY_ENABLED);
            } else {
                auto min_bitrate = idx.data(static_cast<int>(CodecModel::Role::MIN_BITRATE)).toInt();
                auto max_bitrate = idx.data(static_cast<int>(CodecModel::Role::MAX_BITRATE)).toInt();
                auto min_quality = idx.data(static_cast<int>(CodecModel::Role::MIN_QUALITY)).toInt();
                auto max_quality = idx.data(static_cast<int>(CodecModel::Role::MAX_QUALITY)).toInt();

                // g_debug("bitrate min: %d, max: %d, quality min: %d, max: %d", min_bitrate, max_bitrate, min_quality, max_quality);

                double bitrate;
                bitrate = min_bitrate + (double)(max_bitrate - min_bitrate)*(desired_quality/100.0);
                if (bitrate < 0) bitrate = 0;

                double quality;
                // note: a lower value means higher quality
                quality = (double)min_quality - (min_quality - max_quality)*(desired_quality/100.0);
                if (quality < 0) quality = 0;

                // g_debug("disable auto quality; %% quality: %d; bitrate: %d; quality: %d", (int)desired_quality, (int)bitrate, (int)quality);
                videoCodecs->setData(idx, "false", CodecModel::Role::AUTO_QUALITY_ENABLED);
                videoCodecs->setData(idx, QString::number((int)bitrate), CodecModel::Role::BITRATE);
                videoCodecs->setData(idx, QString::number((int)quality), CodecModel::Role::QUALITY);
            }
        }
        codecModel << CodecModel::EditAction::SAVE;
    }
}

static void
autoquality_toggled(GtkToggleButton *button, CurrentCallView *self)
{
    g_return_if_fail(IS_CURRENT_CALL_VIEW(self));
    CurrentCallViewPrivate *priv = CURRENT_CALL_VIEW_GET_PRIVATE(self);

    gboolean auto_quality_on = gtk_toggle_button_get_active(button);

    auto scale = gtk_scale_button_get_scale(GTK_SCALE_BUTTON(priv->scalebutton_quality));
    auto plus_button = gtk_scale_button_get_plus_button(GTK_SCALE_BUTTON(priv->scalebutton_quality));
    auto minus_button = gtk_scale_button_get_minus_button(GTK_SCALE_BUTTON(priv->scalebutton_quality));

    gtk_widget_set_sensitive(GTK_WIDGET(scale), !auto_quality_on);
    gtk_widget_set_sensitive(plus_button, !auto_quality_on);
    gtk_widget_set_sensitive(minus_button, !auto_quality_on);

    double desired_quality = gtk_scale_button_get_value(GTK_SCALE_BUTTON(priv->scalebutton_quality));

    if (priv->call)
        set_quality(priv->call, auto_quality_on, desired_quality);
}

static void
quality_changed(GtkScaleButton *button, G_GNUC_UNUSED gdouble value, CurrentCallView *self)
{
    g_return_if_fail(IS_CURRENT_CALL_VIEW(self));
    CurrentCallViewPrivate *priv = CURRENT_CALL_VIEW_GET_PRIVATE(self);

    /* no need to upate quality if auto quality is enabled */
    if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(priv->checkbutton_autoquality))) return;

    /* only update if the scale button is released, to reduce the number of updates */
    if (priv->quality_scale_pressed) return;

    /* we get the value directly from the widget, in case this function is not
     * called from the event */
    if (priv->call)
        set_quality(priv->call, FALSE, gtk_scale_button_get_value(button));
}

static gboolean
quality_button_pressed(G_GNUC_UNUSED GtkWidget *widget, G_GNUC_UNUSED GdkEvent *event, CurrentCallView *self)
{
    g_return_val_if_fail(IS_CURRENT_CALL_VIEW(self), FALSE);
    CurrentCallViewPrivate *priv = CURRENT_CALL_VIEW_GET_PRIVATE(self);

    priv->quality_scale_pressed = TRUE;

    return GDK_EVENT_PROPAGATE;
}

static gboolean
quality_button_released(G_GNUC_UNUSED GtkWidget *widget, G_GNUC_UNUSED GdkEvent *event, CurrentCallView *self)
{
    g_return_val_if_fail(IS_CURRENT_CALL_VIEW(self), FALSE);
    CurrentCallViewPrivate *priv = CURRENT_CALL_VIEW_GET_PRIVATE(self);

    priv->quality_scale_pressed = FALSE;

    /* now make sure the quality gets updated */
    quality_changed(GTK_SCALE_BUTTON(priv->scalebutton_quality), 0, self);

    return GDK_EVENT_PROPAGATE;
}

Stepan Salenikovich's avatar
Stepan Salenikovich committed
406 407 408 409
static void
current_call_view_init(CurrentCallView *view)
{
    gtk_widget_init_template(GTK_WIDGET(view));
410 411 412

    CurrentCallViewPrivate *priv = CURRENT_CALL_VIEW_GET_PRIVATE(view);

413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429
    /* create video widget and overlay the call info and controls on it */
    priv->video_widget = video_widget_new();
    gtk_container_add(GTK_CONTAINER(priv->frame_video), priv->video_widget);
    gtk_widget_show_all(priv->frame_video);

    auto stage = gtk_clutter_embed_get_stage(GTK_CLUTTER_EMBED(priv->video_widget));
    auto actor_info = gtk_clutter_actor_new_with_contents(priv->hbox_call_info);
    auto actor_controls = gtk_clutter_actor_new_with_contents(priv->hbox_call_controls);

    clutter_actor_add_child(stage, actor_info);
    clutter_actor_set_x_align(actor_info, CLUTTER_ACTOR_ALIGN_FILL);
    clutter_actor_set_y_align(actor_info, CLUTTER_ACTOR_ALIGN_START);

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

430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448
    /* add fade in and out states to the info and controls */
    priv->time_last_mouse_motion = g_get_monotonic_time();
    priv->fade_info = create_fade_out_transition();
    priv->fade_controls = create_fade_out_transition();
    clutter_actor_add_transition(actor_info, "fade_info", priv->fade_info);
    clutter_actor_add_transition(actor_controls, "fade_controls", priv->fade_controls);
    clutter_timeline_set_direction(CLUTTER_TIMELINE(priv->fade_info), CLUTTER_TIMELINE_BACKWARD);
    clutter_timeline_set_direction(CLUTTER_TIMELINE(priv->fade_controls), CLUTTER_TIMELINE_BACKWARD);
    clutter_timeline_stop(CLUTTER_TIMELINE(priv->fade_info));
    clutter_timeline_stop(CLUTTER_TIMELINE(priv->fade_controls));

    /* have a timer check every 1 second if the controls should fade out */
    priv->timer_fade = g_timeout_add(1000, (GSourceFunc)timeout_check_last_motion_event, view);

    /* connect to the mouse motion event to reset the last moved time */
    g_signal_connect_swapped(priv->video_widget, "motion-notify-event", G_CALLBACK(mouse_moved), view);
    g_signal_connect_swapped(priv->video_widget, "button-press-event", G_CALLBACK(mouse_moved), view);
    g_signal_connect_swapped(priv->video_widget, "button-release-event", G_CALLBACK(mouse_moved), view);

449 450 451
    /* manually handle the focus of the video widget to be able to focus on the call controls */
    g_signal_connect(priv->video_widget, "focus", G_CALLBACK(video_widget_focus), view);

452
    /* toggle whether or not the chat is displayed */
453 454
    g_signal_connect(priv->togglebutton_chat, "toggled", G_CALLBACK(chat_toggled), view);

455
    /* bind the chat orientation to the gsetting */
456 457
    priv->settings = g_settings_new_full(get_ring_schema(), NULL, NULL);
    g_settings_bind_with_mapping(priv->settings, "chat-pane-horizontal",
458
                                 priv->paned_call, "orientation",
459 460 461
                                 G_SETTINGS_BIND_GET,
                                 map_boolean_to_orientation,
                                 nullptr, nullptr, nullptr);
462 463 464 465 466 467 468 469 470 471 472 473 474 475

    g_signal_connect(priv->scalebutton_quality, "value-changed", G_CALLBACK(quality_changed), view);
    /* customize the quality button scale */
    if (auto scale_box = gtk_scale_button_get_box(GTK_SCALE_BUTTON(priv->scalebutton_quality))) {
        priv->checkbutton_autoquality = gtk_check_button_new_with_label(C_("Enable automatic video quality", "Auto"));
        gtk_widget_show(priv->checkbutton_autoquality);
        gtk_box_pack_start(GTK_BOX(scale_box), priv->checkbutton_autoquality, FALSE, TRUE, 0);
        g_signal_connect(priv->checkbutton_autoquality, "toggled", G_CALLBACK(autoquality_toggled), view);
        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->checkbutton_autoquality), TRUE);
    }
    if (auto scale = gtk_scale_button_get_scale(GTK_SCALE_BUTTON(priv->scalebutton_quality))) {
        g_signal_connect(scale, "button-press-event", G_CALLBACK(quality_button_pressed), view);
        g_signal_connect(scale, "button-release-event", G_CALLBACK(quality_button_released), view);
    }
Stepan Salenikovich's avatar
Stepan Salenikovich committed
476 477 478 479 480 481 482 483 484 485
}

static void
current_call_view_class_init(CurrentCallViewClass *klass)
{
    G_OBJECT_CLASS(klass)->dispose = current_call_view_dispose;

    gtk_widget_class_set_template_from_resource(GTK_WIDGET_CLASS (klass),
                                                "/cx/ring/RingGnome/currentcallview.ui");

486 487
    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, hbox_call_info);
    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, hbox_call_controls);
Stepan Salenikovich's avatar
Stepan Salenikovich committed
488 489 490 491
    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, image_peer);
    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, label_identity);
    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, label_status);
    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, label_duration);
492
    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, paned_call);
493
    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, frame_video);
494
    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, frame_chat);
495
    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, togglebutton_chat);
496
    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, button_hangup);
497
    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, scalebutton_quality);
498 499 500 501 502 503 504 505 506 507

    current_call_view_signals[VIDEO_DOUBLE_CLICKED] = g_signal_new (
        "video-double-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);
Stepan Salenikovich's avatar
Stepan Salenikovich committed
508 509 510 511 512 513 514 515 516 517 518 519 520
}

GtkWidget *
current_call_view_new(void)
{
    return (GtkWidget *)g_object_new(CURRENT_CALL_VIEW_TYPE, NULL);
}

static void
update_state(CurrentCallView *view, Call *call)
{
    CurrentCallViewPrivate *priv = CURRENT_CALL_VIEW_GET_PRIVATE(view);

521 522 523 524 525
    gchar *status = g_strdup_printf("%s", call->toHumanStateName().toUtf8().constData());

    gtk_label_set_text(GTK_LABEL(priv->label_status), status);

    g_free(status);
Stepan Salenikovich's avatar
Stepan Salenikovich committed
526 527 528 529 530 531 532 533 534 535 536 537
}

static void
update_details(CurrentCallView *view, Call *call)
{
    CurrentCallViewPrivate *priv = CURRENT_CALL_VIEW_GET_PRIVATE(view);

    /* update call duration */
    QByteArray ba_length = call->length().toLocal8Bit();
    gtk_label_set_text(GTK_LABEL(priv->label_duration), ba_length.constData());
}

538 539 540 541 542 543 544 545 546 547 548 549 550 551 552
static gboolean
on_button_press_in_video_event(GtkWidget *self, GdkEventButton *event, CurrentCallView *view)
{
    g_return_val_if_fail(IS_VIDEO_WIDGET(self), FALSE);
    g_return_val_if_fail(IS_CURRENT_CALL_VIEW(view), FALSE);

    /* on double click */
    if (event->type == GDK_2BUTTON_PRESS) {
        g_debug("double click in video");
        g_signal_emit(G_OBJECT(view), current_call_view_signals[VIDEO_DOUBLE_CLICKED], 0);
    }

    return GDK_EVENT_PROPAGATE;
}

Stepan Salenikovich's avatar
Stepan Salenikovich committed
553 554 555 556
void
current_call_view_set_call_info(CurrentCallView *view, const QModelIndex& idx) {
    CurrentCallViewPrivate *priv = CURRENT_CALL_VIEW_GET_PRIVATE(view);

557
    priv->call = CallModel::instance().getCall(idx);
558 559

    /* get call image */
560
    QVariant var_i = GlobalInstances::pixmapManipulator().callPhoto(priv->call, QSize(60, 60), false);
561 562
    std::shared_ptr<GdkPixbuf> image = var_i.value<std::shared_ptr<GdkPixbuf>>();
    gtk_image_set_from_pixbuf(GTK_IMAGE(priv->image_peer), image.get());
Stepan Salenikovich's avatar
Stepan Salenikovich committed
563 564

    /* get name */
565
    QVariant var = idx.model()->data(idx, static_cast<int>(Call::Role::Name));
566
    QByteArray ba_name = var.toString().toUtf8();
Stepan Salenikovich's avatar
Stepan Salenikovich committed
567 568 569
    gtk_label_set_text(GTK_LABEL(priv->label_identity), ba_name.constData());

    /* change some things depending on call state */
570 571
    update_state(view, priv->call);
    update_details(view, priv->call);
Stepan Salenikovich's avatar
Stepan Salenikovich committed
572 573

    priv->state_change_connection = QObject::connect(
574
        priv->call,
Stepan Salenikovich's avatar
Stepan Salenikovich committed
575
        &Call::stateChanged,
576
        [view, priv]() { update_state(view, priv->call); }
Stepan Salenikovich's avatar
Stepan Salenikovich committed
577 578 579
    );

    priv->call_details_connection = QObject::connect(
580
        priv->call,
Stepan Salenikovich's avatar
Stepan Salenikovich committed
581
        static_cast<void (Call::*)(void)>(&Call::changed),
582
        [view, priv]() { update_details(view, priv->call); }
Stepan Salenikovich's avatar
Stepan Salenikovich committed
583
    );
584 585

    /* check if we already have a renderer */
586
    video_widget_push_new_renderer(VIDEO_WIDGET(priv->video_widget),
587
                                   priv->call->videoRenderer(),
588
                                   VIDEO_RENDERER_REMOTE);
589

590
    /* callback for remote renderer */
591
    priv->remote_renderer_connection = QObject::connect(
592
        priv->call,
593
        &Call::videoStarted,
594
        [priv](Video::Renderer *renderer) {
595 596 597
            video_widget_push_new_renderer(VIDEO_WIDGET(priv->video_widget),
                                           renderer,
                                           VIDEO_RENDERER_REMOTE);
598 599
        }
    );
Stepan Salenikovich's avatar
Stepan Salenikovich committed
600

601
    /* local renderer */
602
    if (Video::PreviewManager::instance().isPreviewing())
603
        video_widget_push_new_renderer(VIDEO_WIDGET(priv->video_widget),
604
                                       Video::PreviewManager::instance().previewRenderer(),
605
                                       VIDEO_RENDERER_LOCAL);
606 607

    /* callback for local renderer */
608
    priv->local_renderer_connection = QObject::connect(
609
        &Video::PreviewManager::instance(),
610
        &Video::PreviewManager::previewStarted,
611
        [priv](Video::Renderer *renderer) {
612 613 614
            video_widget_push_new_renderer(VIDEO_WIDGET(priv->video_widget),
                                           renderer,
                                           VIDEO_RENDERER_LOCAL);
615 616
        }
    );
Stepan Salenikovich's avatar
Stepan Salenikovich committed
617 618 619 620 621

    /* catch double click to make full screen */
    g_signal_connect(priv->video_widget, "button-press-event",
                     G_CALLBACK(on_button_press_in_video_event),
                     view);
622

623 624 625 626 627 628 629 630 631 632 633 634 635 636 637
    /* check if auto quality is enabled or not; */
    if (const auto& codecModel = priv->call->account()->codecModel()) {
        const auto& videoCodecs = codecModel->videoCodecs();
        if (videoCodecs->rowCount() > 0) {
            /* we only need to check the first codec since by default it is ON for all, and the
             * gnome client sets its ON or OFF for all codecs as well */
            const auto& idx = videoCodecs->index(0,0);
            auto auto_quality_enabled = idx.data(static_cast<int>(CodecModel::Role::AUTO_QUALITY_ENABLED)).toString() == "true";
            gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->checkbutton_autoquality), auto_quality_enabled);

            // TODO: save the manual quality setting in the client and set the slider to that value here;
            //       the daemon resets the bitrate/quality between each call, and the default may be
            //       different for each codec, so there is no reason to check it here
        }
    }
638 639

    /* init chat view */
Stepan Salenikovich's avatar
Stepan Salenikovich committed
640
    auto chat_view = chat_view_new_call(priv->call);
641 642 643 644 645 646 647 648
    gtk_container_add(GTK_CONTAINER(priv->frame_chat), chat_view);

    /* check if there were any chat notifications and open the chat view if so */
    if (ring_notify_close_chat_notification(priv->call))
        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->togglebutton_chat), TRUE);

    /* show chat view on any new incoming messages */
    g_signal_connect_swapped(chat_view, "new-messages-displayed", G_CALLBACK(show_chat_view), view);
Stepan Salenikovich's avatar
Stepan Salenikovich committed
649
}