mirror of
https://github.com/Rafostar/clapper.git
synced 2025-08-31 08:21:59 +02:00
lib: Introduce Clapper GTK integration library
An easy to use GTK integration library. Meant as a GtkVideo alternative. While GtkVideo is more of a simple example of video playback under GTK4, this acts as a full-fledged video player in the form of a GtkWidget that can be placed anywhere within application. The widget offers customization options for both top header and bottom playback controls panels. The Clapper playback API is exposed under widget "player" property making it easy for the programmer to set media and control playback programically. The new library will be distributed with Clapper player. This includes public headers and GObject Introspection support. Licensed under LGPL-2.1-or-later.
This commit is contained in:
783
src/lib/clapper-gtk/clapper-gtk-seek-bar.c
Normal file
783
src/lib/clapper-gtk/clapper-gtk-seek-bar.c
Normal file
@@ -0,0 +1,783 @@
|
||||
/* 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 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
|
||||
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 (¤t_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, 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");
|
||||
}
|
Reference in New Issue
Block a user