]> git.0d.be Git - empathy.git/blobdiff - src/empathy-call-window.c
Updated Polish translation
[empathy.git] / src / empathy-call-window.c
index 72cee8affc9eb039be444083faf947b665dfe5bd..8b7af2dcf0a0715158b8dfed25e539e99ef56ff5 100644 (file)
 #include <libempathy-gtk/empathy-video-src.h>
 #include <libempathy-gtk/empathy-ui-utils.h>
 #include <libempathy-gtk/empathy-sound.h>
+#include <libempathy-gtk/empathy-geometry.h>
+
+#define DEBUG_FLAG EMPATHY_DEBUG_VOIP
+#include <libempathy/empathy-debug.h>
 
 #include "empathy-call-window.h"
 #include "empathy-call-window-fullscreen.h"
@@ -92,6 +96,12 @@ typedef enum {
   REDIALING
 } CallState;
 
+typedef enum {
+  CAMERA_STATE_OFF = 0,
+  CAMERA_STATE_PREVIEW,
+  CAMERA_STATE_ON,
+} CameraState;
+
 /* private structure */
 typedef struct _EmpathyCallWindowPriv EmpathyCallWindowPriv;
 
@@ -116,13 +126,15 @@ struct _EmpathyCallWindowPriv
   GtkWidget *volume_button;
   GtkWidget *redial_button;
   GtkWidget *mic_button;
-  GtkWidget *camera_button;
   GtkWidget *toolbar;
   GtkWidget *pane;
-  GtkAction *show_preview;
-  GtkAction *send_video;
   GtkAction *redial;
   GtkAction *menu_fullscreen;
+  GtkAction *action_camera;
+  GtkAction *action_camera_preview;
+  GtkWidget *tool_button_camera_off;
+  GtkWidget *tool_button_camera_preview;
+  GtkWidget *tool_button_camera_on;
 
   /* The frames and boxes that contain self and remote avatar and video
      input/output. When we redial, we destroy and re-create the boxes */
@@ -173,6 +185,7 @@ struct _EmpathyCallWindowPriv
   GMutex *lock;
   gboolean call_started;
   gboolean sending_video;
+  CameraState camera_state;
 
   EmpathyCallWindowFullscreen *fullscreen;
   gboolean is_fullscreen;
@@ -200,18 +213,9 @@ static gboolean empathy_call_window_state_event_cb (GtkWidget *widget,
 static void empathy_call_window_sidebar_toggled_cb (GtkToggleButton *toggle,
   EmpathyCallWindow *window);
 
-static void empathy_call_window_camera_toggled_cb (GtkToggleToolButton *toggle,
-  EmpathyCallWindow *window);
-
 static void empathy_call_window_set_send_video (EmpathyCallWindow *window,
   gboolean send);
 
-static void empathy_call_window_send_video_toggled_cb (GtkToggleAction *toggle,
-  EmpathyCallWindow *window);
-
-static void empathy_call_window_show_preview_toggled_cb (
-  GtkToggleAction *toggle, EmpathyCallWindow *window);
-
 static void empathy_call_window_mic_toggled_cb (
   GtkToggleToolButton *toggle, EmpathyCallWindow *window);
 
@@ -259,11 +263,32 @@ static void
 empathy_call_window_volume_changed_cb (GtkScaleButton *button,
   gdouble value, EmpathyCallWindow *window);
 
+static void block_camera_control_signals (EmpathyCallWindow *self);
+static void unblock_camera_control_signals (EmpathyCallWindow *self);
+
 static void
 empathy_call_window_setup_toolbar (EmpathyCallWindow *self)
 {
   EmpathyCallWindowPriv *priv = GET_PRIV (self);
   GtkToolItem *tool_item;
+  GtkWidget *camera_off_icon;
+  GdkPixbuf *pixbuf, *modded_pixbuf;
+
+  /* set the icon of the 'camera off' button by greying off the webcam icon */
+  pixbuf = empathy_pixbuf_from_icon_name ("camera-web",
+      GTK_ICON_SIZE_SMALL_TOOLBAR);
+
+  modded_pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8,
+      gdk_pixbuf_get_width (pixbuf),
+      gdk_pixbuf_get_height (pixbuf));
+
+  gdk_pixbuf_saturate_and_pixelate (pixbuf, modded_pixbuf, 1.0, TRUE);
+  g_object_unref (pixbuf);
+
+  camera_off_icon = gtk_image_new_from_pixbuf (modded_pixbuf);
+  g_object_unref (modded_pixbuf);
+  gtk_tool_button_set_icon_widget (GTK_TOOL_BUTTON (
+        priv->tool_button_camera_off), camera_off_icon);
 
   /* Add an empty expanded GtkToolItem so the volume button is at the end of
    * the toolbar. */
@@ -642,6 +667,7 @@ empathy_call_window_setup_video_preview (EmpathyCallWindow *window)
       return;
     }
 
+  DEBUG ("Create video preview");
   g_assert (priv->video_tee == NULL);
 
   priv->video_tee = gst_element_factory_make ("tee", NULL);
@@ -668,12 +694,39 @@ empathy_call_window_setup_video_preview (EmpathyCallWindow *window)
   gst_element_set_state (priv->video_tee, GST_STATE_PLAYING);
 }
 
+static void
+display_video_preview (EmpathyCallWindow *self,
+    gboolean display)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+
+  if (display)
+    {
+      /* Display the preview and hide the self avatar */
+      DEBUG ("Show video preview");
+
+      if (priv->video_preview == NULL)
+        empathy_call_window_setup_video_preview (self);
+      gtk_widget_show (priv->video_preview);
+      gtk_widget_hide (priv->self_user_avatar_widget);
+    }
+  else
+    {
+      /* Display the self avatar and hide the preview */
+      DEBUG ("Show self avatar");
+
+      if (priv->video_preview != NULL)
+        gtk_widget_hide (priv->video_preview);
+      gtk_widget_show (priv->self_user_avatar_widget);
+    }
+}
+
 static void
 empathy_call_window_set_state_connecting (EmpathyCallWindow *window)
 {
   EmpathyCallWindowPriv *priv = GET_PRIV (window);
 
-  empathy_call_window_status_message (window, _("Connecting..."));
+  empathy_call_window_status_message (window, _("Connecting"));
   priv->call_state = CONNECTING;
 
   if (priv->outgoing)
@@ -681,6 +734,188 @@ empathy_call_window_set_state_connecting (EmpathyCallWindow *window)
         EMPATHY_SOUND_PHONE_OUTGOING, MS_BETWEEN_RING);
 }
 
+static void
+disable_camera (EmpathyCallWindow *self)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+
+  if (priv->camera_state == CAMERA_STATE_OFF)
+    return;
+
+  DEBUG ("Disable camera");
+
+  display_video_preview (self, FALSE);
+
+  if (priv->camera_state == CAMERA_STATE_ON)
+    empathy_call_window_set_send_video (self, FALSE);
+
+  block_camera_control_signals (self);
+  gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
+        priv->tool_button_camera_on), FALSE);
+  gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
+      priv->tool_button_camera_preview), FALSE);
+
+  gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
+      priv->tool_button_camera_off), TRUE);
+  gtk_radio_action_set_current_value (GTK_RADIO_ACTION (priv->action_camera),
+      CAMERA_STATE_OFF);
+  unblock_camera_control_signals (self);
+
+  priv->camera_state = CAMERA_STATE_OFF;
+}
+
+static void
+tool_button_camera_off_toggled_cb (GtkToggleToolButton *toggle,
+  EmpathyCallWindow *self)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+
+  if (!gtk_toggle_tool_button_get_active (toggle))
+    {
+      if (priv->camera_state == CAMERA_STATE_OFF)
+        {
+          /* We can't change the state by disabling the button */
+          block_camera_control_signals (self);
+          gtk_toggle_tool_button_set_active (toggle, TRUE);
+          unblock_camera_control_signals (self);
+        }
+
+      return;
+    }
+
+  disable_camera (self);
+}
+
+static void
+enable_preview (EmpathyCallWindow *self)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+
+  if (priv->camera_state == CAMERA_STATE_PREVIEW)
+    return;
+
+  DEBUG ("Enable preview");
+
+  if (priv->camera_state == CAMERA_STATE_ON)
+    /* preview is already displayed so we just have to stop sending */
+    empathy_call_window_set_send_video (self, FALSE);
+
+  display_video_preview (self, TRUE);
+
+  block_camera_control_signals (self);
+  gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
+      priv->tool_button_camera_off), FALSE);
+  gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
+        priv->tool_button_camera_on), FALSE);
+
+  gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
+        priv->tool_button_camera_preview), TRUE);
+  gtk_radio_action_set_current_value (GTK_RADIO_ACTION (priv->action_camera),
+      CAMERA_STATE_PREVIEW);
+  unblock_camera_control_signals (self);
+
+  priv->camera_state = CAMERA_STATE_PREVIEW;
+}
+
+static void
+tool_button_camera_preview_toggled_cb (GtkToggleToolButton *toggle,
+  EmpathyCallWindow *self)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+
+  if (!gtk_toggle_tool_button_get_active (toggle))
+    {
+      if (priv->camera_state == CAMERA_STATE_PREVIEW)
+        {
+          /* We can't change the state by disabling the button */
+          block_camera_control_signals (self);
+          gtk_toggle_tool_button_set_active (toggle, TRUE);
+          unblock_camera_control_signals (self);
+        }
+
+      return;
+    }
+
+  enable_preview (self);
+}
+
+static void
+enable_camera (EmpathyCallWindow *self)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+
+  if (priv->camera_state == CAMERA_STATE_ON)
+    return;
+
+  DEBUG ("Enable camera");
+
+  empathy_call_window_set_send_video (self, TRUE);
+
+  block_camera_control_signals (self);
+  gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
+      priv->tool_button_camera_off), FALSE);
+  gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
+        priv->tool_button_camera_preview), FALSE);
+
+  gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (
+      priv->tool_button_camera_on), TRUE);
+  gtk_radio_action_set_current_value (GTK_RADIO_ACTION (priv->action_camera),
+      CAMERA_STATE_ON);
+  unblock_camera_control_signals (self);
+
+  priv->camera_state = CAMERA_STATE_ON;
+}
+
+static void
+tool_button_camera_on_toggled_cb (GtkToggleToolButton *toggle,
+  EmpathyCallWindow *self)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+
+  if (!gtk_toggle_tool_button_get_active (toggle))
+    {
+      if (priv->camera_state == CAMERA_STATE_ON)
+        {
+          /* We can't change the state by disabling the button */
+          block_camera_control_signals (self);
+          gtk_toggle_tool_button_set_active (toggle, TRUE);
+          unblock_camera_control_signals (self);
+        }
+
+      return;
+    }
+
+  enable_camera (self);
+}
+
+static void
+action_camera_change_cb (GtkRadioAction *action,
+    GtkRadioAction *current,
+    EmpathyCallWindow *self)
+{
+  CameraState state;
+
+  state = gtk_radio_action_get_current_value (current);
+
+  switch (state)
+    {
+      case CAMERA_STATE_OFF:
+        disable_camera (self);
+        break;
+
+      case CAMERA_STATE_PREVIEW:
+        enable_preview (self);
+        break;
+
+      case CAMERA_STATE_ON:
+        enable_camera (self);
+        break;
+
+      default:
+        g_assert_not_reached ();
+    }
+}
+
 static void
 empathy_call_window_init (EmpathyCallWindow *self)
 {
@@ -703,13 +938,15 @@ empathy_call_window_init (EmpathyCallWindow *self)
     "statusbar", &priv->statusbar,
     "redial", &priv->redial_button,
     "microphone", &priv->mic_button,
-    "camera", &priv->camera_button,
     "toolbar", &priv->toolbar,
-    "send_video", &priv->send_video,
     "menuredial", &priv->redial,
-    "show_preview", &priv->show_preview,
     "ui_manager", &priv->ui_manager,
     "menufullscreen", &priv->menu_fullscreen,
+    "camera_off", &priv->tool_button_camera_off,
+    "camera_preview", &priv->tool_button_camera_preview,
+    "camera_on", &priv->tool_button_camera_on,
+    "action_camera_off",  &priv->action_camera,
+    "action_camera_preview",  &priv->action_camera_preview,
     NULL);
   g_free (filename);
 
@@ -719,10 +956,11 @@ empathy_call_window_init (EmpathyCallWindow *self)
     "menuredial", "activate", empathy_call_window_redial_cb,
     "redial", "clicked", empathy_call_window_redial_cb,
     "microphone", "toggled", empathy_call_window_mic_toggled_cb,
-    "camera", "toggled", empathy_call_window_camera_toggled_cb,
-    "send_video", "toggled", empathy_call_window_send_video_toggled_cb,
-    "show_preview", "toggled", empathy_call_window_show_preview_toggled_cb,
     "menufullscreen", "activate", empathy_call_window_fullscreen_cb,
+    "camera_off", "toggled", tool_button_camera_off_toggled_cb,
+    "camera_preview", "toggled", tool_button_camera_preview_toggled_cb,
+    "camera_on", "toggled", tool_button_camera_on_toggled_cb,
+    "action_camera_off", "changed", action_camera_change_cb,
     NULL);
 
   priv->lock = g_mutex_new ();
@@ -839,6 +1077,8 @@ empathy_call_window_init (EmpathyCallWindow *self)
 
   g_object_ref (priv->ui_manager);
   g_object_unref (gui);
+
+  empathy_geometry_bind (GTK_WINDOW (self), "call-window");
 }
 
 /* Instead of specifying a width and a height, we specify only one size. That's
@@ -974,18 +1214,6 @@ empathy_call_window_setup_avatars (EmpathyCallWindow *self,
   gtk_widget_show (priv->remote_user_avatar_widget);
 }
 
-static void
-empathy_call_window_setup_video_preview_visibility (EmpathyCallWindow *self,
-    EmpathyCallHandler *handler)
-{
-  EmpathyCallWindowPriv *priv = GET_PRIV (self);
-  gboolean initial_video =
-    empathy_call_handler_has_initial_video (priv->handler);
-
-  gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->show_preview),
-      initial_video);
-}
-
 static void
 empathy_call_window_constructed (GObject *object)
 {
@@ -1001,8 +1229,15 @@ empathy_call_window_constructed (GObject *object)
     g_object_unref (call);
 
   empathy_call_window_setup_avatars (self, priv->handler);
-  empathy_call_window_setup_video_preview_visibility (self, priv->handler);
   empathy_call_window_set_state_connecting (self);
+
+  if (!empathy_call_handler_has_initial_video (priv->handler))
+    {
+      gtk_toggle_tool_button_set_active (
+          GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_off), TRUE);
+    }
+  /* If call has InitialVideo, the preview will be started once the call has
+   * been started (start_call()). */
 }
 
 static void empathy_call_window_dispose (GObject *object);
@@ -1069,6 +1304,7 @@ static void
 empathy_call_window_video_stream_changed_cb (EmpathyTpCall *call,
     GParamSpec *property, EmpathyCallWindow *self)
 {
+  DEBUG ("video stream changed");
   empathy_call_window_update_avatars_visibility (call, self);
 }
 
@@ -1196,7 +1432,7 @@ empathy_call_window_request_resource_cb (EmpathyCallHandler *handler,
   EmpathyCallWindow *self = EMPATHY_CALL_WINDOW (user_data);
   EmpathyCallWindowPriv *priv = GET_PRIV (self);
 
-  if (type != TP_MEDIA_STREAM_TYPE_VIDEO)
+  if (type != FS_MEDIA_TYPE_VIDEO)
     return TRUE;
 
   if (direction == FS_DIRECTION_RECV)
@@ -1282,8 +1518,6 @@ empathy_call_window_disconnected (EmpathyCallWindow *self)
 
   if (could_reset_pipeline)
     {
-      gboolean initial_video = empathy_call_handler_has_initial_video (
-          priv->handler);
       g_mutex_lock (priv->lock);
 
       g_timer_stop (priv->timer);
@@ -1301,19 +1535,19 @@ empathy_call_window_disconnected (EmpathyCallWindow *self)
 
       /* Reseting the send_video, camera_buton and mic_button to their
          initial state */
-      gtk_widget_set_sensitive (priv->camera_button, FALSE);
+      gtk_widget_set_sensitive (priv->tool_button_camera_on, FALSE);
       gtk_widget_set_sensitive (priv->mic_button, FALSE);
-      gtk_action_set_sensitive (priv->send_video, FALSE);
-      gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video),
-          initial_video);
       gtk_toggle_tool_button_set_active (
-          GTK_TOGGLE_TOOL_BUTTON (priv->camera_button), initial_video);
+          GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_off), TRUE);
       gtk_toggle_tool_button_set_active (
           GTK_TOGGLE_TOOL_BUTTON (priv->mic_button), TRUE);
 
-      gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->show_preview),
-          FALSE);
-      gtk_action_set_sensitive (priv->show_preview, FALSE);
+      /* FIXME: This is to workaround the fact that the pipeline has been
+       * destroyed and so we can't display preview until a new call (and so a
+       * new pipeline) is created. We should fix this properly by refactoring
+       * the code managing the pipeline. This is bug #602937 */
+      gtk_widget_set_sensitive (priv->tool_button_camera_preview, FALSE);
+      gtk_action_set_sensitive (priv->action_camera_preview, FALSE);
 
       gtk_progress_bar_set_fraction (
           GTK_PROGRESS_BAR (priv->volume_progress_bar), 0);
@@ -1325,6 +1559,9 @@ empathy_call_window_disconnected (EmpathyCallWindow *self)
       priv->call_started = FALSE;
 
       could_disconnect = TRUE;
+
+      /* TODO: display the self avatar of the preview (depends if the "Always
+       * Show Video Preview" is enabled or not) */
     }
 
   return could_disconnect;
@@ -1464,6 +1701,7 @@ empathy_call_window_update_timer (gpointer user_data)
 
 static void
 display_error (EmpathyCallWindow *self,
+    EmpathyTpCall *call,
     const gchar *img,
     const gchar *title,
     const gchar *desc,
@@ -1472,6 +1710,7 @@ display_error (EmpathyCallWindow *self,
   EmpathyCallWindowPriv *priv = GET_PRIV (self);
   GtkWidget *info_bar;
   GtkWidget *content_area;
+  GtkWidget *hbox;
   GtkWidget *vbox;
   GtkWidget *image;
   GtkWidget *label;
@@ -1481,27 +1720,32 @@ display_error (EmpathyCallWindow *self,
   info_bar = gtk_info_bar_new_with_buttons (GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
       NULL);
 
-  gtk_widget_set_no_show_all (info_bar, TRUE);
   gtk_info_bar_set_message_type (GTK_INFO_BAR (info_bar), GTK_MESSAGE_WARNING);
 
   content_area = gtk_info_bar_get_content_area (GTK_INFO_BAR (info_bar));
 
+  /* hbox containing the image and the messages vbox */
+  hbox = gtk_hbox_new (FALSE, 3);
+  gtk_container_add (GTK_CONTAINER (content_area), hbox);
+
   /* Add image */
   image = gtk_image_new_from_icon_name (img, GTK_ICON_SIZE_DIALOG);
-  gtk_widget_show (image);
-  gtk_container_add (GTK_CONTAINER (content_area), image);
+  gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0);
 
+  /* vbox containing the main message and the details expander */
   vbox = gtk_vbox_new (FALSE, 3);
-  gtk_container_add (GTK_CONTAINER (content_area), vbox);
+  gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0);
 
   /* Add text */
   txt = g_strdup_printf ("<b>%s</b>\n%s", title, desc);
 
   label = gtk_label_new (NULL);
   gtk_label_set_markup (GTK_LABEL (label), txt);
+  gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
+  gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
   g_free (txt);
 
-  gtk_container_add (GTK_CONTAINER (vbox), label);
+  gtk_box_pack_start (GTK_BOX (vbox), label, TRUE, TRUE, 0);
 
   /* Add details */
   if (details != NULL)
@@ -1519,7 +1763,7 @@ display_error (EmpathyCallWindow *self,
       g_free (txt);
 
       gtk_container_add (GTK_CONTAINER (expander), label);
-      gtk_container_add (GTK_CONTAINER (vbox), expander);
+      gtk_box_pack_start (GTK_BOX (vbox), expander, TRUE, TRUE, 0);
     }
 
   g_signal_connect (info_bar, "response",
@@ -1527,16 +1771,19 @@ display_error (EmpathyCallWindow *self,
 
   gtk_box_pack_start (GTK_BOX (priv->errors_vbox), info_bar,
       FALSE, FALSE, CONTENT_HBOX_CHILDREN_PACKING_PADDING);
-  gtk_widget_show_all (vbox);
-  gtk_widget_show (info_bar);
+  gtk_widget_show_all (info_bar);
 }
 
 static gchar *
 media_stream_error_to_txt (EmpathyCallWindow *self,
+    EmpathyTpCall *call,
     gboolean audio,
     TpMediaStreamError error)
 {
   EmpathyCallWindowPriv *priv = GET_PRIV (self);
+  const gchar *cm;
+  gchar *url;
+  gchar *result;
 
   switch (error)
     {
@@ -1559,7 +1806,34 @@ media_stream_error_to_txt (EmpathyCallWindow *self,
               "direct connections."),
           empathy_contact_get_name (priv->contact));
 
-      /* TODO: support more errors */
+      case TP_MEDIA_STREAM_ERROR_NETWORK_ERROR:
+          return g_strdup (_("There was a failure on the network"));
+
+      case TP_MEDIA_STREAM_ERROR_NO_CODECS:
+        if (audio)
+          return g_strdup (_("The audio formats necessary for this call "
+                "are not installed on your computer"));
+        else
+          return g_strdup (_("The video formats necessary for this call "
+                "are not installed on your computer"));
+
+      case TP_MEDIA_STREAM_ERROR_INVALID_CM_BEHAVIOR:
+        cm = empathy_tp_call_get_connection_manager (call);
+
+        url = g_strdup_printf ("http://bugs.freedesktop.org/enter_bug.cgi?"
+            "product=Telepathy&amp;component=%s", cm);
+
+        result = g_strdup_printf (
+            _("Something unexpected happened in a Telepathy component. "
+              "Please <a href=\"%s\">report this bug</a> and attach "
+              "logs gathered from the 'Debug' window in the Help menu."), url);
+
+        g_free (url);
+        return result;
+
+      case TP_MEDIA_STREAM_ERROR_MEDIA_ERROR:
+        return g_strdup (_("There was a failure in the call engine"));
+
       default:
         return NULL;
     }
@@ -1567,6 +1841,7 @@ media_stream_error_to_txt (EmpathyCallWindow *self,
 
 static void
 empathy_call_window_stream_error (EmpathyCallWindow *self,
+    EmpathyTpCall *call,
     gboolean audio,
     guint code,
     const gchar *msg,
@@ -1575,16 +1850,16 @@ empathy_call_window_stream_error (EmpathyCallWindow *self,
 {
   gchar *desc;
 
-  desc = media_stream_error_to_txt (self, audio, code);
+  desc = media_stream_error_to_txt (self, call, audio, code);
   if (desc == NULL)
     {
       /* No description, use the error message. That's not great as it's not
        * localized but it's better than nothing. */
-      display_error (self, icon, title, msg, NULL);
+      display_error (self, call, icon, title, msg, NULL);
     }
   else
     {
-      display_error (self, icon, title, desc, msg);
+      display_error (self, call, icon, title, desc, msg);
       g_free (desc);
     }
 }
@@ -1595,7 +1870,7 @@ empathy_call_window_audio_stream_error (EmpathyTpCall *call,
     const gchar *msg,
     EmpathyCallWindow *self)
 {
-  empathy_call_window_stream_error (self, TRUE, code, msg,
+  empathy_call_window_stream_error (self, call, TRUE, code, msg,
       "gnome-stock-mic", _("Can't establish audio stream"));
 }
 
@@ -1605,7 +1880,7 @@ empathy_call_window_video_stream_error (EmpathyTpCall *call,
     const gchar *msg,
     EmpathyCallWindow *self)
 {
-  empathy_call_window_stream_error (self, FALSE, code, msg,
+  empathy_call_window_stream_error (self, call, FALSE, code, msg,
       "camera-web", _("Can't establish video stream"));
 }
 
@@ -1636,24 +1911,21 @@ empathy_call_window_connected (gpointer user_data)
   priv->sending_video = can_send_video ?
     empathy_tp_call_is_sending_video (call) : FALSE;
 
-  gtk_action_set_sensitive (priv->show_preview, TRUE);
-  gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->show_preview),
-      priv->sending_video
-      || gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (
-              priv->show_preview)));
-  gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video),
-      priv->sending_video && priv->video_input != NULL);
   gtk_toggle_tool_button_set_active (
-      GTK_TOGGLE_TOOL_BUTTON (priv->camera_button),
+      GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_on),
       priv->sending_video && priv->video_input != NULL);
-  gtk_widget_set_sensitive (priv->camera_button, can_send_video);
-  gtk_action_set_sensitive (priv->send_video, can_send_video);
+  gtk_widget_set_sensitive (priv->tool_button_camera_on, can_send_video);
 
   gtk_action_set_sensitive (priv->redial, FALSE);
   gtk_widget_set_sensitive (priv->redial_button, FALSE);
 
   gtk_widget_set_sensitive (priv->mic_button, TRUE);
 
+  /* FIXME: this should won't be needed once bug #602937 is fixed
+   * (see empathy_call_window_disconnected for details) */
+  gtk_widget_set_sensitive (priv->tool_button_camera_preview, TRUE);
+  gtk_action_set_sensitive (priv->action_camera_preview, TRUE);
+
   empathy_call_window_update_avatars_visibility (call, self);
 
   g_object_unref (call);
@@ -1732,23 +2004,6 @@ empathy_call_window_sink_added_cb (EmpathyCallHandler *handler,
       case TP_MEDIA_STREAM_TYPE_VIDEO:
         if (priv->video_input != NULL)
           {
-            EmpathyTpCall *call;
-            g_object_get (priv->handler, "tp-call", &call, NULL);
-
-            if (empathy_tp_call_is_sending_video (call))
-              {
-                empathy_call_window_setup_video_preview (self);
-
-                gtk_toggle_action_set_active (
-                    GTK_TOGGLE_ACTION (priv->show_preview), TRUE);
-
-                if (priv->video_preview != NULL)
-                  gtk_widget_show (priv->video_preview);
-                gtk_widget_hide (priv->self_user_avatar_widget);
-              }
-
-            g_object_unref (call);
-
             if (priv->video_tee != NULL)
               {
                 pad = gst_element_get_request_pad (priv->video_tee, "src%d");
@@ -1768,6 +2023,7 @@ empathy_call_window_remove_video_input (EmpathyCallWindow *self)
   EmpathyCallWindowPriv *priv = GET_PRIV (self);
   GstElement *preview;
 
+  DEBUG ("remove video input");
   preview = empathy_video_widget_get_element (
     EMPATHY_VIDEO_WIDGET (priv->video_preview));
 
@@ -1785,15 +2041,29 @@ empathy_call_window_remove_video_input (EmpathyCallWindow *self)
   gtk_widget_destroy (priv->video_preview);
   priv->video_preview = NULL;
 
-  gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video), FALSE);
   gtk_toggle_tool_button_set_active (
-      GTK_TOGGLE_TOOL_BUTTON (priv->camera_button), FALSE);
-  gtk_widget_set_sensitive (priv->camera_button, FALSE);
-  gtk_action_set_sensitive (priv->send_video, FALSE);
+      GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_on), FALSE);
+  gtk_widget_set_sensitive (priv->tool_button_camera_on, FALSE);
 
   gtk_widget_show (priv->self_user_avatar_widget);
 }
 
+static void
+start_call (EmpathyCallWindow *self)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+
+  priv->call_started = TRUE;
+  empathy_call_handler_start_call (priv->handler);
+  gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
+
+  if (empathy_call_handler_has_initial_video (priv->handler))
+    {
+      /* Enable 'send video' buttons and display the preview */
+      gtk_toggle_tool_button_set_active (
+          GTK_TOGGLE_TOOL_BUTTON (priv->tool_button_camera_on), TRUE);
+    }
+}
 
 static gboolean
 empathy_call_window_bus_message (GstBus *bus, GstMessage *message,
@@ -1820,9 +2090,7 @@ empathy_call_window_bus_message (GstBus *bus, GstMessage *message,
             gst_message_parse_state_changed (message, NULL, &newstate, NULL);
             if (newstate == GST_STATE_PAUSED)
               {
-                priv->call_started = TRUE;
-                empathy_call_handler_start_call (priv->handler);
-                gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
+                start_call (self);
               }
           }
         break;
@@ -1859,28 +2127,6 @@ empathy_call_window_bus_message (GstBus *bus, GstMessage *message,
   return TRUE;
 }
 
-static void
-empathy_call_window_update_self_avatar_visibility (EmpathyCallWindow *window)
-{
-  EmpathyCallWindowPriv *priv = GET_PRIV (window);
-
-  if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (priv->show_preview)))
-    {
-      if (priv->video_preview != NULL)
-        {
-          gtk_widget_hide (priv->self_user_avatar_widget);
-          gtk_widget_show (priv->video_preview);
-        }
-      else
-        {
-          if (priv->video_preview != NULL)
-            gtk_widget_hide (priv->video_preview);
-
-          gtk_widget_show (priv->self_user_avatar_widget);
-        }
-    }
-}
-
 static void
 empathy_call_window_update_avatars_visibility (EmpathyTpCall *call,
     EmpathyCallWindow *window)
@@ -1897,8 +2143,6 @@ empathy_call_window_update_avatars_visibility (EmpathyTpCall *call,
       gtk_widget_hide (priv->video_output);
       gtk_widget_show (priv->remote_user_avatar_widget);
     }
-
-  empathy_call_window_update_self_avatar_visibility (window);
 }
 
 static void
@@ -2048,8 +2292,12 @@ empathy_call_window_state_event_cb (GtkWidget *widget,
       if (set_fullscreen)
         {
           gboolean sidebar_was_visible;
-          gint original_width = GTK_WIDGET (window)->allocation.width;
-          gint original_height = GTK_WIDGET (window)->allocation.height;
+          GtkAllocation allocation;
+          gint original_width, original_height;
+
+          gtk_widget_get_allocation (GTK_WIDGET (window), &allocation);
+          original_width = allocation.width;
+          original_height = allocation.height;
 
           g_object_get (priv->sidebar, "visible", &sidebar_was_visible, NULL);
 
@@ -2094,23 +2342,26 @@ empathy_call_window_sidebar_toggled_cb (GtkToggleButton *toggle,
 {
   EmpathyCallWindowPriv *priv = GET_PRIV (window);
   GtkWidget *arrow;
-  int w,h, handle_size;
+  int w, h, handle_size;
+  GtkAllocation allocation, sidebar_allocation;
 
-  w = GTK_WIDGET (window)->allocation.width;
-  h = GTK_WIDGET (window)->allocation.height;
+  gtk_widget_get_allocation (GTK_WIDGET (window), &allocation);
+  w = allocation.width;
+  h = allocation.height;
 
   gtk_widget_style_get (priv->pane, "handle_size", &handle_size, NULL);
 
+  gtk_widget_get_allocation (priv->sidebar, &sidebar_allocation);
   if (gtk_toggle_button_get_active (toggle))
     {
       arrow = gtk_arrow_new (GTK_ARROW_LEFT, GTK_SHADOW_NONE);
       gtk_widget_show (priv->sidebar);
-      w += priv->sidebar->allocation.width + handle_size;
+      w += sidebar_allocation.width + handle_size;
     }
   else
     {
       arrow = gtk_arrow_new (GTK_ARROW_RIGHT, GTK_SHADOW_NONE);
-      w -= priv->sidebar->allocation.width + handle_size;
+      w -= sidebar_allocation.width + handle_size;
       gtk_widget_hide (priv->sidebar);
     }
 
@@ -2131,76 +2382,15 @@ empathy_call_window_set_send_video (EmpathyCallWindow *window,
 
   /* When we start sending video, we want to show the video preview by
      default. */
-  if (send)
-    {
-      empathy_call_window_setup_video_preview (window);
-      gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->show_preview),
-          TRUE);
-    }
-
-  g_object_get (priv->handler, "tp-call", &call, NULL);
-  empathy_tp_call_request_video_stream_direction (call, send);
-  g_object_unref (call);
-}
-
-static void
-empathy_call_window_camera_toggled_cb (GtkToggleToolButton *toggle,
-  EmpathyCallWindow *window)
-{
-  EmpathyCallWindowPriv *priv = GET_PRIV (window);
-  gboolean active;
-
-  if (priv->call_state != CONNECTED)
-    return;
-
-  active = (gtk_toggle_tool_button_get_active (toggle));
-
-  if (priv->sending_video == active)
-    return;
-
-  empathy_call_window_set_send_video (window, active);
-  gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (priv->send_video), active);
-}
-
-static void
-empathy_call_window_send_video_toggled_cb (GtkToggleAction *toggle,
-  EmpathyCallWindow *window)
-{
-  EmpathyCallWindowPriv *priv = GET_PRIV (window);
-  gboolean active;
+  display_video_preview (window, send);
 
   if (priv->call_state != CONNECTED)
     return;
 
-  active = (gtk_toggle_action_get_active (toggle));
-
-  if (priv->sending_video == active)
-    return;
-
-  empathy_call_window_set_send_video (window, active);
-  gtk_toggle_tool_button_set_active (
-      GTK_TOGGLE_TOOL_BUTTON (priv->camera_button), active);
-}
-
-static void
-empathy_call_window_show_preview_toggled_cb (GtkToggleAction *toggle,
-  EmpathyCallWindow *window)
-{
-  gboolean show_preview_toggled;
-  EmpathyCallWindowPriv *priv = GET_PRIV (window);
-
-  show_preview_toggled = gtk_toggle_action_get_active (toggle);
-
-  if (show_preview_toggled)
-    {
-      empathy_call_window_setup_video_preview (window);
-      gtk_widget_show (priv->self_user_output_frame);
-      empathy_call_window_update_self_avatar_visibility (window);
-    }
-  else
-    {
-      gtk_widget_hide (priv->self_user_output_frame);
-    }
+  g_object_get (priv->handler, "tp-call", &call, NULL);
+  DEBUG ("%s sending video", send ? "start": "stop");
+  empathy_tp_call_request_video_stream_direction (call, send);
+  g_object_unref (call);
 }
 
 static void
@@ -2291,16 +2481,11 @@ empathy_call_window_restart_call (EmpathyCallWindow *window)
 
   gtk_widget_show_all (priv->content_hbox);
 
-  if (!empathy_call_handler_has_initial_video (priv->handler))
-    gtk_widget_hide (priv->self_user_output_frame);
-
   priv->outgoing = TRUE;
   empathy_call_window_set_state_connecting (window);
 
-  priv->call_started = TRUE;
-  empathy_call_handler_start_call (priv->handler);
+  start_call (window);
   empathy_call_window_setup_avatars (window, priv->handler);
-  gst_element_set_state (priv->pipeline, GST_STATE_PLAYING);
 
   gtk_action_set_sensitive (priv->redial, FALSE);
   gtk_widget_set_sensitive (priv->redial_button, FALSE);
@@ -2429,3 +2614,36 @@ empathy_call_window_volume_changed_cb (GtkScaleButton *button,
   empathy_audio_sink_set_volume (EMPATHY_GST_AUDIO_SINK (priv->audio_output),
     value);
 }
+
+/* block all the signals related to camera control widgets. This is useful
+ * when we are manually updating the UI and so don't want to fire the
+ * callbacks */
+static void
+block_camera_control_signals (EmpathyCallWindow *self)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+
+  g_signal_handlers_block_by_func (priv->tool_button_camera_off,
+      tool_button_camera_off_toggled_cb, self);
+  g_signal_handlers_block_by_func (priv->tool_button_camera_preview,
+      tool_button_camera_preview_toggled_cb, self);
+  g_signal_handlers_block_by_func (priv->tool_button_camera_on,
+      tool_button_camera_on_toggled_cb, self);
+  g_signal_handlers_block_by_func (priv->action_camera,
+      action_camera_change_cb, self);
+}
+
+static void
+unblock_camera_control_signals (EmpathyCallWindow *self)
+{
+  EmpathyCallWindowPriv *priv = GET_PRIV (self);
+
+  g_signal_handlers_unblock_by_func (priv->tool_button_camera_off,
+      tool_button_camera_off_toggled_cb, self);
+  g_signal_handlers_unblock_by_func (priv->tool_button_camera_preview,
+      tool_button_camera_preview_toggled_cb, self);
+  g_signal_handlers_unblock_by_func (priv->tool_button_camera_on,
+      tool_button_camera_on_toggled_cb, self);
+  g_signal_handlers_unblock_by_func (priv->action_camera,
+      action_camera_change_cb, self);
+}