diff options
author | Benjamin Otte <otte@redhat.com> | 2018-03-01 04:52:27 +0100 |
---|---|---|
committer | Benjamin Otte <otte@redhat.com> | 2018-03-18 21:01:23 +0100 |
commit | 7cf6da60da07f3b88c7a2b7900e47df8f743991e (patch) | |
tree | 8921af8ac9a761c7fa4932d484dbb33ddd68a025 | |
parent | 9700a98f4867ee523e9366419c36d580c57c88fa (diff) | |
download | gtk+-7cf6da60da07f3b88c7a2b7900e47df8f743991e.tar.gz |
gtk: Add GtkVideo
GtkVideo is a simple video player widget. It probably needs some more
configurability, but it does its job.
-rw-r--r-- | gtk/gtk.h | 1 | ||||
-rw-r--r-- | gtk/gtkvideo.c | 615 | ||||
-rw-r--r-- | gtk/gtkvideo.h | 64 | ||||
-rw-r--r-- | gtk/meson.build | 2 | ||||
-rw-r--r-- | gtk/theme/Adwaita/_common.scss | 13 | ||||
-rw-r--r-- | gtk/ui/gtkvideo.ui | 47 |
6 files changed, 742 insertions, 0 deletions
@@ -228,6 +228,7 @@ #include <gtk/gtktypebuiltins.h> #include <gtk/gtktypes.h> #include <gtk/gtkversion.h> +#include <gtk/gtkvideo.h> #include <gtk/gtkviewport.h> #include <gtk/gtkvolumebutton.h> #include <gtk/gtkwidget.h> diff --git a/gtk/gtkvideo.c b/gtk/gtkvideo.c new file mode 100644 index 0000000000..bf51a236f8 --- /dev/null +++ b/gtk/gtkvideo.c @@ -0,0 +1,615 @@ +/* + * Copyright © 2018 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + * + * Authors: Benjamin Otte <otte@gnome.org> + */ + +#include "config.h" + +#include "gtkvideo.h" + +#include "gtkeventcontrollermotion.h" +#include "gtkimage.h" +#include "gtkintl.h" +#include "gtkmediacontrols.h" +#include "gtkmediafile.h" +#include "gtkrevealer.h" + +/** + * SECTION:gtkvideo + * @title: GtkVideo + * @short_description: A widget for displaying video + * + * GtkVideo is a widget to show a #GtkMediaStream. + */ + +struct _GtkVideo +{ + GtkWidget parent_instance; + + GFile *file; + GtkMediaStream *media_stream; + + GtkEventController *motion_controller; + + GtkWidget *box; + GtkWidget *video_image; + GtkWidget *overlay_icon; + GtkWidget *controls_revealer; + GtkWidget *controls; + guint controls_hide_source; +}; + +enum +{ + PROP_0, + PROP_FILE, + PROP_MEDIA_STREAM, + + N_PROPS +}; + +G_DEFINE_TYPE (GtkVideo, gtk_video, GTK_TYPE_WIDGET) + +static GParamSpec *properties[N_PROPS] = { NULL, }; + +static void +gtk_video_measure (GtkWidget *widget, + GtkOrientation orientation, + int for_size, + int *minimum, + int *natural, + int *minimum_baseline, + int *natural_baseline) +{ + GtkVideo *self = GTK_VIDEO (widget); + + gtk_widget_measure (self->box, + orientation, + for_size, + minimum, natural, + minimum_baseline, natural_baseline); +} + +static void +gtk_video_size_allocate (GtkWidget *widget, + const GtkAllocation *allocation, + int baseline, + GtkAllocation *out_clip) +{ + GtkVideo *self = GTK_VIDEO (widget); + + gtk_widget_size_allocate (self->box, allocation, baseline, out_clip); +} + +static void +gtk_video_unmap (GtkWidget *widget) +{ + GtkVideo *self = GTK_VIDEO (widget); + + if (self->controls_hide_source) + { + g_source_remove (self->controls_hide_source); + self->controls_hide_source = 0; + gtk_revealer_set_reveal_child (GTK_REVEALER (self->controls_revealer), FALSE); + } + + /* XXX: pause video here? */ + + GTK_WIDGET_CLASS (gtk_video_parent_class)->unmap (widget); +} + +static void +gtk_video_dispose (GObject *object) +{ + GtkVideo *self = GTK_VIDEO (object); + + gtk_video_set_media_stream (self, NULL); + + g_clear_object (&self->motion_controller); + g_clear_pointer (&self->box, gtk_widget_unparent); + + G_OBJECT_CLASS (gtk_video_parent_class)->dispose (object); +} + +static void +gtk_video_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GtkVideo *self = GTK_VIDEO (object); + + switch (property_id) + { + case PROP_FILE: + g_value_set_object (value, self->file); + break; + + case PROP_MEDIA_STREAM: + g_value_set_object (value, self->media_stream); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gtk_video_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkVideo *self = GTK_VIDEO (object); + + switch (property_id) + { + case PROP_FILE: + gtk_video_set_file (self, g_value_get_object (value)); + break; + + case PROP_MEDIA_STREAM: + gtk_video_set_media_stream (self, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gtk_video_class_init (GtkVideoClass *klass) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + widget_class->measure = gtk_video_measure; + widget_class->size_allocate = gtk_video_size_allocate; + widget_class->unmap = gtk_video_unmap; + + gobject_class->dispose = gtk_video_dispose; + gobject_class->get_property = gtk_video_get_property; + gobject_class->set_property = gtk_video_set_property; + + /** + * GtkVideo:file: + * + * The file played by this video if the video is playing a file. + */ + properties[PROP_FILE] = + g_param_spec_object ("file", + P_("File"), + P_("The video file played back"), + G_TYPE_FILE, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + /** + * GtkVideo:media-stream: + * + * The media-stream played + */ + properties[PROP_MEDIA_STREAM] = + g_param_spec_object ("media-stream", + P_("Media Stream"), + P_("The media stream played"), + GTK_TYPE_MEDIA_STREAM, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (gobject_class, N_PROPS, properties); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gtk/libgtk/ui/gtkvideo.ui"); + gtk_widget_class_bind_template_child (widget_class, GtkVideo, box); + gtk_widget_class_bind_template_child (widget_class, GtkVideo, video_image); + gtk_widget_class_bind_template_child (widget_class, GtkVideo, overlay_icon); + gtk_widget_class_bind_template_child (widget_class, GtkVideo, controls); + gtk_widget_class_bind_template_child (widget_class, GtkVideo, controls_revealer); + + gtk_widget_class_set_css_name (widget_class, I_("video")); +} + +static gboolean +gtk_video_hide_controls (gpointer data) +{ + GtkVideo *self = data; + + gtk_revealer_set_reveal_child (GTK_REVEALER (self->controls_revealer), FALSE); + + self->controls_hide_source = 0; + + return G_SOURCE_REMOVE; +} + +static void +gtk_video_motion (GtkEventControllerMotion *motion, + double x, + double y, + GtkVideo *self) +{ + gtk_revealer_set_reveal_child (GTK_REVEALER (self->controls_revealer), TRUE); + if (self->controls_hide_source) + g_source_remove (self->controls_hide_source); + self->controls_hide_source = g_timeout_add (5 * 1000, + gtk_video_hide_controls, + self); +} + +static void +gtk_video_init (GtkVideo *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); + gtk_widget_set_has_window (GTK_WIDGET (self), FALSE); + + self->motion_controller = gtk_event_controller_motion_new (GTK_WIDGET (self)); + g_signal_connect (self->motion_controller, "motion", G_CALLBACK (gtk_video_motion), self); +} + +/** + * gtk_video_new: + * + * Creates a new empty #GtkVideo. + * + * Returns: a new #GtkVideo + **/ +GtkWidget * +gtk_video_new (void) +{ + return g_object_new (GTK_TYPE_VIDEO, NULL); +} + +/** + * gtk_video_new_for_media_stream: + * @stream: (allow-none): a #GtkMediaStream + * + * Creates a #GtkVideo to play back the given @stream. + * + * Returns: a new #GtkVideo + **/ +GtkWidget * +gtk_video_new_for_media_stream (GtkMediaStream *stream) +{ + g_return_val_if_fail (stream == NULL || GTK_IS_MEDIA_STREAM (stream), NULL); + + return g_object_new (GTK_TYPE_VIDEO, + "media-stream", stream, + NULL); +} + +/** + * gtk_video_new_for_file: + * @file: (allow-none): a #GFile + * + * Creates a #GtkVideo to play back the given @file. + * + * Returns: a new #GtkVideo + **/ +GtkWidget * +gtk_video_new_for_file (GFile *file) +{ + g_return_val_if_fail (file == NULL || G_IS_FILE (file), NULL); + + return g_object_new (GTK_TYPE_VIDEO, + "file", file, + NULL); +} + +/** + * gtk_video_new_for_filename: + * @filename: (allow-none): filename to play back + * + * Creates a #GtkVideo to play back the given @filename. + * + * This is a utility function that calls gtk_video_new_for_file(), + * + * Returns: a new #GtkVideo + **/ +GtkWidget * +gtk_video_new_for_filename (const char *filename) +{ + GtkWidget *result; + GFile *file; + + if (filename) + file = g_file_new_for_path (filename); + else + file = NULL; + + result = gtk_video_new_for_file (file); + + if (file) + g_object_unref (file); + + return result; +} + +/** + * gtk_video_new_for_resource: + * @filename: (allow-none): resource path to play back + * + * Creates a #GtkVideo to play back the resource at the + * given @resource_path. + * + * This is a utility function that calls gtk_video_new_for_file(), + * + * Returns: a new #GtkVideo + **/ +GtkWidget * +gtk_video_new_for_resource (const char *resource_path) +{ + GtkWidget *result; + GFile *file; + + if (resource_path) + { + char *uri, *escaped; + + escaped = g_uri_escape_string (resource_path, + G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, FALSE); + uri = g_strconcat ("resource://", escaped, NULL); + g_free (escaped); + + file = g_file_new_for_uri (uri); + g_free (uri); + } + else + { + file = NULL; + } + + result = gtk_video_new_for_file (file); + + if (file) + g_object_unref (file); + + return result; +} + +/** + * gtk_video_get_media_stream: + * @self: a #GtkVideo + * + * Gets the media stream managed by @self or %NULL if none. + * + * Returns: (nullable): The media stream managed by @self + **/ +GtkMediaStream * +gtk_video_get_media_stream (GtkVideo *self) +{ + g_return_val_if_fail (GTK_IS_VIDEO (self), NULL); + + return self->media_stream; +} + +static void +gtk_video_update_overlay_icon (GtkVideo *self) +{ + const char *icon_name; + const GError *error = NULL; + + if (self->media_stream == NULL) + icon_name = "media-eject-symbolic"; + else if ((error = gtk_media_stream_get_error (self->media_stream))) + icon_name = "dialog-error-symbolic"; + else if (gtk_media_stream_get_ended (self->media_stream)) + icon_name = "media-playlist-repeat-symbolic"; + else + icon_name = "media-playback-start-symbolic"; + + gtk_image_set_from_icon_name (GTK_IMAGE (self->overlay_icon), icon_name); + if (error) + gtk_widget_set_tooltip_text (self->overlay_icon, error->message); + else + gtk_widget_set_tooltip_text (self->overlay_icon, NULL); +} + +static void +gtk_video_update_ended (GtkVideo *self) +{ + gtk_video_update_overlay_icon (self); +} + +static void +gtk_video_update_error (GtkVideo *self) +{ + gtk_video_update_overlay_icon (self); +} + +static void +gtk_video_update_playing (GtkVideo *self) +{ + gboolean playing; + + if (self->media_stream != NULL) + playing = gtk_media_stream_get_playing (self->media_stream); + else + playing = FALSE; + + gtk_widget_set_visible (self->overlay_icon, !playing); +} + +static void +gtk_video_update_all (GtkVideo *self) +{ + gtk_video_update_ended (self); + gtk_video_update_error (self); + gtk_video_update_playing (self); +} + +static void +gtk_video_notify_cb (GtkMediaStream *stream, + GParamSpec *pspec, + GtkVideo *self) +{ + if (g_str_equal (pspec->name, "ended")) + gtk_video_update_ended (self); + if (g_str_equal (pspec->name, "error")) + gtk_video_update_error (self); + if (g_str_equal (pspec->name, "playing")) + gtk_video_update_playing (self); +} + +/** + * gtk_video_set_media_stream: + * @self: a #GtkVideo + * @stream: (allow-none): The media stream to play or %NULL to unset + * + * Sets the media stream to be played back. @self will take full control + * of managing the media stream. If you want to manage a media stream + * yourself, consider using a #GtkImage for display. + * + * If you want to display a file, consider using gtk_video_set_file() + * instead. + **/ +void +gtk_video_set_media_stream (GtkVideo *self, + GtkMediaStream *stream) +{ + g_return_if_fail (GTK_IS_VIDEO (self)); + g_return_if_fail (stream == NULL || GTK_IS_MEDIA_STREAM (stream)); + + if (self->media_stream == stream) + return; + + if (self->media_stream) + { + g_signal_handlers_disconnect_by_func (self->media_stream, + gtk_video_notify_cb, + self); + g_object_unref (self->media_stream); + self->media_stream = NULL; + } + + if (stream) + { + self->media_stream = g_object_ref (stream); + g_signal_connect (self->media_stream, + "notify", + G_CALLBACK (gtk_video_notify_cb), + self); + } + + gtk_media_controls_set_media_stream (GTK_MEDIA_CONTROLS (self->controls), stream); + gtk_image_set_from_paintable (GTK_IMAGE (self->video_image), GDK_PAINTABLE (stream)); + + gtk_video_update_all (self); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MEDIA_STREAM]); +} + +/** + * gtk_video_set_file: + * @self: a #GtkVideo + * @file: (allow-none): the file to play + * + * Makes @self play the given @file. + **/ +void +gtk_video_set_file (GtkVideo *self, + GFile *file) +{ + GtkMediaStream *stream; + + g_return_if_fail (GTK_IS_VIDEO (self)); + g_return_if_fail (file == NULL || G_IS_FILE (file)); + + if (!g_set_object (&self->file, file)) + return; + + g_object_freeze_notify (G_OBJECT (self)); + + if (file) + stream = gtk_media_file_new_for_file (file); + else + stream = NULL; + + gtk_video_set_media_stream (self, stream); + + if (stream) + g_object_unref (stream); + + g_object_thaw_notify (G_OBJECT (self)); +} + +/** + * gtk_video_set_resource: + * @self: a #GtkVideo + * @filename: (allow-none): the filename to play + * + * Makes @self play the given @filename. + * + * This is a utility function that calls gtk_video_set_file(), + **/ +void +gtk_video_set_filename (GtkVideo *self, + const char *filename) +{ + GFile *file; + + g_return_if_fail (GTK_IS_VIDEO (self)); + + if (filename) + file = g_file_new_for_path (filename); + else + file = NULL; + + gtk_video_set_file (self, file); + + if (file) + g_object_unref (file); +} + +/** + * gtk_video_set_resource: + * @self: a #GtkVideo + * @resource_path: (allow-none): the resource to set + * + * Makes @self play the resource at the given @resource_path. + * + * This is a utility function that calls gtk_video_set_file(), + **/ +void +gtk_video_set_resource (GtkVideo *self, + const char *resource_path) +{ + GFile *file; + + g_return_if_fail (GTK_IS_VIDEO (self)); + + if (resource_path) + { + char *uri, *escaped; + + escaped = g_uri_escape_string (resource_path, + G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, FALSE); + uri = g_strconcat ("resource://", escaped, NULL); + g_free (escaped); + + file = g_file_new_for_uri (uri); + g_free (uri); + } + else + { + file = NULL; + } + + gtk_video_set_file (self, file); + + if (file) + g_object_unref (file); +} + diff --git a/gtk/gtkvideo.h b/gtk/gtkvideo.h new file mode 100644 index 0000000000..16ffbbfcc6 --- /dev/null +++ b/gtk/gtkvideo.h @@ -0,0 +1,64 @@ +/* + * Copyright © 2018 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see <http://www.gnu.org/licenses/>. + * + * Authors: Benjamin Otte <otte@gnome.org> + */ + +#ifndef __GTK_VIDEO_H__ +#define __GTK_VIDEO_H__ + +#include <gtk/gtkmediastream.h> +#include <gtk/gtkwidget.h> + +G_BEGIN_DECLS + +#define GTK_TYPE_VIDEO (gtk_video_get_type ()) + +GDK_AVAILABLE_IN_ALL +G_DECLARE_FINAL_TYPE (GtkVideo, gtk_video, GTK, VIDEO, GtkWidget) + +GDK_AVAILABLE_IN_ALL +GtkWidget * gtk_video_new (void); +GDK_AVAILABLE_IN_ALL +GtkWidget * gtk_video_new_for_media_stream (GtkMediaStream *stream); +GDK_AVAILABLE_IN_ALL +GtkWidget * gtk_video_new_for_file (GFile *file); +GDK_AVAILABLE_IN_ALL +GtkWidget * gtk_video_new_for_filename (const char *filename); +GDK_AVAILABLE_IN_ALL +GtkWidget * gtk_video_new_for_resource (const char *resource_path); + +GDK_AVAILABLE_IN_ALL +GtkMediaStream *gtk_video_get_media_stream (GtkVideo *self); +GDK_AVAILABLE_IN_ALL +void gtk_video_set_media_stream (GtkVideo *self, + GtkMediaStream *stream); +GDK_AVAILABLE_IN_ALL +GFile * gtk_video_get_file (GtkVideo *self); +GDK_AVAILABLE_IN_ALL +void gtk_video_set_file (GtkVideo *self, + GFile *file); +GDK_AVAILABLE_IN_ALL +void gtk_video_set_filename (GtkVideo *self, + const char *filename); +GDK_AVAILABLE_IN_ALL +void gtk_video_set_resource (GtkVideo *self, + const char *resource_path); + + +G_END_DECLS + +#endif /* __GTK_VIDEO_H__ */ diff --git a/gtk/meson.build b/gtk/meson.build index 22d849f2e9..c06b5aa075 100644 --- a/gtk/meson.build +++ b/gtk/meson.build @@ -358,6 +358,7 @@ gtk_public_sources = files([ 'gtktreeview.c', 'gtktreeviewcolumn.c', 'gtkutils.c', + 'gtkvideo.c', 'gtkviewport.c', 'gtkvolumebutton.c', 'gtkwidget.c', @@ -573,6 +574,7 @@ gtk_public_headers = files([ 'gtktreeview.h', 'gtktreeviewcolumn.h', 'gtktypes.h', + 'gtkvideo.h', 'gtkviewport.h', 'gtkvolumebutton.h', 'gtkwidget.h', diff --git a/gtk/theme/Adwaita/_common.scss b/gtk/theme/Adwaita/_common.scss index 3e28edd0f3..783df4fbf6 100644 --- a/gtk/theme/Adwaita/_common.scss +++ b/gtk/theme/Adwaita/_common.scss @@ -3999,6 +3999,19 @@ paned { /************** + * GtkVideo * + **************/ + +video { + & image.osd { + min-width: 64px; + min-height: 64px; + border-radius: 32px; + } + background: black; +} + +/************** * GtkInfoBar * **************/ infobar { diff --git a/gtk/ui/gtkvideo.ui b/gtk/ui/gtkvideo.ui new file mode 100644 index 0000000000..8acd989bc2 --- /dev/null +++ b/gtk/ui/gtkvideo.ui @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface domain="gtk40"> + <!-- interface-requires gtk+ 3.6 --> + <template class="GtkVideo" parent="GtkWidget"> + <child> + <object class="GtkOverlay" id="box"> + <child> + <object class="GtkImage" id="video_image"> + <property name="can-shrink">1</property> + </object> + </child> + <child type="overlay"> + <object class="GtkImage" id="overlay_icon"> + <style> + <class name="osd"/> + <class name="circular"/> + </style> + <property name="halign">center</property> + <property name="valign">center</property> + <property name="icon-name">media-playback-start-symbolic</property> + <property name="icon-size">large</property> + </object> + <packing> + <property name="measure">1</property> + </packing> + </child> + <child type="overlay"> + <object class="GtkRevealer" id="controls_revealer"> + <property name="reveal-child">0</property> + <property name="valign">end</property> + <child> + <object class="GtkMediaControls" id="controls"> + <style> + <class name="osd"/> + <class name="bottom"/> + </style> + </object> + </child> + </object> + <packing> + <property name="measure">1</property> + </packing> + </child> + </object> + </child> + </template> +</interface> |