static void
mex_content_tile_paint (ClutterActor *actor)
{
  MexContentTilePrivate *priv = MEX_CONTENT_TILE (actor)->priv;

  if (priv->content && MEX_IS_PROGRAM (priv->content))
    _mex_program_complete (MEX_PROGRAM (priv->content));

  if (!priv->thumbnail_loaded && !priv->download_id)
    _update_thumbnail (MEX_CONTENT_TILE (actor));

  CLUTTER_ACTOR_CLASS (mex_content_tile_parent_class)->paint (actor);
}
static gboolean
_stop_video_preview (MexContentTile *self)
{
  MexContentTilePrivate *priv = MEX_CONTENT_TILE (self)->priv;

  if (priv->start_video_preview > 0)
    g_source_remove (priv->start_video_preview);

  /* video never started */
  if (priv->child == priv->image)
    return FALSE;

  if (!priv->video_preview)
    return FALSE;

  clutter_media_set_playing (CLUTTER_MEDIA (priv->video_preview), FALSE);
  clutter_actor_remove_child (CLUTTER_ACTOR (self), priv->video_preview);
  clutter_actor_add_child (CLUTTER_ACTOR (self), priv->image);
  /* After setting the child the old child is killed off so NULL this to
   *  help with checks
   */
  priv->video_preview = NULL;


  return FALSE;
}
static void
mex_content_tile_set_property (GObject      *object,
                               guint         property_id,
                               const GValue *value,
                               GParamSpec   *pspec)
{
  MexContentTilePrivate *priv = MEX_CONTENT_TILE (object)->priv;

  switch (property_id)
    {
    case PROP_THUMB_WIDTH:
      priv->thumb_width = g_value_get_int (value);

      /* Ideally we'd use the image_set variable to determine if we set this,
       * but for all our use-cases, we always want the set thumbnail width.
       */
      clutter_actor_set_width (priv->image, priv->thumb_width);
      break;

    case PROP_THUMB_HEIGHT:
      priv->thumb_height = g_value_get_int (value);
      if (priv->image_set)
        clutter_actor_set_height (priv->image, priv->thumb_height);
      break;


    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
    }

}
static MxFocusable*
mex_content_tile_accept_focus (MxFocusable *focusable,
                               MxFocusHint  hint)
{
  MexContentTilePrivate *priv = MEX_CONTENT_TILE (focusable)->priv;

  clutter_actor_grab_key_focus (CLUTTER_ACTOR (focusable));

  priv->start_video_preview =
    g_timeout_add_seconds (1, (GSourceFunc)_start_video_preview,
                           MEX_CONTENT_TILE (focusable));

  g_signal_emit (focusable, signals[FOCUS_IN], 0);

  return focusable;
}
static void
mex_content_tile_dispose (GObject *object)
{
  MexContentTilePrivate *priv = MEX_CONTENT_TILE (object)->priv;

  if (priv->content)
    {
      /* remove the reference to the MexContent and also disconnect the signal
       * handlers */
      mex_content_tile_set_content (MEX_CONTENT_VIEW (object), NULL);
    }

  if (priv->model)
    {
      g_object_unref (priv->model);
      priv->model = NULL;
    }

  if (priv->download_id)
    {
      MexDownloadQueue *dl_queue = mex_download_queue_get_default ();
      mex_download_queue_cancel (dl_queue, priv->download_id);
      priv->download_id = NULL;
    }

  G_OBJECT_CLASS (mex_content_tile_parent_class)->dispose (object);
}
static MexModel*
mex_content_tile_get_context (MexContentView *view)
{
  MexContentTile *tile = MEX_CONTENT_TILE (view);
  MexContentTilePrivate *priv = tile->priv;

  return priv->model;
}
static void
mex_content_tile_set_content (MexContentView *view,
                              MexContent     *content)
{
  MexContentTile *tile = MEX_CONTENT_TILE (view);
  MexContentTilePrivate *priv = tile->priv;
  const gchar *label_prop_name, *secondary_label_prop_name;

  if (priv->content == content)
    return;

  if (priv->changed_id)
    {
      g_signal_handler_disconnect (priv->content, priv->changed_id);
      priv->changed_id = 0;
    }

  if (priv->content)
    {
      g_object_unref (priv->content);
      priv->content = NULL;
    }

  if (!content)
    return;

  priv->content = g_object_ref_sink (content);

  /* Update title/thumbnail display */

  label_prop_name = mex_content_get_property_name (priv->content,
                                                   MEX_CONTENT_METADATA_TITLE);
  secondary_label_prop_name =
    mex_content_get_property_name (priv->content,
                                   MEX_CONTENT_METADATA_ARTIST);

  g_object_bind_property (content, label_prop_name,
                          tile, "label",
                          G_BINDING_SYNC_CREATE);
  if (secondary_label_prop_name)
    {
      g_object_bind_property (content, secondary_label_prop_name,
                              tile, "secondary-label",
                              G_BINDING_SYNC_CREATE);
    }



  _update_logo (tile);
  _reset_thumbnail (tile);

  /* TODO: use g_object_bind_property */
  priv->changed_id = g_signal_connect (priv->content,
                                       "notify",
                                       G_CALLBACK (_content_notify),
                                       view);
}
static void
mex_content_tile_actor_removed (ClutterActor *container,
                                ClutterActor *actor)
{
  MexContentTilePrivate *priv = MEX_CONTENT_TILE (container)->priv;

  if (priv->child == actor)
    priv->child = NULL;
}
static MxFocusable*
mex_content_tile_move_focus (MxFocusable      *focusable,
                             MxFocusDirection  direction,
                             MxFocusable      *old_focus)
{
  g_signal_emit (focusable, signals[FOCUS_OUT], 0);

  _stop_video_preview (MEX_CONTENT_TILE (old_focus));

  return NULL;
}
static void
mex_content_tile_set_context (MexContentView *view,
                              MexModel       *model)
{
  MexContentTile *tile = MEX_CONTENT_TILE (view);
  MexContentTilePrivate *priv = tile->priv;

  if (priv->model)
    g_object_unref (priv->model);

  priv->model = g_object_ref (model);
}
static void
mex_content_tile_actor_added (ClutterActor *container,
                              ClutterActor *actor)
{
  MexContentTilePrivate *priv = MEX_CONTENT_TILE (container)->priv;

  if (MX_IS_TOOLTIP (actor))
    return;

  if (priv->child)
    clutter_actor_remove_child (container, priv->child);

  priv->child = actor;
}
static void
download_queue_completed (MexDownloadQueue *queue,
                          const gchar      *uri,
                          const gchar      *buffer,
                          gsize             count,
                          const GError     *error,
                          gpointer          user_data)
{
  MexContentTile *tile = MEX_CONTENT_TILE (user_data);
  MexContentTilePrivate *priv = tile->priv;
  GError *suberror = NULL;

  priv->download_id = NULL;
  priv->thumbnail_loaded = TRUE;

  if (error)
    {
      g_warning ("Error loading %s: %s", uri, error->message);
      return;
    }

  /* TODO: Find a way of not having to do a g_memdup here */
  if (!mx_image_set_from_buffer_at_size (MX_IMAGE (priv->image),
                                         g_memdup (buffer, count), count,
                                         (GDestroyNotify)g_free,
                                         priv->thumb_width,
                                         priv->thumb_height,
                                         &suberror))
    {
      g_warning ("Error loading %s: %s", uri, suberror->message);
      g_error_free (suberror);

      /* TODO: Maybe set a broken-image tile? */

      return;
    }

  priv->image_set = TRUE;
  clutter_actor_set_size (priv->image,
                          priv->thumb_width, priv->thumb_height);
}
static void
mex_content_tile_dispose (GObject *object)
{
  MexContentTilePrivate *priv = MEX_CONTENT_TILE (object)->priv;

  if (priv->content)
    {
      /* remove the reference to the MexContent and also disconnect the signal
       * handlers */
      mex_content_tile_set_content (MEX_CONTENT_VIEW (object), NULL);
    }

  if (priv->model)
    {
      g_object_unref (priv->model);
      priv->model = NULL;
    }

  if (priv->download_id)
    {
      MexDownloadQueue *dl_queue = mex_download_queue_get_default ();
      mex_download_queue_cancel (dl_queue, priv->download_id);
      priv->download_id = NULL;
    }

  if (priv->start_video_preview > 0)
    g_source_remove (priv->start_video_preview);

  if (priv->stop_video_preview > 0)
    g_source_remove (priv->stop_video_preview);

  /* This may or may not be parented so explicitly mark for destroying */
  if (priv->video_preview)
    {
      clutter_actor_destroy (CLUTTER_ACTOR (priv->video_preview));
      priv->video_preview = NULL;
    }

  G_OBJECT_CLASS (mex_content_tile_parent_class)->dispose (object);
}
static void
mex_content_tile_get_property (GObject    *object,
                               guint       property_id,
                               GValue     *value,
                               GParamSpec *pspec)
{
  MexContentTilePrivate *priv = MEX_CONTENT_TILE (object)->priv;

  switch (property_id)
    {
    case PROP_THUMB_WIDTH:
      g_value_set_int (value, priv->thumb_width);
      break;

    case PROP_THUMB_HEIGHT:
      g_value_set_int (value, priv->thumb_height);
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
    }
}
static void
mex_content_tile_set_content (MexContentView *view,
                              MexContent     *content)
{
  MexContentTile *tile = MEX_CONTENT_TILE (view);
  MexContentTilePrivate *priv = tile->priv;

  if (priv->content == content)
    return;

  if (priv->changed_id)
    {
      g_signal_handler_disconnect (priv->content, priv->changed_id);
      priv->changed_id = 0;
    }

  if (priv->content)
    {
      g_object_unref (priv->content);
      priv->content = NULL;
    }

  if (!content)
    return;

  priv->content = g_object_ref_sink (content);

  /* Update title/thumbnail display */
  _update_title (tile);
  _update_logo (tile);
  _reset_thumbnail (tile);

  /* TODO: use g_object_bind_property */
  priv->changed_id = g_signal_connect (priv->content,
                                       "notify",
                                       G_CALLBACK (_content_notify),
                                       view);
}