Chat window's tab label changes the font color when got an incoming msg. (Fixes ...
[empathy.git] / src / empathy-call-window.c
1 /*
2  * empathy-call-window.c - Source for EmpathyCallWindow
3  * Copyright (C) 2008-2009 Collabora Ltd.
4  * @author Sjoerd Simons <sjoerd.simons@collabora.co.uk>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19  */
20
21
22 #include <stdio.h>
23 #include <stdlib.h>
24
25 #include <math.h>
26
27 #include <gdk/gdkkeysyms.h>
28 #include <gst/gst.h>
29 #include <gtk/gtk.h>
30 #include <glib/gi18n.h>
31
32 #include <telepathy-farsight/channel.h>
33
34 #include <libempathy/empathy-tp-contact-factory.h>
35 #include <libempathy/empathy-call-factory.h>
36 #include <libempathy/empathy-utils.h>
37 #include <libempathy-gtk/empathy-avatar-image.h>
38 #include <libempathy-gtk/empathy-video-widget.h>
39 #include <libempathy-gtk/empathy-audio-src.h>
40 #include <libempathy-gtk/empathy-audio-sink.h>
41 #include <libempathy-gtk/empathy-video-src.h>
42 #include <libempathy-gtk/empathy-ui-utils.h>
43 #include <libempathy-gtk/empathy-sound.h>
44
45 #include "empathy-call-window.h"
46 #include "empathy-call-window-fullscreen.h"
47 #include "empathy-sidebar.h"
48
49 #define BUTTON_ID "empathy-call-dtmf-button-id"
50
51 #define CONTENT_HBOX_BORDER_WIDTH 6
52 #define CONTENT_HBOX_SPACING 3
53 #define CONTENT_HBOX_CHILDREN_PACKING_PADDING 3
54
55 #define SELF_VIDEO_SECTION_WIDTH 160
56 #define SELF_VIDEO_SECTION_HEIGTH 120
57
58 /* The avatar's default width and height are set to the same value because we
59    want a square icon. */
60 #define REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH EMPATHY_VIDEO_WIDGET_DEFAULT_HEIGHT
61 #define REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT EMPATHY_VIDEO_WIDGET_DEFAULT_HEIGHT
62
63 /* If an video input error occurs, the error message will start with "v4l" */
64 #define VIDEO_INPUT_ERROR_PREFIX "v4l"
65
66 /* The time interval in milliseconds between 2 outgoing rings */
67 #define MS_BETWEEN_RING 500
68
69 G_DEFINE_TYPE(EmpathyCallWindow, empathy_call_window, GTK_TYPE_WINDOW)
70
71 /* signal enum */
72 #if 0
73 enum
74 {
75     LAST_SIGNAL
76 };
77
78 static guint signals[LAST_SIGNAL] = {0};
79 #endif
80
81 enum {
82   PROP_CALL_HANDLER = 1,
83 };
84
85 typedef enum {
86   CONNECTING,
87   CONNECTED,
88   DISCONNECTED,
89   REDIALING
90 } CallState;
91
92 /* private structure */
93 typedef struct _EmpathyCallWindowPriv EmpathyCallWindowPriv;
94
95 struct _EmpathyCallWindowPriv
96 {
97   gboolean dispose_has_run;
98   EmpathyCallHandler *handler;
99   EmpathyContact *contact;
100
101   guint call_state;
102   gboolean outgoing;
103
104   GtkUIManager *ui_manager;
105   GtkWidget *video_output;
106   GtkWidget *video_preview;
107   GtkWidget *remote_user_avatar_widget;
108   GtkWidget *self_user_avatar_widget;
109   GtkWidget *sidebar;
110   GtkWidget *sidebar_button;
111   GtkWidget *statusbar;
112   GtkWidget *volume_button;
113   GtkWidget *redial_button;
114   GtkWidget *mic_button;
115   GtkWidget *camera_button;
116   GtkWidget *toolbar;
117   GtkWidget *pane;
118   GtkAction *show_preview;
119   GtkAction *send_video;
120   GtkAction *redial;
121   GtkAction *menu_fullscreen;
122
123   /* The frames and boxes that contain self and remote avatar and video
124      input/output. When we redial, we destroy and re-create the boxes */
125   GtkWidget *remote_user_output_frame;
126   GtkWidget *self_user_output_frame;
127   GtkWidget *remote_user_output_hbox;
128   GtkWidget *self_user_output_hbox;
129
130   /* We keep a reference on the hbox which contains the main content so we can
131      easilly repack everything when toggling fullscreen */
132   GtkWidget *content_hbox;
133
134   /* This vbox is contained in the content_hbox and it contains the
135      self_user_output_frame and the sidebar button. When toggling fullscreen,
136      it needs to be repacked. We keep a reference on it for easier access. */
137   GtkWidget *vbox;
138
139   gulong video_output_motion_handler_id;
140
141   gdouble volume;
142   GtkWidget *volume_progress_bar;
143   GtkAdjustment *audio_input_adj;
144
145   GtkWidget *dtmf_panel;
146
147   GstElement *video_input;
148   GstElement *audio_input;
149   GstElement *audio_output;
150   GstElement *pipeline;
151   GstElement *video_tee;
152
153   GstElement *funnel;
154   GstElement *liveadder;
155
156   guint context_id;
157
158   GTimer *timer;
159   guint timer_id;
160
161   GtkWidget *video_contrast;
162   GtkWidget *video_brightness;
163   GtkWidget *video_gamma;
164
165   GMutex *lock;
166   gboolean call_started;
167   gboolean sending_video;
168
169   EmpathyCallWindowFullscreen *fullscreen;
170   gboolean is_fullscreen;
171
172   /* Those fields represent the state of the window before it actually was in
173      fullscreen mode. */
174   gboolean sidebar_was_visible_before_fs;
175   gint original_width_before_fs;
176   gint original_height_before_fs;
177 };
178
179 #define GET_PRIV(o) \
180   (G_TYPE_INSTANCE_GET_PRIVATE ((o), EMPATHY_TYPE_CALL_WINDOW, \
181     EmpathyCallWindowPriv))
182
183 static void empathy_call_window_realized_cb (GtkWidget *widget,
184   EmpathyCallWindow *window);
185
186 static gboolean empathy_call_window_delete_cb (GtkWidget *widget,
187   GdkEvent *event, EmpathyCallWindow *window);
188
189 static gboolean empathy_call_window_state_event_cb (GtkWidget *widget,
190   GdkEventWindowState *event, EmpathyCallWindow *window);
191
192 static void empathy_call_window_sidebar_toggled_cb (GtkToggleButton *toggle,
193   EmpathyCallWindow *window);
194
195 static void empathy_call_window_camera_toggled_cb (GtkToggleToolButton *toggle,
196   EmpathyCallWindow *window);
197
198 static void empathy_call_window_set_send_video (EmpathyCallWindow *window,
199   gboolean send);
200
201 static void empathy_call_window_send_video_toggled_cb (GtkToggleAction *toggle,
202   EmpathyCallWindow *window);
203
204 static void empathy_call_window_show_preview_toggled_cb (GtkToggleAction *toggle,
205   EmpathyCallWindow *window);
206
207 static void empathy_call_window_mic_toggled_cb (
208   GtkToggleToolButton *toggle, EmpathyCallWindow *window);
209
210 static void empathy_call_window_sidebar_hidden_cb (EmpathySidebar *sidebar,
211   EmpathyCallWindow *window);
212
213 static void empathy_call_window_sidebar_shown_cb (EmpathySidebar *sidebar,
214   EmpathyCallWindow *window);
215
216 static void empathy_call_window_hangup_cb (gpointer object,
217   EmpathyCallWindow *window);
218
219 static void empathy_call_window_fullscreen_cb (gpointer object,
220   EmpathyCallWindow *window);
221
222 static void empathy_call_window_fullscreen_toggle (EmpathyCallWindow *window);
223
224 static gboolean empathy_call_window_video_button_press_cb (GtkWidget *video_output,
225   GdkEventButton *event, EmpathyCallWindow *window);
226
227 static gboolean empathy_call_window_key_press_cb (GtkWidget *video_output,
228   GdkEventKey *event, EmpathyCallWindow *window);
229
230 static gboolean empathy_call_window_video_output_motion_notify (GtkWidget *widget,
231   GdkEventMotion *event, EmpathyCallWindow *window);
232
233 static void empathy_call_window_video_menu_popup (EmpathyCallWindow *window,
234   guint button);
235
236 static void empathy_call_window_redial_cb (gpointer object,
237   EmpathyCallWindow *window);
238
239 static void empathy_call_window_restart_call (EmpathyCallWindow *window);
240
241 static void empathy_call_window_status_message (EmpathyCallWindow *window,
242   gchar *message);
243
244 static void empathy_call_window_update_avatars_visibility (EmpathyTpCall *call,
245   EmpathyCallWindow *window);
246
247 static gboolean empathy_call_window_bus_message (GstBus *bus,
248   GstMessage *message, gpointer user_data);
249
250 static void
251 empathy_call_window_volume_changed_cb (GtkScaleButton *button,
252   gdouble value, EmpathyCallWindow *window);
253
254 static void
255 empathy_call_window_setup_toolbar (EmpathyCallWindow *self)
256 {
257   EmpathyCallWindowPriv *priv = GET_PRIV (self);
258   GtkToolItem *tool_item;
259
260   /* Add an empty expanded GtkToolItem so the volume button is at the end of
261    * the toolbar. */
262   tool_item = gtk_tool_item_new ();
263   gtk_tool_item_set_expand (tool_item, TRUE);
264   gtk_widget_show (GTK_WIDGET (tool_item));
265   gtk_toolbar_insert (GTK_TOOLBAR (priv->toolbar), tool_item, -1);
266
267   priv->volume_button = gtk_volume_button_new ();
268   /* FIXME listen to the audiosinks signals and update the button according to
269    * that, for now starting out at 1.0 and assuming only the app changes the
270    * volume will do */
271   gtk_scale_button_set_value (GTK_SCALE_BUTTON (priv->volume_button), 1.0);
272   g_signal_connect (G_OBJECT (priv->volume_button), "value-changed",
273     G_CALLBACK (empathy_call_window_volume_changed_cb), self);
274
275   tool_item = gtk_tool_item_new ();
276   gtk_container_add (GTK_CONTAINER (tool_item), priv->volume_button);
277   gtk_widget_show_all (GTK_WIDGET (tool_item));
278   gtk_toolbar_insert (GTK_TOOLBAR (priv->toolbar), tool_item, -1);
279 }
280
281 static void
282 dtmf_button_pressed_cb (GtkButton *button, EmpathyCallWindow *window)
283 {
284   EmpathyCallWindowPriv *priv = GET_PRIV (window);
285   EmpathyTpCall *call;
286   GQuark button_quark;
287   TpDTMFEvent event;
288
289   g_object_get (priv->handler, "tp-call", &call, NULL);
290
291   button_quark = g_quark_from_static_string (BUTTON_ID);
292   event = GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (button),
293     button_quark));
294
295   empathy_tp_call_start_tone (call, event);
296
297   g_object_unref (call);
298 }
299
300 static void
301 dtmf_button_released_cb (GtkButton *button, EmpathyCallWindow *window)
302 {
303   EmpathyCallWindowPriv *priv = GET_PRIV (window);
304   EmpathyTpCall *call;
305
306   g_object_get (priv->handler, "tp-call", &call, NULL);
307
308   empathy_tp_call_stop_tone (call);
309
310   g_object_unref (call);
311 }
312
313 static GtkWidget *
314 empathy_call_window_create_dtmf (EmpathyCallWindow *self)
315 {
316   GtkWidget *table;
317   int i;
318   GQuark button_quark;
319   struct {
320     gchar *label;
321     TpDTMFEvent event;
322   } dtmfbuttons[] = { { "1", TP_DTMF_EVENT_DIGIT_1 },
323                       { "2", TP_DTMF_EVENT_DIGIT_2 },
324                       { "3", TP_DTMF_EVENT_DIGIT_3 },
325                       { "4", TP_DTMF_EVENT_DIGIT_4 },
326                       { "5", TP_DTMF_EVENT_DIGIT_5 },
327                       { "6", TP_DTMF_EVENT_DIGIT_6 },
328                       { "7", TP_DTMF_EVENT_DIGIT_7 },
329                       { "8", TP_DTMF_EVENT_DIGIT_8 },
330                       { "9", TP_DTMF_EVENT_DIGIT_9 },
331                       { "#", TP_DTMF_EVENT_HASH },
332                       { "0", TP_DTMF_EVENT_DIGIT_0 },
333                       { "*", TP_DTMF_EVENT_ASTERISK },
334                       { NULL, } };
335
336   button_quark = g_quark_from_static_string (BUTTON_ID);
337
338   table = gtk_table_new (4, 3, TRUE);
339
340   for (i = 0; dtmfbuttons[i].label != NULL; i++)
341     {
342       GtkWidget *button = gtk_button_new_with_label (dtmfbuttons[i].label);
343       gtk_table_attach (GTK_TABLE (table), button, i % 3, i % 3 + 1,
344         i/3, i/3 + 1, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 1, 1);
345
346       g_object_set_qdata (G_OBJECT (button), button_quark,
347         GUINT_TO_POINTER (dtmfbuttons[i].event));
348
349       g_signal_connect (G_OBJECT (button), "pressed",
350         G_CALLBACK (dtmf_button_pressed_cb), self);
351       g_signal_connect (G_OBJECT (button), "released",
352         G_CALLBACK (dtmf_button_released_cb), self);
353     }
354
355   return table;
356 }
357
358 static GtkWidget *
359 empathy_call_window_create_video_input_add_slider (EmpathyCallWindow *self,
360   gchar *label_text, GtkWidget *bin)
361 {
362    GtkWidget *vbox = gtk_vbox_new (FALSE, 2);
363    GtkWidget *scale = gtk_vscale_new_with_range (0, 100, 10);
364    GtkWidget *label = gtk_label_new (label_text);
365
366    gtk_widget_set_sensitive (scale, FALSE);
367
368    gtk_container_add (GTK_CONTAINER (bin), vbox);
369
370    gtk_range_set_inverted (GTK_RANGE (scale), TRUE);
371    gtk_box_pack_start (GTK_BOX (vbox), scale, TRUE, TRUE, 0);
372    gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);
373
374    return scale;
375 }
376
377 static void
378 empathy_call_window_video_contrast_changed_cb (GtkAdjustment *adj,
379   EmpathyCallWindow *self)
380
381 {
382   EmpathyCallWindowPriv *priv = GET_PRIV (self);
383
384   empathy_video_src_set_channel (priv->video_input,
385     EMPATHY_GST_VIDEO_SRC_CHANNEL_CONTRAST, gtk_adjustment_get_value (adj));
386 }
387
388 static void
389 empathy_call_window_video_brightness_changed_cb (GtkAdjustment *adj,
390   EmpathyCallWindow *self)
391
392 {
393   EmpathyCallWindowPriv *priv = GET_PRIV (self);
394
395   empathy_video_src_set_channel (priv->video_input,
396     EMPATHY_GST_VIDEO_SRC_CHANNEL_BRIGHTNESS, gtk_adjustment_get_value (adj));
397 }
398
399 static void
400 empathy_call_window_video_gamma_changed_cb (GtkAdjustment *adj,
401   EmpathyCallWindow *self)
402
403 {
404   EmpathyCallWindowPriv *priv = GET_PRIV (self);
405
406   empathy_video_src_set_channel (priv->video_input,
407     EMPATHY_GST_VIDEO_SRC_CHANNEL_GAMMA, gtk_adjustment_get_value (adj));
408 }
409
410
411 static GtkWidget *
412 empathy_call_window_create_video_input (EmpathyCallWindow *self)
413 {
414   EmpathyCallWindowPriv *priv = GET_PRIV (self);
415   GtkWidget *hbox;
416
417   hbox = gtk_hbox_new (TRUE, 3);
418
419   priv->video_contrast = empathy_call_window_create_video_input_add_slider (
420     self,  _("Contrast"), hbox);
421
422   priv->video_brightness = empathy_call_window_create_video_input_add_slider (
423     self,  _("Brightness"), hbox);
424
425   priv->video_gamma = empathy_call_window_create_video_input_add_slider (
426     self,  _("Gamma"), hbox);
427
428   return hbox;
429 }
430
431 static void
432 empathy_call_window_setup_video_input (EmpathyCallWindow *self)
433 {
434   EmpathyCallWindowPriv *priv = GET_PRIV (self);
435   guint supported;
436   GtkAdjustment *adj;
437
438   supported = empathy_video_src_get_supported_channels (priv->video_input);
439
440   if (supported & EMPATHY_GST_VIDEO_SRC_SUPPORTS_CONTRAST)
441     {
442       adj = gtk_range_get_adjustment (GTK_RANGE (priv->video_contrast));
443
444       gtk_adjustment_set_value (adj,
445         empathy_video_src_get_channel (priv->video_input,
446           EMPATHY_GST_VIDEO_SRC_CHANNEL_CONTRAST));
447
448       g_signal_connect (G_OBJECT (adj), "value-changed",
449         G_CALLBACK (empathy_call_window_video_contrast_changed_cb), self);
450
451       gtk_widget_set_sensitive (priv->video_contrast, TRUE);
452     }
453
454   if (supported & EMPATHY_GST_VIDEO_SRC_SUPPORTS_BRIGHTNESS)
455     {
456       adj = gtk_range_get_adjustment (GTK_RANGE (priv->video_brightness));
457
458       gtk_adjustment_set_value (adj,
459         empathy_video_src_get_channel (priv->video_input,
460           EMPATHY_GST_VIDEO_SRC_CHANNEL_BRIGHTNESS));
461
462       g_signal_connect (G_OBJECT (adj), "value-changed",
463         G_CALLBACK (empathy_call_window_video_brightness_changed_cb), self);
464       gtk_widget_set_sensitive (priv->video_brightness, TRUE);
465     }
466
467   if (supported & EMPATHY_GST_VIDEO_SRC_SUPPORTS_GAMMA)
468     {
469       adj = gtk_range_get_adjustment (GTK_RANGE (priv->video_gamma));
470
471       gtk_adjustment_set_value (adj,
472         empathy_video_src_get_channel (priv->video_input,
473           EMPATHY_GST_VIDEO_SRC_CHANNEL_GAMMA));
474
475       g_signal_connect (G_OBJECT (adj), "value-changed",
476         G_CALLBACK (empathy_call_window_video_gamma_changed_cb), self);
477       gtk_widget_set_sensitive (priv->video_gamma, TRUE);
478     }
479 }
480
481 static void
482 empathy_call_window_mic_volume_changed_cb (GtkAdjustment *adj,
483   EmpathyCallWindow *self)
484 {
485   EmpathyCallWindowPriv *priv = GET_PRIV (self);
486   gdouble volume;
487
488   volume =  gtk_adjustment_get_value (adj)/100.0;
489
490   /* Don't store the volume because of muting */
491   if (volume > 0 || gtk_toggle_tool_button_get_active (
492         GTK_TOGGLE_TOOL_BUTTON (priv->mic_button)))
493     priv->volume = volume;
494
495   /* Ensure that the toggle button is active if the volume is > 0 and inactive
496    * if it's smaller than 0 */
497   if ((volume > 0) != gtk_toggle_tool_button_get_active (
498         GTK_TOGGLE_TOOL_BUTTON (priv->mic_button)))
499     gtk_toggle_tool_button_set_active (
500       GTK_TOGGLE_TOOL_BUTTON (priv->mic_button), volume > 0);
501
502   empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
503     volume);
504 }
505
506 static void
507 empathy_call_window_audio_input_level_changed_cb (EmpathyGstAudioSrc *src,
508   gdouble level, EmpathyCallWindow *window)
509 {
510   gdouble value;
511   EmpathyCallWindowPriv *priv = GET_PRIV (window);
512
513   value = CLAMP (pow (10, level / 20), 0.0, 1.0);
514   gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (priv->volume_progress_bar), value);
515 }
516
517 static GtkWidget *
518 empathy_call_window_create_audio_input (EmpathyCallWindow *self)
519 {
520   EmpathyCallWindowPriv *priv = GET_PRIV (self);
521   GtkWidget *hbox, *vbox, *scale, *label;
522   GtkAdjustment *adj;
523
524   hbox = gtk_hbox_new (TRUE, 3);
525
526   vbox = gtk_vbox_new (FALSE, 3);
527   gtk_box_pack_start (GTK_BOX (hbox), vbox, FALSE, FALSE, 3);
528
529   scale = gtk_vscale_new_with_range (0, 150, 100);
530   gtk_range_set_inverted (GTK_RANGE (scale), TRUE);
531   label = gtk_label_new (_("Volume"));
532
533   priv->audio_input_adj = adj = gtk_range_get_adjustment (GTK_RANGE (scale));
534   priv->volume =  empathy_audio_src_get_volume (EMPATHY_GST_AUDIO_SRC
535     (priv->audio_input));
536   gtk_adjustment_set_value (adj, priv->volume * 100);
537
538   g_signal_connect (G_OBJECT (adj), "value-changed",
539     G_CALLBACK (empathy_call_window_mic_volume_changed_cb), self);
540
541   gtk_box_pack_start (GTK_BOX (vbox), scale, TRUE, TRUE, 3);
542   gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 3);
543
544   priv->volume_progress_bar = gtk_progress_bar_new ();
545   gtk_progress_bar_set_orientation (
546       GTK_PROGRESS_BAR (priv->volume_progress_bar), GTK_PROGRESS_BOTTOM_TO_TOP);
547   gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (priv->volume_progress_bar),
548       0);
549
550   gtk_box_pack_start (GTK_BOX (hbox), priv->volume_progress_bar, FALSE, FALSE,
551       3);
552
553   return hbox;
554 }
555
556 static void
557 empathy_call_window_setup_remote_frame (GstBus *bus, EmpathyCallWindow *self)
558 {
559   EmpathyCallWindowPriv *priv = GET_PRIV (self);
560
561   /* Initializing all the content (UI and output gst elements) related to the
562      remote contact */
563   priv->remote_user_output_hbox = gtk_hbox_new (FALSE, 0);
564
565   priv->remote_user_avatar_widget = gtk_image_new ();
566   gtk_box_pack_start (GTK_BOX (priv->remote_user_output_hbox),
567       priv->remote_user_avatar_widget, TRUE, TRUE, 0);
568
569   priv->video_output = empathy_video_widget_new (bus);
570   gtk_box_pack_start (GTK_BOX (priv->remote_user_output_hbox),
571       priv->video_output, TRUE, TRUE, 0);
572
573   gtk_widget_add_events (priv->video_output,
574       GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK);
575   g_signal_connect (G_OBJECT (priv->video_output), "button-press-event",
576       G_CALLBACK (empathy_call_window_video_button_press_cb), self);
577
578   gtk_container_add (GTK_CONTAINER (priv->remote_user_output_frame),
579       priv->remote_user_output_hbox);
580
581   priv->audio_output = empathy_audio_sink_new ();
582   gst_object_ref (priv->audio_output);
583   gst_object_sink (priv->audio_output);
584 }
585
586 static void
587 empathy_call_window_setup_self_frame (GstBus *bus, EmpathyCallWindow *self)
588 {
589   EmpathyCallWindowPriv *priv = GET_PRIV (self);
590
591   /* Initializing all the content (UI and input gst elements) related to the
592      self contact, except for the video preview widget. This widget is only
593      initialized when the "show video preview" option is activated */
594   priv->self_user_output_hbox = gtk_hbox_new (FALSE, 0);
595
596   priv->self_user_avatar_widget = gtk_image_new ();
597   gtk_box_pack_start (GTK_BOX (priv->self_user_output_hbox),
598       priv->self_user_avatar_widget, TRUE, TRUE, 0);
599
600   gtk_container_add (GTK_CONTAINER (priv->self_user_output_frame),
601       priv->self_user_output_hbox);
602
603   priv->video_input = empathy_video_src_new ();
604   gst_object_ref (priv->video_input);
605   gst_object_sink (priv->video_input);
606
607   priv->audio_input = empathy_audio_src_new ();
608   gst_object_ref (priv->audio_input);
609   gst_object_sink (priv->audio_input);
610
611   g_signal_connect (priv->audio_input, "peak-level-changed",
612     G_CALLBACK (empathy_call_window_audio_input_level_changed_cb), self);
613 }
614
615 static void
616 empathy_call_window_setup_video_preview (EmpathyCallWindow *window)
617 {
618   EmpathyCallWindowPriv *priv = GET_PRIV (window);
619   GstElement *preview;
620   GstBus *bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
621
622   if (priv->video_preview != NULL)
623     {
624       /* Since the video preview and the video tee are initialized and freed
625          at the same time, if one is initialized, then the other one should
626          be too. */
627       g_assert (priv->video_tee != NULL);
628       return;
629     }
630
631   g_assert (priv->video_tee == NULL);
632
633   priv->video_tee = gst_element_factory_make ("tee", NULL);
634   gst_object_ref (priv->video_tee);
635   gst_object_sink (priv->video_tee);
636
637   priv->video_preview = empathy_video_widget_new_with_size (bus,
638       SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH);
639   g_object_set (priv->video_preview, "sync", FALSE, "async", TRUE, NULL);
640   gtk_box_pack_start (GTK_BOX (priv->self_user_output_hbox),
641       priv->video_preview, TRUE, TRUE, 0);
642
643   preview = empathy_video_widget_get_element (
644       EMPATHY_VIDEO_WIDGET (priv->video_preview));
645   gst_bin_add_many (GST_BIN (priv->pipeline), priv->video_input,
646       priv->video_tee, preview, NULL);
647   gst_element_link_many (priv->video_input, priv->video_tee,
648       preview, NULL);
649
650   g_object_unref (bus);
651
652   gst_element_set_state (preview, GST_STATE_PLAYING);
653   gst_element_set_state (priv->video_input, GST_STATE_PLAYING);
654   gst_element_set_state (priv->video_tee, GST_STATE_PLAYING);
655 }
656
657 static void
658 empathy_call_window_set_state_connecting (EmpathyCallWindow *window)
659 {
660   EmpathyCallWindowPriv *priv = GET_PRIV (window);
661
662   empathy_call_window_status_message (window, _("Connecting..."));
663   priv->call_state = CONNECTING;
664
665   if (priv->outgoing)
666     empathy_sound_start_playing (GTK_WIDGET (window),
667         EMPATHY_SOUND_PHONE_OUTGOING, MS_BETWEEN_RING);
668 }
669
670 static void
671 empathy_call_window_init (EmpathyCallWindow *self)
672 {
673   EmpathyCallWindowPriv *priv = GET_PRIV (self);
674   GtkBuilder *gui;
675   GtkWidget *top_vbox;
676   GtkWidget *h;
677   GtkWidget *arrow;
678   GtkWidget *page;
679   GstBus *bus;
680   gchar *filename;
681
682   filename = empathy_file_lookup ("empathy-call-window.ui", "src");
683   gui = empathy_builder_get_file (filename,
684     "call_window_vbox", &top_vbox,
685     "pane", &priv->pane,
686     "statusbar", &priv->statusbar,
687     "redial", &priv->redial_button,
688     "microphone", &priv->mic_button,
689     "camera", &priv->camera_button,
690     "toolbar", &priv->toolbar,
691     "send_video", &priv->send_video,
692     "menuredial", &priv->redial,
693     "show_preview", &priv->show_preview,
694     "ui_manager", &priv->ui_manager,
695     "menufullscreen", &priv->menu_fullscreen,
696     NULL);
697
698   empathy_builder_connect (gui, self,
699     "menuhangup", "activate", empathy_call_window_hangup_cb,
700     "hangup", "clicked", empathy_call_window_hangup_cb,
701     "menuredial", "activate", empathy_call_window_redial_cb,
702     "redial", "clicked", empathy_call_window_redial_cb,
703     "microphone", "toggled", empathy_call_window_mic_toggled_cb,
704     "camera", "toggled", empathy_call_window_camera_toggled_cb,
705     "send_video", "toggled", empathy_call_window_send_video_toggled_cb,
706     "show_preview", "toggled", empathy_call_window_show_preview_toggled_cb,
707     "menufullscreen", "activate", empathy_call_window_fullscreen_cb,
708     NULL);
709
710   priv->lock = g_mutex_new ();
711
712   gtk_container_add (GTK_CONTAINER (self), top_vbox);
713
714   empathy_call_window_setup_toolbar (self);
715
716   priv->content_hbox = gtk_hbox_new (FALSE, CONTENT_HBOX_SPACING);
717   gtk_container_set_border_width (GTK_CONTAINER (priv->content_hbox),
718                                   CONTENT_HBOX_BORDER_WIDTH);
719   gtk_paned_pack1 (GTK_PANED (priv->pane), priv->content_hbox, TRUE, FALSE);
720
721   priv->pipeline = gst_pipeline_new (NULL);
722   bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
723   gst_bus_add_watch (bus, empathy_call_window_bus_message, self);
724
725   priv->remote_user_output_frame = gtk_frame_new (NULL);
726   gtk_widget_set_size_request (priv->remote_user_output_frame,
727       EMPATHY_VIDEO_WIDGET_DEFAULT_WIDTH, EMPATHY_VIDEO_WIDGET_DEFAULT_HEIGHT);
728   gtk_box_pack_start (GTK_BOX (priv->content_hbox),
729       priv->remote_user_output_frame, TRUE, TRUE,
730       CONTENT_HBOX_CHILDREN_PACKING_PADDING);
731   empathy_call_window_setup_remote_frame (bus, self);
732
733   priv->self_user_output_frame = gtk_frame_new (NULL);
734   gtk_widget_set_size_request (priv->self_user_output_frame,
735       SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH);
736
737   priv->vbox = gtk_vbox_new (FALSE, 3);
738   gtk_box_pack_start (GTK_BOX (priv->content_hbox), priv->vbox,
739       FALSE, FALSE, CONTENT_HBOX_CHILDREN_PACKING_PADDING);
740   gtk_box_pack_start (GTK_BOX (priv->vbox), priv->self_user_output_frame, FALSE,
741       FALSE, 0);
742   empathy_call_window_setup_self_frame (bus, self);
743
744   g_object_unref (bus);
745
746   priv->sidebar_button = gtk_toggle_button_new_with_mnemonic (_("_Sidebar"));
747   arrow = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
748   g_signal_connect (G_OBJECT (priv->sidebar_button), "toggled",
749     G_CALLBACK (empathy_call_window_sidebar_toggled_cb), self);
750
751   gtk_button_set_image (GTK_BUTTON (priv->sidebar_button), arrow);
752
753   h = gtk_hbox_new (FALSE, 3);
754   gtk_box_pack_end (GTK_BOX (priv->vbox), h, FALSE, FALSE, 3);
755   gtk_box_pack_end (GTK_BOX (h), priv->sidebar_button, FALSE, FALSE, 3);
756
757   priv->sidebar = empathy_sidebar_new ();
758   g_signal_connect (G_OBJECT (priv->sidebar),
759     "hide", G_CALLBACK (empathy_call_window_sidebar_hidden_cb), self);
760   g_signal_connect (G_OBJECT (priv->sidebar),
761     "show", G_CALLBACK (empathy_call_window_sidebar_shown_cb), self);
762   gtk_paned_pack2 (GTK_PANED (priv->pane), priv->sidebar, FALSE, FALSE);
763
764   priv->dtmf_panel = empathy_call_window_create_dtmf (self);
765   empathy_sidebar_add_page (EMPATHY_SIDEBAR (priv->sidebar), _("Dialpad"),
766     priv->dtmf_panel);
767
768   gtk_widget_set_sensitive (priv->dtmf_panel, FALSE);
769
770   page = empathy_call_window_create_audio_input (self);
771   empathy_sidebar_add_page (EMPATHY_SIDEBAR (priv->sidebar), _("Audio input"),
772     page);
773
774   page = empathy_call_window_create_video_input (self);
775   empathy_sidebar_add_page (EMPATHY_SIDEBAR (priv->sidebar), _("Video input"),
776     page);
777
778   gtk_widget_show_all (top_vbox);
779
780   gtk_widget_hide (priv->sidebar);
781
782   priv->fullscreen = empathy_call_window_fullscreen_new (self);
783   empathy_call_window_fullscreen_set_video_widget (priv->fullscreen, priv->video_output);
784   g_signal_connect (G_OBJECT (priv->fullscreen->leave_fullscreen_button),
785       "clicked", G_CALLBACK (empathy_call_window_fullscreen_cb), self);
786
787   g_signal_connect (G_OBJECT (self), "realize",
788     G_CALLBACK (empathy_call_window_realized_cb), self);
789
790   g_signal_connect (G_OBJECT (self), "delete-event",
791     G_CALLBACK (empathy_call_window_delete_cb), self);
792
793   g_signal_connect (G_OBJECT (self), "window-state-event",
794     G_CALLBACK (empathy_call_window_state_event_cb), self);
795
796   g_signal_connect (G_OBJECT (self), "key-press-event",
797       G_CALLBACK (empathy_call_window_key_press_cb), self);
798
799   priv->timer = g_timer_new ();
800
801   g_object_ref (priv->ui_manager);
802   g_object_unref (gui);
803   g_free (filename);
804 }
805
806 /* Instead of specifying a width and a height, we specify only one size. That's
807    because we want a square avatar icon.  */
808 static void
809 init_contact_avatar_with_size (EmpathyContact *contact, GtkWidget *image_widget,
810     gint size)
811 {
812
813   GdkPixbuf *pixbuf_avatar = NULL;
814
815   if (contact != NULL)
816     {
817       pixbuf_avatar = empathy_pixbuf_avatar_from_contact_scaled (contact,
818         size, size);
819     }
820
821   if (pixbuf_avatar == NULL)
822     {
823       pixbuf_avatar = empathy_pixbuf_from_icon_name_sized ("stock_person",
824           size);
825     }
826
827   gtk_image_set_from_pixbuf (GTK_IMAGE (image_widget), pixbuf_avatar);
828 }
829
830 static void
831 set_window_title (EmpathyCallWindow *self)
832 {
833   EmpathyCallWindowPriv *priv = GET_PRIV (self);
834   gchar *tmp;
835
836   tmp = g_strdup_printf (_("Call with %s"),
837       empathy_contact_get_name (priv->contact));
838   gtk_window_set_title (GTK_WINDOW (self), tmp);
839   g_free (tmp);
840 }
841
842 static void
843 contact_name_changed_cb (EmpathyContact *contact,
844     GParamSpec *pspec, EmpathyCallWindow *self)
845 {
846   set_window_title (self);
847 }
848
849 static void
850 contact_avatar_changed_cb (EmpathyContact *contact,
851     GParamSpec *pspec, GtkWidget *avatar_widget)
852 {
853   init_contact_avatar_with_size (contact, avatar_widget,
854       avatar_widget->allocation.height);
855 }
856
857 static void
858 empathy_call_window_got_self_contact_cb (EmpathyTpContactFactory *factory,
859     EmpathyContact *contact, const GError *error, gpointer user_data,
860     GObject *weak_object)
861 {
862   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
863   EmpathyCallWindowPriv *priv = GET_PRIV (self);
864
865   init_contact_avatar_with_size (contact, priv->self_user_avatar_widget,
866       MIN (SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH));
867
868   g_signal_connect (contact, "notify::avatar",
869       G_CALLBACK (contact_avatar_changed_cb), priv->self_user_avatar_widget);
870 }
871
872 static void
873 empathy_call_window_setup_avatars (EmpathyCallWindow *self,
874     EmpathyCallHandler *handler)
875 {
876   EmpathyCallWindowPriv *priv = GET_PRIV (self);
877
878   g_object_get (handler, "contact", &(priv->contact), NULL);
879
880   if (priv->contact != NULL)
881     {
882       TpConnection *connection;
883       EmpathyTpContactFactory *factory;
884
885       set_window_title (self);
886
887       g_signal_connect (priv->contact, "notify::name",
888           G_CALLBACK (contact_name_changed_cb), self);
889       g_signal_connect (priv->contact, "notify::avatar",
890           G_CALLBACK (contact_avatar_changed_cb),
891           priv->remote_user_avatar_widget);
892
893       /* Retreiving the self avatar */
894       connection = empathy_contact_get_connection (priv->contact);
895       factory = empathy_tp_contact_factory_dup_singleton (connection);
896       empathy_tp_contact_factory_get_from_handle (factory,
897           tp_connection_get_self_handle (connection),
898           empathy_call_window_got_self_contact_cb, self, NULL, NULL);
899
900       g_object_unref (factory);
901     }
902   else
903     {
904       g_warning ("call handler doesn't have a contact");
905       gtk_window_set_title (GTK_WINDOW (self), _("Call"));
906
907       /* Since we can't access the remote contact, we can't get a connection
908          to it and can't get the self contact (and its avatar). This means
909          that we have to manually set the self avatar. */
910       init_contact_avatar_with_size (NULL, priv->self_user_avatar_widget,
911           MIN (SELF_VIDEO_SECTION_WIDTH, SELF_VIDEO_SECTION_HEIGTH));
912     }
913
914   init_contact_avatar_with_size (priv->contact,
915       priv->remote_user_avatar_widget, MIN (REMOTE_CONTACT_AVATAR_DEFAULT_WIDTH,
916       REMOTE_CONTACT_AVATAR_DEFAULT_HEIGHT));
917
918   /* The remote avatar is shown by default and will be hidden when we receive
919      video from the remote side. */
920   gtk_widget_hide (priv->video_output);
921   gtk_widget_show (priv->remote_user_avatar_widget);
922 }
923
924 static void
925 empathy_call_window_setup_video_preview_visibility (EmpathyCallWindow *self,
926     EmpathyCallHandler *handler)
927 {
928   EmpathyCallWindowPriv *priv = GET_PRIV (self);
929   gboolean initial_video = empathy_call_handler_has_initial_video (priv->handler);
930
931   gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->show_preview),
932       initial_video);
933 }
934
935 static void
936 empathy_call_window_constructed (GObject *object)
937 {
938   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
939   EmpathyCallWindowPriv *priv = GET_PRIV (self);
940   EmpathyTpCall *call;
941
942   g_assert (priv->handler != NULL);
943
944   g_object_get (priv->handler, "tp-call", &call, NULL);
945   priv->outgoing = (call == NULL);
946   if (call != NULL)
947     g_object_unref (call);
948
949   empathy_call_window_setup_avatars (self, priv->handler);
950   empathy_call_window_setup_video_preview_visibility (self, priv->handler);
951   empathy_call_window_set_state_connecting (self);
952 }
953
954 static void empathy_call_window_dispose (GObject *object);
955 static void empathy_call_window_finalize (GObject *object);
956
957 static void
958 empathy_call_window_set_property (GObject *object,
959   guint property_id, const GValue *value, GParamSpec *pspec)
960 {
961   EmpathyCallWindowPriv *priv = GET_PRIV (object);
962
963   switch (property_id)
964     {
965       case PROP_CALL_HANDLER:
966         priv->handler = g_value_dup_object (value);
967         break;
968       default:
969         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
970     }
971 }
972
973 static void
974 empathy_call_window_get_property (GObject *object,
975   guint property_id, GValue *value, GParamSpec *pspec)
976 {
977   EmpathyCallWindowPriv *priv = GET_PRIV (object);
978
979   switch (property_id)
980     {
981       case PROP_CALL_HANDLER:
982         g_value_set_object (value, priv->handler);
983         break;
984       default:
985         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
986     }
987 }
988
989 static void
990 empathy_call_window_class_init (
991   EmpathyCallWindowClass *empathy_call_window_class)
992 {
993   GObjectClass *object_class = G_OBJECT_CLASS (empathy_call_window_class);
994   GParamSpec *param_spec;
995
996   g_type_class_add_private (empathy_call_window_class,
997     sizeof (EmpathyCallWindowPriv));
998
999   object_class->constructed = empathy_call_window_constructed;
1000   object_class->set_property = empathy_call_window_set_property;
1001   object_class->get_property = empathy_call_window_get_property;
1002
1003   object_class->dispose = empathy_call_window_dispose;
1004   object_class->finalize = empathy_call_window_finalize;
1005
1006   param_spec = g_param_spec_object ("handler",
1007     "handler", "The call handler",
1008     EMPATHY_TYPE_CALL_HANDLER,
1009     G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
1010   g_object_class_install_property (object_class,
1011     PROP_CALL_HANDLER, param_spec);
1012 }
1013
1014 static void
1015 empathy_call_window_video_stream_changed_cb (EmpathyTpCall *call,
1016     GParamSpec *property, EmpathyCallWindow *self)
1017 {
1018   empathy_call_window_update_avatars_visibility (call, self);
1019 }
1020
1021 void
1022 empathy_call_window_dispose (GObject *object)
1023 {
1024   EmpathyTpCall *call;
1025   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
1026   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1027
1028   if (priv->dispose_has_run)
1029     return;
1030
1031   priv->dispose_has_run = TRUE;
1032
1033   g_object_get (priv->handler, "tp-call", &call, NULL);
1034
1035   if (call != NULL)
1036     {
1037       g_signal_handlers_disconnect_by_func (call,
1038         empathy_call_window_video_stream_changed_cb, object);
1039     }
1040
1041   g_object_unref (call);
1042
1043   if (priv->handler != NULL)
1044     g_object_unref (priv->handler);
1045
1046   priv->handler = NULL;
1047
1048   if (priv->pipeline != NULL)
1049     g_object_unref (priv->pipeline);
1050   priv->pipeline = NULL;
1051
1052   if (priv->video_input != NULL)
1053     g_object_unref (priv->video_input);
1054   priv->video_input = NULL;
1055
1056   if (priv->audio_input != NULL)
1057     g_object_unref (priv->audio_input);
1058   priv->audio_input = NULL;
1059
1060   if (priv->audio_output != NULL)
1061     g_object_unref (priv->audio_output);
1062   priv->audio_output = NULL;
1063
1064   if (priv->video_tee != NULL)
1065     g_object_unref (priv->video_tee);
1066   priv->video_tee = NULL;
1067
1068   if (priv->timer_id != 0)
1069     g_source_remove (priv->timer_id);
1070   priv->timer_id = 0;
1071
1072   if (priv->ui_manager != NULL)
1073     g_object_unref (priv->ui_manager);
1074   priv->ui_manager = NULL;
1075
1076   if (priv->contact != NULL)
1077     {
1078       g_signal_handlers_disconnect_by_func (priv->contact,
1079           contact_name_changed_cb, self);
1080       g_object_unref (priv->contact);
1081       priv->contact = NULL;
1082     }
1083
1084   /* release any references held by the object here */
1085   if (G_OBJECT_CLASS (empathy_call_window_parent_class)->dispose)
1086     G_OBJECT_CLASS (empathy_call_window_parent_class)->dispose (object);
1087 }
1088
1089 void
1090 empathy_call_window_finalize (GObject *object)
1091 {
1092   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
1093   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1094
1095   if (priv->video_output_motion_handler_id != 0)
1096     {
1097       g_signal_handler_disconnect (G_OBJECT (priv->video_output),
1098           priv->video_output_motion_handler_id);
1099       priv->video_output_motion_handler_id = 0;
1100     }
1101
1102   /* free any data held directly by the object here */
1103   g_mutex_free (priv->lock);
1104
1105   g_timer_destroy (priv->timer);
1106
1107   G_OBJECT_CLASS (empathy_call_window_parent_class)->finalize (object);
1108 }
1109
1110
1111 EmpathyCallWindow *
1112 empathy_call_window_new (EmpathyCallHandler *handler)
1113 {
1114   return EMPATHY_CALL_WINDOW (
1115     g_object_new (EMPATHY_TYPE_CALL_WINDOW, "handler", handler, NULL));
1116 }
1117
1118 static void
1119 empathy_call_window_conference_added_cb (EmpathyCallHandler *handler,
1120   GstElement *conference, gpointer user_data)
1121 {
1122   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1123   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1124
1125   gst_bin_add (GST_BIN (priv->pipeline), conference);
1126
1127   gst_element_set_state (conference, GST_STATE_PLAYING);
1128 }
1129
1130 static gboolean
1131 empathy_call_window_request_resource_cb (EmpathyCallHandler *handler,
1132   FsMediaType type, FsStreamDirection direction, gpointer user_data)
1133 {
1134   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1135   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1136
1137   if (type != TP_MEDIA_STREAM_TYPE_VIDEO)
1138     return TRUE;
1139
1140   if (direction == FS_DIRECTION_RECV)
1141     return TRUE;
1142
1143   /* video and direction is send */
1144   return priv->video_input != NULL;
1145 }
1146
1147 static gboolean
1148 empathy_call_window_reset_pipeline (EmpathyCallWindow *self)
1149 {
1150   GstStateChangeReturn state_change_return;
1151   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1152
1153   if (priv->pipeline == NULL)
1154     return TRUE;
1155
1156   state_change_return = gst_element_set_state (priv->pipeline, GST_STATE_NULL);
1157
1158   if (state_change_return == GST_STATE_CHANGE_SUCCESS ||
1159         state_change_return == GST_STATE_CHANGE_NO_PREROLL)
1160     {
1161       if (priv->pipeline != NULL)
1162         g_object_unref (priv->pipeline);
1163       priv->pipeline = NULL;
1164
1165       if (priv->video_input != NULL)
1166         g_object_unref (priv->video_input);
1167       priv->video_input = NULL;
1168
1169       if (priv->audio_input != NULL)
1170         g_object_unref (priv->audio_input);
1171       priv->audio_input = NULL;
1172
1173       if (priv->audio_output != NULL)
1174         g_object_unref (priv->audio_output);
1175       priv->audio_output = NULL;
1176
1177       if (priv->video_tee != NULL)
1178         g_object_unref (priv->video_tee);
1179       priv->video_tee = NULL;
1180
1181       if (priv->video_preview != NULL)
1182         gtk_widget_destroy (priv->video_preview);
1183       priv->video_preview = NULL;
1184
1185       priv->liveadder = NULL;
1186       priv->funnel = NULL;
1187
1188       return TRUE;
1189     }
1190   else
1191     {
1192       g_message ("Error: could not destroy pipeline. Closing call window");
1193       gtk_widget_destroy (GTK_WIDGET (self));
1194
1195       return FALSE;
1196     }
1197 }
1198
1199 static gboolean
1200 empathy_call_window_disconnected (EmpathyCallWindow *self)
1201 {
1202   gboolean could_disconnect = FALSE;
1203   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1204   gboolean could_reset_pipeline = empathy_call_window_reset_pipeline (self);
1205
1206   if (priv->call_state == CONNECTING)
1207       empathy_sound_stop (EMPATHY_SOUND_PHONE_OUTGOING);
1208
1209   if (priv->call_state != REDIALING)
1210     priv->call_state = DISCONNECTED;
1211
1212   if (could_reset_pipeline)
1213     {
1214       gboolean initial_video = empathy_call_handler_has_initial_video (
1215           priv->handler);
1216       g_mutex_lock (priv->lock);
1217
1218       g_timer_stop (priv->timer);
1219
1220       if (priv->timer_id != 0)
1221         g_source_remove (priv->timer_id);
1222       priv->timer_id = 0;
1223
1224       g_mutex_unlock (priv->lock);
1225
1226       empathy_call_window_status_message (self, _("Disconnected"));
1227
1228       gtk_action_set_sensitive (priv->redial, TRUE);
1229       gtk_widget_set_sensitive (priv->redial_button, TRUE);
1230
1231       /* Reseting the send_video, camera_buton and mic_button to their
1232          initial state */
1233       gtk_widget_set_sensitive (priv->camera_button, FALSE);
1234       gtk_widget_set_sensitive (priv->mic_button, FALSE);
1235       gtk_action_set_sensitive (priv->send_video, FALSE);
1236       gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video),
1237           initial_video);
1238       gtk_toggle_tool_button_set_active (
1239           GTK_TOGGLE_TOOL_BUTTON (priv->camera_button), initial_video);
1240       gtk_toggle_tool_button_set_active (
1241           GTK_TOGGLE_TOOL_BUTTON (priv->mic_button), TRUE);
1242
1243       gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->show_preview),
1244           FALSE);
1245       gtk_action_set_sensitive (priv->show_preview, FALSE);
1246
1247       gtk_widget_hide (priv->video_output);
1248       gtk_widget_show (priv->remote_user_avatar_widget);
1249
1250       priv->sending_video = FALSE;
1251       priv->call_started = FALSE;
1252
1253       could_disconnect = TRUE;
1254     }
1255
1256   return could_disconnect;
1257 }
1258
1259
1260 static void
1261 empathy_call_window_channel_closed_cb (TfChannel *channel, gpointer user_data)
1262 {
1263   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1264   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1265
1266   if (empathy_call_window_disconnected (self) && priv->call_state == REDIALING)
1267       empathy_call_window_restart_call (self);
1268 }
1269
1270 /* Called with global lock held */
1271 static GstPad *
1272 empathy_call_window_get_video_sink_pad (EmpathyCallWindow *self)
1273 {
1274   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1275   GstPad *pad;
1276
1277   if (priv->funnel == NULL)
1278     {
1279       GstElement *output;
1280
1281       output = empathy_video_widget_get_element (EMPATHY_VIDEO_WIDGET
1282         (priv->video_output));
1283
1284       priv->funnel = gst_element_factory_make ("fsfunnel", NULL);
1285
1286       gst_bin_add (GST_BIN (priv->pipeline), priv->funnel);
1287       gst_bin_add (GST_BIN (priv->pipeline), output);
1288
1289       gst_element_link (priv->funnel, output);
1290
1291       gst_element_set_state (priv->funnel, GST_STATE_PLAYING);
1292       gst_element_set_state (output, GST_STATE_PLAYING);
1293     }
1294
1295   pad = gst_element_get_request_pad (priv->funnel, "sink%d");
1296
1297   return pad;
1298 }
1299
1300 /* Called with global lock held */
1301 static GstPad *
1302 empathy_call_window_get_audio_sink_pad (EmpathyCallWindow *self)
1303 {
1304   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1305   GstPad *pad;
1306
1307   if (priv->liveadder == NULL)
1308     {
1309       priv->liveadder = gst_element_factory_make ("liveadder", NULL);
1310
1311       gst_bin_add (GST_BIN (priv->pipeline), priv->liveadder);
1312       gst_bin_add (GST_BIN (priv->pipeline), priv->audio_output);
1313
1314       gst_element_link (priv->liveadder, priv->audio_output);
1315
1316       gst_element_set_state (priv->liveadder, GST_STATE_PLAYING);
1317       gst_element_set_state (priv->audio_output, GST_STATE_PLAYING);
1318     }
1319
1320   pad = gst_element_get_request_pad (priv->liveadder, "sink%d");
1321
1322   return pad;
1323 }
1324
1325 static gboolean
1326 empathy_call_window_update_timer (gpointer user_data)
1327 {
1328   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1329   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1330   gchar *str;
1331   gdouble time;
1332
1333   time = g_timer_elapsed (priv->timer, NULL);
1334
1335   /* Translators: number of minutes:seconds the caller has been connected */
1336   str = g_strdup_printf (_("Connected — %d:%02dm"), (int) time / 60,
1337     (int) time % 60);
1338   empathy_call_window_status_message (self, str);
1339   g_free (str);
1340
1341   return TRUE;
1342 }
1343
1344 static gboolean
1345 empathy_call_window_connected (gpointer user_data)
1346 {
1347   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1348   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1349   EmpathyTpCall *call;
1350
1351   g_object_get (priv->handler, "tp-call", &call, NULL);
1352
1353   g_signal_connect (call, "notify::video-stream",
1354     G_CALLBACK (empathy_call_window_video_stream_changed_cb), self);
1355
1356   if (empathy_tp_call_has_dtmf (call))
1357     gtk_widget_set_sensitive (priv->dtmf_panel, TRUE);
1358
1359   if (priv->video_input == NULL)
1360       empathy_call_window_set_send_video (self, FALSE);
1361
1362   priv->sending_video = empathy_tp_call_is_sending_video (call);
1363
1364   gtk_action_set_sensitive (priv->show_preview, TRUE);
1365   gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->show_preview),
1366       priv->sending_video
1367       || gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (priv->show_preview)));
1368   gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video),
1369       priv->sending_video && priv->video_input != NULL);
1370   gtk_toggle_tool_button_set_active (
1371       GTK_TOGGLE_TOOL_BUTTON (priv->camera_button),
1372       priv->sending_video && priv->video_input != NULL);
1373   gtk_widget_set_sensitive (priv->camera_button, priv->video_input != NULL);
1374   gtk_action_set_sensitive (priv->send_video, priv->video_input != NULL);
1375
1376   gtk_action_set_sensitive (priv->redial, FALSE);
1377   gtk_widget_set_sensitive (priv->redial_button, FALSE);
1378
1379   gtk_widget_set_sensitive (priv->mic_button, TRUE);
1380
1381   empathy_call_window_update_avatars_visibility (call, self);
1382
1383   g_object_unref (call);
1384
1385   g_mutex_lock (priv->lock);
1386
1387   priv->timer_id = g_timeout_add_seconds (1,
1388     empathy_call_window_update_timer, self);
1389
1390   g_mutex_unlock (priv->lock);
1391
1392   empathy_call_window_update_timer (self);
1393
1394   return FALSE;
1395 }
1396
1397
1398 /* Called from the streaming thread */
1399 static void
1400 empathy_call_window_src_added_cb (EmpathyCallHandler *handler,
1401   GstPad *src, guint media_type, gpointer user_data)
1402 {
1403   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1404   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1405
1406   GstPad *pad;
1407
1408   g_mutex_lock (priv->lock);
1409
1410   if (priv->call_state != CONNECTED)
1411     {
1412       g_timer_start (priv->timer);
1413       priv->timer_id = g_idle_add  (empathy_call_window_connected, self);
1414       priv->call_state = CONNECTED;
1415       empathy_sound_stop (EMPATHY_SOUND_PHONE_OUTGOING);
1416     }
1417
1418   switch (media_type)
1419     {
1420       case TP_MEDIA_STREAM_TYPE_AUDIO:
1421         pad = empathy_call_window_get_audio_sink_pad (self);
1422         break;
1423       case TP_MEDIA_STREAM_TYPE_VIDEO:
1424         gtk_widget_hide (priv->remote_user_avatar_widget);
1425         gtk_widget_show (priv->video_output);
1426         pad = empathy_call_window_get_video_sink_pad (self);
1427         break;
1428       default:
1429         g_assert_not_reached ();
1430     }
1431
1432   gst_pad_link (src, pad);
1433   gst_object_unref (pad);
1434
1435   g_mutex_unlock (priv->lock);
1436 }
1437
1438 /* Called from the streaming thread */
1439 static void
1440 empathy_call_window_sink_added_cb (EmpathyCallHandler *handler,
1441   GstPad *sink, guint media_type, gpointer user_data)
1442 {
1443   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1444   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1445   GstPad *pad;
1446
1447   switch (media_type)
1448     {
1449       case TP_MEDIA_STREAM_TYPE_AUDIO:
1450         gst_bin_add (GST_BIN (priv->pipeline), priv->audio_input);
1451
1452         pad = gst_element_get_static_pad (priv->audio_input, "src");
1453         gst_pad_link (pad, sink);
1454
1455         gst_element_set_state (priv->audio_input, GST_STATE_PLAYING);
1456         break;
1457       case TP_MEDIA_STREAM_TYPE_VIDEO:
1458         if (priv->video_input != NULL)
1459           {
1460             EmpathyTpCall *call;
1461             g_object_get (priv->handler, "tp-call", &call, NULL);
1462
1463             if (empathy_tp_call_is_sending_video (call))
1464               {
1465                 empathy_call_window_setup_video_preview (self);
1466
1467                 gtk_toggle_action_set_active (
1468                     GTK_TOGGLE_ACTION (priv->show_preview), TRUE);
1469
1470                 if (priv->video_preview != NULL)
1471                   gtk_widget_show (priv->video_preview);
1472                 gtk_widget_hide (priv->self_user_avatar_widget);
1473               }
1474
1475             g_object_unref (call);
1476
1477             if (priv->video_tee != NULL)
1478               {
1479                 pad = gst_element_get_request_pad (priv->video_tee, "src%d");
1480                 gst_pad_link (pad, sink);
1481               }
1482           }
1483         break;
1484       default:
1485         g_assert_not_reached ();
1486     }
1487
1488 }
1489
1490 static void
1491 empathy_call_window_remove_video_input (EmpathyCallWindow *self)
1492 {
1493   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1494   GstElement *preview;
1495
1496   preview = empathy_video_widget_get_element (
1497     EMPATHY_VIDEO_WIDGET (priv->video_preview));
1498
1499   gst_element_set_state (priv->video_input, GST_STATE_NULL);
1500   gst_element_set_state (priv->video_tee, GST_STATE_NULL);
1501   gst_element_set_state (preview, GST_STATE_NULL);
1502
1503   gst_bin_remove_many (GST_BIN (priv->pipeline), priv->video_input,
1504     priv->video_tee, preview, NULL);
1505
1506   g_object_unref (priv->video_input);
1507   priv->video_input = NULL;
1508   g_object_unref (priv->video_tee);
1509   priv->video_tee = NULL;
1510   gtk_widget_destroy (priv->video_preview);
1511   priv->video_preview = NULL;
1512
1513   gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video), FALSE);
1514   gtk_toggle_tool_button_set_active (
1515       GTK_TOGGLE_TOOL_BUTTON (priv->camera_button), FALSE);
1516   gtk_widget_set_sensitive (priv->camera_button, FALSE);
1517   gtk_action_set_sensitive (priv->send_video, FALSE);
1518
1519   gtk_widget_show (priv->self_user_avatar_widget);
1520 }
1521
1522
1523 static gboolean
1524 empathy_call_window_bus_message (GstBus *bus, GstMessage *message,
1525   gpointer user_data)
1526 {
1527   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1528   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1529   GstState newstate;
1530
1531   empathy_call_handler_bus_message (priv->handler, bus, message);
1532
1533   switch (GST_MESSAGE_TYPE (message))
1534     {
1535       case GST_MESSAGE_STATE_CHANGED:
1536         if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->video_input))
1537           {
1538             gst_message_parse_state_changed (message, NULL, &newstate, NULL);
1539             if (newstate == GST_STATE_PAUSED)
1540                 empathy_call_window_setup_video_input (self);
1541           }
1542         if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->pipeline) &&
1543             !priv->call_started)
1544           {
1545             gst_message_parse_state_changed (message, NULL, &newstate, NULL);
1546             if (newstate == GST_STATE_PAUSED)
1547               {
1548                 priv->call_started = TRUE;
1549                 empathy_call_handler_start_call (priv->handler);
1550                 gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
1551               }
1552           }
1553         break;
1554       case GST_MESSAGE_ERROR:
1555         {
1556           GError *error = NULL;
1557           GstElement *gst_error;
1558           gchar *debug;
1559
1560           gst_message_parse_error (message, &error, &debug);
1561           gst_error = GST_ELEMENT (GST_MESSAGE_SRC (message));
1562
1563           g_message ("Element error: %s -- %s\n", error->message, debug);
1564
1565           if (g_str_has_prefix (gst_element_get_name (gst_error),
1566                 VIDEO_INPUT_ERROR_PREFIX))
1567             {
1568               /* Remove the video input and continue */
1569               if (priv->video_input != NULL)
1570                 empathy_call_window_remove_video_input (self);
1571               gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
1572             }
1573           else
1574             {
1575               empathy_call_window_disconnected (self);
1576             }
1577           g_error_free (error);
1578           g_free (debug);
1579         }
1580       default:
1581         break;
1582     }
1583
1584   return TRUE;
1585 }
1586
1587 static void
1588 empathy_call_window_update_self_avatar_visibility (EmpathyCallWindow *window)
1589 {
1590   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1591
1592   if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (priv->show_preview)))
1593     {
1594       if (priv->video_preview != NULL)
1595         {
1596           gtk_widget_hide (priv->self_user_avatar_widget);
1597           gtk_widget_show (priv->video_preview);
1598         }
1599       else
1600         {
1601           if (priv->video_preview != NULL)
1602             gtk_widget_hide (priv->video_preview);
1603
1604           gtk_widget_show (priv->self_user_avatar_widget);
1605         }
1606     }
1607 }
1608
1609 static void
1610 empathy_call_window_update_avatars_visibility (EmpathyTpCall *call,
1611     EmpathyCallWindow *window)
1612 {
1613   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1614
1615   if (empathy_tp_call_is_receiving_video (call))
1616     {
1617       gtk_widget_hide (priv->remote_user_avatar_widget);
1618       gtk_widget_show (priv->video_output);
1619     }
1620   else
1621     {
1622       gtk_widget_hide (priv->video_output);
1623       gtk_widget_show (priv->remote_user_avatar_widget);
1624     }
1625
1626   empathy_call_window_update_self_avatar_visibility (window);
1627 }
1628
1629 static void
1630 empathy_call_window_realized_cb (GtkWidget *widget, EmpathyCallWindow *window)
1631 {
1632   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1633
1634   g_signal_connect (priv->handler, "conference-added",
1635     G_CALLBACK (empathy_call_window_conference_added_cb), window);
1636   g_signal_connect (priv->handler, "request-resource",
1637     G_CALLBACK (empathy_call_window_request_resource_cb), window);
1638   g_signal_connect (priv->handler, "closed",
1639     G_CALLBACK (empathy_call_window_channel_closed_cb), window);
1640   g_signal_connect (priv->handler, "src-pad-added",
1641     G_CALLBACK (empathy_call_window_src_added_cb), window);
1642   g_signal_connect (priv->handler, "sink-pad-added",
1643     G_CALLBACK (empathy_call_window_sink_added_cb), window);
1644
1645   gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
1646 }
1647
1648 static gboolean
1649 empathy_call_window_delete_cb (GtkWidget *widget, GdkEvent*event,
1650   EmpathyCallWindow *window)
1651 {
1652   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1653
1654   if (priv->pipeline != NULL)
1655     gst_element_set_state (priv->pipeline, GST_STATE_NULL);
1656
1657   if (priv->call_state == CONNECTING)
1658     empathy_sound_stop (EMPATHY_SOUND_PHONE_OUTGOING);
1659
1660   return FALSE;
1661 }
1662
1663 static void
1664 show_controls (EmpathyCallWindow *window, gboolean set_fullscreen)
1665 {
1666   GtkWidget *menu;
1667   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1668
1669   menu = gtk_ui_manager_get_widget (priv->ui_manager,
1670             "/menubar1");
1671
1672   if (set_fullscreen)
1673     {
1674       gtk_widget_hide (priv->sidebar);
1675       gtk_widget_hide (menu);
1676       gtk_widget_hide (priv->vbox);
1677       gtk_widget_hide (priv->statusbar);
1678       gtk_widget_hide (priv->toolbar);
1679     }
1680   else
1681     {
1682       if (priv->sidebar_was_visible_before_fs)
1683         gtk_widget_show (priv->sidebar);
1684
1685       gtk_widget_show (menu);
1686       gtk_widget_show (priv->vbox);
1687       gtk_widget_show (priv->statusbar);
1688       gtk_widget_show (priv->toolbar);
1689
1690       gtk_window_resize (GTK_WINDOW (window), priv->original_width_before_fs,
1691           priv->original_height_before_fs);
1692     }
1693 }
1694
1695 static void
1696 show_borders (EmpathyCallWindow *window, gboolean set_fullscreen)
1697 {
1698   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1699
1700   gtk_container_set_border_width (GTK_CONTAINER (priv->content_hbox),
1701       set_fullscreen ? 0 : CONTENT_HBOX_BORDER_WIDTH);
1702   gtk_box_set_spacing (GTK_BOX (priv->content_hbox),
1703       set_fullscreen ? 0 : CONTENT_HBOX_SPACING);
1704   gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
1705       priv->video_output, TRUE, TRUE,
1706       set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
1707       GTK_PACK_START);
1708   gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
1709       priv->vbox, TRUE, TRUE,
1710       set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
1711       GTK_PACK_START);
1712 }
1713
1714 static gboolean
1715 empathy_call_window_state_event_cb (GtkWidget *widget,
1716   GdkEventWindowState *event, EmpathyCallWindow *window)
1717 {
1718   if (event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN)
1719     {
1720       EmpathyCallWindowPriv *priv = GET_PRIV (window);
1721       gboolean set_fullscreen = event->new_window_state & GDK_WINDOW_STATE_FULLSCREEN;
1722
1723       if (set_fullscreen)
1724         {
1725           gboolean sidebar_was_visible;
1726           gint original_width = GTK_WIDGET (window)->allocation.width;
1727           gint original_height = GTK_WIDGET (window)->allocation.height;
1728
1729           g_object_get (priv->sidebar, "visible", &sidebar_was_visible, NULL);
1730
1731           priv->sidebar_was_visible_before_fs = sidebar_was_visible;
1732           priv->original_width_before_fs = original_width;
1733           priv->original_height_before_fs = original_height;
1734
1735           if (priv->video_output_motion_handler_id == 0 &&
1736                 priv->video_output != NULL)
1737             {
1738               priv->video_output_motion_handler_id = g_signal_connect (
1739                   G_OBJECT (priv->video_output), "motion-notify-event",
1740                   G_CALLBACK (empathy_call_window_video_output_motion_notify), window);
1741             }
1742         }
1743       else
1744         {
1745           if (priv->video_output_motion_handler_id != 0)
1746             {
1747               g_signal_handler_disconnect (G_OBJECT (priv->video_output),
1748                   priv->video_output_motion_handler_id);
1749               priv->video_output_motion_handler_id = 0;
1750             }
1751         }
1752
1753       empathy_call_window_fullscreen_set_fullscreen (priv->fullscreen,
1754           set_fullscreen);
1755       show_controls (window, set_fullscreen);
1756       show_borders (window, set_fullscreen);
1757       gtk_action_set_stock_id (priv->menu_fullscreen,
1758           (set_fullscreen ? "gtk-leave-fullscreen" : "gtk-fullscreen"));
1759       priv->is_fullscreen = set_fullscreen;
1760   }
1761
1762   return FALSE;
1763 }
1764
1765 static void
1766 empathy_call_window_sidebar_toggled_cb (GtkToggleButton *toggle,
1767   EmpathyCallWindow *window)
1768 {
1769   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1770   GtkWidget *arrow;
1771   int w,h, handle_size;
1772
1773   w = GTK_WIDGET (window)->allocation.width;
1774   h = GTK_WIDGET (window)->allocation.height;
1775
1776   gtk_widget_style_get (priv->pane, "handle_size", &handle_size, NULL);
1777
1778   if (gtk_toggle_button_get_active (toggle))
1779     {
1780       arrow = gtk_arrow_new (GTK_ARROW_LEFT, GTK_SHADOW_NONE);
1781       gtk_widget_show (priv->sidebar);
1782       w += priv->sidebar->allocation.width + handle_size;
1783     }
1784   else
1785     {
1786       arrow = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
1787       w -= priv->sidebar->allocation.width + handle_size;
1788       gtk_widget_hide (priv->sidebar);
1789     }
1790
1791   gtk_button_set_image (GTK_BUTTON (priv->sidebar_button), arrow);
1792
1793   if (w > 0 && h > 0)
1794     gtk_window_resize (GTK_WINDOW (window), w, h);
1795 }
1796
1797 static void
1798 empathy_call_window_set_send_video (EmpathyCallWindow *window,
1799   gboolean send)
1800 {
1801   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1802   EmpathyTpCall *call;
1803
1804   priv->sending_video = send;
1805
1806   /* When we start sending video, we want to show the video preview by
1807      default. */
1808   if (send)
1809     {
1810       empathy_call_window_setup_video_preview (window);
1811       gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->show_preview),
1812           TRUE);
1813     }
1814
1815   g_object_get (priv->handler, "tp-call", &call, NULL);
1816   empathy_tp_call_request_video_stream_direction (call, send);
1817   g_object_unref (call);
1818 }
1819
1820 static void
1821 empathy_call_window_camera_toggled_cb (GtkToggleToolButton *toggle,
1822   EmpathyCallWindow *window)
1823 {
1824   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1825   gboolean active;
1826
1827   if (priv->call_state != CONNECTED)
1828     return;
1829
1830   active = (gtk_toggle_tool_button_get_active (toggle));
1831
1832   if (priv->sending_video == active)
1833     return;
1834
1835   empathy_call_window_set_send_video (window, active);
1836   gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video), active);
1837 }
1838
1839 static void
1840 empathy_call_window_send_video_toggled_cb (GtkToggleAction *toggle,
1841   EmpathyCallWindow *window)
1842 {
1843   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1844   gboolean active;
1845
1846   if (priv->call_state != CONNECTED)
1847     return;
1848
1849   active = (gtk_toggle_action_get_active (toggle));
1850
1851   if (priv->sending_video == active)
1852     return;
1853
1854   empathy_call_window_set_send_video (window, active);
1855   gtk_toggle_tool_button_set_active (
1856       GTK_TOGGLE_TOOL_BUTTON (priv->camera_button), active);
1857 }
1858
1859 static void
1860 empathy_call_window_show_preview_toggled_cb (GtkToggleAction *toggle,
1861   EmpathyCallWindow *window)
1862 {
1863   gboolean show_preview_toggled;
1864   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1865
1866   show_preview_toggled = gtk_toggle_action_get_active (toggle);
1867
1868   if (show_preview_toggled)
1869     {
1870       empathy_call_window_setup_video_preview (window);
1871       gtk_widget_show (priv->self_user_output_frame);
1872       empathy_call_window_update_self_avatar_visibility (window);
1873     }
1874   else
1875     {
1876       gtk_widget_hide (priv->self_user_output_frame);
1877     }
1878 }
1879
1880 static void
1881 empathy_call_window_mic_toggled_cb (GtkToggleToolButton *toggle,
1882   EmpathyCallWindow *window)
1883 {
1884   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1885   gboolean active;
1886
1887   if (priv->audio_input == NULL)
1888     return;
1889
1890   active = (gtk_toggle_tool_button_get_active (toggle));
1891
1892   if (active)
1893     {
1894       empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
1895         priv->volume);
1896       gtk_adjustment_set_value (priv->audio_input_adj, priv->volume * 100);
1897     }
1898   else
1899     {
1900       /* TODO, Instead of setting the input volume to 0 we should probably
1901        * stop sending but this would cause the audio call to drop if both
1902        * sides mute at the same time on certain CMs AFAIK. Need to revisit this
1903        * in the future. GNOME #574574
1904        */
1905       empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
1906         0);
1907       gtk_adjustment_set_value (priv->audio_input_adj, 0);
1908     }
1909 }
1910
1911 static void
1912 empathy_call_window_sidebar_hidden_cb (EmpathySidebar *sidebar,
1913   EmpathyCallWindow *window)
1914 {
1915   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1916
1917   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->sidebar_button),
1918     FALSE);
1919 }
1920
1921 static void
1922 empathy_call_window_sidebar_shown_cb (EmpathySidebar *sidebar,
1923   EmpathyCallWindow *window)
1924 {
1925   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1926
1927   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->sidebar_button),
1928     TRUE);
1929 }
1930
1931 static void
1932 empathy_call_window_hangup_cb (gpointer object,
1933                                EmpathyCallWindow *window)
1934 {
1935   if (empathy_call_window_disconnected (window))
1936     gtk_widget_destroy (GTK_WIDGET (window));
1937 }
1938
1939 static void
1940 empathy_call_window_restart_call (EmpathyCallWindow *window)
1941 {
1942   GstBus *bus;
1943   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1944
1945   gtk_widget_destroy (priv->remote_user_output_hbox);
1946   gtk_widget_destroy (priv->self_user_output_hbox);
1947
1948   priv->pipeline = gst_pipeline_new (NULL);
1949   bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
1950   gst_bus_add_watch (bus, empathy_call_window_bus_message, window);
1951
1952   empathy_call_window_setup_remote_frame (bus, window);
1953   empathy_call_window_setup_self_frame (bus, window);
1954
1955   g_object_unref (bus);
1956
1957   gtk_widget_show_all (priv->content_hbox);
1958
1959   if (!empathy_call_handler_has_initial_video (priv->handler))
1960     gtk_widget_hide (priv->self_user_output_frame);
1961
1962   priv->outgoing = TRUE;
1963   empathy_call_window_set_state_connecting (window);
1964
1965   priv->call_started = TRUE;
1966   empathy_call_handler_start_call (priv->handler);
1967   empathy_call_window_setup_avatars (window, priv->handler);
1968   gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
1969
1970   gtk_action_set_sensitive (priv->redial, FALSE);
1971   gtk_widget_set_sensitive (priv->redial_button, FALSE);
1972 }
1973
1974 static void
1975 empathy_call_window_redial_cb (gpointer object,
1976     EmpathyCallWindow *window)
1977 {
1978   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1979
1980   if (priv->call_state == CONNECTED)
1981     priv->call_state = REDIALING;
1982
1983   empathy_call_handler_stop_call (priv->handler);
1984
1985   if (priv->call_state != CONNECTED)
1986     empathy_call_window_restart_call (window);
1987 }
1988
1989 static void
1990 empathy_call_window_fullscreen_cb (gpointer object,
1991                                    EmpathyCallWindow *window)
1992 {
1993   empathy_call_window_fullscreen_toggle (window);
1994 }
1995
1996 static void
1997 empathy_call_window_fullscreen_toggle (EmpathyCallWindow *window)
1998 {
1999   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2000
2001   if (priv->is_fullscreen)
2002     gtk_window_unfullscreen (GTK_WINDOW (window));
2003   else
2004     gtk_window_fullscreen (GTK_WINDOW (window));
2005 }
2006
2007 static gboolean
2008 empathy_call_window_video_button_press_cb (GtkWidget *video_output,
2009   GdkEventButton *event, EmpathyCallWindow *window)
2010 {
2011   if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
2012     {
2013       empathy_call_window_video_menu_popup (window, event->button);
2014       return TRUE;
2015     }
2016
2017   return FALSE;
2018 }
2019
2020 static gboolean
2021 empathy_call_window_key_press_cb (GtkWidget *video_output,
2022   GdkEventKey *event, EmpathyCallWindow *window)
2023 {
2024   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2025
2026   if (priv->is_fullscreen && event->keyval == GDK_Escape)
2027     {
2028       /* Since we are in fullscreen mode, toggling will bring us back to
2029          normal mode. */
2030       empathy_call_window_fullscreen_toggle (window);
2031       return TRUE;
2032     }
2033
2034   return FALSE;
2035 }
2036
2037 static gboolean
2038 empathy_call_window_video_output_motion_notify (GtkWidget *widget,
2039     GdkEventMotion *event, EmpathyCallWindow *window)
2040 {
2041   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2042
2043   if (priv->is_fullscreen)
2044     {
2045       empathy_call_window_fullscreen_show_popup (priv->fullscreen);
2046       return TRUE;
2047     }
2048   return FALSE;
2049 }
2050
2051 static void
2052 empathy_call_window_video_menu_popup (EmpathyCallWindow *window,
2053   guint button)
2054 {
2055   GtkWidget *menu;
2056   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2057
2058   menu = gtk_ui_manager_get_widget (priv->ui_manager,
2059             "/video-popup");
2060   gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
2061       button, gtk_get_current_event_time ());
2062   gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE);
2063 }
2064
2065 static void
2066 empathy_call_window_status_message (EmpathyCallWindow *window,
2067   gchar *message)
2068 {
2069   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2070
2071   if (priv->context_id == 0)
2072     {
2073       priv->context_id = gtk_statusbar_get_context_id (
2074         GTK_STATUSBAR (priv->statusbar), "voip call status messages");
2075     }
2076   else
2077     {
2078       gtk_statusbar_pop (GTK_STATUSBAR (priv->statusbar), priv->context_id);
2079     }
2080
2081   gtk_statusbar_push (GTK_STATUSBAR (priv->statusbar), priv->context_id,
2082     message);
2083 }
2084
2085 static void
2086 empathy_call_window_volume_changed_cb (GtkScaleButton *button,
2087   gdouble value, EmpathyCallWindow *window)
2088 {
2089   EmpathyCallWindowPriv *priv = GET_PRIV (window);
2090
2091   empathy_audio_sink_set_volume (EMPATHY_GST_AUDIO_SINK (priv->audio_output),
2092     value);
2093 }