<commit_before>/*
 * Mex - a media explorer
 *
 * Copyright © 2010, 2011 Intel Corporation.
 * Copyright © 2011 Collabora Ltd.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms and conditions of the GNU Lesser General Public License,
 * version 2.1, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, see <http://www.gnu.org/licenses>
 */


#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <string.h>
#include <stdlib.h>
#include <libgen.h>
#include <signal.h>

#include <locale.h>

#include <grilo.h>
#include <mex/mex.h>
#ifdef USE_PLAYER_CLUTTER_GST
#include <clutter-gst/clutter-gst.h>
#endif

#include <glib/gi18n.h>

#include "mex-version.h"

#ifdef HAVE_CLUTTER_X11
#include <clutter/x11/clutter-x11.h>
#endif

#ifdef HAVE_WEBREMOTE
#include "webremote.h"
#endif

#define ALPHA CLUTTER_EASE_OUT_QUAD
#define DURATION 400

#define MEX_LOG_DOMAIN_DEFAULT  main_log_domain
MEX_LOG_DOMAIN_STATIC(main_log_domain);

typedef struct
{
  MxWindow     *window;
  ClutterStage *stage;
  ClutterActor *stack;
  ClutterActor *layout;
  ClutterActor *tool_area;
  ClutterActor *explorer;
  ClutterActor *spinner;
  ClutterActor *version;
  ClutterActor *error_dialog;
  ClutterActor *error_label;
  ClutterActor *slide_show;
  ClutterActor *info_bar;
  ClutterActor *volume_control;

  MexBackground *background;
  ClutterActor *player;
  ClutterMedia *media;

  ClutterActor *current_actor;

  ClutterActor    *current_tool;
  MexToolProvider *current_tool_provider;
  MexToolMode      current_tool_mode;
  ClutterActor    *other_tool;
  MexToolProvider *other_tool_provider;
  MexToolMode      other_tool_mode;

  ClutterActor *current_applet;
  ClutterActor *last_applet;

  GHashTable         *model_to_provider;
  MexModel           *toplevel_model;
  MexModel           *toplevel_plugin_model;
  guint               toplevel_plugin_model_depth;
  gboolean            using_alt;
  ClutterBindingPool *bindings;

  guint hide_volume_source;

  MexContent *folder_content;
  gboolean    folder_opened;

  gint busy_count;

  guint cursor_timeout_source;
} MexData;

void mex_toggle_pip (MexData *data);

static void mex_show_actor (MexData *data, ClutterActor *actor);
static void mex_hide_actor (MexData *data, ClutterActor *actor);

static void mex_push_busy (MexData *data);
static void mex_pop_busy (MexData *data);

static gboolean opt_fullscreen   = FALSE;
static gboolean opt_version      = FALSE;
static gboolean opt_show_version = FALSE;
static gboolean opt_ignore_res   = FALSE;
static gboolean opt_touch        = FALSE;
static gchar **opt_file = NULL;

static void
mex_header_activated_cb (MexExplorer *explorer,
                         MexModel    *model,
                         MexData     *data);

static void
mex_activate_background (MexData *data, gboolean active)
{
  MexBackgroundManager *manager;

  /* check to see if we actually have a background in place */
  if (!data->background)
    {
      manager = mex_background_manager_get_default ();
      /* return if we have no backgrounds available */
      if (!(data->background = mex_background_manager_get_active (manager)))
        return;
    }

  /* Starts or stops the background from actually "playing" */
  mex_background_set_active (data->background, active);

  if (!active)
    clutter_actor_hide (CLUTTER_ACTOR (data->background));
  else
    clutter_actor_show (CLUTTER_ACTOR (data->background));
}

static void
mex_background_changed_cb (MexBackgroundManager *manager,
                           MexBackground        *new_background,
                           MexData              *data)
{
  /* We want to maintain the previous hidden/shown state of the
   * background so store it in bg_was_visible.
   */
  gboolean bg_was_visible;

   if (data->background)
    {
      bg_was_visible =
        CLUTTER_ACTOR_IS_VISIBLE (CLUTTER_ACTOR (data->background));

      clutter_container_remove_actor (CLUTTER_CONTAINER (data->stack),
                                      CLUTTER_ACTOR (data->background));
      clutter_actor_hide (CLUTTER_ACTOR (data->background));

      /* Stop the old background */
      mex_background_set_active (data->background, FALSE);
    }
   else
     {
       bg_was_visible = TRUE;
     }

  data->background = new_background;

  clutter_container_add_actor (CLUTTER_CONTAINER (data->stack),
                               CLUTTER_ACTOR (data->background));
  clutter_actor_lower_bottom (CLUTTER_ACTOR (data->background));


  mex_background_set_active (data->background, bg_was_visible);

  if (bg_was_visible)
      clutter_actor_show (CLUTTER_ACTOR (data->background));
  else
      clutter_actor_hide (CLUTTER_ACTOR (data->background));
}

static void
error_dialog_dismiss_action_cb (MxAction *action,
                                MexData  *data)
{
  MxFocusManager *fmanager;
  fmanager = mx_focus_manager_get_for_stage (data->stage);

  clutter_actor_hide (data->error_dialog);
  mx_focus_manager_push_focus_with_hint (fmanager,
                                         MX_FOCUSABLE (data->error_dialog),
                                         MX_FOCUS_HINT_PRIOR);
}

static void
mex_player_media_error_cb (ClutterMedia *media,
                           const GError *error,
                           MexData      *data)
{
  gchar *error_msg;
  MxAction *action;

  if (mex_background_manager_get_active (mex_background_manager_get_default ()))
    {
      g_warning ("Error playing content: %s", error->message);
      return;
    }

  if (!data->error_dialog)
    {
      /* TRANSLATORS: This is the label on the button pressed to close a modal
         error dialog */
      action = mx_action_new_full ("dismiss", _("Dismiss"),
                                   (GCallback)error_dialog_dismiss_action_cb,
                                   data);
      data->error_dialog = mx_dialog_new ();
      mx_stylable_set_style_class (MX_STYLABLE (data->error_dialog),
                                   "MexErrorDialog");
      clutter_actor_set_name (data->error_dialog, "mex-player-error-dialog");
      mx_dialog_set_transient_parent (MX_DIALOG (data->error_dialog),
                                      data->stack);
      mx_dialog_add_action (MX_DIALOG (data->error_dialog), action);
    }


  error_msg = g_strdup_printf (_("Error playing content: %s"),
                                 error->message);

  if (!data->error_label)
    {
      data->error_label = mx_label_new_with_text (error_msg);
      clutter_actor_set_name (data->error_dialog, "mex-player-error-label");
      clutter_container_add_actor (CLUTTER_CONTAINER (data->error_dialog),
                                   data->error_label);
    }
  else
    mx_label_set_text (MX_LABEL (data->error_label), error_msg);

  g_free (error_msg);
  clutter_actor_show (data->error_dialog);
  mex_push_focus (MX_FOCUSABLE (data->error_dialog));
}

static void
mex_player_media_notify_buffer_fill_cb (ClutterMedia *media,
                                        GParamSpec   *pspec,
                                        MexData      *data)
{
  static gdouble prev_buffer_fill = 1.0;
  gdouble buffer_fill;

  buffer_fill = clutter_media_get_buffer_fill (media);

  if (buffer_fill == 1.0 && prev_buffer_fill < 1.0)
    mex_pop_busy (data);
  else if (buffer_fill < 1.0 && prev_buffer_fill == 1.0)
    mex_push_busy (data);

  prev_buffer_fill = buffer_fill;
}

static void
play_transition_complete (ClutterAnimation *animation,
                          ClutterActor     *actor)
{
  GObject *fade;
  MexContent *content;
  MexData *data;

  fade = clutter_animation_get_object (animation);
  content = g_object_get_data (fade, "mex-content");
  data = g_object_get_data (fade, "mex-data");

  clutter_actor_destroy (CLUTTER_ACTOR (fade));

  mex_show_actor (data, actor);
  mex_hide_actor (data, data->explorer);

  mex_content_view_set_content (MEX_CONTENT_VIEW (actor), content);
}

static void
explorer_transition_complete (ClutterAnimation *animation,
                              ClutterActor     *actor)
{
  ClutterActor *explorer;

  explorer = (ClutterActor*)clutter_animation_get_object (animation);

  clutter_actor_set_scale (explorer, 1.0, 1.0);
  clutter_actor_set_opacity (explorer, 255);
}

static void
run_play_transition (MexData      *data,
                     MexContent   *content,
                     ClutterActor *actor,
                     gboolean      fade_out)
{
  ClutterActor *fade;
  gfloat x, y, width, height, stage_width, stage_height;
  ClutterColor black = {0, 0, 0, 255};
  ClutterActor *a;
  MxFocusManager *manager;
  gint duration = 500;

  clutter_actor_get_size (CLUTTER_ACTOR (data->stage), &stage_width,
                          &stage_height);

  fade = clutter_rectangle_new_with_color (&black);
  g_object_set_data (G_OBJECT (fade), "mex-content", content);
  g_object_set_data (G_OBJECT (fade), "mex-data", data);
  clutter_actor_set_opacity (fade, 128);
  clutter_actor_set_anchor_point_from_gravity (fade, CLUTTER_GRAVITY_CENTER);
  clutter_actor_set_rotation (fade, CLUTTER_X_AXIS, -80, 0, 0, 0);

  /* find the content box that has been activated */
  manager = mx_focus_manager_get_for_stage (data->stage);
  a = (ClutterActor*) mx_focus_manager_get_focused (manager);
  while (a)
    {
      if (MEX_IS_CONTENT_BOX (a))
        break;
      a = clutter_actor_get_parent (a);
    }

  if (a)
    {
      clutter_actor_get_transformed_position (a, &x, &y);
      clutter_actor_get_size (CLUTTER_ACTOR (a), &width, &height);

      clutter_actor_set_position (fade, x + width / 2, y + height / 2);
      clutter_actor_set_size (CLUTTER_ACTOR (fade), width, height);
    }
  else
    {
      /* show the actor immediately if no content box was found */
      mex_hide_actor (data, data->explorer);
      mex_show_actor (data, actor);
      mex_content_view_set_content (MEX_CONTENT_VIEW (actor), content);

      clutter_actor_destroy (fade);

      return;
    }

  clutter_container_add (CLUTTER_CONTAINER (data->stage), fade, NULL);
  clutter_actor_raise_top (fade);

  g_object_set (data->explorer, "scale-gravity", CLUTTER_GRAVITY_CENTER, NULL);
  clutter_actor_animate (data->explorer, CLUTTER_EASE_IN_OUT_CUBIC, duration,
                         "scale-x", 0.9, "scale-y", 0.9,
                         "signal-after::completed",
                         explorer_transition_complete, actor,
                         "opacity", 128, NULL);

  clutter_actor_animate (fade, CLUTTER_EASE_OUT_CUBIC, duration,
                         "opacity", 255,
                         "x", stage_width / 2.0,
                         "y", stage_height / 2.0,
                         "width", stage_width,
                         "height", stage_height,
                         "rotation-angle-x", (gdouble) 0,
                         "signal-after::completed", play_transition_complete,
                         actor,
                         NULL);
}

static void
mex_play_from_last_cb (MxAction *action, MexData *data)
{
  MexContent *content = mex_action_get_content (action);
  MexModel *model = mex_action_get_context (action);

  if (!content)
    {
      g_warning (G_STRLOC ": Play initiated on action with "
                 "no associated content");
      return;
    }

  g_object_set (content, "last-position-start", TRUE, NULL);
  mex_content_view_set_context (MEX_CONTENT_VIEW (data->player), model);

  /* hide other actors */
  mex_hide_actor (data, data->slide_show);

  /* show the video player */
  run_play_transition (data, content, data->player, TRUE);
}

static void
mex_play_from_begin_cb (MxAction *action, MexData *data)
{
  MexContent *content = mex_action_get_content (action);
  MexModel *model = mex_action_get_context (action);

  if (!content)
    {
      g_warning (G_STRLOC ": Play initiated on action with "
                 "no associated content");
      return;
    }

  g_object_set (content, "last-position-start", FALSE, NULL);
  mex_content_view_set_context (MEX_CONTENT_VIEW (data->player), model);

  /* hide other actors */
  mex_hide_actor (data, data->slide_show);

  /* show the video player */
  run_play_transition (data, content, data->player, TRUE);
}

static void
mex_player_content_set_externally_cb (MexData *data)
{
  /* hide other actors */
  mex_hide_actor (data, data->explorer);
  mex_hide_actor (data, data->slide_show);

  /* show the video player */
  mex_show_actor (data, data->player);
}

static void
mex_show_cb (MxAction *action, MexData *data)
{
  MexContent *content = mex_action_get_content (action);
  MexModel *model = mex_action_get_context (action);

  if (!content)
    {
      g_warning (G_STRLOC ": Play initiated on action with "
                 "no associated content");
      return;
    }

  /* start the slide show */
  mex_content_view_set_context (MEX_CONTENT_VIEW (data->slide_show), model);

  /* hide other actors */
  mex_hide_actor (data, data->player);

  /* stop any playing video, even in idle mode */
  clutter_actor_hide (data->player);
  mex_activate_background (data, FALSE);

  /* show the slide show */
  run_play_transition (data, content, data->slide_show, FALSE);
}

static gboolean
mex_is_toplevel_plugin_model (MexData  *data,
                              MexModel *model)
{
  if (g_hash_table_lookup (data->model_to_provider, model))
    return TRUE;
  else
    return FALSE;
}

static void
mex_grilo_open_folder_cb (MxAction *action,
                          MexData  *data)
{
  GrlMedia *media;
  MexFeed *feed;
  GrlMediaSource *source = NULL;
  gchar *filter = NULL;
  MexModel *parent_model;
  MexModel *parent_alt_model;
  MexModelManager *manager;
  gchar *category;

  MexProgram *program = MEX_PROGRAM (mex_action_get_content (action));

  parent_model = (MexModel*) mex_program_get_feed (program);

  if (mex_is_toplevel_plugin_model (data, MEX_MODEL (parent_model)))
    {
      data->toplevel_plugin_model = mex_model_get_model (parent_model);
      data->toplevel_plugin_model_depth =
        mex_explorer_get_depth (MEX_EXPLORER (data->explorer));
    }

  g_object_get (G_OBJECT (parent_model), "grilo-source", &source, "alt-model",
                &parent_alt_model, "category", &category, NULL);

  if (MEX_IS_GRILO_TRACKER_FEED (parent_model))
      g_object_get (G_OBJECT (parent_model), "tracker-filter", &filter, NULL);

  media = mex_grilo_program_get_grilo_media (MEX_GRILO_PROGRAM (program));

  if (filter)
    {
      feed = g_object_new (G_OBJECT_TYPE (parent_model),
                         "grilo-source", source,
                         "grilo-box", media,
                         "tracker-filter", filter,
                         NULL);
      g_free (filter);
    }
  else
    {
      feed = g_object_new (G_OBJECT_TYPE (parent_model),
                         "grilo-source", source,
                         "grilo-box", media,
                         NULL);
    }

  /* register the new model with the model manager */

  /* FIXME: The model might not have had an alt model/no alt model string
   * but because this sets up the alt model as the parent folder model the
   * button will be displayed. This just stops the button being blank for now.
   */
  g_object_set (feed,
                "alt-model-string", _("Show Folders"),
                "alt-model", parent_model,
                "alt-model-active", ((MexModel*) feed == parent_alt_model),
                "category", category,
                NULL);
  g_object_unref (parent_alt_model);

  manager = mex_model_manager_get_default ();
  mex_model_manager_add_model (manager, MEX_MODEL (feed));

  if (source)
    g_object_unref (source);

  data->folder_opened = TRUE;

  mex_grilo_feed_browse (MEX_GRILO_FEED (feed), 0, G_MAXINT);
  mex_header_activated_cb (MEX_EXPLORER (data->explorer),
                           MEX_MODEL (feed), data);

  g_free (category);
}

static void
mex_header_activated_cb (MexExplorer *explorer,
                         MexModel    *model,
                         MexData     *data)
{
  MexModel *current_model;
  MexModelProvider *plugin;

  current_model = mex_explorer_get_model (explorer);

  if (current_model == mex_explorer_get_root_model (explorer))
    data->toplevel_model = mex_explorer_get_focused_model (explorer);
  else if (mex_is_toplevel_plugin_model (data, model))
    {
      data->toplevel_plugin_model = mex_model_get_model (model);
      data->toplevel_plugin_model_depth =
        mex_explorer_get_depth (MEX_EXPLORER (data->explorer));
    }

  /* Let plugins override the default behaviour of activating models */
  plugin = g_hash_table_lookup (data->model_to_provider, model);
  if (plugin && mex_model_provider_model_activated (plugin, model))
    return;

  /* If the aggregate model only houses one model, push that model instead */
  if (MEX_IS_AGGREGATE_MODEL (model))
    {
      const GList *l, *models =
        mex_aggregate_model_get_models (MEX_AGGREGATE_MODEL (model));
      gint n_items, n_models_with_items;

      /* count the total number of items and the number of columns with items */
      n_items = 0;
      n_models_with_items = 0;
      for (l = models; l; l = g_list_next (l))
        {
          gint length;

          length = mex_model_get_length (l->data);

          if (length > 0)
            n_models_with_items++;

          n_items += length;

          /* check if a provider wants to override this action */
          plugin = g_hash_table_lookup (data->model_to_provider, l->data);
          if (plugin && mex_model_provider_model_activated (plugin, l->data))
            return;
        }

      /* no items in the models */
      if (n_items == 0)
        return;

      /* only one model */
      if (models && !models->next)
        {
          mex_header_activated_cb (explorer, models->data, data);
          return;
        }

      /* only one model with any items */
      if (n_models_with_items == 1)
        {
          /* find the model with the items */
          for (l = models; l; l = g_list_next (l))
            if (mex_model_get_length (l->data))
              mex_header_activated_cb (explorer, l->data, data);
          return;
        }

      if (models)
        mex_explorer_push_model (explorer, g_object_ref (model));
    }
  else
    {
      /* For non-aggregate models, you get a grid representation, which we
       * pack sorting chrome around. Use a proxy model so that when we change
       * the sort function of the model, we don't affect views on other pages.
       */
      MexModel *new_model = mex_proxy_model_new ();
      mex_proxy_model_set_model (MEX_PROXY_MODEL (new_model), model);
      mex_explorer_push_model (explorer, new_model);
    }
}


static void
mex_notify_depth_cb (MexExplorer *explorer,
                     GParamSpec  *pspec,
                     MexData     *data)
{
  guint depth;
  MexModel *model;
  ClutterContainer *container;

  depth = mex_explorer_get_depth (explorer);

  if (depth == 2)
    mex_info_bar_show_buttons (MEX_INFO_BAR (data->info_bar), FALSE);
  if (depth == 1)
    mex_info_bar_show_buttons (MEX_INFO_BAR (data->info_bar), TRUE);

  if (data->toplevel_plugin_model &&
      (mex_explorer_get_depth (explorer) <= data->toplevel_plugin_model_depth))<commit_msg>main: set the group by key on the view model when the header is activated<commit_after>/*
 * Mex - a media explorer
 *
 * Copyright © 2010, 2011 Intel Corporation.
 * Copyright © 2011 Collabora Ltd.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms and conditions of the GNU Lesser General Public License,
 * version 2.1, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, see <http://www.gnu.org/licenses>
 */


#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <string.h>
#include <stdlib.h>
#include <libgen.h>
#include <signal.h>

#include <locale.h>

#include <grilo.h>
#include <mex/mex.h>
#ifdef USE_PLAYER_CLUTTER_GST
#include <clutter-gst/clutter-gst.h>
#endif

#include <glib/gi18n.h>

#include "mex-version.h"

#ifdef HAVE_CLUTTER_X11
#include <clutter/x11/clutter-x11.h>
#endif

#ifdef HAVE_WEBREMOTE
#include "webremote.h"
#endif

#define ALPHA CLUTTER_EASE_OUT_QUAD
#define DURATION 400

#define MEX_LOG_DOMAIN_DEFAULT  main_log_domain
MEX_LOG_DOMAIN_STATIC(main_log_domain);

typedef struct
{
  MxWindow     *window;
  ClutterStage *stage;
  ClutterActor *stack;
  ClutterActor *layout;
  ClutterActor *tool_area;
  ClutterActor *explorer;
  ClutterActor *spinner;
  ClutterActor *version;
  ClutterActor *error_dialog;
  ClutterActor *error_label;
  ClutterActor *slide_show;
  ClutterActor *info_bar;
  ClutterActor *volume_control;

  MexBackground *background;
  ClutterActor *player;
  ClutterMedia *media;

  ClutterActor *current_actor;

  ClutterActor    *current_tool;
  MexToolProvider *current_tool_provider;
  MexToolMode      current_tool_mode;
  ClutterActor    *other_tool;
  MexToolProvider *other_tool_provider;
  MexToolMode      other_tool_mode;

  ClutterActor *current_applet;
  ClutterActor *last_applet;

  GHashTable         *model_to_provider;
  MexModel           *toplevel_model;
  MexModel           *toplevel_plugin_model;
  guint               toplevel_plugin_model_depth;
  gboolean            using_alt;
  ClutterBindingPool *bindings;

  guint hide_volume_source;

  MexContent *folder_content;
  gboolean    folder_opened;

  gint busy_count;

  guint cursor_timeout_source;
} MexData;

void mex_toggle_pip (MexData *data);

static void mex_show_actor (MexData *data, ClutterActor *actor);
static void mex_hide_actor (MexData *data, ClutterActor *actor);

static void mex_push_busy (MexData *data);
static void mex_pop_busy (MexData *data);

static gboolean opt_fullscreen   = FALSE;
static gboolean opt_version      = FALSE;
static gboolean opt_show_version = FALSE;
static gboolean opt_ignore_res   = FALSE;
static gboolean opt_touch        = FALSE;
static gchar **opt_file = NULL;

static void
mex_header_activated_cb (MexExplorer *explorer,
                         MexModel    *model,
                         MexData     *data);

static void
mex_activate_background (MexData *data, gboolean active)
{
  MexBackgroundManager *manager;

  /* check to see if we actually have a background in place */
  if (!data->background)
    {
      manager = mex_background_manager_get_default ();
      /* return if we have no backgrounds available */
      if (!(data->background = mex_background_manager_get_active (manager)))
        return;
    }

  /* Starts or stops the background from actually "playing" */
  mex_background_set_active (data->background, active);

  if (!active)
    clutter_actor_hide (CLUTTER_ACTOR (data->background));
  else
    clutter_actor_show (CLUTTER_ACTOR (data->background));
}

static void
mex_background_changed_cb (MexBackgroundManager *manager,
                           MexBackground        *new_background,
                           MexData              *data)
{
  /* We want to maintain the previous hidden/shown state of the
   * background so store it in bg_was_visible.
   */
  gboolean bg_was_visible;

   if (data->background)
    {
      bg_was_visible =
        CLUTTER_ACTOR_IS_VISIBLE (CLUTTER_ACTOR (data->background));

      clutter_container_remove_actor (CLUTTER_CONTAINER (data->stack),
                                      CLUTTER_ACTOR (data->background));
      clutter_actor_hide (CLUTTER_ACTOR (data->background));

      /* Stop the old background */
      mex_background_set_active (data->background, FALSE);
    }
   else
     {
       bg_was_visible = TRUE;
     }

  data->background = new_background;

  clutter_container_add_actor (CLUTTER_CONTAINER (data->stack),
                               CLUTTER_ACTOR (data->background));
  clutter_actor_lower_bottom (CLUTTER_ACTOR (data->background));


  mex_background_set_active (data->background, bg_was_visible);

  if (bg_was_visible)
      clutter_actor_show (CLUTTER_ACTOR (data->background));
  else
      clutter_actor_hide (CLUTTER_ACTOR (data->background));
}

static void
error_dialog_dismiss_action_cb (MxAction *action,
                                MexData  *data)
{
  MxFocusManager *fmanager;
  fmanager = mx_focus_manager_get_for_stage (data->stage);

  clutter_actor_hide (data->error_dialog);
  mx_focus_manager_push_focus_with_hint (fmanager,
                                         MX_FOCUSABLE (data->error_dialog),
                                         MX_FOCUS_HINT_PRIOR);
}

static void
mex_player_media_error_cb (ClutterMedia *media,
                           const GError *error,
                           MexData      *data)
{
  gchar *error_msg;
  MxAction *action;

  if (mex_background_manager_get_active (mex_background_manager_get_default ()))
    {
      g_warning ("Error playing content: %s", error->message);
      return;
    }

  if (!data->error_dialog)
    {
      /* TRANSLATORS: This is the label on the button pressed to close a modal
         error dialog */
      action = mx_action_new_full ("dismiss", _("Dismiss"),
                                   (GCallback)error_dialog_dismiss_action_cb,
                                   data);
      data->error_dialog = mx_dialog_new ();
      mx_stylable_set_style_class (MX_STYLABLE (data->error_dialog),
                                   "MexErrorDialog");
      clutter_actor_set_name (data->error_dialog, "mex-player-error-dialog");
      mx_dialog_set_transient_parent (MX_DIALOG (data->error_dialog),
                                      data->stack);
      mx_dialog_add_action (MX_DIALOG (data->error_dialog), action);
    }


  error_msg = g_strdup_printf (_("Error playing content: %s"),
                                 error->message);

  if (!data->error_label)
    {
      data->error_label = mx_label_new_with_text (error_msg);
      clutter_actor_set_name (data->error_dialog, "mex-player-error-label");
      clutter_container_add_actor (CLUTTER_CONTAINER (data->error_dialog),
                                   data->error_label);
    }
  else
    mx_label_set_text (MX_LABEL (data->error_label), error_msg);

  g_free (error_msg);
  clutter_actor_show (data->error_dialog);
  mex_push_focus (MX_FOCUSABLE (data->error_dialog));
}

static void
mex_player_media_notify_buffer_fill_cb (ClutterMedia *media,
                                        GParamSpec   *pspec,
                                        MexData      *data)
{
  static gdouble prev_buffer_fill = 1.0;
  gdouble buffer_fill;

  buffer_fill = clutter_media_get_buffer_fill (media);

  if (buffer_fill == 1.0 && prev_buffer_fill < 1.0)
    mex_pop_busy (data);
  else if (buffer_fill < 1.0 && prev_buffer_fill == 1.0)
    mex_push_busy (data);

  prev_buffer_fill = buffer_fill;
}

static void
play_transition_complete (ClutterAnimation *animation,
                          ClutterActor     *actor)
{
  GObject *fade;
  MexContent *content;
  MexData *data;

  fade = clutter_animation_get_object (animation);
  content = g_object_get_data (fade, "mex-content");
  data = g_object_get_data (fade, "mex-data");

  clutter_actor_destroy (CLUTTER_ACTOR (fade));

  mex_show_actor (data, actor);
  mex_hide_actor (data, data->explorer);

  mex_content_view_set_content (MEX_CONTENT_VIEW (actor), content);
}

static void
explorer_transition_complete (ClutterAnimation *animation,
                              ClutterActor     *actor)
{
  ClutterActor *explorer;

  explorer = (ClutterActor*)clutter_animation_get_object (animation);

  clutter_actor_set_scale (explorer, 1.0, 1.0);
  clutter_actor_set_opacity (explorer, 255);
}

static void
run_play_transition (MexData      *data,
                     MexContent   *content,
                     ClutterActor *actor,
                     gboolean      fade_out)
{
  ClutterActor *fade;
  gfloat x, y, width, height, stage_width, stage_height;
  ClutterColor black = {0, 0, 0, 255};
  ClutterActor *a;
  MxFocusManager *manager;
  gint duration = 500;

  clutter_actor_get_size (CLUTTER_ACTOR (data->stage), &stage_width,
                          &stage_height);

  fade = clutter_rectangle_new_with_color (&black);
  g_object_set_data (G_OBJECT (fade), "mex-content", content);
  g_object_set_data (G_OBJECT (fade), "mex-data", data);
  clutter_actor_set_opacity (fade, 128);
  clutter_actor_set_anchor_point_from_gravity (fade, CLUTTER_GRAVITY_CENTER);
  clutter_actor_set_rotation (fade, CLUTTER_X_AXIS, -80, 0, 0, 0);

  /* find the content box that has been activated */
  manager = mx_focus_manager_get_for_stage (data->stage);
  a = (ClutterActor*) mx_focus_manager_get_focused (manager);
  while (a)
    {
      if (MEX_IS_CONTENT_BOX (a))
        break;
      a = clutter_actor_get_parent (a);
    }

  if (a)
    {
      clutter_actor_get_transformed_position (a, &x, &y);
      clutter_actor_get_size (CLUTTER_ACTOR (a), &width, &height);

      clutter_actor_set_position (fade, x + width / 2, y + height / 2);
      clutter_actor_set_size (CLUTTER_ACTOR (fade), width, height);
    }
  else
    {
      /* show the actor immediately if no content box was found */
      mex_hide_actor (data, data->explorer);
      mex_show_actor (data, actor);
      mex_content_view_set_content (MEX_CONTENT_VIEW (actor), content);

      clutter_actor_destroy (fade);

      return;
    }

  clutter_container_add (CLUTTER_CONTAINER (data->stage), fade, NULL);
  clutter_actor_raise_top (fade);

  g_object_set (data->explorer, "scale-gravity", CLUTTER_GRAVITY_CENTER, NULL);
  clutter_actor_animate (data->explorer, CLUTTER_EASE_IN_OUT_CUBIC, duration,
                         "scale-x", 0.9, "scale-y", 0.9,
                         "signal-after::completed",
                         explorer_transition_complete, actor,
                         "opacity", 128, NULL);

  clutter_actor_animate (fade, CLUTTER_EASE_OUT_CUBIC, duration,
                         "opacity", 255,
                         "x", stage_width / 2.0,
                         "y", stage_height / 2.0,
                         "width", stage_width,
                         "height", stage_height,
                         "rotation-angle-x", (gdouble) 0,
                         "signal-after::completed", play_transition_complete,
                         actor,
                         NULL);
}

static void
mex_play_from_last_cb (MxAction *action, MexData *data)
{
  MexContent *content = mex_action_get_content (action);
  MexModel *model = mex_action_get_context (action);

  if (!content)
    {
      g_warning (G_STRLOC ": Play initiated on action with "
                 "no associated content");
      return;
    }

  g_object_set (content, "last-position-start", TRUE, NULL);
  mex_content_view_set_context (MEX_CONTENT_VIEW (data->player), model);

  /* hide other actors */
  mex_hide_actor (data, data->slide_show);

  /* show the video player */
  run_play_transition (data, content, data->player, TRUE);
}

static void
mex_play_from_begin_cb (MxAction *action, MexData *data)
{
  MexContent *content = mex_action_get_content (action);
  MexModel *model = mex_action_get_context (action);

  if (!content)
    {
      g_warning (G_STRLOC ": Play initiated on action with "
                 "no associated content");
      return;
    }

  g_object_set (content, "last-position-start", FALSE, NULL);
  mex_content_view_set_context (MEX_CONTENT_VIEW (data->player), model);

  /* hide other actors */
  mex_hide_actor (data, data->slide_show);

  /* show the video player */
  run_play_transition (data, content, data->player, TRUE);
}

static void
mex_player_content_set_externally_cb (MexData *data)
{
  /* hide other actors */
  mex_hide_actor (data, data->explorer);
  mex_hide_actor (data, data->slide_show);

  /* show the video player */
  mex_show_actor (data, data->player);
}

static void
mex_show_cb (MxAction *action, MexData *data)
{
  MexContent *content = mex_action_get_content (action);
  MexModel *model = mex_action_get_context (action);

  if (!content)
    {
      g_warning (G_STRLOC ": Play initiated on action with "
                 "no associated content");
      return;
    }

  /* start the slide show */
  mex_content_view_set_context (MEX_CONTENT_VIEW (data->slide_show), model);

  /* hide other actors */
  mex_hide_actor (data, data->player);

  /* stop any playing video, even in idle mode */
  clutter_actor_hide (data->player);
  mex_activate_background (data, FALSE);

  /* show the slide show */
  run_play_transition (data, content, data->slide_show, FALSE);
}

static gboolean
mex_is_toplevel_plugin_model (MexData  *data,
                              MexModel *model)
{
  if (g_hash_table_lookup (data->model_to_provider, model))
    return TRUE;
  else
    return FALSE;
}

static void
mex_grilo_open_folder_cb (MxAction *action,
                          MexData  *data)
{
  GrlMedia *media;
  MexFeed *feed;
  GrlMediaSource *source = NULL;
  gchar *filter = NULL;
  MexModel *parent_model;
  MexModel *parent_alt_model;
  MexModelManager *manager;
  gchar *category;

  MexProgram *program = MEX_PROGRAM (mex_action_get_content (action));

  parent_model = (MexModel*) mex_program_get_feed (program);

  if (mex_is_toplevel_plugin_model (data, MEX_MODEL (parent_model)))
    {
      data->toplevel_plugin_model = mex_model_get_model (parent_model);
      data->toplevel_plugin_model_depth =
        mex_explorer_get_depth (MEX_EXPLORER (data->explorer));
    }

  g_object_get (G_OBJECT (parent_model), "grilo-source", &source, "alt-model",
                &parent_alt_model, "category", &category, NULL);

  if (MEX_IS_GRILO_TRACKER_FEED (parent_model))
      g_object_get (G_OBJECT (parent_model), "tracker-filter", &filter, NULL);

  media = mex_grilo_program_get_grilo_media (MEX_GRILO_PROGRAM (program));

  if (filter)
    {
      feed = g_object_new (G_OBJECT_TYPE (parent_model),
                         "grilo-source", source,
                         "grilo-box", media,
                         "tracker-filter", filter,
                         NULL);
      g_free (filter);
    }
  else
    {
      feed = g_object_new (G_OBJECT_TYPE (parent_model),
                         "grilo-source", source,
                         "grilo-box", media,
                         NULL);
    }

  /* register the new model with the model manager */

  /* FIXME: The model might not have had an alt model/no alt model string
   * but because this sets up the alt model as the parent folder model the
   * button will be displayed. This just stops the button being blank for now.
   */
  g_object_set (feed,
                "alt-model-string", _("Show Folders"),
                "alt-model", parent_model,
                "alt-model-active", ((MexModel*) feed == parent_alt_model),
                "category", category,
                NULL);
  g_object_unref (parent_alt_model);

  manager = mex_model_manager_get_default ();
  mex_model_manager_add_model (manager, MEX_MODEL (feed));

  if (source)
    g_object_unref (source);

  data->folder_opened = TRUE;

  mex_grilo_feed_browse (MEX_GRILO_FEED (feed), 0, G_MAXINT);
  mex_header_activated_cb (MEX_EXPLORER (data->explorer),
                           MEX_MODEL (feed), data);

  g_free (category);
}

static void
mex_header_activated_cb (MexExplorer *explorer,
                         MexModel    *model,
                         MexData     *data)
{
  MexModel *current_model;
  MexModelProvider *plugin;

  current_model = mex_explorer_get_model (explorer);

  if (current_model == mex_explorer_get_root_model (explorer))
    data->toplevel_model = mex_explorer_get_focused_model (explorer);
  else if (mex_is_toplevel_plugin_model (data, model))
    {
      data->toplevel_plugin_model = mex_model_get_model (model);
      data->toplevel_plugin_model_depth =
        mex_explorer_get_depth (MEX_EXPLORER (data->explorer));
    }

  /* Let plugins override the default behaviour of activating models */
  plugin = g_hash_table_lookup (data->model_to_provider, model);
  if (plugin && mex_model_provider_model_activated (plugin, model))
    return;

  /* If the aggregate model only houses one model, push that model instead */
  if (MEX_IS_AGGREGATE_MODEL (model))
    {
      const GList *l, *models =
        mex_aggregate_model_get_models (MEX_AGGREGATE_MODEL (model));
      gint n_items, n_models_with_items;

      /* count the total number of items and the number of columns with items */
      n_items = 0;
      n_models_with_items = 0;
      for (l = models; l; l = g_list_next (l))
        {
          gint length;

          length = mex_model_get_length (l->data);

          if (length > 0)
            n_models_with_items++;

          n_items += length;

          /* check if a provider wants to override this action */
          plugin = g_hash_table_lookup (data->model_to_provider, l->data);
          if (plugin && mex_model_provider_model_activated (plugin, l->data))
            return;
        }

      /* no items in the models */
      if (n_items == 0)
        return;

      /* only one model */
      if (models && !models->next)
        {
          mex_header_activated_cb (explorer, models->data, data);
          return;
        }

      /* only one model with any items */
      if (n_models_with_items == 1)
        {
          /* find the model with the items */
          for (l = models; l; l = g_list_next (l))
            if (mex_model_get_length (l->data))
              mex_header_activated_cb (explorer, l->data, data);
          return;
        }

      if (models)
        {
          MexModel *view;
          const MexModelCategoryInfo *c_info;
          gchar *category;

          g_object_get (model, "category", &category, NULL);

          c_info = mex_model_manager_get_category_info (mex_model_manager_get_default (),
                                                        category);
          g_free (category);

          view = mex_view_model_new (model);
          mex_view_model_set_group_by (MEX_VIEW_MODEL (view),
                                       c_info->primary_group_by_key);
          mex_explorer_push_model (explorer, view);
        }
    }
  else
    {
      MexModel *view;
      const MexModelCategoryInfo *c_info;
      gchar *category;

      g_object_get (model, "category", &category, NULL);

      c_info = mex_model_manager_get_category_info (mex_model_manager_get_default (),
                                                    category);
      g_free (category);

      if (MEX_IS_VIEW_MODEL (model))
        model = mex_model_get_model (model);

      view = mex_view_model_new (model);
      mex_view_model_set_group_by (MEX_VIEW_MODEL (view),
                                   c_info->primary_group_by_key);
      mex_explorer_push_model (explorer, view);
    }
}


static void
mex_notify_depth_cb (MexExplorer *explorer,
                     GParamSpec  *pspec,
                     MexData     *data)
{
  guint depth;
  MexModel *model;
  ClutterContainer *container;

  depth = mex_explorer_get_depth (explorer);

  if (depth == 2)
    mex_info_bar_show_buttons (MEX_INFO_BAR (data->info_bar), FALSE);
  if (depth == 1)
    mex_info_bar_show_buttons (MEX_INFO_BAR (data->info_bar), TRUE);

  if (data->toplevel_plugin_model &&
      (mex_explorer_get_depth (explorer) <= data->toplevel_plugin_model_depth))
{'fraction_non_alphanumeric': 0.09364222522117259, 'fraction_numerical': 0.004198530514319988, 'mean_word_length': 2.742167773309642, 'pattern_counts': {'":': 6, '<': 31, '<?xml version=': 0, '>': 191, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 32, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '868150', 'n_tokens_mistral': 13249, 'n_tokens_neox': 12684, 'n_words': 2967}