diff --git a/doc/reference/clapper-gtk/clapper-gtk.toml.in b/doc/reference/clapper-gtk/clapper-gtk.toml.in new file mode 100644 index 00000000..f120b038 --- /dev/null +++ b/doc/reference/clapper-gtk/clapper-gtk.toml.in @@ -0,0 +1,63 @@ +[library] +version = "@CLAPPER_VERSION@" +browse_url = "https://github.com/Rafostar/clapper/" +repository_url = "https://github.com/Rafostar/clapper.git" +website_url = "https://rafostar.github.io/clapper/" +docs_url = "https://rafostar.github.io/clapper/doc/clapper-gtk/" +authors = "RafaÅ‚ DziÄ™giel" +logo_url = "clapper-logo.svg" +license = "LGPL-2.1-or-later" +description = "Clapper GTK integration library" +devhelp = true +search_index = true + +dependencies = ["Clapper@CLAPPER_VERSION_SUFFIX@", "Gtk-4.0"] + + [dependencies."Clapper@CLAPPER_VERSION_SUFFIX@"] + name = "Clapper" + description = "Clapper playback library" + docs_url = "https://rafostar.github.io/clapper/doc/clapper/" + + [dependencies."Gtk-4.0"] + name = "Gtk" + description = "The GTK toolkit" + docs_url = "https://docs.gtk.org/gtk4/" + +related = ["GLib-2.0", "GObject-2.0", "Gio-2.0", "Gst-1.0"] + + [related."GLib-2.0"] + name = "GLib" + description = "A general-purpose, portable utility library" + docs_url = "https://docs.gtk.org/glib/" + + [related."GObject-2.0"] + name = "GObject" + description = "The base type system library" + docs_url = "https://docs.gtk.org/gobject/" + + [related."Gio-2.0"] + name = "Gio" + description = "GObject Interfaces and Objects, Networking, IPC, and I/O" + docs_url = "https://docs.gtk.org/gio/" + + [related."Gst-1.0"] + name = "Gst" + description = "GStreamer core library" + docs_url = "https://gstreamer.freedesktop.org/documentation/gstreamer/gi-index.html" + +[theme] +name = "basic" +show_index_summary = true +show_class_hierarchy = true + +[source-location] +base_url = "https://github.com/Rafostar/clapper/tree/master/" + +[extra] +# The same order will be used when generating the index +content_files = [ +] +content_images = [ + "images/clapper-logo.svg", +] +urlmap_file = "urlmap.js" diff --git a/doc/reference/clapper-gtk/images/clapper-logo.svg b/doc/reference/clapper-gtk/images/clapper-logo.svg new file mode 100644 index 00000000..2889584b --- /dev/null +++ b/doc/reference/clapper-gtk/images/clapper-logo.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/reference/clapper-gtk/meson.build b/doc/reference/clapper-gtk/meson.build new file mode 100644 index 00000000..24590ace --- /dev/null +++ b/doc/reference/clapper-gtk/meson.build @@ -0,0 +1,29 @@ +clappergtk_toml = configure_file( + input: 'clapper-gtk.toml.in', + output: 'clapper-gtk.toml', + configuration: doc_version_conf, + install: true, + install_dir: join_paths(datadir, 'doc', 'clapper-gtk'), +) + +custom_target('clapper-gtk-doc', + input: [ + clappergtk_toml, + clappergtk_gir[0], + ], + output: 'clapper-gtk', + command: [ + gi_docgen, + 'generate', + gi_docgen_common_args, + '--add-include-path=@0@'.format(join_paths(meson.project_build_root(), 'src', 'lib', 'clapper-gtk')), + '--config=@INPUT0@', + '--output-dir=@OUTPUT@', + '--content-dir=@0@'.format(meson.current_build_dir()), + '--content-dir=@0@'.format(meson.current_source_dir()), + '@INPUT1@', + ], + build_by_default: true, + install: true, + install_dir: join_paths(datadir, 'doc'), +) diff --git a/doc/reference/clapper-gtk/urlmap.js b/doc/reference/clapper-gtk/urlmap.js new file mode 100644 index 00000000..17b6f4e5 --- /dev/null +++ b/doc/reference/clapper-gtk/urlmap.js @@ -0,0 +1,8 @@ +baseURLs = [ + ['GLib', 'https://docs.gtk.org/glib/'], + ['GObject', 'https://docs.gtk.org/gobject/'], + ['Gio', 'https://docs.gtk.org/gio/'], + ['Gtk', 'https://docs.gtk.org/gtk4/'], + ['Gst', 'https://gstreamer.freedesktop.org/documentation/gstreamer/gi-index.html?'], + ['Clapper', 'https://rafostar.github.io/clapper/doc/clapper/'], +] diff --git a/doc/reference/meson.build b/doc/reference/meson.build index ccf2941c..065e3a48 100644 --- a/doc/reference/meson.build +++ b/doc/reference/meson.build @@ -13,3 +13,6 @@ endif if build_clapper subdir('clapper') endif +if build_clappergtk + subdir('clapper-gtk') +endif diff --git a/meson.build b/meson.build index e3b02f7c..4ed7103f 100644 --- a/meson.build +++ b/meson.build @@ -130,6 +130,7 @@ summary({ }, section: 'Directories') summary('clapper', build_clapper ? 'Yes' : 'No', section: 'Build') +summary('clapper-gtk', build_clappergtk ? 'Yes' : 'No', section: 'Build') summary('gst-plugin', build_gst_plugin ? 'Yes' : 'No', section: 'Build') summary('introspection', build_gir ? 'Yes' : 'No', section: 'Build') summary('vapi', build_vapi ? 'Yes' : 'No', section: 'Build') diff --git a/meson_options.txt b/meson_options.txt index 61d4e4d4..5cea2a0e 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -4,6 +4,11 @@ option('clapper', value: 'auto', description: 'Build Clapper library' ) +option('clapper-gtk', + type: 'feature', + value: 'auto', + description: 'Build Clapper GTK integration library' +) option('gst-plugin', type: 'feature', value: 'auto', diff --git a/src/lib/clapper-gtk/clapper-gtk-billboard.c b/src/lib/clapper-gtk/clapper-gtk-billboard.c new file mode 100644 index 00000000..cf139416 --- /dev/null +++ b/src/lib/clapper-gtk/clapper-gtk-billboard.c @@ -0,0 +1,464 @@ +/* Clapper GTK Integration Library + * Copyright (C) 2024 RafaÅ‚ DziÄ™giel + * + * 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. + */ + +/** + * ClapperGtkBillboard: + * + * A layer where various messages can be displayed. + * + * #ClapperGtkBillboard widget is meant to be overlaid on top of + * [class@ClapperGtk.Video] as a normal (non-fading) overlay. + * + * It is used to display various messages/announcements and later + * takes care of fading them on its own. + * + * If automatic volume/speed change notifications when their values do + * change are desired, functions for announcing them can be run in callbacks + * to corresponding property notify signals on the [class@Clapper.Player]. + */ + +#include "config.h" + +#include +#include + +#include "clapper-gtk-billboard.h" +#include "clapper-gtk-utils-private.h" + +#define PERCENTAGE_ROUND(a) (round ((gdouble) a / 0.01) * 0.01) +#define WORDS_PER_MSECOND 0.004 + +#define GST_CAT_DEFAULT clapper_gtk_billboard_debug +GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); + +struct _ClapperGtkBillboard +{ + ClapperGtkContainer parent; + + GtkWidget *side_revealer; + GtkWidget *progress_revealer; + GtkWidget *progress_box; + GtkWidget *top_progress; + GtkWidget *bottom_progress; + GtkWidget *progress_image; + GtkWidget *progress_label; + + GtkWidget *message_revealer; + GtkWidget *message_image; + GtkWidget *message_label; + + gboolean mute; + + gboolean has_pinned; + + guint side_timeout; + guint message_timeout; + + ClapperPlayer *player; +}; + +#define parent_class clapper_gtk_billboard_parent_class +G_DEFINE_TYPE (ClapperGtkBillboard, clapper_gtk_billboard, CLAPPER_GTK_TYPE_CONTAINER) + +/* We calculate estimated read time. This allows + * translated text to be displayed as long as + * necessary without app developer caring. */ +static guint +_estimate_read_time (const gchar *text) +{ + guint i, n_words = 1; + guint read_time; + + for (i = 0; text[i] != '\0'; ++i) { + if (text[i] == ' ' || text[i] == '\n') + n_words++; + } + + read_time = MAX (1500, (n_words / WORDS_PER_MSECOND) + 500); + GST_DEBUG ("Estimated message read time: %u", read_time); + + return read_time; +} + +static void +_unreveal_side_delay_cb (ClapperGtkBillboard *self) +{ + GST_LOG_OBJECT (self, "Unreveal side handler reached"); + self->side_timeout = 0; + + gtk_revealer_set_reveal_child (GTK_REVEALER (self->side_revealer), FALSE); +} + +static void +_unreveal_message_delay_cb (ClapperGtkBillboard *self) +{ + GST_LOG_OBJECT (self, "Unreveal message handler reached"); + self->message_timeout = 0; + + gtk_revealer_set_reveal_child (GTK_REVEALER (self->message_revealer), FALSE); +} + +static void +_reset_fade_side_timeout (ClapperGtkBillboard *self) +{ + GST_TRACE_OBJECT (self, "Fade side timeout reset"); + + g_clear_handle_id (&self->side_timeout, g_source_remove); + self->side_timeout = g_timeout_add_once (1500, + (GSourceOnceFunc) _unreveal_side_delay_cb, self); +} + +static void +_reset_fade_message_timeout (ClapperGtkBillboard *self) +{ + const gchar *text = gtk_label_get_text (GTK_LABEL (self->message_label)); + + GST_TRACE_OBJECT (self, "Fade side timeout reset"); + + g_clear_handle_id (&self->message_timeout, g_source_remove); + self->message_timeout = g_timeout_add_once ( + _estimate_read_time (text), + (GSourceOnceFunc) _unreveal_message_delay_cb, self); +} + +static void +adapt_cb (ClapperGtkContainer *container, gboolean adapt, + ClapperGtkBillboard *self) +{ + GST_DEBUG_OBJECT (self, "Adapted: %s", (adapt) ? "yes" : "no"); + + gtk_revealer_set_reveal_child (GTK_REVEALER (self->progress_revealer), !adapt); +} + +static void +revealer_revealed_cb (GtkRevealer *revealer, + GParamSpec *pspec G_GNUC_UNUSED, ClapperGtkBillboard *self) +{ + if (!gtk_revealer_get_child_revealed (revealer)) { + GtkWidget *other_revealer = (GTK_WIDGET (revealer) == self->side_revealer) + ? self->message_revealer + : self->side_revealer; + + gtk_widget_set_visible (GTK_WIDGET (revealer), FALSE); + + /* We only hide here when nothing is posted on the board, + * visiblity is set to TRUE when post is made */ + if (!gtk_revealer_get_child_revealed (GTK_REVEALER (other_revealer))) + gtk_widget_set_visible (GTK_WIDGET (self), FALSE); + } else { + if ((GTK_WIDGET (revealer) == self->side_revealer)) + _reset_fade_side_timeout (self); + else if (!self->has_pinned) + _reset_fade_message_timeout (self); + } +} + +static void +reveal_side (ClapperGtkBillboard *self) +{ + g_clear_handle_id (&self->side_timeout, g_source_remove); + + gtk_widget_set_visible (GTK_WIDGET (self), TRUE); + gtk_widget_set_visible (self->side_revealer, TRUE); + gtk_revealer_set_reveal_child (GTK_REVEALER (self->side_revealer), TRUE); + + if (gtk_revealer_get_child_revealed (GTK_REVEALER (self->side_revealer))) + _reset_fade_side_timeout (self); +} + +static void +_post_message_internal (ClapperGtkBillboard *self, + const gchar *icon_name, const gchar *message, gboolean pin) +{ + if (self->has_pinned) + return; + + self->has_pinned = pin; + + gtk_image_set_from_icon_name (GTK_IMAGE (self->message_image), icon_name); + gtk_label_set_label (GTK_LABEL (self->message_label), message); + + g_clear_handle_id (&self->message_timeout, g_source_remove); + + gtk_widget_set_visible (GTK_WIDGET (self), TRUE); + gtk_widget_set_visible (self->message_revealer, TRUE); + gtk_revealer_set_reveal_child (GTK_REVEALER (self->message_revealer), TRUE); + + if (!self->has_pinned + && gtk_revealer_get_child_revealed (GTK_REVEALER (self->message_revealer))) + _reset_fade_message_timeout (self); +} + +static void +_player_mute_changed_cb (ClapperPlayer *player, + GParamSpec *pspec G_GNUC_UNUSED, ClapperGtkBillboard *self) +{ + self->mute = clapper_player_get_mute (player); + + clapper_gtk_billboard_announce_volume (self); +} + +/** + * clapper_gtk_billboard_new: + * + * Creates a new #ClapperGtkBillboard instance. + * + * Returns: a new billboard #GtkWidget. + */ +GtkWidget * +clapper_gtk_billboard_new (void) +{ + return g_object_new (CLAPPER_GTK_TYPE_BILLBOARD, NULL); +} + +/** + * clapper_gtk_billboard_post_message: + * @billboard: a #ClapperGtkBillboard + * @icon_name: an icon name + * @message: a message text + * + * Posts a temporary message on the @billboard. + * + * Duration how long a message will stay is automatically + * calculated based on amount of text. + */ +void +clapper_gtk_billboard_post_message (ClapperGtkBillboard *self, + const gchar *icon_name, const gchar *message) +{ + _post_message_internal (self, icon_name, message, FALSE); +} + +/** + * clapper_gtk_billboard_pin_message: + * @billboard: a #ClapperGtkBillboard + * @icon_name: an icon name + * @message: a message text + * + * Pins a permanent message on the @billboard. + * + * The message will stay on the @billboard until a + * [method@ClapperGtk.Billboard.unpin_pinned_message] is called. + */ +void +clapper_gtk_billboard_pin_message (ClapperGtkBillboard *self, + const gchar *icon_name, const gchar *message) +{ + _post_message_internal (self, icon_name, message, TRUE); +} + +/** + * clapper_gtk_billboard_unpin_pinned_message: + * @billboard: a #ClapperGtkBillboard + * + * Unpins previously pinned message on the @billboard. + * + * If no message was pinned this function will do nothing, + * so it is safe to call when unsure. + */ +void +clapper_gtk_billboard_unpin_pinned_message (ClapperGtkBillboard *self) +{ + if (!self->has_pinned) + return; + + _unreveal_message_delay_cb (self); + self->has_pinned = FALSE; +} + +/** + * clapper_gtk_billboard_announce_volume: + * @billboard: a #ClapperGtkBillboard + * + * Temporarily displays current volume level on the + * side of @billboard. + * + * Use this if you want to present current volume level to the user. + * Note that @billboard also automatically announces volume changes. + */ +void +clapper_gtk_billboard_announce_volume (ClapperGtkBillboard *self) +{ + gdouble volume = PERCENTAGE_ROUND (clapper_player_get_volume (self->player)); + gchar *percent_str; + gboolean has_overamp; + + /* Revert popup_speed changes */ + gtk_progress_bar_set_inverted (GTK_PROGRESS_BAR (self->bottom_progress), TRUE); + + has_overamp = gtk_widget_has_css_class (self->progress_box, "overamp"); + percent_str = g_strdup_printf ("%.0lf%%", volume * 100); + + if (volume <= 1.0) { + gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (self->top_progress), 0.0); + gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (self->bottom_progress), volume); + + if (has_overamp) + gtk_widget_remove_css_class (self->progress_box, "overamp"); + } else { + gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (self->top_progress), volume - 1.0); + gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (self->bottom_progress), 1.0); + + if (!has_overamp) + gtk_widget_add_css_class (self->progress_box, "overamp"); + } + + gtk_image_set_from_icon_name (GTK_IMAGE (self->progress_image), + clapper_gtk_get_icon_name_for_volume ((!self->mute) ? volume : 0)); + gtk_label_set_label (GTK_LABEL (self->progress_label), percent_str); + + g_free (percent_str); + + reveal_side (self); +} + +/** + * clapper_gtk_billboard_announce_speed: + * @billboard: a #ClapperGtkBillboard + * + * Temporarily displays current speed value on the + * side of @billboard. + * + * Use this if you want to present current speed value to the user. + * Note that @billboard also automatically announces speed changes. + */ +void +clapper_gtk_billboard_announce_speed (ClapperGtkBillboard *self) +{ + gdouble speed = PERCENTAGE_ROUND (clapper_player_get_speed (self->player)); + gchar *speed_str; + + /* Revert popup_volume changes */ + if (gtk_widget_has_css_class (self->progress_box, "overamp")) + gtk_widget_remove_css_class (self->progress_box, "overamp"); + + gtk_progress_bar_set_inverted (GTK_PROGRESS_BAR (self->bottom_progress), FALSE); + + speed_str = g_strdup_printf ("%.2lfx", speed); + + if (speed <= 1.0) { + gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (self->top_progress), 0.0); + gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (self->bottom_progress), 1.0 - speed); + } else { + gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (self->top_progress), speed - 1.0); + gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (self->bottom_progress), 0.0); + } + + gtk_image_set_from_icon_name (GTK_IMAGE (self->progress_image), + clapper_gtk_get_icon_name_for_speed (speed)); + gtk_label_set_label (GTK_LABEL (self->progress_label), speed_str); + + g_free (speed_str); + + reveal_side (self); +} + +static void +clapper_gtk_billboard_root (GtkWidget *widget) +{ + ClapperGtkBillboard *self = CLAPPER_GTK_BILLBOARD_CAST (widget); + + GTK_WIDGET_CLASS (parent_class)->root (widget); + + if ((self->player = clapper_gtk_get_player_from_ancestor (widget))) { + g_signal_connect (self->player, "notify::mute", + G_CALLBACK (_player_mute_changed_cb), self); + self->mute = clapper_player_get_mute (self->player); + } +} + +static void +clapper_gtk_billboard_unroot (GtkWidget *widget) +{ + ClapperGtkBillboard *self = CLAPPER_GTK_BILLBOARD_CAST (widget); + + if (self->player) { + g_signal_handlers_disconnect_by_func (self->player, _player_mute_changed_cb, self); + + self->player = NULL; + } + + /* Reset in case of rooted again not within video widget */ + self->mute = FALSE; + + GTK_WIDGET_CLASS (parent_class)->unroot (widget); +} + +static void +clapper_gtk_billboard_init (ClapperGtkBillboard *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); +} + +static void +clapper_gtk_billboard_dispose (GObject *object) +{ + gtk_widget_dispose_template (GTK_WIDGET (object), CLAPPER_GTK_TYPE_BILLBOARD); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +clapper_gtk_billboard_finalize (GObject *object) +{ + ClapperGtkBillboard *self = CLAPPER_GTK_BILLBOARD_CAST (object); + + g_clear_handle_id (&self->side_timeout, g_source_remove); + g_clear_handle_id (&self->message_timeout, g_source_remove); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +clapper_gtk_billboard_class_init (ClapperGtkBillboardClass *klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + GtkWidgetClass *widget_class = (GtkWidgetClass *) klass; + + GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clappergtkbillboard", 0, + "Clapper GTK Billboard"); + + gobject_class->dispose = clapper_gtk_billboard_dispose; + gobject_class->finalize = clapper_gtk_billboard_finalize; + + /* Using root/unroot since initially invisible (unrealized) + * and we want for signals to stay connected as long as parented */ + widget_class->root = clapper_gtk_billboard_root; + widget_class->unroot = clapper_gtk_billboard_unroot; + + gtk_widget_class_set_template_from_resource (widget_class, + CLAPPER_GTK_RESOURCE_PREFIX "/ui/clapper-gtk-billboard.ui"); + + gtk_widget_class_bind_template_child (widget_class, ClapperGtkBillboard, side_revealer); + gtk_widget_class_bind_template_child (widget_class, ClapperGtkBillboard, progress_box); + gtk_widget_class_bind_template_child (widget_class, ClapperGtkBillboard, progress_revealer); + gtk_widget_class_bind_template_child (widget_class, ClapperGtkBillboard, top_progress); + gtk_widget_class_bind_template_child (widget_class, ClapperGtkBillboard, bottom_progress); + gtk_widget_class_bind_template_child (widget_class, ClapperGtkBillboard, progress_image); + gtk_widget_class_bind_template_child (widget_class, ClapperGtkBillboard, progress_label); + + gtk_widget_class_bind_template_child (widget_class, ClapperGtkBillboard, message_revealer); + gtk_widget_class_bind_template_child (widget_class, ClapperGtkBillboard, message_image); + gtk_widget_class_bind_template_child (widget_class, ClapperGtkBillboard, message_label); + + gtk_widget_class_bind_template_callback (widget_class, adapt_cb); + gtk_widget_class_bind_template_callback (widget_class, revealer_revealed_cb); + + gtk_widget_class_set_css_name (widget_class, "clapper-gtk-billboard"); +} diff --git a/src/lib/clapper-gtk/clapper-gtk-billboard.h b/src/lib/clapper-gtk/clapper-gtk-billboard.h new file mode 100644 index 00000000..b9f6f067 --- /dev/null +++ b/src/lib/clapper-gtk/clapper-gtk-billboard.h @@ -0,0 +1,51 @@ +/* Clapper GTK Integration Library + * Copyright (C) 2024 RafaÅ‚ DziÄ™giel + * + * 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. + */ + +#pragma once + +#if !defined(__CLAPPER_GTK_INSIDE__) && !defined(CLAPPER_GTK_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#include +#include + +#include + +G_BEGIN_DECLS + +#define CLAPPER_GTK_TYPE_BILLBOARD (clapper_gtk_billboard_get_type()) +#define CLAPPER_GTK_BILLBOARD_CAST(obj) ((ClapperGtkBillboard *)(obj)) + +G_DECLARE_FINAL_TYPE (ClapperGtkBillboard, clapper_gtk_billboard, CLAPPER_GTK, BILLBOARD, ClapperGtkContainer) + +GtkWidget * clapper_gtk_billboard_new (void); + +void clapper_gtk_billboard_post_message (ClapperGtkBillboard *billboard, const gchar *icon_name, const gchar *message); + +void clapper_gtk_billboard_pin_message (ClapperGtkBillboard *billboard, const gchar *icon_name, const gchar *message); + +void clapper_gtk_billboard_unpin_pinned_message (ClapperGtkBillboard *billboard); + +void clapper_gtk_billboard_announce_volume (ClapperGtkBillboard *billboard); + +void clapper_gtk_billboard_announce_speed (ClapperGtkBillboard *billboard); + +G_END_DECLS diff --git a/src/lib/clapper-gtk/clapper-gtk-buffering-animation-private.h b/src/lib/clapper-gtk/clapper-gtk-buffering-animation-private.h new file mode 100644 index 00000000..6946470a --- /dev/null +++ b/src/lib/clapper-gtk/clapper-gtk-buffering-animation-private.h @@ -0,0 +1,41 @@ +/* Clapper GTK Integration Library + * Copyright (C) 2024 RafaÅ‚ DziÄ™giel + * + * 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. + */ + +#pragma once + +#include +#include +#include + +#include "clapper-gtk-container.h" + +G_BEGIN_DECLS + +#define CLAPPER_GTK_TYPE_BUFFERING_ANIMATION (clapper_gtk_buffering_animation_get_type()) +#define CLAPPER_GTK_BUFFERING_ANIMATION_CAST(obj) ((ClapperGtkBufferingAnimation *)(obj)) + +G_DECLARE_FINAL_TYPE (ClapperGtkBufferingAnimation, clapper_gtk_buffering_animation, CLAPPER_GTK, BUFFERING_ANIMATION, ClapperGtkContainer) + +G_GNUC_INTERNAL +void clapper_gtk_buffering_animation_start (ClapperGtkBufferingAnimation *animation); + +G_GNUC_INTERNAL +void clapper_gtk_buffering_animation_stop (ClapperGtkBufferingAnimation *animation); + +G_END_DECLS diff --git a/src/lib/clapper-gtk/clapper-gtk-buffering-animation.c b/src/lib/clapper-gtk/clapper-gtk-buffering-animation.c new file mode 100644 index 00000000..f2d501ff --- /dev/null +++ b/src/lib/clapper-gtk/clapper-gtk-buffering-animation.c @@ -0,0 +1,140 @@ +/* Clapper GTK Integration Library + * Copyright (C) 2024 RafaÅ‚ DziÄ™giel + * + * 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. + */ + +#include + +#include "clapper-gtk-buffering-animation-private.h" +#include "clapper-gtk-buffering-paintable-private.h" + +#define MIN_STEP_DELAY 30000 + +#define GST_CAT_DEFAULT clapper_gtk_buffering_animation_debug +GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); + +struct _ClapperGtkBufferingAnimation +{ + ClapperGtkContainer parent; + + ClapperGtkBufferingPaintable *buffering_paintable; + + guint tick_id; + gint64 last_tick; +}; + +#define parent_class clapper_gtk_buffering_animation_parent_class +G_DEFINE_TYPE (ClapperGtkBufferingAnimation, clapper_gtk_buffering_animation, CLAPPER_GTK_TYPE_CONTAINER) + +static gboolean +_animation_tick (GtkWidget *picture, GdkFrameClock *frame_clock, ClapperGtkBufferingAnimation *self) +{ + gint64 now = gdk_frame_clock_get_frame_time (frame_clock); + + /* We do not want for this animation to move too fast */ + if (now - self->last_tick >= MIN_STEP_DELAY) { + GST_LOG_OBJECT (self, "Animation step, last: %" G_GINT64_FORMAT + ", now: %" G_GINT64_FORMAT, self->last_tick, now); + clapper_gtk_buffering_paintable_step (self->buffering_paintable); + self->last_tick = now; + } + + return G_SOURCE_CONTINUE; +} + +void +clapper_gtk_buffering_animation_start (ClapperGtkBufferingAnimation *self) +{ + GtkWidget *picture; + + if (self->tick_id != 0) + return; + + GST_DEBUG_OBJECT (self, "Animation start"); + + picture = clapper_gtk_container_get_child (CLAPPER_GTK_CONTAINER (self)); + self->tick_id = gtk_widget_add_tick_callback (picture, + (GtkTickCallback) _animation_tick, self, NULL); +} + +void +clapper_gtk_buffering_animation_stop (ClapperGtkBufferingAnimation *self) +{ + GtkWidget *picture; + + if (self->tick_id == 0) + return; + + GST_DEBUG_OBJECT (self, "Animation stop"); + + picture = clapper_gtk_container_get_child (CLAPPER_GTK_CONTAINER (self)); + gtk_widget_remove_tick_callback (picture, self->tick_id); + + self->tick_id = 0; + self->last_tick = 0; + clapper_gtk_buffering_paintable_reset (self->buffering_paintable); +} + +static void +clapper_gtk_buffering_animation_init (ClapperGtkBufferingAnimation *self) +{ + GtkWidget *picture = gtk_picture_new (); + self->buffering_paintable = clapper_gtk_buffering_paintable_new (); + + gtk_picture_set_paintable (GTK_PICTURE (picture), + GDK_PAINTABLE (self->buffering_paintable)); + + clapper_gtk_container_set_child (CLAPPER_GTK_CONTAINER (self), picture); +} + +static void +clapper_gtk_buffering_animation_unmap (GtkWidget *widget) +{ + ClapperGtkBufferingAnimation *self = CLAPPER_GTK_BUFFERING_ANIMATION_CAST (widget); + + clapper_gtk_buffering_animation_stop (self); + + GTK_WIDGET_CLASS (parent_class)->unmap (widget); +} + +static void +clapper_gtk_buffering_animation_finalize (GObject *object) +{ + ClapperGtkBufferingAnimation *self = CLAPPER_GTK_BUFFERING_ANIMATION_CAST (object); + + GST_TRACE_OBJECT (self, "Finalize"); + + g_object_unref (self->buffering_paintable); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +clapper_gtk_buffering_animation_class_init (ClapperGtkBufferingAnimationClass *klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + GtkWidgetClass *widget_class = (GtkWidgetClass *) klass; + + GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clappergtkbufferinganimation", 0, + "Clapper GTK Buffering Animation"); + + gobject_class->finalize = clapper_gtk_buffering_animation_finalize; + + widget_class->unmap = clapper_gtk_buffering_animation_unmap; + + gtk_widget_class_set_css_name (widget_class, "clapper-gtk-buffering-animation"); +} diff --git a/src/lib/clapper-gtk/clapper-gtk-buffering-paintable-private.h b/src/lib/clapper-gtk/clapper-gtk-buffering-paintable-private.h new file mode 100644 index 00000000..ac7d2067 --- /dev/null +++ b/src/lib/clapper-gtk/clapper-gtk-buffering-paintable-private.h @@ -0,0 +1,42 @@ +/* Clapper GTK Integration Library + * Copyright (C) 2024 RafaÅ‚ DziÄ™giel + * + * 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. + */ + +#pragma once + +#include +#include +#include + +G_BEGIN_DECLS + +#define CLAPPER_GTK_TYPE_BUFFERING_PAINTABLE (clapper_gtk_buffering_paintable_get_type()) +#define CLAPPER_GTK_BUFFERING_PAINTABLE_CAST(obj) ((ClapperGtkBufferingPaintable *)(obj)) + +G_DECLARE_FINAL_TYPE (ClapperGtkBufferingPaintable, clapper_gtk_buffering_paintable, CLAPPER_GTK, BUFFERING_PAINTABLE, GObject) + +G_GNUC_INTERNAL +ClapperGtkBufferingPaintable * clapper_gtk_buffering_paintable_new (void); + +G_GNUC_INTERNAL +void clapper_gtk_buffering_paintable_step (ClapperGtkBufferingPaintable *buffering_paintable); + +G_GNUC_INTERNAL +void clapper_gtk_buffering_paintable_reset (ClapperGtkBufferingPaintable *buffering_paintable); + +G_END_DECLS diff --git a/src/lib/clapper-gtk/clapper-gtk-buffering-paintable.c b/src/lib/clapper-gtk/clapper-gtk-buffering-paintable.c new file mode 100644 index 00000000..0a8f4f54 --- /dev/null +++ b/src/lib/clapper-gtk/clapper-gtk-buffering-paintable.c @@ -0,0 +1,177 @@ +/* Clapper GTK Integration Library + * Copyright (C) 2024 RafaÅ‚ DziÄ™giel + * + * 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. + */ + +#include "clapper-gtk-buffering-paintable-private.h" + +#define CIRCLE_MAX_SIZE 48 +#define CIRCLE_SPACING 10 +#define CIRCLE_OUTLINE 2 +#define INTRINSIC_SIZE 184 // 3 * CIRCLE_MAX_SIZE + 4 * CIRCLE_SPACING + +#define BLACK ((GdkRGBA) { 0, 0, 0, 1 }) +#define WHITE ((GdkRGBA) { 1, 1, 1, 1 }) + +struct _ClapperGtkBufferingPaintable +{ + GObject parent; + + gfloat sizes[3]; // current size of each circle + gboolean reverses[3]; // grow/shrink direction + gboolean initialized[3]; // big enough to start growing the next one +}; + +static GdkPaintableFlags +clapper_gtk_buffering_paintable_get_flags (GdkPaintable *paintable) +{ + return GDK_PAINTABLE_STATIC_SIZE; +} + +static gint +clapper_gtk_buffering_paintable_get_intrinsic_size (GdkPaintable *paintable) +{ + return INTRINSIC_SIZE; +} + +static void +_draw_scaled_circle (GdkSnapshot *snapshot, gfloat scale) +{ + GskRoundedRect outline; + gfloat half_size = ((gfloat) CIRCLE_MAX_SIZE / 2) * scale; + gfloat inside_size = ((gfloat) (CIRCLE_MAX_SIZE - 2 * CIRCLE_OUTLINE) / 2) * scale; + + /* Draw white inner circle */ + gsk_rounded_rect_init_from_rect (&outline, + &GRAPHENE_RECT_INIT (-inside_size, -inside_size, 2 * inside_size, 2 * inside_size), inside_size); + gtk_snapshot_append_border (snapshot, &outline, + (gfloat[4]) { inside_size, inside_size, inside_size, inside_size }, + (GdkRGBA[4]) { WHITE, WHITE, WHITE, WHITE }); + + /* Draw black circle border */ + gsk_rounded_rect_init_from_rect (&outline, + &GRAPHENE_RECT_INIT (-half_size, -half_size, 2 * half_size, 2 * half_size), half_size); + gtk_snapshot_append_border (snapshot, &outline, + (gfloat[4]) { CIRCLE_OUTLINE, CIRCLE_OUTLINE, CIRCLE_OUTLINE, CIRCLE_OUTLINE }, + (GdkRGBA[4]) { BLACK, BLACK, BLACK, BLACK }); +} + +static void +clapper_gtk_buffering_paintable_snapshot (GdkPaintable *paintable, + GdkSnapshot *snapshot, gdouble width, gdouble height) +{ + ClapperGtkBufferingPaintable *self = CLAPPER_GTK_BUFFERING_PAINTABLE_CAST (paintable); + guint i; + + gtk_snapshot_save (snapshot); + + gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (0, height / 2)); + gtk_snapshot_scale (snapshot, MIN (width, height) / INTRINSIC_SIZE, MIN (width, height) / INTRINSIC_SIZE); + + for (i = 0; i < 3; ++i) { + gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (CIRCLE_SPACING + CIRCLE_MAX_SIZE / 2, 0)); + _draw_scaled_circle (snapshot, self->sizes[i]); + gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (CIRCLE_MAX_SIZE / 2, 0)); + } + + gtk_snapshot_restore (snapshot); +} + +static GdkPaintable * +clapper_gtk_buffering_paintable_get_current_image (GdkPaintable *paintable) +{ + ClapperGtkBufferingPaintable *self = CLAPPER_GTK_BUFFERING_PAINTABLE_CAST (paintable); + ClapperGtkBufferingPaintable *copy = clapper_gtk_buffering_paintable_new (); + guint i; + + /* Only current sizes are needed for static image */ + for (i = 0; i < 3; ++i) + copy->sizes[i] = self->sizes[i]; + + return GDK_PAINTABLE (copy); +} + +static void +_paintable_iface_init (GdkPaintableInterface *iface) +{ + iface->get_flags = clapper_gtk_buffering_paintable_get_flags; + iface->get_intrinsic_width = clapper_gtk_buffering_paintable_get_intrinsic_size; + iface->get_intrinsic_height = clapper_gtk_buffering_paintable_get_intrinsic_size; + iface->snapshot = clapper_gtk_buffering_paintable_snapshot; + iface->get_current_image = clapper_gtk_buffering_paintable_get_current_image; +} + +#define parent_class clapper_gtk_buffering_paintable_parent_class +G_DEFINE_TYPE_WITH_CODE (ClapperGtkBufferingPaintable, clapper_gtk_buffering_paintable, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE, _paintable_iface_init)) + +ClapperGtkBufferingPaintable * +clapper_gtk_buffering_paintable_new (void) +{ + return g_object_new (CLAPPER_GTK_TYPE_BUFFERING_PAINTABLE, NULL); +} + +void +clapper_gtk_buffering_paintable_step (ClapperGtkBufferingPaintable *self) +{ + guint i; + + for (i = 0; i < 3; ++i) { + /* If previous circle is not big enough + * do not start animating the next one */ + if (i > 0 && !self->initialized[i - 1]) + break; + + if (!self->initialized[i] && self->sizes[i] >= 0.3) + self->initialized[i] = TRUE; + + self->sizes[i] += (self->reverses[i]) ? -0.04 : 0.04; + if (self->sizes[i] > 1.0) { + self->sizes[i] = 1.0; + self->reverses[i] = TRUE; + } else if (self->sizes[i] < 0.0) { + self->sizes[i] = 0.0; + self->reverses[i] = FALSE; + } + } + + gdk_paintable_invalidate_contents ((GdkPaintable *) self); +} + +void +clapper_gtk_buffering_paintable_reset (ClapperGtkBufferingPaintable *self) +{ + guint i; + + for (i = 0; i < 3; ++i) { + self->sizes[i] = 0; + self->reverses[i] = FALSE; + self->initialized[i] = FALSE; + } + + gdk_paintable_invalidate_contents ((GdkPaintable *) self); +} + +static void +clapper_gtk_buffering_paintable_init (ClapperGtkBufferingPaintable *self) +{ +} + +static void +clapper_gtk_buffering_paintable_class_init (ClapperGtkBufferingPaintableClass *klass) +{ +} diff --git a/src/lib/clapper-gtk/clapper-gtk-container-private.h b/src/lib/clapper-gtk/clapper-gtk-container-private.h new file mode 100644 index 00000000..7a6a1e17 --- /dev/null +++ b/src/lib/clapper-gtk/clapper-gtk-container-private.h @@ -0,0 +1,28 @@ +/* Clapper GTK Integration Library + * Copyright (C) 2024 RafaÅ‚ DziÄ™giel + * + * 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. + */ + +#pragma once + +#include "clapper-gtk-container.h" + +G_BEGIN_DECLS + +void clapper_gtk_container_emit_adapt (ClapperGtkContainer *container, gboolean adapt); + +G_END_DECLS diff --git a/src/lib/clapper-gtk/clapper-gtk-container.c b/src/lib/clapper-gtk/clapper-gtk-container.c new file mode 100644 index 00000000..3115f60e --- /dev/null +++ b/src/lib/clapper-gtk/clapper-gtk-container.c @@ -0,0 +1,413 @@ +/* Clapper GTK Integration Library + * Copyright (C) 2024 RafaÅ‚ DziÄ™giel + * + * 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. + */ + +/** + * ClapperGtkContainer: + * + * A simple container widget that holds just one child. + * + * It is designed to work well with OSD overlay, adding some useful functionalities + * to it, such as width and height that widget should target. This helps with + * implementing simple adaptive widgets by observing its own width and signalling + * when adaptive threshold is reached. + * + * You can use this when you need to create a widget that is adaptive or should have + * a limited maximal width/height. + * + * If you need to have more then single widget as child, place a widget that + * can hold multiple children such as [class@Gtk.Box] as a single conatiner child + * and then your widgets into that child. + */ + +#include "clapper-gtk-container.h" +#include "clapper-gtk-container-private.h" +#include "clapper-gtk-limited-layout-private.h" + +#define parent_class clapper_gtk_container_parent_class +G_DEFINE_TYPE (ClapperGtkContainer, clapper_gtk_container, GTK_TYPE_WIDGET) + +enum +{ + PROP_0, + PROP_WIDTH_TARGET, + PROP_HEIGHT_TARGET, + PROP_ADAPTIVE_WIDTH, + PROP_ADAPTIVE_HEIGHT, + PROP_LAST +}; + +enum +{ + SIGNAL_ADAPT, + SIGNAL_LAST +}; + +static GParamSpec *param_specs[PROP_LAST] = { NULL, }; +static guint signals[SIGNAL_LAST] = { 0, }; + +static inline void +_unparent_child (ClapperGtkContainer *self) +{ + GtkWidget *child; + + if ((child = gtk_widget_get_first_child (GTK_WIDGET (self)))) + gtk_widget_unparent (child); +} + +/** + * clapper_gtk_container_new: + * + * Creates a new #ClapperGtkContainer instance. + * + * Returns: a new container #GtkWidget. + */ +GtkWidget * +clapper_gtk_container_new (void) +{ + return g_object_new (CLAPPER_GTK_TYPE_CONTAINER, NULL); +} + +/** + * clapper_gtk_container_set_child: + * @container: a #ClapperGtkContainer + * + * Set a child #GtkWidget of @container. + */ +void +clapper_gtk_container_set_child (ClapperGtkContainer *self, GtkWidget *child) +{ + g_return_if_fail (CLAPPER_GTK_IS_CONTAINER (self)); + g_return_if_fail (GTK_IS_WIDGET (child)); + + _unparent_child (self); + gtk_widget_set_parent (child, GTK_WIDGET (self)); +} + +/** + * clapper_gtk_container_get_child: + * @container: a #ClapperGtkContainer + * + * Get a child #GtkWidget of @container. + * + * Returns: (transfer none) (nullable): #GtkWidget set as child. + */ +GtkWidget * +clapper_gtk_container_get_child (ClapperGtkContainer *self) +{ + g_return_val_if_fail (CLAPPER_GTK_IS_CONTAINER (self), NULL); + + return gtk_widget_get_first_child (GTK_WIDGET (self)); +} + +/** + * clapper_gtk_container_set_width_target: + * @container: a #ClapperGtkContainer + * @width: width to target -1 to restore default behavior + * + * Set a width that @container should target. When set container + * will not stretch beyond set @width while still expanding into + * possible boundaries trying to reach its target. + */ +void +clapper_gtk_container_set_width_target (ClapperGtkContainer *self, gint width) +{ + GtkLayoutManager *layout; + + g_return_if_fail (CLAPPER_GTK_IS_CONTAINER (self)); + + layout = gtk_widget_get_layout_manager (GTK_WIDGET (self)); + clapper_gtk_limited_layout_set_max_width (CLAPPER_GTK_LIMITED_LAYOUT_CAST (layout), width); +} + +/** + * clapper_gtk_container_get_width_target: + * @container: a #ClapperGtkContainer + * + * Get a @container width target. + * + * Returns: width target set by user or -1 when none. + */ +gint +clapper_gtk_container_get_width_target (ClapperGtkContainer *self) +{ + GtkLayoutManager *layout; + + g_return_val_if_fail (CLAPPER_GTK_IS_CONTAINER (self), -1); + + layout = gtk_widget_get_layout_manager (GTK_WIDGET (self)); + return clapper_gtk_limited_layout_get_max_width (CLAPPER_GTK_LIMITED_LAYOUT_CAST (layout)); +} + +/** + * clapper_gtk_container_set_height_target: + * @container: a #ClapperGtkContainer + * @height: height to target or -1 to restore default behavior + * + * Same as clapper_gtk_container_set_width_target() but for widget height. + */ +void +clapper_gtk_container_set_height_target (ClapperGtkContainer *self, gint height) +{ + GtkLayoutManager *layout; + + g_return_if_fail (CLAPPER_GTK_IS_CONTAINER (self)); + + layout = gtk_widget_get_layout_manager (GTK_WIDGET (self)); + clapper_gtk_limited_layout_set_max_height (CLAPPER_GTK_LIMITED_LAYOUT_CAST (layout), height); +} + +/** + * clapper_gtk_container_get_height_target: + * @container: a #ClapperGtkContainer + * + * Get a @container height target. + * + * Returns: height target set by user or -1 when none. + */ +gint +clapper_gtk_container_get_height_target (ClapperGtkContainer *self) +{ + GtkLayoutManager *layout; + + g_return_val_if_fail (CLAPPER_GTK_IS_CONTAINER (self), -1); + + layout = gtk_widget_get_layout_manager (GTK_WIDGET (self)); + return clapper_gtk_limited_layout_get_max_height (CLAPPER_GTK_LIMITED_LAYOUT_CAST (layout)); +} + +/** + * clapper_gtk_container_set_adaptive_width: + * @container: a #ClapperGtkContainer + * @width: a threshold on which adapt signal should be triggered or -1 to disable. + * + * Set an adaptive width threshold. When widget is resized to value or lower, + * an [signal@ClapperGtk.Container::adapt] signal will be emitted with %TRUE to + * notify implementation about mobile adaptation request, otherwise %FALSE when + * both threshold values are exceeded. + */ +void +clapper_gtk_container_set_adaptive_width (ClapperGtkContainer *self, gint width) +{ + GtkLayoutManager *layout; + + g_return_if_fail (CLAPPER_GTK_IS_CONTAINER (self)); + + layout = gtk_widget_get_layout_manager (GTK_WIDGET (self)); + clapper_gtk_limited_layout_set_adaptive_width ( + CLAPPER_GTK_LIMITED_LAYOUT_CAST (layout), width); +} + +/** + * clapper_gtk_container_get_adaptive_width: + * @container: a #ClapperGtkContainer + * + * Get a @container adaptive width threshold. + * + * Returns: adaptive width set by user or -1 when none. + */ +gint +clapper_gtk_container_get_adaptive_width (ClapperGtkContainer *self) +{ + GtkLayoutManager *layout; + + g_return_val_if_fail (CLAPPER_GTK_IS_CONTAINER (self), -1); + + layout = gtk_widget_get_layout_manager (GTK_WIDGET (self)); + return clapper_gtk_limited_layout_get_adaptive_width (CLAPPER_GTK_LIMITED_LAYOUT_CAST (layout)); +} + +/** + * clapper_gtk_container_set_adaptive_height: + * @container: a #ClapperGtkContainer + * @height: a threshold on which adapt signal should be triggered or -1 to disable. + * + * Set an adaptive height threshold. When widget is resized to value or lower, + * an [signal@ClapperGtk.Container::adapt] signal will be emitted with %TRUE to + * notify implementation about mobile adaptation request, otherwise %FALSE when + * both threshold values are exceeded. + */ +void +clapper_gtk_container_set_adaptive_height (ClapperGtkContainer *self, gint height) +{ + GtkLayoutManager *layout; + + g_return_if_fail (CLAPPER_GTK_IS_CONTAINER (self)); + + layout = gtk_widget_get_layout_manager (GTK_WIDGET (self)); + clapper_gtk_limited_layout_set_adaptive_height ( + CLAPPER_GTK_LIMITED_LAYOUT_CAST (layout), height); +} + +/** + * clapper_gtk_container_get_adaptive_height: + * @container: a #ClapperGtkContainer + * + * Get a @container adaptive height threshold. + * + * Returns: adaptive height set by user or -1 when none. + */ +gint +clapper_gtk_container_get_adaptive_height (ClapperGtkContainer *self) +{ + GtkLayoutManager *layout; + + g_return_val_if_fail (CLAPPER_GTK_IS_CONTAINER (self), -1); + + layout = gtk_widget_get_layout_manager (GTK_WIDGET (self)); + return clapper_gtk_limited_layout_get_adaptive_height (CLAPPER_GTK_LIMITED_LAYOUT_CAST (layout)); +} + +void +clapper_gtk_container_emit_adapt (ClapperGtkContainer *self, gboolean adapt) +{ + if (g_signal_handler_find (self, G_SIGNAL_MATCH_ID, + signals[SIGNAL_ADAPT], 0, NULL, NULL, NULL) != 0) { + g_signal_emit (self, signals[SIGNAL_ADAPT], 0, adapt); + } +} + +static void +clapper_gtk_container_init (ClapperGtkContainer *self) +{ +} + +static void +clapper_gtk_container_dispose (GObject *object) +{ + ClapperGtkContainer *self = CLAPPER_GTK_CONTAINER_CAST (object); + + _unparent_child (self); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +clapper_gtk_container_get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + ClapperGtkContainer *self = CLAPPER_GTK_CONTAINER_CAST (object); + + switch (prop_id) { + case PROP_WIDTH_TARGET: + g_value_set_int (value, clapper_gtk_container_get_width_target (self)); + break; + case PROP_HEIGHT_TARGET: + g_value_set_int (value, clapper_gtk_container_get_height_target (self)); + break; + case PROP_ADAPTIVE_WIDTH: + g_value_set_int (value, clapper_gtk_container_get_adaptive_width (self)); + break; + case PROP_ADAPTIVE_HEIGHT: + g_value_set_int (value, clapper_gtk_container_get_adaptive_height (self)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +clapper_gtk_container_set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + ClapperGtkContainer *self = CLAPPER_GTK_CONTAINER_CAST (object); + + switch (prop_id) { + case PROP_WIDTH_TARGET: + clapper_gtk_container_set_width_target (self, g_value_get_int (value)); + break; + case PROP_HEIGHT_TARGET: + clapper_gtk_container_set_height_target (self, g_value_get_int (value)); + break; + case PROP_ADAPTIVE_WIDTH: + clapper_gtk_container_set_adaptive_width (self, g_value_get_int (value)); + break; + case PROP_ADAPTIVE_HEIGHT: + clapper_gtk_container_set_adaptive_height (self, g_value_get_int (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +clapper_gtk_container_class_init (ClapperGtkContainerClass *klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + GtkWidgetClass *widget_class = (GtkWidgetClass *) klass; + + gobject_class->get_property = clapper_gtk_container_get_property; + gobject_class->set_property = clapper_gtk_container_set_property; + gobject_class->dispose = clapper_gtk_container_dispose; + + /** + * ClapperGtkContainer:width-target: + * + * Width that container should target. + */ + param_specs[PROP_WIDTH_TARGET] = g_param_spec_int ("width-target", + NULL, NULL, -1, G_MAXINT, -1, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + /** + * ClapperGtkContainer:height-target: + * + * Height that container should target. + */ + param_specs[PROP_HEIGHT_TARGET] = g_param_spec_int ("height-target", + NULL, NULL, -1, G_MAXINT, -1, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + /** + * ClapperGtkContainer:adaptive-width: + * + * Adaptive width threshold that triggers [signal@ClapperGtk.Container::adapt] signal. + */ + param_specs[PROP_ADAPTIVE_WIDTH] = g_param_spec_int ("adaptive-width", + NULL, NULL, -1, G_MAXINT, -1, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + /** + * ClapperGtkContainer:adaptive-height: + * + * Adaptive height threshold that triggers [signal@ClapperGtk.Container::adapt] signal. + */ + param_specs[PROP_ADAPTIVE_HEIGHT] = g_param_spec_int ("adaptive-height", + NULL, NULL, -1, G_MAXINT, -1, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + /** + * ClapperGtkContainer::adapt: + * @container: a #ClapperGtkContainer + * @adapt: %TRUE if narrowness reached adaptive threshold, %FALSE otherwise + * + * A helper signal for implementing mobile/narrow adaptive + * behavior on descendants. + */ + signals[SIGNAL_ADAPT] = g_signal_new ("adapt", + 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, G_TYPE_BOOLEAN); + + g_object_class_install_properties (gobject_class, PROP_LAST, param_specs); + + gtk_widget_class_set_layout_manager_type (widget_class, CLAPPER_GTK_TYPE_LIMITED_LAYOUT); + gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_GENERIC); + gtk_widget_class_set_css_name (widget_class, "clapper-gtk-container"); +} diff --git a/src/lib/clapper-gtk/clapper-gtk-container.h b/src/lib/clapper-gtk/clapper-gtk-container.h new file mode 100644 index 00000000..dbc54002 --- /dev/null +++ b/src/lib/clapper-gtk/clapper-gtk-container.h @@ -0,0 +1,67 @@ +/* Clapper GTK Integration Library + * Copyright (C) 2024 RafaÅ‚ DziÄ™giel + * + * 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. + */ + +#pragma once + +#if !defined(__CLAPPER_GTK_INSIDE__) && !defined(CLAPPER_GTK_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#include +#include + +G_BEGIN_DECLS + +#define CLAPPER_GTK_TYPE_CONTAINER (clapper_gtk_container_get_type()) +#define CLAPPER_GTK_CONTAINER_CAST(obj) ((ClapperGtkContainer *)(obj)) + +G_DECLARE_DERIVABLE_TYPE (ClapperGtkContainer, clapper_gtk_container, CLAPPER_GTK, CONTAINER, GtkWidget) + +struct _ClapperGtkContainerClass +{ + GtkWidgetClass parent_class; + + /*< private >*/ + gpointer padding[4]; +}; + +GtkWidget * clapper_gtk_container_new (void); + +void clapper_gtk_container_set_child (ClapperGtkContainer *container, GtkWidget *child); + +GtkWidget * clapper_gtk_container_get_child (ClapperGtkContainer *container); + +void clapper_gtk_container_set_width_target (ClapperGtkContainer *container, gint width); + +gint clapper_gtk_container_get_width_target (ClapperGtkContainer *container); + +void clapper_gtk_container_set_height_target (ClapperGtkContainer *container, gint height); + +gint clapper_gtk_container_get_height_target (ClapperGtkContainer *container); + +void clapper_gtk_container_set_adaptive_width (ClapperGtkContainer *container, gint width); + +gint clapper_gtk_container_get_adaptive_width (ClapperGtkContainer *container); + +void clapper_gtk_container_set_adaptive_height (ClapperGtkContainer *container, gint height); + +gint clapper_gtk_container_get_adaptive_height (ClapperGtkContainer *container); + +G_END_DECLS diff --git a/src/lib/clapper-gtk/clapper-gtk-enums.h b/src/lib/clapper-gtk/clapper-gtk-enums.h new file mode 100644 index 00000000..f761c363 --- /dev/null +++ b/src/lib/clapper-gtk/clapper-gtk-enums.h @@ -0,0 +1,50 @@ +/* Clapper GTK Integration Library + * Copyright (C) 2024 RafaÅ‚ DziÄ™giel + * + * 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. + */ + +#pragma once + +#if !defined(__CLAPPER_GTK_INSIDE__) && !defined(CLAPPER_GTK_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#include + +G_BEGIN_DECLS + +/** + * ClapperGtkVideoActionMask: + * @CLAPPER_GTK_VIDEO_ACTION_NONE: No action + * @CLAPPER_GTK_VIDEO_ACTION_REVEAL_OVERLAYS: Reveal fading overlays + * @CLAPPER_GTK_VIDEO_ACTION_TOGGLE_PLAY: Toggle playback (triggered by single click/tap) + * @CLAPPER_GTK_VIDEO_ACTION_TOGGLE_FULLSCREEN: Toggle fullscreen (triggered by double click/tap) + * @CLAPPER_GTK_VIDEO_ACTION_SEEK_REQUEST: Seek request (triggered by double tap on screen side) + * @CLAPPER_GTK_VIDEO_ACTION_ANY: All of the above + */ +typedef enum +{ + CLAPPER_GTK_VIDEO_ACTION_NONE = 0, + CLAPPER_GTK_VIDEO_ACTION_REVEAL_OVERLAYS = 1 << 0, + CLAPPER_GTK_VIDEO_ACTION_TOGGLE_PLAY = 1 << 1, + CLAPPER_GTK_VIDEO_ACTION_TOGGLE_FULLSCREEN = 1 << 2, + CLAPPER_GTK_VIDEO_ACTION_SEEK_REQUEST = 1 << 3, + CLAPPER_GTK_VIDEO_ACTION_ANY = 0x3FFFFFF +} ClapperGtkVideoActionMask; + +G_END_DECLS diff --git a/src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c b/src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c new file mode 100644 index 00000000..69723806 --- /dev/null +++ b/src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c @@ -0,0 +1,801 @@ +/* Clapper GTK Integration Library + * Copyright (C) 2024 RafaÅ‚ DziÄ™giel + * + * 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 +#include +#include + +#include "clapper-gtk-extra-menu-button.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)); + gchar *sign = NULL; + gdouble volume = g_ascii_strtod (text, &sign); + + if (volume < 0 || volume > 200 + || (sign && sign[0] != '\0' && sign[0] != '%')) + return GTK_INPUT_ERROR; + + 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)); + gchar *sign = NULL; + gdouble speed = g_ascii_strtod (text, &sign); + + if (speed < 0.05 || speed > 2.0 + || (sign && sign[0] != '\0' && sign[0] != 'x')) + return GTK_INPUT_ERROR; + + 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 (¤t_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 }, + }; + + 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"); +} diff --git a/src/lib/clapper-gtk/clapper-gtk-extra-menu-button.h b/src/lib/clapper-gtk/clapper-gtk-extra-menu-button.h new file mode 100644 index 00000000..19a5310a --- /dev/null +++ b/src/lib/clapper-gtk/clapper-gtk-extra-menu-button.h @@ -0,0 +1,51 @@ +/* Clapper GTK Integration Library + * Copyright (C) 2024 RafaÅ‚ DziÄ™giel + * + * 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. + */ + +#pragma once + +#if !defined(__CLAPPER_GTK_INSIDE__) && !defined(CLAPPER_GTK_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#include +#include + +G_BEGIN_DECLS + +#define CLAPPER_GTK_TYPE_EXTRA_MENU_BUTTON (clapper_gtk_extra_menu_button_get_type()) +#define CLAPPER_GTK_EXTRA_MENU_BUTTON_CAST(obj) ((ClapperGtkExtraMenuButton *)(obj)) + +G_DECLARE_FINAL_TYPE (ClapperGtkExtraMenuButton, clapper_gtk_extra_menu_button, CLAPPER_GTK, EXTRA_MENU_BUTTON, GtkWidget) + +GtkWidget * clapper_gtk_extra_menu_button_new (void); + +void clapper_gtk_extra_menu_button_set_volume_visible (ClapperGtkExtraMenuButton *button, gboolean visible); + +gboolean clapper_gtk_extra_menu_button_get_volume_visible (ClapperGtkExtraMenuButton *button); + +void clapper_gtk_extra_menu_button_set_speed_visible (ClapperGtkExtraMenuButton *button, gboolean visible); + +gboolean clapper_gtk_extra_menu_button_get_speed_visible (ClapperGtkExtraMenuButton *button); + +void clapper_gtk_extra_menu_button_set_can_open_subtitles (ClapperGtkExtraMenuButton *button, gboolean allowed); + +gboolean clapper_gtk_extra_menu_button_get_can_open_subtitles (ClapperGtkExtraMenuButton *button); + +G_END_DECLS diff --git a/src/lib/clapper-gtk/clapper-gtk-lead-container.c b/src/lib/clapper-gtk/clapper-gtk-lead-container.c new file mode 100644 index 00000000..50d8e07e --- /dev/null +++ b/src/lib/clapper-gtk/clapper-gtk-lead-container.c @@ -0,0 +1,235 @@ +/* Clapper GTK Integration Library + * Copyright (C) 2024 RafaÅ‚ DziÄ™giel + * + * 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. + */ + +/** + * ClapperGtkLeadContainer: + * + * A #ClapperGtkContainer that can take priority in user interactions with the #ClapperGtkVideo. + * + * #ClapperGtkLeadContainer is a special type of [class@ClapperGtk.Container] that can + * lead in interaction events. When "leading", it is assumed that user interactions + * over it which would normally trigger actions can be blocked/ignored when set in mask + * of actions that this widget should block. + * + * This kind of container is useful when creating some statically visible overlays + * covering top of [class@ClapperGtk.Video] that you want to take priority instead of + * triggering default actions such as toggle play on click or revealing fading overlays. + * + * For more info how container widget works see [class@ClapperGtk.Container] documentation. + */ + +#include "clapper-gtk-lead-container.h" + +#define DEFAULT_LEADING TRUE +#define DEFAULT_BLOCKED_ACTIONS CLAPPER_GTK_VIDEO_ACTION_NONE + +typedef struct _ClapperGtkLeadContainerPrivate ClapperGtkLeadContainerPrivate; + +struct _ClapperGtkLeadContainerPrivate +{ + gboolean leading; + ClapperGtkVideoActionMask blocked_actions; +}; + +#define parent_class clapper_gtk_lead_container_parent_class +G_DEFINE_TYPE_WITH_PRIVATE (ClapperGtkLeadContainer, clapper_gtk_lead_container, CLAPPER_GTK_TYPE_CONTAINER) + +enum +{ + PROP_0, + PROP_LEADING, + PROP_BLOCKED_ACTIONS, + PROP_LAST +}; + +static GParamSpec *param_specs[PROP_LAST] = { NULL, }; + +/** + * clapper_gtk_lead_container_new: + * + * Creates a new #ClapperGtkLeadContainer instance. + * + * Returns: a new lead container #GtkWidget. + */ +GtkWidget * +clapper_gtk_lead_container_new (void) +{ + return g_object_new (CLAPPER_GTK_TYPE_LEAD_CONTAINER, NULL); +} + +/** + * clapper_gtk_lead_container_set_leading: + * @lead_container: a #ClapperGtkLeadContainer + * @leading: enable leadership + * + * Set if @lead_container leadership should be enabled. + * + * When enabled, interactions with @lead_container will not trigger + * their default behavior, instead container and its contents will take priority. + */ +void +clapper_gtk_lead_container_set_leading (ClapperGtkLeadContainer *self, gboolean leading) +{ + ClapperGtkLeadContainerPrivate *priv; + + g_return_if_fail (CLAPPER_GTK_IS_LEAD_CONTAINER (self)); + + priv = clapper_gtk_lead_container_get_instance_private (self); + + priv->leading = leading; +} + +/** + * clapper_gtk_lead_container_get_leading: + * @lead_container: a #ClapperGtkLeadContainer + * + * Get a whenever @lead_container has leadership set. + * + * Returns: %TRUE if container is leading, %FALSE otherwise. + */ +gboolean +clapper_gtk_lead_container_get_leading (ClapperGtkLeadContainer *self) +{ + ClapperGtkLeadContainerPrivate *priv; + + g_return_val_if_fail (CLAPPER_GTK_IS_LEAD_CONTAINER (self), FALSE); + + priv = clapper_gtk_lead_container_get_instance_private (self); + + return priv->leading; +} + +/** + * clapper_gtk_lead_container_set_blocked_actions: + * @lead_container: a #ClapperGtkLeadContainer + * @actions: a #ClapperGtkVideoActionMask of actions to block + * + * Set @actions that #ClapperGtkVideo should skip when #GdkEvent which + * would normally trigger them happens inside @lead_container. + */ +void +clapper_gtk_lead_container_set_blocked_actions (ClapperGtkLeadContainer *self, ClapperGtkVideoActionMask actions) +{ + ClapperGtkLeadContainerPrivate *priv; + + g_return_if_fail (CLAPPER_GTK_IS_LEAD_CONTAINER (self)); + + priv = clapper_gtk_lead_container_get_instance_private (self); + + priv->blocked_actions = actions; +} + +/** + * clapper_gtk_lead_container_get_blocked_actions: + * @lead_container: a #ClapperGtkLeadContainer + * + * Get @actions that were set for this @lead_container to block. + * + * Returns: a mask of actions that container blocks from being triggered on video. + */ +ClapperGtkVideoActionMask +clapper_gtk_lead_container_get_blocked_actions (ClapperGtkLeadContainer *self) +{ + ClapperGtkLeadContainerPrivate *priv; + + g_return_val_if_fail (CLAPPER_GTK_IS_LEAD_CONTAINER (self), 0); + + priv = clapper_gtk_lead_container_get_instance_private (self); + + return priv->blocked_actions; +} + +static void +clapper_gtk_lead_container_init (ClapperGtkLeadContainer *self) +{ + ClapperGtkLeadContainerPrivate *priv = clapper_gtk_lead_container_get_instance_private (self); + + priv->leading = DEFAULT_LEADING; + priv->blocked_actions = DEFAULT_BLOCKED_ACTIONS; +} + +static void +clapper_gtk_lead_container_get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + ClapperGtkLeadContainer *self = CLAPPER_GTK_LEAD_CONTAINER_CAST (object); + + switch (prop_id) { + case PROP_LEADING: + g_value_set_boolean (value, clapper_gtk_lead_container_get_leading (self)); + break; + case PROP_BLOCKED_ACTIONS: + g_value_set_flags (value, clapper_gtk_lead_container_get_blocked_actions (self)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +clapper_gtk_lead_container_set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + ClapperGtkLeadContainer *self = CLAPPER_GTK_LEAD_CONTAINER_CAST (object); + + switch (prop_id) { + case PROP_LEADING: + clapper_gtk_lead_container_set_leading (self, g_value_get_boolean (value)); + break; + case PROP_BLOCKED_ACTIONS: + clapper_gtk_lead_container_set_blocked_actions (self, g_value_get_flags (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +clapper_gtk_lead_container_class_init (ClapperGtkLeadContainerClass *klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + GtkWidgetClass *widget_class = (GtkWidgetClass *) klass; + + gobject_class->get_property = clapper_gtk_lead_container_get_property; + gobject_class->set_property = clapper_gtk_lead_container_set_property; + + /** + * ClapperGtkLeadContainer:leading: + * + * Width that container should target. + */ + param_specs[PROP_LEADING] = g_param_spec_boolean ("leading", + NULL, NULL, DEFAULT_LEADING, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + /** + * ClapperGtkLeadContainer:blocked-actions: + * + * Mask of actions that container blocks from being triggered on video. + */ + param_specs[PROP_BLOCKED_ACTIONS] = g_param_spec_flags ("blocked-actions", + NULL, NULL, CLAPPER_GTK_TYPE_VIDEO_ACTION_MASK, DEFAULT_BLOCKED_ACTIONS, + 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_css_name (widget_class, "clapper-gtk-lead-container"); +} diff --git a/src/lib/clapper-gtk/clapper-gtk-lead-container.h b/src/lib/clapper-gtk/clapper-gtk-lead-container.h new file mode 100644 index 00000000..8d5be7ce --- /dev/null +++ b/src/lib/clapper-gtk/clapper-gtk-lead-container.h @@ -0,0 +1,58 @@ +/* Clapper GTK Integration Library + * Copyright (C) 2024 RafaÅ‚ DziÄ™giel + * + * 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. + */ + +#pragma once + +#if !defined(__CLAPPER_GTK_INSIDE__) && !defined(CLAPPER_GTK_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#include +#include + +#include +#include + +G_BEGIN_DECLS + +#define CLAPPER_GTK_TYPE_LEAD_CONTAINER (clapper_gtk_lead_container_get_type()) +#define CLAPPER_GTK_LEAD_CONTAINER_CAST(obj) ((ClapperGtkLeadContainer *)(obj)) + +G_DECLARE_DERIVABLE_TYPE (ClapperGtkLeadContainer, clapper_gtk_lead_container, CLAPPER_GTK, LEAD_CONTAINER, ClapperGtkContainer) + +struct _ClapperGtkLeadContainerClass +{ + ClapperGtkContainerClass parent_class; + + /*< private >*/ + gpointer padding[4]; +}; + +GtkWidget * clapper_gtk_lead_container_new (void); + +void clapper_gtk_lead_container_set_leading (ClapperGtkLeadContainer *lead_container, gboolean leading); + +gboolean clapper_gtk_lead_container_get_leading (ClapperGtkLeadContainer *lead_container); + +void clapper_gtk_lead_container_set_blocked_actions (ClapperGtkLeadContainer *lead_container, ClapperGtkVideoActionMask actions); + +ClapperGtkVideoActionMask clapper_gtk_lead_container_get_blocked_actions (ClapperGtkLeadContainer *lead_container); + +G_END_DECLS diff --git a/src/lib/clapper-gtk/clapper-gtk-limited-layout-private.h b/src/lib/clapper-gtk/clapper-gtk-limited-layout-private.h new file mode 100644 index 00000000..2ce50b58 --- /dev/null +++ b/src/lib/clapper-gtk/clapper-gtk-limited-layout-private.h @@ -0,0 +1,57 @@ +/* Clapper GTK Integration Library + * Copyright (C) 2024 RafaÅ‚ DziÄ™giel + * + * 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. + */ + +#pragma once + +#include +#include +#include + +G_BEGIN_DECLS + +#define CLAPPER_GTK_TYPE_LIMITED_LAYOUT (clapper_gtk_limited_layout_get_type()) +#define CLAPPER_GTK_LIMITED_LAYOUT_CAST(obj) ((ClapperGtkLimitedLayout *)(obj)) + +G_DECLARE_FINAL_TYPE (ClapperGtkLimitedLayout, clapper_gtk_limited_layout, CLAPPER_GTK, LIMITED_LAYOUT, GtkLayoutManager) + +G_GNUC_INTERNAL +void clapper_gtk_limited_layout_set_max_width (ClapperGtkLimitedLayout *layout, gint max_width); + +G_GNUC_INTERNAL +gint clapper_gtk_limited_layout_get_max_width (ClapperGtkLimitedLayout *layout); + +G_GNUC_INTERNAL +void clapper_gtk_limited_layout_set_max_height (ClapperGtkLimitedLayout *layout, gint max_height); + +G_GNUC_INTERNAL +gint clapper_gtk_limited_layout_get_max_height (ClapperGtkLimitedLayout *layout); + +G_GNUC_INTERNAL +void clapper_gtk_limited_layout_set_adaptive_width (ClapperGtkLimitedLayout *layout, gint width); + +G_GNUC_INTERNAL +gint clapper_gtk_limited_layout_get_adaptive_width (ClapperGtkLimitedLayout *layout); + +G_GNUC_INTERNAL +void clapper_gtk_limited_layout_set_adaptive_height (ClapperGtkLimitedLayout *layout, gint height); + +G_GNUC_INTERNAL +gint clapper_gtk_limited_layout_get_adaptive_height (ClapperGtkLimitedLayout *layout); + +G_END_DECLS diff --git a/src/lib/clapper-gtk/clapper-gtk-limited-layout.c b/src/lib/clapper-gtk/clapper-gtk-limited-layout.c new file mode 100644 index 00000000..44fdc2f8 --- /dev/null +++ b/src/lib/clapper-gtk/clapper-gtk-limited-layout.c @@ -0,0 +1,159 @@ +/* Clapper GTK Integration Library + * Copyright (C) 2024 RafaÅ‚ DziÄ™giel + * + * 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. + */ + +#include + +#include "clapper-gtk-limited-layout-private.h" +#include "clapper-gtk-container-private.h" + +#define GST_CAT_DEFAULT clapper_gtk_limited_layout_debug +GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); + +struct _ClapperGtkLimitedLayout +{ + GtkLayoutManager parent; + + gint max_width; + gint max_height; + + gint adaptive_width; + gint adaptive_height; + + gboolean adapt; +}; + +#define parent_class clapper_gtk_limited_layout_parent_class +G_DEFINE_TYPE (ClapperGtkLimitedLayout, clapper_gtk_limited_layout, GTK_TYPE_LAYOUT_MANAGER) + +void +clapper_gtk_limited_layout_set_max_width (ClapperGtkLimitedLayout *self, gint max_width) +{ + self->max_width = max_width; +} + +gint +clapper_gtk_limited_layout_get_max_width (ClapperGtkLimitedLayout *self) +{ + return self->max_width; +} + +void +clapper_gtk_limited_layout_set_max_height (ClapperGtkLimitedLayout *self, gint max_height) +{ + self->max_height = max_height; +} + +gint +clapper_gtk_limited_layout_get_max_height (ClapperGtkLimitedLayout *self) +{ + return self->max_height; +} + +void +clapper_gtk_limited_layout_set_adaptive_width (ClapperGtkLimitedLayout *self, gint width) +{ + self->adaptive_width = width; +} + +gint +clapper_gtk_limited_layout_get_adaptive_width (ClapperGtkLimitedLayout *self) +{ + return self->adaptive_width; +} + +void +clapper_gtk_limited_layout_set_adaptive_height (ClapperGtkLimitedLayout *self, gint height) +{ + self->adaptive_height = height; +} + +gint +clapper_gtk_limited_layout_get_adaptive_height (ClapperGtkLimitedLayout *self) +{ + return self->adaptive_height; +} + +static void +clapper_gtk_limited_layout_measure (GtkLayoutManager *layout_manager, + GtkWidget *widget, GtkOrientation orientation, gint for_size, + gint *minimum, gint *natural, gint *minimum_baseline, gint *natural_baseline) +{ + ClapperGtkLimitedLayout *self = CLAPPER_GTK_LIMITED_LAYOUT_CAST (layout_manager); + GtkWidget *child = gtk_widget_get_first_child (widget); + + if (child && gtk_widget_should_layout (child)) { + gint child_min = 0, child_nat = 0; + gint child_min_baseline = -1, child_nat_baseline = -1; + + gtk_widget_measure (child, orientation, for_size, + &child_min, &child_nat, + &child_min_baseline, &child_nat_baseline); + + if (orientation == GTK_ORIENTATION_HORIZONTAL) + *natural = (self->max_width < 0) ? MAX (*natural, child_nat) : self->max_width; + else if (orientation == GTK_ORIENTATION_VERTICAL) + *natural = (self->max_height < 0) ? MAX (*natural, child_nat) : self->max_height; + + *minimum = MAX (*minimum, child_min); + + if (child_min_baseline > -1) + *minimum_baseline = MAX (*minimum_baseline, child_min_baseline); + if (child_nat_baseline > -1) + *natural_baseline = MAX (*natural_baseline, child_nat_baseline); + } +} + +static void +clapper_gtk_limited_layout_allocate (GtkLayoutManager *layout_manager, + GtkWidget *widget, gint width, gint height, gint baseline) +{ + ClapperGtkLimitedLayout *self = CLAPPER_GTK_LIMITED_LAYOUT_CAST (layout_manager); + GtkWidget *child = gtk_widget_get_first_child (widget); + gboolean adapt = (width <= self->adaptive_width || height <= self->adaptive_height); + + if (child && gtk_widget_should_layout (child)) + gtk_widget_allocate (child, width, height, baseline, NULL); + + if (G_UNLIKELY (self->adapt != adapt)) { + self->adapt = adapt; + clapper_gtk_container_emit_adapt (CLAPPER_GTK_CONTAINER_CAST (widget), adapt); + } +} + +static void +clapper_gtk_limited_layout_init (ClapperGtkLimitedLayout *self) +{ + self->max_width = -1; + self->max_height = -1; + + self->adaptive_width = -1; + self->adaptive_height = -1; +} + +static void +clapper_gtk_limited_layout_class_init (ClapperGtkLimitedLayoutClass *klass) +{ + GtkLayoutManagerClass *layout_manager_class = (GtkLayoutManagerClass *) klass; + + GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clappergtklimitedlayout", 0, + "Clapper GTK Limited Layout"); + + layout_manager_class->measure = clapper_gtk_limited_layout_measure; + layout_manager_class->allocate = clapper_gtk_limited_layout_allocate; +} diff --git a/src/lib/clapper-gtk/clapper-gtk-next-item-button.c b/src/lib/clapper-gtk/clapper-gtk-next-item-button.c new file mode 100644 index 00000000..79cafc92 --- /dev/null +++ b/src/lib/clapper-gtk/clapper-gtk-next-item-button.c @@ -0,0 +1,132 @@ +/* Clapper GTK Integration Library + * Copyright (C) 2024 RafaÅ‚ DziÄ™giel + * + * 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. + */ + +/** + * ClapperGtkNextItemButton: + * + * A #GtkButton for selecting next queue item. + */ + +#include + +#include "clapper-gtk-next-item-button.h" +#include "clapper-gtk-utils.h" + +#define GST_CAT_DEFAULT clapper_gtk_next_item_button_debug +GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); + +struct _ClapperGtkNextItemButton +{ + GtkButton parent; + + GBinding *n_items_binding; + GBinding *current_index_binding; +}; + +#define parent_class clapper_gtk_next_item_button_parent_class +G_DEFINE_TYPE (ClapperGtkNextItemButton, clapper_gtk_next_item_button, GTK_TYPE_BUTTON) + +static gboolean +_transform_sensitive_func (GBinding *binding, const GValue *from_value, + GValue *to_value, ClapperGtkNextItemButton *self) +{ + ClapperQueue *queue = CLAPPER_QUEUE_CAST (g_binding_dup_source (binding)); + guint current_index; + gboolean can_skip; + + if (G_UNLIKELY (queue == NULL)) + return FALSE; + + current_index = clapper_queue_get_current_index (queue); + + can_skip = (current_index != CLAPPER_QUEUE_INVALID_POSITION + && current_index < clapper_queue_get_n_items (queue) - 1); + gst_object_unref (queue); + + g_value_set_boolean (to_value, can_skip); + GST_DEBUG_OBJECT (self, "Set sensitive: %s", (can_skip) ? "yes" : "no"); + + return TRUE; +} + +/** + * clapper_gtk_next_item_button_new: + * + * Creates a new #ClapperGtkNextItemButton to play next #ClapperMediaItem. + * + * Returns: a new next item button #GtkWidget. + */ +GtkWidget * +clapper_gtk_next_item_button_new (void) +{ + return g_object_new (CLAPPER_GTK_TYPE_NEXT_ITEM_BUTTON, NULL); +} + +static void +clapper_gtk_next_item_button_init (ClapperGtkNextItemButton *self) +{ + gtk_widget_set_sensitive (GTK_WIDGET (self), FALSE); + gtk_button_set_icon_name (GTK_BUTTON (self), "media-skip-forward-symbolic"); + gtk_actionable_set_action_name (GTK_ACTIONABLE (self), "video.next-item"); +} + +static void +clapper_gtk_next_item_button_map (GtkWidget *widget) +{ + ClapperGtkNextItemButton *self = CLAPPER_GTK_NEXT_ITEM_BUTTON_CAST (widget); + ClapperPlayer *player; + + if ((player = clapper_gtk_get_player_from_ancestor (widget))) { + ClapperQueue *queue = clapper_player_get_queue (player); + + self->n_items_binding = g_object_bind_property_full (queue, "n-items", + self, "sensitive", G_BINDING_DEFAULT, // Since we sync below, no need to do it twice + (GBindingTransformFunc) _transform_sensitive_func, + NULL, self, NULL); + self->current_index_binding = g_object_bind_property_full (queue, "current-index", + self, "sensitive", G_BINDING_SYNC_CREATE, + (GBindingTransformFunc) _transform_sensitive_func, + NULL, self, NULL); + } + + GTK_WIDGET_CLASS (parent_class)->map (widget); +} + +static void +clapper_gtk_next_item_button_unmap (GtkWidget *widget) +{ + ClapperGtkNextItemButton *self = CLAPPER_GTK_NEXT_ITEM_BUTTON_CAST (widget); + + g_clear_pointer (&self->n_items_binding, g_binding_unbind); + g_clear_pointer (&self->current_index_binding, g_binding_unbind); + + GTK_WIDGET_CLASS (parent_class)->unmap (widget); +} + +static void +clapper_gtk_next_item_button_class_init (ClapperGtkNextItemButtonClass *klass) +{ + GtkWidgetClass *widget_class = (GtkWidgetClass *) klass; + + GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clappergtknextitembutton", 0, + "Clapper GTK Next Item Button"); + + widget_class->map = clapper_gtk_next_item_button_map; + widget_class->unmap = clapper_gtk_next_item_button_unmap; +} diff --git a/src/lib/clapper-gtk/clapper-gtk-next-item-button.h b/src/lib/clapper-gtk/clapper-gtk-next-item-button.h new file mode 100644 index 00000000..a792c5ed --- /dev/null +++ b/src/lib/clapper-gtk/clapper-gtk-next-item-button.h @@ -0,0 +1,39 @@ +/* Clapper GTK Integration Library + * Copyright (C) 2024 RafaÅ‚ DziÄ™giel + * + * 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. + */ + +#pragma once + +#if !defined(__CLAPPER_GTK_INSIDE__) && !defined(CLAPPER_GTK_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#include +#include + +G_BEGIN_DECLS + +#define CLAPPER_GTK_TYPE_NEXT_ITEM_BUTTON (clapper_gtk_next_item_button_get_type()) +#define CLAPPER_GTK_NEXT_ITEM_BUTTON_CAST(obj) ((ClapperGtkNextItemButton *)(obj)) + +G_DECLARE_FINAL_TYPE (ClapperGtkNextItemButton, clapper_gtk_next_item_button, CLAPPER_GTK, NEXT_ITEM_BUTTON, GtkButton) + +GtkWidget * clapper_gtk_next_item_button_new (void); + +G_END_DECLS diff --git a/src/lib/clapper-gtk/clapper-gtk-previous-item-button.c b/src/lib/clapper-gtk/clapper-gtk-previous-item-button.c new file mode 100644 index 00000000..f214ebb8 --- /dev/null +++ b/src/lib/clapper-gtk/clapper-gtk-previous-item-button.c @@ -0,0 +1,132 @@ +/* Clapper GTK Integration Library + * Copyright (C) 2024 RafaÅ‚ DziÄ™giel + * + * 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. + */ + +/** + * ClapperGtkPreviousItemButton: + * + * A #GtkButton for selecting previous queue item. + */ + +#include + +#include "clapper-gtk-previous-item-button.h" +#include "clapper-gtk-utils.h" + +#define GST_CAT_DEFAULT clapper_gtk_previous_item_button_debug +GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); + +struct _ClapperGtkPreviousItemButton +{ + GtkButton parent; + + GBinding *n_items_binding; + GBinding *current_index_binding; +}; + +#define parent_class clapper_gtk_previous_item_button_parent_class +G_DEFINE_TYPE (ClapperGtkPreviousItemButton, clapper_gtk_previous_item_button, GTK_TYPE_BUTTON) + +static gboolean +_transform_sensitive_func (GBinding *binding, const GValue *from_value, + GValue *to_value, ClapperGtkPreviousItemButton *self) +{ + ClapperQueue *queue = CLAPPER_QUEUE_CAST (g_binding_dup_source (binding)); + guint current_index; + gboolean can_skip; + + if (G_UNLIKELY (queue == NULL)) + return FALSE; + + current_index = clapper_queue_get_current_index (queue); + + can_skip = (current_index != CLAPPER_QUEUE_INVALID_POSITION + && current_index > 0); + gst_object_unref (queue); + + g_value_set_boolean (to_value, can_skip); + GST_DEBUG_OBJECT (self, "Set sensitive: %s", (can_skip) ? "yes" : "no"); + + return TRUE; +} + +/** + * clapper_gtk_previous_item_button_new: + * + * Creates a new #ClapperGtkPreviousItemButton to play previous #ClapperMediaItem. + * + * Returns: a new previous item button #GtkWidget. + */ +GtkWidget * +clapper_gtk_previous_item_button_new (void) +{ + return g_object_new (CLAPPER_GTK_TYPE_PREVIOUS_ITEM_BUTTON, NULL); +} + +static void +clapper_gtk_previous_item_button_init (ClapperGtkPreviousItemButton *self) +{ + gtk_widget_set_sensitive (GTK_WIDGET (self), FALSE); + gtk_button_set_icon_name (GTK_BUTTON (self), "media-skip-backward-symbolic"); + gtk_actionable_set_action_name (GTK_ACTIONABLE (self), "video.previous-item"); +} + +static void +clapper_gtk_previous_item_button_map (GtkWidget *widget) +{ + ClapperGtkPreviousItemButton *self = CLAPPER_GTK_PREVIOUS_ITEM_BUTTON_CAST (widget); + ClapperPlayer *player; + + if ((player = clapper_gtk_get_player_from_ancestor (widget))) { + ClapperQueue *queue = clapper_player_get_queue (player); + + self->n_items_binding = g_object_bind_property_full (queue, "n-items", + self, "sensitive", G_BINDING_DEFAULT, // Since we sync below, no need to do it twice + (GBindingTransformFunc) _transform_sensitive_func, + NULL, self, NULL); + self->current_index_binding = g_object_bind_property_full (queue, "current-index", + self, "sensitive", G_BINDING_SYNC_CREATE, + (GBindingTransformFunc) _transform_sensitive_func, + NULL, self, NULL); + } + + GTK_WIDGET_CLASS (parent_class)->map (widget); +} + +static void +clapper_gtk_previous_item_button_unmap (GtkWidget *widget) +{ + ClapperGtkPreviousItemButton *self = CLAPPER_GTK_PREVIOUS_ITEM_BUTTON_CAST (widget); + + g_clear_pointer (&self->n_items_binding, g_binding_unbind); + g_clear_pointer (&self->current_index_binding, g_binding_unbind); + + GTK_WIDGET_CLASS (parent_class)->unmap (widget); +} + +static void +clapper_gtk_previous_item_button_class_init (ClapperGtkPreviousItemButtonClass *klass) +{ + GtkWidgetClass *widget_class = (GtkWidgetClass *) klass; + + GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clappergtkpreviousitembutton", 0, + "Clapper GTK Previous Item Button"); + + widget_class->map = clapper_gtk_previous_item_button_map; + widget_class->unmap = clapper_gtk_previous_item_button_unmap; +} diff --git a/src/lib/clapper-gtk/clapper-gtk-previous-item-button.h b/src/lib/clapper-gtk/clapper-gtk-previous-item-button.h new file mode 100644 index 00000000..e6a4838b --- /dev/null +++ b/src/lib/clapper-gtk/clapper-gtk-previous-item-button.h @@ -0,0 +1,39 @@ +/* Clapper GTK Integration Library + * Copyright (C) 2024 RafaÅ‚ DziÄ™giel + * + * 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. + */ + +#pragma once + +#if !defined(__CLAPPER_GTK_INSIDE__) && !defined(CLAPPER_GTK_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#include +#include + +G_BEGIN_DECLS + +#define CLAPPER_GTK_TYPE_PREVIOUS_ITEM_BUTTON (clapper_gtk_previous_item_button_get_type()) +#define CLAPPER_GTK_PREVIOUS_ITEM_BUTTON_CAST(obj) ((ClapperGtkPreviousItemButton *)(obj)) + +G_DECLARE_FINAL_TYPE (ClapperGtkPreviousItemButton, clapper_gtk_previous_item_button, CLAPPER_GTK, PREVIOUS_ITEM_BUTTON, GtkButton) + +GtkWidget * clapper_gtk_previous_item_button_new (void); + +G_END_DECLS diff --git a/src/lib/clapper-gtk/clapper-gtk-seek-bar.c b/src/lib/clapper-gtk/clapper-gtk-seek-bar.c new file mode 100644 index 00000000..f157379b --- /dev/null +++ b/src/lib/clapper-gtk/clapper-gtk-seek-bar.c @@ -0,0 +1,783 @@ +/* Clapper GTK Integration Library + * Copyright (C) 2024 RafaÅ‚ DziÄ™giel + * + * 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 + +#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"); +} diff --git a/src/lib/clapper-gtk/clapper-gtk-seek-bar.h b/src/lib/clapper-gtk/clapper-gtk-seek-bar.h new file mode 100644 index 00000000..b2f93215 --- /dev/null +++ b/src/lib/clapper-gtk/clapper-gtk-seek-bar.h @@ -0,0 +1,48 @@ +/* Clapper GTK Integration Library + * Copyright (C) 2024 RafaÅ‚ DziÄ™giel + * + * 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. + */ + +#pragma once + +#if !defined(__CLAPPER_GTK_INSIDE__) && !defined(CLAPPER_GTK_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#include +#include +#include + +G_BEGIN_DECLS + +#define CLAPPER_GTK_TYPE_SEEK_BAR (clapper_gtk_seek_bar_get_type()) +#define CLAPPER_GTK_SEEK_BAR_CAST(obj) ((ClapperGtkSeekBar *)(obj)) + +G_DECLARE_FINAL_TYPE (ClapperGtkSeekBar, clapper_gtk_seek_bar, CLAPPER_GTK, SEEK_BAR, GtkWidget) + +GtkWidget * clapper_gtk_seek_bar_new (void); + +void clapper_gtk_seek_bar_set_reveal_labels (ClapperGtkSeekBar *seek_bar, gboolean reveal); + +gboolean clapper_gtk_seek_bar_get_reveal_labels (ClapperGtkSeekBar *seek_bar); + +void clapper_gtk_seek_bar_set_seek_method (ClapperGtkSeekBar *seek_bar, ClapperPlayerSeekMethod method); + +ClapperPlayerSeekMethod clapper_gtk_seek_bar_get_seek_method (ClapperGtkSeekBar *seek_bar); + +G_END_DECLS diff --git a/src/lib/clapper-gtk/clapper-gtk-simple-controls.c b/src/lib/clapper-gtk/clapper-gtk-simple-controls.c new file mode 100644 index 00000000..38a9dcc2 --- /dev/null +++ b/src/lib/clapper-gtk/clapper-gtk-simple-controls.c @@ -0,0 +1,344 @@ +/* Clapper GTK Integration Library + * Copyright (C) 2024 RafaÅ‚ DziÄ™giel + * + * 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. + */ + +/** + * ClapperGtkSimpleControls: + * + * A minimalistic playback controls panel widget. + * + * #ClapperGtkSimpleControls is a simple, ready to be used playback controls widget. + * It is meant to be placed as an overlay (either fading or not) of [class@ClapperGtk.Video] + * as-is, providing minimal yet universal playback controls for your app. + * + * If you need a further customized controls, please use individual widgets this + * widget consists of to build your own controls implementation instead. + */ + +#include "config.h" + +#include + +#include "clapper-gtk-simple-controls.h" +#include "clapper-gtk-seek-bar.h" + +#define DEFAULT_FULLSCREENABLE TRUE +#define DEFAULT_SEEK_METHOD CLAPPER_PLAYER_SEEK_METHOD_NORMAL + +#define IS_REVEALED(widget) (gtk_revealer_get_child_revealed ((GtkRevealer *) (widget))) +#define IS_REVEAL(widget) (gtk_revealer_get_reveal_child ((GtkRevealer *) (widget))) +#define SET_REVEAL(widget,reveal) (gtk_revealer_set_reveal_child ((GtkRevealer *) (widget), reveal)) + +#define GST_CAT_DEFAULT clapper_gtk_simple_controls_debug +GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); + +struct _ClapperGtkSimpleControls +{ + ClapperGtkContainer parent; + + GtkWidget *seek_bar; + GtkWidget *extra_menu_button; + GtkWidget *fullscreen_top_revealer; + GtkWidget *fullscreen_bottom_revealer; + GtkWidget *controls_slide_revealer; + + gboolean fullscreenable; + ClapperPlayerSeekMethod seek_method; + + gboolean adapt; +}; + +#define parent_class clapper_gtk_simple_controls_parent_class +G_DEFINE_TYPE (ClapperGtkSimpleControls, clapper_gtk_simple_controls, CLAPPER_GTK_TYPE_CONTAINER) + +enum +{ + PROP_0, + PROP_FULLSCREENABLE, + PROP_SEEK_METHOD, + PROP_EXTRA_MENU_BUTTON, + PROP_LAST +}; + +static GParamSpec *param_specs[PROP_LAST] = { NULL, }; + +static void +initial_adapt_cb (ClapperGtkContainer *container, gboolean adapt, + ClapperGtkSimpleControls *self) +{ + GST_DEBUG_OBJECT (self, "Initially adapted: %s", (adapt) ? "yes" : "no"); + + clapper_gtk_seek_bar_set_reveal_labels (CLAPPER_GTK_SEEK_BAR (self->seek_bar), !adapt); +} + +static void +full_adapt_cb (ClapperGtkContainer *container, gboolean adapt, + ClapperGtkSimpleControls *self) +{ + self->adapt = adapt; + + GST_DEBUG_OBJECT (self, "Width adapted: %s", (self->adapt) ? "yes" : "no"); + + /* Take different action, depending on transition step we are currently at */ + if (self->adapt) { + if (IS_REVEAL (self->fullscreen_bottom_revealer)) + SET_REVEAL (self->fullscreen_bottom_revealer, FALSE); + else if (IS_REVEAL (self->controls_slide_revealer)) + SET_REVEAL (self->controls_slide_revealer, FALSE); + else + SET_REVEAL (self->fullscreen_top_revealer, TRUE); + } else { + if (IS_REVEAL (self->fullscreen_top_revealer)) + SET_REVEAL (self->fullscreen_top_revealer, FALSE); + else if (!IS_REVEAL (self->controls_slide_revealer)) + SET_REVEAL (self->controls_slide_revealer, TRUE); + else + SET_REVEAL (self->fullscreen_bottom_revealer, TRUE); + } +} + +static void +controls_revealed_cb (GtkRevealer *revealer, + GParamSpec *pspec G_GNUC_UNUSED, ClapperGtkSimpleControls *self) +{ + gboolean revealed = IS_REVEALED (revealer); + + GST_DEBUG_OBJECT (self, "Slide revealed: %s", (revealed) ? "yes" : "no"); + + /* We should be hidden when adapted, otherwise go back */ + if (G_UNLIKELY (revealed == self->adapt)) + gtk_revealer_set_reveal_child (revealer, !revealed); +} + +/** + * clapper_gtk_simple_controls_new: + * + * Creates a new #ClapperGtkSimpleControls instance. + * + * Returns: a new simple controls #GtkWidget. + */ +GtkWidget * +clapper_gtk_simple_controls_new (void) +{ + return g_object_new (CLAPPER_GTK_TYPE_SIMPLE_CONTROLS, NULL); +} + +/** + * clapper_gtk_simple_controls_set_fullscreenable: + * @controls: a #ClapperGtkSimpleControls + * @fullscreenable: whether show button for toggling fullscreen state + * + * Set whether [class@ClapperGtk.ToggleFullscreenButton] button in the @controls + * should be visible. + * + * You might want to consider setting this to %FALSE, if your application + * does not implement [signal@ClapperGtk.Video::toggle-fullscreen] signal. + */ +void +clapper_gtk_simple_controls_set_fullscreenable (ClapperGtkSimpleControls *self, gboolean fullscreenable) +{ + g_return_if_fail (CLAPPER_GTK_IS_SIMPLE_CONTROLS (self)); + + if (self->fullscreenable != fullscreenable) { + self->fullscreenable = fullscreenable; + g_object_notify_by_pspec (G_OBJECT (self), param_specs[PROP_FULLSCREENABLE]); + } +} + +/** + * clapper_gtk_simple_controls_get_fullscreenable: + * @controls: a #ClapperGtkSimpleControls + * + * Get whether [class@ClapperGtk.ToggleFullscreenButton] button in the @controls + * is set to be visible. + */ +gboolean +clapper_gtk_simple_controls_get_fullscreenable (ClapperGtkSimpleControls *self) +{ + g_return_val_if_fail (CLAPPER_GTK_IS_SIMPLE_CONTROLS (self), FALSE); + + return self->fullscreenable; +} + +/** + * clapper_gtk_simple_controls_set_seek_method: + * @controls: a #ClapperGtkSimpleControls + * @method: a #ClapperPlayerSeekMethod + * + * Set [enum@Clapper.PlayerSeekMethod] to use when seeking with progress bar. + */ +void +clapper_gtk_simple_controls_set_seek_method (ClapperGtkSimpleControls *self, + ClapperPlayerSeekMethod method) +{ + g_return_if_fail (CLAPPER_GTK_IS_SIMPLE_CONTROLS (self)); + + clapper_gtk_seek_bar_set_seek_method (CLAPPER_GTK_SEEK_BAR (self->seek_bar), method); +} + +/** + * clapper_gtk_simple_controls_get_seek_method: + * @controls: a #ClapperGtkSimpleControls + * + * Get [enum@Clapper.PlayerSeekMethod] used when seeking with progress bar. + * + * Returns: #ClapperPlayerSeekMethod used for seeking. + */ +ClapperPlayerSeekMethod +clapper_gtk_simple_controls_get_seek_method (ClapperGtkSimpleControls *self) +{ + g_return_val_if_fail (CLAPPER_GTK_IS_SIMPLE_CONTROLS (self), DEFAULT_SEEK_METHOD); + + return clapper_gtk_seek_bar_get_seek_method (CLAPPER_GTK_SEEK_BAR (self->seek_bar)); +} + +/** + * clapper_gtk_simple_controls_get_extra_menu_button: + * @controls: a #ClapperGtkSimpleControls + * + * Get [class@ClapperGtk.ExtraMenuButton] that resides within @controls. + * + * Returns: (transfer none): #ClapperGtkExtraMenuButton within simple controls panel. + */ +ClapperGtkExtraMenuButton * +clapper_gtk_simple_controls_get_extra_menu_button (ClapperGtkSimpleControls *self) +{ + g_return_val_if_fail (CLAPPER_GTK_IS_SIMPLE_CONTROLS (self), NULL); + + return CLAPPER_GTK_EXTRA_MENU_BUTTON (self->extra_menu_button); +} + +static void +clapper_gtk_simple_controls_init (ClapperGtkSimpleControls *self) +{ + self->fullscreenable = DEFAULT_FULLSCREENABLE; + self->seek_method = DEFAULT_SEEK_METHOD; + + gtk_widget_init_template (GTK_WIDGET (self)); + + /* Set our defaults to children */ + clapper_gtk_seek_bar_set_seek_method ( + CLAPPER_GTK_SEEK_BAR_CAST (self->seek_bar), self->seek_method); +} + +static void +clapper_gtk_simple_controls_dispose (GObject *object) +{ + gtk_widget_dispose_template (GTK_WIDGET (object), CLAPPER_GTK_TYPE_SIMPLE_CONTROLS); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +clapper_gtk_simple_controls_get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + ClapperGtkSimpleControls *self = CLAPPER_GTK_SIMPLE_CONTROLS_CAST (object); + + switch (prop_id) { + case PROP_FULLSCREENABLE: + g_value_set_boolean (value, clapper_gtk_simple_controls_get_fullscreenable (self)); + break; + case PROP_SEEK_METHOD: + g_value_set_enum (value, clapper_gtk_simple_controls_get_seek_method (self)); + break; + case PROP_EXTRA_MENU_BUTTON: + g_value_set_object (value, clapper_gtk_simple_controls_get_extra_menu_button (self)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +clapper_gtk_simple_controls_set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + ClapperGtkSimpleControls *self = CLAPPER_GTK_SIMPLE_CONTROLS_CAST (object); + + switch (prop_id) { + case PROP_FULLSCREENABLE: + clapper_gtk_simple_controls_set_fullscreenable (self, g_value_get_boolean (value)); + break; + case PROP_SEEK_METHOD: + clapper_gtk_simple_controls_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_simple_controls_class_init (ClapperGtkSimpleControlsClass *klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + GtkWidgetClass *widget_class = (GtkWidgetClass *) klass; + + GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clappergtksimplecontrols", 0, + "Clapper GTK Simple Controls"); + + gobject_class->get_property = clapper_gtk_simple_controls_get_property; + gobject_class->set_property = clapper_gtk_simple_controls_set_property; + gobject_class->dispose = clapper_gtk_simple_controls_dispose; + + /** + * ClapperGtkSimpleControls:fullscreenable: + * + * Whether toggle fullscreen button should be visible. + */ + param_specs[PROP_FULLSCREENABLE] = g_param_spec_boolean ("fullscreenable", + NULL, NULL, DEFAULT_FULLSCREENABLE, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + /** + * ClapperGtkSimpleControls: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_STATIC_STRINGS); + + /** + * ClapperGtkSimpleControls:extra-menu-button: + * + * Access to extra menu button within controls. + */ + param_specs[PROP_EXTRA_MENU_BUTTON] = g_param_spec_object ("extra-menu-button", + NULL, NULL, CLAPPER_GTK_TYPE_EXTRA_MENU_BUTTON, + G_PARAM_READABLE | 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-simple-controls.ui"); + + gtk_widget_class_bind_template_child (widget_class, ClapperGtkSimpleControls, seek_bar); + gtk_widget_class_bind_template_child (widget_class, ClapperGtkSimpleControls, extra_menu_button); + gtk_widget_class_bind_template_child (widget_class, ClapperGtkSimpleControls, fullscreen_top_revealer); + gtk_widget_class_bind_template_child (widget_class, ClapperGtkSimpleControls, fullscreen_bottom_revealer); + gtk_widget_class_bind_template_child (widget_class, ClapperGtkSimpleControls, controls_slide_revealer); + + gtk_widget_class_bind_template_callback (widget_class, initial_adapt_cb); + gtk_widget_class_bind_template_callback (widget_class, full_adapt_cb); + gtk_widget_class_bind_template_callback (widget_class, controls_revealed_cb); + + gtk_widget_class_set_css_name (widget_class, "clapper-gtk-simple-controls"); +} diff --git a/src/lib/clapper-gtk/clapper-gtk-simple-controls.h b/src/lib/clapper-gtk/clapper-gtk-simple-controls.h new file mode 100644 index 00000000..cd1fcd4a --- /dev/null +++ b/src/lib/clapper-gtk/clapper-gtk-simple-controls.h @@ -0,0 +1,51 @@ +/* Clapper GTK Integration Library + * Copyright (C) 2024 RafaÅ‚ DziÄ™giel + * + * 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. + */ + +#pragma once + +#if !defined(__CLAPPER_GTK_INSIDE__) && !defined(CLAPPER_GTK_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#include +#include +#include +#include + +G_BEGIN_DECLS + +#define CLAPPER_GTK_TYPE_SIMPLE_CONTROLS (clapper_gtk_simple_controls_get_type()) +#define CLAPPER_GTK_SIMPLE_CONTROLS_CAST(obj) ((ClapperGtkSimpleControls *)(obj)) + +G_DECLARE_FINAL_TYPE (ClapperGtkSimpleControls, clapper_gtk_simple_controls, CLAPPER_GTK, SIMPLE_CONTROLS, ClapperGtkContainer) + +GtkWidget * clapper_gtk_simple_controls_new (void); + +void clapper_gtk_simple_controls_set_fullscreenable (ClapperGtkSimpleControls *controls, gboolean fullscreenable); + +gboolean clapper_gtk_simple_controls_get_fullscreenable (ClapperGtkSimpleControls *controls); + +void clapper_gtk_simple_controls_set_seek_method (ClapperGtkSimpleControls *controls, ClapperPlayerSeekMethod method); + +ClapperPlayerSeekMethod clapper_gtk_simple_controls_get_seek_method (ClapperGtkSimpleControls *controls); + +ClapperGtkExtraMenuButton * clapper_gtk_simple_controls_get_extra_menu_button (ClapperGtkSimpleControls *controls); + +G_END_DECLS diff --git a/src/lib/clapper-gtk/clapper-gtk-status-private.h b/src/lib/clapper-gtk/clapper-gtk-status-private.h new file mode 100644 index 00000000..00b94bfa --- /dev/null +++ b/src/lib/clapper-gtk/clapper-gtk-status-private.h @@ -0,0 +1,44 @@ +/* Clapper GTK Integration Library + * Copyright (C) 2024 RafaÅ‚ DziÄ™giel + * + * 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. + */ + +#pragma once + +#include +#include +#include + +#include "clapper-gtk-container.h" + +G_BEGIN_DECLS + +#define CLAPPER_GTK_TYPE_STATUS (clapper_gtk_status_get_type()) +#define CLAPPER_GTK_STATUS_CAST(obj) ((ClapperGtkStatus *)(obj)) + +G_DECLARE_FINAL_TYPE (ClapperGtkStatus, clapper_gtk_status, CLAPPER_GTK, STATUS, ClapperGtkContainer) + +G_GNUC_INTERNAL +void clapper_gtk_status_set_error (ClapperGtkStatus *status, const GError *error); + +G_GNUC_INTERNAL +void clapper_gtk_status_set_missing_plugin (ClapperGtkStatus *status, const gchar *name); + +G_GNUC_INTERNAL +void clapper_gtk_status_clear (ClapperGtkStatus *status); + +G_END_DECLS diff --git a/src/lib/clapper-gtk/clapper-gtk-status.c b/src/lib/clapper-gtk/clapper-gtk-status.c new file mode 100644 index 00000000..0076f926 --- /dev/null +++ b/src/lib/clapper-gtk/clapper-gtk-status.c @@ -0,0 +1,137 @@ +/* Clapper GTK Integration Library + * Copyright (C) 2024 RafaÅ‚ DziÄ™giel + * + * 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. + */ + +#include "config.h" + +#include + +#include "clapper-gtk-status-private.h" + +#define NORMAL_SPACING 16 +#define ADAPT_SPACING 8 + +#define GST_CAT_DEFAULT clapper_gtk_status_debug +GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); + +struct _ClapperGtkStatus +{ + ClapperGtkContainer parent; + + GtkWidget *status_box; + GtkWidget *image; + GtkWidget *title_label; + GtkWidget *description_label; +}; + +#define parent_class clapper_gtk_status_parent_class +G_DEFINE_TYPE (ClapperGtkStatus, clapper_gtk_status, CLAPPER_GTK_TYPE_CONTAINER) + +static void +adapt_cb (ClapperGtkContainer *container, gboolean adapt, + ClapperGtkStatus *self) +{ + GST_DEBUG_OBJECT (self, "Adapted: %s", (adapt) ? "yes" : "no"); + + gtk_box_set_spacing (GTK_BOX (self->status_box), (adapt) ? ADAPT_SPACING : NORMAL_SPACING); + + if (adapt) { + gtk_widget_add_css_class (GTK_WIDGET (self), "adapted"); + gtk_widget_add_css_class (GTK_WIDGET (self->title_label), "title-2"); + } else { + gtk_widget_remove_css_class (GTK_WIDGET (self), "adapted"); + gtk_widget_remove_css_class (GTK_WIDGET (self->title_label), "title-2"); + } +} + +static void +_set_status (ClapperGtkStatus *self, const gchar *icon_name, + const gchar *title, const gchar *description) +{ + gtk_image_set_from_icon_name (GTK_IMAGE (self->image), icon_name); + gtk_label_set_label (GTK_LABEL (self->title_label), title); + gtk_label_set_label (GTK_LABEL (self->description_label), description); + + gtk_widget_set_visible (GTK_WIDGET (self), TRUE); +} + +void +clapper_gtk_status_set_error (ClapperGtkStatus *self, const GError *error) +{ + GST_DEBUG_OBJECT (self, "Status set to \"error\""); + _set_status (self, "dialog-warning-symbolic", "Unplayable Content", error->message); +} + +void +clapper_gtk_status_set_missing_plugin (ClapperGtkStatus *self, const gchar *name) +{ + gchar *description; + + GST_DEBUG_OBJECT (self, "Status set to \"missing-plugin\""); + description = g_strdup_printf ("Your GStreamer installation is missing a plugin: %s", name); + _set_status (self, "dialog-information-symbolic", "Missing Plugin", description); + + g_free (description); +} + +void +clapper_gtk_status_clear (ClapperGtkStatus *self) +{ + GST_DEBUG_OBJECT (self, "Status cleared"); + gtk_widget_set_visible (GTK_WIDGET (self), FALSE); +} + +static void +clapper_gtk_status_init (ClapperGtkStatus *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); + + gtk_box_set_spacing (GTK_BOX (self->status_box), NORMAL_SPACING); +} + +static void +clapper_gtk_status_dispose (GObject *object) +{ + gtk_widget_dispose_template (GTK_WIDGET (object), CLAPPER_GTK_TYPE_STATUS); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +clapper_gtk_status_class_init (ClapperGtkStatusClass *klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + GtkWidgetClass *widget_class = (GtkWidgetClass *) klass; + + GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clappergtkstatus", 0, + "Clapper GTK Status"); + + gobject_class->dispose = clapper_gtk_status_dispose; + + gtk_widget_class_set_template_from_resource (widget_class, + CLAPPER_GTK_RESOURCE_PREFIX "/ui/clapper-gtk-status.ui"); + + gtk_widget_class_bind_template_child (widget_class, ClapperGtkStatus, status_box); + gtk_widget_class_bind_template_child (widget_class, ClapperGtkStatus, image); + gtk_widget_class_bind_template_child (widget_class, ClapperGtkStatus, title_label); + gtk_widget_class_bind_template_child (widget_class, ClapperGtkStatus, description_label); + + gtk_widget_class_bind_template_callback (widget_class, adapt_cb); + + gtk_widget_class_set_css_name (widget_class, "clapper-gtk-status"); +} diff --git a/src/lib/clapper-gtk/clapper-gtk-stream-check-button-private.h b/src/lib/clapper-gtk/clapper-gtk-stream-check-button-private.h new file mode 100644 index 00000000..b3f51084 --- /dev/null +++ b/src/lib/clapper-gtk/clapper-gtk-stream-check-button-private.h @@ -0,0 +1,33 @@ +/* Clapper GTK Integration Library + * Copyright (C) 2024 RafaÅ‚ DziÄ™giel + * + * 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. + */ + +#pragma once + +#include +#include +#include + +G_BEGIN_DECLS + +#define CLAPPER_GTK_TYPE_STREAM_CHECK_BUTTON (clapper_gtk_stream_check_button_get_type()) +#define CLAPPER_GTK_STREAM_CHECK_BUTTON_CAST(obj) ((ClapperGtkStreamCheckButton *)(obj)) + +G_DECLARE_FINAL_TYPE (ClapperGtkStreamCheckButton, clapper_gtk_stream_check_button, CLAPPER_GTK, STREAM_CHECK_BUTTON, GtkCheckButton) + +G_END_DECLS diff --git a/src/lib/clapper-gtk/clapper-gtk-stream-check-button.c b/src/lib/clapper-gtk/clapper-gtk-stream-check-button.c new file mode 100644 index 00000000..ad5be786 --- /dev/null +++ b/src/lib/clapper-gtk/clapper-gtk-stream-check-button.c @@ -0,0 +1,284 @@ +/* Clapper GTK Integration Library + * Copyright (C) 2024 RafaÅ‚ DziÄ™giel + * + * 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. + */ + +#include "config.h" + +#include +#include + +#include "clapper-gtk-stream-check-button-private.h" +#include "clapper-gtk-utils-private.h" + +#define MAX_SIGNALS 4 + +struct _ClapperGtkStreamCheckButton +{ + GtkCheckButton parent; + + ClapperStream *stream; + + GtkWidget *fallback_check_button; + + gulong signal_ids[MAX_SIGNALS]; + gboolean grouped; +}; + +#define parent_class clapper_gtk_stream_check_button_parent_class +G_DEFINE_TYPE (ClapperGtkStreamCheckButton, clapper_gtk_stream_check_button, GTK_TYPE_CHECK_BUTTON) + +enum +{ + PROP_0, + PROP_STREAM, + PROP_LAST +}; + +static GParamSpec *param_specs[PROP_LAST] = { NULL, }; + +static inline gchar * +_get_video_stream_label (ClapperVideoStream *vstream) +{ + gchar *label, *codec = clapper_video_stream_get_codec (vstream); + gint height = clapper_video_stream_get_height (vstream); + gdouble fps = clapper_video_stream_get_fps (vstream); + + if (codec) { + label = g_strdup_printf ("%ip@%.3f [%s]", height, fps, codec); + g_free (codec); + } else { + label = g_strdup_printf ("%ip@%.3f", height, fps); + } + + return label; +} + +static inline gchar * +_get_audio_stream_label (ClapperAudioStream *astream) +{ + gchar *label, *title, *codec = clapper_audio_stream_get_codec (astream); + gint channels = clapper_audio_stream_get_channels (astream); + + if (!(title = clapper_stream_get_title (CLAPPER_STREAM_CAST (astream)))) + title = clapper_audio_stream_get_lang_name (astream); + + if (codec) { + label = g_strdup_printf ("%s [%s, %i %s]", + (title) ? title : _("Undetermined"), codec, channels, _("Channels")); + g_free (title); + g_free (codec); + } else { + label = title; + } + + return label; +} + +static inline gchar * +_get_subtitle_stream_label (ClapperSubtitleStream *sstream) +{ + gchar *title; + + if (!(title = clapper_stream_get_title (CLAPPER_STREAM_CAST (sstream)))) + title = clapper_subtitle_stream_get_lang_name (sstream); + + return title; +} + +static void +_refresh_label_cb (ClapperStream *stream, + GParamSpec *pspec G_GNUC_UNUSED, ClapperGtkStreamCheckButton *self) +{ + gchar *label = NULL; + + if (stream) { + switch (clapper_stream_get_stream_type (stream)) { + case CLAPPER_STREAM_TYPE_VIDEO: + label = _get_video_stream_label (CLAPPER_VIDEO_STREAM (stream)); + break; + case CLAPPER_STREAM_TYPE_AUDIO: + label = _get_audio_stream_label (CLAPPER_AUDIO_STREAM (stream)); + break; + case CLAPPER_STREAM_TYPE_SUBTITLE: + label = _get_subtitle_stream_label (CLAPPER_SUBTITLE_STREAM (stream)); + break; + default: + break; + } + } + + gtk_check_button_set_label (GTK_CHECK_BUTTON (self), + (label != NULL) ? label : _("Undetermined")); + g_free (label); +} + +static inline void +_disconnect_current_signals (ClapperGtkStreamCheckButton *self) +{ + guint i; + + for (i = 0; i < MAX_SIGNALS; ++i) { + /* No more signals connected */ + if (self->signal_ids[i] == 0) + break; + + g_signal_handler_disconnect (self->stream, self->signal_ids[i]); + self->signal_ids[i] = 0; + } +} + +static inline void +clapper_gtk_stream_check_button_set_stream (ClapperGtkStreamCheckButton *self, ClapperStream *stream) +{ + guint i = 0; + + _disconnect_current_signals (self); + gst_object_replace ((GstObject **) &self->stream, GST_OBJECT_CAST (stream)); + + if (!self->stream) + return; + + switch (clapper_stream_get_stream_type (stream)) { + case CLAPPER_STREAM_TYPE_VIDEO: + self->signal_ids[i++] = g_signal_connect (self->stream, "notify::codec", + G_CALLBACK (_refresh_label_cb), self); + self->signal_ids[i++] = g_signal_connect (self->stream, "notify::height", + G_CALLBACK (_refresh_label_cb), self); + self->signal_ids[i++] = g_signal_connect (self->stream, "notify::fps", + G_CALLBACK (_refresh_label_cb), self); + break; + case CLAPPER_STREAM_TYPE_AUDIO: + self->signal_ids[i++] = g_signal_connect (self->stream, "notify::codec", + G_CALLBACK (_refresh_label_cb), self); + self->signal_ids[i++] = g_signal_connect (self->stream, "notify::channels", + G_CALLBACK (_refresh_label_cb), self); + G_GNUC_FALLTHROUGH; + case CLAPPER_STREAM_TYPE_SUBTITLE: + self->signal_ids[i++] = g_signal_connect (self->stream, "notify::title", + G_CALLBACK (_refresh_label_cb), self); + self->signal_ids[i++] = g_signal_connect (self->stream, "notify::lang-name", + G_CALLBACK (_refresh_label_cb), self); + break; + default: + break; + } + + _refresh_label_cb (self->stream, NULL, self); +} + +static void +clapper_gtk_stream_check_button_realize (GtkWidget *widget) +{ + ClapperGtkStreamCheckButton *self = CLAPPER_GTK_STREAM_CHECK_BUTTON_CAST (widget); + GtkWidget *other_widget; + + GST_TRACE_OBJECT (self, "Realize"); + + /* Set same group as previous check button in the same list view */ + if (!self->grouped) { + if ((other_widget = gtk_widget_get_parent (widget)) + && (other_widget = gtk_widget_get_prev_sibling (other_widget)) + && (other_widget = gtk_widget_get_first_child (other_widget)) + && CLAPPER_GTK_IS_STREAM_CHECK_BUTTON (other_widget)) { + gtk_check_button_set_group (GTK_CHECK_BUTTON (self), GTK_CHECK_BUTTON (other_widget)); + } else { + if (!self->fallback_check_button) + self->fallback_check_button = g_object_ref_sink (gtk_check_button_new ()); + + gtk_check_button_set_group (GTK_CHECK_BUTTON (self), GTK_CHECK_BUTTON (self->fallback_check_button)); + } + self->grouped = TRUE; + } + + GTK_WIDGET_CLASS (parent_class)->realize (widget); +} + +static void +clapper_gtk_stream_check_button_toggled (GtkCheckButton *check_button) +{ + ClapperGtkStreamCheckButton *self = CLAPPER_GTK_STREAM_CHECK_BUTTON_CAST (check_button); + + if (gtk_check_button_get_active (check_button) && self->stream) { + ClapperStreamList *stream_list = CLAPPER_STREAM_LIST (gst_object_get_parent (GST_OBJECT (self->stream))); + + if (G_LIKELY (stream_list != NULL)) { + GST_INFO_OBJECT (self, "Toggled: %" GST_PTR_FORMAT, self->stream); + clapper_stream_list_select_stream (stream_list, self->stream); + gst_object_unref (stream_list); + } + } +} + +static void +clapper_gtk_stream_check_button_init (ClapperGtkStreamCheckButton *self) +{ +} + +static void +clapper_gtk_stream_check_button_finalize (GObject *object) +{ + ClapperGtkStreamCheckButton *self = CLAPPER_GTK_STREAM_CHECK_BUTTON_CAST (object); + + GST_TRACE_OBJECT (self, "Finalize"); + + _disconnect_current_signals (self); + gst_clear_object (&self->stream); + + g_clear_object (&self->fallback_check_button); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +clapper_gtk_stream_check_button_set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + ClapperGtkStreamCheckButton *self = CLAPPER_GTK_STREAM_CHECK_BUTTON_CAST (object); + + switch (prop_id) { + case PROP_STREAM: + clapper_gtk_stream_check_button_set_stream (self, g_value_get_object (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +clapper_gtk_stream_check_button_class_init (ClapperGtkStreamCheckButtonClass *klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + GtkWidgetClass *widget_class = (GtkWidgetClass *) klass; + GtkCheckButtonClass *check_button_class = (GtkCheckButtonClass *) klass; + + clapper_gtk_init_translations (); + + gobject_class->set_property = clapper_gtk_stream_check_button_set_property; + gobject_class->finalize = clapper_gtk_stream_check_button_finalize; + + widget_class->realize = clapper_gtk_stream_check_button_realize; + + check_button_class->toggled = clapper_gtk_stream_check_button_toggled; + + param_specs[PROP_STREAM] = g_param_spec_object ("stream", + NULL, NULL, CLAPPER_TYPE_STREAM, + G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (gobject_class, PROP_LAST, param_specs); +} diff --git a/src/lib/clapper-gtk/clapper-gtk-title-header.c b/src/lib/clapper-gtk/clapper-gtk-title-header.c new file mode 100644 index 00000000..06d36187 --- /dev/null +++ b/src/lib/clapper-gtk/clapper-gtk-title-header.c @@ -0,0 +1,228 @@ +/* Clapper GTK Integration Library + * Copyright (C) 2024 RafaÅ‚ DziÄ™giel + * + * 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. + */ + +/** + * ClapperGtkTitleHeader: + * + * A header panel widget that displays current media title. + * + * #ClapperGtkTitleHeader is a simple, ready to be used header widget that + * displays current media title. It can be placed as-is as a [class@ClapperGtk.Video] + * overlay (either fading or not). + * + * If you need a further customized header, you can use [class@ClapperGtk.TitleLabel] + * which is used by this widget to build your own implementation instead. + */ + +#include "config.h" + +#include + +#include "clapper-gtk-title-header.h" +#include "clapper-gtk-title-label.h" + +#define DEFAULT_FALLBACK_TO_URI FALSE + +#define GST_CAT_DEFAULT clapper_gtk_title_header_debug +GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); + +struct _ClapperGtkTitleHeader +{ + ClapperGtkLeadContainer parent; + + ClapperGtkTitleLabel *label; +}; + +#define parent_class clapper_gtk_title_header_parent_class +G_DEFINE_TYPE (ClapperGtkTitleHeader, clapper_gtk_title_header, CLAPPER_GTK_TYPE_LEAD_CONTAINER) + +enum +{ + PROP_0, + PROP_CURRENT_TITLE, + PROP_FALLBACK_TO_URI, + PROP_LAST +}; + +static GParamSpec *param_specs[PROP_LAST] = { NULL, }; + +static void +_label_current_title_changed_cb (ClapperGtkTitleLabel *label G_GNUC_UNUSED, + GParamSpec *pspec G_GNUC_UNUSED, ClapperGtkTitleHeader *self) +{ + /* Forward current title changed notify from internal label */ + g_object_notify_by_pspec (G_OBJECT (self), param_specs[PROP_CURRENT_TITLE]); +} + +/** + * clapper_gtk_title_header_new: + * + * Creates a new #ClapperGtkTitleHeader instance. + * + * Returns: a new title header #GtkWidget. + */ +GtkWidget * +clapper_gtk_title_header_new (void) +{ + return g_object_new (CLAPPER_GTK_TYPE_TITLE_HEADER, NULL); +} + +/** + * clapper_gtk_title_header_get_current_title: + * @header: a #ClapperGtkTitleHeader + * + * Get currently displayed title by @header. + * + * Returns: (transfer none): text of title label. + */ +const gchar * +clapper_gtk_title_header_get_current_title (ClapperGtkTitleHeader *self) +{ + g_return_val_if_fail (CLAPPER_GTK_IS_TITLE_HEADER (self), NULL); + + return clapper_gtk_title_label_get_current_title (self->label); +} + +/** + * clapper_gtk_title_header_set_fallback_to_uri: + * @header: a #ClapperGtkTitleHeader + * @enabled: whether enabled + * + * Set whether a [property@Clapper.MediaItem:uri] property should + * be displayed as a header text when no other title could be determined. + */ +void +clapper_gtk_title_header_set_fallback_to_uri (ClapperGtkTitleHeader *self, gboolean enabled) +{ + g_return_if_fail (CLAPPER_GTK_IS_TITLE_HEADER (self)); + + clapper_gtk_title_label_set_fallback_to_uri (self->label, enabled); +} + +/** + * clapper_gtk_title_header_get_fallback_to_uri: + * @header: a #ClapperGtkTitleHeader + * + * Get whether a [property@Clapper.MediaItem:uri] property is going + * be displayed as a header text when no other title could be determined. + */ +gboolean +clapper_gtk_title_header_get_fallback_to_uri (ClapperGtkTitleHeader *self) +{ + g_return_val_if_fail (CLAPPER_GTK_IS_TITLE_HEADER (self), FALSE); + + return clapper_gtk_title_label_get_fallback_to_uri (self->label); +} + +static void +clapper_gtk_title_header_init (ClapperGtkTitleHeader *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); + + clapper_gtk_title_label_set_fallback_to_uri (self->label, DEFAULT_FALLBACK_TO_URI); + + g_object_bind_property (self->label, "fallback-to-uri", + self, "fallback-to-uri", G_BINDING_DEFAULT); + g_signal_connect (self->label, "notify::current-title", + G_CALLBACK (_label_current_title_changed_cb), self); +} + +static void +clapper_gtk_title_header_dispose (GObject *object) +{ + gtk_widget_dispose_template (GTK_WIDGET (object), CLAPPER_GTK_TYPE_TITLE_HEADER); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +clapper_gtk_title_header_get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + ClapperGtkTitleHeader *self = CLAPPER_GTK_TITLE_HEADER_CAST (object); + + switch (prop_id) { + case PROP_CURRENT_TITLE: + g_value_set_string (value, clapper_gtk_title_header_get_current_title (self)); + break; + case PROP_FALLBACK_TO_URI: + g_value_set_boolean (value, clapper_gtk_title_header_get_fallback_to_uri (self)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +clapper_gtk_title_header_set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + ClapperGtkTitleHeader *self = CLAPPER_GTK_TITLE_HEADER_CAST (object); + + switch (prop_id) { + case PROP_FALLBACK_TO_URI: + clapper_gtk_title_header_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_header_class_init (ClapperGtkTitleHeaderClass *klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + GtkWidgetClass *widget_class = (GtkWidgetClass *) klass; + + GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clappergtktitleheader", 0, + "Clapper GTK Title Header"); + + gobject_class->get_property = clapper_gtk_title_header_get_property; + gobject_class->set_property = clapper_gtk_title_header_set_property; + gobject_class->dispose = clapper_gtk_title_header_dispose; + + /** + * ClapperGtkTitleHeader: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); + + /** + * ClapperGtkTitleHeader: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_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-title-header.ui"); + + gtk_widget_class_bind_template_child (widget_class, ClapperGtkTitleHeader, label); + + gtk_widget_class_set_css_name (widget_class, "clapper-gtk-title-header"); +} diff --git a/src/lib/clapper-gtk/clapper-gtk-title-header.h b/src/lib/clapper-gtk/clapper-gtk-title-header.h new file mode 100644 index 00000000..2f918867 --- /dev/null +++ b/src/lib/clapper-gtk/clapper-gtk-title-header.h @@ -0,0 +1,46 @@ +/* Clapper GTK Integration Library + * Copyright (C) 2024 RafaÅ‚ DziÄ™giel + * + * 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. + */ + +#pragma once + +#if !defined(__CLAPPER_GTK_INSIDE__) && !defined(CLAPPER_GTK_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#include +#include +#include + +G_BEGIN_DECLS + +#define CLAPPER_GTK_TYPE_TITLE_HEADER (clapper_gtk_title_header_get_type()) +#define CLAPPER_GTK_TITLE_HEADER_CAST(obj) ((ClapperGtkTitleHeader *)(obj)) + +G_DECLARE_FINAL_TYPE (ClapperGtkTitleHeader, clapper_gtk_title_header, CLAPPER_GTK, TITLE_HEADER, ClapperGtkLeadContainer) + +GtkWidget * clapper_gtk_title_header_new (void); + +const gchar * clapper_gtk_title_header_get_current_title (ClapperGtkTitleHeader *header); + +void clapper_gtk_title_header_set_fallback_to_uri (ClapperGtkTitleHeader *header, gboolean enabled); + +gboolean clapper_gtk_title_header_get_fallback_to_uri (ClapperGtkTitleHeader *header); + +G_END_DECLS diff --git a/src/lib/clapper-gtk/clapper-gtk-title-label.c b/src/lib/clapper-gtk/clapper-gtk-title-label.c new file mode 100644 index 00000000..87ca4f60 --- /dev/null +++ b/src/lib/clapper-gtk/clapper-gtk-title-label.c @@ -0,0 +1,488 @@ +/* Clapper GTK Integration Library + * Copyright (C) 2024 RafaÅ‚ DziÄ™giel + * + * 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 +#include + +#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"); +} diff --git a/src/lib/clapper-gtk/clapper-gtk-title-label.h b/src/lib/clapper-gtk/clapper-gtk-title-label.h new file mode 100644 index 00000000..d5593a77 --- /dev/null +++ b/src/lib/clapper-gtk/clapper-gtk-title-label.h @@ -0,0 +1,50 @@ +/* Clapper GTK Integration Library + * Copyright (C) 2024 RafaÅ‚ DziÄ™giel + * + * 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. + */ + +#pragma once + +#if !defined(__CLAPPER_GTK_INSIDE__) && !defined(CLAPPER_GTK_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#include +#include +#include + +G_BEGIN_DECLS + +#define CLAPPER_GTK_TYPE_TITLE_LABEL (clapper_gtk_title_label_get_type()) +#define CLAPPER_GTK_TITLE_LABEL_CAST(obj) ((ClapperGtkTitleLabel *)(obj)) + +G_DECLARE_FINAL_TYPE (ClapperGtkTitleLabel, clapper_gtk_title_label, CLAPPER_GTK, TITLE_LABEL, GtkWidget) + +GtkWidget * clapper_gtk_title_label_new (void); + +void clapper_gtk_title_label_set_media_item (ClapperGtkTitleLabel *label, ClapperMediaItem *item); + +ClapperMediaItem * clapper_gtk_title_label_get_media_item (ClapperGtkTitleLabel *label); + +const gchar * clapper_gtk_title_label_get_current_title (ClapperGtkTitleLabel *label); + +void clapper_gtk_title_label_set_fallback_to_uri (ClapperGtkTitleLabel *label, gboolean enabled); + +gboolean clapper_gtk_title_label_get_fallback_to_uri (ClapperGtkTitleLabel *label); + +G_END_DECLS diff --git a/src/lib/clapper-gtk/clapper-gtk-toggle-fullscreen-button.c b/src/lib/clapper-gtk/clapper-gtk-toggle-fullscreen-button.c new file mode 100644 index 00000000..90cefc77 --- /dev/null +++ b/src/lib/clapper-gtk/clapper-gtk-toggle-fullscreen-button.c @@ -0,0 +1,154 @@ +/* Clapper GTK Integration Library + * Copyright (C) 2024 RafaÅ‚ DziÄ™giel + * + * 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. + */ + +/** + * ClapperGtkToggleFullscreenButton: + * + * A #GtkButton for toggling fullscreen state. + */ + +#include + +#include "clapper-gtk-toggle-fullscreen-button.h" +#include "clapper-gtk-video.h" + +#define ENTER_FULLSCREEN_ICON_NAME "view-fullscreen-symbolic" +#define LEAVE_FULLSCREEN_ICON_NAME "view-restore-symbolic" + +#define GST_CAT_DEFAULT clapper_gtk_toggle_fullscreen_button_debug +GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); + +struct _ClapperGtkToggleFullscreenButton +{ + GtkButton parent; + + gboolean is_fullscreen; +}; + +#define parent_class clapper_gtk_toggle_fullscreen_button_parent_class +G_DEFINE_TYPE (ClapperGtkToggleFullscreenButton, clapper_gtk_toggle_fullscreen_button, GTK_TYPE_BUTTON) + +static void +_toplevel_state_changed_cb (GdkToplevel *toplevel, + GParamSpec *pspec G_GNUC_UNUSED, ClapperGtkToggleFullscreenButton *self) +{ + GdkToplevelState state = gdk_toplevel_get_state (toplevel); + gboolean is_fullscreen = (state & GDK_TOPLEVEL_STATE_FULLSCREEN); + + if (self->is_fullscreen == is_fullscreen) + return; + + self->is_fullscreen = is_fullscreen; + + GST_DEBUG_OBJECT (self, "Toplevel state changed, fullscreen: %s", + (self->is_fullscreen) ? "yes" : "no"); + + gtk_button_set_icon_name (GTK_BUTTON (self), + (!self->is_fullscreen) ? ENTER_FULLSCREEN_ICON_NAME : LEAVE_FULLSCREEN_ICON_NAME); +} + +/** + * clapper_gtk_toggle_fullscreen_button_new: + * + * Creates a new #ClapperGtkToggleFullscreenButton instance. + * + * Returns: a new toggle fullscreen button #GtkWidget. + */ +GtkWidget * +clapper_gtk_toggle_fullscreen_button_new (void) +{ + return g_object_new (CLAPPER_GTK_TYPE_TOGGLE_FULLSCREEN_BUTTON, NULL); +} + +static void +clapper_gtk_toggle_fullscreen_button_init (ClapperGtkToggleFullscreenButton *self) +{ + gtk_button_set_icon_name (GTK_BUTTON (self), ENTER_FULLSCREEN_ICON_NAME); +} + +static void +clapper_gtk_toggle_fullscreen_button_map (GtkWidget *widget) +{ + ClapperGtkToggleFullscreenButton *self = CLAPPER_GTK_TOGGLE_FULLSCREEN_BUTTON_CAST (widget); + GtkRoot *root; + GdkSurface *surface; + + GST_TRACE_OBJECT (self, "Map"); + + root = gtk_widget_get_root (widget); + surface = gtk_native_get_surface (GTK_NATIVE (root)); + + if (G_LIKELY (GDK_IS_TOPLEVEL (surface))) { + GdkToplevel *toplevel = GDK_TOPLEVEL (surface); + + g_signal_connect (toplevel, "notify::state", + G_CALLBACK (_toplevel_state_changed_cb), self); + _toplevel_state_changed_cb (toplevel, NULL, self); + } + + GTK_WIDGET_CLASS (parent_class)->map (widget); +} + +static void +clapper_gtk_toggle_fullscreen_button_unmap (GtkWidget *widget) +{ + ClapperGtkToggleFullscreenButton *self = CLAPPER_GTK_TOGGLE_FULLSCREEN_BUTTON_CAST (widget); + GtkRoot *root; + GdkSurface *surface; + + GST_TRACE_OBJECT (self, "Unmap"); + + root = gtk_widget_get_root (widget); + surface = gtk_native_get_surface (GTK_NATIVE (root)); + + if (G_LIKELY (GDK_IS_TOPLEVEL (surface))) { + GdkToplevel *toplevel = GDK_TOPLEVEL (surface); + + g_signal_handlers_disconnect_by_func (toplevel, + _toplevel_state_changed_cb, self); + } + + GTK_WIDGET_CLASS (parent_class)->unmap (widget); +} + +static void +clapper_gtk_toggle_fullscreen_button_clicked (GtkButton* button) +{ + GtkWidget *video; + + GST_DEBUG_OBJECT (button, "Clicked"); + + if ((video = gtk_widget_get_ancestor (GTK_WIDGET (button), CLAPPER_GTK_TYPE_VIDEO))) + g_signal_emit_by_name (video, "toggle-fullscreen"); +} + +static void +clapper_gtk_toggle_fullscreen_button_class_init (ClapperGtkToggleFullscreenButtonClass *klass) +{ + GtkWidgetClass *widget_class = (GtkWidgetClass *) klass; + GtkButtonClass *button_class = (GtkButtonClass *) klass; + + GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clappergtktogglefullscreenbutton", 0, + "Clapper GTK Toggle Fullscreen Button"); + + widget_class->map = clapper_gtk_toggle_fullscreen_button_map; + widget_class->unmap = clapper_gtk_toggle_fullscreen_button_unmap; + + button_class->clicked = clapper_gtk_toggle_fullscreen_button_clicked; +} diff --git a/src/lib/clapper-gtk/clapper-gtk-toggle-fullscreen-button.h b/src/lib/clapper-gtk/clapper-gtk-toggle-fullscreen-button.h new file mode 100644 index 00000000..d3f51083 --- /dev/null +++ b/src/lib/clapper-gtk/clapper-gtk-toggle-fullscreen-button.h @@ -0,0 +1,39 @@ +/* Clapper GTK Integration Library + * Copyright (C) 2024 RafaÅ‚ DziÄ™giel + * + * 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. + */ + +#pragma once + +#if !defined(__CLAPPER_GTK_INSIDE__) && !defined(CLAPPER_GTK_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#include +#include + +G_BEGIN_DECLS + +#define CLAPPER_GTK_TYPE_TOGGLE_FULLSCREEN_BUTTON (clapper_gtk_toggle_fullscreen_button_get_type()) +#define CLAPPER_GTK_TOGGLE_FULLSCREEN_BUTTON_CAST(obj) ((ClapperGtkToggleFullscreenButton *)(obj)) + +G_DECLARE_FINAL_TYPE (ClapperGtkToggleFullscreenButton, clapper_gtk_toggle_fullscreen_button, CLAPPER_GTK, TOGGLE_FULLSCREEN_BUTTON, GtkButton) + +GtkWidget * clapper_gtk_toggle_fullscreen_button_new (void); + +G_END_DECLS diff --git a/src/lib/clapper-gtk/clapper-gtk-toggle-play-button.c b/src/lib/clapper-gtk/clapper-gtk-toggle-play-button.c new file mode 100644 index 00000000..376244d7 --- /dev/null +++ b/src/lib/clapper-gtk/clapper-gtk-toggle-play-button.c @@ -0,0 +1,126 @@ +/* Clapper GTK Integration Library + * Copyright (C) 2024 RafaÅ‚ DziÄ™giel + * + * 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. + */ + +/** + * ClapperGtkTogglePlayButton: + * + * A #GtkButton for toggling play/pause of playback. + */ + +#include + +#include "clapper-gtk-toggle-play-button.h" +#include "clapper-gtk-utils.h" + +#define PLAY_ICON_NAME "media-playback-start-symbolic" +#define PAUSE_ICON_NAME "media-playback-pause-symbolic" + +#define GST_CAT_DEFAULT clapper_gtk_toggle_play_button_debug +GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); + +struct _ClapperGtkTogglePlayButton +{ + GtkButton parent; + + GBinding *state_binding; +}; + +#define parent_class clapper_gtk_toggle_play_button_parent_class +G_DEFINE_TYPE (ClapperGtkTogglePlayButton, clapper_gtk_toggle_play_button, GTK_TYPE_BUTTON) + +static gboolean +_transform_state_func (GBinding *binding, const GValue *from_value, + GValue *to_value, ClapperGtkTogglePlayButton *self) +{ + ClapperPlayerState state = g_value_get_enum (from_value); + + GST_DEBUG_OBJECT (self, "Reflecting player state change, now: %i", state); + + switch (state) { + case CLAPPER_PLAYER_STATE_STOPPED: + case CLAPPER_PLAYER_STATE_PAUSED: + g_value_set_string (to_value, PLAY_ICON_NAME); + break; + case CLAPPER_PLAYER_STATE_PLAYING: + g_value_set_string (to_value, PAUSE_ICON_NAME); + break; + default: + return FALSE; // no change + } + + return TRUE; +} + +/** + * clapper_gtk_toggle_play_button_new: + * + * Creates a new #ClapperGtkTogglePlayButton instance. + * + * Returns: a new toggle play button #GtkWidget. + */ +GtkWidget * +clapper_gtk_toggle_play_button_new (void) +{ + return g_object_new (CLAPPER_GTK_TYPE_TOGGLE_PLAY_BUTTON, NULL); +} + +static void +clapper_gtk_toggle_play_button_init (ClapperGtkTogglePlayButton *self) +{ + gtk_button_set_icon_name (GTK_BUTTON (self), PLAY_ICON_NAME); + gtk_actionable_set_action_name (GTK_ACTIONABLE (self), "video.toggle-play"); +} + +static void +clapper_gtk_toggle_play_button_map (GtkWidget *widget) +{ + ClapperGtkTogglePlayButton *self = CLAPPER_GTK_TOGGLE_PLAY_BUTTON_CAST (widget); + ClapperPlayer *player; + + if ((player = clapper_gtk_get_player_from_ancestor (widget))) { + self->state_binding = g_object_bind_property_full (player, "state", + self, "icon-name", G_BINDING_SYNC_CREATE, + (GBindingTransformFunc) _transform_state_func, + NULL, self, NULL); + } + + GTK_WIDGET_CLASS (parent_class)->map (widget); +} + +static void +clapper_gtk_toggle_play_button_unmap (GtkWidget *widget) +{ + ClapperGtkTogglePlayButton *self = CLAPPER_GTK_TOGGLE_PLAY_BUTTON_CAST (widget); + + g_clear_pointer (&self->state_binding, g_binding_unbind); + + GTK_WIDGET_CLASS (parent_class)->unmap (widget); +} + +static void +clapper_gtk_toggle_play_button_class_init (ClapperGtkTogglePlayButtonClass *klass) +{ + GtkWidgetClass *widget_class = (GtkWidgetClass *) klass; + + GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clappergtktoggleplaybutton", 0, + "Clapper GTK Toggle Play Button"); + + widget_class->map = clapper_gtk_toggle_play_button_map; + widget_class->unmap = clapper_gtk_toggle_play_button_unmap; +} diff --git a/src/lib/clapper-gtk/clapper-gtk-toggle-play-button.h b/src/lib/clapper-gtk/clapper-gtk-toggle-play-button.h new file mode 100644 index 00000000..1eec02e0 --- /dev/null +++ b/src/lib/clapper-gtk/clapper-gtk-toggle-play-button.h @@ -0,0 +1,39 @@ +/* Clapper GTK Integration Library + * Copyright (C) 2024 RafaÅ‚ DziÄ™giel + * + * 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. + */ + +#pragma once + +#if !defined(__CLAPPER_GTK_INSIDE__) && !defined(CLAPPER_GTK_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#include +#include + +G_BEGIN_DECLS + +#define CLAPPER_GTK_TYPE_TOGGLE_PLAY_BUTTON (clapper_gtk_toggle_play_button_get_type()) +#define CLAPPER_GTK_TOGGLE_PLAY_BUTTON_CAST(obj) ((ClapperGtkTogglePlayButton *)(obj)) + +G_DECLARE_FINAL_TYPE (ClapperGtkTogglePlayButton, clapper_gtk_toggle_play_button, CLAPPER_GTK, TOGGLE_PLAY_BUTTON, GtkButton) + +GtkWidget * clapper_gtk_toggle_play_button_new (void); + +G_END_DECLS diff --git a/src/lib/clapper-gtk/clapper-gtk-utils-private.h b/src/lib/clapper-gtk/clapper-gtk-utils-private.h new file mode 100644 index 00000000..ca4be335 --- /dev/null +++ b/src/lib/clapper-gtk/clapper-gtk-utils-private.h @@ -0,0 +1,35 @@ +/* Clapper GTK Integration Library + * Copyright (C) 2024 RafaÅ‚ DziÄ™giel + * + * 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. + */ + +#pragma once + +#include "clapper-gtk-utils.h" + +G_BEGIN_DECLS + +G_GNUC_INTERNAL +void clapper_gtk_init_translations (void); + +G_GNUC_INTERNAL +const gchar * clapper_gtk_get_icon_name_for_volume (gfloat volume); + +G_GNUC_INTERNAL +const gchar * clapper_gtk_get_icon_name_for_speed (gfloat speed); + +G_END_DECLS diff --git a/src/lib/clapper-gtk/clapper-gtk-utils.c b/src/lib/clapper-gtk/clapper-gtk-utils.c new file mode 100644 index 00000000..bc0f3364 --- /dev/null +++ b/src/lib/clapper-gtk/clapper-gtk-utils.c @@ -0,0 +1,96 @@ +/* Clapper GTK Integration Library + * Copyright (C) 2024 RafaÅ‚ DziÄ™giel + * + * 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. + */ + +#include "config.h" + +#include + +#include "clapper-gtk-utils-private.h" +#include "clapper-gtk-video.h" + +static gboolean initialized = FALSE; + +/** + * clapper_gtk_get_player_from_ancestor: + * @widget: a #GtkWidget + * + * Get [class@Clapper.Player] used by [class@ClapperGtk.Video] ancestor of @widget. + * + * This utility is a convenience wrapper for calling [method@Gtk.Widget.get_ancestor] + * of type `CLAPPER_GTK_TYPE_VIDEO` and [method@ClapperGtk.Video.get_player] with + * additional %NULL checking and type casting. + * + * This is meant to be used mainly for custom widget development as an easy access to the + * underlying parent [class@Clapper.Player] object. If you want to get the player from + * [class@ClapperGtk.Video] widget itself, use [method@ClapperGtk.Video.get_player] instead. + * + * Rememeber that this function will return %NULL when widget does not have + * a [class@ClapperGtk.Video] ancestor in widget hierarchy (widget is not yet placed). + * + * Returns: (transfer none) (nullable): a #ClapperPlayer from ancestor of a @widget. + */ +ClapperPlayer * +clapper_gtk_get_player_from_ancestor (GtkWidget *widget) +{ + GtkWidget *parent; + ClapperPlayer *player = NULL; + + g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL); + + if ((parent = gtk_widget_get_ancestor (widget, CLAPPER_GTK_TYPE_VIDEO))) + player = clapper_gtk_video_get_player (CLAPPER_GTK_VIDEO_CAST (parent)); + + return player; +} + +void +clapper_gtk_init_translations (void) +{ + if (initialized) + return; + + bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + + initialized = TRUE; +} + +const gchar * +clapper_gtk_get_icon_name_for_volume (gfloat volume) +{ + return (volume <= 0.0f) + ? "audio-volume-muted-symbolic" + : (volume <= 0.3f) + ? "audio-volume-low-symbolic" + : (volume <= 0.7f) + ? "audio-volume-medium-symbolic" + : (volume <= 1.0f) + ? "audio-volume-high-symbolic" + : "audio-volume-overamplified-symbolic"; +} + +const gchar * +clapper_gtk_get_icon_name_for_speed (gfloat speed) +{ + return (speed < 1.0f) + ? "power-profile-power-saver-symbolic" + : (speed == 1.0f) + ? "power-profile-balanced-symbolic" + : "power-profile-performance-symbolic"; +} diff --git a/src/lib/clapper-gtk/clapper-gtk-utils.h b/src/lib/clapper-gtk/clapper-gtk-utils.h new file mode 100644 index 00000000..be5d54de --- /dev/null +++ b/src/lib/clapper-gtk/clapper-gtk-utils.h @@ -0,0 +1,34 @@ +/* Clapper GTK Integration Library + * Copyright (C) 2024 RafaÅ‚ DziÄ™giel + * + * 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. + */ + +#pragma once + +#if !defined(__CLAPPER_GTK_INSIDE__) && !defined(CLAPPER_GTK_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#include +#include + +G_BEGIN_DECLS + +ClapperPlayer * clapper_gtk_get_player_from_ancestor (GtkWidget *widget); + +G_END_DECLS diff --git a/src/lib/clapper-gtk/clapper-gtk-version.h.in b/src/lib/clapper-gtk/clapper-gtk-version.h.in new file mode 100644 index 00000000..17145751 --- /dev/null +++ b/src/lib/clapper-gtk/clapper-gtk-version.h.in @@ -0,0 +1,76 @@ +/* Clapper GTK Integration Library + * Copyright (C) 2024 RafaÅ‚ DziÄ™giel + * + * 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. + */ + +#pragma once + +#if !defined(__CLAPPER_GTK_INSIDE__) && !defined(CLAPPER_GTK_COMPILATION) +#error "Only can be included directly." +#endif + +/** + * CLAPPER_GTK_MAJOR_VERSION: + * + * ClapperGtk major version component + */ +#define CLAPPER_GTK_MAJOR_VERSION (@CLAPPER_GTK_MAJOR_VERSION@) + +/** + * CLAPPER_GTK_MINOR_VERSION: + * + * ClapperGtk minor version component + */ +#define CLAPPER_GTK_MINOR_VERSION (@CLAPPER_GTK_MINOR_VERSION@) + +/** + * CLAPPER_GTK_MICRO_VERSION: + * + * ClapperGtk micro version component + */ +#define CLAPPER_GTK_MICRO_VERSION (@CLAPPER_GTK_MICRO_VERSION@) + +/** + * CLAPPER_GTK_VERSION: + * + * ClapperGtk version + */ +#define CLAPPER_GTK_VERSION (@CLAPPER_GTK_VERSION@) + +/** + * CLAPPER_GTK_VERSION_S: + * + * ClapperGtk version, encoded as a string + */ +#define CLAPPER_GTK_VERSION_S "@CLAPPER_GTK_VERSION@" + +#define CLAPPER_GTK_ENCODE_VERSION(major,minor,micro) \ + ((major) << 24 | (minor) << 16 | (micro) << 8) + +/** + * CLAPPER_GTK_VERSION_HEX: + * + * ClapperGtk version, encoded as an hexadecimal number, useful for integer comparisons. + */ +#define CLAPPER_GTK_VERSION_HEX \ + (CLAPPER_GTK_ENCODE_VERSION (CLAPPER_GTK_MAJOR_VERSION, CLAPPER_GTK_MINOR_VERSION, CLAPPER_GTK_MICRO_VERSION)) + +#define CLAPPER_GTK_CHECK_VERSION(major, minor, micro) \ + (CLAPPER_GTK_MAJOR_VERSION > (major) || \ + (CLAPPER_GTK_MAJOR_VERSION == (major) && CLAPPER_GTK_MINOR_VERSION > (minor)) || \ + (CLAPPER_GTK_MAJOR_VERSION == (major) && CLAPPER_GTK_MINOR_VERSION == (minor) && \ + CLAPPER_GTK_MICRO_VERSION >= (micro))) diff --git a/src/lib/clapper-gtk/clapper-gtk-video.c b/src/lib/clapper-gtk/clapper-gtk-video.c new file mode 100644 index 00000000..f65ca461 --- /dev/null +++ b/src/lib/clapper-gtk/clapper-gtk-video.c @@ -0,0 +1,1601 @@ +/* Clapper GTK Integration Library + * Copyright (C) 2024 RafaÅ‚ DziÄ™giel + * + * 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. + */ + +/** + * ClapperGtkVideo: + * + * A ready to be used GTK video widget implementing Clapper API. + * + * #ClapperGtkVideo is the main widget exposed by `ClapperGtk` API. It both displays + * videos played by [class@Clapper.Player] (exposed as its property) and manages + * revealing and fading of any additional widgets overlaid on top of it. + * + * Other widgets provided by `ClapperGtk` library, once placed anywhere on video + * (including nesting within another widget like [class@Gtk.Box]) will automatically + * control #ClapperGtkVideo they were overlaid on top of. This allows to freely create + * custom playback control panels best suited for specific application. Additionally, + * pre-made widgets such as [class@ClapperGtk.SimpleControls] are also available. + * + * # Basic usage + * + * A typical use case is to embed video widget as part of your app where video playback + * is needed. Get the [class@Clapper.Player] belonging to the video widget and start adding + * new [class@Clapper.MediaItem] items to the [class@Clapper.Queue] for playback. + * For more information please refer to the Clapper playback library documentation. + * + * #ClapperGtkVideo can automatically take care of revealing and later fading overlaid + * content when interacting with the video. To do this, simply add your widgets with + * [method@ClapperGtk.Video.add_fading_overlay]. If you want to display some static content + * on top of video (or take care of visibility within overlaid widget itself) you can add + * it to the video as a normal overlay with [method@ClapperGtk.Video.add_overlay]. + * + * # Actions + * + * #ClapperGtkVideo defines a set of built-in actions: + * + * ```yaml + * - "video.toggle-play": toggle play/pause + * - "video.play": start/resume playback + * - "video.pause": pause playback + * - "video.stop": stop playback + * - "video.seek": seek to position (variant "d") + * - "video.seek-custom": seek to position using seek method (variant "(di)") + * - "video.toggle-mute": toggle mute state + * - "video.set-mute": set mute state (variant "b") + * - "video.volume-up": increase volume by 2% + * - "video.volume-down": decrease volume by 2% + * - "video.set-volume": set volume to specified value (variant "d") + * - "video.speed-up": increase speed (from 0.05x - 2x range to nearest quarter) + * - "video.speed-down": decrease speed (from 0.05x - 2x range to nearest quarter) + * - "video.set-speed": set speed to specified value (variant "d") + * - "video.previous-item": select previous item in queue + * - "video.next-item": select next item in queue + * - "video.select-item": select item at specified index in queue (variant "u") + * ``` + * + * # ClapperGtkVideo as GtkBuildable + * + * #ClapperGtkVideo implementation of the [iface@Gtk.Buildable] interface supports + * placing children as either normal overlay by specifying `overlay` or a fading + * one by specifying `fading-overlay` as the `type` attribute of a `` element. + * Position of overlaid content is determined by `valign/halign` properties. + * + * ```xml + * + * + * + * start + * + * + * + * + * end + * + * + * + * ``` + */ + +#include "config.h" + +#include + +#include "clapper-gtk-enums.h" +#include "clapper-gtk-video.h" +#include "clapper-gtk-lead-container.h" +#include "clapper-gtk-status-private.h" +#include "clapper-gtk-buffering-animation-private.h" + +#define PERCENTAGE_ROUND(a) (round ((gdouble) a / 0.01) * 0.01) + +#define DEFAULT_FADE_DELAY 3000 +#define DEFAULT_TOUCH_FADE_DELAY 5000 +#define DEFAULT_AUTO_INHIBIT FALSE + +#define MIN_MOTION_DELAY 100000 + +#define GST_CAT_DEFAULT clapper_gtk_video_debug +GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); + +struct _ClapperGtkVideo +{ + GtkWidget parent; + + GtkWidget *overlay; + GtkWidget *status; + GtkWidget *buffering_animation; + + GtkGesture *touch_gesture; + GtkGesture *click_gesture; + + /* Props */ + ClapperPlayer *player; + guint fade_delay; + guint touch_fade_delay; + gboolean auto_inhibit; + + GPtrArray *overlays; + GPtrArray *fading_overlays; + + gboolean buffering; + gboolean showing_status; + + gulong notify_revealed_id; + guint fade_timeout; + gboolean reveal, revealed; + + guint inhibit_cookie; + + /* Current pointer coords and type */ + gdouble x, y; + gboolean is_touch; + gboolean touching; + gint64 last_motion_time; + gboolean pending_toggle_play; +}; + +static void +clapper_gtk_video_add_child (GtkBuildable *buildable, + GtkBuilder *builder, GObject *child, const char *type) +{ + if (GTK_IS_WIDGET (child)) { + if (g_strcmp0 (type, "overlay") == 0) + clapper_gtk_video_add_overlay (CLAPPER_GTK_VIDEO (buildable), GTK_WIDGET (child)); + else if (g_strcmp0 (type, "fading-overlay") == 0) + clapper_gtk_video_add_fading_overlay (CLAPPER_GTK_VIDEO (buildable), GTK_WIDGET (child)); + else + GTK_BUILDER_WARN_INVALID_CHILD_TYPE (buildable, type); + } 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_video_add_child; +} + +#define parent_class clapper_gtk_video_parent_class +G_DEFINE_TYPE_WITH_CODE (ClapperGtkVideo, clapper_gtk_video, GTK_TYPE_WIDGET, + G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, _buildable_iface_init)) + +enum +{ + PROP_0, + PROP_PLAYER, + PROP_FADE_DELAY, + PROP_TOUCH_FADE_DELAY, + PROP_AUTO_INHIBIT, + PROP_INHIBITED, + PROP_LAST +}; + +enum +{ + SIGNAL_TOGGLE_FULLSCREEN, + SIGNAL_SEEK_REQUEST, + SIGNAL_LAST +}; + +static gboolean provider_added = FALSE; +static GParamSpec *param_specs[PROP_LAST] = { NULL, }; +static guint signals[SIGNAL_LAST] = { 0, }; + +static void +toggle_play_action_cb (GtkWidget *widget, const gchar *action_name, GVariant *parameter) +{ + ClapperGtkVideo *self = CLAPPER_GTK_VIDEO_CAST (widget); + ClapperPlayer *player = clapper_gtk_video_get_player (self); + + switch (clapper_player_get_state (player)) { + case CLAPPER_PLAYER_STATE_PLAYING: + clapper_player_pause (player); + break; + case CLAPPER_PLAYER_STATE_STOPPED: + case CLAPPER_PLAYER_STATE_PAUSED: + clapper_player_play (player); + break; + default: + break; + } +} + +static void +play_action_cb (GtkWidget *widget, const gchar *action_name, GVariant *parameter) +{ + ClapperGtkVideo *self = CLAPPER_GTK_VIDEO_CAST (widget); + ClapperPlayer *player = clapper_gtk_video_get_player (self); + + clapper_player_play (player); +} + +static void +pause_action_cb (GtkWidget *widget, const gchar *action_name, GVariant *parameter) +{ + ClapperGtkVideo *self = CLAPPER_GTK_VIDEO_CAST (widget); + ClapperPlayer *player = clapper_gtk_video_get_player (self); + + clapper_player_pause (player); +} + +static void +stop_action_cb (GtkWidget *widget, const gchar *action_name, GVariant *parameter) +{ + ClapperGtkVideo *self = CLAPPER_GTK_VIDEO_CAST (widget); + ClapperPlayer *player = clapper_gtk_video_get_player (self); + + clapper_player_stop (player); +} + +static void +seek_action_cb (GtkWidget *widget, const gchar *action_name, GVariant *parameter) +{ + ClapperGtkVideo *self = CLAPPER_GTK_VIDEO_CAST (widget); + ClapperPlayer *player = clapper_gtk_video_get_player (self); + gdouble position = g_variant_get_double (parameter); + + clapper_player_seek (player, position); +} + +static void +seek_custom_action_cb (GtkWidget *widget, const gchar *action_name, GVariant *parameter) +{ + ClapperGtkVideo *self = CLAPPER_GTK_VIDEO_CAST (widget); + ClapperPlayer *player = clapper_gtk_video_get_player (self); + ClapperPlayerSeekMethod method = CLAPPER_PLAYER_SEEK_METHOD_NORMAL; + gdouble position = 0; + + g_variant_get (parameter, "(di)", &position, &method); + clapper_player_seek_custom (player, position, method); +} + +static void +toggle_mute_action_cb (GtkWidget *widget, const gchar *action_name, GVariant *parameter) +{ + ClapperGtkVideo *self = CLAPPER_GTK_VIDEO_CAST (widget); + ClapperPlayer *player = clapper_gtk_video_get_player (self); + + clapper_player_set_mute (player, !clapper_player_get_mute (player)); +} + +static void +set_mute_action_cb (GtkWidget *widget, const gchar *action_name, GVariant *parameter) +{ + ClapperGtkVideo *self = CLAPPER_GTK_VIDEO_CAST (widget); + ClapperPlayer *player = clapper_gtk_video_get_player (self); + gboolean mute = g_variant_get_boolean (parameter); + + clapper_player_set_mute (player, mute); +} + +static void +volume_up_action_cb (GtkWidget *widget, const gchar *action_name, GVariant *parameter) +{ + ClapperGtkVideo *self = CLAPPER_GTK_VIDEO_CAST (widget); + ClapperPlayer *player = clapper_gtk_video_get_player (self); + gdouble volume = (clapper_player_get_volume (player) + 0.02); + + if (volume > 2.0) + volume = 2.0; + + clapper_player_set_volume (player, PERCENTAGE_ROUND (volume)); +} + +static void +volume_down_action_cb (GtkWidget *widget, const gchar *action_name, GVariant *parameter) +{ + ClapperGtkVideo *self = CLAPPER_GTK_VIDEO_CAST (widget); + ClapperPlayer *player = clapper_gtk_video_get_player (self); + gdouble volume = (clapper_player_get_volume (player) - 0.02); + + if (volume < 0) + volume = 0; + + clapper_player_set_volume (player, PERCENTAGE_ROUND (volume)); +} + +static void +set_volume_action_cb (GtkWidget *widget, const gchar *action_name, GVariant *parameter) +{ + ClapperGtkVideo *self = CLAPPER_GTK_VIDEO_CAST (widget); + ClapperPlayer *player = clapper_gtk_video_get_player (self); + gdouble volume = g_variant_get_double (parameter); + + clapper_player_set_volume (player, volume); +} + +static void +speed_up_action_cb (GtkWidget *widget, const gchar *action_name, GVariant *parameter) +{ + ClapperGtkVideo *self = CLAPPER_GTK_VIDEO_CAST (widget); + ClapperPlayer *player = clapper_gtk_video_get_player (self); + gdouble dest, speed = clapper_player_get_speed (player); + + if (speed >= 2.0) + return; + + dest = 0.25; + while (speed >= dest) + dest += 0.25; + + if (dest > 2.0) + dest = 2.0; + + clapper_player_set_speed (player, dest); +} + +static void +speed_down_action_cb (GtkWidget *widget, const gchar *action_name, GVariant *parameter) +{ + ClapperGtkVideo *self = CLAPPER_GTK_VIDEO_CAST (widget); + ClapperPlayer *player = clapper_gtk_video_get_player (self); + gdouble dest, speed = clapper_player_get_speed (player); + + if (speed <= 0.05) + return; + + dest = 2.0; + while (speed <= dest) + dest -= 0.25; + + if (dest < 0.05) + dest = 0.05; + + clapper_player_set_speed (player, dest); +} + +static void +set_speed_action_cb (GtkWidget *widget, const gchar *action_name, GVariant *parameter) +{ + ClapperGtkVideo *self = CLAPPER_GTK_VIDEO_CAST (widget); + ClapperPlayer *player = clapper_gtk_video_get_player (self); + gdouble speed = g_variant_get_double (parameter); + + clapper_player_set_speed (player, speed); +} + +static void +previous_item_action_cb (GtkWidget *widget, const gchar *action_name, GVariant *parameter) +{ + ClapperGtkVideo *self = CLAPPER_GTK_VIDEO_CAST (widget); + ClapperPlayer *player = clapper_gtk_video_get_player (self); + + clapper_queue_select_previous_item (clapper_player_get_queue (player)); +} + +static void +next_item_action_cb (GtkWidget *widget, const gchar *action_name, GVariant *parameter) +{ + ClapperGtkVideo *self = CLAPPER_GTK_VIDEO_CAST (widget); + ClapperPlayer *player = clapper_gtk_video_get_player (self); + + clapper_queue_select_next_item (clapper_player_get_queue (player)); +} + +static void +select_item_action_cb (GtkWidget *widget, const gchar *action_name, GVariant *parameter) +{ + ClapperGtkVideo *self = CLAPPER_GTK_VIDEO_CAST (widget); + ClapperPlayer *player = clapper_gtk_video_get_player (self); + guint index = g_variant_get_uint32 (parameter); + + clapper_queue_select_index (clapper_player_get_queue (player), index); +} + +static void +_set_reveal_fading_overlays (ClapperGtkVideo *self, gboolean reveal) +{ + GdkCursor *cursor = gdk_cursor_new_from_name ((reveal) ? "default" : "none", NULL); + guint i; + + self->reveal = reveal; + GST_LOG_OBJECT (self, "%s requested", (self->reveal) ? "Reveal" : "Fade"); + + gtk_widget_set_cursor (GTK_WIDGET (self), cursor); + g_object_unref (cursor); + + for (i = 0; i < self->fading_overlays->len; ++i) { + GtkRevealer *revealer = (GtkRevealer *) g_ptr_array_index (self->fading_overlays, i); + + if (reveal) + gtk_widget_set_visible ((GtkWidget *) revealer, TRUE); + + gtk_revealer_set_reveal_child (revealer, reveal); + } +} + +static inline gboolean +_is_on_leading_overlay (ClapperGtkVideo *self, ClapperGtkVideoActionMask blocked_action) +{ + GtkWidget *video = (GtkWidget *) self; + GtkWidget *tmp_widget = gtk_widget_pick (video, self->x, self->y, GTK_PICK_DEFAULT); + gboolean is_leading = FALSE; + + GST_LOG_OBJECT (self, "Checking if is on leading overlay..."); + + while (tmp_widget && tmp_widget != video) { + if (CLAPPER_GTK_IS_LEAD_CONTAINER (tmp_widget)) { + ClapperGtkLeadContainer *lead_container = CLAPPER_GTK_LEAD_CONTAINER_CAST (tmp_widget); + + if (clapper_gtk_lead_container_get_leading (lead_container) + && (clapper_gtk_lead_container_get_blocked_actions (lead_container) & blocked_action)) { + is_leading = TRUE; + break; + } + } + tmp_widget = gtk_widget_get_parent (tmp_widget); + } + + GST_LOG_OBJECT (self, "Is on leading overlay: %s", (is_leading) ? "yes" : "no"); + + return is_leading; +} + +static inline gboolean +_determine_can_fade (ClapperGtkVideo *self) +{ + GtkWidget *video = (GtkWidget *) self; + GtkRoot *root; + GtkNative *native, *child_native; + GtkWidget *focus_child; + gboolean in_fading_overlay = FALSE; + + GST_LOG_OBJECT (self, "Checking if overlays can fade..."); + + if (self->is_touch) { + if (self->touching) { + GST_LOG_OBJECT (self, "Cannot fade while interacting with touchscreen"); + return FALSE; + } + } else if (self->x > 0 && self->y > 0) { + GtkWidget *tmp_widget = gtk_widget_pick (video, self->x, self->y, GTK_PICK_DEFAULT); + guint i; + + if (!tmp_widget) { + GST_LOG_OBJECT (self, "Can fade, since no widget under pointer"); + return TRUE; + } + + for (i = 0; i < self->fading_overlays->len; ++i) { + GtkWidget *revealer = (GtkWidget *) g_ptr_array_index (self->fading_overlays, i); + + if (tmp_widget == revealer || gtk_widget_is_ancestor (tmp_widget, revealer)) { + in_fading_overlay = TRUE; + break; + } + } + + if (!in_fading_overlay) { + GST_LOG_OBJECT (self, "Can fade, since pointer not within fading overlay"); + return TRUE; + } + + while (tmp_widget && tmp_widget != video) { + GtkStateFlags state_flags = gtk_widget_get_state_flags (tmp_widget); + + if (GTK_IS_ACTIONABLE (tmp_widget) + && (state_flags & (GTK_STATE_FLAG_PRELIGHT | GTK_STATE_FLAG_ACTIVE))) { + GST_LOG_OBJECT (self, "Cannot fade while on activatable widget"); + return FALSE; + } + if ((state_flags & GTK_STATE_FLAG_DROP_ACTIVE)) { + GST_LOG_OBJECT (self, "Cannot fade on drop-active widget"); + return FALSE; + } + if (GTK_IS_ACCESSIBLE (tmp_widget) && gtk_widget_get_can_target (tmp_widget)) { + GtkAccessibleRole role = gtk_accessible_get_accessible_role ((GtkAccessible *) tmp_widget); + + switch (role) { + case GTK_ACCESSIBLE_ROLE_LIST: + GST_LOG_OBJECT (self, "Cannot fade while browsing list"); + return FALSE; + case GTK_ACCESSIBLE_ROLE_SLIDER: + case GTK_ACCESSIBLE_ROLE_SCROLLBAR: + GST_LOG_OBJECT (self, "Cannot fade while on slider/scrollbar"); + return FALSE; + default: + break; + } + } + + tmp_widget = gtk_widget_get_parent (tmp_widget); + }; + } + + root = gtk_widget_get_root (video); + + if (G_UNLIKELY (root == NULL)) + return FALSE; + + focus_child = gtk_root_get_focus (root); + + if (!focus_child + || !gtk_widget_has_focus (focus_child) + || !gtk_widget_is_ancestor (focus_child, video)) { + GST_LOG_OBJECT (self, "Can fade, since no focused child in video"); + return TRUE; + } + + native = gtk_widget_get_native (video); + child_native = gtk_widget_get_native (focus_child); + + if (native != child_native) { + GST_LOG_OBJECT (self, "Cannot fade while another surface is open"); + return FALSE; + } + + GST_LOG_OBJECT (self, "Can fade"); + return TRUE; +} + +static void +_fade_overlay_delay_cb (ClapperGtkVideo *self) +{ + GST_LOG_OBJECT (self, "Fade handler reached"); + self->fade_timeout = 0; + + if (self->reveal) { + gboolean can_fade = _determine_can_fade (self); + + GST_DEBUG_OBJECT (self, "Can fade overlays: %s", (can_fade) ? "yes" : "no"); + + if (can_fade) + _set_reveal_fading_overlays (self, FALSE); + } +} + +static void +_reset_fade_timeout (ClapperGtkVideo *self) +{ + GST_TRACE_OBJECT (self, "Fade timeout reset"); + + g_clear_handle_id (&self->fade_timeout, g_source_remove); + self->fade_timeout = g_timeout_add_once ( + (self->is_touch) ? self->touch_fade_delay : self->fade_delay, + (GSourceOnceFunc) _fade_overlay_delay_cb, self); +} + +static void +_window_is_active_cb (GtkWindow *window, + GParamSpec *pspec G_GNUC_UNUSED, ClapperGtkVideo *self) +{ + gboolean active = gtk_window_is_active (window); + + GST_DEBUG_OBJECT (self, "Window is now %sactive", + (active) ? "" : "in"); + + if (!active) { + /* Needs to set when drag starts during touch, + * we do not get touch release then */ + self->touching = FALSE; + + /* Ensure our overlays will fade eventually */ + if (self->revealed && !self->fade_timeout) + _reset_fade_timeout (self); + } +} + +static void +_handle_motion (ClapperGtkVideo *self, GtkEventController *controller, gdouble x, gdouble y) +{ + gint64 now; + + /* Start with points comparison as its faster, + * otherwise we will check if threshold exceeded */ + if (self->x == x && self->y == y) + return; + + now = g_get_monotonic_time (); + + /* We do not want to reset timeout too often + * (especially on high refresh rate screens). */ + if (now - self->last_motion_time >= MIN_MOTION_DELAY) { + GdkDevice *device = gtk_event_controller_get_current_event_device (controller); + gboolean is_threshold = (ABS (self->x - x) > 1 || ABS (self->y - y) > 1); + + self->x = x; + self->y = y; + self->is_touch = (device && gdk_device_get_source (device) == GDK_SOURCE_TOUCHSCREEN); + + if (is_threshold) { + if (!self->reveal && !_is_on_leading_overlay (self, CLAPPER_GTK_VIDEO_ACTION_REVEAL_OVERLAYS)) + _set_reveal_fading_overlays (self, TRUE); + + /* Extend time until fade */ + if (self->revealed) + _reset_fade_timeout (self); + } + + self->last_motion_time = now; + } +} + +static void +_handle_motion_leave (ClapperGtkVideo *self) +{ + GST_LOG_OBJECT (self, "Motion leave"); + + /* On leave we only reset coords to let overlays fade, + * device is not expected to change here */ + self->x = -1; + self->y = -1; + + /* Ensure our overlays will fade eventually */ + if (self->revealed && !self->fade_timeout) + _reset_fade_timeout (self); +} + +static void +motion_enter_cb (GtkEventControllerMotion *motion, + gdouble x, gdouble y, ClapperGtkVideo *self) +{ + GdkDevice *device = gtk_event_controller_get_current_event_device (GTK_EVENT_CONTROLLER (motion)); + + /* XXX: We do not update x/y coords here in order to not mislead us + * that we are not on non-fading overlay when another surface is open */ + + self->is_touch = (device && gdk_device_get_source (device) == GDK_SOURCE_TOUCHSCREEN); + + /* Tap to reveal is handled elsewhere */ + if (self->is_touch) + return; + + if (!self->reveal && !_is_on_leading_overlay (self, CLAPPER_GTK_VIDEO_ACTION_REVEAL_OVERLAYS)) + _set_reveal_fading_overlays (self, TRUE); + + /* Extend time until fade */ + if (self->revealed) + _reset_fade_timeout (self); +} + +static void +motion_cb (GtkEventControllerMotion *motion, + gdouble x, gdouble y, ClapperGtkVideo *self) +{ + _handle_motion (self, GTK_EVENT_CONTROLLER (motion), x, y); +} + +static void +motion_leave_cb (GtkEventControllerMotion *motion, ClapperGtkVideo *self) +{ + _handle_motion_leave (self); +} + +static void +drop_motion_cb (GtkDropControllerMotion *drop_motion, + gdouble x, gdouble y, ClapperGtkVideo *self) +{ + /* We do not actually support D&D here, just want to track + * drop motion events from it and reveal overlays as one + * or more widgets overlaid may support current drop */ + + _handle_motion (self, GTK_EVENT_CONTROLLER (drop_motion), x, y); +} + +static void +drop_motion_leave_cb (GtkDropControllerMotion *drop_motion, ClapperGtkVideo *self) +{ + _handle_motion_leave (self); +} + +static void +left_click_pressed_cb (GtkGestureClick *click, gint n_press, + gdouble x, gdouble y, ClapperGtkVideo *self) +{ + GdkDevice *device; + + GST_LOG_OBJECT (self, "Left click pressed"); + + /* Need to always clear click timeout, + * so we will not pause after double click */ + self->pending_toggle_play = FALSE; + + device = gtk_gesture_get_device (GTK_GESTURE (click)); + + self->x = x; + self->y = y; + self->is_touch = (device && gdk_device_get_source (device) == GDK_SOURCE_TOUCHSCREEN); +} + +static gboolean +_touch_in_lr_area (ClapperGtkVideo *self, gboolean *forward) +{ + gint video_w = gtk_widget_get_width (GTK_WIDGET (self)); + gdouble area_w = (video_w / 4.); + gboolean in_area; + + if ((in_area = (self->x <= area_w))) { + if (forward) + *forward = FALSE; + } else if ((in_area = (self->x >= video_w - area_w))) { + if (forward) + *forward = TRUE; + } + + if (in_area && forward) + *forward ^= (gtk_widget_get_default_direction () == GTK_TEXT_DIR_RTL); + + GST_LOG_OBJECT (self, "Touch in area: %s (x: %.2lf, video_w: %i, area_w: %.0lf)", + (in_area) ? "yes" : "no", self->x, video_w, area_w); + + return in_area; +} + +static inline void +_handle_single_click (ClapperGtkVideo *self, GtkGestureClick *click) +{ + GdkDevice *device = gtk_gesture_get_device (GTK_GESTURE (click)); + + /* FIXME: Try GstNavigation first and do below logic only when not handled + * by upstream elements (maybe use sequence claiming for that?) */ + + switch (gdk_device_get_source (device)) { + case GDK_SOURCE_TOUCHSCREEN: + /* First tap should only reveal overlays if fading/faded */ + if (!self->reveal && !_is_on_leading_overlay (self, CLAPPER_GTK_VIDEO_ACTION_REVEAL_OVERLAYS)) { + _set_reveal_fading_overlays (self, TRUE); + gtk_gesture_set_state (GTK_GESTURE (click), GTK_EVENT_SEQUENCE_CLAIMED); + break; + } + G_GNUC_FALLTHROUGH; + default: + if (!_is_on_leading_overlay (self, CLAPPER_GTK_VIDEO_ACTION_TOGGLE_PLAY)) { + self->pending_toggle_play = TRUE; + gtk_gesture_set_state (GTK_GESTURE (click), GTK_EVENT_SEQUENCE_CLAIMED); + } + break; + } +} + +static inline void +_handle_double_click (ClapperGtkVideo *self, GtkGestureClick *click) +{ + gboolean handled = FALSE; + + if (self->is_touch) { + gboolean forward = FALSE; + + if (_touch_in_lr_area (self, &forward) + && !_is_on_leading_overlay (self, CLAPPER_GTK_VIDEO_ACTION_SEEK_REQUEST) + && g_signal_handler_find (self, G_SIGNAL_MATCH_ID, + signals[SIGNAL_SEEK_REQUEST], 0, NULL, NULL, NULL) != 0) { + g_signal_emit (self, signals[SIGNAL_SEEK_REQUEST], 0, forward); + handled = TRUE; + } + } + + if (!handled) { + if ((handled = !_is_on_leading_overlay (self, CLAPPER_GTK_VIDEO_ACTION_TOGGLE_FULLSCREEN))) + g_signal_emit (self, signals[SIGNAL_TOGGLE_FULLSCREEN], 0); + } + + if (handled) + gtk_gesture_set_state (GTK_GESTURE (click), GTK_EVENT_SEQUENCE_CLAIMED); +} + +static inline void +_handle_nth_click (ClapperGtkVideo *self, GtkGestureClick *click) +{ + gboolean forward = FALSE; + + if (_touch_in_lr_area (self, &forward) + && !_is_on_leading_overlay (self, CLAPPER_GTK_VIDEO_ACTION_SEEK_REQUEST)) { + g_signal_emit (self, signals[SIGNAL_SEEK_REQUEST], 0, forward); + gtk_gesture_set_state (GTK_GESTURE (click), GTK_EVENT_SEQUENCE_CLAIMED); + } +} + +static void +left_click_released_cb (GtkGestureClick *click, gint n_press, + gdouble x, gdouble y, ClapperGtkVideo *self) +{ + GST_LOG_OBJECT (self, "Left click released"); + + if (self->x < 0 || self->y < 0) { + GST_LOG_OBJECT (self, "Ignoring click release outside of video"); + return; + } + + self->x = x; + self->y = y; + + switch (n_press) { + case 1: + _handle_single_click (self, click); + break; + case 2: + _handle_double_click (self, click); + break; + default: + _handle_nth_click (self, click); + break; + } + + /* Keep fading overlays revealed while clicking/tapping on video */ + if (self->revealed) + _reset_fade_timeout (self); +} + +static void +left_click_stopped_cb (GtkGestureClick *click, ClapperGtkVideo *self) +{ + GST_LOG_OBJECT (self, "Left click stopped"); + + if (self->pending_toggle_play) { + toggle_play_action_cb (GTK_WIDGET (self), NULL, NULL); + self->pending_toggle_play = FALSE; + } +} + +static void +touch_pressed_cb (GtkGestureClick *click, gint n_press, + gdouble x, gdouble y, ClapperGtkVideo *self) +{ + GST_LOG_OBJECT (self, "Touch pressed"); + + self->is_touch = TRUE; + self->touching = TRUE; + + if (self->revealed) + _reset_fade_timeout (self); +} + +static void +touch_released_cb (GtkGestureClick *click, gint n_press, + gdouble x, gdouble y, ClapperGtkVideo *self) +{ + GST_LOG_OBJECT (self, "Touch released"); + + self->touching = FALSE; + + /* Ensure our overlays will fade eventually */ + if (self->revealed) + _reset_fade_timeout (self); +} + +static void +_ensure_css_provider (void) +{ + GdkDisplay *display; + + if (provider_added) + return; + + display = gdk_display_get_default (); + + if (G_LIKELY (display != NULL)) { + GtkCssProvider *provider = gtk_css_provider_new (); + gtk_css_provider_load_from_resource (provider, + CLAPPER_GTK_RESOURCE_PREFIX "/css/styles.css"); + + gtk_style_context_add_provider_for_display (display, + (GtkStyleProvider *) provider, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION - 1); + g_object_unref (provider); + + provider_added = TRUE; + } +} + +static inline void +_set_inhibit_session (ClapperGtkVideo *self, gboolean inhibit) +{ + GtkRoot *root; + GApplication *app; + gboolean inhibited = (self->inhibit_cookie != 0); + + if (inhibited == inhibit) + return; + + GST_DEBUG_OBJECT (self, "Trying to %sinhibit session...", (inhibit) ? "" : "un"); + + root = gtk_widget_get_root (GTK_WIDGET (self)); + + if (!root && !GTK_IS_WINDOW (root)) { + GST_WARNING_OBJECT (self, "Cannot %sinhibit session " + "without root window", (inhibit) ? "" : "un"); + return; + } + + /* NOTE: Not using application from window prop, + * as it goes away early when unrooting */ + app = g_application_get_default (); + + if (!app && !GTK_IS_APPLICATION (app)) { + GST_WARNING_OBJECT (self, "Cannot %sinhibit session " + "without window application set", (inhibit) ? "" : "un"); + return; + } + + if (inhibited) { + gtk_application_uninhibit (GTK_APPLICATION (app), self->inhibit_cookie); + self->inhibit_cookie = 0; + } + if (inhibit) { + self->inhibit_cookie = gtk_application_inhibit (GTK_APPLICATION (app), + GTK_WINDOW (root), GTK_APPLICATION_INHIBIT_IDLE, + "Video is playing"); + } + + GST_DEBUG_OBJECT (self, "Session %sinhibited", (inhibit) ? "" : "un"); + g_object_notify_by_pspec (G_OBJECT (self), param_specs[PROP_INHIBITED]); +} + +static inline void +_set_buffering_animation_enabled (ClapperGtkVideo *self, gboolean enabled) +{ + ClapperGtkBufferingAnimation *animation; + + if (self->buffering == enabled) + return; + + animation = CLAPPER_GTK_BUFFERING_ANIMATION_CAST (self->buffering_animation); + gtk_widget_set_visible (self->buffering_animation, enabled); + + if (enabled) + clapper_gtk_buffering_animation_start (animation); + else + clapper_gtk_buffering_animation_stop (animation); + + self->buffering = enabled; +} + +static void +_player_state_changed_cb (ClapperPlayer *player, + GParamSpec *pspec G_GNUC_UNUSED, ClapperGtkVideo *self) +{ + ClapperPlayerState state = clapper_player_get_state (player); + + if (self->auto_inhibit) + _set_inhibit_session (self, state == CLAPPER_PLAYER_STATE_PLAYING); + + _set_buffering_animation_enabled (self, state == CLAPPER_PLAYER_STATE_BUFFERING); +} + +static void +_video_sink_changed_cb (ClapperPlayer *player, + GParamSpec *pspec G_GNUC_UNUSED, ClapperGtkVideo *self) +{ + GstElement *vsink = clapper_player_get_video_sink (player); + GtkWidget *child = NULL; + + GST_DEBUG_OBJECT (self, "Video sink changed"); + + if (vsink) { + GParamSpec *pspec; + + if ((pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (vsink), "widget")) + && pspec->value_type == GTK_TYPE_WIDGET) { + g_object_get (vsink, "widget", &child, NULL); + GST_DEBUG_OBJECT (self, "Video sink provides a widget"); + } else if ((pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (vsink), "paintable")) + && pspec->value_type == GDK_TYPE_PAINTABLE) { + GdkPaintable *paintable = NULL; + + g_object_get (vsink, "paintable", &paintable, NULL); + GST_DEBUG_OBJECT (self, "Video sink provides a paintable"); + + child = gtk_picture_new (); + gtk_picture_set_paintable (GTK_PICTURE (child), paintable); + + g_object_unref (paintable); + } + + gst_object_unref (vsink); + } + + if (!child) { + /* FIXME: Create some default widget to show */ + child = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + + GST_DEBUG_OBJECT (self, "No widget from video sink, using placeholder"); + } + + gtk_overlay_set_child (GTK_OVERLAY (self->overlay), child); + GST_DEBUG_OBJECT (self, "Set new video widget"); +} + +static void +_player_error_cb (ClapperPlayer *player, GError *error, + const gchar *debug_info, ClapperGtkVideo *self) +{ + /* FIXME: Handle authentication error (pop dialog to set credentials and retry) */ + + /* Buffering will not finish anymore if we were in middle of it */ + _set_buffering_animation_enabled (self, FALSE); + + if (!self->showing_status) { + clapper_gtk_status_set_error (CLAPPER_GTK_STATUS_CAST (self->status), error); + self->showing_status = TRUE; + } +} + +static void +_player_missing_plugin_cb (ClapperPlayer *player, const gchar *name, + const gchar *installer_detail, ClapperGtkVideo *self) +{ + /* XXX: Playbin2 seems to not emit state change here, + * so manually stop buffering animation just in case */ + _set_buffering_animation_enabled (self, FALSE); + + /* XXX: Some content can still be played partially (e.g. without audio), + * but it should be better to stop and notify user that something is missing */ + clapper_player_stop (player); + + /* We might get "missing-plugin" followed by "error" signal. This boolean prevents + * immediately overwriting status and lets user deal with problems in order. */ + if (!self->showing_status) { + clapper_gtk_status_set_missing_plugin (CLAPPER_GTK_STATUS_CAST (self->status), name); + self->showing_status = TRUE; + } +} + +static void +_queue_current_item_changed_cb (ClapperQueue *queue, + GParamSpec *pspec G_GNUC_UNUSED, ClapperGtkVideo *self) +{ + clapper_gtk_status_clear (CLAPPER_GTK_STATUS_CAST (self->status)); + self->showing_status = FALSE; +} + +static void +_fading_overlay_revealed_cb (GtkRevealer *revealer, + GParamSpec *pspec G_GNUC_UNUSED, ClapperGtkVideo *self) +{ + self->revealed = gtk_revealer_get_child_revealed (revealer); + + /* Start fade timeout once fully revealed */ + if (self->revealed) + _reset_fade_timeout (self); +} + +/** + * clapper_gtk_video_new: + * + * Creates a new #ClapperGtkVideo instance. + * + * Returns: a new video #GtkWidget. + */ +GtkWidget * +clapper_gtk_video_new (void) +{ + return g_object_new (CLAPPER_GTK_TYPE_VIDEO, NULL); +} + +/** + * clapper_gtk_video_add_overlay: + * @video: a #ClapperGtkVideo + * @widget: a #GtkWidget + * + * Add another #GtkWidget to be overlaid on top of video. + * + * The position at which @widget is placed is determined from + * [property@Gtk.Widget:halign] and [property@Gtk.Widget:valign] properties. + * + * This function will overlay @widget as-is meaning that widget is responsible + * for managing its own visablity if needed. If you want to add a #GtkWidget + * that will reveal and fade itself automatically when interacting with @video + * (e.g. controls panel) you can use clapper_gtk_video_add_fading_overlay() + * function for convenience. + */ +void +clapper_gtk_video_add_overlay (ClapperGtkVideo *self, GtkWidget *widget) +{ + g_return_if_fail (CLAPPER_GTK_IS_VIDEO (self)); + g_return_if_fail (GTK_IS_WIDGET (widget)); + + g_ptr_array_add (self->overlays, widget); + gtk_overlay_add_overlay (GTK_OVERLAY (self->overlay), widget); +} + +/** + * clapper_gtk_video_add_fading_overlay: + * @video: a #ClapperGtkVideo + * @widget: a #GtkWidget + * + * Similiar as clapper_gtk_video_add_overlay() but will also automatically + * add fading functionality to overlaid #GtkWidget for convenience. This will + * make widget reveal itself when interacting with @video and fade otherwise. + * Useful when placing widgets such as playback controls panels. + */ +void +clapper_gtk_video_add_fading_overlay (ClapperGtkVideo *self, GtkWidget *widget) +{ + GtkWidget *revealer; + + g_return_if_fail (CLAPPER_GTK_IS_VIDEO (self)); + g_return_if_fail (GTK_IS_WIDGET (widget)); + + revealer = gtk_revealer_new (); + + g_object_bind_property (revealer, "child-revealed", revealer, "visible", G_BINDING_DEFAULT); + + g_object_bind_property (widget, "halign", revealer, "halign", G_BINDING_SYNC_CREATE); + g_object_bind_property (widget, "valign", revealer, "valign", G_BINDING_SYNC_CREATE); + + /* Since we reveal/fade all at once, one signal connection is enough */ + if (self->notify_revealed_id == 0) { + self->notify_revealed_id = g_signal_connect (revealer, "notify::child-revealed", + G_CALLBACK (_fading_overlay_revealed_cb), self); + } + + gtk_widget_set_visible (revealer, self->reveal); + gtk_revealer_set_reveal_child (GTK_REVEALER (revealer), self->reveal); + gtk_revealer_set_transition_type (GTK_REVEALER (revealer), GTK_REVEALER_TRANSITION_TYPE_CROSSFADE); + gtk_revealer_set_transition_duration (GTK_REVEALER (revealer), 800); + gtk_revealer_set_child (GTK_REVEALER (revealer), widget); + + g_ptr_array_add (self->fading_overlays, revealer); + gtk_overlay_add_overlay (GTK_OVERLAY (self->overlay), revealer); +} + +/** + * clapper_gtk_video_get_player: + * @video: a #ClapperGtkVideo + * + * Get #ClapperPlayer used by this #ClapperGtkVideo instance. + * + * Returns: (transfer none): a #ClapperPlayer used by video. + */ +ClapperPlayer * +clapper_gtk_video_get_player (ClapperGtkVideo *self) +{ + g_return_val_if_fail (CLAPPER_GTK_IS_VIDEO (self), NULL); + + return self->player; +} + +/** + * clapper_gtk_video_set_fade_delay: + * @video: a #ClapperGtkVideo + * @delay: a fade delay + * + * Set time in milliseconds after which fading overlays should fade. + */ +void +clapper_gtk_video_set_fade_delay (ClapperGtkVideo *self, guint delay) +{ + g_return_if_fail (CLAPPER_GTK_IS_VIDEO (self)); + g_return_if_fail (delay >= 1000); + + self->fade_delay = delay; + g_object_notify_by_pspec (G_OBJECT (self), param_specs[PROP_FADE_DELAY]); +} + +/** + * clapper_gtk_video_get_fade_delay: + * @video: a #ClapperGtkVideo + * + * Get time in milliseconds after which fading overlays should fade. + * + * Returns: currently set fade delay. + */ +guint +clapper_gtk_video_get_fade_delay (ClapperGtkVideo *self) +{ + g_return_val_if_fail (CLAPPER_GTK_IS_VIDEO (self), 0); + + return self->fade_delay; +} + +/** + * clapper_gtk_video_set_touch_fade_delay: + * @video: a #ClapperGtkVideo + * @delay: a touch fade delay + * + * Set time in milliseconds after which fading overlays should fade + * when using touchscreen. + * + * It is often useful to set this higher then normal fade delay property, + * as in case of touch events user do not have a moving pointer that would + * extend fade timeout, so he can have more time to decide what to press next. + */ +void +clapper_gtk_video_set_touch_fade_delay (ClapperGtkVideo *self, guint delay) +{ + g_return_if_fail (CLAPPER_GTK_IS_VIDEO (self)); + g_return_if_fail (delay >= 1); + + self->touch_fade_delay = delay; + g_object_notify_by_pspec (G_OBJECT (self), param_specs[PROP_TOUCH_FADE_DELAY]); +} + +/** + * clapper_gtk_video_get_touch_fade_delay: + * @video: a #ClapperGtkVideo + * + * Get time in milliseconds after which fading overlays should fade + * when revealed using touch device. + * + * Returns: currently set touch fade delay. + */ +guint +clapper_gtk_video_get_touch_fade_delay (ClapperGtkVideo *self) +{ + g_return_val_if_fail (CLAPPER_GTK_IS_VIDEO (self), 0); + + return self->touch_fade_delay; +} + +/** + * clapper_gtk_video_set_auto_inhibit: + * @video: a #ClapperGtkVideo + * @inhibit: whether to enable automatic session inhibit + * + * Set whether video should try to automatically inhibit session + * from idling (and possibly screen going black) when video is playing. + */ +void +clapper_gtk_video_set_auto_inhibit (ClapperGtkVideo *self, gboolean inhibit) +{ + g_return_if_fail (CLAPPER_GTK_IS_VIDEO (self)); + + if (self->auto_inhibit != inhibit) { + self->auto_inhibit = inhibit; + + /* Uninhibit if we were auto inhibited earlier */ + if (!self->auto_inhibit) + _set_inhibit_session (self, FALSE); + + g_object_notify_by_pspec (G_OBJECT (self), param_specs[PROP_AUTO_INHIBIT]); + } +} + +/** + * clapper_gtk_video_get_auto_inhibit: + * @video: a #ClapperGtkVideo + * + * Get whether automatic session inhibit is enabled. + * + * Returns: %TRUE if enabled, %FALSE otherwise. + */ +gboolean +clapper_gtk_video_get_auto_inhibit (ClapperGtkVideo *self) +{ + g_return_val_if_fail (CLAPPER_GTK_IS_VIDEO (self), FALSE); + + return self->auto_inhibit; +} + +/** + * clapper_gtk_video_get_inhibited: + * @video: a #ClapperGtkVideo + * + * Get whether session is currently inhibited by + * [property@ClapperGtk.Video:auto-inhibit]. + * + * Returns: %TRUE if inhibited, %FALSE otherwise. + */ +gboolean +clapper_gtk_video_get_inhibited (ClapperGtkVideo *self) +{ + g_return_val_if_fail (CLAPPER_GTK_IS_VIDEO (self), FALSE); + + return (self->inhibit_cookie != 0); +} + +static void +clapper_gtk_video_root (GtkWidget *widget) +{ + ClapperGtkVideo *self = CLAPPER_GTK_VIDEO_CAST (widget); + GtkRoot *root; + + _ensure_css_provider (); + + GTK_WIDGET_CLASS (parent_class)->root (widget); + + root = gtk_widget_get_root (widget); + + if (root && GTK_IS_WINDOW (root)) { + GtkWindow *window = GTK_WINDOW (root); + + g_signal_connect (window, "notify::is-active", + G_CALLBACK (_window_is_active_cb), self); + _window_is_active_cb (window, NULL, self); + } + + if (self->auto_inhibit) { + ClapperPlayerState state = clapper_player_get_state (self->player); + _set_inhibit_session (self, state == CLAPPER_PLAYER_STATE_PLAYING); + } +} + +static void +clapper_gtk_video_unroot (GtkWidget *widget) +{ + ClapperGtkVideo *self = CLAPPER_GTK_VIDEO_CAST (widget); + GtkRoot *root = gtk_widget_get_root (widget); + + if (root && GTK_IS_WINDOW (root)) { + g_signal_handlers_disconnect_by_func (GTK_WINDOW (root), + _window_is_active_cb, self); + } + + _set_inhibit_session (self, FALSE); + + GTK_WIDGET_CLASS (parent_class)->unroot (widget); +} + +static void +clapper_gtk_video_init (ClapperGtkVideo *self) +{ + self->overlay = gtk_overlay_new (); + gtk_widget_set_overflow (self->overlay, GTK_OVERFLOW_HIDDEN); + gtk_widget_set_parent (self->overlay, GTK_WIDGET (self)); + + self->overlays = g_ptr_array_new (); + self->fading_overlays = g_ptr_array_new (); + + self->fade_delay = DEFAULT_FADE_DELAY; + self->touch_fade_delay = DEFAULT_TOUCH_FADE_DELAY; + self->auto_inhibit = DEFAULT_AUTO_INHIBIT; + + gtk_widget_init_template (GTK_WIDGET (self)); + + gtk_gesture_group (self->touch_gesture, self->click_gesture); +} + +static void +clapper_gtk_video_constructed (GObject *object) +{ + ClapperGtkVideo *self = CLAPPER_GTK_VIDEO_CAST (object); + GstElement *vsink = gst_element_factory_make ("clappersink", NULL); + ClapperQueue *queue; + + self->player = clapper_player_new (); + queue = clapper_player_get_queue (self->player); + + g_signal_connect (self->player, "notify::state", + G_CALLBACK (_player_state_changed_cb), self); + g_signal_connect (self->player, "notify::video-sink", + G_CALLBACK (_video_sink_changed_cb), self); + + clapper_player_set_video_sink (self->player, vsink); + + g_signal_connect (self->player, "error", + G_CALLBACK (_player_error_cb), self); + g_signal_connect (self->player, "missing-plugin", + G_CALLBACK (_player_missing_plugin_cb), self); + + g_signal_connect (queue, "notify::current-item", + G_CALLBACK (_queue_current_item_changed_cb), self); + + G_OBJECT_CLASS (parent_class)->constructed (object); +} + +static void +clapper_gtk_video_dispose (GObject *object) +{ + ClapperGtkVideo *self = CLAPPER_GTK_VIDEO_CAST (object); + + if (self->notify_revealed_id != 0) { + GtkRevealer *revealer = GTK_REVEALER (g_ptr_array_index (self->fading_overlays, 0)); + + g_signal_handler_disconnect (revealer, self->notify_revealed_id); + self->notify_revealed_id = 0; + } + + g_clear_handle_id (&self->fade_timeout, g_source_remove); + + gtk_widget_dispose_template (GTK_WIDGET (object), CLAPPER_GTK_TYPE_VIDEO); + + g_clear_pointer (&self->overlay, gtk_widget_unparent); + gst_clear_object (&self->player); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +clapper_gtk_video_finalize (GObject *object) +{ + ClapperGtkVideo *self = CLAPPER_GTK_VIDEO_CAST (object); + + g_ptr_array_unref (self->overlays); + g_ptr_array_unref (self->fading_overlays); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +clapper_gtk_video_get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + ClapperGtkVideo *self = CLAPPER_GTK_VIDEO_CAST (object); + + switch (prop_id) { + case PROP_PLAYER: + g_value_set_object (value, clapper_gtk_video_get_player (self)); + break; + case PROP_FADE_DELAY: + g_value_set_uint (value, clapper_gtk_video_get_fade_delay (self)); + break; + case PROP_TOUCH_FADE_DELAY: + g_value_set_uint (value, clapper_gtk_video_get_touch_fade_delay (self)); + break; + case PROP_AUTO_INHIBIT: + g_value_set_boolean (value, clapper_gtk_video_get_auto_inhibit (self)); + break; + case PROP_INHIBITED: + g_value_set_boolean (value, clapper_gtk_video_get_inhibited (self)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +clapper_gtk_video_set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + ClapperGtkVideo *self = CLAPPER_GTK_VIDEO_CAST (object); + + switch (prop_id) { + case PROP_FADE_DELAY: + clapper_gtk_video_set_fade_delay (self, g_value_get_uint (value)); + break; + case PROP_TOUCH_FADE_DELAY: + clapper_gtk_video_set_touch_fade_delay (self, g_value_get_uint (value)); + break; + case PROP_AUTO_INHIBIT: + clapper_gtk_video_set_auto_inhibit (self, g_value_get_boolean (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +clapper_gtk_video_class_init (ClapperGtkVideoClass *klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + GtkWidgetClass *widget_class = (GtkWidgetClass *) klass; + + GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clappergtkvideo", GST_DEBUG_FG_MAGENTA, + "Clapper GTK Video"); + + widget_class->root = clapper_gtk_video_root; + widget_class->unroot = clapper_gtk_video_unroot; + + gobject_class->constructed = clapper_gtk_video_constructed; + gobject_class->get_property = clapper_gtk_video_get_property; + gobject_class->set_property = clapper_gtk_video_set_property; + gobject_class->dispose = clapper_gtk_video_dispose; + gobject_class->finalize = clapper_gtk_video_finalize; + + /** + * ClapperGtkVideo:player: + * + * A #ClapperPlayer used by video. + */ + param_specs[PROP_PLAYER] = g_param_spec_object ("player", + NULL, NULL, CLAPPER_TYPE_PLAYER, + G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + /** + * ClapperGtkVideo:fade-delay: + * + * A delay in milliseconds before trying to fade all fading overlays. + */ + param_specs[PROP_FADE_DELAY] = g_param_spec_uint ("fade-delay", + NULL, NULL, 1, G_MAXUINT, DEFAULT_FADE_DELAY, + G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + /** + * ClapperGtkVideo:touch-fade-delay: + * + * A delay in milliseconds before trying to fade all fading overlays + * after revealed using touchscreen. + */ + param_specs[PROP_TOUCH_FADE_DELAY] = g_param_spec_uint ("touch-fade-delay", + NULL, NULL, 1, G_MAXUINT, DEFAULT_TOUCH_FADE_DELAY, + G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + /** + * ClapperGtkVideo:auto-inhibit: + * + * Try to automatically inhibit session when video is playing. + */ + param_specs[PROP_AUTO_INHIBIT] = g_param_spec_boolean ("auto-inhibit", + NULL, NULL, DEFAULT_AUTO_INHIBIT, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + /** + * ClapperGtkVideo:inhibited: + * + * Get whether session is currently inhibited by the video. + */ + param_specs[PROP_INHIBITED] = g_param_spec_boolean ("inhibited", + NULL, NULL, FALSE, + G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + /** + * ClapperGtkVideo::toggle-fullscreen: + * @video: a #ClapperGtkVideo + * + * A signal that user requested a change in fullscreen state of the video. + * + * Note that when going fullscreen from this signal, user will expect + * for only video to be fullscreened and not the whole app window. + * It is up to implementation to decide how to handle that. + */ + signals[SIGNAL_TOGGLE_FULLSCREEN] = g_signal_new ("toggle-fullscreen", + G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, + 0, NULL, NULL, NULL, G_TYPE_NONE, 0); + + /** + * ClapperGtkVideo::seek-request: + * @video: a #ClapperGtkVideo + * @forward: %TRUE if seek should be forward, %FALSE if backward + * + * A helper signal for implementing common seeking by double tap + * on screen side for touchscreen devices. + * + * Note that @forward already takes into account RTL direction, + * so implementation does not have to check. + */ + signals[SIGNAL_SEEK_REQUEST] = g_signal_new ("seek-request", + 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, G_TYPE_BOOLEAN); + + g_object_class_install_properties (gobject_class, PROP_LAST, param_specs); + + gtk_widget_class_install_action (widget_class, "video.toggle-play", NULL, toggle_play_action_cb); + gtk_widget_class_install_action (widget_class, "video.play", NULL, play_action_cb); + gtk_widget_class_install_action (widget_class, "video.pause", NULL, pause_action_cb); + gtk_widget_class_install_action (widget_class, "video.stop", NULL, stop_action_cb); + gtk_widget_class_install_action (widget_class, "video.seek", "d", seek_action_cb); + gtk_widget_class_install_action (widget_class, "video.seek-custom", "(di)", seek_custom_action_cb); + gtk_widget_class_install_action (widget_class, "video.toggle-mute", NULL, toggle_mute_action_cb); + gtk_widget_class_install_action (widget_class, "video.set-mute", "b", set_mute_action_cb); + gtk_widget_class_install_action (widget_class, "video.volume-up", NULL, volume_up_action_cb); + gtk_widget_class_install_action (widget_class, "video.volume-down", NULL, volume_down_action_cb); + gtk_widget_class_install_action (widget_class, "video.set-volume", "d", set_volume_action_cb); + gtk_widget_class_install_action (widget_class, "video.speed-up", NULL, speed_up_action_cb); + gtk_widget_class_install_action (widget_class, "video.speed-down", NULL, speed_down_action_cb); + gtk_widget_class_install_action (widget_class, "video.set-speed", "d", set_speed_action_cb); + gtk_widget_class_install_action (widget_class, "video.previous-item", NULL, previous_item_action_cb); + gtk_widget_class_install_action (widget_class, "video.next-item", NULL, next_item_action_cb); + gtk_widget_class_install_action (widget_class, "video.select-item", "u", select_item_action_cb); + + gtk_widget_class_set_template_from_resource (widget_class, + CLAPPER_GTK_RESOURCE_PREFIX "/ui/clapper-gtk-video.ui"); + + gtk_widget_class_bind_template_child (widget_class, ClapperGtkVideo, status); + gtk_widget_class_bind_template_child (widget_class, ClapperGtkVideo, buffering_animation); + gtk_widget_class_bind_template_child (widget_class, ClapperGtkVideo, touch_gesture); + gtk_widget_class_bind_template_child (widget_class, ClapperGtkVideo, click_gesture); + + gtk_widget_class_bind_template_callback (widget_class, left_click_pressed_cb); + gtk_widget_class_bind_template_callback (widget_class, left_click_released_cb); + gtk_widget_class_bind_template_callback (widget_class, left_click_stopped_cb); + gtk_widget_class_bind_template_callback (widget_class, touch_pressed_cb); + gtk_widget_class_bind_template_callback (widget_class, touch_released_cb); + gtk_widget_class_bind_template_callback (widget_class, motion_enter_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, drop_motion_cb); + gtk_widget_class_bind_template_callback (widget_class, drop_motion_leave_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_GENERIC); + gtk_widget_class_set_css_name (widget_class, "clapper-gtk-video"); +} diff --git a/src/lib/clapper-gtk/clapper-gtk-video.h b/src/lib/clapper-gtk/clapper-gtk-video.h new file mode 100644 index 00000000..d86a6f5c --- /dev/null +++ b/src/lib/clapper-gtk/clapper-gtk-video.h @@ -0,0 +1,60 @@ +/* Clapper GTK Integration Library + * Copyright (C) 2024 RafaÅ‚ DziÄ™giel + * + * 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. + */ + +#pragma once + +#if !defined(__CLAPPER_GTK_INSIDE__) && !defined(CLAPPER_GTK_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#include +#include +#include + +G_BEGIN_DECLS + +#define CLAPPER_GTK_TYPE_VIDEO (clapper_gtk_video_get_type()) +#define CLAPPER_GTK_VIDEO_CAST(obj) ((ClapperGtkVideo *)(obj)) + +G_DECLARE_FINAL_TYPE (ClapperGtkVideo, clapper_gtk_video, CLAPPER_GTK, VIDEO, GtkWidget) + +GtkWidget * clapper_gtk_video_new (void); + +void clapper_gtk_video_add_overlay (ClapperGtkVideo *video, GtkWidget *widget); + +void clapper_gtk_video_add_fading_overlay (ClapperGtkVideo *video, GtkWidget *widget); + +ClapperPlayer * clapper_gtk_video_get_player (ClapperGtkVideo *video); + +void clapper_gtk_video_set_fade_delay (ClapperGtkVideo *video, guint delay); + +guint clapper_gtk_video_get_fade_delay (ClapperGtkVideo *video); + +void clapper_gtk_video_set_touch_fade_delay (ClapperGtkVideo *video, guint delay); + +guint clapper_gtk_video_get_touch_fade_delay (ClapperGtkVideo *video); + +void clapper_gtk_video_set_auto_inhibit (ClapperGtkVideo *video, gboolean inhibit); + +gboolean clapper_gtk_video_get_auto_inhibit (ClapperGtkVideo *video); + +gboolean clapper_gtk_video_get_inhibited (ClapperGtkVideo *video); + +G_END_DECLS diff --git a/src/lib/clapper-gtk/clapper-gtk.gresources.xml b/src/lib/clapper-gtk/clapper-gtk.gresources.xml new file mode 100644 index 00000000..c25bb2f6 --- /dev/null +++ b/src/lib/clapper-gtk/clapper-gtk.gresources.xml @@ -0,0 +1,14 @@ + + + + ui/clapper-gtk-billboard.ui + ui/clapper-gtk-extra-menu-button.ui + ui/clapper-gtk-seek-bar.ui + ui/clapper-gtk-simple-controls.ui + ui/clapper-gtk-status.ui + ui/clapper-gtk-stream-list-item.ui + ui/clapper-gtk-title-header.ui + ui/clapper-gtk-video.ui + css/styles.css + + diff --git a/src/lib/clapper-gtk/clapper-gtk.h b/src/lib/clapper-gtk/clapper-gtk.h new file mode 100644 index 00000000..f34608ed --- /dev/null +++ b/src/lib/clapper-gtk/clapper-gtk.h @@ -0,0 +1,42 @@ +/* Clapper GTK Integration Library + * Copyright (C) 2024 RafaÅ‚ DziÄ™giel + * + * 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. + */ + +#pragma once + +#define __CLAPPER_GTK_INSIDE__ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#undef __CLAPPER_GTK_INSIDE__ diff --git a/src/lib/clapper-gtk/css/styles.css b/src/lib/clapper-gtk/css/styles.css new file mode 100644 index 00000000..9110496d --- /dev/null +++ b/src/lib/clapper-gtk/css/styles.css @@ -0,0 +1,153 @@ +/* Adwaita OSD background color is unacceptable: + * https://gitlab.gnome.org/GNOME/libadwaita/-/issues/454 */ +clapper-gtk-video menubutton.osd, +clapper-gtk-video button.osd, +clapper-gtk-video box.osd, +clapper-gtk-video clapper-gtk-title-header.osd, +clapper-gtk-video .osd popover contents, +clapper-gtk-video .osd popover arrow { + background-color: rgba(38,38,38,0.8); +} +clapper-gtk-video button.osd:hover, +clapper-gtk-video button.osd:checked { + background-color: rgba(63,63,63,0.8); +} +clapper-gtk-video button.osd:active { + background-color: rgba(82,82,82,0.8); +} +clapper-gtk-video box.osd listview.osd { + background-color: transparent; +} + +clapper-gtk-status box { + padding-left: 6px; + padding-right: 6px; +} +clapper-gtk-status box image { + -gtk-icon-size: 128px; + -gtk-icon-filter: opacity(0.6); +} +clapper-gtk-status.adapted box image { + -gtk-icon-size: 96px; +} + +clapper-gtk-billboard .sidebox { + padding: 12px; + border-radius: 9999px; +} +clapper-gtk-billboard .sidebox:dir(ltr) { + margin-left: 6px; +} +clapper-gtk-billboard .sidebox:dir(rtl) { + margin-right: 6px; +} +clapper-gtk-billboard .sidebox .progressbox { + margin-bottom: 6px; +} +clapper-gtk-billboard .sidebox .progressbox progressbar { + min-height: 90px; +} +clapper-gtk-billboard .sidebox .progressbox.overamp progress { + background: #c01c28; +} +clapper-gtk-billboard .sidebox .progresslabel:dir(ltr) { + margin-left: 6px; +} +clapper-gtk-billboard .sidebox .progresslabel:dir(rtl) { + margin-right: 6px; +} +clapper-gtk-billboard .messagebox { + margin: 6px; + padding: 12px; + min-width: 92px; +} +clapper-gtk-billboard .messagebox image { + -gtk-icon-size: 48px; +} + +menubutton.circular, +dropdown.circular button.toggle { + border-radius: 9999px; +} +.rounded { + border-radius: 19px; +} + +clapper-gtk-title-header { + margin: 6px; + min-height: 38px; +} +clapper-gtk-title-header label { + margin-left: 12px; + margin-right: 12px; +} + +clapper-gtk-simple-controls .centerbox { + margin-bottom: 2px; +} +clapper-gtk-simple-controls .mainbox { + margin-left: 6px; + margin-right: 6px; + margin-bottom: 4px; +} +clapper-gtk-simple-controls .mainbox button { + margin: 2px; +} +clapper-gtk-simple-controls .mainbox scale { + margin-top: 2px; + margin-bottom: 2px; +} +clapper-gtk-simple-controls .mainbox popover button { + margin: 0px; +} +clapper-gtk-simple-controls .fullscreenbutton { + margin-bottom: 6px; +} +clapper-gtk-simple-controls .fullscreenbutton:dir(ltr) { + margin-right: 6px; +} +clapper-gtk-simple-controls .fullscreenbutton:dir(rtl) { + margin-left: 6px; +} + +clapper-gtk-seek-bar scale trough highlight { + min-height: 6px; +} +clapper-gtk-seek-bar label { + margin-left: 2px; + margin-right: 2px; +} + +clapper-gtk-extra-menu-button popover .spinsidebutton { + min-width: 28px; + min-height: 28px; +} +clapper-gtk-extra-menu-button popover .spinsidebutton:dir(ltr) { + margin-right: 2px; +} +clapper-gtk-extra-menu-button popover .spinsidebutton:dir(rtl) { + margin-left: 2px; +} +clapper-gtk-extra-menu-button popover spinbutton { + border-radius: 9999px; +} +clapper-gtk-extra-menu-button popover spinbutton button.up:dir(ltr) { + border-radius: 0px 9999px 9999px 0px; +} +clapper-gtk-extra-menu-button popover spinbutton button.up:dir(rtl) { + border-radius: 9999px 0px 0px 9999px; +} +clapper-gtk-extra-menu-button popover spinbutton button.up:dir(ltr) { + padding-right: 6px; +} +clapper-gtk-extra-menu-button popover spinbutton button.up:dir(rtl) { + padding-left: 6px; +} +clapper-gtk-menu-scrolled-window listview row { + padding-left: 6px; + padding-right: 6px; +} +clapper-gtk-menu-scrolled-window listview row checkbutton label { + margin-left: 2px; + margin-right: 2px; +} diff --git a/src/lib/clapper-gtk/meson.build b/src/lib/clapper-gtk/meson.build new file mode 100644 index 00000000..8c989a73 --- /dev/null +++ b/src/lib/clapper-gtk/meson.build @@ -0,0 +1,221 @@ +clappergtk_dep = dependency('', required: false) +clappergtk_option = get_option('clapper-gtk') +clappergtk_api_name = meson.project_name() + '-gtk' + clapper_version_suffix +clappergtk_resource_prefix = '/com/github/rafostar/Clapper/clapper-gtk' +build_clappergtk = false + +if clappergtk_option.disabled() + subdir_done() +endif + +clappergtk_deps = [ + clapper_dep, + gst_dep, + gst_clapper_sink_dep, + gtk4_dep, + glib_dep, + gobject_dep, + libm, +] + +foreach dep : clappergtk_deps + if not dep.found() + if clappergtk_option.enabled() + error('clapper-gtk option was enabled, but required dependencies were not found') + endif + subdir_done() + endif +endforeach + +subdir('po') + +config_h = configuration_data() +config_h.set_quoted('GETTEXT_PACKAGE', meson.project_name() + '-app') +config_h.set_quoted('LOCALEDIR', join_paths (prefix, localedir)) +config_h.set_quoted('CLAPPER_GTK_RESOURCE_PREFIX', clappergtk_resource_prefix) + +configure_file( + output: 'config.h', + configuration: config_h, +) + +version_conf = configuration_data() + +version_conf.set( + 'CLAPPER_GTK_VERSION', + meson.project_version(), +) +version_conf.set( + 'CLAPPER_GTK_MAJOR_VERSION', + version_array[0].to_int(), +) +version_conf.set( + 'CLAPPER_GTK_MINOR_VERSION', + version_array[1].to_int(), +) +version_conf.set( + 'CLAPPER_GTK_MICRO_VERSION', + version_array[2].to_int(), +) + +clappergtk_version_header = configure_file( + input: 'clapper-gtk-version.h.in', + output: 'clapper-gtk-version.h', + configuration: version_conf, +) +clappergtk_resources = gnome.compile_resources( + 'clapper-gtk-resources', + 'clapper-gtk.gresources.xml', + c_name: 'clapper_gtk', +) + +# Include the generated headers +clappergtk_conf_inc = [ + include_directories('.'), + include_directories('..'), +] + +clappergtk_headers = [ + 'clapper-gtk.h', + 'clapper-gtk-enums.h', + 'clapper-gtk-billboard.h', + 'clapper-gtk-container.h', + 'clapper-gtk-extra-menu-button.h', + 'clapper-gtk-lead-container.h', + 'clapper-gtk-next-item-button.h', + 'clapper-gtk-previous-item-button.h', + 'clapper-gtk-seek-bar.h', + 'clapper-gtk-simple-controls.h', + 'clapper-gtk-title-header.h', + 'clapper-gtk-title-label.h', + 'clapper-gtk-toggle-fullscreen-button.h', + 'clapper-gtk-toggle-play-button.h', + 'clapper-gtk-utils.h', + 'clapper-gtk-video.h', + clappergtk_version_header, +] +clappergtk_sources = [ + 'clapper-gtk-billboard.c', + 'clapper-gtk-buffering-animation.c', + 'clapper-gtk-buffering-paintable.c', + 'clapper-gtk-container.c', + 'clapper-gtk-extra-menu-button.c', + 'clapper-gtk-lead-container.c', + 'clapper-gtk-limited-layout.c', + 'clapper-gtk-next-item-button.c', + 'clapper-gtk-previous-item-button.c', + 'clapper-gtk-seek-bar.c', + 'clapper-gtk-simple-controls.c', + 'clapper-gtk-status.c', + 'clapper-gtk-stream-check-button.c', + 'clapper-gtk-title-header.c', + 'clapper-gtk-title-label.c', + 'clapper-gtk-toggle-fullscreen-button.c', + 'clapper-gtk-toggle-play-button.c', + 'clapper-gtk-utils.c', + 'clapper-gtk-video.c', + clappergtk_resources, +] +clappergtk_c_args = [ + '-DG_LOG_DOMAIN="ClapperGtk"', + '-DCLAPPER_GTK_COMPILATION', + '-DGST_USE_UNSTABLE_API', +] + +clappergtk_headers_dir = join_paths(includedir, clapper_api_name, 'clapper-gtk') + +clappergtk_enums = gnome.mkenums_simple( + 'clapper-gtk-enum-types', + sources: clappergtk_headers, + identifier_prefix: 'ClapperGtk', + symbol_prefix: 'clapper_gtk', + install_header: true, + install_dir: clappergtk_headers_dir, +) + +clappergtk_lib = library( + clappergtk_api_name, + clappergtk_sources + clappergtk_enums, + dependencies: clappergtk_deps, + include_directories: clappergtk_conf_inc, + c_args: clappergtk_c_args, + version: clapper_version, + install: true, +) +install_headers(clappergtk_headers, + install_dir: clappergtk_headers_dir, +) +build_clappergtk = true + +if build_gir + clappergtk_gir = gnome.generate_gir(clappergtk_lib, + sources: [ + clappergtk_sources, + clappergtk_headers, + clappergtk_enums, + ], + extra_args: [ + gir_init_section, + '--quiet', + '--warn-all', + '-DCLAPPER_GTK_COMPILATION', + '-DGST_USE_UNSTABLE_API', + ], + nsversion: version_array[0] + '.0', + namespace: 'ClapperGtk', + identifier_prefix: 'ClapperGtk', + symbol_prefix: 'clapper_gtk', + export_packages: clappergtk_api_name, + install: true, + includes: [ + clapper_gir[0], + 'Gst-1.0', + 'Gtk-4.0', + 'GLib-2.0', + 'GObject-2.0', + ], + header: join_paths(meson.project_name() + '-gtk', 'clapper-gtk.h'), + ) +endif + +if build_vapi + if not build_gir + if get_option('vapi').enabled() + error('Cannot build "vapi" without "introspection"') + endif + else + gnome.generate_vapi(clappergtk_api_name, + sources: clappergtk_gir[0], + packages: clapper_pkg_reqs + [ + clapper_vapi, + 'gtk4', + ], + install: true, + ) + endif +endif + +pkgconfig.generate(clappergtk_lib, + subdirs: [clappergtk_api_name], + filebase: clappergtk_api_name, + name: meson.project_name() + '-gtk', + version: meson.project_version(), + description: 'Clapper GTK integration library', + requires: [ + clapper_lib, + 'gstreamer-1.0', + 'gtk4', + 'glib-2.0', + 'gobject-2.0', + ], +) + +clappergtk_dep = declare_dependency( + link_with: clappergtk_lib, + include_directories: clappergtk_conf_inc, + dependencies: clappergtk_deps, + sources: [ + clappergtk_version_header, + clappergtk_enums[1], + ], +) diff --git a/src/lib/clapper-gtk/po/LINGUAS b/src/lib/clapper-gtk/po/LINGUAS new file mode 100644 index 00000000..ad6fe0cf --- /dev/null +++ b/src/lib/clapper-gtk/po/LINGUAS @@ -0,0 +1 @@ +ar ast ca cs de es eu fa fi fr he hr hu it ja lt nl pl pt pt_BR ru sk sv tr zh_CN diff --git a/src/lib/clapper-gtk/po/POTFILES b/src/lib/clapper-gtk/po/POTFILES new file mode 100644 index 00000000..cc4ca53f --- /dev/null +++ b/src/lib/clapper-gtk/po/POTFILES @@ -0,0 +1,5 @@ +src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui + +src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c +src/lib/clapper-gtk/clapper-gtk-stream-check-button.c +src/lib/clapper-gtk/clapper-gtk-title-label.c diff --git a/src/lib/clapper-gtk/po/ar.po b/src/lib/clapper-gtk/po/ar.po new file mode 100644 index 00000000..1801b808 --- /dev/null +++ b/src/lib/clapper-gtk/po/ar.po @@ -0,0 +1,65 @@ +# Arabic translations for clapper-gtk package. +# Copyright (C) 2024 THE clapper-gtk'S COPYRIGHT HOLDER +# This file is distributed under the same license as the clapper-gtk package. +# Automatically generated, 2024. +# +msgid "" +msgstr "" +"Project-Id-Version: clapper-gtk\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-04-05 20:50+0200\n" +"PO-Revision-Date: 2024-03-07 20:49+0100\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: ar\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:9 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:227 +msgid "Video" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:17 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:228 +msgid "Audio" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:25 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:229 +msgid "Subtitles" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:28 +msgid "Enabled" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:32 +msgid "Open…" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:75 +msgid "Mute" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:119 +msgid "Reset" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:126 +msgid "Undetermined" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 +msgid "Channels" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-title-label.c:91 +msgid "No media" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-title-label.c:103 +msgid "Unknown title" +msgstr "" diff --git a/src/lib/clapper-gtk/po/ast.po b/src/lib/clapper-gtk/po/ast.po new file mode 100644 index 00000000..5f7329d1 --- /dev/null +++ b/src/lib/clapper-gtk/po/ast.po @@ -0,0 +1,65 @@ +# Asturian translations for clapper-gtk package. +# Copyright (C) 2024 THE clapper-gtk'S COPYRIGHT HOLDER +# This file is distributed under the same license as the clapper-gtk package. +# Automatically generated, 2024. +# +msgid "" +msgstr "" +"Project-Id-Version: clapper-gtk\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-04-05 20:50+0200\n" +"PO-Revision-Date: 2024-03-07 20:49+0100\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: ast\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:9 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:227 +msgid "Video" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:17 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:228 +msgid "Audio" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:25 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:229 +msgid "Subtitles" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:28 +msgid "Enabled" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:32 +msgid "Open…" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:75 +msgid "Mute" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:119 +msgid "Reset" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:126 +msgid "Undetermined" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 +msgid "Channels" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-title-label.c:91 +msgid "No media" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-title-label.c:103 +msgid "Unknown title" +msgstr "" diff --git a/src/lib/clapper-gtk/po/ca.po b/src/lib/clapper-gtk/po/ca.po new file mode 100644 index 00000000..6ddaa142 --- /dev/null +++ b/src/lib/clapper-gtk/po/ca.po @@ -0,0 +1,66 @@ +# Catalan translations for clapper-gtk package. +# Copyright (C) 2024 THE clapper-gtk'S COPYRIGHT HOLDER +# This file is distributed under the same license as the clapper-gtk package. +# Automatically generated, 2024. +# +msgid "" +msgstr "" +"Project-Id-Version: clapper-gtk\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-04-05 20:50+0200\n" +"PO-Revision-Date: 2024-03-07 20:49+0100\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: ca\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:9 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:227 +msgid "Video" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:17 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:228 +msgid "Audio" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:25 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:229 +msgid "Subtitles" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:28 +msgid "Enabled" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:32 +msgid "Open…" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:75 +msgid "Mute" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:119 +msgid "Reset" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:126 +msgid "Undetermined" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 +msgid "Channels" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-title-label.c:91 +msgid "No media" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-title-label.c:103 +msgid "Unknown title" +msgstr "" diff --git a/src/lib/clapper-gtk/po/clapper-gtk.pot b/src/lib/clapper-gtk/po/clapper-gtk.pot new file mode 100644 index 00000000..ab4eee1d --- /dev/null +++ b/src/lib/clapper-gtk/po/clapper-gtk.pot @@ -0,0 +1,66 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the clapper-gtk package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: clapper-gtk\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-04-05 20:50+0200\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:9 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:227 +msgid "Video" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:17 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:228 +msgid "Audio" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:25 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:229 +msgid "Subtitles" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:28 +msgid "Enabled" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:32 +msgid "Open…" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:75 +msgid "Mute" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:119 +msgid "Reset" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:126 +msgid "Undetermined" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 +msgid "Channels" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-title-label.c:91 +msgid "No media" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-title-label.c:103 +msgid "Unknown title" +msgstr "" diff --git a/src/lib/clapper-gtk/po/cs.po b/src/lib/clapper-gtk/po/cs.po new file mode 100644 index 00000000..94e0ddaf --- /dev/null +++ b/src/lib/clapper-gtk/po/cs.po @@ -0,0 +1,66 @@ +# Czech translations for clapper-gtk package. +# Copyright (C) 2024 THE clapper-gtk'S COPYRIGHT HOLDER +# This file is distributed under the same license as the clapper-gtk package. +# Automatically generated, 2024. +# +msgid "" +msgstr "" +"Project-Id-Version: clapper-gtk\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-04-05 20:50+0200\n" +"PO-Revision-Date: 2024-03-07 20:49+0100\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: cs\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:9 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:227 +msgid "Video" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:17 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:228 +msgid "Audio" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:25 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:229 +msgid "Subtitles" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:28 +msgid "Enabled" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:32 +msgid "Open…" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:75 +msgid "Mute" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:119 +msgid "Reset" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:126 +msgid "Undetermined" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 +msgid "Channels" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-title-label.c:91 +msgid "No media" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-title-label.c:103 +msgid "Unknown title" +msgstr "" diff --git a/src/lib/clapper-gtk/po/de.po b/src/lib/clapper-gtk/po/de.po new file mode 100644 index 00000000..808a44b6 --- /dev/null +++ b/src/lib/clapper-gtk/po/de.po @@ -0,0 +1,66 @@ +# German translations for clapper-gtk package. +# Copyright (C) 2024 THE clapper-gtk'S COPYRIGHT HOLDER +# This file is distributed under the same license as the clapper-gtk package. +# Automatically generated, 2024. +# +msgid "" +msgstr "" +"Project-Id-Version: clapper-gtk\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-04-05 20:50+0200\n" +"PO-Revision-Date: 2024-03-07 20:49+0100\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: de\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:9 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:227 +msgid "Video" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:17 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:228 +msgid "Audio" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:25 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:229 +msgid "Subtitles" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:28 +msgid "Enabled" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:32 +msgid "Open…" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:75 +msgid "Mute" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:119 +msgid "Reset" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:126 +msgid "Undetermined" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 +msgid "Channels" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-title-label.c:91 +msgid "No media" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-title-label.c:103 +msgid "Unknown title" +msgstr "" diff --git a/src/lib/clapper-gtk/po/es.po b/src/lib/clapper-gtk/po/es.po new file mode 100644 index 00000000..7ea062c7 --- /dev/null +++ b/src/lib/clapper-gtk/po/es.po @@ -0,0 +1,66 @@ +# Spanish translations for clapper-gtk package. +# Copyright (C) 2024 THE clapper-gtk'S COPYRIGHT HOLDER +# This file is distributed under the same license as the clapper-gtk package. +# Automatically generated, 2024. +# +msgid "" +msgstr "" +"Project-Id-Version: clapper-gtk\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-04-05 20:50+0200\n" +"PO-Revision-Date: 2024-03-07 20:49+0100\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: es\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:9 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:227 +msgid "Video" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:17 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:228 +msgid "Audio" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:25 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:229 +msgid "Subtitles" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:28 +msgid "Enabled" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:32 +msgid "Open…" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:75 +msgid "Mute" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:119 +msgid "Reset" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:126 +msgid "Undetermined" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 +msgid "Channels" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-title-label.c:91 +msgid "No media" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-title-label.c:103 +msgid "Unknown title" +msgstr "" diff --git a/src/lib/clapper-gtk/po/eu.po b/src/lib/clapper-gtk/po/eu.po new file mode 100644 index 00000000..475d2662 --- /dev/null +++ b/src/lib/clapper-gtk/po/eu.po @@ -0,0 +1,65 @@ +# Basque translations for clapper-gtk package. +# Copyright (C) 2024 THE clapper-gtk'S COPYRIGHT HOLDER +# This file is distributed under the same license as the clapper-gtk package. +# Automatically generated, 2024. +# +msgid "" +msgstr "" +"Project-Id-Version: clapper-gtk\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-04-05 20:50+0200\n" +"PO-Revision-Date: 2024-03-07 20:49+0100\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: eu\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:9 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:227 +msgid "Video" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:17 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:228 +msgid "Audio" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:25 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:229 +msgid "Subtitles" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:28 +msgid "Enabled" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:32 +msgid "Open…" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:75 +msgid "Mute" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:119 +msgid "Reset" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:126 +msgid "Undetermined" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 +msgid "Channels" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-title-label.c:91 +msgid "No media" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-title-label.c:103 +msgid "Unknown title" +msgstr "" diff --git a/src/lib/clapper-gtk/po/fa.po b/src/lib/clapper-gtk/po/fa.po new file mode 100644 index 00000000..984605d7 --- /dev/null +++ b/src/lib/clapper-gtk/po/fa.po @@ -0,0 +1,65 @@ +# Persian translations for clapper-gtk package. +# Copyright (C) 2024 THE clapper-gtk'S COPYRIGHT HOLDER +# This file is distributed under the same license as the clapper-gtk package. +# Automatically generated, 2024. +# +msgid "" +msgstr "" +"Project-Id-Version: clapper-gtk\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-04-05 20:50+0200\n" +"PO-Revision-Date: 2024-03-07 20:49+0100\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: fa\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:9 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:227 +msgid "Video" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:17 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:228 +msgid "Audio" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:25 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:229 +msgid "Subtitles" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:28 +msgid "Enabled" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:32 +msgid "Open…" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:75 +msgid "Mute" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:119 +msgid "Reset" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:126 +msgid "Undetermined" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 +msgid "Channels" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-title-label.c:91 +msgid "No media" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-title-label.c:103 +msgid "Unknown title" +msgstr "" diff --git a/src/lib/clapper-gtk/po/fi.po b/src/lib/clapper-gtk/po/fi.po new file mode 100644 index 00000000..c012bfad --- /dev/null +++ b/src/lib/clapper-gtk/po/fi.po @@ -0,0 +1,66 @@ +# Finnish translations for clapper-gtk package. +# Copyright (C) 2024 THE clapper-gtk'S COPYRIGHT HOLDER +# This file is distributed under the same license as the clapper-gtk package. +# Automatically generated, 2024. +# +msgid "" +msgstr "" +"Project-Id-Version: clapper-gtk\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-04-05 20:50+0200\n" +"PO-Revision-Date: 2024-03-07 20:49+0100\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: fi\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:9 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:227 +msgid "Video" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:17 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:228 +msgid "Audio" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:25 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:229 +msgid "Subtitles" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:28 +msgid "Enabled" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:32 +msgid "Open…" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:75 +msgid "Mute" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:119 +msgid "Reset" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:126 +msgid "Undetermined" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 +msgid "Channels" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-title-label.c:91 +msgid "No media" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-title-label.c:103 +msgid "Unknown title" +msgstr "" diff --git a/src/lib/clapper-gtk/po/fr.po b/src/lib/clapper-gtk/po/fr.po new file mode 100644 index 00000000..da3b6737 --- /dev/null +++ b/src/lib/clapper-gtk/po/fr.po @@ -0,0 +1,66 @@ +# French translations for clapper-gtk package. +# Copyright (C) 2024 THE clapper-gtk'S COPYRIGHT HOLDER +# This file is distributed under the same license as the clapper-gtk package. +# Automatically generated, 2024. +# +msgid "" +msgstr "" +"Project-Id-Version: clapper-gtk\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-04-05 20:50+0200\n" +"PO-Revision-Date: 2024-03-07 20:49+0100\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: fr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:9 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:227 +msgid "Video" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:17 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:228 +msgid "Audio" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:25 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:229 +msgid "Subtitles" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:28 +msgid "Enabled" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:32 +msgid "Open…" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:75 +msgid "Mute" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:119 +msgid "Reset" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:126 +msgid "Undetermined" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 +msgid "Channels" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-title-label.c:91 +msgid "No media" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-title-label.c:103 +msgid "Unknown title" +msgstr "" diff --git a/src/lib/clapper-gtk/po/he.po b/src/lib/clapper-gtk/po/he.po new file mode 100644 index 00000000..a56c6620 --- /dev/null +++ b/src/lib/clapper-gtk/po/he.po @@ -0,0 +1,66 @@ +# Hebrew translations for clapper-gtk package. +# Copyright (C) 2024 THE clapper-gtk'S COPYRIGHT HOLDER +# This file is distributed under the same license as the clapper-gtk package. +# Automatically generated, 2024. +# +msgid "" +msgstr "" +"Project-Id-Version: clapper-gtk\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-04-05 20:50+0200\n" +"PO-Revision-Date: 2024-03-07 20:49+0100\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: he\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:9 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:227 +msgid "Video" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:17 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:228 +msgid "Audio" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:25 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:229 +msgid "Subtitles" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:28 +msgid "Enabled" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:32 +msgid "Open…" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:75 +msgid "Mute" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:119 +msgid "Reset" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:126 +msgid "Undetermined" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 +msgid "Channels" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-title-label.c:91 +msgid "No media" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-title-label.c:103 +msgid "Unknown title" +msgstr "" diff --git a/src/lib/clapper-gtk/po/hr.po b/src/lib/clapper-gtk/po/hr.po new file mode 100644 index 00000000..c6eea09d --- /dev/null +++ b/src/lib/clapper-gtk/po/hr.po @@ -0,0 +1,67 @@ +# Croatian translations for clapper-gtk package. +# Copyright (C) 2024 THE clapper-gtk'S COPYRIGHT HOLDER +# This file is distributed under the same license as the clapper-gtk package. +# Automatically generated, 2024. +# +msgid "" +msgstr "" +"Project-Id-Version: clapper-gtk\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-04-05 20:50+0200\n" +"PO-Revision-Date: 2024-03-07 20:49+0100\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: hr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " +"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:9 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:227 +msgid "Video" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:17 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:228 +msgid "Audio" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:25 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:229 +msgid "Subtitles" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:28 +msgid "Enabled" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:32 +msgid "Open…" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:75 +msgid "Mute" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:119 +msgid "Reset" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:126 +msgid "Undetermined" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 +msgid "Channels" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-title-label.c:91 +msgid "No media" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-title-label.c:103 +msgid "Unknown title" +msgstr "" diff --git a/src/lib/clapper-gtk/po/hu.po b/src/lib/clapper-gtk/po/hu.po new file mode 100644 index 00000000..e1c002dd --- /dev/null +++ b/src/lib/clapper-gtk/po/hu.po @@ -0,0 +1,66 @@ +# Hungarian translations for clapper-gtk package. +# Copyright (C) 2024 THE clapper-gtk'S COPYRIGHT HOLDER +# This file is distributed under the same license as the clapper-gtk package. +# Automatically generated, 2024. +# +msgid "" +msgstr "" +"Project-Id-Version: clapper-gtk\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-04-05 20:50+0200\n" +"PO-Revision-Date: 2024-03-07 20:49+0100\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: hu\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:9 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:227 +msgid "Video" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:17 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:228 +msgid "Audio" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:25 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:229 +msgid "Subtitles" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:28 +msgid "Enabled" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:32 +msgid "Open…" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:75 +msgid "Mute" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:119 +msgid "Reset" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:126 +msgid "Undetermined" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 +msgid "Channels" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-title-label.c:91 +msgid "No media" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-title-label.c:103 +msgid "Unknown title" +msgstr "" diff --git a/src/lib/clapper-gtk/po/it.po b/src/lib/clapper-gtk/po/it.po new file mode 100644 index 00000000..4157f052 --- /dev/null +++ b/src/lib/clapper-gtk/po/it.po @@ -0,0 +1,66 @@ +# Italian translations for clapper-gtk package. +# Copyright (C) 2024 THE clapper-gtk'S COPYRIGHT HOLDER +# This file is distributed under the same license as the clapper-gtk package. +# Automatically generated, 2024. +# +msgid "" +msgstr "" +"Project-Id-Version: clapper-gtk\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-04-05 20:50+0200\n" +"PO-Revision-Date: 2024-03-07 20:49+0100\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: it\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:9 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:227 +msgid "Video" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:17 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:228 +msgid "Audio" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:25 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:229 +msgid "Subtitles" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:28 +msgid "Enabled" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:32 +msgid "Open…" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:75 +msgid "Mute" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:119 +msgid "Reset" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:126 +msgid "Undetermined" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 +msgid "Channels" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-title-label.c:91 +msgid "No media" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-title-label.c:103 +msgid "Unknown title" +msgstr "" diff --git a/src/lib/clapper-gtk/po/ja.po b/src/lib/clapper-gtk/po/ja.po new file mode 100644 index 00000000..046a7f28 --- /dev/null +++ b/src/lib/clapper-gtk/po/ja.po @@ -0,0 +1,66 @@ +# Japanese translations for clapper-gtk package. +# Copyright (C) 2024 THE clapper-gtk'S COPYRIGHT HOLDER +# This file is distributed under the same license as the clapper-gtk package. +# Automatically generated, 2024. +# +msgid "" +msgstr "" +"Project-Id-Version: clapper-gtk\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-04-05 20:50+0200\n" +"PO-Revision-Date: 2024-03-07 20:49+0100\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: ja\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:9 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:227 +msgid "Video" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:17 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:228 +msgid "Audio" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:25 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:229 +msgid "Subtitles" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:28 +msgid "Enabled" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:32 +msgid "Open…" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:75 +msgid "Mute" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:119 +msgid "Reset" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:126 +msgid "Undetermined" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 +msgid "Channels" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-title-label.c:91 +msgid "No media" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-title-label.c:103 +msgid "Unknown title" +msgstr "" diff --git a/src/lib/clapper-gtk/po/lt.po b/src/lib/clapper-gtk/po/lt.po new file mode 100644 index 00000000..174ccb5f --- /dev/null +++ b/src/lib/clapper-gtk/po/lt.po @@ -0,0 +1,67 @@ +# Lithuanian translations for clapper-gtk package. +# Copyright (C) 2024 THE clapper-gtk'S COPYRIGHT HOLDER +# This file is distributed under the same license as the clapper-gtk package. +# Automatically generated, 2024. +# +msgid "" +msgstr "" +"Project-Id-Version: clapper-gtk\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-04-05 20:50+0200\n" +"PO-Revision-Date: 2024-03-07 20:49+0100\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: lt\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " +"(n%100<10 || n%100>=20) ? 1 : 2);\n" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:9 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:227 +msgid "Video" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:17 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:228 +msgid "Audio" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:25 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:229 +msgid "Subtitles" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:28 +msgid "Enabled" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:32 +msgid "Open…" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:75 +msgid "Mute" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:119 +msgid "Reset" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:126 +msgid "Undetermined" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 +msgid "Channels" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-title-label.c:91 +msgid "No media" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-title-label.c:103 +msgid "Unknown title" +msgstr "" diff --git a/src/lib/clapper-gtk/po/meson.build b/src/lib/clapper-gtk/po/meson.build new file mode 100644 index 00000000..8072d4d9 --- /dev/null +++ b/src/lib/clapper-gtk/po/meson.build @@ -0,0 +1 @@ +i18n.gettext(meson.project_name() + '-gtk', preset: 'glib') diff --git a/src/lib/clapper-gtk/po/nl.po b/src/lib/clapper-gtk/po/nl.po new file mode 100644 index 00000000..ca71250d --- /dev/null +++ b/src/lib/clapper-gtk/po/nl.po @@ -0,0 +1,66 @@ +# Dutch translations for clapper-gtk package. +# Copyright (C) 2024 THE clapper-gtk'S COPYRIGHT HOLDER +# This file is distributed under the same license as the clapper-gtk package. +# Automatically generated, 2024. +# +msgid "" +msgstr "" +"Project-Id-Version: clapper-gtk\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-04-05 20:50+0200\n" +"PO-Revision-Date: 2024-03-07 20:49+0100\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: nl\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:9 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:227 +msgid "Video" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:17 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:228 +msgid "Audio" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:25 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:229 +msgid "Subtitles" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:28 +msgid "Enabled" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:32 +msgid "Open…" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:75 +msgid "Mute" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:119 +msgid "Reset" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:126 +msgid "Undetermined" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 +msgid "Channels" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-title-label.c:91 +msgid "No media" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-title-label.c:103 +msgid "Unknown title" +msgstr "" diff --git a/src/lib/clapper-gtk/po/pl.po b/src/lib/clapper-gtk/po/pl.po new file mode 100644 index 00000000..c7b56599 --- /dev/null +++ b/src/lib/clapper-gtk/po/pl.po @@ -0,0 +1,67 @@ +# Polish translations for clapper-gtk package. +# Copyright (C) 2024 THE clapper-gtk'S COPYRIGHT HOLDER +# This file is distributed under the same license as the clapper-gtk package. +# Automatically generated, 2024. +# +msgid "" +msgstr "" +"Project-Id-Version: clapper-gtk\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-04-05 20:50+0200\n" +"PO-Revision-Date: 2024-03-07 20:49+0100\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: pl\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 " +"|| n%100>=20) ? 1 : 2);\n" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:9 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:227 +msgid "Video" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:17 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:228 +msgid "Audio" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:25 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:229 +msgid "Subtitles" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:28 +msgid "Enabled" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:32 +msgid "Open…" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:75 +msgid "Mute" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:119 +msgid "Reset" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:126 +msgid "Undetermined" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 +msgid "Channels" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-title-label.c:91 +msgid "No media" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-title-label.c:103 +msgid "Unknown title" +msgstr "" diff --git a/src/lib/clapper-gtk/po/pt.po b/src/lib/clapper-gtk/po/pt.po new file mode 100644 index 00000000..f5e0cc2e --- /dev/null +++ b/src/lib/clapper-gtk/po/pt.po @@ -0,0 +1,66 @@ +# Portuguese translations for clapper-gtk package. +# Copyright (C) 2024 THE clapper-gtk'S COPYRIGHT HOLDER +# This file is distributed under the same license as the clapper-gtk package. +# Automatically generated, 2024. +# +msgid "" +msgstr "" +"Project-Id-Version: clapper-gtk\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-04-05 20:50+0200\n" +"PO-Revision-Date: 2024-03-07 20:49+0100\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: pt\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:9 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:227 +msgid "Video" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:17 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:228 +msgid "Audio" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:25 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:229 +msgid "Subtitles" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:28 +msgid "Enabled" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:32 +msgid "Open…" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:75 +msgid "Mute" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:119 +msgid "Reset" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:126 +msgid "Undetermined" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 +msgid "Channels" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-title-label.c:91 +msgid "No media" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-title-label.c:103 +msgid "Unknown title" +msgstr "" diff --git a/src/lib/clapper-gtk/po/pt_BR.po b/src/lib/clapper-gtk/po/pt_BR.po new file mode 100644 index 00000000..50508028 --- /dev/null +++ b/src/lib/clapper-gtk/po/pt_BR.po @@ -0,0 +1,67 @@ +# Portuguese translations for clapper-gtk package +# Traduções em português brasileiro para o pacote clapper-gtk. +# Copyright (C) 2024 THE clapper-gtk'S COPYRIGHT HOLDER +# This file is distributed under the same license as the clapper-gtk package. +# Automatically generated, 2024. +# +msgid "" +msgstr "" +"Project-Id-Version: clapper-gtk\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-04-05 20:50+0200\n" +"PO-Revision-Date: 2024-03-07 20:49+0100\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: pt_BR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:9 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:227 +msgid "Video" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:17 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:228 +msgid "Audio" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:25 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:229 +msgid "Subtitles" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:28 +msgid "Enabled" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:32 +msgid "Open…" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:75 +msgid "Mute" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:119 +msgid "Reset" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:126 +msgid "Undetermined" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 +msgid "Channels" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-title-label.c:91 +msgid "No media" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-title-label.c:103 +msgid "Unknown title" +msgstr "" diff --git a/src/lib/clapper-gtk/po/ru.po b/src/lib/clapper-gtk/po/ru.po new file mode 100644 index 00000000..89a3405d --- /dev/null +++ b/src/lib/clapper-gtk/po/ru.po @@ -0,0 +1,67 @@ +# Russian translations for clapper-gtk package. +# Copyright (C) 2024 THE clapper-gtk'S COPYRIGHT HOLDER +# This file is distributed under the same license as the clapper-gtk package. +# Automatically generated, 2024. +# +msgid "" +msgstr "" +"Project-Id-Version: clapper-gtk\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-04-05 20:50+0200\n" +"PO-Revision-Date: 2024-03-07 20:49+0100\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: ru\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " +"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:9 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:227 +msgid "Video" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:17 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:228 +msgid "Audio" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:25 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:229 +msgid "Subtitles" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:28 +msgid "Enabled" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:32 +msgid "Open…" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:75 +msgid "Mute" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:119 +msgid "Reset" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:126 +msgid "Undetermined" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 +msgid "Channels" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-title-label.c:91 +msgid "No media" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-title-label.c:103 +msgid "Unknown title" +msgstr "" diff --git a/src/lib/clapper-gtk/po/sk.po b/src/lib/clapper-gtk/po/sk.po new file mode 100644 index 00000000..0d48f82a --- /dev/null +++ b/src/lib/clapper-gtk/po/sk.po @@ -0,0 +1,66 @@ +# Slovak translations for clapper-gtk package. +# Copyright (C) 2024 THE clapper-gtk'S COPYRIGHT HOLDER +# This file is distributed under the same license as the clapper-gtk package. +# Automatically generated, 2024. +# +msgid "" +msgstr "" +"Project-Id-Version: clapper-gtk\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-04-05 20:50+0200\n" +"PO-Revision-Date: 2024-03-07 20:49+0100\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: sk\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:9 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:227 +msgid "Video" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:17 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:228 +msgid "Audio" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:25 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:229 +msgid "Subtitles" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:28 +msgid "Enabled" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:32 +msgid "Open…" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:75 +msgid "Mute" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:119 +msgid "Reset" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:126 +msgid "Undetermined" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 +msgid "Channels" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-title-label.c:91 +msgid "No media" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-title-label.c:103 +msgid "Unknown title" +msgstr "" diff --git a/src/lib/clapper-gtk/po/sv.po b/src/lib/clapper-gtk/po/sv.po new file mode 100644 index 00000000..f53d620c --- /dev/null +++ b/src/lib/clapper-gtk/po/sv.po @@ -0,0 +1,66 @@ +# Swedish translations for clapper-gtk package. +# Copyright (C) 2024 THE clapper-gtk'S COPYRIGHT HOLDER +# This file is distributed under the same license as the clapper-gtk package. +# Automatically generated, 2024. +# +msgid "" +msgstr "" +"Project-Id-Version: clapper-gtk\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-04-05 20:50+0200\n" +"PO-Revision-Date: 2024-03-07 20:49+0100\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: sv\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:9 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:227 +msgid "Video" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:17 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:228 +msgid "Audio" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:25 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:229 +msgid "Subtitles" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:28 +msgid "Enabled" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:32 +msgid "Open…" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:75 +msgid "Mute" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:119 +msgid "Reset" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:126 +msgid "Undetermined" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 +msgid "Channels" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-title-label.c:91 +msgid "No media" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-title-label.c:103 +msgid "Unknown title" +msgstr "" diff --git a/src/lib/clapper-gtk/po/tr.po b/src/lib/clapper-gtk/po/tr.po new file mode 100644 index 00000000..143ea047 --- /dev/null +++ b/src/lib/clapper-gtk/po/tr.po @@ -0,0 +1,66 @@ +# Turkish translations for clapper-gtk package. +# Copyright (C) 2024 THE clapper-gtk'S COPYRIGHT HOLDER +# This file is distributed under the same license as the clapper-gtk package. +# Automatically generated, 2024. +# +msgid "" +msgstr "" +"Project-Id-Version: clapper-gtk\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-04-05 20:50+0200\n" +"PO-Revision-Date: 2024-03-07 20:49+0100\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: tr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:9 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:227 +msgid "Video" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:17 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:228 +msgid "Audio" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:25 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:229 +msgid "Subtitles" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:28 +msgid "Enabled" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:32 +msgid "Open…" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:75 +msgid "Mute" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:119 +msgid "Reset" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:126 +msgid "Undetermined" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 +msgid "Channels" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-title-label.c:91 +msgid "No media" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-title-label.c:103 +msgid "Unknown title" +msgstr "" diff --git a/src/lib/clapper-gtk/po/zh_CN.po b/src/lib/clapper-gtk/po/zh_CN.po new file mode 100644 index 00000000..e5b6567f --- /dev/null +++ b/src/lib/clapper-gtk/po/zh_CN.po @@ -0,0 +1,66 @@ +# Chinese translations for clapper-gtk package +# clapper-gtk Èí¼þ°üµÄ¼òÌåÖÐÎÄ·­Òë. +# Copyright (C) 2024 THE clapper-gtk'S COPYRIGHT HOLDER +# This file is distributed under the same license as the clapper-gtk package. +# Automatically generated, 2024. +# +msgid "" +msgstr "" +"Project-Id-Version: clapper-gtk\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-04-05 20:50+0200\n" +"PO-Revision-Date: 2024-03-07 20:49+0100\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: zh_CN\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:9 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:227 +msgid "Video" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:17 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:228 +msgid "Audio" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:25 +#: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:229 +msgid "Subtitles" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:28 +msgid "Enabled" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:32 +msgid "Open…" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:75 +msgid "Mute" +msgstr "" + +#: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:119 +msgid "Reset" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:126 +msgid "Undetermined" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 +msgid "Channels" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-title-label.c:91 +msgid "No media" +msgstr "" + +#: src/lib/clapper-gtk/clapper-gtk-title-label.c:103 +msgid "Unknown title" +msgstr "" diff --git a/src/lib/clapper-gtk/ui/clapper-gtk-billboard.ui b/src/lib/clapper-gtk/ui/clapper-gtk-billboard.ui new file mode 100644 index 00000000..e1156608 --- /dev/null +++ b/src/lib/clapper-gtk/ui/clapper-gtk-billboard.ui @@ -0,0 +1,161 @@ + + + + diff --git a/src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui b/src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui new file mode 100644 index 00000000..92604fc2 --- /dev/null +++ b/src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui @@ -0,0 +1,254 @@ + + + +
+ + top-box + + + Video +
+ + video-streams + +
+
+ + Audio +
+ + audio-streams + +
+
+ + Subtitles +
+ + Enabled + clappergtk.subtitle-stream-enabled + + + Open… + clappergtk.open-subtitle-stream + action-disabled + +
+
+ + subtitle-streams + +
+
+
+
+ +
diff --git a/src/lib/clapper-gtk/ui/clapper-gtk-seek-bar.ui b/src/lib/clapper-gtk/ui/clapper-gtk-seek-bar.ui new file mode 100644 index 00000000..264ecb49 --- /dev/null +++ b/src/lib/clapper-gtk/ui/clapper-gtk-seek-bar.ui @@ -0,0 +1,83 @@ + + + + + 0 + 0 + 0 + 0 + 8 + + diff --git a/src/lib/clapper-gtk/ui/clapper-gtk-simple-controls.ui b/src/lib/clapper-gtk/ui/clapper-gtk-simple-controls.ui new file mode 100644 index 00000000..62057441 --- /dev/null +++ b/src/lib/clapper-gtk/ui/clapper-gtk-simple-controls.ui @@ -0,0 +1,137 @@ + + + + diff --git a/src/lib/clapper-gtk/ui/clapper-gtk-status.ui b/src/lib/clapper-gtk/ui/clapper-gtk-status.ui new file mode 100644 index 00000000..e33dd04c --- /dev/null +++ b/src/lib/clapper-gtk/ui/clapper-gtk-status.ui @@ -0,0 +1,45 @@ + + + + diff --git a/src/lib/clapper-gtk/ui/clapper-gtk-stream-list-item.ui b/src/lib/clapper-gtk/ui/clapper-gtk-stream-list-item.ui new file mode 100644 index 00000000..5b453790 --- /dev/null +++ b/src/lib/clapper-gtk/ui/clapper-gtk-stream-list-item.ui @@ -0,0 +1,15 @@ + + + + diff --git a/src/lib/clapper-gtk/ui/clapper-gtk-title-header.ui b/src/lib/clapper-gtk/ui/clapper-gtk-title-header.ui new file mode 100644 index 00000000..fae5d27b --- /dev/null +++ b/src/lib/clapper-gtk/ui/clapper-gtk-title-header.ui @@ -0,0 +1,22 @@ + + + + diff --git a/src/lib/clapper-gtk/ui/clapper-gtk-video.ui b/src/lib/clapper-gtk/ui/clapper-gtk-video.ui new file mode 100644 index 00000000..8ee8db7d --- /dev/null +++ b/src/lib/clapper-gtk/ui/clapper-gtk-video.ui @@ -0,0 +1,48 @@ + + + + diff --git a/src/lib/meson.build b/src/lib/meson.build index 95ce0967..70569fb3 100644 --- a/src/lib/meson.build +++ b/src/lib/meson.build @@ -12,3 +12,4 @@ gir_init_section = '--add-init-section=extern void gst_init(gint*,gchar**);' + \ subdir('gst') subdir('clapper') +subdir('clapper-gtk')