diff options
-rw-r--r-- | gtk/gtk.h | 1 | ||||
-rw-r--r-- | gtk/gtkmediacontrols.c | 501 | ||||
-rw-r--r-- | gtk/gtkmediacontrols.h | 45 | ||||
-rw-r--r-- | gtk/meson.build | 2 | ||||
-rw-r--r-- | gtk/ui/gtkmediacontrols.ui | 64 |
5 files changed, 613 insertions, 0 deletions
@@ -137,6 +137,7 @@ #include <gtk/gtkliststore.h> #include <gtk/gtklockbutton.h> #include <gtk/gtkmain.h> +#include <gtk/gtkmediacontrols.h> #include <gtk/gtkmediafile.h> #include <gtk/gtkmediastream.h> #include <gtk/gtkmenu.h> diff --git a/gtk/gtkmediacontrols.c b/gtk/gtkmediacontrols.c new file mode 100644 index 0000000000..1f299bd2f2 --- /dev/null +++ b/gtk/gtkmediacontrols.c @@ -0,0 +1,501 @@ +/* + * 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 "gtkmediacontrols.h" + +#include "gtkadjustment.h" +#include "gtkbutton.h" +#include "gtkintl.h" +#include "gtklabel.h" + +/** + * SECTION:gtkmediacontrols + * @title: GtkMediaControls + * @short_description: A widget showing controls for a media stream + * + * GtkMediaControls is a widget to show controls for a #GtkMediaStream + * and giving users a way to use it. + */ + +struct _GtkMediaControls +{ + GtkWidget parent_instance; + + GtkMediaStream *stream; + + GtkAdjustment *time_adjustment; + GtkAdjustment *volume_adjustment; + GtkWidget *box; + GtkWidget *play_button; + GtkWidget *time_box; + GtkWidget *time_label; + GtkWidget *seek_scale; + GtkWidget *duration_label; +}; + +enum +{ + PROP_0, + PROP_MEDIA_STREAM, + + N_PROPS +}; + +G_DEFINE_TYPE (GtkMediaControls, gtk_media_controls, GTK_TYPE_WIDGET) + +static GParamSpec *properties[N_PROPS] = { NULL, }; + +/* FIXME: Remove + * See https://bugzilla.gnome.org/show_bug.cgi?id=679850 */ +static char * +totem_time_to_string (gint64 usecs, + gboolean remaining, + gboolean force_hour) +{ + int sec, min, hour, _time; + + _time = (int) (usecs / G_USEC_PER_SEC); + /* When calculating the remaining time, + * we want to make sure that: + * current time + time remaining = total run time */ + if (remaining) + _time++; + + sec = _time % 60; + _time = _time - sec; + min = (_time % (60*60)) / 60; + _time = _time - (min * 60); + hour = _time / (60*60); + + if (hour > 0 || force_hour) { + if (!remaining) { + /* hour:minutes:seconds */ + /* Translators: This is a time format, like "-9:05:02" for 9 + * hours, 5 minutes, and 2 seconds. You may change ":" to + * the separator that your locale uses or use "%Id" instead + * of "%d" if your locale uses localized digits. + */ + return g_strdup_printf (C_("long time format", "%d:%02d:%02d"), hour, min, sec); + } else { + /* -hour:minutes:seconds */ + /* Translators: This is a time format, like "-9:05:02" for 9 + * hours, 5 minutes, and 2 seconds playback remaining. You may + * change ":" to the separator that your locale uses or use + * "%Id" instead of "%d" if your locale uses localized digits. + */ + return g_strdup_printf (C_("long time format", "-%d:%02d:%02d"), hour, min, sec); + } + } + + if (remaining) { + /* -minutes:seconds */ + /* Translators: This is a time format, like "-5:02" for 5 + * minutes and 2 seconds playback remaining. You may change + * ":" to the separator that your locale uses or use "%Id" + * instead of "%d" if your locale uses localized digits. + */ + return g_strdup_printf (C_("short time format", "-%d:%02d"), min, sec); + } + + /* minutes:seconds */ + /* Translators: This is a time format, like "5:02" for 5 + * minutes and 2 seconds. You may change ":" to the + * separator that your locale uses or use "%Id" instead of + * "%d" if your locale uses localized digits. + */ + return g_strdup_printf (C_("short time format", "%d:%02d"), min, sec); +} + +static void +time_adjustment_changed (GtkAdjustment *adjustment, + GtkMediaControls *controls) +{ + if (controls->stream == NULL) + return; + + /* We just updated the adjustment and it's correct now */ + if (gtk_adjustment_get_value (adjustment) == (double) gtk_media_stream_get_timestamp (controls->stream) / G_USEC_PER_SEC) + return; + + gtk_media_stream_seek (controls->stream, + gtk_adjustment_get_value (adjustment) * G_USEC_PER_SEC + 0.5); +} + +static void +volume_adjustment_changed (GtkAdjustment *adjustment, + GtkMediaControls *controls) +{ + if (controls->stream == NULL) + return; + + /* We just updated the adjustment and it's correct now */ + if (gtk_adjustment_get_value (adjustment) == gtk_media_stream_get_volume (controls->stream)) + return; + + gtk_media_stream_set_muted (controls->stream, gtk_adjustment_get_value (adjustment) == 0.0); + gtk_media_stream_set_volume (controls->stream, gtk_adjustment_get_value (adjustment)); +} + +static void +play_button_clicked (GtkWidget *button, + GtkMediaControls *controls) +{ + if (controls->stream == NULL) + return; + + gtk_media_stream_set_playing (controls->stream, + !gtk_media_stream_get_playing (controls->stream)); +} + +static void +gtk_media_controls_measure (GtkWidget *widget, + GtkOrientation orientation, + int for_size, + int *minimum, + int *natural, + int *minimum_baseline, + int *natural_baseline) +{ + GtkMediaControls *controls = GTK_MEDIA_CONTROLS (widget); + + gtk_widget_measure (controls->box, + orientation, + for_size, + minimum, natural, + minimum_baseline, natural_baseline); +} + +static void +gtk_media_controls_size_allocate (GtkWidget *widget, + const GtkAllocation *allocation, + int baseline, + GtkAllocation *out_clip) +{ + GtkMediaControls *controls = GTK_MEDIA_CONTROLS (widget); + + gtk_widget_size_allocate (controls->box, allocation, baseline, out_clip); +} + +static void +gtk_media_controls_dispose (GObject *object) +{ + GtkMediaControls *controls = GTK_MEDIA_CONTROLS (object); + + gtk_media_controls_set_media_stream (controls, NULL); + + g_clear_pointer (&controls->box, gtk_widget_unparent); + + G_OBJECT_CLASS (gtk_media_controls_parent_class)->dispose (object); +} + +static void +gtk_media_controls_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GtkMediaControls *controls = GTK_MEDIA_CONTROLS (object); + + switch (property_id) + { + case PROP_MEDIA_STREAM: + g_value_set_object (value, controls->stream); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gtk_media_controls_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkMediaControls *controls = GTK_MEDIA_CONTROLS (object); + + switch (property_id) + { + case PROP_MEDIA_STREAM: + gtk_media_controls_set_media_stream (controls, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gtk_media_controls_class_init (GtkMediaControlsClass *klass) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + widget_class->measure = gtk_media_controls_measure; + widget_class->size_allocate = gtk_media_controls_size_allocate; + + gobject_class->dispose = gtk_media_controls_dispose; + gobject_class->get_property = gtk_media_controls_get_property; + gobject_class->set_property = gtk_media_controls_set_property; + + /** + * GtkMediaControls:media-stream: + * + * The media-stream managed by this object or %NULL if none. + */ + properties[PROP_MEDIA_STREAM] = + g_param_spec_object ("media-stream", + P_("Media Stream"), + P_("The media stream managed"), + 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/gtkmediacontrols.ui"); + gtk_widget_class_bind_template_child (widget_class, GtkMediaControls, time_adjustment); + gtk_widget_class_bind_template_child (widget_class, GtkMediaControls, volume_adjustment); + gtk_widget_class_bind_template_child (widget_class, GtkMediaControls, box); + gtk_widget_class_bind_template_child (widget_class, GtkMediaControls, play_button); + gtk_widget_class_bind_template_child (widget_class, GtkMediaControls, time_box); + gtk_widget_class_bind_template_child (widget_class, GtkMediaControls, time_label); + gtk_widget_class_bind_template_child (widget_class, GtkMediaControls, seek_scale); + gtk_widget_class_bind_template_child (widget_class, GtkMediaControls, duration_label); + + gtk_widget_class_bind_template_callback (widget_class, play_button_clicked); + gtk_widget_class_bind_template_callback (widget_class, time_adjustment_changed); + gtk_widget_class_bind_template_callback (widget_class, volume_adjustment_changed); + + gtk_widget_class_set_css_name (widget_class, I_("controls")); +} + +static void +gtk_media_controls_init (GtkMediaControls *controls) +{ + gtk_widget_init_template (GTK_WIDGET (controls)); + gtk_widget_set_has_window (GTK_WIDGET (controls), FALSE); +} + +/** + * gtk_media_controls_new: + * @stream: (allow-none) (transfer none): a #GtkMediaStream to + * manage or %NULL for none. + * + * Creates a new #GtkMediaControls managing the @stream passed to it. + * + * Returns: a new #GtkMediaControls + **/ +GtkWidget * +gtk_media_controls_new (GtkMediaStream *stream) +{ + return g_object_new (GTK_TYPE_MEDIA_CONTROLS, + "media-stream", stream, + NULL); +} + +/** + * gtk_media_controls_get_media_stream: + * @controls: a #GtkMediaControls + * + * Gets the media stream managed by @controls or %NULL if none. + * + * Returns: (nullable): The media stream managed by @controls + **/ +GtkMediaStream * +gtk_media_controls_get_media_stream (GtkMediaControls *controls) +{ + g_return_val_if_fail (GTK_IS_MEDIA_CONTROLS (controls), NULL); + + return controls->stream; +} + +static void +update_timestamp (GtkMediaControls *controls) +{ + gint64 timestamp, duration; + char *time_string; + + if (controls->stream) + { + timestamp = gtk_media_stream_get_timestamp (controls->stream); + duration = gtk_media_stream_get_duration (controls->stream); + } + else + { + timestamp = 0; + duration = 0; + } + + time_string = totem_time_to_string (timestamp, FALSE, FALSE); + gtk_label_set_text (GTK_LABEL (controls->time_label), time_string); + g_free (time_string); + + if (duration > 0) + { + time_string = totem_time_to_string (duration > timestamp ? duration - timestamp : 0, TRUE, FALSE); + gtk_label_set_text (GTK_LABEL (controls->duration_label), time_string); + g_free (time_string); + + gtk_adjustment_set_value (controls->time_adjustment, (double) timestamp / G_USEC_PER_SEC); + } +} + +static void +update_duration (GtkMediaControls *controls) +{ + gint64 timestamp, duration; + char *time_string; + + if (controls->stream) + { + timestamp = gtk_media_stream_get_timestamp (controls->stream); + duration = gtk_media_stream_get_duration (controls->stream); + } + else + { + timestamp = 0; + duration = 0; + } + + time_string = totem_time_to_string (duration > timestamp ? duration - timestamp : 0, TRUE, FALSE); + gtk_label_set_text (GTK_LABEL (controls->duration_label), time_string); + gtk_widget_set_visible (controls->duration_label, duration > 0); + g_free (time_string); + + gtk_adjustment_set_upper (controls->time_adjustment, + gtk_adjustment_get_page_size (controls->time_adjustment) + + (double) duration / G_USEC_PER_SEC); + gtk_adjustment_set_value (controls->time_adjustment, (double) timestamp / G_USEC_PER_SEC); +} + +static void +update_playing (GtkMediaControls *controls) +{ + gboolean playing; + const char *icon_name; + + if (controls->stream) + playing = gtk_media_stream_get_playing (controls->stream); + else + playing = FALSE; + + if (playing) + icon_name = "media-playback-pause-symbolic"; + else + icon_name = "media-playback-start-symbolic"; + + gtk_button_set_icon_name (GTK_BUTTON (controls->play_button), icon_name); +} + +static void +update_seekable (GtkMediaControls *controls) +{ + gboolean seekable; + + if (controls->stream) + seekable = gtk_media_stream_is_seekable (controls->stream); + else + seekable = FALSE; + + gtk_widget_set_sensitive (controls->seek_scale, seekable); +} + +static void +update_volume (GtkMediaControls *controls) +{ + double volume; + + if (controls->stream == NULL) + volume = 1.0; + else if (gtk_media_stream_get_muted (controls->stream)) + volume = 0.0; + else + volume = gtk_media_stream_get_volume (controls->stream); + + gtk_adjustment_set_value (controls->volume_adjustment, volume); +} + +static void +update_all (GtkMediaControls *controls) +{ + update_timestamp (controls); + update_duration (controls); + update_playing (controls); + update_seekable (controls); + update_volume (controls); +} + +static void +gtk_media_controls_notify_cb (GtkMediaStream *stream, + GParamSpec *pspec, + GtkMediaControls *controls) +{ + if (g_str_equal (pspec->name, "timestamp")) + update_timestamp (controls); + else if (g_str_equal (pspec->name, "duration")) + update_duration (controls); + else if (g_str_equal (pspec->name, "playing")) + update_playing (controls); + else if (g_str_equal (pspec->name, "seekable")) + update_seekable (controls); + else if (g_str_equal (pspec->name, "muted")) + update_volume (controls); + else if (g_str_equal (pspec->name, "volume")) + update_volume (controls); +} + +void +gtk_media_controls_set_media_stream (GtkMediaControls *controls, + GtkMediaStream *stream) +{ + g_return_if_fail (GTK_IS_MEDIA_CONTROLS (controls)); + g_return_if_fail (stream == NULL || GTK_IS_MEDIA_STREAM (stream)); + + if (controls->stream == stream) + return; + + if (controls->stream) + { + g_signal_handlers_disconnect_by_func (controls->stream, + gtk_media_controls_notify_cb, + controls); + g_object_unref (controls->stream); + controls->stream = NULL; + } + + if (stream) + { + controls->stream = g_object_ref (stream); + g_signal_connect (controls->stream, + "notify", + G_CALLBACK (gtk_media_controls_notify_cb), + controls); + } + + update_all (controls); + gtk_widget_set_sensitive (controls->box, stream != NULL); + + g_object_notify_by_pspec (G_OBJECT (controls), properties[PROP_MEDIA_STREAM]); +} + diff --git a/gtk/gtkmediacontrols.h b/gtk/gtkmediacontrols.h new file mode 100644 index 0000000000..be399364ec --- /dev/null +++ b/gtk/gtkmediacontrols.h @@ -0,0 +1,45 @@ +/* + * 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_MEDIA_CONTROLS_H__ +#define __GTK_MEDIA_CONTROLS_H__ + +#include <gtk/gtkmediastream.h> +#include <gtk/gtkwidget.h> + +G_BEGIN_DECLS + +#define GTK_TYPE_MEDIA_CONTROLS (gtk_media_controls_get_type ()) + +GDK_AVAILABLE_IN_ALL +G_DECLARE_FINAL_TYPE (GtkMediaControls, gtk_media_controls, GTK, MEDIA_CONTROLS, GtkWidget) + +GDK_AVAILABLE_IN_ALL +GtkWidget *gtk_media_controls_new (GtkMediaStream *stream); + +GDK_AVAILABLE_IN_ALL +GtkMediaStream *gtk_media_controls_get_media_stream (GtkMediaControls *controls); +GDK_AVAILABLE_IN_ALL +void gtk_media_controls_set_media_stream (GtkMediaControls *controls, + GtkMediaStream *stream); + + +G_END_DECLS + +#endif /* __GTK_MEDIA_CONTROLS_H__ */ diff --git a/gtk/meson.build b/gtk/meson.build index f62afe4c3d..22d849f2e9 100644 --- a/gtk/meson.build +++ b/gtk/meson.build @@ -251,6 +251,7 @@ gtk_public_sources = files([ 'gtkliststore.c', 'gtklockbutton.c', 'gtkmain.c', + 'gtkmediacontrols.c', 'gtkmediafile.c', 'gtkmediastream.c', 'gtkmenu.c', @@ -483,6 +484,7 @@ gtk_public_headers = files([ 'gtkliststore.h', 'gtklockbutton.h', 'gtkmain.h', + 'gtkmediacontrols.h', 'gtkmediafile.h', 'gtkmediastream.h', 'gtkmenu.h', diff --git a/gtk/ui/gtkmediacontrols.ui b/gtk/ui/gtkmediacontrols.ui new file mode 100644 index 0000000000..895259dc50 --- /dev/null +++ b/gtk/ui/gtkmediacontrols.ui @@ -0,0 +1,64 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface domain="gtk40"> + <!-- interface-requires gtk+ 3.6 --> + <object class="GtkAdjustment" id="time_adjustment"> + <property name="upper">10</property> + <property name="step-increment">1</property> + <property name="page-increment">10</property> + <signal name="value-changed" handler="time_adjustment_changed" object="GtkMediaControls" swapped="no"/> + </object> + <object class="GtkAdjustment" id="volume_adjustment"> + <property name="upper">1</property> + <property name="step-increment">0.1</property> + <property name="page-increment">1</property> + <property name="value">1</property> + <signal name="value-changed" handler="volume_adjustment_changed" object="GtkMediaControls" swapped="no"/> + </object> + <template class="GtkMediaControls" parent="GtkWidget"> + <child> + <object class="GtkBox" id="box"> + <property name="hexpand">0</property> + <property name="sensitive">0</property> + <property name="spacing">6</property> + <child> + <object class="GtkButton" id="play_button"> + <property name="can-focus">1</property> + <property name="receives-default">1</property> + <property name="halign">center</property> + <property name="valign">center</property> + <property name="icon-name">media-playback-start-symbolic</property> + <signal name="clicked" handler="play_button_clicked" object="GtkMediaControls" swapped="no"/> + </object> + </child> + <child> + <object class="GtkBox" id="time_box"> + <child> + <object class="GtkLabel" id="time_label"> + <property name="can_focus">False</property> + </object> + </child> + <child> + <object class="GtkScale" id="seek_scale"> + <property name="adjustment">time_adjustment</property> + <property name="can_focus">True</property> + <property name="draw_value">False</property> + <property name="restrict-to-fill-level">False</property> + <property name="hexpand">True</property> + </object> + </child> + <child> + <object class="GtkLabel" id="duration_label"> + <property name="can_focus">False</property> + </object> + </child> + </object> + </child> + <child> + <object class="GtkVolumeButton" id="volume_button"> + <property name="adjustment">volume_adjustment</property> + </object> + </child> + </object> + </child> + </template> +</interface> |