Files
clapper/src/lib/clapper-gtk/clapper-gtk-seek-bar.c
Rafał Dzięgiel 1327dc23e4 clapper-gtk: Fix seek bar touchpad scrolling
Since we only do seek on scale drop, handle scrubbing using touchpad
the same way by marking it as "dragging" after scrubbing starts and
doing seek after it ends.
2024-04-07 20:08:53 +02:00

822 lines
24 KiB
C

/* 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.
*/
/**
* ClapperGtkSeekBar:
*
* A bar for seeking and displaying playback position.
*/
#include "config.h"
#include <clapper/clapper.h>
#include "clapper-gtk-seek-bar.h"
#include "clapper-gtk-container.h"
#include "clapper-gtk-utils.h"
#define DEFAULT_REVEAL_LABELS TRUE
#define DEFAULT_SEEK_METHOD CLAPPER_PLAYER_SEEK_METHOD_NORMAL
#define GST_CAT_DEFAULT clapper_gtk_seek_bar_debug
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
struct _ClapperGtkSeekBar
{
GtkWidget parent;
GtkWidget *position_revealer;
GtkWidget *position_label;
GtkWidget *scale;
GtkPopover *popover;
GtkLabel *popover_label;
GtkWidget *duration_revealer;
GtkWidget *duration_label;
gboolean has_hours;
gboolean has_markers;
gboolean can_scrub;
gboolean scrubbing;
gboolean dragging;
guint position_uint;
gulong position_signal_id;
gboolean reveal_labels;
ClapperPlayerSeekMethod seek_method;
ClapperPlayer *player;
ClapperMediaItem *current_item;
/* Cache */
gdouble curr_marker_start;
gdouble next_marker_start;
};
static void
clapper_gtk_seek_bar_add_child (GtkBuildable *buildable,
GtkBuilder *builder, GObject *child, const char *type)
{
if (GTK_IS_WIDGET (child)) {
gtk_widget_insert_before (GTK_WIDGET (child), GTK_WIDGET (buildable), NULL);
} else {
GtkBuildableIface *parent_iface = g_type_interface_peek_parent (GTK_BUILDABLE_GET_IFACE (buildable));
parent_iface->add_child (buildable, builder, child, type);
}
}
static void
_buildable_iface_init (GtkBuildableIface *iface)
{
iface->add_child = clapper_gtk_seek_bar_add_child;
}
#define parent_class clapper_gtk_seek_bar_parent_class
G_DEFINE_TYPE_WITH_CODE (ClapperGtkSeekBar, clapper_gtk_seek_bar, GTK_TYPE_WIDGET,
G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, _buildable_iface_init))
enum
{
PROP_0,
PROP_REVEAL_LABELS,
PROP_SEEK_METHOD,
PROP_LAST
};
static GParamSpec *param_specs[PROP_LAST] = { NULL, };
static inline gboolean
_prepare_popover (ClapperGtkSeekBar *self, gdouble x,
gdouble pointing_val, gdouble upper)
{
/* Avoid iterating through markers if within last marker range
* (currently set title label remains the same) */
gboolean found_title = (pointing_val >= self->curr_marker_start
&& pointing_val < self->next_marker_start);
if (!found_title) {
ClapperTimeline *timeline = clapper_media_item_get_timeline (self->current_item);
guint i = clapper_timeline_get_n_markers (timeline);
GST_DEBUG ("Searching for marker at: %lf", pointing_val);
/* We start from the end of scale */
self->next_marker_start = upper;
while (i--) {
ClapperMarker *marker = clapper_timeline_get_marker (timeline, i);
self->curr_marker_start = clapper_marker_get_start (marker);
if (self->curr_marker_start <= pointing_val) {
const gchar *title = clapper_marker_get_title (marker);
GST_DEBUG ("Found marker, range: (%lf-%lf), title: \"%s\"",
self->curr_marker_start, self->next_marker_start,
GST_STR_NULL (title));
/* XXX: It does string comparison internally, so its more efficient
* for us and we do not have to compare strings here too */
gtk_label_set_label (self->popover_label, title);
found_title = (title != NULL);
}
gst_object_unref (marker);
if (found_title)
break;
self->next_marker_start = self->curr_marker_start;
}
}
gtk_popover_set_pointing_to (self->popover,
&(const GdkRectangle){ x, 0, 1, 1 });
return found_title;
}
static inline gboolean
_compute_scale_coords (ClapperGtkSeekBar *self,
gdouble *min_pointing_val, gdouble *max_pointing_val)
{
graphene_rect_t slider_bounds;
if (!gtk_widget_compute_bounds (GTK_WIDGET (self), self->scale, &slider_bounds))
return FALSE;
/* XXX: Number "2" is the correction for range protruding rounded sides
* compared to how marks above/below it are positioned */
*min_pointing_val = -slider_bounds.origin.x + 2;
*max_pointing_val = slider_bounds.size.width + slider_bounds.origin.x - 2;
return TRUE;
}
static void
_player_position_changed_cb (ClapperPlayer *player,
GParamSpec *pspec G_GNUC_UNUSED, ClapperGtkSeekBar *self)
{
GtkAdjustment *adjustment;
gdouble position;
if (self->dragging)
return;
position = clapper_player_get_position (player);
if (ABS (self->position_uint - position) < 1)
return;
GST_LOG_OBJECT (self, "Position changed: %lf", position);
self->position_uint = (guint) position;
adjustment = gtk_range_get_adjustment (GTK_RANGE (self->scale));
gtk_adjustment_set_value (adjustment, position);
}
static void
_player_state_changed_cb (ClapperPlayer *player,
GParamSpec *pspec G_GNUC_UNUSED, ClapperGtkSeekBar *self)
{
switch (clapper_player_get_state (player)) {
case CLAPPER_PLAYER_STATE_PAUSED:
/* Force refresh, so scale always reaches end after playback */
self->position_uint = G_MAXUINT;
_player_position_changed_cb (player, NULL, self);
break;
default:
break;
}
}
static void
_player_seek_done_cb (ClapperPlayer *player, ClapperGtkSeekBar *self)
{
GST_DEBUG ("Seek done");
if (self->position_signal_id == 0) {
self->position_signal_id = g_signal_connect (self->player,
"notify::position", G_CALLBACK (_player_position_changed_cb), self);
}
_player_position_changed_cb (player, NULL, self);
}
static void
scale_value_changed_cb (GtkRange *range, ClapperGtkSeekBar *self)
{
gdouble value = gtk_range_get_value (range);
gchar *position_str = g_strdup_printf ("%" CLAPPER_TIME_FORMAT, CLAPPER_TIME_ARGS (value));
gtk_label_set_label (GTK_LABEL (self->position_label),
(self->has_hours) ? position_str : position_str + 3);
g_free (position_str);
if (self->dragging && self->has_markers) {
gdouble min_pointing_val, max_pointing_val;
gdouble x, upper, scaling;
if (!_compute_scale_coords (self, &min_pointing_val, &max_pointing_val)) {
gtk_popover_popdown (self->popover);
return;
}
upper = gtk_adjustment_get_upper (
gtk_range_get_adjustment (GTK_RANGE (self->scale)));
scaling = (upper / (max_pointing_val - min_pointing_val));
x = min_pointing_val + (value / scaling);
if (_prepare_popover (self, x, value, upper))
gtk_popover_popup (self->popover);
else
gtk_popover_popdown (self->popover);
}
}
static void
scale_css_classes_changed_cb (GtkWidget *widget,
GParamSpec *pspec G_GNUC_UNUSED, ClapperGtkSeekBar *self)
{
const gboolean dragging = gtk_widget_has_css_class (widget, "dragging");
gdouble value;
if (self->dragging == dragging)
return;
if ((self->dragging = dragging)) {
GST_DEBUG_OBJECT (self, "Scale drag started");
return;
}
value = gtk_range_get_value (GTK_RANGE (widget));
GST_DEBUG_OBJECT (self, "Scale dropped at: %lf", value);
if (G_UNLIKELY (self->player == NULL))
return;
if (self->position_signal_id != 0) {
g_signal_handler_disconnect (self->player, self->position_signal_id);
self->position_signal_id = 0;
}
/* We should be ALWAYS doing normal seeks if dropped at marker position */
if (self->has_markers
&& G_APPROX_VALUE (self->curr_marker_start, value, FLT_EPSILON)) {
GST_DEBUG ("Seeking to marker");
clapper_player_seek (self->player, value);
} else {
clapper_player_seek_custom (self->player, value, self->seek_method);
}
}
static void
scale_scroll_begin_cb (GtkEventControllerScroll *scroll, ClapperGtkSeekBar *self)
{
self->can_scrub = TRUE;
}
static gboolean
scale_scroll_cb (GtkEventControllerScroll *scroll,
gdouble dx, gdouble dy, ClapperGtkSeekBar *self)
{
if (self->can_scrub && !self->scrubbing) {
GST_DEBUG_OBJECT (self, "Scrubbing start");
self->scrubbing = TRUE;
gtk_widget_add_css_class (self->scale, "dragging");
return TRUE;
}
return FALSE;
}
static void
scale_scroll_end_cb (GtkEventControllerScroll *scroll, ClapperGtkSeekBar *self)
{
if (self->scrubbing) {
GST_DEBUG_OBJECT (self, "Scrubbing end");
gtk_widget_remove_css_class (self->scale, "dragging");
self->scrubbing = FALSE;
}
self->can_scrub = FALSE;
}
static void
motion_cb (GtkEventControllerMotion *motion,
gdouble x, gdouble y, ClapperGtkSeekBar *self)
{
gdouble min_pointing_val, max_pointing_val, pointing_val;
gdouble upper, scaling;
/* If no markers, popover should never popup,
* so we do not try to pop it down here */
if (!self->has_markers)
return;
if (!_compute_scale_coords (self, &min_pointing_val, &max_pointing_val)
|| (x < min_pointing_val || x > max_pointing_val)) {
gtk_popover_popdown (self->popover);
return;
}
upper = gtk_adjustment_get_upper (
gtk_range_get_adjustment (GTK_RANGE (self->scale)));
scaling = (upper / (max_pointing_val - min_pointing_val));
pointing_val = (x - min_pointing_val) * scaling;
GST_LOG ("Cursor pointing to: %lf", pointing_val);
if (_prepare_popover (self, x, pointing_val, upper))
gtk_popover_popup (self->popover);
else
gtk_popover_popdown (self->popover);
}
static void
motion_leave_cb (GtkEventControllerMotion *motion, ClapperGtkSeekBar *self)
{
gtk_popover_popdown (self->popover);
}
static void
touch_released_cb (GtkGestureClick *click, gint n_press,
gdouble x, gdouble y, ClapperGtkSeekBar *self)
{
gtk_popover_popdown (self->popover);
}
static void
_update_duration_label (ClapperGtkSeekBar *self, gdouble duration)
{
GtkAdjustment *adjustment = gtk_range_get_adjustment (GTK_RANGE (self->scale));
gchar *duration_str = g_strdup_printf ("%" CLAPPER_TIME_FORMAT, CLAPPER_TIME_ARGS (duration));
gboolean has_hours = (duration >= 3600);
GST_LOG_OBJECT (self, "Duration changed: %lf", duration);
/* Refresh position label when changing text length */
if (has_hours != self->has_hours) {
self->has_hours = has_hours;
scale_value_changed_cb (GTK_RANGE (self->scale), self);
}
gtk_label_set_label (GTK_LABEL (self->duration_label),
(self->has_hours) ? duration_str : duration_str + 3);
g_free (duration_str);
gtk_adjustment_set_upper (adjustment, duration);
}
static void
_update_scale_marks (ClapperGtkSeekBar *self, ClapperTimeline *timeline)
{
GtkAdjustment *adjustment;
guint i, n_markers = clapper_timeline_get_n_markers (timeline);
GST_DEBUG_OBJECT (self, "Placing %u markers on scale", n_markers);
gtk_scale_clear_marks (GTK_SCALE (self->scale));
self->curr_marker_start = -1;
self->next_marker_start = -1;
self->has_markers = FALSE;
if (n_markers == 0) {
gtk_popover_popdown (self->popover);
return;
}
adjustment = gtk_range_get_adjustment (GTK_RANGE (self->scale));
/* Avoid placing marks when duration is zero. Otherwise we may
* end up with a single mark at zero until another refresh. */
if (gtk_adjustment_get_upper (adjustment) <= 0)
return;
for (i = 0; i < n_markers; ++i) {
ClapperMarker *marker = clapper_timeline_get_marker (timeline, i);
gdouble start = clapper_marker_get_start (marker);
gtk_scale_add_mark (GTK_SCALE (self->scale), start, GTK_POS_TOP, NULL);
gtk_scale_add_mark (GTK_SCALE (self->scale), start, GTK_POS_BOTTOM, NULL);
gst_object_unref (marker);
}
self->has_markers = TRUE;
}
static void
_current_item_duration_changed_cb (ClapperMediaItem *current_item,
GParamSpec *pspec G_GNUC_UNUSED, ClapperGtkSeekBar *self)
{
/* GtkScale ignores markers placed post its adjustment upper range.
* We need to place them again on scale AFTER duration changes. */
_update_duration_label (self, clapper_media_item_get_duration (current_item));
_update_scale_marks (self, clapper_media_item_get_timeline (current_item));
}
static void
_timeline_markers_changed_cb (GListModel *list_model, guint position,
guint removed, guint added, ClapperGtkSeekBar *self)
{
_update_scale_marks (self, CLAPPER_TIMELINE (list_model));
}
static void
_queue_current_item_changed_cb (ClapperQueue *queue,
GParamSpec *pspec G_GNUC_UNUSED, ClapperGtkSeekBar *self)
{
ClapperMediaItem *current_item = clapper_queue_get_current_item (queue);
/* Disconnect signals from old item */
if (self->current_item) {
ClapperTimeline *timeline = clapper_media_item_get_timeline (self->current_item);
g_signal_handlers_disconnect_by_func (self->current_item,
_current_item_duration_changed_cb, self);
g_signal_handlers_disconnect_by_func (timeline,
_timeline_markers_changed_cb, self);
}
gst_object_replace ((GstObject **) &self->current_item, GST_OBJECT_CAST (current_item));
gst_clear_object (&current_item);
/* Reconnect signals to new item */
if (self->current_item) {
ClapperTimeline *timeline = clapper_media_item_get_timeline (self->current_item);
g_signal_connect (self->current_item, "notify::duration",
G_CALLBACK (_current_item_duration_changed_cb), self);
g_signal_connect (timeline, "items-changed",
G_CALLBACK (_timeline_markers_changed_cb), self);
_update_duration_label (self, clapper_media_item_get_duration (self->current_item));
_update_scale_marks (self, timeline);
} else {
gtk_scale_clear_marks (GTK_SCALE (self->scale));
_update_duration_label (self, 0);
}
}
/**
* clapper_gtk_seek_bar_new:
*
* Creates a new #ClapperGtkSeekBar instance.
*
* Returns: a new seek bar #GtkWidget.
*/
GtkWidget *
clapper_gtk_seek_bar_new (void)
{
return g_object_new (CLAPPER_GTK_TYPE_SEEK_BAR, NULL);
}
/**
* clapper_gtk_seek_bar_set_reveal_labels:
* @seek_bar: a #ClapperGtkSeekBar
* @reveal: whether to reveal labels
*
* Set whether the position and duration labels should be revealed.
*/
void
clapper_gtk_seek_bar_set_reveal_labels (ClapperGtkSeekBar *self, gboolean reveal)
{
g_return_if_fail (CLAPPER_GTK_IS_SEEK_BAR (self));
if (self->reveal_labels != reveal) {
self->reveal_labels = reveal;
gtk_revealer_set_reveal_child (GTK_REVEALER (self->position_revealer), reveal);
g_object_notify_by_pspec (G_OBJECT (self), param_specs[PROP_REVEAL_LABELS]);
}
}
/**
* clapper_gtk_seek_bar_get_reveal_labels:
* @seek_bar: a #ClapperGtkSeekBar
*
* Get whether the position and duration labels are going to be revealed.
*
* Returns: TRUE if the labels are going to be revealed, %FALSE otherwise.
*/
gboolean
clapper_gtk_seek_bar_get_reveal_labels (ClapperGtkSeekBar *self)
{
g_return_val_if_fail (CLAPPER_GTK_IS_SEEK_BAR (self), FALSE);
return self->reveal_labels;
}
/**
* clapper_gtk_seek_bar_set_seek_method:
* @seek_bar: a #ClapperGtkSeekBar
* @method: a #ClapperPlayerSeekMethod
*
* Set [enum@Clapper.PlayerSeekMethod] to use when seeking with seek bar.
*/
void
clapper_gtk_seek_bar_set_seek_method (ClapperGtkSeekBar *self, ClapperPlayerSeekMethod method)
{
g_return_if_fail (CLAPPER_GTK_IS_SEEK_BAR (self));
if (self->seek_method != method) {
self->seek_method = method;
GST_DEBUG_OBJECT (self, "Set seek method to: %i", self->seek_method);
g_object_notify_by_pspec (G_OBJECT (self), param_specs[PROP_SEEK_METHOD]);
}
}
/**
* clapper_gtk_seek_bar_get_seek_method:
* @seek_bar: a #ClapperGtkSeekBar
*
* Get [enum@Clapper.PlayerSeekMethod] used when seeking with seek bar.
*
* Returns: #ClapperPlayerSeekMethod used for seeking.
*/
ClapperPlayerSeekMethod
clapper_gtk_seek_bar_get_seek_method (ClapperGtkSeekBar *self)
{
g_return_val_if_fail (CLAPPER_GTK_IS_SEEK_BAR (self), DEFAULT_SEEK_METHOD);
return self->seek_method;
}
static void
clapper_gtk_seek_bar_init (ClapperGtkSeekBar *self)
{
gtk_widget_init_template (GTK_WIDGET (self));
self->reveal_labels = DEFAULT_REVEAL_LABELS;
self->seek_method = DEFAULT_SEEK_METHOD;
self->curr_marker_start = -1;
self->next_marker_start = -1;
gtk_revealer_set_reveal_child (GTK_REVEALER (self->position_revealer), self->reveal_labels);
/* Correction for calculated popover position when marks are drawn */
gtk_popover_set_offset (self->popover, 0, -2);
}
static void
clapper_gtk_seek_bar_compute_expand (GtkWidget *widget,
gboolean *hexpand_p, gboolean *vexpand_p)
{
GtkWidget *w;
gboolean hexpand = FALSE;
gboolean vexpand = FALSE;
for (w = gtk_widget_get_first_child (widget); w != NULL; w = gtk_widget_get_next_sibling (w)) {
hexpand = (hexpand || gtk_widget_compute_expand (w, GTK_ORIENTATION_HORIZONTAL));
vexpand = (vexpand || gtk_widget_compute_expand (w, GTK_ORIENTATION_VERTICAL));
}
*hexpand_p = hexpand;
*vexpand_p = vexpand;
}
static void
clapper_gtk_seek_bar_size_allocate (GtkWidget *widget,
gint width, gint height, gint baseline)
{
ClapperGtkSeekBar *self = CLAPPER_GTK_SEEK_BAR_CAST (widget);
gtk_popover_present (self->popover);
GTK_WIDGET_CLASS (parent_class)->size_allocate (widget, width, height, baseline);
}
static void
clapper_gtk_seek_bar_realize (GtkWidget *widget)
{
ClapperGtkSeekBar *self = CLAPPER_GTK_SEEK_BAR_CAST (widget);
if ((self->player = clapper_gtk_get_player_from_ancestor (widget))) {
ClapperQueue *queue = clapper_player_get_queue (self->player);
g_signal_connect (queue, "notify::current-item",
G_CALLBACK (_queue_current_item_changed_cb), self);
_queue_current_item_changed_cb (queue, NULL, self);
}
GTK_WIDGET_CLASS (parent_class)->realize (widget);
}
static void
clapper_gtk_seek_bar_unrealize (GtkWidget *widget)
{
ClapperGtkSeekBar *self = CLAPPER_GTK_SEEK_BAR_CAST (widget);
if (self->player) {
ClapperQueue *queue = clapper_player_get_queue (self->player);
if (self->position_signal_id != 0) {
g_signal_handler_disconnect (self->player, self->position_signal_id);
self->position_signal_id = 0;
}
g_signal_handlers_disconnect_by_func (queue, _queue_current_item_changed_cb, self);
self->player = NULL;
}
GTK_WIDGET_CLASS (parent_class)->unrealize (widget);
}
static void
clapper_gtk_seek_bar_map (GtkWidget *widget)
{
ClapperGtkSeekBar *self = CLAPPER_GTK_SEEK_BAR_CAST (widget);
if (self->player) {
if (self->position_signal_id == 0) {
self->position_signal_id = g_signal_connect (self->player,
"notify::position", G_CALLBACK (_player_position_changed_cb), self);
}
g_signal_connect (self->player, "notify::state",
G_CALLBACK (_player_state_changed_cb), self);
g_signal_connect (self->player, "seek-done",
G_CALLBACK (_player_seek_done_cb), self);
_player_position_changed_cb (self->player, NULL, self);
}
GTK_WIDGET_CLASS (parent_class)->map (widget);
}
static void
clapper_gtk_seek_bar_unmap (GtkWidget *widget)
{
ClapperGtkSeekBar *self = CLAPPER_GTK_SEEK_BAR_CAST (widget);
if (self->player) {
if (self->position_signal_id != 0) {
g_signal_handler_disconnect (self->player, self->position_signal_id);
self->position_signal_id = 0;
}
g_signal_handlers_disconnect_by_func (self->player, _player_state_changed_cb, self);
g_signal_handlers_disconnect_by_func (self->player, _player_seek_done_cb, self);
}
GTK_WIDGET_CLASS (parent_class)->unmap (widget);
}
static void
_popover_unparent (GtkPopover *popover)
{
gtk_widget_unparent (GTK_WIDGET (popover));
}
static void
clapper_gtk_seek_bar_dispose (GObject *object)
{
ClapperGtkSeekBar *self = CLAPPER_GTK_SEEK_BAR_CAST (object);
gtk_widget_dispose_template (GTK_WIDGET (object), CLAPPER_GTK_TYPE_SEEK_BAR);
g_clear_pointer (&self->position_revealer, gtk_widget_unparent);
g_clear_pointer (&self->scale, gtk_widget_unparent);
g_clear_pointer (&self->popover, _popover_unparent);
g_clear_pointer (&self->duration_revealer, gtk_widget_unparent);
G_OBJECT_CLASS (parent_class)->dispose (object);
}
static void
clapper_gtk_seek_bar_finalize (GObject *object)
{
ClapperGtkSeekBar *self = CLAPPER_GTK_SEEK_BAR_CAST (object);
GST_TRACE_OBJECT (self, "Finalize");
gst_clear_object (&self->current_item);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static void
clapper_gtk_seek_bar_get_property (GObject *object, guint prop_id,
GValue *value, GParamSpec *pspec)
{
ClapperGtkSeekBar *self = CLAPPER_GTK_SEEK_BAR_CAST (object);
switch (prop_id) {
case PROP_REVEAL_LABELS:
g_value_set_boolean (value, clapper_gtk_seek_bar_get_reveal_labels (self));
break;
case PROP_SEEK_METHOD:
g_value_set_enum (value, clapper_gtk_seek_bar_get_seek_method (self));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
clapper_gtk_seek_bar_set_property (GObject *object, guint prop_id,
const GValue *value, GParamSpec *pspec)
{
ClapperGtkSeekBar *self = CLAPPER_GTK_SEEK_BAR_CAST (object);
switch (prop_id) {
case PROP_REVEAL_LABELS:
clapper_gtk_seek_bar_set_reveal_labels (self, g_value_get_boolean (value));
break;
case PROP_SEEK_METHOD:
clapper_gtk_seek_bar_set_seek_method (self, g_value_get_enum (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
clapper_gtk_seek_bar_class_init (ClapperGtkSeekBarClass *klass)
{
GObjectClass *gobject_class = (GObjectClass *) klass;
GtkWidgetClass *widget_class = (GtkWidgetClass *) klass;
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clappergtkseekbar", 0,
"Clapper GTK Seek Bar");
gobject_class->get_property = clapper_gtk_seek_bar_get_property;
gobject_class->set_property = clapper_gtk_seek_bar_set_property;
gobject_class->dispose = clapper_gtk_seek_bar_dispose;
gobject_class->finalize = clapper_gtk_seek_bar_finalize;
widget_class->compute_expand = clapper_gtk_seek_bar_compute_expand;
widget_class->size_allocate = clapper_gtk_seek_bar_size_allocate;
widget_class->realize = clapper_gtk_seek_bar_realize;
widget_class->unrealize = clapper_gtk_seek_bar_unrealize;
widget_class->map = clapper_gtk_seek_bar_map;
widget_class->unmap = clapper_gtk_seek_bar_unmap;
/**
* ClapperGtkSeekBar:reveal-labels:
*
* Reveal state of the position and duration labels.
*/
param_specs[PROP_REVEAL_LABELS] = g_param_spec_boolean ("reveal-labels",
NULL, NULL, DEFAULT_REVEAL_LABELS,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* ClapperGtkSeekBar:seek-method:
*
* Method used for seeking.
*/
param_specs[PROP_SEEK_METHOD] = g_param_spec_enum ("seek-method",
NULL, NULL, CLAPPER_TYPE_PLAYER_SEEK_METHOD, DEFAULT_SEEK_METHOD,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
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-seek-bar.ui");
gtk_widget_class_bind_template_child (widget_class, ClapperGtkSeekBar, position_revealer);
gtk_widget_class_bind_template_child (widget_class, ClapperGtkSeekBar, position_label);
gtk_widget_class_bind_template_child (widget_class, ClapperGtkSeekBar, scale);
gtk_widget_class_bind_template_child (widget_class, ClapperGtkSeekBar, popover);
gtk_widget_class_bind_template_child (widget_class, ClapperGtkSeekBar, popover_label);
gtk_widget_class_bind_template_child (widget_class, ClapperGtkSeekBar, duration_revealer);
gtk_widget_class_bind_template_child (widget_class, ClapperGtkSeekBar, duration_label);
gtk_widget_class_bind_template_callback (widget_class, scale_value_changed_cb);
gtk_widget_class_bind_template_callback (widget_class, scale_css_classes_changed_cb);
gtk_widget_class_bind_template_callback (widget_class, scale_scroll_begin_cb);
gtk_widget_class_bind_template_callback (widget_class, scale_scroll_cb);
gtk_widget_class_bind_template_callback (widget_class, scale_scroll_end_cb);
gtk_widget_class_bind_template_callback (widget_class, motion_cb);
gtk_widget_class_bind_template_callback (widget_class, motion_leave_cb);
gtk_widget_class_bind_template_callback (widget_class, touch_released_cb);
gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BOX_LAYOUT);
gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_GENERIC);
gtk_widget_class_set_css_name (widget_class, "clapper-gtk-seek-bar");
}