mirror of
https://github.com/Rafostar/clapper.git
synced 2025-08-31 16:31:58 +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:
488
src/lib/clapper-gtk/clapper-gtk-title-label.c
Normal file
488
src/lib/clapper-gtk/clapper-gtk-title-label.c
Normal file
@@ -0,0 +1,488 @@
|
||||
/* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* ClapperGtkTitleLabel:
|
||||
*
|
||||
* A label showing an up to date title of media item.
|
||||
*
|
||||
* By default #ClapperGtkTitleLabel will automatically show title
|
||||
* of [property@Clapper.Queue:current-item] when placed within
|
||||
* [class@ClapperGtk.Video] widget hierarchy.
|
||||
*
|
||||
* Setting [property@ClapperGtk.TitleLabel:media-item] property will
|
||||
* make it show title of that particular [class@Clapper.MediaItem]
|
||||
* instead. Providing an item to read title from also allows using
|
||||
* this [class@Gtk.Widget] outside of [class@ClapperGtk.Video].
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <glib/gi18n-lib.h>
|
||||
#include <gst/gst.h>
|
||||
|
||||
#include "clapper-gtk-title-label.h"
|
||||
#include "clapper-gtk-utils-private.h"
|
||||
|
||||
#define DEFAULT_FALLBACK_TO_URI FALSE
|
||||
|
||||
#define GST_CAT_DEFAULT clapper_gtk_title_label_debug
|
||||
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
|
||||
|
||||
struct _ClapperGtkTitleLabel
|
||||
{
|
||||
GtkWidget parent;
|
||||
|
||||
GtkLabel *label;
|
||||
|
||||
ClapperMediaItem *current_item;
|
||||
ClapperMediaItem *custom_item;
|
||||
gboolean fallback_to_uri;
|
||||
|
||||
ClapperPlayer *player;
|
||||
};
|
||||
|
||||
#define parent_class clapper_gtk_title_label_parent_class
|
||||
G_DEFINE_TYPE (ClapperGtkTitleLabel, clapper_gtk_title_label, GTK_TYPE_WIDGET)
|
||||
|
||||
enum
|
||||
{
|
||||
PROP_0,
|
||||
PROP_MEDIA_ITEM,
|
||||
PROP_CURRENT_TITLE,
|
||||
PROP_FALLBACK_TO_URI,
|
||||
PROP_LAST
|
||||
};
|
||||
|
||||
static GParamSpec *param_specs[PROP_LAST] = { NULL, };
|
||||
|
||||
static void
|
||||
_label_changed_cb (GtkLabel *label G_GNUC_UNUSED,
|
||||
GParamSpec *pspec G_GNUC_UNUSED, ClapperGtkTitleLabel *self)
|
||||
{
|
||||
g_object_notify_by_pspec (G_OBJECT (self), param_specs[PROP_CURRENT_TITLE]);
|
||||
}
|
||||
|
||||
static void
|
||||
_refresh_title (ClapperGtkTitleLabel *self)
|
||||
{
|
||||
ClapperMediaItem *item;
|
||||
gchar *title;
|
||||
|
||||
item = (self->custom_item) ? self->custom_item : self->current_item;
|
||||
|
||||
if (!item) {
|
||||
gtk_label_set_label (self->label, _("No media"));
|
||||
return;
|
||||
}
|
||||
|
||||
title = clapper_media_item_get_title (item);
|
||||
|
||||
if (title) {
|
||||
gtk_label_set_label (self->label, title);
|
||||
g_free (title);
|
||||
} else if (self->fallback_to_uri) {
|
||||
gtk_label_set_label (self->label, clapper_media_item_get_uri (item));
|
||||
} else {
|
||||
gtk_label_set_label (self->label, _("Unknown title"));
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
_media_item_title_changed_cb (ClapperMediaItem *item G_GNUC_UNUSED,
|
||||
GParamSpec *pspec G_GNUC_UNUSED, ClapperGtkTitleLabel *self)
|
||||
{
|
||||
_refresh_title (self);
|
||||
}
|
||||
|
||||
static void
|
||||
_set_current_item (ClapperGtkTitleLabel *self, ClapperMediaItem *current_item)
|
||||
{
|
||||
/* Disconnect signal from old item */
|
||||
if (self->current_item) {
|
||||
g_signal_handlers_disconnect_by_func (self->current_item,
|
||||
_media_item_title_changed_cb, self);
|
||||
}
|
||||
|
||||
gst_object_replace ((GstObject **) &self->current_item, GST_OBJECT_CAST (current_item));
|
||||
GST_DEBUG ("Current item changed to: %" GST_PTR_FORMAT, self->current_item);
|
||||
|
||||
/* Reconnect signal to new item */
|
||||
if (self->current_item) {
|
||||
g_signal_connect (self->current_item, "notify::title",
|
||||
G_CALLBACK (_media_item_title_changed_cb), self);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
_queue_current_item_changed_cb (ClapperQueue *queue,
|
||||
GParamSpec *pspec G_GNUC_UNUSED, ClapperGtkTitleLabel *self)
|
||||
{
|
||||
ClapperMediaItem *current_item = clapper_queue_get_current_item (queue);
|
||||
|
||||
_set_current_item (self, current_item);
|
||||
_refresh_title (self);
|
||||
|
||||
gst_clear_object (¤t_item);
|
||||
}
|
||||
|
||||
static void
|
||||
_bind_current_item (ClapperGtkTitleLabel *self)
|
||||
{
|
||||
ClapperQueue *queue = clapper_player_get_queue (self->player);
|
||||
ClapperMediaItem *current_item;
|
||||
|
||||
GST_DEBUG ("Binding current item");
|
||||
|
||||
g_signal_connect (queue, "notify::current-item",
|
||||
G_CALLBACK (_queue_current_item_changed_cb), self);
|
||||
|
||||
current_item = clapper_queue_get_current_item (queue);
|
||||
_set_current_item (self, current_item);
|
||||
gst_clear_object (¤t_item);
|
||||
}
|
||||
|
||||
static void
|
||||
_unbind_current_item (ClapperGtkTitleLabel *self)
|
||||
{
|
||||
ClapperQueue *queue = clapper_player_get_queue (self->player);
|
||||
|
||||
GST_DEBUG ("Unbinding current item");
|
||||
|
||||
g_signal_handlers_disconnect_by_func (queue,
|
||||
_queue_current_item_changed_cb, self);
|
||||
_set_current_item (self, NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* clapper_gtk_title_label_new:
|
||||
*
|
||||
* Creates a new #ClapperGtkTitleLabel instance.
|
||||
*
|
||||
* Returns: a new title label #GtkWidget.
|
||||
*/
|
||||
GtkWidget *
|
||||
clapper_gtk_title_label_new (void)
|
||||
{
|
||||
return g_object_new (CLAPPER_GTK_TYPE_TITLE_LABEL, NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* clapper_gtk_title_label_set_media_item:
|
||||
* @label: a #ClapperGtkTitleLabel
|
||||
* @item: (nullable): a #ClapperMediaItem
|
||||
*
|
||||
* Set a media item to display title of as label. When set to %NULL,
|
||||
* @label will use default behavior (showing title of current queue item).
|
||||
*/
|
||||
void
|
||||
clapper_gtk_title_label_set_media_item (ClapperGtkTitleLabel *self, ClapperMediaItem *item)
|
||||
{
|
||||
g_return_if_fail (CLAPPER_GTK_IS_TITLE_LABEL (self));
|
||||
g_return_if_fail (item == NULL || CLAPPER_IS_MEDIA_ITEM (item));
|
||||
|
||||
if (self->custom_item == item)
|
||||
return;
|
||||
|
||||
if (self->player) {
|
||||
_unbind_current_item (self);
|
||||
self->player = NULL;
|
||||
}
|
||||
if (self->custom_item) {
|
||||
g_signal_handlers_disconnect_by_func (self->custom_item,
|
||||
_media_item_title_changed_cb, self);
|
||||
}
|
||||
|
||||
gst_object_replace ((GstObject **) &self->custom_item, GST_OBJECT_CAST (item));
|
||||
|
||||
GST_DEBUG ("Set media item: %" GST_PTR_FORMAT, self->custom_item);
|
||||
g_object_notify_by_pspec (G_OBJECT (self), param_specs[PROP_MEDIA_ITEM]);
|
||||
|
||||
if (self->custom_item) {
|
||||
g_signal_connect (self->custom_item, "notify::title",
|
||||
G_CALLBACK (_media_item_title_changed_cb), self);
|
||||
} else if ((self->player = clapper_gtk_get_player_from_ancestor (GTK_WIDGET (self)))) {
|
||||
_bind_current_item (self);
|
||||
}
|
||||
|
||||
_refresh_title (self);
|
||||
}
|
||||
|
||||
/**
|
||||
* clapper_gtk_title_label_get_media_item:
|
||||
* @label: a #ClapperGtkTitleLabel
|
||||
*
|
||||
* Get currently set media item to display title of.
|
||||
*
|
||||
* Returns: (transfer none) (nullable): currently set media item.
|
||||
*/
|
||||
ClapperMediaItem *
|
||||
clapper_gtk_title_label_get_media_item (ClapperGtkTitleLabel *self)
|
||||
{
|
||||
g_return_val_if_fail (CLAPPER_GTK_IS_TITLE_LABEL (self), NULL);
|
||||
|
||||
return self->custom_item;
|
||||
}
|
||||
|
||||
/**
|
||||
* clapper_gtk_title_label_get_current_title:
|
||||
* @label: a #ClapperGtkTitleLabel
|
||||
*
|
||||
* Get currently displayed title by @label.
|
||||
*
|
||||
* Returns: (transfer none): text of title label.
|
||||
*/
|
||||
const gchar *
|
||||
clapper_gtk_title_label_get_current_title (ClapperGtkTitleLabel *self)
|
||||
{
|
||||
g_return_val_if_fail (CLAPPER_GTK_IS_TITLE_LABEL (self), NULL);
|
||||
|
||||
return gtk_label_get_label (self->label);
|
||||
}
|
||||
|
||||
/**
|
||||
* clapper_gtk_title_label_set_fallback_to_uri:
|
||||
* @label: a #ClapperGtkTitleLabel
|
||||
* @enabled: whether enabled
|
||||
*
|
||||
* Set whether a [property@Clapper.MediaItem:uri] property should
|
||||
* be displayed as a label text when no other title could be determined.
|
||||
*/
|
||||
void
|
||||
clapper_gtk_title_label_set_fallback_to_uri (ClapperGtkTitleLabel *self, gboolean enabled)
|
||||
{
|
||||
g_return_if_fail (CLAPPER_GTK_IS_TITLE_LABEL (self));
|
||||
|
||||
if (self->fallback_to_uri != enabled) {
|
||||
self->fallback_to_uri = enabled;
|
||||
g_object_notify_by_pspec (G_OBJECT (self), param_specs[PROP_FALLBACK_TO_URI]);
|
||||
|
||||
_refresh_title (self);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* clapper_gtk_title_label_get_fallback_to_uri:
|
||||
* @label: a #ClapperGtkTitleLabel
|
||||
*
|
||||
* Get whether a [property@Clapper.MediaItem:uri] property is going
|
||||
* be displayed as a label text when no other title could be determined.
|
||||
*/
|
||||
gboolean
|
||||
clapper_gtk_title_label_get_fallback_to_uri (ClapperGtkTitleLabel *self)
|
||||
{
|
||||
g_return_val_if_fail (CLAPPER_GTK_IS_TITLE_LABEL (self), FALSE);
|
||||
|
||||
return self->fallback_to_uri;
|
||||
}
|
||||
|
||||
static void
|
||||
clapper_gtk_title_label_init (ClapperGtkTitleLabel *self)
|
||||
{
|
||||
self->label = GTK_LABEL (gtk_label_new (NULL));
|
||||
gtk_label_set_single_line_mode (self->label, TRUE);
|
||||
gtk_label_set_ellipsize (self->label, PANGO_ELLIPSIZE_END);
|
||||
gtk_widget_set_can_target (GTK_WIDGET (self->label), FALSE);
|
||||
gtk_widget_set_parent (GTK_WIDGET (self->label), GTK_WIDGET (self));
|
||||
|
||||
self->fallback_to_uri = DEFAULT_FALLBACK_TO_URI;
|
||||
|
||||
/* Apply CSS styles to internal label */
|
||||
g_object_bind_property (self, "css-classes",
|
||||
self->label, "css-classes", G_BINDING_DEFAULT);
|
||||
}
|
||||
|
||||
static void
|
||||
clapper_gtk_title_label_constructed (GObject *object)
|
||||
{
|
||||
ClapperGtkTitleLabel *self = CLAPPER_GTK_TITLE_LABEL_CAST (object);
|
||||
|
||||
/* Ensure label if no custom item set yet */
|
||||
if (!self->custom_item)
|
||||
_refresh_title (self);
|
||||
|
||||
/* This avoids us from comparing label changes as GTK will do this
|
||||
* for us and emit this signal only when label text actually changes. */
|
||||
g_signal_connect (self->label, "notify::label",
|
||||
G_CALLBACK (_label_changed_cb), self);
|
||||
|
||||
G_OBJECT_CLASS (parent_class)->constructed (object);
|
||||
}
|
||||
|
||||
static void
|
||||
clapper_gtk_title_label_compute_expand (GtkWidget *widget,
|
||||
gboolean *hexpand_p, gboolean *vexpand_p)
|
||||
{
|
||||
ClapperGtkTitleLabel *self = CLAPPER_GTK_TITLE_LABEL_CAST (widget);
|
||||
|
||||
*hexpand_p = gtk_widget_compute_expand ((GtkWidget *) self->label, GTK_ORIENTATION_HORIZONTAL);
|
||||
*vexpand_p = gtk_widget_compute_expand ((GtkWidget *) self->label, GTK_ORIENTATION_VERTICAL);
|
||||
}
|
||||
|
||||
static void
|
||||
clapper_gtk_title_label_root (GtkWidget *widget)
|
||||
{
|
||||
ClapperGtkTitleLabel *self = CLAPPER_GTK_TITLE_LABEL_CAST (widget);
|
||||
|
||||
GTK_WIDGET_CLASS (parent_class)->root (widget);
|
||||
|
||||
if (!self->custom_item
|
||||
&& (self->player = clapper_gtk_get_player_from_ancestor (widget))) {
|
||||
GST_LOG ("Label placed without media item set");
|
||||
_bind_current_item (self);
|
||||
_refresh_title (self);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
clapper_gtk_title_label_unroot (GtkWidget *widget)
|
||||
{
|
||||
ClapperGtkTitleLabel *self = CLAPPER_GTK_TITLE_LABEL_CAST (widget);
|
||||
|
||||
if (self->player) {
|
||||
_unbind_current_item (self);
|
||||
self->player = NULL;
|
||||
}
|
||||
|
||||
GTK_WIDGET_CLASS (parent_class)->unroot (widget);
|
||||
}
|
||||
|
||||
static void
|
||||
_label_unparent (GtkLabel *label)
|
||||
{
|
||||
gtk_widget_unparent (GTK_WIDGET (label));
|
||||
}
|
||||
|
||||
static void
|
||||
clapper_gtk_title_label_dispose (GObject *object)
|
||||
{
|
||||
ClapperGtkTitleLabel *self = CLAPPER_GTK_TITLE_LABEL_CAST (object);
|
||||
|
||||
if (self->custom_item) {
|
||||
g_signal_handlers_disconnect_by_func (self->custom_item,
|
||||
_media_item_title_changed_cb, self);
|
||||
}
|
||||
if (self->label) {
|
||||
g_signal_handlers_disconnect_by_func (self->label,
|
||||
_label_changed_cb, self);
|
||||
}
|
||||
|
||||
gst_clear_object (&self->current_item);
|
||||
gst_clear_object (&self->custom_item);
|
||||
g_clear_pointer (&self->label, _label_unparent);
|
||||
|
||||
G_OBJECT_CLASS (parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
static void
|
||||
clapper_gtk_title_label_get_property (GObject *object, guint prop_id,
|
||||
GValue *value, GParamSpec *pspec)
|
||||
{
|
||||
ClapperGtkTitleLabel *self = CLAPPER_GTK_TITLE_LABEL_CAST (object);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_MEDIA_ITEM:
|
||||
g_value_set_object (value, clapper_gtk_title_label_get_media_item (self));
|
||||
break;
|
||||
case PROP_CURRENT_TITLE:
|
||||
g_value_set_string (value, clapper_gtk_title_label_get_current_title (self));
|
||||
break;
|
||||
case PROP_FALLBACK_TO_URI:
|
||||
g_value_set_boolean (value, clapper_gtk_title_label_get_fallback_to_uri (self));
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
clapper_gtk_title_label_set_property (GObject *object, guint prop_id,
|
||||
const GValue *value, GParamSpec *pspec)
|
||||
{
|
||||
ClapperGtkTitleLabel *self = CLAPPER_GTK_TITLE_LABEL_CAST (object);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_MEDIA_ITEM:
|
||||
clapper_gtk_title_label_set_media_item (self, g_value_get_object (value));
|
||||
break;
|
||||
case PROP_FALLBACK_TO_URI:
|
||||
clapper_gtk_title_label_set_fallback_to_uri (self, g_value_get_boolean (value));
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
clapper_gtk_title_label_class_init (ClapperGtkTitleLabelClass *klass)
|
||||
{
|
||||
GObjectClass *gobject_class = (GObjectClass *) klass;
|
||||
GtkWidgetClass *widget_class = (GtkWidgetClass *) klass;
|
||||
|
||||
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clappergtktitlelabel", 0,
|
||||
"Clapper GTK Title Label");
|
||||
clapper_gtk_init_translations ();
|
||||
|
||||
gobject_class->constructed = clapper_gtk_title_label_constructed;
|
||||
gobject_class->get_property = clapper_gtk_title_label_get_property;
|
||||
gobject_class->set_property = clapper_gtk_title_label_set_property;
|
||||
gobject_class->dispose = clapper_gtk_title_label_dispose;
|
||||
|
||||
widget_class->compute_expand = clapper_gtk_title_label_compute_expand;
|
||||
|
||||
/* Using root/unroot so label "current-title" is immediately
|
||||
* updated and can be accessed before label was made visible */
|
||||
widget_class->root = clapper_gtk_title_label_root;
|
||||
widget_class->unroot = clapper_gtk_title_label_unroot;
|
||||
|
||||
/**
|
||||
* ClapperGtkTitleLabel:media-item:
|
||||
*
|
||||
* Currently set media item to display title of.
|
||||
*/
|
||||
param_specs[PROP_MEDIA_ITEM] = g_param_spec_object ("media-item",
|
||||
NULL, NULL, CLAPPER_TYPE_MEDIA_ITEM,
|
||||
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
|
||||
|
||||
/**
|
||||
* ClapperGtkTitleLabel:current-title:
|
||||
*
|
||||
* Currently displayed title.
|
||||
*/
|
||||
param_specs[PROP_CURRENT_TITLE] = g_param_spec_string ("current-title",
|
||||
NULL, NULL, NULL,
|
||||
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
|
||||
|
||||
/**
|
||||
* ClapperGtkTitleLabel:fallback-to-uri:
|
||||
*
|
||||
* When title cannot be determined, show URI instead.
|
||||
*/
|
||||
param_specs[PROP_FALLBACK_TO_URI] = g_param_spec_boolean ("fallback-to-uri",
|
||||
NULL, NULL, DEFAULT_FALLBACK_TO_URI,
|
||||
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_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT);
|
||||
gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_GENERIC);
|
||||
gtk_widget_class_set_css_name (widget_class, "clapper-gtk-title-label");
|
||||
}
|
Reference in New Issue
Block a user