mirror of
https://github.com/Rafostar/clapper.git
synced 2025-08-30 07:42:23 +02:00
The Free Software Foundation is now remote-only, and no longer has a street address. Updated license notice text is from https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html#SEC4.
490 lines
14 KiB
C
490 lines
14 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, see
|
|
* <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/**
|
|
* 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.
|
|
*
|
|
* Returns: %TRUE when item URI will be used as fallback, %FALSE otherwise.
|
|
*/
|
|
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");
|
|
}
|