blob: 8d8808d1ac229db335d80d81106d164c7dee709e [file] [log] [blame]
Stepan Salenikovichc64523b2015-02-27 16:31:00 -05001/*
Stepan Salenikovichbe87d2c2016-01-25 14:14:34 -05002 * Copyright (C) 2015-2016 Savoir-faire Linux Inc.
Stepan Salenikovichc64523b2015-02-27 16:31:00 -05003 * Author: Stepan Salenikovich <stepan.salenikovich@savoirfairelinux.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Stepan Salenikovichc64523b2015-02-27 16:31:00 -050018 */
19
20#include "currentcallview.h"
21
22#include <gtk/gtk.h>
Stepan Salenikovich7e283552015-12-21 16:17:52 -050023#include <glib/gi18n.h>
Stepan Salenikovichc64523b2015-02-27 16:31:00 -050024#include <call.h>
Stepan Salenikovichc64523b2015-02-27 16:31:00 -050025#include <callmodel.h>
Stepan Salenikovich36c025c2015-03-03 19:06:44 -050026#include "utils/drawing.h"
27#include "video/video_widget.h"
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -040028#include <video/previewmanager.h>
Stepan Salenikovich6f687072015-03-26 10:43:37 -040029#include <contactmethod.h>
30#include <person.h>
Stepan Salenikovichbbd6c132015-08-20 15:21:48 -040031#include <globalinstances.h>
32#include "native/pixbufmanipulator.h"
Stepan Salenikovicha448f602015-05-29 13:33:06 -040033#include <media/media.h>
34#include <media/text.h>
35#include <media/textrecording.h>
36#include "models/gtkqtreemodel.h"
Stepan Salenikovich67112d12015-06-16 16:57:06 -040037#include "ringnotify.h"
Stepan Salenikovichf6f42652015-07-15 12:46:14 -040038#include <audio/codecmodel.h>
39#include <account.h>
Stepan Salenikovicha5e8e362015-11-05 16:50:48 -050040#include "utils/files.h"
Stepan Salenikoviche178e632015-11-06 13:31:19 -050041#include <clutter-gtk/clutter-gtk.h>
Stepan Salenikovichd2cad062016-01-08 13:43:49 -050042#include "chatview.h"
Stepan Salenikovichc64523b2015-02-27 16:31:00 -050043
Stepan Salenikovich0c7aa2a2015-11-06 17:00:08 -050044static constexpr int CONTROLS_FADE_TIMEOUT = 3000000; /* microseconds */
45static constexpr int FADE_DURATION = 500; /* miliseconds */
46
Stepan Salenikovichc64523b2015-02-27 16:31:00 -050047struct _CurrentCallView
48{
49 GtkBox parent;
50};
51
52struct _CurrentCallViewClass
53{
54 GtkBoxClass parent_class;
55};
56
57typedef struct _CurrentCallViewPrivate CurrentCallViewPrivate;
58
59struct _CurrentCallViewPrivate
60{
Stepan Salenikoviche178e632015-11-06 13:31:19 -050061 GtkWidget *hbox_call_info;
62 GtkWidget *hbox_call_controls;
Stepan Salenikovichc64523b2015-02-27 16:31:00 -050063 GtkWidget *image_peer;
64 GtkWidget *label_identity;
65 GtkWidget *label_status;
66 GtkWidget *label_duration;
Stepan Salenikovichd2cad062016-01-08 13:43:49 -050067 GtkWidget *paned_call;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -050068 GtkWidget *frame_video;
69 GtkWidget *video_widget;
Stepan Salenikovichd2cad062016-01-08 13:43:49 -050070 GtkWidget *frame_chat;
Stepan Salenikovicha448f602015-05-29 13:33:06 -040071 GtkWidget *togglebutton_chat;
Stepan Salenikovich77baa522015-07-07 15:29:14 -040072 GtkWidget *button_hangup;
Stepan Salenikovich7e283552015-12-21 16:17:52 -050073 GtkWidget *scalebutton_quality;
74 GtkWidget *checkbutton_autoquality;
75
76 /* flag used to keep track of the video quality scale pressed state;
77 * we do not want to update the codec bitrate until the user releases the
78 * scale button */
79 gboolean quality_scale_pressed;
Stepan Salenikovicha448f602015-05-29 13:33:06 -040080
81 Call *call;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -050082
Stepan Salenikovichc64523b2015-02-27 16:31:00 -050083 QMetaObject::Connection state_change_connection;
84 QMetaObject::Connection call_details_connection;
Stepan Salenikovichc5f08152015-03-19 00:53:23 -040085 QMetaObject::Connection local_renderer_connection;
86 QMetaObject::Connection remote_renderer_connection;
Stepan Salenikovicha5e8e362015-11-05 16:50:48 -050087
88 GSettings *settings;
Stepan Salenikovich0c7aa2a2015-11-06 17:00:08 -050089
90 // for clutter animations and to know when to fade in/out the overlays
91 ClutterTransition *fade_info;
92 ClutterTransition *fade_controls;
93 gint64 time_last_mouse_motion;
94 guint timer_fade;
Stepan Salenikovichc64523b2015-02-27 16:31:00 -050095};
96
97G_DEFINE_TYPE_WITH_PRIVATE(CurrentCallView, current_call_view, GTK_TYPE_BOX);
98
99#define CURRENT_CALL_VIEW_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), CURRENT_CALL_VIEW_TYPE, CurrentCallViewPrivate))
100
Stepan Salenikoviche1b54892015-12-13 22:18:44 -0500101enum {
102 VIDEO_DOUBLE_CLICKED,
103 LAST_SIGNAL
104};
105
106static guint current_call_view_signals[LAST_SIGNAL] = { 0 };
107
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500108static void
109current_call_view_dispose(GObject *object)
110{
111 CurrentCallView *view;
112 CurrentCallViewPrivate *priv;
113
114 view = CURRENT_CALL_VIEW(object);
115 priv = CURRENT_CALL_VIEW_GET_PRIVATE(view);
116
117 QObject::disconnect(priv->state_change_connection);
118 QObject::disconnect(priv->call_details_connection);
Stepan Salenikovichc5f08152015-03-19 00:53:23 -0400119 QObject::disconnect(priv->local_renderer_connection);
120 QObject::disconnect(priv->remote_renderer_connection);
121
Stepan Salenikovicha5e8e362015-11-05 16:50:48 -0500122 g_clear_object(&priv->settings);
123
Stepan Salenikovich0c7aa2a2015-11-06 17:00:08 -0500124 g_source_remove(priv->timer_fade);
125
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500126 G_OBJECT_CLASS(current_call_view_parent_class)->dispose(object);
127}
128
129static void
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500130show_chat_view(CurrentCallView *self)
131{
132 g_return_if_fail(IS_CURRENT_CALL_VIEW(self));
133 CurrentCallViewPrivate *priv = CURRENT_CALL_VIEW_GET_PRIVATE(self);
134
135 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->togglebutton_chat), TRUE);
136}
137
138static void
Stepan Salenikovicha448f602015-05-29 13:33:06 -0400139chat_toggled(GtkToggleButton *togglebutton, CurrentCallView *self)
140{
141 g_return_if_fail(IS_CURRENT_CALL_VIEW(self));
142 CurrentCallViewPrivate *priv = CURRENT_CALL_VIEW_GET_PRIVATE(self);
143
Stepan Salenikovicha448f602015-05-29 13:33:06 -0400144 if (gtk_toggle_button_get_active(togglebutton)) {
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500145 gtk_widget_show_all(priv->frame_chat);
146 gtk_widget_grab_focus(priv->frame_chat);
Stepan Salenikovicha5129f62015-11-05 15:10:59 -0500147 } else {
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500148 gtk_widget_hide(priv->frame_chat);
Stepan Salenikovicha448f602015-05-29 13:33:06 -0400149 }
150}
151
Stepan Salenikovicha5e8e362015-11-05 16:50:48 -0500152gboolean
153map_boolean_to_orientation(GValue *value, GVariant *variant, G_GNUC_UNUSED gpointer user_data)
154{
155 if (g_variant_is_of_type(variant, G_VARIANT_TYPE_BOOLEAN)) {
156 if (g_variant_get_boolean(variant)) {
157 // true, chat should be horizontal (to the right)
158 g_value_set_enum(value, GTK_ORIENTATION_HORIZONTAL);
159 } else {
160 // false, chat should be vertical (at the bottom)
161 g_value_set_enum(value, GTK_ORIENTATION_VERTICAL);
162 }
163 return TRUE;
164 }
165 return FALSE;
166}
167
Stepan Salenikovich0c7aa2a2015-11-06 17:00:08 -0500168static gboolean
169timeout_check_last_motion_event(CurrentCallView *self)
170{
171 g_return_val_if_fail(IS_CURRENT_CALL_VIEW(self), G_SOURCE_REMOVE);
172 auto priv = CURRENT_CALL_VIEW_GET_PRIVATE(self);
173
174 auto current_time = g_get_monotonic_time();
175 if (current_time - priv->time_last_mouse_motion >= CONTROLS_FADE_TIMEOUT) {
176 // timeout has passed, hide the controls
177 if (clutter_timeline_get_direction(CLUTTER_TIMELINE(priv->fade_info)) == CLUTTER_TIMELINE_BACKWARD) {
178 clutter_timeline_set_direction(CLUTTER_TIMELINE(priv->fade_info), CLUTTER_TIMELINE_FORWARD);
179 clutter_timeline_set_direction(CLUTTER_TIMELINE(priv->fade_controls), CLUTTER_TIMELINE_FORWARD);
180 if (!clutter_timeline_is_playing(CLUTTER_TIMELINE(priv->fade_info))) {
181 clutter_timeline_rewind(CLUTTER_TIMELINE(priv->fade_info));
182 clutter_timeline_rewind(CLUTTER_TIMELINE(priv->fade_controls));
183 clutter_timeline_start(CLUTTER_TIMELINE(priv->fade_info));
184 clutter_timeline_start(CLUTTER_TIMELINE(priv->fade_controls));
185 }
186 }
187 }
188
189 return G_SOURCE_CONTINUE;
190}
191
192static gboolean
193mouse_moved(CurrentCallView *self)
194{
195 g_return_val_if_fail(IS_CURRENT_CALL_VIEW(self), FALSE);
196 auto priv = CURRENT_CALL_VIEW_GET_PRIVATE(self);
197
198 priv->time_last_mouse_motion = g_get_monotonic_time();
199
200 // since the mouse moved, make sure the controls are shown
201 if (clutter_timeline_get_direction(CLUTTER_TIMELINE(priv->fade_info)) == CLUTTER_TIMELINE_FORWARD) {
202 clutter_timeline_set_direction(CLUTTER_TIMELINE(priv->fade_info), CLUTTER_TIMELINE_BACKWARD);
203 clutter_timeline_set_direction(CLUTTER_TIMELINE(priv->fade_controls), CLUTTER_TIMELINE_BACKWARD);
204 if (!clutter_timeline_is_playing(CLUTTER_TIMELINE(priv->fade_info))) {
205 clutter_timeline_rewind(CLUTTER_TIMELINE(priv->fade_info));
206 clutter_timeline_rewind(CLUTTER_TIMELINE(priv->fade_controls));
207 clutter_timeline_start(CLUTTER_TIMELINE(priv->fade_info));
208 clutter_timeline_start(CLUTTER_TIMELINE(priv->fade_controls));
209 }
210 }
211
212 return FALSE; // propogate event
213}
214
215static ClutterTransition *
216create_fade_out_transition()
217{
218 auto transition = clutter_property_transition_new("opacity");
219 clutter_transition_set_from(transition, G_TYPE_UINT, 255);
220 clutter_transition_set_to(transition, G_TYPE_UINT, 0);
221 clutter_timeline_set_duration(CLUTTER_TIMELINE(transition), FADE_DURATION);
222 clutter_timeline_set_repeat_count(CLUTTER_TIMELINE(transition), 0);
223 clutter_timeline_set_progress_mode(CLUTTER_TIMELINE(transition), CLUTTER_EASE_IN_OUT_CUBIC);
224 return transition;
225}
226
Stepan Salenikovich5ed1b492015-11-13 14:03:31 -0500227static gboolean
228video_widget_focus(GtkWidget *widget, GtkDirectionType direction, CurrentCallView *self)
229{
230 g_return_val_if_fail(IS_CURRENT_CALL_VIEW(self), FALSE);
231 auto priv = CURRENT_CALL_VIEW_GET_PRIVATE(self);
232
233 // if this widget already has focus, we want the focus to move to the next widget, otherwise we
234 // will get stuck in a focus loop on the buttons
235 if (gtk_widget_has_focus(widget))
236 return FALSE;
237
238 // otherwise we want the focus to go to and change between the call control buttons
239 if (gtk_widget_child_focus(GTK_WIDGET(priv->hbox_call_controls), direction)) {
240 // selected a child, make sure call controls are shown
241 mouse_moved(self);
242 return TRUE;
243 }
244
245 // did not select the next child, propogate the event
246 return FALSE;
247}
248
Stepan Salenikovich7e283552015-12-21 16:17:52 -0500249static GtkBox *
250gtk_scale_button_get_box(GtkScaleButton *button)
251{
252 GtkWidget *box = NULL;
253 if (auto dock = gtk_scale_button_get_popup(button)) {
254 // the dock is a popover which contains the box
255 box = gtk_bin_get_child(GTK_BIN(dock));
256 if (box) {
257 if (GTK_IS_FRAME(box)) {
258 // support older versions of gtk; the box used to be in a frame
259 box = gtk_bin_get_child(GTK_BIN(box));
260 }
261 }
262 }
263
264 return GTK_BOX(box);
265}
266
267/**
268 * This gets the GtkScaleButtonScale widget (which is a GtkScale) from the
269 * given GtkScaleButton in order to be able to modify its properties and connect
270 * to its signals
271 */
272static GtkScale *
273gtk_scale_button_get_scale(GtkScaleButton *button)
274{
275 GtkScale *scale = NULL;
276
277 if (auto box = gtk_scale_button_get_box(button)) {
278 GList *children = gtk_container_get_children(GTK_CONTAINER(box));
279 for (GList *c = children; c && !scale; c = c->next) {
280 if (GTK_IS_SCALE(c->data))
281 scale = GTK_SCALE(c->data);
282 }
283 g_list_free(children);
284 }
285
286 return scale;
287}
288
289static void
290set_quality(Call *call, gboolean auto_quality_on, double desired_quality)
291{
292 /* set auto quality true or false, also set the bitrate and quality values;
293 * the slider is from 0 to 100, use the min and max vals to scale each value accordingly */
294 if (const auto& codecModel = call->account()->codecModel()) {
295 const auto& videoCodecs = codecModel->videoCodecs();
296
297 for (int i=0; i < videoCodecs->rowCount();i++) {
298 const auto& idx = videoCodecs->index(i,0);
299
300 if (auto_quality_on) {
301 // g_debug("enable auto quality");
302 videoCodecs->setData(idx, "true", CodecModel::Role::AUTO_QUALITY_ENABLED);
303 } else {
304 auto min_bitrate = idx.data(static_cast<int>(CodecModel::Role::MIN_BITRATE)).toInt();
305 auto max_bitrate = idx.data(static_cast<int>(CodecModel::Role::MAX_BITRATE)).toInt();
306 auto min_quality = idx.data(static_cast<int>(CodecModel::Role::MIN_QUALITY)).toInt();
307 auto max_quality = idx.data(static_cast<int>(CodecModel::Role::MAX_QUALITY)).toInt();
308
309 // g_debug("bitrate min: %d, max: %d, quality min: %d, max: %d", min_bitrate, max_bitrate, min_quality, max_quality);
310
311 double bitrate;
312 bitrate = min_bitrate + (double)(max_bitrate - min_bitrate)*(desired_quality/100.0);
313 if (bitrate < 0) bitrate = 0;
314
315 double quality;
316 // note: a lower value means higher quality
317 quality = (double)min_quality - (min_quality - max_quality)*(desired_quality/100.0);
318 if (quality < 0) quality = 0;
319
320 // g_debug("disable auto quality; %% quality: %d; bitrate: %d; quality: %d", (int)desired_quality, (int)bitrate, (int)quality);
321 videoCodecs->setData(idx, "false", CodecModel::Role::AUTO_QUALITY_ENABLED);
322 videoCodecs->setData(idx, QString::number((int)bitrate), CodecModel::Role::BITRATE);
323 videoCodecs->setData(idx, QString::number((int)quality), CodecModel::Role::QUALITY);
324 }
325 }
326 codecModel << CodecModel::EditAction::SAVE;
327 }
328}
329
330static void
331autoquality_toggled(GtkToggleButton *button, CurrentCallView *self)
332{
333 g_return_if_fail(IS_CURRENT_CALL_VIEW(self));
334 CurrentCallViewPrivate *priv = CURRENT_CALL_VIEW_GET_PRIVATE(self);
335
336 gboolean auto_quality_on = gtk_toggle_button_get_active(button);
337
338 auto scale = gtk_scale_button_get_scale(GTK_SCALE_BUTTON(priv->scalebutton_quality));
339 auto plus_button = gtk_scale_button_get_plus_button(GTK_SCALE_BUTTON(priv->scalebutton_quality));
340 auto minus_button = gtk_scale_button_get_minus_button(GTK_SCALE_BUTTON(priv->scalebutton_quality));
341
342 gtk_widget_set_sensitive(GTK_WIDGET(scale), !auto_quality_on);
343 gtk_widget_set_sensitive(plus_button, !auto_quality_on);
344 gtk_widget_set_sensitive(minus_button, !auto_quality_on);
345
346 double desired_quality = gtk_scale_button_get_value(GTK_SCALE_BUTTON(priv->scalebutton_quality));
347
348 if (priv->call)
349 set_quality(priv->call, auto_quality_on, desired_quality);
350}
351
352static void
353quality_changed(GtkScaleButton *button, G_GNUC_UNUSED gdouble value, CurrentCallView *self)
354{
355 g_return_if_fail(IS_CURRENT_CALL_VIEW(self));
356 CurrentCallViewPrivate *priv = CURRENT_CALL_VIEW_GET_PRIVATE(self);
357
358 /* no need to upate quality if auto quality is enabled */
359 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(priv->checkbutton_autoquality))) return;
360
361 /* only update if the scale button is released, to reduce the number of updates */
362 if (priv->quality_scale_pressed) return;
363
364 /* we get the value directly from the widget, in case this function is not
365 * called from the event */
366 if (priv->call)
367 set_quality(priv->call, FALSE, gtk_scale_button_get_value(button));
368}
369
370static gboolean
371quality_button_pressed(G_GNUC_UNUSED GtkWidget *widget, G_GNUC_UNUSED GdkEvent *event, CurrentCallView *self)
372{
373 g_return_val_if_fail(IS_CURRENT_CALL_VIEW(self), FALSE);
374 CurrentCallViewPrivate *priv = CURRENT_CALL_VIEW_GET_PRIVATE(self);
375
376 priv->quality_scale_pressed = TRUE;
377
378 return GDK_EVENT_PROPAGATE;
379}
380
381static gboolean
382quality_button_released(G_GNUC_UNUSED GtkWidget *widget, G_GNUC_UNUSED GdkEvent *event, CurrentCallView *self)
383{
384 g_return_val_if_fail(IS_CURRENT_CALL_VIEW(self), FALSE);
385 CurrentCallViewPrivate *priv = CURRENT_CALL_VIEW_GET_PRIVATE(self);
386
387 priv->quality_scale_pressed = FALSE;
388
389 /* now make sure the quality gets updated */
390 quality_changed(GTK_SCALE_BUTTON(priv->scalebutton_quality), 0, self);
391
392 return GDK_EVENT_PROPAGATE;
393}
394
Stepan Salenikovicha448f602015-05-29 13:33:06 -0400395static void
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500396current_call_view_init(CurrentCallView *view)
397{
398 gtk_widget_init_template(GTK_WIDGET(view));
Stepan Salenikovicha448f602015-05-29 13:33:06 -0400399
400 CurrentCallViewPrivate *priv = CURRENT_CALL_VIEW_GET_PRIVATE(view);
401
Stepan Salenikoviche178e632015-11-06 13:31:19 -0500402 /* create video widget and overlay the call info and controls on it */
403 priv->video_widget = video_widget_new();
404 gtk_container_add(GTK_CONTAINER(priv->frame_video), priv->video_widget);
405 gtk_widget_show_all(priv->frame_video);
406
407 auto stage = gtk_clutter_embed_get_stage(GTK_CLUTTER_EMBED(priv->video_widget));
408 auto actor_info = gtk_clutter_actor_new_with_contents(priv->hbox_call_info);
409 auto actor_controls = gtk_clutter_actor_new_with_contents(priv->hbox_call_controls);
410
411 clutter_actor_add_child(stage, actor_info);
412 clutter_actor_set_x_align(actor_info, CLUTTER_ACTOR_ALIGN_FILL);
413 clutter_actor_set_y_align(actor_info, CLUTTER_ACTOR_ALIGN_START);
414
415 clutter_actor_add_child(stage, actor_controls);
416 clutter_actor_set_x_align(actor_controls, CLUTTER_ACTOR_ALIGN_CENTER);
417 clutter_actor_set_y_align(actor_controls, CLUTTER_ACTOR_ALIGN_END);
418
Stepan Salenikovich0c7aa2a2015-11-06 17:00:08 -0500419 /* add fade in and out states to the info and controls */
420 priv->time_last_mouse_motion = g_get_monotonic_time();
421 priv->fade_info = create_fade_out_transition();
422 priv->fade_controls = create_fade_out_transition();
423 clutter_actor_add_transition(actor_info, "fade_info", priv->fade_info);
424 clutter_actor_add_transition(actor_controls, "fade_controls", priv->fade_controls);
425 clutter_timeline_set_direction(CLUTTER_TIMELINE(priv->fade_info), CLUTTER_TIMELINE_BACKWARD);
426 clutter_timeline_set_direction(CLUTTER_TIMELINE(priv->fade_controls), CLUTTER_TIMELINE_BACKWARD);
427 clutter_timeline_stop(CLUTTER_TIMELINE(priv->fade_info));
428 clutter_timeline_stop(CLUTTER_TIMELINE(priv->fade_controls));
429
430 /* have a timer check every 1 second if the controls should fade out */
431 priv->timer_fade = g_timeout_add(1000, (GSourceFunc)timeout_check_last_motion_event, view);
432
433 /* connect to the mouse motion event to reset the last moved time */
434 g_signal_connect_swapped(priv->video_widget, "motion-notify-event", G_CALLBACK(mouse_moved), view);
435 g_signal_connect_swapped(priv->video_widget, "button-press-event", G_CALLBACK(mouse_moved), view);
436 g_signal_connect_swapped(priv->video_widget, "button-release-event", G_CALLBACK(mouse_moved), view);
437
Stepan Salenikovich5ed1b492015-11-13 14:03:31 -0500438 /* manually handle the focus of the video widget to be able to focus on the call controls */
439 g_signal_connect(priv->video_widget, "focus", G_CALLBACK(video_widget_focus), view);
440
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500441 /* toggle whether or not the chat is displayed */
Stepan Salenikovicha448f602015-05-29 13:33:06 -0400442 g_signal_connect(priv->togglebutton_chat, "toggled", G_CALLBACK(chat_toggled), view);
Stepan Salenikovicha448f602015-05-29 13:33:06 -0400443
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500444 /* bind the chat orientation to the gsetting */
Stepan Salenikovicha5e8e362015-11-05 16:50:48 -0500445 priv->settings = g_settings_new_full(get_ring_schema(), NULL, NULL);
446 g_settings_bind_with_mapping(priv->settings, "chat-pane-horizontal",
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500447 priv->paned_call, "orientation",
Stepan Salenikovicha5e8e362015-11-05 16:50:48 -0500448 G_SETTINGS_BIND_GET,
449 map_boolean_to_orientation,
450 nullptr, nullptr, nullptr);
Stepan Salenikovich7e283552015-12-21 16:17:52 -0500451
452 g_signal_connect(priv->scalebutton_quality, "value-changed", G_CALLBACK(quality_changed), view);
453 /* customize the quality button scale */
454 if (auto scale_box = gtk_scale_button_get_box(GTK_SCALE_BUTTON(priv->scalebutton_quality))) {
455 priv->checkbutton_autoquality = gtk_check_button_new_with_label(C_("Enable automatic video quality", "Auto"));
456 gtk_widget_show(priv->checkbutton_autoquality);
457 gtk_box_pack_start(GTK_BOX(scale_box), priv->checkbutton_autoquality, FALSE, TRUE, 0);
458 g_signal_connect(priv->checkbutton_autoquality, "toggled", G_CALLBACK(autoquality_toggled), view);
459 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->checkbutton_autoquality), TRUE);
460 }
461 if (auto scale = gtk_scale_button_get_scale(GTK_SCALE_BUTTON(priv->scalebutton_quality))) {
462 g_signal_connect(scale, "button-press-event", G_CALLBACK(quality_button_pressed), view);
463 g_signal_connect(scale, "button-release-event", G_CALLBACK(quality_button_released), view);
464 }
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500465}
466
467static void
468current_call_view_class_init(CurrentCallViewClass *klass)
469{
470 G_OBJECT_CLASS(klass)->dispose = current_call_view_dispose;
471
472 gtk_widget_class_set_template_from_resource(GTK_WIDGET_CLASS (klass),
473 "/cx/ring/RingGnome/currentcallview.ui");
474
Stepan Salenikoviche178e632015-11-06 13:31:19 -0500475 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, hbox_call_info);
476 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, hbox_call_controls);
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500477 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, image_peer);
478 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, label_identity);
479 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, label_status);
480 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, label_duration);
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500481 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, paned_call);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500482 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, frame_video);
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500483 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, frame_chat);
Stepan Salenikovicha448f602015-05-29 13:33:06 -0400484 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, togglebutton_chat);
Stepan Salenikovich77baa522015-07-07 15:29:14 -0400485 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, button_hangup);
Stepan Salenikovich7e283552015-12-21 16:17:52 -0500486 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, scalebutton_quality);
Stepan Salenikoviche1b54892015-12-13 22:18:44 -0500487
488 current_call_view_signals[VIDEO_DOUBLE_CLICKED] = g_signal_new (
489 "video-double-clicked",
490 G_TYPE_FROM_CLASS(klass),
491 (GSignalFlags) (G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION),
492 0,
493 nullptr,
494 nullptr,
495 g_cclosure_marshal_VOID__VOID,
496 G_TYPE_NONE, 0);
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500497}
498
499GtkWidget *
500current_call_view_new(void)
501{
502 return (GtkWidget *)g_object_new(CURRENT_CALL_VIEW_TYPE, NULL);
503}
504
505static void
506update_state(CurrentCallView *view, Call *call)
507{
508 CurrentCallViewPrivate *priv = CURRENT_CALL_VIEW_GET_PRIVATE(view);
509
Stepan Salenikovich7ec8fe82015-06-02 18:26:39 -0400510 gchar *status = g_strdup_printf("%s", call->toHumanStateName().toUtf8().constData());
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500511
Stepan Salenikovich7ec8fe82015-06-02 18:26:39 -0400512 gtk_label_set_text(GTK_LABEL(priv->label_status), status);
513
514 g_free(status);
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500515}
516
517static void
518update_details(CurrentCallView *view, Call *call)
519{
520 CurrentCallViewPrivate *priv = CURRENT_CALL_VIEW_GET_PRIVATE(view);
521
522 /* update call duration */
523 QByteArray ba_length = call->length().toLocal8Bit();
524 gtk_label_set_text(GTK_LABEL(priv->label_duration), ba_length.constData());
525}
526
Stepan Salenikoviche1b54892015-12-13 22:18:44 -0500527static gboolean
528on_button_press_in_video_event(GtkWidget *self, GdkEventButton *event, CurrentCallView *view)
529{
530 g_return_val_if_fail(IS_VIDEO_WIDGET(self), FALSE);
531 g_return_val_if_fail(IS_CURRENT_CALL_VIEW(view), FALSE);
532
533 /* on double click */
534 if (event->type == GDK_2BUTTON_PRESS) {
535 g_debug("double click in video");
536 g_signal_emit(G_OBJECT(view), current_call_view_signals[VIDEO_DOUBLE_CLICKED], 0);
537 }
538
539 return GDK_EVENT_PROPAGATE;
540}
541
Stepan Salenikovich7b60b592015-06-16 12:29:07 -0400542void
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500543current_call_view_set_call_info(CurrentCallView *view, const QModelIndex& idx) {
544 CurrentCallViewPrivate *priv = CURRENT_CALL_VIEW_GET_PRIVATE(view);
545
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400546 priv->call = CallModel::instance().getCall(idx);
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400547
548 /* get call image */
Stepan Salenikovichbbd6c132015-08-20 15:21:48 -0400549 QVariant var_i = GlobalInstances::pixmapManipulator().callPhoto(priv->call, QSize(60, 60), false);
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400550 std::shared_ptr<GdkPixbuf> image = var_i.value<std::shared_ptr<GdkPixbuf>>();
551 gtk_image_set_from_pixbuf(GTK_IMAGE(priv->image_peer), image.get());
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500552
553 /* get name */
Stepan Salenikovich9c1f6682015-03-09 16:21:28 -0400554 QVariant var = idx.model()->data(idx, static_cast<int>(Call::Role::Name));
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400555 QByteArray ba_name = var.toString().toUtf8();
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500556 gtk_label_set_text(GTK_LABEL(priv->label_identity), ba_name.constData());
557
558 /* change some things depending on call state */
Stepan Salenikovicha448f602015-05-29 13:33:06 -0400559 update_state(view, priv->call);
560 update_details(view, priv->call);
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500561
562 priv->state_change_connection = QObject::connect(
Stepan Salenikovicha448f602015-05-29 13:33:06 -0400563 priv->call,
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500564 &Call::stateChanged,
Stepan Salenikovicha448f602015-05-29 13:33:06 -0400565 [view, priv]() { update_state(view, priv->call); }
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500566 );
567
568 priv->call_details_connection = QObject::connect(
Stepan Salenikovicha448f602015-05-29 13:33:06 -0400569 priv->call,
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500570 static_cast<void (Call::*)(void)>(&Call::changed),
Stepan Salenikovicha448f602015-05-29 13:33:06 -0400571 [view, priv]() { update_details(view, priv->call); }
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500572 );
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500573
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500574 /* check if we already have a renderer */
Stepan Salenikovich0f693232015-04-22 10:45:08 -0400575 video_widget_push_new_renderer(VIDEO_WIDGET(priv->video_widget),
Stepan Salenikovicha448f602015-05-29 13:33:06 -0400576 priv->call->videoRenderer(),
Stepan Salenikovich0f693232015-04-22 10:45:08 -0400577 VIDEO_RENDERER_REMOTE);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500578
Stepan Salenikovich8e5c9d02015-03-11 14:07:10 -0400579 /* callback for remote renderer */
Stepan Salenikovichc5f08152015-03-19 00:53:23 -0400580 priv->remote_renderer_connection = QObject::connect(
Stepan Salenikovicha448f602015-05-29 13:33:06 -0400581 priv->call,
Stepan Salenikovich9c1f6682015-03-09 16:21:28 -0400582 &Call::videoStarted,
Stepan Salenikovicha448f602015-05-29 13:33:06 -0400583 [priv](Video::Renderer *renderer) {
Stepan Salenikovich0f693232015-04-22 10:45:08 -0400584 video_widget_push_new_renderer(VIDEO_WIDGET(priv->video_widget),
585 renderer,
586 VIDEO_RENDERER_REMOTE);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500587 }
588 );
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400589
Stepan Salenikovich8e5c9d02015-03-11 14:07:10 -0400590 /* local renderer */
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400591 if (Video::PreviewManager::instance().isPreviewing())
Stepan Salenikovich0f693232015-04-22 10:45:08 -0400592 video_widget_push_new_renderer(VIDEO_WIDGET(priv->video_widget),
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400593 Video::PreviewManager::instance().previewRenderer(),
Stepan Salenikovich0f693232015-04-22 10:45:08 -0400594 VIDEO_RENDERER_LOCAL);
Stepan Salenikovich8e5c9d02015-03-11 14:07:10 -0400595
596 /* callback for local renderer */
Stepan Salenikovichc5f08152015-03-19 00:53:23 -0400597 priv->local_renderer_connection = QObject::connect(
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400598 &Video::PreviewManager::instance(),
Stepan Salenikovich8e5c9d02015-03-11 14:07:10 -0400599 &Video::PreviewManager::previewStarted,
Stepan Salenikovicha448f602015-05-29 13:33:06 -0400600 [priv](Video::Renderer *renderer) {
Stepan Salenikovich0f693232015-04-22 10:45:08 -0400601 video_widget_push_new_renderer(VIDEO_WIDGET(priv->video_widget),
602 renderer,
603 VIDEO_RENDERER_LOCAL);
Stepan Salenikovich8e5c9d02015-03-11 14:07:10 -0400604 }
605 );
Stepan Salenikovichbfe9ac62015-03-11 12:49:20 -0400606
Julien Grossholtza0d4f102015-10-22 14:24:17 -0400607 /* handle video widget button click event */
608 g_signal_connect(priv->video_widget, "button-press-event", G_CALLBACK(video_widget_on_button_press_in_screen_event), priv->call);
609
610 /* handle video widget drag and drop*/
611 g_signal_connect(priv->video_widget, "drag-data-received", G_CALLBACK(video_widget_on_drag_data_received), priv->call);
612
Stepan Salenikovichbfe9ac62015-03-11 12:49:20 -0400613 /* catch double click to make full screen */
614 g_signal_connect(priv->video_widget, "button-press-event",
615 G_CALLBACK(on_button_press_in_video_event),
616 view);
Stepan Salenikovicha448f602015-05-29 13:33:06 -0400617
Stepan Salenikovich7e283552015-12-21 16:17:52 -0500618 /* check if auto quality is enabled or not; */
619 if (const auto& codecModel = priv->call->account()->codecModel()) {
620 const auto& videoCodecs = codecModel->videoCodecs();
621 if (videoCodecs->rowCount() > 0) {
622 /* we only need to check the first codec since by default it is ON for all, and the
623 * gnome client sets its ON or OFF for all codecs as well */
624 const auto& idx = videoCodecs->index(0,0);
625 auto auto_quality_enabled = idx.data(static_cast<int>(CodecModel::Role::AUTO_QUALITY_ENABLED)).toString() == "true";
626 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->checkbutton_autoquality), auto_quality_enabled);
627
628 // TODO: save the manual quality setting in the client and set the slider to that value here;
629 // the daemon resets the bitrate/quality between each call, and the default may be
630 // different for each codec, so there is no reason to check it here
631 }
632 }
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500633
634 /* init chat view */
Stepan Salenikovichc6a3b982016-01-11 18:11:39 -0500635 auto chat_view = chat_view_new_call(priv->call);
Stepan Salenikovichd2cad062016-01-08 13:43:49 -0500636 gtk_container_add(GTK_CONTAINER(priv->frame_chat), chat_view);
637
638 /* check if there were any chat notifications and open the chat view if so */
639 if (ring_notify_close_chat_notification(priv->call))
640 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->togglebutton_chat), TRUE);
641
642 /* show chat view on any new incoming messages */
643 g_signal_connect_swapped(chat_view, "new-messages-displayed", G_CALLBACK(show_chat_view), view);
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500644}