blob: f5604f96cdda7453e890964637ab4586badd76f4 [file] [log] [blame]
Stepan Salenikovichc64523b2015-02-27 16:31:00 -05001/*
Stepan Salenikovichbbd6c132015-08-20 15:21:48 -04002 * Copyright (C) 2015 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.
18 *
19 * Additional permission under GNU GPL version 3 section 7:
20 *
21 * If you modify this program, or any covered work, by linking or
22 * combining it with the OpenSSL project's OpenSSL library (or a
23 * modified version of that library), containing parts covered by the
Stepan Salenikovichbbd6c132015-08-20 15:21:48 -040024 * terms of the OpenSSL or SSLeay licenses, Savoir-faire Linux Inc.
Stepan Salenikovichc64523b2015-02-27 16:31:00 -050025 * grants you additional permission to convey the resulting work.
26 * Corresponding Source for a non-source form of such a combination
27 * shall include the source code for the parts of OpenSSL used as well
28 * as that of the covered work.
29 */
30
31#include "currentcallview.h"
32
33#include <gtk/gtk.h>
34#include <call.h>
Stepan Salenikovichc64523b2015-02-27 16:31:00 -050035#include <callmodel.h>
Stepan Salenikovich36c025c2015-03-03 19:06:44 -050036#include "utils/drawing.h"
37#include "video/video_widget.h"
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -040038#include <video/previewmanager.h>
Stepan Salenikovich6f687072015-03-26 10:43:37 -040039#include <contactmethod.h>
40#include <person.h>
Stepan Salenikovichbbd6c132015-08-20 15:21:48 -040041#include <globalinstances.h>
42#include "native/pixbufmanipulator.h"
Stepan Salenikovicha448f602015-05-29 13:33:06 -040043#include <media/media.h>
44#include <media/text.h>
45#include <media/textrecording.h>
46#include "models/gtkqtreemodel.h"
Stepan Salenikovichb94873c2015-06-02 16:53:18 -040047#include "video/videowindow.h"
Stepan Salenikovich67112d12015-06-16 16:57:06 -040048#include "ringnotify.h"
Stepan Salenikovichf6f42652015-07-15 12:46:14 -040049#include <audio/codecmodel.h>
50#include <account.h>
Stepan Salenikovicha5e8e362015-11-05 16:50:48 -050051#include "utils/files.h"
Stepan Salenikoviche178e632015-11-06 13:31:19 -050052#include <clutter-gtk/clutter-gtk.h>
Stepan Salenikovichc64523b2015-02-27 16:31:00 -050053
Stepan Salenikovich0c7aa2a2015-11-06 17:00:08 -050054static constexpr int CONTROLS_FADE_TIMEOUT = 3000000; /* microseconds */
55static constexpr int FADE_DURATION = 500; /* miliseconds */
56
Stepan Salenikovichc64523b2015-02-27 16:31:00 -050057struct _CurrentCallView
58{
59 GtkBox parent;
60};
61
62struct _CurrentCallViewClass
63{
64 GtkBoxClass parent_class;
65};
66
67typedef struct _CurrentCallViewPrivate CurrentCallViewPrivate;
68
69struct _CurrentCallViewPrivate
70{
Stepan Salenikoviche178e632015-11-06 13:31:19 -050071 GtkWidget *hbox_call_info;
72 GtkWidget *hbox_call_controls;
Stepan Salenikovichc64523b2015-02-27 16:31:00 -050073 GtkWidget *image_peer;
74 GtkWidget *label_identity;
75 GtkWidget *label_status;
76 GtkWidget *label_duration;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -050077 GtkWidget *frame_video;
78 GtkWidget *video_widget;
Stepan Salenikovicha5e8e362015-11-05 16:50:48 -050079 GtkWidget *paned_chat;
Stepan Salenikovicha5129f62015-11-05 15:10:59 -050080 GtkWidget *vbox_chat;
Stepan Salenikovicha448f602015-05-29 13:33:06 -040081 GtkWidget *togglebutton_chat;
82 GtkWidget *textview_chat;
83 GtkWidget *button_chat_input;
84 GtkWidget *entry_chat_input;
85 GtkWidget *scrolledwindow_chat;
Stepan Salenikovichb94873c2015-06-02 16:53:18 -040086 GtkWidget *fullscreen_window;
Stepan Salenikovich77baa522015-07-07 15:29:14 -040087 GtkWidget *button_hangup;
Stepan Salenikovicha448f602015-05-29 13:33:06 -040088
89 Call *call;
Stepan Salenikovich36c025c2015-03-03 19:06:44 -050090
Stepan Salenikovichc64523b2015-02-27 16:31:00 -050091 QMetaObject::Connection state_change_connection;
92 QMetaObject::Connection call_details_connection;
Stepan Salenikovichc5f08152015-03-19 00:53:23 -040093 QMetaObject::Connection local_renderer_connection;
94 QMetaObject::Connection remote_renderer_connection;
Stepan Salenikovicha448f602015-05-29 13:33:06 -040095 QMetaObject::Connection media_added_connection;
96 QMetaObject::Connection new_message_connection;
Stepan Salenikovich7b60b592015-06-16 12:29:07 -040097 QMetaObject::Connection incoming_msg_connection;
Stepan Salenikovicha5e8e362015-11-05 16:50:48 -050098
99 GSettings *settings;
Stepan Salenikovich0c7aa2a2015-11-06 17:00:08 -0500100
101 // for clutter animations and to know when to fade in/out the overlays
102 ClutterTransition *fade_info;
103 ClutterTransition *fade_controls;
104 gint64 time_last_mouse_motion;
105 guint timer_fade;
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500106};
107
108G_DEFINE_TYPE_WITH_PRIVATE(CurrentCallView, current_call_view, GTK_TYPE_BOX);
109
110#define CURRENT_CALL_VIEW_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), CURRENT_CALL_VIEW_TYPE, CurrentCallViewPrivate))
111
112static void
113current_call_view_dispose(GObject *object)
114{
115 CurrentCallView *view;
116 CurrentCallViewPrivate *priv;
117
118 view = CURRENT_CALL_VIEW(object);
119 priv = CURRENT_CALL_VIEW_GET_PRIVATE(view);
120
121 QObject::disconnect(priv->state_change_connection);
122 QObject::disconnect(priv->call_details_connection);
Stepan Salenikovichc5f08152015-03-19 00:53:23 -0400123 QObject::disconnect(priv->local_renderer_connection);
124 QObject::disconnect(priv->remote_renderer_connection);
Stepan Salenikovicha448f602015-05-29 13:33:06 -0400125 QObject::disconnect(priv->media_added_connection);
126 QObject::disconnect(priv->new_message_connection);
Stepan Salenikovich7b60b592015-06-16 12:29:07 -0400127 QObject::disconnect(priv->incoming_msg_connection);
Stepan Salenikovichc5f08152015-03-19 00:53:23 -0400128
Stepan Salenikovichb94873c2015-06-02 16:53:18 -0400129 if (priv->fullscreen_window) {
130 gtk_widget_destroy(priv->fullscreen_window);
131 priv->fullscreen_window = NULL;
132 }
133
Stepan Salenikovicha5e8e362015-11-05 16:50:48 -0500134 g_clear_object(&priv->settings);
135
Stepan Salenikovich0c7aa2a2015-11-06 17:00:08 -0500136 g_source_remove(priv->timer_fade);
137
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500138 G_OBJECT_CLASS(current_call_view_parent_class)->dispose(object);
139}
140
141static void
Stepan Salenikovicha448f602015-05-29 13:33:06 -0400142chat_toggled(GtkToggleButton *togglebutton, CurrentCallView *self)
143{
144 g_return_if_fail(IS_CURRENT_CALL_VIEW(self));
145 CurrentCallViewPrivate *priv = CURRENT_CALL_VIEW_GET_PRIVATE(self);
146
Stepan Salenikovicha448f602015-05-29 13:33:06 -0400147 if (gtk_toggle_button_get_active(togglebutton)) {
Stepan Salenikovicha5129f62015-11-05 15:10:59 -0500148 gtk_widget_show_all(priv->vbox_chat);
Stepan Salenikovicha448f602015-05-29 13:33:06 -0400149 /* create an outgoing media to bring up chat history, if any */
150 priv->call->addOutgoingMedia<Media::Text>();
Stepan Salenikovichbffaf582015-06-22 14:10:40 -0400151 /* change focus to the chat entry */
152 gtk_widget_grab_focus(priv->entry_chat_input);
Stepan Salenikovicha5129f62015-11-05 15:10:59 -0500153 } else {
154 gtk_widget_hide(priv->vbox_chat);
Stepan Salenikovicha448f602015-05-29 13:33:06 -0400155 }
156}
157
158static void
159send_chat(G_GNUC_UNUSED GtkWidget *widget, CurrentCallView *self)
160{
161 g_return_if_fail(IS_CURRENT_CALL_VIEW(self));
162 CurrentCallViewPrivate *priv = CURRENT_CALL_VIEW_GET_PRIVATE(self);
163
164 /* make sure there is text to send */
165 const gchar *text = gtk_entry_get_text(GTK_ENTRY(priv->entry_chat_input));
166 if (text && strlen(text) > 0) {
Stepan Salenikovich6af88db2015-07-17 12:41:29 -0400167 QMap<QString, QString> messages;
168 messages["text/plain"] = text;
169 priv->call->addOutgoingMedia<Media::Text>()->send(messages);
Stepan Salenikovicha448f602015-05-29 13:33:06 -0400170 /* clear the entry */
171 gtk_entry_set_text(GTK_ENTRY(priv->entry_chat_input), "");
172 }
173}
174
175static void
176scroll_to_bottom(GtkAdjustment *adjustment, G_GNUC_UNUSED gpointer user_data)
177{
178 gtk_adjustment_set_value(adjustment,
179 gtk_adjustment_get_upper(adjustment) - gtk_adjustment_get_page_size(adjustment));
180}
181
Stepan Salenikovicha5e8e362015-11-05 16:50:48 -0500182gboolean
183map_boolean_to_orientation(GValue *value, GVariant *variant, G_GNUC_UNUSED gpointer user_data)
184{
185 if (g_variant_is_of_type(variant, G_VARIANT_TYPE_BOOLEAN)) {
186 if (g_variant_get_boolean(variant)) {
187 // true, chat should be horizontal (to the right)
188 g_value_set_enum(value, GTK_ORIENTATION_HORIZONTAL);
189 } else {
190 // false, chat should be vertical (at the bottom)
191 g_value_set_enum(value, GTK_ORIENTATION_VERTICAL);
192 }
193 return TRUE;
194 }
195 return FALSE;
196}
197
Stepan Salenikovich0c7aa2a2015-11-06 17:00:08 -0500198static gboolean
199timeout_check_last_motion_event(CurrentCallView *self)
200{
201 g_return_val_if_fail(IS_CURRENT_CALL_VIEW(self), G_SOURCE_REMOVE);
202 auto priv = CURRENT_CALL_VIEW_GET_PRIVATE(self);
203
204 auto current_time = g_get_monotonic_time();
205 if (current_time - priv->time_last_mouse_motion >= CONTROLS_FADE_TIMEOUT) {
206 // timeout has passed, hide the controls
207 if (clutter_timeline_get_direction(CLUTTER_TIMELINE(priv->fade_info)) == CLUTTER_TIMELINE_BACKWARD) {
208 clutter_timeline_set_direction(CLUTTER_TIMELINE(priv->fade_info), CLUTTER_TIMELINE_FORWARD);
209 clutter_timeline_set_direction(CLUTTER_TIMELINE(priv->fade_controls), CLUTTER_TIMELINE_FORWARD);
210 if (!clutter_timeline_is_playing(CLUTTER_TIMELINE(priv->fade_info))) {
211 clutter_timeline_rewind(CLUTTER_TIMELINE(priv->fade_info));
212 clutter_timeline_rewind(CLUTTER_TIMELINE(priv->fade_controls));
213 clutter_timeline_start(CLUTTER_TIMELINE(priv->fade_info));
214 clutter_timeline_start(CLUTTER_TIMELINE(priv->fade_controls));
215 }
216 }
217 }
218
219 return G_SOURCE_CONTINUE;
220}
221
222static gboolean
223mouse_moved(CurrentCallView *self)
224{
225 g_return_val_if_fail(IS_CURRENT_CALL_VIEW(self), FALSE);
226 auto priv = CURRENT_CALL_VIEW_GET_PRIVATE(self);
227
228 priv->time_last_mouse_motion = g_get_monotonic_time();
229
230 // since the mouse moved, make sure the controls are shown
231 if (clutter_timeline_get_direction(CLUTTER_TIMELINE(priv->fade_info)) == CLUTTER_TIMELINE_FORWARD) {
232 clutter_timeline_set_direction(CLUTTER_TIMELINE(priv->fade_info), CLUTTER_TIMELINE_BACKWARD);
233 clutter_timeline_set_direction(CLUTTER_TIMELINE(priv->fade_controls), CLUTTER_TIMELINE_BACKWARD);
234 if (!clutter_timeline_is_playing(CLUTTER_TIMELINE(priv->fade_info))) {
235 clutter_timeline_rewind(CLUTTER_TIMELINE(priv->fade_info));
236 clutter_timeline_rewind(CLUTTER_TIMELINE(priv->fade_controls));
237 clutter_timeline_start(CLUTTER_TIMELINE(priv->fade_info));
238 clutter_timeline_start(CLUTTER_TIMELINE(priv->fade_controls));
239 }
240 }
241
242 return FALSE; // propogate event
243}
244
245static ClutterTransition *
246create_fade_out_transition()
247{
248 auto transition = clutter_property_transition_new("opacity");
249 clutter_transition_set_from(transition, G_TYPE_UINT, 255);
250 clutter_transition_set_to(transition, G_TYPE_UINT, 0);
251 clutter_timeline_set_duration(CLUTTER_TIMELINE(transition), FADE_DURATION);
252 clutter_timeline_set_repeat_count(CLUTTER_TIMELINE(transition), 0);
253 clutter_timeline_set_progress_mode(CLUTTER_TIMELINE(transition), CLUTTER_EASE_IN_OUT_CUBIC);
254 return transition;
255}
256
Stepan Salenikovicha448f602015-05-29 13:33:06 -0400257static void
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500258current_call_view_init(CurrentCallView *view)
259{
260 gtk_widget_init_template(GTK_WIDGET(view));
Stepan Salenikovicha448f602015-05-29 13:33:06 -0400261
262 CurrentCallViewPrivate *priv = CURRENT_CALL_VIEW_GET_PRIVATE(view);
263
Stepan Salenikoviche178e632015-11-06 13:31:19 -0500264 /* create video widget and overlay the call info and controls on it */
265 priv->video_widget = video_widget_new();
266 gtk_container_add(GTK_CONTAINER(priv->frame_video), priv->video_widget);
267 gtk_widget_show_all(priv->frame_video);
268
269 auto stage = gtk_clutter_embed_get_stage(GTK_CLUTTER_EMBED(priv->video_widget));
270 auto actor_info = gtk_clutter_actor_new_with_contents(priv->hbox_call_info);
271 auto actor_controls = gtk_clutter_actor_new_with_contents(priv->hbox_call_controls);
272
273 clutter_actor_add_child(stage, actor_info);
274 clutter_actor_set_x_align(actor_info, CLUTTER_ACTOR_ALIGN_FILL);
275 clutter_actor_set_y_align(actor_info, CLUTTER_ACTOR_ALIGN_START);
276
277 clutter_actor_add_child(stage, actor_controls);
278 clutter_actor_set_x_align(actor_controls, CLUTTER_ACTOR_ALIGN_CENTER);
279 clutter_actor_set_y_align(actor_controls, CLUTTER_ACTOR_ALIGN_END);
280
Stepan Salenikovich0c7aa2a2015-11-06 17:00:08 -0500281 /* add fade in and out states to the info and controls */
282 priv->time_last_mouse_motion = g_get_monotonic_time();
283 priv->fade_info = create_fade_out_transition();
284 priv->fade_controls = create_fade_out_transition();
285 clutter_actor_add_transition(actor_info, "fade_info", priv->fade_info);
286 clutter_actor_add_transition(actor_controls, "fade_controls", priv->fade_controls);
287 clutter_timeline_set_direction(CLUTTER_TIMELINE(priv->fade_info), CLUTTER_TIMELINE_BACKWARD);
288 clutter_timeline_set_direction(CLUTTER_TIMELINE(priv->fade_controls), CLUTTER_TIMELINE_BACKWARD);
289 clutter_timeline_stop(CLUTTER_TIMELINE(priv->fade_info));
290 clutter_timeline_stop(CLUTTER_TIMELINE(priv->fade_controls));
291
292 /* have a timer check every 1 second if the controls should fade out */
293 priv->timer_fade = g_timeout_add(1000, (GSourceFunc)timeout_check_last_motion_event, view);
294
295 /* connect to the mouse motion event to reset the last moved time */
296 g_signal_connect_swapped(priv->video_widget, "motion-notify-event", G_CALLBACK(mouse_moved), view);
297 g_signal_connect_swapped(priv->video_widget, "button-press-event", G_CALLBACK(mouse_moved), view);
298 g_signal_connect_swapped(priv->video_widget, "button-release-event", G_CALLBACK(mouse_moved), view);
299
Stepan Salenikovicha448f602015-05-29 13:33:06 -0400300 g_signal_connect(priv->togglebutton_chat, "toggled", G_CALLBACK(chat_toggled), view);
301 g_signal_connect(priv->button_chat_input, "clicked", G_CALLBACK(send_chat), view);
302 g_signal_connect(priv->entry_chat_input, "activate", G_CALLBACK(send_chat), view);
303
304 /* the adjustment params will change only when the model is created and when
305 * new messages are added; in these cases we want to scroll to the bottom of
306 * the chat treeview */
307 GtkAdjustment *adjustment = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(priv->scrolledwindow_chat));
308 g_signal_connect(adjustment, "changed", G_CALLBACK(scroll_to_bottom), NULL);
Stepan Salenikovicha5e8e362015-11-05 16:50:48 -0500309
310 // bind the chat location to the gsetting
311 priv->settings = g_settings_new_full(get_ring_schema(), NULL, NULL);
312 g_settings_bind_with_mapping(priv->settings, "chat-pane-horizontal",
313 priv->paned_chat, "orientation",
314 G_SETTINGS_BIND_GET,
315 map_boolean_to_orientation,
316 nullptr, nullptr, nullptr);
317
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500318}
319
320static void
321current_call_view_class_init(CurrentCallViewClass *klass)
322{
323 G_OBJECT_CLASS(klass)->dispose = current_call_view_dispose;
324
325 gtk_widget_class_set_template_from_resource(GTK_WIDGET_CLASS (klass),
326 "/cx/ring/RingGnome/currentcallview.ui");
327
Stepan Salenikoviche178e632015-11-06 13:31:19 -0500328 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, hbox_call_info);
329 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, hbox_call_controls);
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500330 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, image_peer);
331 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, label_identity);
332 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, label_status);
333 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, label_duration);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500334 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, frame_video);
Stepan Salenikovicha5e8e362015-11-05 16:50:48 -0500335 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, paned_chat);
Stepan Salenikovicha5129f62015-11-05 15:10:59 -0500336 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, vbox_chat);
Stepan Salenikovicha448f602015-05-29 13:33:06 -0400337 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, togglebutton_chat);
338 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, textview_chat);
339 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, button_chat_input);
340 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, entry_chat_input);
341 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, scrolledwindow_chat);
Stepan Salenikovich77baa522015-07-07 15:29:14 -0400342 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), CurrentCallView, button_hangup);
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500343}
344
345GtkWidget *
346current_call_view_new(void)
347{
348 return (GtkWidget *)g_object_new(CURRENT_CALL_VIEW_TYPE, NULL);
349}
350
351static void
352update_state(CurrentCallView *view, Call *call)
353{
354 CurrentCallViewPrivate *priv = CURRENT_CALL_VIEW_GET_PRIVATE(view);
355
Stepan Salenikovich7ec8fe82015-06-02 18:26:39 -0400356 gchar *status = g_strdup_printf("%s", call->toHumanStateName().toUtf8().constData());
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500357
Stepan Salenikovich7ec8fe82015-06-02 18:26:39 -0400358 gtk_label_set_text(GTK_LABEL(priv->label_status), status);
359
360 g_free(status);
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500361}
362
363static void
364update_details(CurrentCallView *view, Call *call)
365{
366 CurrentCallViewPrivate *priv = CURRENT_CALL_VIEW_GET_PRIVATE(view);
367
368 /* update call duration */
369 QByteArray ba_length = call->length().toLocal8Bit();
370 gtk_label_set_text(GTK_LABEL(priv->label_duration), ba_length.constData());
371}
372
Stepan Salenikovichc5f08152015-03-19 00:53:23 -0400373static void
Stepan Salenikovichb94873c2015-06-02 16:53:18 -0400374on_fullscreen_destroy(CurrentCallView *view)
Stepan Salenikovichbfe9ac62015-03-11 12:49:20 -0400375{
376 g_return_if_fail(IS_CURRENT_CALL_VIEW(view));
377 CurrentCallViewPrivate *priv = CURRENT_CALL_VIEW_GET_PRIVATE(view);
378
Stepan Salenikovichb94873c2015-06-02 16:53:18 -0400379 /* fullscreen is being destroyed, clear the pointer and un-pause the rendering
380 * in this window */
381 priv->fullscreen_window = NULL;
382 video_widget_pause_rendering(VIDEO_WIDGET(priv->video_widget), FALSE);
Stepan Salenikovichbfe9ac62015-03-11 12:49:20 -0400383}
384
385static gboolean
386on_button_press_in_video_event(GtkWidget *self, GdkEventButton *event, CurrentCallView *view)
387{
388 g_return_val_if_fail(IS_VIDEO_WIDGET(self), FALSE);
389 g_return_val_if_fail(IS_CURRENT_CALL_VIEW(view), FALSE);
390 CurrentCallViewPrivate *priv = CURRENT_CALL_VIEW_GET_PRIVATE(view);
391
392 /* on double click */
393 if (event->type == GDK_2BUTTON_PRESS) {
Stepan Salenikovichb94873c2015-06-02 16:53:18 -0400394 if (priv->fullscreen_window) {
395 /* destroy the fullscreen */
396 gtk_widget_destroy(priv->fullscreen_window);
Stepan Salenikovichbfe9ac62015-03-11 12:49:20 -0400397 } else {
Stepan Salenikovichb94873c2015-06-02 16:53:18 -0400398 /* pause rendering in this window and create fullscreen */
399 video_widget_pause_rendering(VIDEO_WIDGET(priv->video_widget), TRUE);
400
401 priv->fullscreen_window = video_window_new(priv->call,
402 GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(view))));
403
404 /* connect to destruction of fullscreen so we know when to un-pause
405 * the rendering in thiw window */
406 g_signal_connect_swapped(priv->fullscreen_window,
407 "destroy",
408 G_CALLBACK(on_fullscreen_destroy),
409 view);
410
411 /* present the fullscreen widnow */
412 gtk_window_present(GTK_WINDOW(priv->fullscreen_window));
413 gtk_window_fullscreen(GTK_WINDOW(priv->fullscreen_window));
Stepan Salenikovichbfe9ac62015-03-11 12:49:20 -0400414 }
415 }
416
417 /* the event has been fully handled */
418 return TRUE;
419}
420
Stepan Salenikovicha448f602015-05-29 13:33:06 -0400421static void
422print_message_to_buffer(const QModelIndex &idx, GtkTextBuffer *buffer)
423{
424 if (idx.isValid()) {
Stepan Salenikovichdeae6252015-06-22 12:51:52 -0400425 auto message = idx.data().value<QString>().toUtf8();
426 auto sender = idx.data(static_cast<int>(Media::TextRecording::Role::AuthorDisplayname)).value<QString>().toUtf8();
Stepan Salenikovicha448f602015-05-29 13:33:06 -0400427
428 GtkTextIter iter;
Stepan Salenikovicha448f602015-05-29 13:33:06 -0400429
Stepan Salenikovichdeae6252015-06-22 12:51:52 -0400430 /* unless its the very first message, insert a new line */
431 if (idx.row() != 0) {
432 gtk_text_buffer_get_end_iter(buffer, &iter);
433 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
434 }
435
436 auto format_sender = g_strconcat(sender.constData(), ": ", NULL);
437 gtk_text_buffer_get_end_iter(buffer, &iter);
438 gtk_text_buffer_insert_with_tags_by_name(buffer, &iter,
439 format_sender, -1,
440 "bold", NULL);
441 g_free(format_sender);
442
443 /* if the sender name is too long, insert a new line after it */
444 if (sender.length() > 20) {
445 gtk_text_buffer_get_end_iter(buffer, &iter);
446 gtk_text_buffer_insert(buffer, &iter, "\n", -1);
447 }
448
449 gtk_text_buffer_get_end_iter(buffer, &iter);
450 gtk_text_buffer_insert(buffer, &iter, message.constData(), -1);
451
Stepan Salenikovicha448f602015-05-29 13:33:06 -0400452 } else {
453 g_warning("QModelIndex in im model is not valid");
454 }
455}
456
457static void
458parse_chat_model(QAbstractItemModel *model, CurrentCallView *self)
459{
460 g_return_if_fail(IS_CURRENT_CALL_VIEW(self));
461 CurrentCallViewPrivate *priv = CURRENT_CALL_VIEW_GET_PRIVATE(self);
462
463 /* new model, disconnect from the old model updates and clear the text buffer */
464 QObject::disconnect(priv->new_message_connection);
465
466 GtkTextBuffer *new_buffer = gtk_text_buffer_new(NULL);
467 gtk_text_view_set_buffer(GTK_TEXT_VIEW(priv->textview_chat), new_buffer);
Stepan Salenikovichdeae6252015-06-22 12:51:52 -0400468
469 /* add tags to the buffer */
470 gtk_text_buffer_create_tag(new_buffer, "bold", "weight", PANGO_WEIGHT_BOLD, NULL);
471
Stepan Salenikovicha448f602015-05-29 13:33:06 -0400472 g_object_unref(new_buffer);
473
474 /* put all the messages in the im model into the text view */
475 for (int row = 0; row < model->rowCount(); ++row) {
476 QModelIndex idx = model->index(row, 0);
477 print_message_to_buffer(idx, new_buffer);
478 }
479
480 /* append new messages */
481 priv->new_message_connection = QObject::connect(
482 model,
483 &QAbstractItemModel::rowsInserted,
484 [priv, model] (const QModelIndex &parent, int first, int last) {
485 for (int row = first; row <= last; ++row) {
486 QModelIndex idx = model->index(row, 0, parent);
487 print_message_to_buffer(idx, gtk_text_view_get_buffer(GTK_TEXT_VIEW(priv->textview_chat)));
488 }
489 }
490 );
491}
Stepan Salenikovichbfe9ac62015-03-11 12:49:20 -0400492
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500493void
Stepan Salenikovich7b60b592015-06-16 12:29:07 -0400494monitor_incoming_message(CurrentCallView *self, Media::Text *media)
495{
496 g_return_if_fail(IS_CURRENT_CALL_VIEW(self));
497 CurrentCallViewPrivate *priv = CURRENT_CALL_VIEW_GET_PRIVATE(self);
498
499 /* connect to incoming chat messages to open the chat view */
500 QObject::disconnect(priv->incoming_msg_connection);
501 priv->incoming_msg_connection = QObject::connect(
502 media,
503 &Media::Text::messageReceived,
Stepan Salenikovich6af88db2015-07-17 12:41:29 -0400504 [priv] (G_GNUC_UNUSED const QMap<QString,QString>& m) {
Stepan Salenikovich7b60b592015-06-16 12:29:07 -0400505 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->togglebutton_chat), TRUE);
506 }
507 );
508}
509
510void
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500511current_call_view_set_call_info(CurrentCallView *view, const QModelIndex& idx) {
512 CurrentCallViewPrivate *priv = CURRENT_CALL_VIEW_GET_PRIVATE(view);
513
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400514 priv->call = CallModel::instance().getCall(idx);
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400515
516 /* get call image */
Stepan Salenikovichbbd6c132015-08-20 15:21:48 -0400517 QVariant var_i = GlobalInstances::pixmapManipulator().callPhoto(priv->call, QSize(60, 60), false);
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400518 std::shared_ptr<GdkPixbuf> image = var_i.value<std::shared_ptr<GdkPixbuf>>();
519 gtk_image_set_from_pixbuf(GTK_IMAGE(priv->image_peer), image.get());
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500520
521 /* get name */
Stepan Salenikovich9c1f6682015-03-09 16:21:28 -0400522 QVariant var = idx.model()->data(idx, static_cast<int>(Call::Role::Name));
Stepan Salenikovich6f687072015-03-26 10:43:37 -0400523 QByteArray ba_name = var.toString().toUtf8();
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500524 gtk_label_set_text(GTK_LABEL(priv->label_identity), ba_name.constData());
525
526 /* change some things depending on call state */
Stepan Salenikovicha448f602015-05-29 13:33:06 -0400527 update_state(view, priv->call);
528 update_details(view, priv->call);
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500529
530 priv->state_change_connection = QObject::connect(
Stepan Salenikovicha448f602015-05-29 13:33:06 -0400531 priv->call,
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500532 &Call::stateChanged,
Stepan Salenikovicha448f602015-05-29 13:33:06 -0400533 [view, priv]() { update_state(view, priv->call); }
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500534 );
535
536 priv->call_details_connection = QObject::connect(
Stepan Salenikovicha448f602015-05-29 13:33:06 -0400537 priv->call,
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500538 static_cast<void (Call::*)(void)>(&Call::changed),
Stepan Salenikovicha448f602015-05-29 13:33:06 -0400539 [view, priv]() { update_details(view, priv->call); }
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500540 );
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500541
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500542 /* check if we already have a renderer */
Stepan Salenikovich0f693232015-04-22 10:45:08 -0400543 video_widget_push_new_renderer(VIDEO_WIDGET(priv->video_widget),
Stepan Salenikovicha448f602015-05-29 13:33:06 -0400544 priv->call->videoRenderer(),
Stepan Salenikovich0f693232015-04-22 10:45:08 -0400545 VIDEO_RENDERER_REMOTE);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500546
Stepan Salenikovich8e5c9d02015-03-11 14:07:10 -0400547 /* callback for remote renderer */
Stepan Salenikovichc5f08152015-03-19 00:53:23 -0400548 priv->remote_renderer_connection = QObject::connect(
Stepan Salenikovicha448f602015-05-29 13:33:06 -0400549 priv->call,
Stepan Salenikovich9c1f6682015-03-09 16:21:28 -0400550 &Call::videoStarted,
Stepan Salenikovicha448f602015-05-29 13:33:06 -0400551 [priv](Video::Renderer *renderer) {
Stepan Salenikovich0f693232015-04-22 10:45:08 -0400552 video_widget_push_new_renderer(VIDEO_WIDGET(priv->video_widget),
553 renderer,
554 VIDEO_RENDERER_REMOTE);
Stepan Salenikovich36c025c2015-03-03 19:06:44 -0500555 }
556 );
Stepan Salenikovich4ac89f12015-03-10 16:48:47 -0400557
Stepan Salenikovich8e5c9d02015-03-11 14:07:10 -0400558 /* local renderer */
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400559 if (Video::PreviewManager::instance().isPreviewing())
Stepan Salenikovich0f693232015-04-22 10:45:08 -0400560 video_widget_push_new_renderer(VIDEO_WIDGET(priv->video_widget),
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400561 Video::PreviewManager::instance().previewRenderer(),
Stepan Salenikovich0f693232015-04-22 10:45:08 -0400562 VIDEO_RENDERER_LOCAL);
Stepan Salenikovich8e5c9d02015-03-11 14:07:10 -0400563
564 /* callback for local renderer */
Stepan Salenikovichc5f08152015-03-19 00:53:23 -0400565 priv->local_renderer_connection = QObject::connect(
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400566 &Video::PreviewManager::instance(),
Stepan Salenikovich8e5c9d02015-03-11 14:07:10 -0400567 &Video::PreviewManager::previewStarted,
Stepan Salenikovicha448f602015-05-29 13:33:06 -0400568 [priv](Video::Renderer *renderer) {
Stepan Salenikovich0f693232015-04-22 10:45:08 -0400569 video_widget_push_new_renderer(VIDEO_WIDGET(priv->video_widget),
570 renderer,
571 VIDEO_RENDERER_LOCAL);
Stepan Salenikovich8e5c9d02015-03-11 14:07:10 -0400572 }
573 );
Stepan Salenikovichbfe9ac62015-03-11 12:49:20 -0400574
575 /* catch double click to make full screen */
576 g_signal_connect(priv->video_widget, "button-press-event",
577 G_CALLBACK(on_button_press_in_video_event),
578 view);
Stepan Salenikovicha448f602015-05-29 13:33:06 -0400579
580 /* check if text media is already present */
581 if (priv->call->hasMedia(Media::Media::Type::TEXT, Media::Media::Direction::IN)) {
582 Media::Text *text = priv->call->firstMedia<Media::Text>(Media::Media::Direction::IN);
583 parse_chat_model(text->recording()->instantMessagingModel(), view);
Stepan Salenikovich7b60b592015-06-16 12:29:07 -0400584 monitor_incoming_message(view, text);
Stepan Salenikovicha448f602015-05-29 13:33:06 -0400585 } else if (priv->call->hasMedia(Media::Media::Type::TEXT, Media::Media::Direction::OUT)) {
586 Media::Text *text = priv->call->firstMedia<Media::Text>(Media::Media::Direction::OUT);
587 parse_chat_model(text->recording()->instantMessagingModel(), view);
Stepan Salenikovich7b60b592015-06-16 12:29:07 -0400588 monitor_incoming_message(view, text);
Stepan Salenikovicha448f602015-05-29 13:33:06 -0400589 } else {
590 /* monitor media for messaging text messaging */
591 priv->media_added_connection = QObject::connect(
592 priv->call,
593 &Call::mediaAdded,
594 [view, priv] (Media::Media* media) {
595 if (media->type() == Media::Media::Type::TEXT) {
596 parse_chat_model(((Media::Text*)media)->recording()->instantMessagingModel(), view);
Stepan Salenikovich7b60b592015-06-16 12:29:07 -0400597 monitor_incoming_message(view, (Media::Text*)media);
Stepan Salenikovicha448f602015-05-29 13:33:06 -0400598 QObject::disconnect(priv->media_added_connection);
599 }
600 }
601 );
602 }
Stepan Salenikovich67112d12015-06-16 16:57:06 -0400603
604 /* check if there were any chat notifications and open the chat view if so */
605 if (ring_notify_close_chat_notification(priv->call))
606 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->togglebutton_chat), TRUE);
Stepan Salenikovichc64523b2015-02-27 16:31:00 -0500607}