/* Clapper GTK Integration Library
 * Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

/**
 * ClapperGtkExtraMenuButton:
 *
 * A menu button with extra options.
 */

#include "config.h"

#include <math.h>
#include <glib/gi18n-lib.h>
#include <clapper/clapper.h>

#include "clapper-gtk-extra-menu-button.h"
#include "clapper-gtk-stream-check-button-private.h"
#include "clapper-gtk-utils-private.h"

#define PERCENTAGE_ROUND(a) (round ((gdouble) a / 0.01) * 0.01)

#define DEFAULT_CAN_OPEN_SUBTITLES FALSE

#define GST_CAT_DEFAULT clapper_gtk_extra_menu_button_debug
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);

struct _ClapperGtkExtraMenuButton
{
  GtkWidget parent;

  GtkWidget *menu_button;

  GtkWidget *volume_box;
  GtkWidget *volume_button;
  GtkWidget *volume_spin;

  GtkWidget *speed_box;
  GtkWidget *speed_button;
  GtkWidget *speed_spin;

  GtkWidget *top_separator;

  GtkWidget *video_list_view;
  GtkScrolledWindow *video_sw;

  GtkWidget *audio_list_view;
  GtkScrolledWindow *audio_sw;

  GtkWidget *subtitle_list_view;
  GtkScrolledWindow *subtitle_sw;

  ClapperPlayer *player;
  ClapperMediaItem *current_item;

  GSimpleActionGroup *action_group;

  gboolean mute;

  GBinding *volume_binding;
  GBinding *speed_binding;

  GBinding *video_binding;
  GBinding *audio_binding;
  GBinding *subtitle_binding;

  gboolean can_open_subtitles;
};

#define parent_class clapper_gtk_extra_menu_button_parent_class
G_DEFINE_TYPE (ClapperGtkExtraMenuButton, clapper_gtk_extra_menu_button, GTK_TYPE_WIDGET)

enum
{
  PROP_0,
  PROP_VOLUME_VISIBLE,
  PROP_SPEED_VISIBLE,
  PROP_CAN_OPEN_SUBTITLES,
  PROP_LAST
};

enum
{
  SIGNAL_OPEN_SUBTITLES,
  SIGNAL_LAST
};

static GParamSpec *param_specs[PROP_LAST] = { NULL, };
static guint signals[SIGNAL_LAST] = { 0, };

static void
_set_action_enabled (ClapperGtkExtraMenuButton *self, const gchar *name, gboolean enabled)
{
  GAction *action = g_action_map_lookup_action (G_ACTION_MAP (self->action_group), name);
  gboolean was_enabled = g_action_get_enabled (action);

  if (was_enabled == enabled)
    return;

  g_simple_action_set_enabled (G_SIMPLE_ACTION (action), enabled);
}

static gboolean
_double_transform_func (GBinding *binding, const GValue *from_value,
    GValue *to_value, gpointer user_data G_GNUC_UNUSED)
{
  gdouble val_dbl = g_value_get_double (from_value);

  g_value_set_double (to_value, PERCENTAGE_ROUND (val_dbl));
  return TRUE;
}

static gint
volume_spin_input_cb (GtkSpinButton *spin_button, gdouble *value, ClapperGtkExtraMenuButton *self)
{
  const gchar *text = gtk_editable_get_text (GTK_EDITABLE (spin_button));
  gdouble volume = g_strtod (text, NULL);

  if (volume < 0)
    volume = 0;
  else if (volume > 200)
    volume = 200;

  volume /= 100.0;

  if (volume > 0.99 && volume < 1.01)
    volume = 1.0;

  *value = volume;

  return TRUE;
}

static gboolean
volume_spin_output_cb (GtkSpinButton *spin_button, ClapperGtkExtraMenuButton *self)
{
  GtkAdjustment *adjustment = gtk_spin_button_get_adjustment (spin_button);
  gdouble volume = gtk_adjustment_get_value (adjustment);
  gchar *volume_str;

  volume_str = g_strdup_printf ("%.0lf%%", volume * 100);
  gtk_editable_set_text (GTK_EDITABLE (spin_button), volume_str);
  g_free (volume_str);

  return TRUE;
}

static void
volume_spin_changed_cb (GtkSpinButton *spin_button, ClapperGtkExtraMenuButton *self)
{
  GtkAdjustment *adjustment = gtk_spin_button_get_adjustment (spin_button);
  gdouble volume = gtk_adjustment_get_value (adjustment);

  gtk_button_set_icon_name (GTK_BUTTON (self->volume_button),
      clapper_gtk_get_icon_name_for_volume ((!self->mute) ? volume : 0));
}

static gint
speed_spin_input_cb (GtkSpinButton *spin_button, gdouble *value, ClapperGtkExtraMenuButton *self)
{
  const gchar *text = gtk_editable_get_text (GTK_EDITABLE (spin_button));
  gdouble speed = g_strtod (text, NULL);

  if (speed < 0.05)
    speed = 0.05;
  else if (speed > 2.0)
    speed = 2.0;

  if (speed > 0.99 && speed < 1.01)
    speed = 1.0;

  *value = speed;

  return TRUE;
}

static gboolean
speed_spin_output_cb (GtkSpinButton *spin_button, ClapperGtkExtraMenuButton *self)
{
  GtkAdjustment *adjustment = gtk_spin_button_get_adjustment (spin_button);
  gdouble speed = gtk_adjustment_get_value (adjustment);
  gchar *speed_str;

  speed_str = g_strdup_printf ("%.2lfx", speed);
  gtk_editable_set_text (GTK_EDITABLE (spin_button), speed_str);
  g_free (speed_str);

  return TRUE;
}

static void
speed_spin_changed_cb (GtkSpinButton *spin_button, ClapperGtkExtraMenuButton *self)
{
  GtkAdjustment *adjustment = gtk_spin_button_get_adjustment (spin_button);
  gdouble speed = gtk_adjustment_get_value (adjustment);

  gtk_button_set_icon_name (GTK_BUTTON (self->speed_button),
      clapper_gtk_get_icon_name_for_speed (speed));
}

static void
visible_submenu_changed_cb (GtkPopoverMenu *popover_menu,
    GParamSpec *pspec G_GNUC_UNUSED, ClapperGtkExtraMenuButton *self)
{
  gchar *name = NULL;
  gboolean in_video, in_audio, in_subtitles;

  g_object_get (popover_menu, "visible-submenu", &name, NULL);

  /* TODO: Check if we have to compare translated strings here */
  GST_DEBUG ("Visible submenu changed to: \"%s\"", name);

  in_video = (g_strcmp0 (name, _("Video")) == 0);
  in_audio = (g_strcmp0 (name, _("Audio")) == 0);
  in_subtitles = (g_strcmp0 (name, _("Subtitles")) == 0);

  /* XXX: This works around the issue where popover does not adapt its
   * width when navigating submenus making spin buttons unnecesary centered */
  gtk_scrolled_window_set_propagate_natural_width (self->video_sw, in_video);
  gtk_scrolled_window_set_propagate_natural_width (self->audio_sw, in_audio);
  gtk_scrolled_window_set_propagate_natural_width (self->subtitle_sw, in_subtitles);

  g_free (name);
}

static void
_subtitles_enabled_changed_cb (ClapperPlayer *player,
    GParamSpec *pspec G_GNUC_UNUSED, ClapperGtkExtraMenuButton *self)
{
  GAction *action = g_action_map_lookup_action (
      G_ACTION_MAP (self->action_group), "subtitle-stream-enabled");
  GVariant *variant = g_action_get_state (action);
  gboolean was_enabled, enabled;

  was_enabled = g_variant_get_boolean (variant);
  enabled = clapper_player_get_subtitles_enabled (player);

  g_variant_unref (variant);

  if (was_enabled == enabled)
    return;

  variant = g_variant_ref_sink (g_variant_new_boolean (enabled));

  g_simple_action_set_state (G_SIMPLE_ACTION (action), variant);
  g_variant_unref (variant);
}

static void
_player_mute_changed_cb (ClapperPlayer *player,
    GParamSpec *pspec G_GNUC_UNUSED, ClapperGtkExtraMenuButton *self)
{
  self->mute = clapper_player_get_mute (player);

  volume_spin_changed_cb (GTK_SPIN_BUTTON (self->volume_spin), self);
}

static void
_queue_current_item_changed_cb (ClapperQueue *queue,
    GParamSpec *pspec G_GNUC_UNUSED, ClapperGtkExtraMenuButton *self)
{
  ClapperMediaItem *current_item = clapper_queue_get_current_item (queue);

  /* NOTE: This is also called after popover "map" signal */

  if (gst_object_replace ((GstObject **) &self->current_item, GST_OBJECT_CAST (current_item))) {
    _set_action_enabled (self, "open-subtitle-stream",
        (self->can_open_subtitles && self->current_item != NULL));
  }

  gst_clear_object (&current_item);
}

static void
change_subtitle_stream_enabled (GSimpleAction *action, GVariant *value, gpointer user_data)
{
  ClapperGtkExtraMenuButton *self = CLAPPER_GTK_EXTRA_MENU_BUTTON_CAST (user_data);
  gboolean enable = g_variant_get_boolean (value);

  if (G_LIKELY (self->player != NULL))
    clapper_player_set_subtitles_enabled (self->player, enable);

  g_simple_action_set_state (action, value);
}

static void
open_subtitle_stream (GSimpleAction *action, GVariant *param, gpointer user_data)
{
  ClapperGtkExtraMenuButton *self = CLAPPER_GTK_EXTRA_MENU_BUTTON_CAST (user_data);

  /* We should not be here otherwise */
  if (G_LIKELY (self->can_open_subtitles && self->current_item != NULL))
    g_signal_emit (self, signals[SIGNAL_OPEN_SUBTITLES], 0, self->current_item);
}

static void
_determine_top_separator_visibility (ClapperGtkExtraMenuButton *self)
{
  gboolean visible = gtk_widget_get_visible (self->volume_box)
      || gtk_widget_get_visible (self->speed_box);

  gtk_widget_set_visible (self->top_separator, visible);
}

/**
 * clapper_gtk_extra_menu_button_new:
 *
 * Creates a new #ClapperGtkExtraMenuButton instance.
 *
 * Returns: a new extra menu button #GtkWidget.
 */
GtkWidget *
clapper_gtk_extra_menu_button_new (void)
{
  return g_object_new (CLAPPER_GTK_TYPE_EXTRA_MENU_BUTTON, NULL);
}

/**
 * clapper_gtk_extra_menu_button_set_volume_visible:
 * @button: a #ClapperGtkExtraMenuButton
 * @visible: whether visible
 *
 * Set whether volume control inside popover should be visible.
 */
void
clapper_gtk_extra_menu_button_set_volume_visible (ClapperGtkExtraMenuButton *self, gboolean visible)
{
  g_return_if_fail (CLAPPER_GTK_IS_EXTRA_MENU_BUTTON (self));

  if (gtk_widget_get_visible (self->volume_box) != visible) {
    gtk_widget_set_visible (self->volume_box, visible);
    _determine_top_separator_visibility (self);

    g_object_notify_by_pspec (G_OBJECT (self), param_specs[PROP_VOLUME_VISIBLE]);
  }
}

/**
 * clapper_gtk_extra_menu_button_get_volume_visible:
 * @button: a #ClapperGtkExtraMenuButton
 *
 * Get whether volume control inside popover is visible.
 *
 * Returns: TRUE if volume control is visible, %FALSE otherwise.
 */
gboolean
clapper_gtk_extra_menu_button_get_volume_visible (ClapperGtkExtraMenuButton *self)
{
  g_return_val_if_fail (CLAPPER_GTK_IS_EXTRA_MENU_BUTTON (self), FALSE);

  return gtk_widget_get_visible (self->volume_box);
}

/**
 * clapper_gtk_extra_menu_button_set_speed_visible:
 * @button: a #ClapperGtkExtraMenuButton
 * @visible: whether visible
 *
 * Set whether speed control inside popover should be visible.
 */
void
clapper_gtk_extra_menu_button_set_speed_visible (ClapperGtkExtraMenuButton *self, gboolean visible)
{
  g_return_if_fail (CLAPPER_GTK_IS_EXTRA_MENU_BUTTON (self));

  if (gtk_widget_get_visible (self->speed_box) != visible) {
    gtk_widget_set_visible (self->speed_box, visible);
    _determine_top_separator_visibility (self);

    g_object_notify_by_pspec (G_OBJECT (self), param_specs[PROP_SPEED_VISIBLE]);
  }
}

/**
 * clapper_gtk_extra_menu_button_get_speed_visible:
 * @button: a #ClapperGtkExtraMenuButton
 *
 * Get whether speed control inside popover is visible.
 *
 * Returns: %TRUE if speed control is visible, %FALSE otherwise.
 */
gboolean
clapper_gtk_extra_menu_button_get_speed_visible (ClapperGtkExtraMenuButton *self)
{
  g_return_val_if_fail (CLAPPER_GTK_IS_EXTRA_MENU_BUTTON (self), FALSE);

  return gtk_widget_get_visible (self->speed_box);
}

/**
 * clapper_gtk_extra_menu_button_set_can_open_subtitles:
 * @button: a #ClapperGtkExtraMenuButton
 * @allowed: whether opening subtitles should be allowed
 *
 * Set whether an option to open external subtitle stream should be allowed.
 *
 * Note that this [class@Gtk.Widget] can only add subtitles to currently playing
 * [class@Clapper.MediaItem]. When no media is selected, option to open subtitles
 * will not be shown regardless how this option is set.
 */
void
clapper_gtk_extra_menu_button_set_can_open_subtitles (ClapperGtkExtraMenuButton *self, gboolean allowed)
{
  gboolean changed;

  g_return_if_fail (CLAPPER_GTK_IS_EXTRA_MENU_BUTTON (self));

  if ((changed = self->can_open_subtitles != allowed)) {
    self->can_open_subtitles = allowed;

    _set_action_enabled (self, "open-subtitle-stream",
        (self->can_open_subtitles && self->current_item != NULL));

    g_object_notify_by_pspec (G_OBJECT (self), param_specs[PROP_CAN_OPEN_SUBTITLES]);
  }
}

/**
 * clapper_gtk_extra_menu_button_get_can_open_subtitles:
 * @button: a #ClapperGtkExtraMenuButton
 *
 * Get whether an option to open external subtitle stream inside popover is visible.
 *
 * Returns: %TRUE if open subtitles is visible, %FALSE otherwise.
 */
gboolean
clapper_gtk_extra_menu_button_get_can_open_subtitles (ClapperGtkExtraMenuButton *self)
{
  g_return_val_if_fail (CLAPPER_GTK_IS_EXTRA_MENU_BUTTON (self), FALSE);

  return self->can_open_subtitles;
}

static void
clapper_gtk_extra_menu_button_init (ClapperGtkExtraMenuButton *self)
{
  static GActionEntry action_entries[] = {
    { "subtitle-stream-enabled", NULL, NULL, "true", change_subtitle_stream_enabled },
    { "open-subtitle-stream", open_subtitle_stream, NULL, NULL, NULL },
  };

  /* Ensure private types */
  g_type_ensure (CLAPPER_GTK_TYPE_STREAM_CHECK_BUTTON);

  gtk_widget_init_template (GTK_WIDGET (self));

  self->action_group = g_simple_action_group_new ();

  g_object_bind_property (self, "css-classes", self->menu_button,
      "css-classes", G_BINDING_DEFAULT);

  g_action_map_add_action_entries (G_ACTION_MAP (self->action_group),
      action_entries, G_N_ELEMENTS (action_entries), self);
  gtk_widget_insert_action_group (GTK_WIDGET (self),
      "clappergtk", G_ACTION_GROUP (self->action_group));

  /* Set default values */
  self->can_open_subtitles = DEFAULT_CAN_OPEN_SUBTITLES;
  _set_action_enabled (self, "open-subtitle-stream", self->can_open_subtitles);
}

static void
clapper_gtk_extra_menu_button_compute_expand (GtkWidget *widget,
    gboolean *hexpand_p, gboolean *vexpand_p)
{
  GtkWidget *child;
  gboolean hexpand = FALSE;
  gboolean vexpand = FALSE;

  if ((child = gtk_widget_get_first_child (widget))) {
    hexpand = gtk_widget_compute_expand (child, GTK_ORIENTATION_HORIZONTAL);
    vexpand = gtk_widget_compute_expand (child, GTK_ORIENTATION_VERTICAL);
  }

  *hexpand_p = hexpand;
  *vexpand_p = vexpand;
}

static void
clapper_gtk_extra_menu_button_realize (GtkWidget *widget)
{
  ClapperGtkExtraMenuButton *self = CLAPPER_GTK_EXTRA_MENU_BUTTON_CAST (widget);

  GST_TRACE_OBJECT (self, "Realize");

  if ((self->player = clapper_gtk_get_player_from_ancestor (GTK_WIDGET (self)))) {
    ClapperStreamList *stream_list;
    GtkSingleSelection *selection;

    g_signal_connect (self->player, "notify::mute",
        G_CALLBACK (_player_mute_changed_cb), self);
    self->mute = clapper_player_get_mute (self->player);

    stream_list = clapper_player_get_video_streams (self->player);
    selection = gtk_single_selection_new (gst_object_ref (stream_list));
    gtk_single_selection_set_autoselect (selection, FALSE);

    self->video_binding = g_object_bind_property (stream_list, "current-index",
        selection, "selected", G_BINDING_SYNC_CREATE);

    gtk_list_view_set_model (GTK_LIST_VIEW (self->video_list_view),
        GTK_SELECTION_MODEL (selection));
    g_object_unref (selection);

    stream_list = clapper_player_get_audio_streams (self->player);
    selection = gtk_single_selection_new (gst_object_ref (stream_list));
    gtk_single_selection_set_autoselect (selection, FALSE);

    self->audio_binding = g_object_bind_property (stream_list, "current-index",
        selection, "selected", G_BINDING_SYNC_CREATE);

    gtk_list_view_set_model (GTK_LIST_VIEW (self->audio_list_view),
        GTK_SELECTION_MODEL (selection));
    g_object_unref (selection);

    stream_list = clapper_player_get_subtitle_streams (self->player);
    selection = gtk_single_selection_new (gst_object_ref (stream_list));
    gtk_single_selection_set_autoselect (selection, FALSE);

    self->subtitle_binding = g_object_bind_property (stream_list, "current-index",
        selection, "selected", G_BINDING_SYNC_CREATE);

    gtk_list_view_set_model (GTK_LIST_VIEW (self->subtitle_list_view),
        GTK_SELECTION_MODEL (selection));
    g_object_unref (selection);
  }

  GTK_WIDGET_CLASS (parent_class)->realize (widget);
}

static void
clapper_gtk_extra_menu_button_unrealize (GtkWidget *widget)
{
  ClapperGtkExtraMenuButton *self = CLAPPER_GTK_EXTRA_MENU_BUTTON_CAST (widget);

  GST_TRACE_OBJECT (self, "Unrealize");

  g_clear_pointer (&self->video_binding, g_binding_unbind);
  g_clear_pointer (&self->audio_binding, g_binding_unbind);
  g_clear_pointer (&self->subtitle_binding, g_binding_unbind);

  gtk_list_view_set_model (GTK_LIST_VIEW (self->video_list_view), NULL);
  gtk_list_view_set_model (GTK_LIST_VIEW (self->audio_list_view), NULL);
  gtk_list_view_set_model (GTK_LIST_VIEW (self->subtitle_list_view), NULL);

  if (self->player) {
    g_signal_handlers_disconnect_by_func (self->player, _player_mute_changed_cb, self);

    self->player = NULL;
  }

  GTK_WIDGET_CLASS (parent_class)->unrealize (widget);
}

static void
popover_map_cb (GtkWidget *widget, ClapperGtkExtraMenuButton *self)
{
  ClapperQueue *queue;

  GST_TRACE_OBJECT (self, "Popover map");

  if (G_UNLIKELY (self->player == NULL))
    return;

  queue = clapper_player_get_queue (self->player);

  self->volume_binding = g_object_bind_property_full (self->player, "volume",
      self->volume_spin, "value",
      G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE,
      (GBindingTransformFunc) _double_transform_func,
      (GBindingTransformFunc) _double_transform_func,
      NULL, NULL);
  self->speed_binding = g_object_bind_property_full (self->player, "speed",
      self->speed_spin, "value",
      G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE,
      (GBindingTransformFunc) _double_transform_func,
      (GBindingTransformFunc) _double_transform_func,
      NULL, NULL);

  g_signal_connect (self->player, "notify::subtitles-enabled",
      G_CALLBACK (_subtitles_enabled_changed_cb), self);
  _subtitles_enabled_changed_cb (self->player, NULL, self);

  g_signal_connect (queue, "notify::current-item",
      G_CALLBACK (_queue_current_item_changed_cb), self);
  _queue_current_item_changed_cb (queue, NULL, self);
}

static void
popover_unmap_cb (GtkWidget *widget, ClapperGtkExtraMenuButton *self)
{
  ClapperQueue *queue;

  GST_TRACE_OBJECT (self, "Popover unmap");

  if (G_UNLIKELY (self->player == NULL))
    return;

  queue = clapper_player_get_queue (self->player);

  g_clear_pointer (&self->volume_binding, g_binding_unbind);
  g_clear_pointer (&self->speed_binding, g_binding_unbind);

  g_signal_handlers_disconnect_by_func (self->player, _subtitles_enabled_changed_cb, self);
  g_signal_handlers_disconnect_by_func (queue, _queue_current_item_changed_cb, self);
}

static void
clapper_gtk_extra_menu_button_dispose (GObject *object)
{
  ClapperGtkExtraMenuButton *self = CLAPPER_GTK_EXTRA_MENU_BUTTON_CAST (object);

  gtk_widget_dispose_template (GTK_WIDGET (object), CLAPPER_GTK_TYPE_EXTRA_MENU_BUTTON);

  g_clear_pointer (&self->menu_button, gtk_widget_unparent);

  G_OBJECT_CLASS (parent_class)->dispose (object);
}

static void
clapper_gtk_extra_menu_button_finalize (GObject *object)
{
  ClapperGtkExtraMenuButton *self = CLAPPER_GTK_EXTRA_MENU_BUTTON_CAST (object);

  GST_TRACE_OBJECT (self, "Finalize");

  gst_clear_object (&self->current_item);
  g_object_unref (self->action_group);

  G_OBJECT_CLASS (parent_class)->finalize (object);
}

static void
clapper_gtk_extra_menu_button_get_property (GObject *object, guint prop_id,
    GValue *value, GParamSpec *pspec)
{
  ClapperGtkExtraMenuButton *self = CLAPPER_GTK_EXTRA_MENU_BUTTON_CAST (object);

  switch (prop_id) {
    case PROP_VOLUME_VISIBLE:
      g_value_set_boolean (value, clapper_gtk_extra_menu_button_get_volume_visible (self));
      break;
    case PROP_SPEED_VISIBLE:
      g_value_set_boolean (value, clapper_gtk_extra_menu_button_get_speed_visible (self));
      break;
    case PROP_CAN_OPEN_SUBTITLES:
      g_value_set_boolean (value, clapper_gtk_extra_menu_button_get_can_open_subtitles (self));
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
  }
}

static void
clapper_gtk_extra_menu_button_set_property (GObject *object, guint prop_id,
    const GValue *value, GParamSpec *pspec)
{
  ClapperGtkExtraMenuButton *self = CLAPPER_GTK_EXTRA_MENU_BUTTON_CAST (object);

  switch (prop_id) {
    case PROP_VOLUME_VISIBLE:
      clapper_gtk_extra_menu_button_set_volume_visible (self, g_value_get_boolean (value));
      break;
    case PROP_SPEED_VISIBLE:
      clapper_gtk_extra_menu_button_set_speed_visible (self, g_value_get_boolean (value));
      break;
    case PROP_CAN_OPEN_SUBTITLES:
      clapper_gtk_extra_menu_button_set_can_open_subtitles (self, g_value_get_boolean (value));
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
  }
}

static void
clapper_gtk_extra_menu_button_class_init (ClapperGtkExtraMenuButtonClass *klass)
{
  GObjectClass *gobject_class = (GObjectClass *) klass;
  GtkWidgetClass *widget_class = (GtkWidgetClass *) klass;

  GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clappergtkextramenubutton", 0,
      "Clapper GTK Extra Menu Button");
  clapper_gtk_init_translations ();

  gobject_class->get_property = clapper_gtk_extra_menu_button_get_property;
  gobject_class->set_property = clapper_gtk_extra_menu_button_set_property;
  gobject_class->dispose = clapper_gtk_extra_menu_button_dispose;
  gobject_class->finalize = clapper_gtk_extra_menu_button_finalize;

  widget_class->compute_expand = clapper_gtk_extra_menu_button_compute_expand;
  widget_class->realize = clapper_gtk_extra_menu_button_realize;
  widget_class->unrealize = clapper_gtk_extra_menu_button_unrealize;

  /**
   * ClapperGtkExtraMenuButton:volume-visible:
   *
   * Visibility of volume control inside popover.
   */
  param_specs[PROP_VOLUME_VISIBLE] = g_param_spec_boolean ("volume-visible",
      NULL, NULL, TRUE,
      G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);

  /**
   * ClapperGtkExtraMenuButton:speed-visible:
   *
   * Visibility of speed control inside popover.
   */
  param_specs[PROP_SPEED_VISIBLE] = g_param_spec_boolean ("speed-visible",
      NULL, NULL, TRUE,
      G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);

  /**
   * ClapperGtkExtraMenuButton:can-open-subtitles:
   *
   * Visibility of open subtitles option inside popover.
   */
  param_specs[PROP_CAN_OPEN_SUBTITLES] = g_param_spec_boolean ("can-open-subtitles",
      NULL, NULL, FALSE,
      G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);

  /**
   * ClapperGtkExtraMenuButton::open-subtitles:
   * @button: a #ClapperGtkExtraMenuButton
   * @item: a #ClapperMediaItem
   *
   * A signal that user wants to open subtitles file.
   *
   * Implementation should add a way for user to select subtitles to open
   * such as by e.g. using [class@Gtk.FileDialog] and then add them to the
   * @item using [method@Clapper.MediaItem.set_suburi] method.
   *
   * This signal will pass the [class@Clapper.MediaItem] that was current when
   * user clicked the open button and subtitles should be added to this @item.
   * This avoids situations where another item starts playing before user selects
   * subtitles file to be opened. When using asynchronous operations to open file,
   * implementation should [method@GObject.Object.ref] the item to ensure that it
   * stays valid until finish.
   *
   * Note that this signal will not be emitted if open button is not visible by
   * setting [method@ClapperGtk.ExtraMenuButton.set_can_open_subtitles] to %TRUE,
   * so you do not have to implement handler for it otherwise.
   */
  signals[SIGNAL_OPEN_SUBTITLES] = g_signal_new ("open-subtitles",
      G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
      0, NULL, NULL, NULL, G_TYPE_NONE, 1, CLAPPER_TYPE_MEDIA_ITEM);

  g_object_class_install_properties (gobject_class, PROP_LAST, param_specs);

  gtk_widget_class_set_template_from_resource (widget_class,
      CLAPPER_GTK_RESOURCE_PREFIX "/ui/clapper-gtk-extra-menu-button.ui");

  gtk_widget_class_bind_template_child (widget_class, ClapperGtkExtraMenuButton, menu_button);

  gtk_widget_class_bind_template_child (widget_class, ClapperGtkExtraMenuButton, volume_box);
  gtk_widget_class_bind_template_child (widget_class, ClapperGtkExtraMenuButton, volume_button);
  gtk_widget_class_bind_template_child (widget_class, ClapperGtkExtraMenuButton, volume_spin);

  gtk_widget_class_bind_template_child (widget_class, ClapperGtkExtraMenuButton, speed_box);
  gtk_widget_class_bind_template_child (widget_class, ClapperGtkExtraMenuButton, speed_button);
  gtk_widget_class_bind_template_child (widget_class, ClapperGtkExtraMenuButton, speed_spin);

  gtk_widget_class_bind_template_child (widget_class, ClapperGtkExtraMenuButton, top_separator);

  gtk_widget_class_bind_template_child (widget_class, ClapperGtkExtraMenuButton, video_list_view);
  gtk_widget_class_bind_template_child (widget_class, ClapperGtkExtraMenuButton, video_sw);

  gtk_widget_class_bind_template_child (widget_class, ClapperGtkExtraMenuButton, audio_list_view);
  gtk_widget_class_bind_template_child (widget_class, ClapperGtkExtraMenuButton, audio_sw);

  gtk_widget_class_bind_template_child (widget_class, ClapperGtkExtraMenuButton, subtitle_list_view);
  gtk_widget_class_bind_template_child (widget_class, ClapperGtkExtraMenuButton, subtitle_sw);

  gtk_widget_class_bind_template_callback (widget_class, volume_spin_input_cb);
  gtk_widget_class_bind_template_callback (widget_class, volume_spin_output_cb);
  gtk_widget_class_bind_template_callback (widget_class, volume_spin_changed_cb);

  gtk_widget_class_bind_template_callback (widget_class, speed_spin_input_cb);
  gtk_widget_class_bind_template_callback (widget_class, speed_spin_output_cb);
  gtk_widget_class_bind_template_callback (widget_class, speed_spin_changed_cb);

  gtk_widget_class_bind_template_callback (widget_class, popover_map_cb);
  gtk_widget_class_bind_template_callback (widget_class, popover_unmap_cb);
  gtk_widget_class_bind_template_callback (widget_class, visible_submenu_changed_cb);

  gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT);
  gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_BUTTON);
  gtk_widget_class_set_css_name (widget_class, "clapper-gtk-extra-menu-button");
}
