]> git.0d.be Git - empathy.git/blob - src/empathy-call-window.c
Fixed coding style by removing trailing spaces and not using a mix of
[empathy.git] / src / empathy-call-window.c
1 /*
2  * empathy-call-window.c - Source for EmpathyCallWindow
3  * Copyright (C) 2008 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-utils.h>
35 #include <libempathy-gtk/empathy-video-widget.h>
36 #include <libempathy-gtk/empathy-audio-src.h>
37 #include <libempathy-gtk/empathy-audio-sink.h>
38 #include <libempathy-gtk/empathy-video-src.h>
39 #include <libempathy-gtk/empathy-ui-utils.h>
40
41 #include "empathy-call-window.h"
42
43 #include "empathy-call-window-fullscreen.h"
44 #include "empathy-sidebar.h"
45
46 #define BUTTON_ID "empathy-call-dtmf-button-id"
47
48 #define CONTENT_HBOX_BORDER_WIDTH 6
49 #define CONTENT_HBOX_SPACING 3
50 #define CONTENT_HBOX_CHILDREN_PACKING_PADDING 3
51
52 G_DEFINE_TYPE(EmpathyCallWindow, empathy_call_window, GTK_TYPE_WINDOW)
53
54 /* signal enum */
55 #if 0
56 enum
57 {
58     LAST_SIGNAL
59 };
60
61 static guint signals[LAST_SIGNAL] = {0};
62 #endif
63
64 enum {
65   PROP_CALL_HANDLER = 1,
66 };
67
68 /* private structure */
69 typedef struct _EmpathyCallWindowPriv EmpathyCallWindowPriv;
70
71 struct _EmpathyCallWindowPriv
72 {
73   gboolean dispose_has_run;
74   EmpathyCallHandler *handler;
75   EmpathyContact *contact;
76
77   gboolean connected;
78
79   GtkUIManager *ui_manager;
80   GtkWidget *video_output;
81   GtkWidget *video_preview;
82   GtkWidget *sidebar;
83   GtkWidget *sidebar_button;
84   GtkWidget *statusbar;
85   GtkWidget *volume_button;
86   GtkWidget *mic_button;
87   GtkWidget *camera_button;
88   GtkWidget *toolbar;
89   GtkWidget *pane;
90   GtkAction *send_video;
91
92   /* We keep a reference on the hbox which contains the main content so we can
93      easilly repack everything when toggling fullscreen */
94   GtkWidget *content_hbox;
95
96   /* This vbox is contained in the content_hbox. When toggling fullscreen,
97      it needs to be repacked. We keep a reference on it for easier access. */
98   GtkWidget *vbox;
99
100   gulong video_output_motion_handler_id;
101
102   gdouble volume;
103   GtkAdjustment *audio_input_adj;
104
105   GtkWidget *dtmf_panel;
106
107   GstElement *video_input;
108   GstElement *audio_input;
109   GstElement *audio_output;
110   GstElement *pipeline;
111   GstElement *video_tee;
112
113   GstElement *funnel;
114   GstElement *liveadder;
115
116   guint context_id;
117
118   GTimer *timer;
119   guint timer_id;
120
121   GtkWidget *video_contrast;
122   GtkWidget *video_brightness;
123   GtkWidget *video_gamma;
124
125   GMutex *lock;
126   gboolean call_started;
127   gboolean sending_video;
128
129   EmpathyCallWindowFullscreen *fullscreen;
130   gboolean is_fullscreen;
131
132   /* Those fields represent the state of the window before it actually was in
133      fullscreen mode. */
134   gboolean sidebar_was_visible_before_fs;
135   gint original_width_before_fs;
136   gint original_height_before_fs;
137 };
138
139 #define GET_PRIV(o) \
140   (G_TYPE_INSTANCE_GET_PRIVATE ((o), EMPATHY_TYPE_CALL_WINDOW, \
141     EmpathyCallWindowPriv))
142
143 static void empathy_call_window_realized_cb (GtkWidget *widget,
144   EmpathyCallWindow *window);
145
146 static gboolean empathy_call_window_delete_cb (GtkWidget *widget,
147   GdkEvent *event, EmpathyCallWindow *window);
148
149 static gboolean empathy_call_window_state_event_cb (GtkWidget *widget,
150   GdkEventWindowState *event, EmpathyCallWindow *window);
151
152 static void empathy_call_window_sidebar_toggled_cb (GtkToggleButton *toggle,
153   EmpathyCallWindow *window);
154
155 static void empathy_call_window_camera_toggled_cb (GtkToggleToolButton *toggle,
156   EmpathyCallWindow *window);
157
158 static void empathy_call_window_send_video_toggled_cb (GtkToggleAction *toggle,
159   EmpathyCallWindow *window);
160
161 static void empathy_call_window_show_preview_toggled_cb (GtkToggleAction *toggle,
162   EmpathyCallWindow *window);
163
164 static void empathy_call_window_mic_toggled_cb (
165   GtkToggleToolButton *toggle, EmpathyCallWindow *window);
166
167 static void empathy_call_window_sidebar_hidden_cb (EmpathySidebar *sidebar,
168   EmpathyCallWindow *window);
169
170 static void empathy_call_window_sidebar_shown_cb (EmpathySidebar *sidebar,
171   EmpathyCallWindow *window);
172
173 static void empathy_call_window_hangup_cb (gpointer object,
174   EmpathyCallWindow *window);
175
176 static void empathy_call_window_fullscreen_cb (gpointer object,
177   EmpathyCallWindow *window);
178
179 static void empathy_call_window_fullscreen_toggle (EmpathyCallWindow *window);
180
181 static gboolean empathy_call_window_video_button_press_cb (GtkWidget *video_output,
182   GdkEventButton *event, EmpathyCallWindow *window);
183
184 static gboolean empathy_call_window_key_press_cb (GtkWidget *video_output,
185   GdkEventKey *event, EmpathyCallWindow *window);
186
187 static gboolean empathy_call_window_video_output_motion_notify (GtkWidget *widget,
188   GdkEventMotion *event, EmpathyCallWindow *window);
189
190 static void empathy_call_window_video_menu_popup (EmpathyCallWindow *window,
191   guint button);
192
193 static void empathy_call_window_status_message (EmpathyCallWindow *window,
194   gchar *message);
195
196 static gboolean empathy_call_window_bus_message (GstBus *bus,
197   GstMessage *message, gpointer user_data);
198
199 static void
200 empathy_call_window_volume_changed_cb (GtkScaleButton *button,
201   gdouble value, EmpathyCallWindow *window);
202
203 static void
204 empathy_call_window_setup_toolbar (EmpathyCallWindow *self)
205 {
206   EmpathyCallWindowPriv *priv = GET_PRIV (self);
207   GtkToolItem *tool_item;
208
209   /* Add an empty expanded GtkToolItem so the volume button is at the end of
210    * the toolbar. */
211   tool_item = gtk_tool_item_new ();
212   gtk_tool_item_set_expand (tool_item, TRUE);
213   gtk_widget_show (GTK_WIDGET (tool_item));
214   gtk_toolbar_insert (GTK_TOOLBAR (priv->toolbar), tool_item, -1);
215
216   priv->volume_button = gtk_volume_button_new ();
217   /* FIXME listen to the audiosinks signals and update the button according to
218    * that, for now starting out at 1.0 and assuming only the app changes the
219    * volume will do */
220   gtk_scale_button_set_value (GTK_SCALE_BUTTON (priv->volume_button), 1.0);
221   g_signal_connect (G_OBJECT (priv->volume_button), "value-changed",
222     G_CALLBACK (empathy_call_window_volume_changed_cb), self);
223
224   tool_item = gtk_tool_item_new ();
225   gtk_container_add (GTK_CONTAINER (tool_item), priv->volume_button);
226   gtk_widget_show_all (GTK_WIDGET (tool_item));
227   gtk_toolbar_insert (GTK_TOOLBAR (priv->toolbar), tool_item, -1);
228 }
229
230 static void
231 dtmf_button_pressed_cb (GtkButton *button, EmpathyCallWindow *window)
232 {
233   EmpathyCallWindowPriv *priv = GET_PRIV (window);
234   EmpathyTpCall *call;
235   GQuark button_quark;
236   TpDTMFEvent event;
237
238   g_object_get (priv->handler, "tp-call", &call, NULL);
239
240   button_quark = g_quark_from_static_string (BUTTON_ID);
241   event = GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (button),
242     button_quark));
243
244   empathy_tp_call_start_tone (call, event);
245
246   g_object_unref (call);
247 }
248
249 static void
250 dtmf_button_released_cb (GtkButton *button, EmpathyCallWindow *window)
251 {
252   EmpathyCallWindowPriv *priv = GET_PRIV (window);
253   EmpathyTpCall *call;
254
255   g_object_get (priv->handler, "tp-call", &call, NULL);
256
257   empathy_tp_call_stop_tone (call);
258
259   g_object_unref (call);
260 }
261
262 static GtkWidget *
263 empathy_call_window_create_dtmf (EmpathyCallWindow *self)
264 {
265   GtkWidget *table;
266   int i;
267   GQuark button_quark;
268   struct {
269     gchar *label;
270     TpDTMFEvent event;
271   } dtmfbuttons[] = { { "1", TP_DTMF_EVENT_DIGIT_1 },
272                       { "2", TP_DTMF_EVENT_DIGIT_2 },
273                       { "3", TP_DTMF_EVENT_DIGIT_3 },
274                       { "4", TP_DTMF_EVENT_DIGIT_4 },
275                       { "5", TP_DTMF_EVENT_DIGIT_5 },
276                       { "6", TP_DTMF_EVENT_DIGIT_6 },
277                       { "7", TP_DTMF_EVENT_DIGIT_7 },
278                       { "8", TP_DTMF_EVENT_DIGIT_8 },
279                       { "9", TP_DTMF_EVENT_DIGIT_9 },
280                       { "#", TP_DTMF_EVENT_HASH },
281                       { "0", TP_DTMF_EVENT_DIGIT_0 },
282                       { "*", TP_DTMF_EVENT_ASTERISK },
283                       { NULL, } };
284
285   button_quark = g_quark_from_static_string (BUTTON_ID);
286
287   table = gtk_table_new (4, 3, TRUE);
288
289   for (i = 0; dtmfbuttons[i].label != NULL; i++)
290     {
291       GtkWidget *button = gtk_button_new_with_label (dtmfbuttons[i].label);
292       gtk_table_attach (GTK_TABLE (table), button, i % 3, i % 3 + 1,
293         i/3, i/3 + 1, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 1, 1);
294
295       g_object_set_qdata (G_OBJECT (button), button_quark,
296         GUINT_TO_POINTER (dtmfbuttons[i].event));
297
298       g_signal_connect (G_OBJECT (button), "pressed",
299         G_CALLBACK (dtmf_button_pressed_cb), self);
300       g_signal_connect (G_OBJECT (button), "released",
301         G_CALLBACK (dtmf_button_released_cb), self);
302     }
303
304   return table;
305 }
306
307 static GtkWidget *
308 empathy_call_window_create_video_input_add_slider (EmpathyCallWindow *self,
309   gchar *label_text, GtkWidget *bin)
310 {
311    GtkWidget *vbox = gtk_vbox_new (FALSE, 2);
312    GtkWidget *scale = gtk_vscale_new_with_range (0, 100, 10);
313    GtkWidget *label = gtk_label_new (label_text);
314
315    gtk_widget_set_sensitive (scale, FALSE);
316
317    gtk_container_add (GTK_CONTAINER (bin), vbox);
318
319    gtk_range_set_inverted (GTK_RANGE (scale), TRUE);
320    gtk_box_pack_start (GTK_BOX (vbox), scale, TRUE, TRUE, 0);
321    gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);
322
323    return scale;
324 }
325
326 static void
327 empathy_call_window_video_contrast_changed_cb (GtkAdjustment *adj,
328   EmpathyCallWindow *self)
329
330 {
331   EmpathyCallWindowPriv *priv = GET_PRIV (self);
332
333   empathy_video_src_set_channel (priv->video_input,
334     EMPATHY_GST_VIDEO_SRC_CHANNEL_CONTRAST, gtk_adjustment_get_value (adj));
335 }
336
337 static void
338 empathy_call_window_video_brightness_changed_cb (GtkAdjustment *adj,
339   EmpathyCallWindow *self)
340
341 {
342   EmpathyCallWindowPriv *priv = GET_PRIV (self);
343
344   empathy_video_src_set_channel (priv->video_input,
345     EMPATHY_GST_VIDEO_SRC_CHANNEL_BRIGHTNESS, gtk_adjustment_get_value (adj));
346 }
347
348 static void
349 empathy_call_window_video_gamma_changed_cb (GtkAdjustment *adj,
350   EmpathyCallWindow *self)
351
352 {
353   EmpathyCallWindowPriv *priv = GET_PRIV (self);
354
355   empathy_video_src_set_channel (priv->video_input,
356     EMPATHY_GST_VIDEO_SRC_CHANNEL_GAMMA, gtk_adjustment_get_value (adj));
357 }
358
359
360 static GtkWidget *
361 empathy_call_window_create_video_input (EmpathyCallWindow *self)
362 {
363   EmpathyCallWindowPriv *priv = GET_PRIV (self);
364   GtkWidget *hbox;
365
366   hbox = gtk_hbox_new (TRUE, 3);
367
368   priv->video_contrast = empathy_call_window_create_video_input_add_slider (
369     self,  _("Contrast"), hbox);
370
371   priv->video_brightness = empathy_call_window_create_video_input_add_slider (
372     self,  _("Brightness"), hbox);
373
374   priv->video_gamma = empathy_call_window_create_video_input_add_slider (
375     self,  _("Gamma"), hbox);
376
377   return hbox;
378 }
379
380 static void
381 empathy_call_window_setup_video_input (EmpathyCallWindow *self)
382 {
383   EmpathyCallWindowPriv *priv = GET_PRIV (self);
384   guint supported;
385   GtkAdjustment *adj;
386
387   supported = empathy_video_src_get_supported_channels (priv->video_input);
388
389   if (supported & EMPATHY_GST_VIDEO_SRC_SUPPORTS_CONTRAST)
390     {
391       adj = gtk_range_get_adjustment (GTK_RANGE (priv->video_contrast));
392
393       gtk_adjustment_set_value (adj,
394         empathy_video_src_get_channel (priv->video_input,
395           EMPATHY_GST_VIDEO_SRC_CHANNEL_CONTRAST));
396
397       g_signal_connect (G_OBJECT (adj), "value-changed",
398         G_CALLBACK (empathy_call_window_video_contrast_changed_cb), self);
399
400       gtk_widget_set_sensitive (priv->video_contrast, TRUE);
401     }
402
403   if (supported & EMPATHY_GST_VIDEO_SRC_SUPPORTS_BRIGHTNESS)
404     {
405       adj = gtk_range_get_adjustment (GTK_RANGE (priv->video_brightness));
406
407       gtk_adjustment_set_value (adj,
408         empathy_video_src_get_channel (priv->video_input,
409           EMPATHY_GST_VIDEO_SRC_CHANNEL_BRIGHTNESS));
410
411       g_signal_connect (G_OBJECT (adj), "value-changed",
412         G_CALLBACK (empathy_call_window_video_brightness_changed_cb), self);
413       gtk_widget_set_sensitive (priv->video_brightness, TRUE);
414     }
415
416   if (supported & EMPATHY_GST_VIDEO_SRC_SUPPORTS_GAMMA)
417     {
418       adj = gtk_range_get_adjustment (GTK_RANGE (priv->video_gamma));
419
420       gtk_adjustment_set_value (adj,
421         empathy_video_src_get_channel (priv->video_input,
422           EMPATHY_GST_VIDEO_SRC_CHANNEL_GAMMA));
423
424       g_signal_connect (G_OBJECT (adj), "value-changed",
425         G_CALLBACK (empathy_call_window_video_gamma_changed_cb), self);
426       gtk_widget_set_sensitive (priv->video_gamma, TRUE);
427     }
428 }
429
430 static void
431 empathy_call_window_mic_volume_changed_cb (GtkAdjustment *adj,
432   EmpathyCallWindow *self)
433 {
434   EmpathyCallWindowPriv *priv = GET_PRIV (self);
435   gdouble volume;
436
437   volume =  gtk_adjustment_get_value (adj)/100.0;
438
439   /* Don't store the volume because of muting */
440   if (volume > 0 || gtk_toggle_tool_button_get_active (
441         GTK_TOGGLE_TOOL_BUTTON (priv->mic_button)))
442     priv->volume = volume;
443
444   /* Ensure that the toggle button is active if the volume is > 0 and inactive
445    * if it's smaller then 0 */
446   if ((volume > 0) != gtk_toggle_tool_button_get_active (
447         GTK_TOGGLE_TOOL_BUTTON (priv->mic_button)))
448     gtk_toggle_tool_button_set_active (
449       GTK_TOGGLE_TOOL_BUTTON (priv->mic_button), volume > 0);
450
451   empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
452     volume);
453 }
454
455 static void
456 empathy_call_window_audio_input_level_changed_cb (EmpathyGstAudioSrc *src,
457   gdouble level, GtkProgressBar *bar)
458 {
459   gdouble value;
460
461   value = CLAMP (pow (10, level / 20), 0.0, 1.0);
462   gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (bar), value);
463 }
464
465 static GtkWidget *
466 empathy_call_window_create_audio_input (EmpathyCallWindow *self)
467 {
468   EmpathyCallWindowPriv *priv = GET_PRIV (self);
469   GtkWidget *hbox, *vbox, *scale, *progress, *label;
470   GtkAdjustment *adj;
471
472   hbox = gtk_hbox_new (TRUE, 3);
473
474   vbox = gtk_vbox_new (FALSE, 3);
475   gtk_box_pack_start (GTK_BOX (hbox), vbox, FALSE, FALSE, 3);
476
477   scale = gtk_vscale_new_with_range (0, 150, 100);
478   gtk_range_set_inverted (GTK_RANGE (scale), TRUE);
479   label = gtk_label_new (_("Volume"));
480
481   priv->audio_input_adj = adj = gtk_range_get_adjustment (GTK_RANGE (scale));
482   priv->volume =  empathy_audio_src_get_volume (EMPATHY_GST_AUDIO_SRC
483     (priv->audio_input));
484   gtk_adjustment_set_value (adj, priv->volume * 100);
485
486   g_signal_connect (G_OBJECT (adj), "value-changed",
487     G_CALLBACK (empathy_call_window_mic_volume_changed_cb), self);
488
489   gtk_box_pack_start (GTK_BOX (vbox), scale, TRUE, TRUE, 3);
490   gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 3);
491
492   progress = gtk_progress_bar_new ();
493   gtk_progress_bar_set_orientation (GTK_PROGRESS_BAR (progress),
494     GTK_PROGRESS_BOTTOM_TO_TOP);
495   gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (progress), 0);
496
497   g_signal_connect (priv->audio_input, "peak-level-changed",
498     G_CALLBACK (empathy_call_window_audio_input_level_changed_cb), progress);
499
500   gtk_box_pack_start (GTK_BOX (hbox), progress, FALSE, FALSE, 3);
501
502   return hbox;
503 }
504
505 static void
506 empathy_call_window_init (EmpathyCallWindow *self)
507 {
508   EmpathyCallWindowPriv *priv = GET_PRIV (self);
509   GtkBuilder *gui;
510   GtkWidget *top_vbox;
511   GtkWidget *h;
512   GtkWidget *arrow;
513   GtkWidget *page;
514   GstBus *bus;
515   gchar *filename;
516
517   filename = empathy_file_lookup ("empathy-call-window.ui", "src");
518   gui = empathy_builder_get_file (filename,
519     "call_window_vbox", &top_vbox,
520     "pane", &priv->pane,
521     "statusbar", &priv->statusbar,
522     "microphone", &priv->mic_button,
523     "camera", &priv->camera_button,
524     "toolbar", &priv->toolbar,
525     "send_video", &priv->send_video,
526     "ui_manager", &priv->ui_manager,
527     NULL);
528
529   empathy_builder_connect (gui, self,
530     "menuhangup", "activate", empathy_call_window_hangup_cb,
531     "hangup", "clicked", empathy_call_window_hangup_cb,
532     "microphone", "toggled", empathy_call_window_mic_toggled_cb,
533     "camera", "toggled", empathy_call_window_camera_toggled_cb,
534     "send_video", "toggled", empathy_call_window_send_video_toggled_cb,
535     "show_preview", "toggled", empathy_call_window_show_preview_toggled_cb,
536     "menufullscreen", "activate", empathy_call_window_fullscreen_cb,
537     NULL);
538
539   priv->lock = g_mutex_new ();
540
541   gtk_container_add (GTK_CONTAINER (self), top_vbox);
542
543   empathy_call_window_setup_toolbar (self);
544
545   priv->pipeline = gst_pipeline_new (NULL);
546
547   priv->content_hbox = gtk_hbox_new (FALSE, CONTENT_HBOX_SPACING);
548   gtk_container_set_border_width (GTK_CONTAINER (priv->content_hbox),
549                                   CONTENT_HBOX_BORDER_WIDTH);
550   gtk_paned_pack1 (GTK_PANED (priv->pane), priv->content_hbox, TRUE, FALSE);
551
552   bus = gst_pipeline_get_bus (GST_PIPELINE (priv->pipeline));
553
554   gst_bus_add_watch (bus, empathy_call_window_bus_message, self);
555
556   priv->video_output = empathy_video_widget_new (bus);
557   gtk_box_pack_start (GTK_BOX (priv->content_hbox), priv->video_output,
558                       TRUE, TRUE, CONTENT_HBOX_CHILDREN_PACKING_PADDING);
559   gtk_widget_add_events (priv->video_output,
560       GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK);
561   g_signal_connect (G_OBJECT (priv->video_output), "button-press-event",
562       G_CALLBACK (empathy_call_window_video_button_press_cb), self);
563
564   priv->video_tee = gst_element_factory_make ("tee", NULL);
565   gst_object_ref (priv->video_tee);
566   gst_object_sink (priv->video_tee);
567
568   priv->vbox = gtk_vbox_new (FALSE, 3);
569   gtk_box_pack_start (GTK_BOX (priv->content_hbox), priv->vbox,
570                       FALSE, FALSE, CONTENT_HBOX_CHILDREN_PACKING_PADDING);
571
572   priv->video_preview = empathy_video_widget_new_with_size (bus, 160, 120);
573   g_object_set (priv->video_preview, "sync", FALSE, "async", TRUE, NULL);
574   gtk_box_pack_start (GTK_BOX (priv->vbox), priv->video_preview, FALSE, FALSE, 0);
575
576   priv->video_input = empathy_video_src_new ();
577   gst_object_ref (priv->video_input);
578   gst_object_sink (priv->video_input);
579
580   priv->audio_input = empathy_audio_src_new ();
581   gst_object_ref (priv->audio_input);
582   gst_object_sink (priv->audio_input);
583
584   priv->audio_output = empathy_audio_sink_new ();
585   gst_object_ref (priv->audio_output);
586   gst_object_sink (priv->audio_output);
587
588   g_object_unref (bus);
589
590   priv->sidebar_button = gtk_toggle_button_new_with_mnemonic (_("_Sidebar"));
591   arrow = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
592   g_signal_connect (G_OBJECT (priv->sidebar_button), "toggled",
593     G_CALLBACK (empathy_call_window_sidebar_toggled_cb), self);
594
595   gtk_button_set_image (GTK_BUTTON (priv->sidebar_button), arrow);
596
597   h = gtk_hbox_new (FALSE, 3);
598   gtk_box_pack_end (GTK_BOX (priv->vbox), h, FALSE, FALSE, 3);
599   gtk_box_pack_end (GTK_BOX (h), priv->sidebar_button, FALSE, FALSE, 3);
600
601   priv->sidebar = empathy_sidebar_new ();
602   g_signal_connect (G_OBJECT (priv->sidebar),
603     "hide", G_CALLBACK (empathy_call_window_sidebar_hidden_cb), self);
604   g_signal_connect (G_OBJECT (priv->sidebar),
605     "show", G_CALLBACK (empathy_call_window_sidebar_shown_cb), self);
606   gtk_paned_pack2 (GTK_PANED (priv->pane), priv->sidebar, FALSE, FALSE);
607
608   priv->dtmf_panel = empathy_call_window_create_dtmf (self);
609   empathy_sidebar_add_page (EMPATHY_SIDEBAR (priv->sidebar), _("Dialpad"),
610     priv->dtmf_panel);
611
612   gtk_widget_set_sensitive (priv->dtmf_panel, FALSE);
613
614   page = empathy_call_window_create_audio_input (self);
615   empathy_sidebar_add_page (EMPATHY_SIDEBAR (priv->sidebar), _("Audio input"),
616     page);
617
618   page = empathy_call_window_create_video_input (self);
619   empathy_sidebar_add_page (EMPATHY_SIDEBAR (priv->sidebar), _("Video input"),
620     page);
621
622   gtk_widget_show_all (top_vbox);
623
624   gtk_widget_hide (priv->sidebar);
625
626   priv->fullscreen = empathy_call_window_fullscreen_new (self);
627   empathy_call_window_fullscreen_set_video_widget (priv->fullscreen, priv->video_output);
628   g_signal_connect (G_OBJECT (priv->fullscreen->leave_fullscreen_button),
629       "clicked", G_CALLBACK (empathy_call_window_fullscreen_cb), self);
630
631   g_signal_connect (G_OBJECT (self), "realize",
632     G_CALLBACK (empathy_call_window_realized_cb), self);
633
634   g_signal_connect (G_OBJECT (self), "delete-event",
635     G_CALLBACK (empathy_call_window_delete_cb), self);
636
637   g_signal_connect (G_OBJECT (self), "window-state-event",
638     G_CALLBACK (empathy_call_window_state_event_cb), self);
639
640   g_signal_connect (G_OBJECT (self), "key-press-event",
641       G_CALLBACK (empathy_call_window_key_press_cb), self);
642
643   empathy_call_window_status_message (self, _("Connecting..."));
644
645   priv->timer = g_timer_new ();
646
647   g_object_ref (priv->ui_manager);
648   g_object_unref (gui);
649   g_free (filename);
650 }
651
652 static void
653 set_window_title (EmpathyCallWindow *self)
654 {
655   EmpathyCallWindowPriv *priv = GET_PRIV (self);
656   gchar *tmp;
657
658   tmp = g_strdup_printf (_("Call with %s"),
659       empathy_contact_get_name (priv->contact));
660   gtk_window_set_title (GTK_WINDOW (self), tmp);
661   g_free (tmp);
662 }
663
664 static void
665 contact_name_changed_cb (EmpathyContact *contact,
666                          GParamSpec *pspec,
667                          EmpathyCallWindow *self)
668 {
669   set_window_title (self);
670 }
671
672 static void
673 empathy_call_window_constructed (GObject *object)
674 {
675   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
676   EmpathyCallWindowPriv *priv = GET_PRIV (self);
677
678   g_assert (priv->handler != NULL);
679
680   g_object_get (priv->handler, "contact", &(priv->contact), NULL);
681
682   if (priv->contact != NULL)
683     {
684       set_window_title (self);
685
686       g_signal_connect (priv->contact, "notify::name",
687           G_CALLBACK (contact_name_changed_cb), self);
688     }
689   else
690     {
691       g_warning ("call handler doesn't have a contact");
692       gtk_window_set_title (GTK_WINDOW (self), _("Call"));
693     }
694 }
695
696 static void empathy_call_window_dispose (GObject *object);
697 static void empathy_call_window_finalize (GObject *object);
698
699 static void
700 empathy_call_window_set_property (GObject *object,
701   guint property_id, const GValue *value, GParamSpec *pspec)
702 {
703   EmpathyCallWindowPriv *priv = GET_PRIV (object);
704
705   switch (property_id)
706     {
707       case PROP_CALL_HANDLER:
708         priv->handler = g_value_dup_object (value);
709         break;
710       default:
711         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
712     }
713 }
714
715 static void
716 empathy_call_window_get_property (GObject *object,
717   guint property_id, GValue *value, GParamSpec *pspec)
718 {
719   EmpathyCallWindowPriv *priv = GET_PRIV (object);
720
721   switch (property_id)
722     {
723       case PROP_CALL_HANDLER:
724         g_value_set_object (value, priv->handler);
725         break;
726       default:
727         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
728     }
729 }
730
731 static void
732 empathy_call_window_class_init (
733   EmpathyCallWindowClass *empathy_call_window_class)
734 {
735   GObjectClass *object_class = G_OBJECT_CLASS (empathy_call_window_class);
736   GParamSpec *param_spec;
737
738   g_type_class_add_private (empathy_call_window_class,
739     sizeof (EmpathyCallWindowPriv));
740
741   object_class->constructed = empathy_call_window_constructed;
742   object_class->set_property = empathy_call_window_set_property;
743   object_class->get_property = empathy_call_window_get_property;
744
745   object_class->dispose = empathy_call_window_dispose;
746   object_class->finalize = empathy_call_window_finalize;
747
748   param_spec = g_param_spec_object ("handler",
749     "handler", "The call handler",
750     EMPATHY_TYPE_CALL_HANDLER,
751     G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
752   g_object_class_install_property (object_class,
753     PROP_CALL_HANDLER, param_spec);
754
755 }
756
757 void
758 empathy_call_window_dispose (GObject *object)
759 {
760   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
761   EmpathyCallWindowPriv *priv = GET_PRIV (self);
762
763   if (priv->dispose_has_run)
764     return;
765
766   priv->dispose_has_run = TRUE;
767
768   if (priv->handler != NULL)
769     g_object_unref (priv->handler);
770
771   priv->handler = NULL;
772
773   if (priv->pipeline != NULL)
774     g_object_unref (priv->pipeline);
775   priv->pipeline = NULL;
776
777   if (priv->video_input != NULL)
778     g_object_unref (priv->video_input);
779   priv->video_input = NULL;
780
781   if (priv->audio_input != NULL)
782     g_object_unref (priv->audio_input);
783   priv->audio_input = NULL;
784
785   if (priv->audio_output != NULL)
786     g_object_unref (priv->audio_output);
787   priv->audio_output = NULL;
788
789   if (priv->video_tee != NULL)
790     g_object_unref (priv->video_tee);
791   priv->video_tee = NULL;
792
793   if (priv->timer_id != 0)
794     g_source_remove (priv->timer_id);
795   priv->timer_id = 0;
796
797   if (priv->ui_manager != NULL)
798     g_object_unref (priv->ui_manager);
799   priv->ui_manager = NULL;
800
801   if (priv->contact != NULL)
802     {
803       g_signal_handlers_disconnect_by_func (priv->contact,
804           contact_name_changed_cb, self);
805       g_object_unref (priv->contact);
806       priv->contact = NULL;
807     }
808
809   /* release any references held by the object here */
810   if (G_OBJECT_CLASS (empathy_call_window_parent_class)->dispose)
811     G_OBJECT_CLASS (empathy_call_window_parent_class)->dispose (object);
812 }
813
814 void
815 empathy_call_window_finalize (GObject *object)
816 {
817   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (object);
818   EmpathyCallWindowPriv *priv = GET_PRIV (self);
819
820   if (priv->video_output_motion_handler_id != 0)
821     {
822       g_signal_handler_disconnect (G_OBJECT (priv->video_output),
823           priv->video_output_motion_handler_id);
824       priv->video_output_motion_handler_id = 0;
825     }
826
827   /* free any data held directly by the object here */
828   g_mutex_free (priv->lock);
829
830   g_timer_destroy (priv->timer);
831
832   G_OBJECT_CLASS (empathy_call_window_parent_class)->finalize (object);
833 }
834
835
836 EmpathyCallWindow *
837 empathy_call_window_new (EmpathyCallHandler *handler)
838 {
839   return EMPATHY_CALL_WINDOW (
840     g_object_new (EMPATHY_TYPE_CALL_WINDOW, "handler", handler, NULL));
841 }
842
843 static void
844 empathy_call_window_conference_added_cb (EmpathyCallHandler *handler,
845   GstElement *conference, gpointer user_data)
846 {
847   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
848   EmpathyCallWindowPriv *priv = GET_PRIV (self);
849
850   gst_bin_add (GST_BIN (priv->pipeline), conference);
851
852   gst_element_set_state (conference, GST_STATE_PLAYING);
853 }
854
855 static gboolean
856 empathy_call_window_request_resource_cb (EmpathyCallHandler *handler,
857   FsMediaType type, FsStreamDirection direction, gpointer user_data)
858 {
859   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
860   EmpathyCallWindowPriv *priv = GET_PRIV (self);
861
862   if (type != TP_MEDIA_STREAM_TYPE_VIDEO)
863     return TRUE;
864
865   if (direction == FS_DIRECTION_RECV)
866     return TRUE;
867
868   /* video and direction is send */
869   return priv->video_input != NULL;
870 }
871
872 static void
873 empathy_call_window_disconnected (EmpathyCallWindow *self)
874 {
875   EmpathyCallWindowPriv *priv = GET_PRIV (self);
876
877   g_mutex_lock (priv->lock);
878
879   g_timer_stop (priv->timer);
880
881   if (priv->timer_id != 0)
882     g_source_remove (priv->timer_id);
883   priv->timer_id = 0;
884
885   g_mutex_unlock (priv->lock);
886
887   empathy_call_window_status_message (self, _("Disconnected"));
888
889   gtk_widget_set_sensitive (priv->camera_button, FALSE);
890   gtk_action_set_sensitive (priv->send_video, FALSE);
891   priv->sending_video = FALSE;
892 }
893
894
895 static void
896 empathy_call_window_channel_closed_cb (TfChannel *channel, gpointer user_data)
897 {
898   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
899
900   empathy_call_window_disconnected (self);
901 }
902
903 /* Called with global lock held */
904 static GstPad *
905 empathy_call_window_get_video_sink_pad (EmpathyCallWindow *self)
906 {
907   EmpathyCallWindowPriv *priv = GET_PRIV (self);
908   GstPad *pad;
909
910   if (priv->funnel == NULL)
911     {
912       GstElement *output;
913
914       output = empathy_video_widget_get_element (EMPATHY_VIDEO_WIDGET
915         (priv->video_output));
916
917       priv->funnel = gst_element_factory_make ("fsfunnel", NULL);
918
919       gst_bin_add (GST_BIN (priv->pipeline), priv->funnel);
920       gst_bin_add (GST_BIN (priv->pipeline), output);
921
922       gst_element_link (priv->funnel, output);
923
924       gst_element_set_state (priv->funnel, GST_STATE_PLAYING);
925       gst_element_set_state (output, GST_STATE_PLAYING);
926     }
927
928   pad = gst_element_get_request_pad (priv->funnel, "sink%d");
929
930   return pad;
931 }
932
933 /* Called with global lock held */
934 static GstPad *
935 empathy_call_window_get_audio_sink_pad (EmpathyCallWindow *self)
936 {
937   EmpathyCallWindowPriv *priv = GET_PRIV (self);
938   GstPad *pad;
939
940   if (priv->liveadder == NULL)
941     {
942       priv->liveadder = gst_element_factory_make ("liveadder", NULL);
943
944       gst_bin_add (GST_BIN (priv->pipeline), priv->liveadder);
945       gst_bin_add (GST_BIN (priv->pipeline), priv->audio_output);
946
947       gst_element_link (priv->liveadder, priv->audio_output);
948
949       gst_element_set_state (priv->liveadder, GST_STATE_PLAYING);
950       gst_element_set_state (priv->audio_output, GST_STATE_PLAYING);
951     }
952
953   pad = gst_element_get_request_pad (priv->liveadder, "sink%d");
954
955   return pad;
956 }
957
958 static gboolean
959 empathy_call_window_update_timer (gpointer user_data)
960 {
961   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
962   EmpathyCallWindowPriv *priv = GET_PRIV (self);
963   gchar *str;
964   gdouble time;
965
966   time = g_timer_elapsed (priv->timer, NULL);
967
968   /* Translators: number of minutes:seconds the caller has been connected */
969   str = g_strdup_printf (_("Connected â€” %d:%02dm"), (int) time / 60,
970     (int) time % 60);
971   empathy_call_window_status_message (self, str);
972   g_free (str);
973
974   return TRUE;
975 }
976
977 static gboolean
978 empathy_call_window_connected (gpointer user_data)
979 {
980   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
981   EmpathyCallWindowPriv *priv = GET_PRIV (self);
982   EmpathyTpCall *call;
983
984   g_object_get (priv->handler, "tp-call", &call, NULL);
985
986   if (empathy_tp_call_has_dtmf (call))
987     gtk_widget_set_sensitive (priv->dtmf_panel, TRUE);
988
989   if (priv->video_input != NULL)
990     {
991       gtk_widget_set_sensitive (priv->camera_button, TRUE);
992       gtk_action_set_sensitive (priv->send_video, TRUE);
993     }
994
995   g_object_unref (call);
996
997   g_mutex_lock (priv->lock);
998
999   priv->timer_id = g_timeout_add_seconds (1,
1000     empathy_call_window_update_timer, self);
1001
1002   g_mutex_unlock (priv->lock);
1003
1004   empathy_call_window_update_timer (self);
1005
1006   return FALSE;
1007 }
1008
1009
1010 /* Called from the streaming thread */
1011 static void
1012 empathy_call_window_src_added_cb (EmpathyCallHandler *handler,
1013   GstPad *src, guint media_type, gpointer user_data)
1014 {
1015   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1016   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1017
1018   GstPad *pad;
1019
1020   g_mutex_lock (priv->lock);
1021
1022   if (priv->connected == FALSE)
1023     {
1024       g_timer_start (priv->timer);
1025       priv->timer_id = g_idle_add  (empathy_call_window_connected, self);
1026       priv->connected = TRUE;
1027     }
1028
1029   switch (media_type)
1030     {
1031       case TP_MEDIA_STREAM_TYPE_AUDIO:
1032         pad = empathy_call_window_get_audio_sink_pad (self);
1033         break;
1034       case TP_MEDIA_STREAM_TYPE_VIDEO:
1035         pad = empathy_call_window_get_video_sink_pad (self);
1036         break;
1037       default:
1038         g_assert_not_reached ();
1039     }
1040
1041   gst_pad_link (src, pad);
1042   gst_object_unref (pad);
1043
1044   g_mutex_unlock (priv->lock);
1045 }
1046
1047 /* Called from the streaming thread */
1048 static void
1049 empathy_call_window_sink_added_cb (EmpathyCallHandler *handler,
1050   GstPad *sink, guint media_type, gpointer user_data)
1051 {
1052   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1053   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1054   GstPad *pad;
1055
1056   switch (media_type)
1057     {
1058       case TP_MEDIA_STREAM_TYPE_AUDIO:
1059         gst_bin_add (GST_BIN (priv->pipeline), priv->audio_input);
1060
1061         pad = gst_element_get_static_pad (priv->audio_input, "src");
1062         gst_pad_link (pad, sink);
1063
1064         gst_element_set_state (priv->audio_input, GST_STATE_PLAYING);
1065         break;
1066       case TP_MEDIA_STREAM_TYPE_VIDEO:
1067         if (priv->video_input != NULL)
1068           {
1069             pad =  gst_element_get_request_pad (priv->video_tee, "src%d");
1070             gst_pad_link (pad, sink);
1071           }
1072         break;
1073       default:
1074         g_assert_not_reached ();
1075     }
1076
1077 }
1078
1079 static gboolean
1080 empathy_gst_bin_has_child (GstBin *bin, GstElement *element)
1081 {
1082   GstIterator *it;
1083   gboolean ret = FALSE;
1084   GstElement *item;
1085
1086   it = gst_bin_iterate_recurse (bin);
1087
1088   for (;;)
1089     {
1090       switch (gst_iterator_next (it, (gpointer *)&item))
1091        {
1092          case GST_ITERATOR_OK:
1093            if (item == element)
1094             {
1095               gst_object_unref (GST_OBJECT (item));
1096               ret = TRUE;
1097               goto out;
1098             }
1099            gst_object_unref (GST_OBJECT (item));
1100            break;
1101          case GST_ITERATOR_RESYNC:
1102            gst_iterator_resync (it);
1103            break;
1104         case GST_ITERATOR_ERROR:
1105            g_assert_not_reached ();
1106            /* fallthrough */
1107         case GST_ITERATOR_DONE:
1108            goto out;
1109            break;
1110       }
1111     }
1112     gst_iterator_free (it);
1113
1114 out:
1115   return ret;
1116 }
1117
1118 static void
1119 empathy_call_window_remove_video_input (EmpathyCallWindow *self)
1120 {
1121   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1122   GstElement *preview;
1123
1124   preview = empathy_video_widget_get_element (
1125     EMPATHY_VIDEO_WIDGET (priv->video_preview));
1126
1127   gst_element_set_state (priv->video_input, GST_STATE_NULL);
1128   gst_element_set_state (priv->video_tee, GST_STATE_NULL);
1129   gst_element_set_state (preview, GST_STATE_NULL);
1130
1131   gst_bin_remove_many (GST_BIN (priv->pipeline), priv->video_input,
1132     priv->video_tee, preview, NULL);
1133
1134   g_object_unref (priv->video_input);
1135   priv->video_input = NULL;
1136   g_object_unref (priv->video_tee);
1137   priv->video_tee = NULL;
1138 }
1139
1140
1141 static gboolean
1142 empathy_call_window_bus_message (GstBus *bus, GstMessage *message,
1143   gpointer user_data)
1144 {
1145   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
1146   EmpathyCallWindowPriv *priv = GET_PRIV (self);
1147   GstState newstate;
1148
1149   empathy_call_handler_bus_message (priv->handler, bus, message);
1150
1151   switch (GST_MESSAGE_TYPE (message))
1152     {
1153       case GST_MESSAGE_STATE_CHANGED:
1154         if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->video_input))
1155           {
1156             gst_message_parse_state_changed (message, NULL, &newstate, NULL);
1157             if (newstate == GST_STATE_PAUSED)
1158                 empathy_call_window_setup_video_input (self);
1159           }
1160         if (GST_MESSAGE_SRC (message) == GST_OBJECT (priv->pipeline) &&
1161             !priv->call_started)
1162           {
1163             gst_message_parse_state_changed (message, NULL, &newstate, NULL);
1164             if (newstate == GST_STATE_PAUSED)
1165               {
1166                 priv->call_started = TRUE;
1167                 empathy_call_handler_start_call (priv->handler);
1168                 gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
1169               }
1170           }
1171         break;
1172       case GST_MESSAGE_ERROR:
1173         {
1174           GError *error;
1175           gchar *debug;
1176
1177           gst_message_parse_error (message, &error, &debug);
1178
1179           g_message ("Element error: %s -- %s\n", error->message, debug);
1180
1181           if (priv->video_input != NULL &&
1182               empathy_gst_bin_has_child (GST_BIN (priv->video_input),
1183                 GST_ELEMENT (GST_MESSAGE_SRC (message))))
1184             {
1185               /* Remove the video input and continue */
1186               empathy_call_window_remove_video_input (self);
1187               gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
1188             }
1189           else
1190             {
1191               gst_element_set_state (priv->pipeline, GST_STATE_NULL);
1192               empathy_call_window_disconnected (self);
1193             }
1194           g_error_free (error);
1195           g_free (debug);
1196         }
1197       default:
1198         break;
1199     }
1200
1201   return TRUE;
1202 }
1203
1204 static void
1205 empathy_call_window_realized_cb (GtkWidget *widget, EmpathyCallWindow *window)
1206 {
1207   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1208   GstElement *preview;
1209
1210   g_signal_connect (priv->handler, "conference-added",
1211     G_CALLBACK (empathy_call_window_conference_added_cb), window);
1212   g_signal_connect (priv->handler, "request-resource",
1213     G_CALLBACK (empathy_call_window_request_resource_cb), window);
1214   g_signal_connect (priv->handler, "closed",
1215     G_CALLBACK (empathy_call_window_channel_closed_cb), window);
1216   g_signal_connect (priv->handler, "src-pad-added",
1217     G_CALLBACK (empathy_call_window_src_added_cb), window);
1218   g_signal_connect (priv->handler, "sink-pad-added",
1219     G_CALLBACK (empathy_call_window_sink_added_cb), window);
1220
1221
1222   preview = empathy_video_widget_get_element (
1223     EMPATHY_VIDEO_WIDGET (priv->video_preview));
1224
1225   gst_bin_add_many (GST_BIN (priv->pipeline), priv->video_input,
1226     priv->video_tee, preview, NULL);
1227   gst_element_link_many (priv->video_input, priv->video_tee,
1228     preview, NULL);
1229
1230   gst_element_set_state (priv->pipeline, GST_STATE_PAUSED);
1231 }
1232
1233 static gboolean
1234 empathy_call_window_delete_cb (GtkWidget *widget, GdkEvent*event,
1235   EmpathyCallWindow *window)
1236 {
1237   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1238
1239   gst_element_set_state (priv->pipeline, GST_STATE_NULL);
1240
1241   return FALSE;
1242 }
1243
1244 static void
1245 show_controls (EmpathyCallWindow *window, gboolean set_fullscreen)
1246 {
1247   GtkWidget *menu;
1248   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1249
1250   menu = gtk_ui_manager_get_widget (priv->ui_manager,
1251             "/menubar1");
1252
1253   if (set_fullscreen)
1254     {
1255       gtk_widget_hide (priv->sidebar);
1256       gtk_widget_hide (menu);
1257       gtk_widget_hide (priv->vbox);
1258       gtk_widget_hide (priv->statusbar);
1259       gtk_widget_hide (priv->toolbar);
1260     }
1261   else
1262     {
1263       if (priv->sidebar_was_visible_before_fs)
1264         gtk_widget_show (priv->sidebar);
1265
1266       gtk_widget_show (menu);
1267       gtk_widget_show (priv->vbox);
1268       gtk_widget_show (priv->statusbar);
1269       gtk_widget_show (priv->toolbar);
1270
1271       gtk_window_resize (GTK_WINDOW (window), priv->original_width_before_fs,
1272           priv->original_height_before_fs);
1273     }
1274 }
1275
1276 static void
1277 show_borders (EmpathyCallWindow *window, gboolean set_fullscreen)
1278 {
1279   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1280
1281   gtk_container_set_border_width (GTK_CONTAINER (priv->content_hbox),
1282       set_fullscreen ? 0 : CONTENT_HBOX_BORDER_WIDTH);
1283   gtk_box_set_spacing (GTK_BOX (priv->content_hbox),
1284       set_fullscreen ? 0 : CONTENT_HBOX_SPACING);
1285   gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
1286       priv->video_output, TRUE, TRUE,
1287       set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
1288       GTK_PACK_START);
1289   gtk_box_set_child_packing (GTK_BOX (priv->content_hbox),
1290       priv->vbox, TRUE, TRUE,
1291       set_fullscreen ? 0 : CONTENT_HBOX_CHILDREN_PACKING_PADDING,
1292       GTK_PACK_START);
1293 }
1294
1295 static gboolean
1296 empathy_call_window_state_event_cb (GtkWidget *widget,
1297   GdkEventWindowState *event, EmpathyCallWindow *window)
1298 {
1299   if (event->changed_mask & GDK_WINDOW_STATE_FULLSCREEN)
1300     {
1301       EmpathyCallWindowPriv *priv = GET_PRIV (window);
1302       gboolean set_fullscreen = event->new_window_state & GDK_WINDOW_STATE_FULLSCREEN;
1303
1304       if (set_fullscreen)
1305         {
1306           gboolean sidebar_was_visible;
1307           gint original_width = GTK_WIDGET (window)->allocation.width;
1308           gint original_height = GTK_WIDGET (window)->allocation.height;
1309
1310           g_object_get (priv->sidebar, "visible", &sidebar_was_visible, NULL);
1311
1312           priv->sidebar_was_visible_before_fs = sidebar_was_visible;
1313           priv->original_width_before_fs = original_width;
1314           priv->original_height_before_fs = original_height;
1315
1316           if (priv->video_output_motion_handler_id == 0 &&
1317                 priv->video_output != NULL)
1318             {
1319               priv->video_output_motion_handler_id = g_signal_connect (
1320                   G_OBJECT (priv->video_output), "motion-notify-event",
1321                   G_CALLBACK (empathy_call_window_video_output_motion_notify), window);
1322             }
1323         }
1324       else
1325         {
1326           if (priv->video_output_motion_handler_id != 0)
1327             {
1328               g_signal_handler_disconnect (G_OBJECT (priv->video_output),
1329                   priv->video_output_motion_handler_id);
1330               priv->video_output_motion_handler_id = 0;
1331             }
1332         }
1333
1334       empathy_call_window_fullscreen_set_fullscreen (priv->fullscreen,
1335           set_fullscreen);
1336       show_controls (window, set_fullscreen);
1337       show_borders (window, set_fullscreen);
1338       priv->is_fullscreen = set_fullscreen;
1339   }
1340
1341   return FALSE;
1342 }
1343
1344 static void
1345 empathy_call_window_sidebar_toggled_cb (GtkToggleButton *toggle,
1346   EmpathyCallWindow *window)
1347 {
1348   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1349   GtkWidget *arrow;
1350   int w,h, handle_size;
1351
1352   w = GTK_WIDGET (window)->allocation.width;
1353   h = GTK_WIDGET (window)->allocation.height;
1354
1355   gtk_widget_style_get (priv->pane, "handle_size", &handle_size, NULL);
1356
1357   if (gtk_toggle_button_get_active (toggle))
1358     {
1359       arrow = gtk_arrow_new (GTK_ARROW_LEFT, GTK_SHADOW_NONE);
1360       gtk_widget_show (priv->sidebar);
1361       w += priv->sidebar->allocation.width + handle_size;
1362     }
1363   else
1364     {
1365       arrow = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
1366       w -= priv->sidebar->allocation.width + handle_size;
1367       gtk_widget_hide (priv->sidebar);
1368     }
1369
1370   gtk_button_set_image (GTK_BUTTON (priv->sidebar_button), arrow);
1371
1372   if (w > 0 && h > 0)
1373     gtk_window_resize (GTK_WINDOW (window), w, h);
1374 }
1375
1376 static void
1377 empathy_call_window_set_send_video (EmpathyCallWindow *window,
1378   gboolean send)
1379 {
1380   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1381   EmpathyTpCall *call;
1382
1383   g_object_get (priv->handler, "tp-call", &call, NULL);
1384   empathy_tp_call_request_video_stream_direction (call, send);
1385   g_object_unref (call);
1386 }
1387
1388 static void
1389 empathy_call_window_camera_toggled_cb (GtkToggleToolButton *toggle,
1390   EmpathyCallWindow *window)
1391 {
1392   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1393   gboolean active;
1394
1395   active = (gtk_toggle_tool_button_get_active (toggle));
1396
1397   if (priv->sending_video == active)
1398     return;
1399   priv->sending_video = active;
1400
1401   empathy_call_window_set_send_video (window, active);
1402   gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video), active);
1403 }
1404
1405 static void
1406 empathy_call_window_send_video_toggled_cb (GtkToggleAction *toggle,
1407   EmpathyCallWindow *window)
1408 {
1409   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1410   gboolean active;
1411
1412   active = (gtk_toggle_action_get_active (toggle));
1413
1414   if (priv->sending_video == active)
1415     return;
1416   priv->sending_video = active;
1417
1418   empathy_call_window_set_send_video (window, active);
1419   gtk_toggle_tool_button_set_active (
1420       GTK_TOGGLE_TOOL_BUTTON (priv->camera_button), active);
1421 }
1422
1423 static void
1424 empathy_call_window_show_preview_toggled_cb (GtkToggleAction *toggle,
1425   EmpathyCallWindow *window)
1426 {
1427   /* FIXME: Not implemented */
1428 }
1429
1430 static void
1431 empathy_call_window_mic_toggled_cb (GtkToggleToolButton *toggle,
1432   EmpathyCallWindow *window)
1433 {
1434   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1435   gboolean active;
1436
1437   active = (gtk_toggle_tool_button_get_active (toggle));
1438
1439   if (active)
1440     {
1441       empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
1442         priv->volume);
1443       gtk_adjustment_set_value (priv->audio_input_adj, priv->volume * 100);
1444     }
1445   else
1446     {
1447       /* TODO, Instead of setting the input volume to 0 we should probably
1448        * stop sending but this would cause the audio call to drop if both
1449        * sides mute at the same time on certain CMs AFAIK. Need to revisit this
1450        * in the future. GNOME #574574
1451        */
1452       empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (priv->audio_input),
1453         0);
1454       gtk_adjustment_set_value (priv->audio_input_adj, 0);
1455     }
1456 }
1457
1458 static void
1459 empathy_call_window_sidebar_hidden_cb (EmpathySidebar *sidebar,
1460   EmpathyCallWindow *window)
1461 {
1462   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1463
1464   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->sidebar_button),
1465     FALSE);
1466 }
1467
1468 static void
1469 empathy_call_window_sidebar_shown_cb (EmpathySidebar *sidebar,
1470   EmpathyCallWindow *window)
1471 {
1472   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1473
1474   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->sidebar_button),
1475     TRUE);
1476 }
1477
1478 static void
1479 empathy_call_window_hangup_cb (gpointer object,
1480                                EmpathyCallWindow *window)
1481 {
1482   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1483
1484   gst_element_set_state (priv->pipeline, GST_STATE_NULL);
1485   gtk_widget_destroy (GTK_WIDGET (window));
1486 }
1487
1488 static void
1489 empathy_call_window_fullscreen_cb (gpointer object,
1490                                    EmpathyCallWindow *window)
1491 {
1492   empathy_call_window_fullscreen_toggle (window);
1493 }
1494
1495 static void
1496 empathy_call_window_fullscreen_toggle (EmpathyCallWindow *window)
1497 {
1498   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1499
1500   if (priv->is_fullscreen)
1501     gtk_window_unfullscreen (GTK_WINDOW (window));
1502   else
1503     gtk_window_fullscreen (GTK_WINDOW (window));
1504 }
1505
1506 static gboolean
1507 empathy_call_window_video_button_press_cb (GtkWidget *video_output,
1508   GdkEventButton *event, EmpathyCallWindow *window)
1509 {
1510   if (event->button == 3 && event->type == GDK_BUTTON_PRESS)
1511     {
1512       empathy_call_window_video_menu_popup (window, event->button);
1513       return TRUE;
1514     }
1515
1516   return FALSE;
1517 }
1518
1519 static gboolean
1520 empathy_call_window_key_press_cb (GtkWidget *video_output,
1521   GdkEventKey *event, EmpathyCallWindow *window)
1522 {
1523   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1524
1525   if (priv->is_fullscreen && event->keyval == GDK_Escape)
1526     {
1527       /* Since we are in fullscreen mode, toggling will bring us back to
1528          normal mode. */
1529       empathy_call_window_fullscreen_toggle (window);
1530       return TRUE;
1531     }
1532
1533   return FALSE;
1534 }
1535
1536 static gboolean
1537 empathy_call_window_video_output_motion_notify (GtkWidget *widget,
1538     GdkEventMotion *event, EmpathyCallWindow *window)
1539 {
1540   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1541
1542   if (priv->is_fullscreen)
1543     {
1544       empathy_call_window_fullscreen_show_popup (priv->fullscreen);
1545       return TRUE;
1546     }
1547   
1548   return FALSE;
1549 }
1550
1551 static void
1552 empathy_call_window_video_menu_popup (EmpathyCallWindow *window,
1553   guint button)
1554 {
1555   GtkWidget *menu;
1556   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1557
1558   menu = gtk_ui_manager_get_widget (priv->ui_manager,
1559             "/video-popup");
1560   gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL,
1561                         button, gtk_get_current_event_time ());
1562         gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE);
1563 }
1564
1565 static void
1566 empathy_call_window_status_message (EmpathyCallWindow *window,
1567   gchar *message)
1568 {
1569   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1570
1571   if (priv->context_id == 0)
1572     {
1573       priv->context_id = gtk_statusbar_get_context_id (
1574         GTK_STATUSBAR (priv->statusbar), "voip call status messages");
1575     }
1576   else
1577     {
1578       gtk_statusbar_pop (GTK_STATUSBAR (priv->statusbar), priv->context_id);
1579     }
1580
1581   gtk_statusbar_push (GTK_STATUSBAR (priv->statusbar), priv->context_id,
1582     message);
1583 }
1584
1585 static void
1586 empathy_call_window_volume_changed_cb (GtkScaleButton *button,
1587   gdouble value, EmpathyCallWindow *window)
1588 {
1589   EmpathyCallWindowPriv *priv = GET_PRIV (window);
1590
1591   empathy_audio_sink_set_volume (EMPATHY_GST_AUDIO_SINK (priv->audio_output),
1592     value);
1593 }